mejoras UX exquisitas
This commit is contained in:
@@ -265,19 +265,78 @@
|
||||
</template>
|
||||
</UCard>
|
||||
|
||||
<!-- Totales de Ingreso y Compra -->
|
||||
<IngresosTotalesIngresoCompra :metrics="ingresosMetrics" />
|
||||
|
||||
<!-- Vista Tabla según tab activo -->
|
||||
<!-- Totales por Café -->
|
||||
<UCard class="brand-card border border-transparent">
|
||||
<template #header>
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-bold brand-section-title">{{ tableTitle }}</h2>
|
||||
<h2 class="text-xl font-bold brand-section-title">Totales por Café</h2>
|
||||
<p class="text-xs text-[var(--brand-text-muted)] mt-1">
|
||||
{{ tableDescription }}
|
||||
{{ showMonetaryView ? 'Vista de valores monetarios' : 'Vista de quintales (qq) y libras (lb)' }}
|
||||
</p>
|
||||
</div>
|
||||
<UButton
|
||||
size="sm"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
@click="showMonetaryView = !showMonetaryView"
|
||||
>
|
||||
<template #leading>
|
||||
<UIcon :name="showMonetaryView ? 'i-lucide-scale' : 'i-lucide-dollar-sign'" />
|
||||
</template>
|
||||
{{ showMonetaryView ? 'Ver qq/lb' : 'Ver Monetario' }}
|
||||
</UButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<IngresosTotalesIngresoCompra v-if="!showMonetaryView" :metrics="ingresosMetrics" />
|
||||
<IngresosTotalesMonetarios v-else :metrics="ingresosMetrics" />
|
||||
</UCard>
|
||||
|
||||
<!-- Totales Netos de Verde -->
|
||||
<IngresosTotalesVerde v-if="hasVerdeData" :metrics="ingresosMetrics" />
|
||||
|
||||
<!-- Backdrop para fullscreen -->
|
||||
<Transition
|
||||
enter-active-class="transition-opacity duration-300"
|
||||
enter-from-class="opacity-0"
|
||||
enter-to-class="opacity-100"
|
||||
leave-active-class="transition-opacity duration-300"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div
|
||||
v-if="isTableFullscreen"
|
||||
class="fixed inset-0 bg-black/80 z-40 backdrop-blur-sm"
|
||||
@click="toggleTableFullscreen"
|
||||
/>
|
||||
</Transition>
|
||||
|
||||
<!-- Vista Tabla según tab activo -->
|
||||
<UCard
|
||||
:class="[
|
||||
'brand-card border border-transparent transition-all duration-300',
|
||||
isTableFullscreen ? 'fixed inset-4 z-50 rounded-lg overflow-auto' : ''
|
||||
]"
|
||||
>
|
||||
<template #header>
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-bold brand-section-title">{{ tableTitle }}</h2>
|
||||
<p class="text-xs text-[var(--brand-text-muted)] mt-1">
|
||||
{{ tableDescription }}
|
||||
</p>
|
||||
</div>
|
||||
<UButton
|
||||
:icon="isTableFullscreen ? 'i-lucide-minimize' : 'i-lucide-maximize'"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
@click="toggleTableFullscreen"
|
||||
:title="isTableFullscreen ? 'Salir de pantalla completa' : 'Pantalla completa'"
|
||||
/>
|
||||
</div>
|
||||
<div ref="viewSelectorRef" class="flex flex-col gap-3">
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<button
|
||||
@@ -322,6 +381,22 @@
|
||||
>
|
||||
{{ option.label }}
|
||||
</span>
|
||||
|
||||
<!-- Badge con contador -->
|
||||
<span
|
||||
:class="[
|
||||
'relative z-10 inline-flex items-center justify-center min-w-[20px] h-5 px-1.5 rounded-full text-xs font-bold transition-all',
|
||||
selectedView === option.value && option.color === 'cyan' ? 'bg-cyan-500/30 text-cyan-300 border border-cyan-400/50' :
|
||||
selectedView === option.value && option.color === 'yellow' ? 'bg-yellow-500/30 text-yellow-300 border border-yellow-400/50' :
|
||||
selectedView === option.value && option.color === 'gradient' ? 'bg-white/20 text-white border border-white/30' :
|
||||
option.color === 'cyan' ? 'bg-cyan-500/20 text-cyan-400/80 border border-cyan-500/30' :
|
||||
option.color === 'yellow' ? 'bg-yellow-500/20 text-yellow-400/80 border border-yellow-500/30' :
|
||||
option.color === 'gradient' ? 'bg-gradient-to-r from-cyan-500/20 to-yellow-500/20 text-gray-300 border border-gray-500/30' :
|
||||
'bg-gray-500/20 text-gray-400 border border-gray-500/30'
|
||||
]"
|
||||
>
|
||||
{{ getViewCount(option.value) }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -368,6 +443,9 @@
|
||||
:include-clientes-without-ingresos="includeClientesWithoutIngresos"
|
||||
/>
|
||||
</UCard>
|
||||
|
||||
<!-- Top 10 Clientes -->
|
||||
<IngresosTopClientes :ingresos="ingresosFiltrados" :clientes="clientesFiltrados" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
@@ -381,7 +459,7 @@ import type { IngresoRecord } from '~/composables/useIngresosMetrics'
|
||||
// Define page metadata
|
||||
definePageMeta({
|
||||
layout: 'dashboard',
|
||||
title: 'Cuenta Cliente'
|
||||
title: 'Analizador Ingresos-Clientes'
|
||||
})
|
||||
|
||||
// View modes with explicit hierarchy
|
||||
@@ -688,6 +766,65 @@ const clientesFiltrados = computed((): ClienteRecord[] => {
|
||||
// Métricos basados en filtrados
|
||||
const ingresosMetrics = useIngresosMetrics(ingresosFiltrados)
|
||||
|
||||
// Toggle para vista de totales
|
||||
const showMonetaryView = ref(false)
|
||||
|
||||
// Toggle para fullscreen de tabla
|
||||
const isTableFullscreen = ref(false)
|
||||
|
||||
function toggleTableFullscreen() {
|
||||
isTableFullscreen.value = !isTableFullscreen.value
|
||||
|
||||
// Prevenir scroll del body cuando está en fullscreen
|
||||
if (isTableFullscreen.value) {
|
||||
document.body.style.overflow = 'hidden'
|
||||
} else {
|
||||
document.body.style.overflow = ''
|
||||
}
|
||||
}
|
||||
|
||||
// Atajo de teclado para salir de fullscreen con Escape
|
||||
const handleEscape = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape' && isTableFullscreen.value) {
|
||||
toggleTableFullscreen()
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup cuando se desmonta el componente
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('keydown', handleEscape)
|
||||
document.body.style.overflow = ''
|
||||
})
|
||||
|
||||
// Verificar si hay datos de verde para mostrar la sección
|
||||
const hasVerdeData = computed(() => {
|
||||
return ingresosMetrics.totalLbNetoVerde.value > 0 ||
|
||||
ingresosMetrics.inversionVerdeHastaFecha.value > 0 ||
|
||||
ingresosMetrics.totalLbNetoCompradoVerde.value > 0
|
||||
})
|
||||
|
||||
// Función para obtener el conteo de registros según la vista
|
||||
function getViewCount(view: ViewMode): number {
|
||||
switch (view) {
|
||||
case 'ingresos-only':
|
||||
return ingresosFiltrados.value.length
|
||||
case 'clientes-only':
|
||||
return clientesFiltrados.value.length
|
||||
case 'ingresos-clientes':
|
||||
return ingresosFiltrados.value.length
|
||||
case 'clientes-ingresos':
|
||||
// Contar solo clientes con ingresos si el toggle está OFF
|
||||
if (!includeClientesWithoutIngresos.value) {
|
||||
return clientesFiltrados.value.filter(c =>
|
||||
ingresosFiltrados.value.some(i => i.cliente_id === c.id)
|
||||
).length
|
||||
}
|
||||
return clientesFiltrados.value.length
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// Loading and error states
|
||||
const loading = computed(() => ingresosStore.isLoading || clientesStore.isLoading)
|
||||
const error = computed(() => ingresosStore.error || clientesStore.error)
|
||||
@@ -751,6 +888,9 @@ onMounted(async () => {
|
||||
selectedEstados.value = []
|
||||
selectedUbicaciones.value = []
|
||||
}
|
||||
|
||||
// Listener para escape key en fullscreen
|
||||
window.addEventListener('keydown', handleEscape)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user