mejoras ui/Ux y refactorizacion del selector rapido de dateRange

This commit is contained in:
2025-09-30 15:47:03 -06:00
parent 270dc5f0bd
commit aeaf30d1d1
9 changed files with 420 additions and 240 deletions

View File

@@ -0,0 +1,87 @@
<template>
<div class="flex flex-col md:flex-row gap-4">
<!-- Selector de clientes -->
<div class="flex-1">
<label class="text-xs text-[var(--brand-text-muted)] block mb-1">Seleccionar clientes</label>
<USelectMenu
v-model="selectedClientes"
:options="clienteOptions"
multiple
searchable
searchable-placeholder="Buscar cliente..."
placeholder="Todos los clientes"
value-attribute="id"
option-attribute="name"
:ui="{
wrapper: 'w-full',
input: 'w-full'
}"
>
<template #label>
<span v-if="selectedClientes.length === 0">Todos los clientes</span>
<span v-else-if="selectedClientes.length === 1">
{{ clienteOptions.find(c => c.id === selectedClientes[0])?.name }}
</span>
<span v-else>
{{ selectedClientes.length }} clientes seleccionados
</span>
</template>
</USelectMenu>
</div>
<div class="flex items-end">
<UButton
:ui="{ base: 'bg-[#c08040] text-[#1b1209] border border-[#d99a56] hover:bg-[#d99a56] hover:border-[#f0c07c]' }"
@click="clearSelection"
size="sm"
>
Limpiar
</UButton>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, watch } from 'vue'
interface Cliente {
id: number
name: string
cedula?: number
ubicacion?: string
telefono?: string
}
interface Props {
clientes: Cliente[]
selectedIds: number[]
}
const props = defineProps<Props>()
const emit = defineEmits<{
'update:selectedIds': [value: number[]]
}>()
// Reactive reference para el v-model
const selectedClientes = computed({
get: () => props.selectedIds,
set: (value) => emit('update:selectedIds', value)
})
const clienteOptions = computed(() => {
return props.clientes
.filter(c => c.name && c.name.trim() !== '')
.sort((a, b) => a.name.localeCompare(b.name))
})
function clearSelection() {
emit('update:selectedIds', [])
console.log('Cliente selection cleared')
}
// Watch para logging
watch(() => props.selectedIds, (newIds) => {
console.log('Selected cliente IDs:', newIds)
}, { deep: true })
</script>

View File

@@ -0,0 +1,197 @@
<template>
<div class="flex flex-col md:flex-row gap-4">
<!-- Presets -->
<div class="flex-1">
<label class="text-xs text-[var(--brand-text-muted)] block mb-1">Rango rápido</label>
<UFieldGroup>
<UButton
color="neutral"
variant="subtle"
:label="currentPresetLabel"
class="flex-1"
disabled
/>
<UDropdownMenu :items="dropdownItems" :popper="{ placement: 'bottom-end' }">
<UButton
color="neutral"
variant="outline"
icon="i-lucide-chevron-down"
square
/>
</UDropdownMenu>
</UFieldGroup>
</div>
<!-- Fechas manuales -->
<div class="grid grid-cols-2 gap-4 flex-1">
<div>
<label class="text-xs text-[var(--brand-text-muted)]">Fecha desde</label>
<UInput :model-value="fechaDesde" type="date" @input="onManualDateChange" />
</div>
<div>
<label class="text-xs text-[var(--brand-text-muted)]">Fecha hasta</label>
<UInput :model-value="fechaHasta" type="date" @input="onManualDateChange" />
</div>
</div>
<div class="flex items-end">
<UButton
:ui="{ base: 'bg-[#c08040] text-[#1b1209] border border-[#d99a56] hover:bg-[#d99a56] hover:border-[#f0c07c]' }"
@click="clearPreset"
size="sm"
>
Limpiar
</UButton>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, watch, onMounted } from 'vue'
type PresetValue =
| '' | 'custom' | 'hoy' | 'semana' | 'mes' | 'ytd'
| 'cosecha-20-21' | 'cosecha-21-22' | 'cosecha-22-23'
| 'cosecha-23-24' | 'cosecha-24-25' | 'cosecha-25-26'
interface Props {
selectedPreset: PresetValue
fechaDesde: string | null
fechaHasta: string | null
}
const props = defineProps<Props>()
const emit = defineEmits<{
'update:selectedPreset': [value: PresetValue]
'update:fechaDesde': [value: string | null]
'update:fechaHasta': [value: string | null]
}>()
// Definir funciones antes del watch
const toLocalDateStr = (d: Date) => {
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
return `${y}-${m}-${day}`
}
function selectPreset(preset: PresetValue) {
console.log('selectPreset called with:', preset)
emit('update:selectedPreset', preset)
if (preset === '' || preset === 'custom') {
emit('update:fechaDesde', null)
emit('update:fechaHasta', null)
console.log('Cleared dates')
return
}
const now = new Date()
const set = (sd: string, ed: string) => {
emit('update:fechaDesde', sd)
emit('update:fechaHasta', ed)
console.log('Set dates:', sd, ed)
}
switch (preset) {
case 'hoy': set(toLocalDateStr(now), toLocalDateStr(now)); break
case 'semana': {
const d = new Date(now)
const day = d.getDay() || 7
d.setDate(d.getDate() - (day - 1)) // lunes
set(toLocalDateStr(d), toLocalDateStr(now))
break
}
case 'mes': set(toLocalDateStr(new Date(now.getFullYear(), now.getMonth(), 1)), toLocalDateStr(now)); break
case 'ytd': set(toLocalDateStr(new Date(now.getFullYear(), 0, 1)), toLocalDateStr(now)); break
case 'cosecha-20-21': set('2020-09-25', '2021-09-24'); break
case 'cosecha-21-22': set('2021-09-25', '2022-09-24'); break
case 'cosecha-22-23': set('2022-09-25', '2023-09-24'); break
case 'cosecha-23-24': set('2023-09-25', '2024-09-24'); break
case 'cosecha-24-25': set('2024-09-25', '2025-09-09'); break
case 'cosecha-25-26': set('2025-09-10', toLocalDateStr(now)); break
}
}
function onManualDateChange(event: Event) {
const target = event.target as HTMLInputElement
const value = target.value
// Si el usuario modifica las fechas manualmente, cambiar a "Personalizado"
emit('update:selectedPreset', 'custom')
// Actualizar la fecha correspondiente según el input
if (target.type === 'date') {
const label = target.labels?.[0]?.textContent
if (label?.includes('desde')) {
emit('update:fechaDesde', value)
} else if (label?.includes('hasta')) {
emit('update:fechaHasta', value)
}
}
console.log('Manual date change, preset set to custom')
}
function clearPreset() {
emit('update:selectedPreset', '')
emit('update:fechaDesde', null)
emit('update:fechaHasta', null)
console.log('Preset cleared')
}
// Watch para aplicar el preset cuando cambia (incluyendo el valor inicial)
watch(() => props.selectedPreset, (newPreset) => {
if (newPreset && newPreset !== 'custom' && newPreset !== '') {
selectPreset(newPreset)
}
}, { immediate: true })
const currentPresetLabel = computed(() => {
switch (props.selectedPreset) {
case '': return 'Sin filtro'
case 'custom': return 'Personalizado'
case 'hoy': return 'Hoy'
case 'semana': return 'Esta Semana'
case 'mes': return 'Este Mes'
case 'ytd': return 'YTD'
case 'cosecha-20-21': return 'Cosecha 20-21'
case 'cosecha-21-22': return 'Cosecha 21-22'
case 'cosecha-22-23': return 'Cosecha 22-23'
case 'cosecha-23-24': return 'Cosecha 23-24'
case 'cosecha-24-25': return 'Cosecha 24-25'
case 'cosecha-25-26': return 'Cosecha 25-26'
default: return 'Seleccionar rango'
}
})
const dropdownItems = [
{
label: 'Sin filtro',
onSelect: () => {
selectPreset('')
}
},
{
label: 'Rápidos',
children: [
{ label: 'Hoy', onSelect: () => { selectPreset('hoy') } },
{ label: 'Esta Semana', onSelect: () => { selectPreset('semana') } },
{ label: 'Este Mes', onSelect: () => { selectPreset('mes') } },
{ label: 'YTD', onSelect: () => { selectPreset('ytd') } }
]
},
{
label: 'Cosechas',
children: [
{ label: 'Cosecha 20-21 (25 Sep 2020)', onSelect: () => { selectPreset('cosecha-20-21') } },
{ label: 'Cosecha 21-22 (25 Sep 2021)', onSelect: () => { selectPreset('cosecha-21-22') } },
{ label: 'Cosecha 22-23 (25 Sep 2022)', onSelect: () => { selectPreset('cosecha-22-23') } },
{ label: 'Cosecha 23-24 (25 Sep 2023)', onSelect: () => { selectPreset('cosecha-23-24') } },
{ label: 'Cosecha 24-25 (25 Sep 2024)', onSelect: () => { selectPreset('cosecha-24-25') } },
{ label: 'Cosecha 25-26 (10 Sep 2025 → hoy)', onSelect: () => { selectPreset('cosecha-25-26') } }
]
}
]
</script>

View File

@@ -5,25 +5,39 @@
</template>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<MetricCard
label="Total qq Secos por Vender"
:value="formatNumber(metrics.totalQqSecoPorVender.value)"
unit="qq"
variant="info"
/>
<MetricCard
label="Precio de Venta Promedio por qq"
:value="formatCurrency(metrics.precioVentaPromedioPorQq.value)"
/>
<MetricCard
label="Precio de Compra Promedio por qq"
:value="formatCurrency(metrics.precioCompraPromedioPorQq.value)"
/>
<MetricCard
label="Margen de Ganancia por qq"
:value="formatCurrency(metrics.margenGananciaPorQq.value)"
:variant="metrics.margenGananciaPorQq.value > 0 ? 'success' : 'danger'"
/>
<div class="p-4 rounded-lg border transition-all bg-[#1c140c] border-purple-900/40 text-purple-300">
<div class="flex flex-col">
<span class="text-xs uppercase tracking-wide opacity-80 mb-1">Total qq Secos por Vender</span>
<div class="flex items-baseline gap-2">
<span class="text-2xl font-bold">{{ formatNumber(metrics.totalQqSecoPorVender.value) }}</span>
<span class="text-sm font-bold opacity-70">qq</span>
</div>
</div>
</div>
<div class="p-4 rounded-lg border transition-all bg-[#1c140c] border-purple-800/40 text-purple-400">
<div class="flex flex-col">
<span class="text-xs uppercase tracking-wide opacity-80 mb-1">Precio de Venta Promedio por qq</span>
<div class="flex items-baseline gap-2">
<span class="text-2xl font-bold">{{ formatCurrency(metrics.precioVentaPromedioPorQq.value) }}</span>
</div>
</div>
</div>
<div class="p-4 rounded-lg border transition-all bg-[#1c140c] border-purple-700/40 text-purple-500">
<div class="flex flex-col">
<span class="text-xs uppercase tracking-wide opacity-80 mb-1">Precio de Compra Promedio por qq</span>
<div class="flex items-baseline gap-2">
<span class="text-2xl font-bold">{{ formatCurrency(metrics.precioCompraPromedioPorQq.value) }}</span>
</div>
</div>
</div>
<div class="p-4 rounded-lg border transition-all bg-[#1c140c] text-purple-600" :class="metrics.margenGananciaPorQq.value > 0 ? 'border-purple-600/40' : 'border-red-600/40'">
<div class="flex flex-col">
<span class="text-xs uppercase tracking-wide opacity-80 mb-1">Margen de Ganancia por qq</span>
<div class="flex items-baseline gap-2">
<span class="text-2xl font-bold">{{ formatCurrency(metrics.margenGananciaPorQq.value) }}</span>
</div>
</div>
</div>
</div>
</UCard>
</template>

View File

@@ -11,7 +11,7 @@
Totales Generales (Pagado + Pendiente)
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div class="p-4 rounded-lg border transition-all bg-[#1c140c] border-[#c08040] text-[var(--brand-primary)]">
<div class="p-4 rounded-lg border transition-all bg-[#1c140c] border-red-600/40 text-red-400">
<div class="flex items-center justify-between mb-1">
<span class="text-xs uppercase tracking-wide opacity-80">Total Uva Ingresada</span>
<UButton
@@ -31,13 +31,13 @@
label="Total qq Seco Mojado"
:value="formatNumber(metrics.totalQqSecoMojadoIngresado.value)"
unit="qq"
variant="primary"
variant="info"
/>
<MetricCard
label="Total qq Seco Oreado"
:value="formatNumber(metrics.totalQqSecoOreadoIngresado.value)"
unit="qq"
variant="primary"
variant="warning"
/>
<MetricCard
label="Total qq Seco Ingresado"
@@ -54,7 +54,7 @@
Solo Pagados
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div class="p-4 rounded-lg border transition-all bg-[#1c140c] border-green-600/30 text-green-400">
<div class="p-4 rounded-lg border transition-all bg-[#1c140c] border-red-600/40 text-red-400">
<div class="flex items-center justify-between mb-1">
<span class="text-xs uppercase tracking-wide opacity-80">Total Uva Pagada</span>
<UButton
@@ -74,19 +74,19 @@
label="Total qq Seco Mojado Pagado"
:value="formatNumber(metrics.totalQqSecoMojadoPagado.value)"
unit="qq"
variant="success"
variant="info"
/>
<MetricCard
label="Total qq Seco Oreado Pagado"
:value="formatNumber(metrics.totalQqSecoOreadoPagado.value)"
unit="qq"
variant="success"
variant="warning"
/>
<MetricCard
label="Total qq Seco Comprado"
:value="formatNumber(metrics.totalQqSecoComprado.value)"
unit="qq"
variant="success"
variant="primary"
/>
</div>
</div>
@@ -97,7 +97,7 @@
Inventario en Depósito
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div class="p-4 rounded-lg border transition-all bg-[#1c140c] border-yellow-600/30 text-yellow-400">
<div class="p-4 rounded-lg border transition-all bg-[#1c140c] border-red-600/40 text-red-400">
<div class="flex items-center justify-between mb-1">
<span class="text-xs uppercase tracking-wide opacity-80">Total Uva en Depósito</span>
<UButton
@@ -117,7 +117,7 @@
label="Total qq Seco Mojado en Depósito"
:value="formatNumber(metrics.totalQqMojadoDeposito.value)"
unit="qq"
variant="warning"
variant="info"
/>
<MetricCard
label="Total qq Seco Oreado en Depósito"
@@ -129,7 +129,7 @@
label="Total qq Seco en Depósito"
:value="formatNumber(metrics.totalQqSecoDeposito.value)"
unit="qq"
variant="warning"
variant="primary"
/>
</div>
</div>
@@ -147,7 +147,7 @@ defineProps<{
type UnitDisplay = 'lb' | 'qq' | 'both'
const unitDisplay = ref<UnitDisplay>('both')
const unitDisplay = ref<UnitDisplay>('lb')
const unitDisplayLabel = computed(() => {
switch (unitDisplay.value) {

View File

@@ -14,17 +14,17 @@
<MetricCard
label="Inversión en Uva"
:value="formatCurrency(metrics.inversionUva.value)"
variant="success"
/>
<MetricCard
label="Inversión en Oreado"
:value="formatCurrency(metrics.inversionOreado.value)"
variant="success"
variant="danger"
/>
<MetricCard
label="Inversión en Mojado"
:value="formatCurrency(metrics.inversionMojado.value)"
variant="success"
variant="info"
/>
<MetricCard
label="Inversión en Oreado"
:value="formatCurrency(metrics.inversionOreado.value)"
variant="warning"
/>
<MetricCard
label="Total Invertido"
@@ -40,7 +40,7 @@
Precios Promedio Ponderados
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div class="p-4 rounded-lg border transition-all bg-[#1c140c] border-[#3a2a16] text-[var(--brand-text)]">
<div class="p-4 rounded-lg border transition-all bg-[#1c140c] border-red-600/40 text-red-400">
<div class="flex items-center justify-between mb-1">
<span class="text-xs uppercase tracking-wide opacity-80">Precio Promedio Ponderado Uva</span>
<UButton
@@ -56,20 +56,23 @@
<span class="text-sm font-bold opacity-70 ml-2">{{ formatPrecioUvaUnit() }}</span>
</div>
</div>
<MetricCard
label="Precio Promedio Ponderado Oreado"
:value="formatNumber(metrics.precioPromedioOreadoPorQq.value)"
unit="L./qq"
/>
<MetricCard
label="Precio Promedio Ponderado Mojado"
:value="formatNumber(metrics.precioPromedioMojadoPorQq.value)"
unit="L./qq"
variant="info"
/>
<MetricCard
label="Precio Promedio Ponderado Oreado"
:value="formatNumber(metrics.precioPromedioOreadoPorQq.value)"
unit="L./qq"
variant="warning"
/>
<MetricCard
label="Precio Promedio Ponderado qq Seco"
:value="formatNumber(metrics.precioPromedioQqSeco.value)"
unit="L./qq"
variant="primary"
/>
</div>
</div>
@@ -80,25 +83,25 @@
Inversión Restante a Realizar
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<MetricCard
label="Inversión Restante Uva"
:value="formatCurrency(metrics.inversionRestanteUva.value)"
variant="danger"
/>
<MetricCard
label="Inversión Restante Mojado"
:value="formatCurrency(metrics.inversionRestanteMojado.value)"
variant="info"
/>
<MetricCard
label="Inversión Restante Oreado"
:value="formatCurrency(metrics.inversionRestanteOreado.value)"
variant="warning"
/>
<MetricCard
label="Inversión Restante Mojado"
:value="formatCurrency(metrics.inversionRestanteMojado.value)"
variant="warning"
/>
<MetricCard
label="Inversión Restante Uva"
:value="formatCurrency(metrics.inversionRestanteUva.value)"
variant="warning"
/>
<MetricCard
label="Inversión Restante Esperada"
:value="formatCurrency(metrics.inversionRestanteEsperada.value)"
variant="danger"
variant="primary"
/>
</div>
</div>
@@ -116,7 +119,7 @@ const props = defineProps<{
type UnitDisplay = 'lb' | 'qq' | 'both'
const unitDisplay = ref<UnitDisplay>('both')
const unitDisplay = ref<UnitDisplay>('lb')
const unitDisplayLabel = computed(() => {
switch (unitDisplay.value) {

View File

@@ -5,37 +5,58 @@
</template>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<MetricCard
label="Total Lb Neto de Verde"
:value="formatNumber(metrics.totalLbNetoVerde.value)"
unit="lb"
/>
<MetricCard
label="Precio Promedio Ponderado Pagado"
:value="formatNumber(metrics.precioPromedioVerdePagado.value)"
unit="L./lb"
/>
<MetricCard
label="Total Lb Neto de Verde en Depósito"
:value="formatNumber(metrics.totalLbNetoVerdeDeposito.value)"
unit="lb"
variant="info"
/>
<MetricCard
label="Inversión en Verde Hasta la Fecha"
:value="formatCurrency(metrics.inversionVerdeHastaFecha.value)"
variant="success"
/>
<MetricCard
label="Inversión Restante a Realizar en Verde"
:value="formatCurrency(metrics.inversionRestanteVerde.value)"
variant="warning"
/>
<MetricCard
label="Total Lb Neto Comprado de Verde"
:value="formatNumber(metrics.totalLbNetoCompradoVerde.value)"
unit="lb"
/>
<div class="p-4 rounded-lg border transition-all bg-[#1c140c] border-green-700/40 text-green-300">
<div class="flex flex-col">
<span class="text-xs uppercase tracking-wide opacity-80 mb-1">Total Lb Neto de Verde</span>
<div class="flex items-baseline gap-2">
<span class="text-2xl font-bold">{{ formatNumber(metrics.totalLbNetoVerde.value) }}</span>
<span class="text-sm font-bold opacity-70">lb</span>
</div>
</div>
</div>
<div class="p-4 rounded-lg border transition-all bg-[#1c140c] border-green-600/40 text-green-400">
<div class="flex flex-col">
<span class="text-xs uppercase tracking-wide opacity-80 mb-1">Precio Promedio Ponderado Pagado</span>
<div class="flex items-baseline gap-2">
<span class="text-2xl font-bold">{{ formatNumber(metrics.precioPromedioVerdePagado.value) }}</span>
<span class="text-sm font-bold opacity-70">L./lb</span>
</div>
</div>
</div>
<div class="p-4 rounded-lg border transition-all bg-[#1c140c] border-green-500/40 text-green-500">
<div class="flex flex-col">
<span class="text-xs uppercase tracking-wide opacity-80 mb-1">Total Lb Neto de Verde en Depósito</span>
<div class="flex items-baseline gap-2">
<span class="text-2xl font-bold">{{ formatNumber(metrics.totalLbNetoVerdeDeposito.value) }}</span>
<span class="text-sm font-bold opacity-70">lb</span>
</div>
</div>
</div>
<div class="p-4 rounded-lg border transition-all bg-[#1c140c] border-emerald-600/40 text-emerald-400">
<div class="flex flex-col">
<span class="text-xs uppercase tracking-wide opacity-80 mb-1">Inversión en Verde Hasta la Fecha</span>
<div class="flex items-baseline gap-2">
<span class="text-2xl font-bold">{{ formatCurrency(metrics.inversionVerdeHastaFecha.value) }}</span>
</div>
</div>
</div>
<div class="p-4 rounded-lg border transition-all bg-[#1c140c] border-lime-600/40 text-lime-400">
<div class="flex flex-col">
<span class="text-xs uppercase tracking-wide opacity-80 mb-1">Inversión Restante a Realizar en Verde</span>
<div class="flex items-baseline gap-2">
<span class="text-2xl font-bold">{{ formatCurrency(metrics.inversionRestanteVerde.value) }}</span>
</div>
</div>
</div>
<div class="p-4 rounded-lg border transition-all bg-[#1c140c] border-teal-600/40 text-teal-400">
<div class="flex flex-col">
<span class="text-xs uppercase tracking-wide opacity-80 mb-1">Total Lb Neto Comprado de Verde</span>
<div class="flex items-baseline gap-2">
<span class="text-2xl font-bold">{{ formatNumber(metrics.totalLbNetoCompradoVerde.value) }}</span>
<span class="text-sm font-bold opacity-70">lb</span>
</div>
</div>
</div>
</div>
</UCard>
</template>

View File

@@ -12,7 +12,7 @@
</div>
<div class="flex justify-between items-center pt-2 border-t border-[#3a2a16]">
<span class="text-[var(--brand-text)] font-semibold">Total cobrado:</span>
<span class="font-bold text-green-400">{{ formatCurrency(metrics.value.totalCobrado) }}</span>
<span class="font-bold text-white-400">{{ formatCurrency(metrics.value.totalCobrado) }}</span>
</div>
</div>
</div>

View File

@@ -5,7 +5,7 @@
<h2 class="text-xl font-bold brand-section-title">Rechazos y Subproductos</h2>
<div class="rounded-lg border border-[#3a2a16] bg-[#1c140c] px-4 py-2">
<div class="text-xs text-[var(--brand-text-muted)] uppercase tracking-wide mb-1">Total Rechazos</div>
<div class="text-2xl font-bold text-green-400">{{ formatCurrency(totalRechazos.value) }}</div>
<div class="text-2xl font-bold text-[var(--brand-text)]">{{ formatCurrency(totalRechazos.value) }}</div>
</div>
</div>
</template>

View File

@@ -40,7 +40,7 @@
<div class="flex flex-col gap-3">
<div class="flex flex-wrap items-center justify-between gap-3">
<div>
<h2 class="text-xl font-bold brand-section-title">Filtros</h2>
<h2 class="text-xl font-bold brand-section-title">Filtros y Configuraciones</h2>
<p class="text-xs text-[var(--brand-text-muted)] mt-1">
Aplicados a <code>created_at</code> de ingresos y rechazos
</p>
@@ -62,51 +62,14 @@
</div>
</template>
<div class="flex flex-col md:flex-row gap-4">
<!-- Presets -->
<div class="flex-1">
<label class="text-xs text-[var(--brand-text-muted)] block mb-1">Rango rápido</label>
<UFieldGroup>
<UButton
color="neutral"
variant="subtle"
:label="currentPresetLabel"
class="flex-1"
disabled
/>
<UDropdownMenu :items="dropdownItems" :popper="{ placement: 'bottom-end' }">
<UButton
color="neutral"
variant="outline"
icon="i-lucide-chevron-down"
square
/>
</UDropdownMenu>
</UFieldGroup>
</div>
<!-- Fechas manuales -->
<div class="grid grid-cols-2 gap-4 flex-1">
<div>
<label class="text-xs text-[var(--brand-text-muted)]">Fecha desde</label>
<UInput v-model="fechaDesde" type="date" @input="onManualDateChange" />
</div>
<div>
<label class="text-xs text-[var(--brand-text-muted)]">Fecha hasta</label>
<UInput v-model="fechaHasta" type="date" @input="onManualDateChange" />
</div>
</div>
<div class="flex items-end">
<UButton
:ui="{ base: 'bg-[#c08040] text-[#1b1209] border border-[#d99a56] hover:bg-[#d99a56] hover:border-[#f0c07c]' }"
@click="clearPreset"
size="sm"
>
Limpiar
</UButton>
</div>
</div>
<DateRangeSelector
:selected-preset="selectedPreset"
:fecha-desde="fechaDesde"
:fecha-hasta="fechaHasta"
@update:selected-preset="selectedPreset = $event"
@update:fecha-desde="fechaDesde = $event"
@update:fecha-hasta="fechaHasta = $event"
/>
<template #footer>
<div class="text-xs text-[var(--brand-text-muted)]">
@@ -210,108 +173,9 @@ type PresetValue =
| 'cosecha-23-24' | 'cosecha-24-25' | 'cosecha-25-26';
const selectedPreset = ref<PresetValue>('cosecha-25-26')
const currentPresetLabel = computed(() => {
switch (selectedPreset.value) {
case '': return 'Sin filtro'
case 'custom': return 'Personalizado'
case 'hoy': return 'Hoy'
case 'semana': return 'Esta Semana'
case 'mes': return 'Este Mes'
case 'ytd': return 'YTD'
case 'cosecha-20-21': return 'Cosecha 20-21'
case 'cosecha-21-22': return 'Cosecha 21-22'
case 'cosecha-22-23': return 'Cosecha 22-23'
case 'cosecha-23-24': return 'Cosecha 23-24'
case 'cosecha-24-25': return 'Cosecha 24-25'
case 'cosecha-25-26': return 'Cosecha 25-26'
default: return 'Seleccionar rango'
}
})
const dropdownItems = [
{
label: 'Sin filtro',
onSelect: () => {
selectPreset('')
}
},
{
label: 'Rápidos',
children: [
{ label: 'Hoy', onSelect: () => { selectPreset('hoy') } },
{ label: 'Esta Semana', onSelect: () => { selectPreset('semana') } },
{ label: 'Este Mes', onSelect: () => { selectPreset('mes') } },
{ label: 'YTD', onSelect: () => { selectPreset('ytd') } }
]
},
{
label: 'Cosechas',
children: [
{ label: 'Cosecha 20-21 (25 Sep 2020)', onSelect: () => { selectPreset('cosecha-20-21') } },
{ label: 'Cosecha 21-22 (25 Sep 2021)', onSelect: () => { selectPreset('cosecha-21-22') } },
{ label: 'Cosecha 22-23 (25 Sep 2022)', onSelect: () => { selectPreset('cosecha-22-23') } },
{ label: 'Cosecha 23-24 (25 Sep 2023)', onSelect: () => { selectPreset('cosecha-23-24') } },
{ label: 'Cosecha 24-25 (25 Sep 2024)', onSelect: () => { selectPreset('cosecha-24-25') } },
{ label: 'Cosecha 25-26 (10 Sep 2025 → hoy)', onSelect: () => { selectPreset('cosecha-25-26') } }
]
}
]
// Fechas (YYYY-MM-DD) — Honduras (UTC-6)
const toLocalDateStr = (d: Date) => {
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2,'0')
const day = String(d.getDate()).padStart(2,'0')
return `${y}-${m}-${day}`
}
const fechaDesde = ref<string | null>(null)
const fechaHasta = ref<string | null>(null)
function selectPreset(preset: PresetValue) {
console.log('selectPreset called with:', preset)
selectedPreset.value = preset
if (preset === '' || preset === 'custom') {
fechaDesde.value = null
fechaHasta.value = null
console.log('Cleared dates')
return
}
const now = new Date()
const set = (sd: string, ed: string) => {
fechaDesde.value = sd
fechaHasta.value = ed
console.log('Set dates:', sd, ed)
}
switch (preset) {
case 'hoy': set(toLocalDateStr(now), toLocalDateStr(now)); break
case 'semana': {
const d = new Date(now)
const day = d.getDay() || 7
d.setDate(d.getDate() - (day - 1)) // lunes
set(toLocalDateStr(d), toLocalDateStr(now))
break
}
case 'mes': set(toLocalDateStr(new Date(now.getFullYear(), now.getMonth(), 1)), toLocalDateStr(now)); break
case 'ytd': set(toLocalDateStr(new Date(now.getFullYear(), 0, 1)), toLocalDateStr(now)); break
case 'cosecha-20-21': set('2020-09-25', '2021-09-24'); break
case 'cosecha-21-22': set('2021-09-25', '2022-09-24'); break
case 'cosecha-22-23': set('2022-09-25', '2023-09-24'); break
case 'cosecha-23-24': set('2023-09-25', '2024-09-24'); break
case 'cosecha-24-25': set('2024-09-25', '2025-09-09'); break
case 'cosecha-25-26': set('2025-09-10', toLocalDateStr(now)); break
}
}
function onManualDateChange() {
// Si el usuario modifica las fechas manualmente, cambiar a "Personalizado"
selectedPreset.value = 'custom'
console.log('Manual date change, preset set to custom')
}
async function onToggleAnulados(newValue: boolean | 'indeterminate') {
if (newValue === true) {
// Pedir confirmación al activar
@@ -335,12 +199,6 @@ async function onToggleAnulados(newValue: boolean | 'indeterminate') {
}
}
function clearPreset() {
selectedPreset.value = ''
fechaDesde.value = null
fechaHasta.value = null
console.log('Preset cleared')
}
const rangoLegible = computed(() => {
if (!fechaDesde.value && !fechaHasta.value) return 'Sin filtro de fecha'
@@ -494,7 +352,7 @@ onMounted(async () => {
console.error('Error loading data:', err)
} finally {
// Default preset: cosecha 25-26
selectPreset('cosecha-25-26')
selectedPreset.value = 'cosecha-25-26'
includeAnulados.value = false
}
})