119 lines
3.3 KiB
Vue
119 lines
3.3 KiB
Vue
<script setup>
|
|
import { ref, watch, computed, onBeforeUnmount } from 'vue';
|
|
import Input from '@/Components/Dashboard/Form/Input.vue';
|
|
|
|
const props = defineProps({
|
|
modelValue: {
|
|
type: Object,
|
|
default: () => ({ start: '', end: '' })
|
|
},
|
|
required: Boolean,
|
|
onError: String,
|
|
idStart: { type: String, default: 'startDate' },
|
|
idEnd: { type: String, default: 'endDate' },
|
|
titleStart: { type: String, default: 'Fecha inicio' },
|
|
titleEnd: { type: String, default: 'Fecha fin' },
|
|
presets: {
|
|
type: Boolean,
|
|
default: true
|
|
},
|
|
debounceMs: {
|
|
type: Number,
|
|
default: 350
|
|
}
|
|
});
|
|
|
|
const emit = defineEmits(['update:modelValue', 'invalid']);
|
|
|
|
const local = ref({ start: props.modelValue.start, end: props.modelValue.end });
|
|
let debounceId = null;
|
|
|
|
// --- utilidades fechas ---
|
|
const fmt = (d) => new Date(d).toISOString().slice(0,10);
|
|
const today = fmt(new Date());
|
|
const firstDayOfMonth = fmt(new Date(new Date().getFullYear(), new Date().getMonth(), 1));
|
|
const daysAgo = (n) => {
|
|
const d = new Date(); d.setDate(d.getDate()-n);
|
|
return fmt(d);
|
|
};
|
|
|
|
// --- validación ---
|
|
const isValid = computed(() => {
|
|
const { start, end } = local.value;
|
|
if (!start || !end) return !props.required; // si no son obligatorias
|
|
return new Date(start) <= new Date(end);
|
|
});
|
|
|
|
// sincroniza si el padre cambia externamente
|
|
watch(() => props.modelValue, (nv) => {
|
|
if (nv?.start !== local.value.start || nv?.end !== local.value.end) {
|
|
local.value = { start: nv?.start || '', end: nv?.end || '' };
|
|
}
|
|
}, { deep: true });
|
|
|
|
// emite con debounce solo si es válido
|
|
const emitChange = () => {
|
|
clearTimeout(debounceId);
|
|
debounceId = setTimeout(() => {
|
|
if (isValid.value) {
|
|
emit('update:modelValue', { ...local.value });
|
|
} else {
|
|
emit('invalid', { ...local.value });
|
|
}
|
|
}, props.debounceMs);
|
|
};
|
|
|
|
watch(() => local.value.start, emitChange);
|
|
watch(() => local.value.end, emitChange);
|
|
|
|
// presets
|
|
const applyPreset = (type) => {
|
|
if (type === 'today') {
|
|
local.value = { start: today, end: today };
|
|
} else if (type === 'last7') {
|
|
local.value = { start: daysAgo(6), end: today }; // 7 días incluyendo hoy
|
|
} else if (type === 'month') {
|
|
local.value = { start: firstDayOfMonth, end: today };
|
|
}
|
|
emitChange();
|
|
};
|
|
|
|
onBeforeUnmount(() => clearTimeout(debounceId));
|
|
</script>
|
|
|
|
<template>
|
|
<div class="flex flex-wrap items-end gap-3">
|
|
<div class="flex space-x-4">
|
|
<Input
|
|
:id="idStart"
|
|
type="date"
|
|
:title="titleStart"
|
|
v-model="local.start"
|
|
:required="required"
|
|
:onError="onError"
|
|
/>
|
|
<Input
|
|
:id="idEnd"
|
|
type="date"
|
|
:title="titleEnd"
|
|
v-model="local.end"
|
|
:required="required"
|
|
:onError="onError"
|
|
/>
|
|
</div>
|
|
|
|
<div v-if="presets" class="flex gap-2">
|
|
<button type="button" class="px-3 py-2 rounded bg-gray-200 hover:bg-gray-300"
|
|
@click="applyPreset('today')">Hoy</button>
|
|
<button type="button" class="px-3 py-2 rounded bg-gray-200 hover:bg-gray-300"
|
|
@click="applyPreset('last7')">Últimos 7 días</button>
|
|
<button type="button" class="px-3 py-2 rounded bg-gray-200 hover:bg-gray-300"
|
|
@click="applyPreset('month')">Mes actual</button>
|
|
</div>
|
|
|
|
<p v-if="!isValid" class="text-sm text-red-600 w-full">
|
|
La fecha fin debe ser mayor o igual a la fecha inicio.
|
|
</p>
|
|
</div>
|
|
</template>
|