sper mejoras de UI
This commit is contained in:
@@ -8,18 +8,56 @@
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<UIcon name="i-lucide-calendar-heat" class="size-5 text-[#c08040]" />
|
||||
<UIcon name="i-lucide-activity" class="size-5 text-[#c08040]" />
|
||||
<h3 class="text-base font-semibold text-[var(--brand-text)]">Vista de Calor por Día</h3>
|
||||
</div>
|
||||
<UButton
|
||||
@click="toggleFullscreen"
|
||||
size="xs"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
:icon="isFullscreen ? 'i-lucide-minimize' : 'i-lucide-maximize'"
|
||||
>
|
||||
{{ isFullscreen ? 'Salir' : 'Pantalla completa' }}
|
||||
</UButton>
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- Controles de Zoom -->
|
||||
<div
|
||||
class="flex items-center gap-1 px-2 py-1 rounded-lg border border-[var(--brand-border)] bg-[var(--brand-bg-secondary)]"
|
||||
title="También puedes usar Ctrl+Rueda del mouse para zoom"
|
||||
>
|
||||
<UButton
|
||||
@click="zoomOut"
|
||||
size="xs"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
icon="i-lucide-zoom-out"
|
||||
:disabled="nivelZoom <= 0.5"
|
||||
title="Alejar (Ctrl+Rueda abajo)"
|
||||
/>
|
||||
<span class="text-xs text-[var(--brand-text-muted)] px-2 min-w-[3rem] text-center">
|
||||
{{ (nivelZoom * 100).toFixed(0) }}%
|
||||
</span>
|
||||
<UButton
|
||||
@click="zoomIn"
|
||||
size="xs"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
icon="i-lucide-zoom-in"
|
||||
:disabled="nivelZoom >= 2"
|
||||
title="Acercar (Ctrl+Rueda arriba)"
|
||||
/>
|
||||
<div class="w-px h-4 bg-[var(--brand-border)] mx-1" />
|
||||
<UButton
|
||||
@click="resetZoom"
|
||||
size="xs"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
icon="i-lucide-refresh-cw"
|
||||
title="Restablecer zoom (100%)"
|
||||
/>
|
||||
</div>
|
||||
<UButton
|
||||
@click="toggleFullscreen"
|
||||
size="xs"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
:icon="isFullscreen ? 'i-lucide-minimize' : 'i-lucide-maximize'"
|
||||
>
|
||||
{{ isFullscreen ? 'Salir' : 'Pantalla completa' }}
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 flex-wrap">
|
||||
<label class="text-xs text-[var(--brand-text-muted)]">Vista:</label>
|
||||
@@ -230,7 +268,7 @@
|
||||
class="flex flex-col items-center"
|
||||
:style="{ width: `${barMaxWidth}px` }"
|
||||
>
|
||||
<div class="text-xs font-medium mb-1" :style="{ color: getCosechaColor(index) }">
|
||||
<div class="text-xs font-medium mb-1" :style="{ color: getCosechaColor(cosecha.id) }">
|
||||
{{ cosecha.label }}
|
||||
</div>
|
||||
<div class="text-xs text-[var(--brand-text-muted)]">
|
||||
@@ -291,7 +329,7 @@
|
||||
:style="{
|
||||
width: getBarWidth(cosecha.valoresPorDia[dia - 1] || 0),
|
||||
height: `${barHeight - 2}px`,
|
||||
backgroundColor: getCosechaColor(cosechaIndex)
|
||||
backgroundColor: getCosechaColor(cosecha.id)
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
@@ -353,7 +391,7 @@
|
||||
>
|
||||
<div
|
||||
class="w-4 h-3 rounded"
|
||||
:style="{ backgroundColor: getCosechaColor(index) }"
|
||||
:style="{ backgroundColor: getCosechaColor(cosecha.id) }"
|
||||
/>
|
||||
<span class="text-xs text-[var(--brand-text-muted)]">{{ cosecha.label }}</span>
|
||||
</div>
|
||||
@@ -383,12 +421,12 @@
|
||||
</span>
|
||||
<div
|
||||
class="w-3 h-3 rounded-full"
|
||||
:style="{ backgroundColor: getCosechaColor(index) }"
|
||||
:style="{ backgroundColor: getCosechaColor(cosecha.id) }"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Valor total -->
|
||||
<div class="text-2xl font-bold" :style="{ color: getCosechaColor(index) }">
|
||||
<div class="text-2xl font-bold" :style="{ color: getCosechaColor(cosecha.id) }">
|
||||
{{ formatTotal(cosecha.total) }}
|
||||
</div>
|
||||
|
||||
@@ -398,7 +436,7 @@
|
||||
class="h-full transition-all"
|
||||
:style="{
|
||||
width: `${(cosecha.total / maxTotalEnRango) * 100}%`,
|
||||
backgroundColor: getCosechaColor(index)
|
||||
backgroundColor: getCosechaColor(cosecha.id)
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
@@ -439,10 +477,23 @@ const props = defineProps<Props>()
|
||||
// Obtener definiciones de cosechas
|
||||
const cosechasDisponibles = inject<any[]>('cosechasDisponibles', [])
|
||||
|
||||
// Obtener configuración de estilos
|
||||
const estilosGraficas = inject<any>('estilosGraficas', ref({
|
||||
coloresCosechas: ['#c08040', '#d99a56', '#8b6f47', '#a0826e', '#b89968', '#f0c07c'],
|
||||
anchoCelda: 80,
|
||||
altoCelda: 6,
|
||||
anchoMaxBarra: 300,
|
||||
altoBarra: 8,
|
||||
opacidadVacias: 0.05
|
||||
}))
|
||||
|
||||
// Referencia al contenedor y estado de fullscreen
|
||||
const cardContainer = ref<HTMLElement | null>(null)
|
||||
const isFullscreen = ref(false)
|
||||
|
||||
// Estado de zoom (0.5x a 2x)
|
||||
const nivelZoom = ref(1)
|
||||
|
||||
// Selección de vista, métrica y tipo
|
||||
const vistaMode = ref<'heatmap' | 'barras'>('barras')
|
||||
const metrica = ref<'peso' | 'cantidad' | 'inversion'>('peso')
|
||||
@@ -452,13 +503,27 @@ const tipoSeleccionado = ref<string>('todos')
|
||||
const primerDiaSeleccionado = ref<number | null>(null)
|
||||
const rangoSeleccionado = ref<{ diaDesde: number, diaHasta: number, diasTotales: number } | null>(null)
|
||||
|
||||
// Configuración de celdas (heatmap)
|
||||
const cellWidth = 80
|
||||
const cellHeight = 6
|
||||
|
||||
// Configuración de barras
|
||||
const barMaxWidth = 300
|
||||
const barHeight = 8
|
||||
// Configuración de celdas y barras (ahora reactivas desde la configuración y zoom)
|
||||
const cellWidth = computed(() => {
|
||||
const value = estilosGraficas.value.anchoCelda
|
||||
const baseValue = typeof value === 'number' ? value : 80
|
||||
return Math.round(baseValue * nivelZoom.value)
|
||||
})
|
||||
const cellHeight = computed(() => {
|
||||
const value = estilosGraficas.value.altoCelda
|
||||
const baseValue = typeof value === 'number' ? value : 6
|
||||
return Math.round(baseValue * nivelZoom.value)
|
||||
})
|
||||
const barMaxWidth = computed(() => {
|
||||
const value = estilosGraficas.value.anchoMaxBarra
|
||||
const baseValue = typeof value === 'number' ? value : 300
|
||||
return Math.round(baseValue * nivelZoom.value)
|
||||
})
|
||||
const barHeight = computed(() => {
|
||||
const value = estilosGraficas.value.altoBarra
|
||||
const baseValue = typeof value === 'number' ? value : 8
|
||||
return Math.round(baseValue * nivelZoom.value)
|
||||
})
|
||||
|
||||
// Tooltip state
|
||||
const tooltipVisible = ref(false)
|
||||
@@ -630,7 +695,7 @@ function getCellColor(valor: number, max: number): string {
|
||||
|
||||
// Escala de color desde transparente a naranja intenso
|
||||
if (intensity === 0) {
|
||||
return 'rgba(192, 128, 64, 0.05)' // Muy suave para vacío
|
||||
return `rgba(192, 128, 64, ${estilosGraficas.value.opacidadVacias})` // Opacidad configurable
|
||||
}
|
||||
|
||||
// Gradiente de naranja con opacidad basada en intensidad
|
||||
@@ -642,26 +707,42 @@ function getCellColor(valor: number, max: number): string {
|
||||
return `rgba(${r}, ${g}, ${b}, ${alpha})`
|
||||
}
|
||||
|
||||
// Colores sólidos por cosecha (vista barras)
|
||||
const cosechaColors = [
|
||||
'#c08040', // Naranja primario
|
||||
'#d99a56', // Naranja claro
|
||||
'#8b6f47', // Marrón
|
||||
'#a0826e', // Café claro
|
||||
'#b89968', // Beige
|
||||
'#f0c07c' // Amarillo dorado
|
||||
]
|
||||
// Mapeo de cosechas a índices fijos
|
||||
const cosechaColorMap: Record<string, number> = {
|
||||
'cosecha-20-21': 0,
|
||||
'cosecha-21-22': 1,
|
||||
'cosecha-22-23': 2,
|
||||
'cosecha-23-24': 3,
|
||||
'cosecha-24-25': 4,
|
||||
'cosecha-25-26': 5
|
||||
}
|
||||
|
||||
// Función para obtener color fijo de cosecha
|
||||
function getCosechaColor(index: number): string {
|
||||
return cosechaColors[index % cosechaColors.length]
|
||||
// Función para obtener color fijo de cosecha según su ID (no su orden de selección)
|
||||
function getCosechaColor(cosechaIdOrIndex: string | number): string {
|
||||
const colores = estilosGraficas.value.coloresCosechas
|
||||
|
||||
// Si es un string, es un ID de cosecha
|
||||
if (typeof cosechaIdOrIndex === 'string') {
|
||||
const index = cosechaColorMap[cosechaIdOrIndex] ?? 0
|
||||
return colores[index % colores.length]
|
||||
}
|
||||
|
||||
// Si es un número, buscar el ID de la cosecha en datosCosechas
|
||||
const cosecha = datosCosechas.value[cosechaIdOrIndex]
|
||||
if (cosecha?.id) {
|
||||
const index = cosechaColorMap[cosecha.id] ?? cosechaIdOrIndex
|
||||
return colores[index % colores.length]
|
||||
}
|
||||
|
||||
// Fallback al índice directo
|
||||
return colores[cosechaIdOrIndex % colores.length]
|
||||
}
|
||||
|
||||
// Función para calcular ancho de barra según valor
|
||||
function getBarWidth(valor: number): string {
|
||||
if (maxValorDia.value === 0) return '0px'
|
||||
const percentage = (valor / maxValorDia.value) * 100
|
||||
const width = (percentage / 100) * barMaxWidth
|
||||
const width = (percentage / 100) * barMaxWidth.value // .value es necesario en el script setup
|
||||
return `${width}px`
|
||||
}
|
||||
|
||||
@@ -798,6 +879,23 @@ function formatRangoFecha(diaRelativo: number): string {
|
||||
})
|
||||
}
|
||||
|
||||
// Funciones de zoom
|
||||
function zoomIn() {
|
||||
if (nivelZoom.value < 2) {
|
||||
nivelZoom.value = Math.min(2, nivelZoom.value + 0.25)
|
||||
}
|
||||
}
|
||||
|
||||
function zoomOut() {
|
||||
if (nivelZoom.value > 0.5) {
|
||||
nivelZoom.value = Math.max(0.5, nivelZoom.value - 0.25)
|
||||
}
|
||||
}
|
||||
|
||||
function resetZoom() {
|
||||
nivelZoom.value = 1
|
||||
}
|
||||
|
||||
// Funciones de pantalla completa
|
||||
async function toggleFullscreen() {
|
||||
if (!cardContainer.value) return
|
||||
@@ -822,13 +920,40 @@ function handleFullscreenChange() {
|
||||
isFullscreen.value = !!document.fullscreenElement
|
||||
}
|
||||
|
||||
// Zoom con rueda del mouse + Ctrl
|
||||
function handleWheel(event: WheelEvent) {
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
event.preventDefault()
|
||||
|
||||
if (event.deltaY < 0) {
|
||||
// Scroll up = zoom in
|
||||
zoomIn()
|
||||
} else {
|
||||
// Scroll down = zoom out
|
||||
zoomOut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Montar y desmontar listeners
|
||||
onMounted(() => {
|
||||
document.addEventListener('fullscreenchange', handleFullscreenChange)
|
||||
|
||||
// Agregar listener para zoom con rueda del mouse
|
||||
const container = cardContainer.value?.$el
|
||||
if (container) {
|
||||
container.addEventListener('wheel', handleWheel, { passive: false })
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('fullscreenchange', handleFullscreenChange)
|
||||
|
||||
// Remover listener de zoom
|
||||
const container = cardContainer.value?.$el
|
||||
if (container) {
|
||||
container.removeEventListener('wheel', handleWheel)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user