317 lines
10 KiB
Vue
317 lines
10 KiB
Vue
<template>
|
|
<UCard class="brand-card border border-transparent">
|
|
<template #header>
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center gap-2">
|
|
<UIcon name="i-lucide-filter" class="size-4 text-[#c08040]" />
|
|
<h3 class="text-sm font-semibold text-[var(--brand-text)]">Filtros Activos</h3>
|
|
<span class="text-xs text-[var(--brand-text-muted)]">({{ totalFiltros }})</span>
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<UButton
|
|
size="xs"
|
|
variant="ghost"
|
|
color="neutral"
|
|
@click="showFiltros"
|
|
icon="i-lucide-plus"
|
|
>
|
|
Agregar filtros
|
|
</UButton>
|
|
<UButton
|
|
v-if="totalFiltros > 0"
|
|
size="xs"
|
|
variant="ghost"
|
|
color="neutral"
|
|
@click="resetearAHoy"
|
|
icon="i-lucide-rotate-ccw"
|
|
>
|
|
Resetear a Hoy
|
|
</UButton>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<div v-if="totalFiltros === 0" class="text-center py-4 text-sm text-[var(--brand-text-muted)]">
|
|
No hay filtros activos
|
|
</div>
|
|
|
|
<div v-else class="flex flex-wrap gap-2">
|
|
<!-- Clientes -->
|
|
<div
|
|
v-for="clienteId in selectedClienteIds"
|
|
:key="`cliente-${clienteId}`"
|
|
class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium bg-blue-500/10 text-blue-400 border border-blue-500/30 transition-all hover:bg-blue-500/20"
|
|
>
|
|
<UIcon name="i-lucide-user" class="size-3" />
|
|
<span>{{ getNombreCliente(clienteId) }}</span>
|
|
<button
|
|
@click="removeCliente(clienteId)"
|
|
class="ml-1 hover:text-blue-300 transition-colors"
|
|
>
|
|
<UIcon name="i-lucide-x" class="size-3" />
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Botón para agregar más clientes -->
|
|
<button
|
|
v-if="selectedClienteIds.length > 0"
|
|
@click="showClienteSelector"
|
|
class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium bg-blue-500/5 text-blue-400 border border-blue-500/20 border-dashed transition-all hover:bg-blue-500/10 hover:border-blue-500/40"
|
|
title="Agregar más clientes"
|
|
>
|
|
<UIcon name="i-lucide-user-plus" class="size-3" />
|
|
<span>Agregar cliente</span>
|
|
</button>
|
|
|
|
<!-- Fechas -->
|
|
<div
|
|
v-if="fechaDesde || fechaHasta"
|
|
class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium bg-purple-500/10 text-purple-400 border border-purple-500/30 transition-all hover:bg-purple-500/20"
|
|
>
|
|
<UIcon name="i-lucide-calendar" class="size-3" />
|
|
<span>{{ getPresetLabel() }}</span>
|
|
<button
|
|
@click="removeFechas"
|
|
class="ml-1 hover:text-purple-300 transition-colors"
|
|
>
|
|
<UIcon name="i-lucide-x" class="size-3" />
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Tipos -->
|
|
<div
|
|
v-for="tipo in selectedTipos"
|
|
:key="`tipo-${tipo}`"
|
|
class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium bg-orange-500/10 text-orange-400 border border-orange-500/30 transition-all hover:bg-orange-500/20"
|
|
>
|
|
<UIcon name="i-lucide-coffee" class="size-3" />
|
|
<span>{{ getLabelTipo(tipo) }}</span>
|
|
<button
|
|
@click="removeTipo(tipo)"
|
|
class="ml-1 hover:text-orange-300 transition-colors"
|
|
>
|
|
<UIcon name="i-lucide-x" class="size-3" />
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Estados -->
|
|
<div
|
|
v-for="estado in selectedEstados"
|
|
:key="`estado-${estado}`"
|
|
class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium bg-green-500/10 text-green-400 border border-green-500/30 transition-all hover:bg-green-500/20"
|
|
>
|
|
<UIcon name="i-lucide-check-circle" class="size-3" />
|
|
<span>{{ getLabelEstado(estado) }}</span>
|
|
<button
|
|
@click="removeEstado(estado)"
|
|
class="ml-1 hover:text-green-300 transition-colors"
|
|
>
|
|
<UIcon name="i-lucide-x" class="size-3" />
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Ubicaciones -->
|
|
<div
|
|
v-for="ubicacion in selectedUbicaciones"
|
|
:key="`ubicacion-${ubicacion}`"
|
|
class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium bg-cyan-500/10 text-cyan-400 border border-cyan-500/30 transition-all hover:bg-cyan-500/20"
|
|
>
|
|
<UIcon name="i-lucide-map-pin" class="size-3" />
|
|
<span>{{ ubicacion }}</span>
|
|
<button
|
|
@click="removeUbicacion(ubicacion)"
|
|
class="ml-1 hover:text-cyan-300 transition-colors"
|
|
>
|
|
<UIcon name="i-lucide-x" class="size-3" />
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Calidades -->
|
|
<div
|
|
v-for="calidad in selectedCalidades"
|
|
:key="`calidad-${calidad}`"
|
|
class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium bg-yellow-500/10 text-yellow-400 border border-yellow-500/30 transition-all hover:bg-yellow-500/20"
|
|
>
|
|
<UIcon name="i-lucide-star" class="size-3" />
|
|
<span>{{ calidad }}</span>
|
|
<button
|
|
@click="removeCalidad(calidad)"
|
|
class="ml-1 hover:text-yellow-300 transition-colors"
|
|
>
|
|
<UIcon name="i-lucide-x" class="size-3" />
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Incluir Anulados -->
|
|
<div
|
|
v-if="includeAnulados"
|
|
class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium bg-red-500/10 text-red-400 border border-red-500/30 transition-all hover:bg-red-500/20"
|
|
>
|
|
<UIcon name="i-lucide-alert-triangle" class="size-3" />
|
|
<span>Con anulados</span>
|
|
<button
|
|
@click="removeAnulados"
|
|
class="ml-1 hover:text-red-300 transition-colors"
|
|
>
|
|
<UIcon name="i-lucide-x" class="size-3" />
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Sin Filtros -->
|
|
<div
|
|
v-if="noFilter"
|
|
class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium bg-gray-500/10 text-gray-400 border border-gray-500/30 transition-all hover:bg-gray-500/20"
|
|
>
|
|
<UIcon name="i-lucide-ban" class="size-3" />
|
|
<span>Sin filtros</span>
|
|
<button
|
|
@click="removeNoFilter"
|
|
class="ml-1 hover:text-gray-300 transition-colors"
|
|
>
|
|
<UIcon name="i-lucide-x" class="size-3" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</UCard>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
interface Props {
|
|
clientes: any[]
|
|
selectedClienteIds: number[]
|
|
selectedPreset: string
|
|
fechaDesde: string | null
|
|
fechaHasta: string | null
|
|
selectedTipos: string[]
|
|
selectedEstados: string[]
|
|
selectedUbicaciones: string[]
|
|
selectedCalidades: string[]
|
|
includeAnulados: boolean
|
|
noFilter: boolean
|
|
tiposOptions: any[]
|
|
estadosOptions: any[]
|
|
}
|
|
|
|
const props = defineProps<Props>()
|
|
|
|
const emit = defineEmits<{
|
|
'update:selectedClienteIds': [value: number[]]
|
|
'update:fechaDesde': [value: string | null]
|
|
'update:fechaHasta': [value: string | null]
|
|
'update:selectedPreset': [value: string]
|
|
'update:selectedTipos': [value: string[]]
|
|
'update:selectedEstados': [value: string[]]
|
|
'update:selectedUbicaciones': [value: string[]]
|
|
'update:selectedCalidades': [value: string[]]
|
|
'update:includeAnulados': [value: boolean]
|
|
'update:noFilter': [value: boolean]
|
|
'showClienteSelector': []
|
|
'showFiltros': []
|
|
}>()
|
|
|
|
const totalFiltros = computed(() => {
|
|
let count = 0
|
|
count += props.selectedClienteIds.length
|
|
count += (props.fechaDesde || props.fechaHasta) ? 1 : 0
|
|
count += props.selectedTipos.length
|
|
count += props.selectedEstados.length
|
|
count += props.selectedUbicaciones.length
|
|
count += props.selectedCalidades.length
|
|
count += props.includeAnulados ? 1 : 0
|
|
count += props.noFilter ? 1 : 0
|
|
return count
|
|
})
|
|
|
|
function showClienteSelector() {
|
|
emit('showClienteSelector')
|
|
}
|
|
|
|
function showFiltros() {
|
|
emit('showFiltros')
|
|
}
|
|
|
|
function getNombreCliente(id: number): string {
|
|
const cliente = props.clientes.find(c => c.id === id)
|
|
return cliente?.name || `Cliente ${id}`
|
|
}
|
|
|
|
function getLabelTipo(value: string): string {
|
|
const tipo = props.tiposOptions.find(t => t.value === value)
|
|
return tipo?.label || value
|
|
}
|
|
|
|
function getLabelEstado(value: string): string {
|
|
const estado = props.estadosOptions.find(e => e.value === value)
|
|
return estado?.label || value
|
|
}
|
|
|
|
function getPresetLabel(): string {
|
|
// Si es un preset conocido, mostrar su nombre
|
|
const presetLabels: Record<string, string> = {
|
|
'hoy': 'Hoy',
|
|
'semana': 'Esta Semana',
|
|
'mes': 'Este Mes',
|
|
'ytd': 'YTD',
|
|
'cosecha-20-21': 'Cosecha 20-21',
|
|
'cosecha-21-22': 'Cosecha 21-22',
|
|
'cosecha-22-23': 'Cosecha 22-23',
|
|
'cosecha-23-24': 'Cosecha 23-24',
|
|
'cosecha-24-25': 'Cosecha 24-25',
|
|
'cosecha-25-26': 'Cosecha 25-26'
|
|
}
|
|
|
|
const label = props.selectedPreset && props.selectedPreset !== 'custom' ? presetLabels[props.selectedPreset] : undefined
|
|
if (label) {
|
|
return label
|
|
}
|
|
|
|
// Si es personalizado, mostrar las fechas
|
|
return `${props.fechaDesde || '—'} → ${props.fechaHasta || '—'}`
|
|
}
|
|
|
|
function removeCliente(id: number) {
|
|
emit('update:selectedClienteIds', props.selectedClienteIds.filter(cid => cid !== id))
|
|
}
|
|
|
|
function removeFechas() {
|
|
emit('update:fechaDesde', null)
|
|
emit('update:fechaHasta', null)
|
|
emit('update:selectedPreset', '')
|
|
}
|
|
|
|
function removeTipo(tipo: string) {
|
|
emit('update:selectedTipos', props.selectedTipos.filter(t => t !== tipo))
|
|
}
|
|
|
|
function removeEstado(estado: string) {
|
|
emit('update:selectedEstados', props.selectedEstados.filter(e => e !== estado))
|
|
}
|
|
|
|
function removeUbicacion(ubicacion: string) {
|
|
emit('update:selectedUbicaciones', props.selectedUbicaciones.filter(u => u !== ubicacion))
|
|
}
|
|
|
|
function removeCalidad(calidad: string) {
|
|
emit('update:selectedCalidades', props.selectedCalidades.filter(c => c !== calidad))
|
|
}
|
|
|
|
function removeAnulados() {
|
|
emit('update:includeAnulados', false)
|
|
}
|
|
|
|
function removeNoFilter() {
|
|
emit('update:noFilter', false)
|
|
}
|
|
|
|
function resetearAHoy() {
|
|
emit('update:selectedClienteIds', [])
|
|
emit('update:selectedPreset', 'hoy')
|
|
emit('update:selectedTipos', [])
|
|
emit('update:selectedEstados', [])
|
|
emit('update:selectedUbicaciones', [])
|
|
emit('update:selectedCalidades', [])
|
|
emit('update:includeAnulados', false)
|
|
emit('update:noFilter', false)
|
|
}
|
|
</script>
|