364 lines
11 KiB
TypeScript
364 lines
11 KiB
TypeScript
|
|
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, null] as unknown as () => string | null, default: null },
|
|||
|
|
endValue: { type: [String, 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<string | null>(null);
|
|||
|
|
const endVal = ref<string | null>(null);
|
|||
|
|
const startPrev = ref<string | null>(null);
|
|||
|
|
const endPrev = ref<string | null>(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<string>('');
|
|||
|
|
|
|||
|
|
// 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,
|
|||
|
|
};
|
|||
|
|
},
|
|||
|
|
});
|