import { defineComponent, ref, watch, computed } from 'vue'; import { useQuasar } from 'quasar'; import { useI18n } from 'vue-i18n'; import { tools } from '@src/store/Modules/tools'; import { toolsext } from '@store/Modules/toolsext'; function toTS(s: string | null): number | null { if (!s) return null; const iso = s.includes('T') ? s : s.replace(' ', 'T'); const ts = Date.parse(iso); return Number.isNaN(ts) ? null : ts; } export default defineComponent({ name: 'CDateTimeStartEnd', emits: [ 'update:startValue', 'update:endValue', 'show', 'savetoclose', 'clear-start', 'clear-end', ], props: { startValue: { type: [String, Date, null] as unknown as () => string | null, default: null }, endValue: { type: [String, Date, null] as unknown as () => string | null, default: null }, startLabel: { type: String, default: 'Inizio' }, endLabel: { type: String, default: 'Fine' }, data_class: { type: String, default: '' }, canEdit: { type: Boolean, default: true }, disable: { type: Boolean, default: false }, bgcolor: { type: String, default: '' }, dense: { type: Boolean, default: false }, view: { type: String as () => 'date-time' | 'date' | 'time', default: 'date-time' }, nullableStart: { type: Boolean, default: true }, nullableEnd: { type: Boolean, default: true }, nullText: { type: String, default: '—' }, calendarIcon: { type: String, default: 'fas fa-calendar-day' }, clockIcon: { type: String, default: 'fas fa-clock' }, clearIcon: { type: String, default: 'fas fa-ban' }, optionalText: { type: String, default: 'opzionale' }, enableEndText: { type: String, default: 'Attiva' }, }, setup(props, { emit }) { const $q = useQuasar(); const { t } = useI18n(); const isMobile = computed(() => $q.screen.lt.sm); // local state const startVal = ref(null); const endVal = ref(null); const startPrev = ref(null); const endPrev = ref(null); // dialog states const startDateDialog = ref(false); const startTimeDialog = ref(false); const endDateDialog = ref(false); const endTimeDialog = ref(false); const startDateDesktopDialog = ref(false); const startTimeDesktopDialog = ref(false); const endDateDesktopDialog = ref(false); const endTimeDesktopDialog = ref(false); const endError = ref(''); // sync props -> local watch( () => props.startValue, (v) => { startVal.value = v ? tools.getstrYYMMDDDateTime(v) : null; }, { immediate: true } ); watch( () => props.endValue, (v) => { endVal.value = v ? tools.getstrYYMMDDDateTime(v) : null; validateRange(); }, { immediate: true } ); function getstrDate(val: string | Date | null) { if (!val) return props.nullText; if (props.view === 'date-time') return tools.getstrDateTime(val); if (props.view === 'date') return tools.getstrDate(val); return tools.getstrTime(val); } function opening(kind: 'start' | 'end') { if (kind === 'start') { startPrev.value = startVal.value; if (!startVal.value) startVal.value = tools.getstrYYMMDDDateTime(new Date()); } else { endPrev.value = endVal.value; if (!endVal.value) endVal.value = startVal.value || tools.getstrYYMMDDDateTime(new Date()); } endError.value = ''; emit('show'); } function cancelStart() { startVal.value = startPrev.value; closeAllStart(); } function cancelEnd() { endVal.value = endPrev.value; endError.value = ''; closeAllEnd(); } function saveStart() { const currStart = startVal.value; const prevStart = startPrev.value; // salva start emit('update:startValue', currStart as unknown as string); emit('savetoclose', { which: 'start', current: currStart, prev: prevStart }); // Se esiste end e l'intervallo è invalido, riallinea fine preservando l'ora originale if (endVal.value) { const tsStart = toTS(currStart); const tsEnd = toTS(endVal.value); if (tsStart !== null && tsEnd !== null && tsEnd < tsStart) { endVal.value = buildEndWithStartDatePreservingEndTime(tsStart, tsEnd); endError.value = ''; emit('update:endValue', endVal.value as unknown as string); emit('savetoclose', { which: 'end', current: endVal.value, prev: endPrev.value }); tools.showNeutralNotif( $q, t('date.rangeFixed') || 'Fine allineata all’inizio mantenendo l’ora originale' ); } } closeAllStart(); } /** * Costruisce una nuova data di fine usando: * - data (Y/M/D) = quella di start * - ora (h:m:s:ms) = quella di end originale * Se il risultato è ancora < start, sposta end al giorno successivo mantenendo la stessa ora. */ function buildEndWithStartDatePreservingEndTime( tsStart: number, tsEnd: number ): string { // Se il componente è solo 'date', non c'è ora da preservare: end = start if (props.view === 'date') { return tools.getstrYYMMDDDateTime(new Date(tsStart)); } const dStart = new Date(tsStart); const dEnd = new Date(tsEnd); // Ricostruisci end: data = start, ora = end originale const newEnd = new Date( dStart.getFullYear(), dStart.getMonth(), dStart.getDate(), dEnd.getHours(), dEnd.getMinutes(), dEnd.getSeconds(), dEnd.getMilliseconds() ); // Se così è ancora < start (es. end 08:00, start 10:00 stesso giorno) → bump di 1 giorno if (newEnd.getTime() < tsStart) { newEnd.setDate(newEnd.getDate() + 1); } return tools.getstrYYMMDDDateTime(newEnd); } function saveEnd() { if (!validateRange(true)) { // blocco il salvataggio se ancora invalida return; } emit('update:endValue', endVal.value as unknown as string); emit('savetoclose', { which: 'end', current: endVal.value, prev: endPrev.value }); closeAllEnd(); } function onStartChange(v: string | Date) { startVal.value = typeof v === 'string' ? v : tools.getstrYYMMDDDateTime(v); } function onEndChange(v: string | Date) { endVal.value = typeof v === 'string' ? v : tools.getstrYYMMDDDateTime(v); validateRange(); } function clearStart() { startVal.value = null; emit('update:startValue', null as unknown as string); emit('savetoclose', { which: 'start', current: null, prev: startPrev.value }); // se non c'è inizio, rimuovo anche fine per coerenza if (endVal.value) { endVal.value = null; emit('update:endValue', null as unknown as string); emit('savetoclose', { which: 'end', current: null, prev: endPrev.value }); } emit('clear-start'); tools.showNeutralNotif($q, t('common.cleared') || 'Valore rimosso'); closeAllStart(); } function clearEnd() { endVal.value = null; emit('update:endValue', null as unknown as string); emit('clear-end'); tools.showNeutralNotif($q, t('common.cleared') || 'Valore rimosso'); endError.value = ''; closeAllEnd(); } function enableEnd() { endVal.value = startVal.value || tools.getstrYYMMDDDateTime(new Date()); validateRange(true); emit('update:endValue', endVal.value as unknown as string); } function openStartDate() { opening('start'); if (isMobile.value) startDateDialog.value = true; else startDateDesktopDialog.value = true; } function openStartTime() { opening('start'); if (isMobile.value) startTimeDialog.value = true; else startTimeDesktopDialog.value = true; } function openEndDate() { opening('end'); if (isMobile.value) endDateDialog.value = true; else endDateDesktopDialog.value = true; } function openEndTime() { opening('end'); if (isMobile.value) endTimeDialog.value = true; else endTimeDesktopDialog.value = true; } function closeAllStart() { startDateDialog.value = false; startTimeDialog.value = false; startDateDesktopDialog.value = false; startTimeDesktopDialog.value = false; } function closeAllEnd() { endDateDialog.value = false; endTimeDialog.value = false; endDateDesktopDialog.value = false; endTimeDesktopDialog.value = false; } function validateRange(showMsg = false): boolean { endError.value = ''; const tsStart = toTS(startVal.value); const tsEnd = toTS(endVal.value); if (tsStart != null && tsEnd != null && tsEnd < tsStart) { endError.value = t('date.invalidRange') || 'La data di fine non può precedere l’inizio'; if (showMsg) tools.showNeutralNotif($q, endError.value); return false; } return true; } const confirmLabelStartDate = computed(() => isMobile.value ? t('common.set') || 'Imposta' : `Imposta a ${tools.getstrDateLong(startVal.value)}` ); const confirmLabelStartTime = computed(() => isMobile.value ? t('common.set') || 'Imposta' : `Imposta a ${tools.getstrTime(startVal.value)}` ); const confirmLabelEndDate = computed(() => isMobile.value ? t('common.set') || 'Imposta' : `Imposta a ${tools.getstrDateLong(endVal.value)}` ); const confirmLabelEndTime = computed(() => isMobile.value ? t('common.set') || 'Imposta' : `Imposta a ${tools.getstrTime(endVal.value)}` ); const canEditEnd = computed(() => !!endVal.value || !props.nullableEnd); return { toolsext, tools, // values & labels startVal, endVal, startLabel: computed(() => props.startLabel), endLabel: computed(() => props.endLabel), // ui flags dense: props.dense, bgcolor: props.bgcolor, disable: props.disable, data_class: props.data_class, canEdit: props.canEdit, nullableStart: props.nullableStart, nullableEnd: props.nullableEnd, nullText: props.nullText, optionalText: props.optionalText, enableEndText: props.enableEndText, calendarIcon: props.calendarIcon, clockIcon: props.clockIcon, clearIcon: props.clearIcon, // dialogs startDateDialog, startTimeDialog, endDateDialog, endTimeDialog, startDateDesktopDialog, startTimeDesktopDialog, endDateDesktopDialog, endTimeDesktopDialog, // methods getstrDate, openStartDate, openStartTime, openEndDate, openEndTime, cancelStart, cancelEnd, saveStart, saveEnd, onStartChange, onEndChange, clearStart, clearEnd, enableEnd, // helpers confirmLabelStartDate, confirmLabelStartTime, confirmLabelEndDate, confirmLabelEndTime, isMobile, canEditEnd, endError, }; }, });