178 lines
4.2 KiB
Vue
178 lines
4.2 KiB
Vue
<script setup>
|
|
import { CalendarIcon, XIcon } from "lucide-vue-next";
|
|
import { computed, ref } from "vue";
|
|
import { cn } from "@/lib/utils";
|
|
import { Button } from "@/Components/ui/button";
|
|
import { Popover, PopoverContent, PopoverTrigger } from "@/Components/ui/popover";
|
|
import { RangeCalendar } from "@/Components/ui/range-calendar";
|
|
import {
|
|
DateFormatter,
|
|
getLocalTimeZone,
|
|
today,
|
|
parseDate,
|
|
CalendarDate,
|
|
} from "@internationalized/date";
|
|
|
|
const props = defineProps({
|
|
modelValue: {
|
|
type: Object,
|
|
default: () => ({ start: null, end: null }),
|
|
},
|
|
placeholder: {
|
|
type: String,
|
|
default: "Izberi datumski obseg",
|
|
},
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
buttonClass: {
|
|
type: String,
|
|
default: "w-[280px]",
|
|
},
|
|
locale: {
|
|
type: String,
|
|
default: "sl-SI",
|
|
},
|
|
numberOfMonths: {
|
|
type: Number,
|
|
default: 2,
|
|
},
|
|
minValue: {
|
|
type: Object,
|
|
default: undefined,
|
|
},
|
|
maxValue: {
|
|
type: Object,
|
|
default: undefined,
|
|
},
|
|
clearable: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
});
|
|
|
|
const emit = defineEmits(["update:modelValue"]);
|
|
|
|
const open = ref(false);
|
|
|
|
const df = new DateFormatter(props.locale, {
|
|
dateStyle: "medium",
|
|
});
|
|
|
|
// Check if there's a selected value
|
|
const hasValue = computed(() => {
|
|
const val = props.modelValue;
|
|
return val?.start || val?.end;
|
|
});
|
|
|
|
// Convert string dates to CalendarDate objects for the calendar
|
|
const calendarValue = computed({
|
|
get() {
|
|
const val = props.modelValue;
|
|
if (!val) return undefined;
|
|
|
|
let start = null;
|
|
let end = null;
|
|
|
|
if (val.start) {
|
|
if (typeof val.start === "string") {
|
|
start = parseDate(val.start);
|
|
} else if (val.start instanceof CalendarDate) {
|
|
start = val.start;
|
|
}
|
|
}
|
|
|
|
if (val.end) {
|
|
if (typeof val.end === "string") {
|
|
end = parseDate(val.end);
|
|
} else if (val.end instanceof CalendarDate) {
|
|
end = val.end;
|
|
}
|
|
}
|
|
|
|
if (!start && !end) return undefined;
|
|
return { start, end };
|
|
},
|
|
set(newValue) {
|
|
if (!newValue) {
|
|
emit("update:modelValue", { start: null, end: null });
|
|
return;
|
|
}
|
|
|
|
// Convert CalendarDate to ISO string (YYYY-MM-DD) for easier handling
|
|
const result = {
|
|
start: newValue.start ? newValue.start.toString() : null,
|
|
end: newValue.end ? newValue.end.toString() : null,
|
|
};
|
|
emit("update:modelValue", result);
|
|
|
|
// Close popover when both dates are selected
|
|
if (result.start && result.end) {
|
|
open.value = false;
|
|
}
|
|
},
|
|
});
|
|
|
|
const displayText = computed(() => {
|
|
const val = calendarValue.value;
|
|
if (!val?.start) return props.placeholder;
|
|
|
|
const startFormatted = df.format(val.start.toDate(getLocalTimeZone()));
|
|
if (!val.end) return startFormatted;
|
|
|
|
const endFormatted = df.format(val.end.toDate(getLocalTimeZone()));
|
|
return `${startFormatted} - ${endFormatted}`;
|
|
});
|
|
|
|
function clearValue(event) {
|
|
event.stopPropagation();
|
|
emit("update:modelValue", { start: null, end: null });
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<Popover v-model:open="open">
|
|
<PopoverTrigger as-child>
|
|
<Button
|
|
variant="outline"
|
|
:disabled="disabled"
|
|
:class="
|
|
cn(
|
|
'justify-start text-left font-normal',
|
|
!calendarValue?.start && 'text-muted-foreground',
|
|
buttonClass
|
|
)
|
|
"
|
|
>
|
|
<CalendarIcon class="mr-2 h-4 w-4 shrink-0" />
|
|
<span class="truncate flex-1">{{ displayText }}</span>
|
|
<span
|
|
v-if="clearable && hasValue && !disabled"
|
|
class="ml-2 shrink-0 opacity-50 hover:opacity-100 cursor-pointer"
|
|
@click.stop.prevent="clearValue"
|
|
>
|
|
<XIcon class="h-4 w-4" />
|
|
</span>
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent class="w-auto p-0" align="start">
|
|
<RangeCalendar
|
|
v-model="calendarValue"
|
|
:locale="locale"
|
|
:number-of-months="numberOfMonths"
|
|
:min-value="minValue"
|
|
:max-value="maxValue"
|
|
initial-focus
|
|
@update:start-value="
|
|
(startDate) => {
|
|
if (calendarValue?.start?.toString() !== startDate?.toString()) {
|
|
calendarValue = { start: startDate, end: undefined };
|
|
}
|
|
}
|
|
"
|
|
/>
|
|
</PopoverContent>
|
|
</Popover>
|
|
</template>
|