ui?ux mejorada, comparativa cosecha-cosecha
This commit is contained in:
176
nuxt4-app/app/pages/comparativa-cosechas.vue
Normal file
176
nuxt4-app/app/pages/comparativa-cosechas.vue
Normal file
@@ -0,0 +1,176 @@
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<!-- Toolbar con selector de cosechas -->
|
||||
<UDashboardToolbar>
|
||||
<template #left>
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="text-sm text-[var(--brand-text-muted)]">Seleccionar cosechas a comparar:</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #right>
|
||||
<div class="flex items-center gap-3">
|
||||
<USwitch
|
||||
v-model="pageSections.totales"
|
||||
size="xs"
|
||||
label="Totales"
|
||||
/>
|
||||
<USwitch
|
||||
v-model="pageSections.evolucion"
|
||||
size="xs"
|
||||
label="Evolución"
|
||||
/>
|
||||
<USwitch
|
||||
v-model="pageSections.porTipo"
|
||||
size="xs"
|
||||
label="Por Tipo"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</UDashboardToolbar>
|
||||
|
||||
<div class="flex flex-col gap-8 p-6">
|
||||
<!-- Loading State -->
|
||||
<UCard v-if="loading && !ingresosStore.hasData" class="brand-card border border-transparent">
|
||||
<div class="flex flex-col items-center justify-center gap-4 py-10 text-[var(--brand-text-muted)]">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="inline-flex h-8 w-8 animate-spin rounded-full border-2 border-[#c08040] border-t-transparent align-middle" aria-hidden="true" />
|
||||
<span class="text-sm uppercase tracking-[0.3em]">Cargando datos...</span>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<!-- Error State -->
|
||||
<div v-else-if="error" class="rounded-lg border border-red-500/40 bg-red-500/18 p-4 text-sm text-red-200">
|
||||
<p>Error al cargar datos: {{ error }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<template v-else>
|
||||
<!-- Selector de Cosechas -->
|
||||
<UCard class="brand-card border border-transparent">
|
||||
<template #header>
|
||||
<div class="flex items-center gap-2">
|
||||
<UIcon name="i-lucide-calendar-range" class="size-5 text-[#c08040]" />
|
||||
<h3 class="text-base font-semibold text-[var(--brand-text)]">Cosechas a Comparar</h3>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-3">
|
||||
<label
|
||||
v-for="cosecha in cosechasDisponibles"
|
||||
:key="cosecha.id"
|
||||
class="flex items-center gap-2 p-3 rounded-lg border cursor-pointer transition-all"
|
||||
:class="cosechasSeleccionadas.includes(cosecha.id)
|
||||
? 'border-[#c08040] bg-[#c08040]/10'
|
||||
: 'border-[var(--brand-border)] hover:border-[#c08040]/50'"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
:value="cosecha.id"
|
||||
v-model="cosechasSeleccionadas"
|
||||
class="rounded border-[var(--brand-border)] text-[#c08040] focus:ring-[#c08040]"
|
||||
/>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-sm font-medium text-[var(--brand-text)]">{{ cosecha.label }}</span>
|
||||
<span class="text-xs text-[var(--brand-text-muted)]">{{ cosecha.periodo }}</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<!-- Resumen General por Cosecha -->
|
||||
<div v-if="cosechasSeleccionadas.length > 0 && pageSections.totales">
|
||||
<ComparativaCosechasTotales :ingresos="ingresos" :cosechas-seleccionadas="cosechasSeleccionadas" />
|
||||
</div>
|
||||
|
||||
<!-- Comparativa por Tipo de Café -->
|
||||
<div v-if="cosechasSeleccionadas.length > 0 && pageSections.porTipo">
|
||||
<ComparativaCosechasPorTipo :ingresos="ingresos" :cosechas-seleccionadas="cosechasSeleccionadas" />
|
||||
</div>
|
||||
|
||||
<!-- Evolución Temporal Comparada -->
|
||||
<div v-if="cosechasSeleccionadas.length > 0 && pageSections.evolucion">
|
||||
<ComparativaCosechasEvolucion :ingresos="ingresos" :cosechas-seleccionadas="cosechasSeleccionadas" />
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<UCard v-if="cosechasSeleccionadas.length === 0" class="brand-card border border-transparent">
|
||||
<div class="flex flex-col items-center justify-center gap-4 py-16 text-[var(--brand-text-muted)]">
|
||||
<UIcon name="i-lucide-bar-chart-2" class="size-16 opacity-50" />
|
||||
<p class="text-sm">Selecciona al menos una cosecha para ver las comparativas</p>
|
||||
</div>
|
||||
</UCard>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useTableDataStore } from '~/stores/tableDataFactory'
|
||||
import type { IngresoRecord } from '~/composables/useIngresosMetrics'
|
||||
|
||||
definePageMeta({
|
||||
layout: 'informe',
|
||||
title: 'Comparativa Cosechas'
|
||||
})
|
||||
|
||||
// Definir secciones específicas de esta página
|
||||
const pageSections = ref({
|
||||
totales: true,
|
||||
evolucion: true,
|
||||
porTipo: true
|
||||
})
|
||||
|
||||
// Definición de cosechas disponibles
|
||||
const cosechasDisponibles = [
|
||||
{ id: 'cosecha-20-21', label: 'Cosecha 20-21', periodo: 'Sep 2020 - Sep 2021', fechaInicio: '2020-09-25', fechaFin: '2021-09-24' },
|
||||
{ id: 'cosecha-21-22', label: 'Cosecha 21-22', periodo: 'Sep 2021 - Sep 2022', fechaInicio: '2021-09-25', fechaFin: '2022-09-24' },
|
||||
{ id: 'cosecha-22-23', label: 'Cosecha 22-23', periodo: 'Sep 2022 - Sep 2023', fechaInicio: '2022-09-25', fechaFin: '2023-09-24' },
|
||||
{ id: 'cosecha-23-24', label: 'Cosecha 23-24', periodo: 'Sep 2023 - Sep 2024', fechaInicio: '2023-09-25', fechaFin: '2024-09-24' },
|
||||
{ id: 'cosecha-24-25', label: 'Cosecha 24-25', periodo: 'Sep 2024 - Sep 2025', fechaInicio: '2024-09-25', fechaFin: '2025-09-09' },
|
||||
{ id: 'cosecha-25-26', label: 'Cosecha 25-26', periodo: 'Sep 2025 - Hoy', fechaInicio: '2025-09-10', fechaFin: new Date().toISOString().split('T')[0] }
|
||||
]
|
||||
|
||||
// Cosechas seleccionadas (por defecto las 3 más recientes)
|
||||
const cosechasSeleccionadas = ref<string[]>(['cosecha-23-24', 'cosecha-24-25'])
|
||||
|
||||
// Store de ingresos
|
||||
const ingresosStore = useTableDataStore('ingresos')
|
||||
|
||||
const loading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
// Cargar datos
|
||||
onMounted(async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
await ingresosStore.fetch()
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Error desconocido'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
|
||||
// Datos de ingresos
|
||||
const ingresos = computed(() => {
|
||||
return ingresosStore.data.map(row => {
|
||||
const ingreso: IngresoRecord = {
|
||||
id: row.id as number,
|
||||
created_at: row.created_at as string,
|
||||
peso_neto: row.peso_neto as number,
|
||||
precio: row.precio as number,
|
||||
tipo: row.tipo as string,
|
||||
cliente: row.cliente as string,
|
||||
peso_seco: row.peso_seco as number | undefined,
|
||||
pagado: row.pagado as boolean | undefined
|
||||
}
|
||||
return ingreso
|
||||
})
|
||||
})
|
||||
|
||||
// Exportar cosechas para los componentes
|
||||
provide('cosechasDisponibles', cosechasDisponibles)
|
||||
</script>
|
||||
Reference in New Issue
Block a user