Teren-app/resources/js/Components/DatePicker.vue
2025-12-01 19:30:53 +01:00

153 lines
3.7 KiB
Vue

<script setup>
import { computed, ref, useAttrs } from "vue";
import { Button } from "@/Components/ui/button";
import { Calendar } from "@/Components/ui/calendar";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/Components/ui/popover";
import { cn } from "@/lib/utils";
import { CalendarIcon } from "lucide-vue-next";
import { format } from "date-fns";
import { sl } from "date-fns/locale";
import { CalendarDate, parseDate } from "@internationalized/date";
defineOptions({ inheritAttrs: false });
const props = defineProps({
modelValue: {
type: [Date, String, null],
default: null,
},
placeholder: {
type: String,
default: "Izberi datum",
},
format: {
type: String,
default: "dd.MM.yyyy",
},
disabled: {
type: Boolean,
default: false,
},
id: {
type: String,
default: undefined,
},
error: {
type: [String, Array],
default: undefined,
},
});
const emit = defineEmits(["update:modelValue"]);
const attrs = useAttrs();
const forwardedAttrs = computed(() => {
const { class: _class, id: _id, ...rest } = attrs;
return rest;
});
const controlId = computed(() => attrs.id ?? props.id);
// Convert string/Date to CalendarDate
const toCalendarDate = (value) => {
if (!value) return null;
let dateObj;
if (value instanceof Date) {
dateObj = value;
} else if (typeof value === "string") {
// Handle YYYY-MM-DD format
if (/^\d{4}-\d{2}-\d{2}$/.test(value)) {
try {
const [year, month, day] = value.split("-").map(Number);
return new CalendarDate(year, month, day);
} catch {
dateObj = new Date(value);
}
} else {
dateObj = new Date(value);
}
} else {
return null;
}
if (dateObj && !isNaN(dateObj.getTime())) {
return new CalendarDate(
dateObj.getFullYear(),
dateObj.getMonth() + 1,
dateObj.getDate()
);
}
return null;
};
// Convert CalendarDate to ISO string (YYYY-MM-DD)
const fromCalendarDate = (calendarDate) => {
if (!calendarDate) return null;
return `${String(calendarDate.year).padStart(4, "0")}-${String(calendarDate.month).padStart(2, "0")}-${String(calendarDate.day).padStart(2, "0")}`;
};
const calendarDate = computed({
get: () => toCalendarDate(props.modelValue),
set: (value) => {
const isoString = fromCalendarDate(value);
emit("update:modelValue", isoString);
},
});
// Format for display
const formattedDate = computed(() => {
if (!calendarDate.value) return props.placeholder;
try {
const dateObj = new Date(
calendarDate.value.year,
calendarDate.value.month - 1,
calendarDate.value.day
);
const formatMap = {
"dd.MM.yyyy": "dd.MM.yyyy",
"yyyy-MM-dd": "yyyy-MM-dd",
};
const dateFormat = formatMap[props.format] || "dd.MM.yyyy";
return format(dateObj, dateFormat, { locale: sl });
} catch {
return props.placeholder;
}
});
const open = ref(false);
</script>
<template>
<Popover v-model:open="open">
<PopoverTrigger as-child>
<Button
v-bind="forwardedAttrs"
:id="controlId"
variant="outline"
:class="
cn(
'w-full justify-start text-left font-normal',
!calendarDate && 'text-muted-foreground',
error && 'border-red-500 focus:border-red-500 focus:ring-red-500',
attrs.class
)
"
:disabled="disabled"
>
<CalendarIcon class="mr-2 h-4 w-4" />
{{ formattedDate }}
</Button>
</PopoverTrigger>
<PopoverContent class="w-auto p-0" align="start">
<Calendar v-model="calendarDate" :disabled="disabled" />
</PopoverContent>
</Popover>
<p v-if="error" class="mt-1 text-sm text-red-600">
{{ Array.isArray(error) ? error[0] : error }}
</p>
</template>