All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 47s
Se aplica el mismo tema café/dorado a todos los InputMenu de filtros: - Tipo de café - Estado - Ubicación - Calidad Estilos aplicados (igual que ClienteMultiSelector): - Fondo: --brand-surface (#1f180f) - Borde: --brand-border (#3a2a16) - Focus ring: ring-1 con --brand-primary (#e0c080) - Item highlighted: rgba(224,192,128,0.12) - Tags: color --brand-primary con bordes dorados - Transición suave en el borde de focus Todos los filtros ahora mantienen la coherencia visual con el resto de la aplicación.
336 lines
13 KiB
Vue
336 lines
13 KiB
Vue
<template>
|
|
<div class="flex flex-col gap-6">
|
|
<!-- Fila 1: Selector de Clientes -->
|
|
<Transition
|
|
enter-active-class="transition-all duration-300 ease-out"
|
|
enter-from-class="opacity-0 -translate-y-2"
|
|
enter-to-class="opacity-100 translate-y-0"
|
|
leave-active-class="transition-all duration-200 ease-in"
|
|
leave-from-class="opacity-100 translate-y-0"
|
|
leave-to-class="opacity-0 -translate-y-2"
|
|
>
|
|
<div v-if="selectedClienteIds.length === 0" class="flex flex-col gap-3">
|
|
<h3 class="text-sm font-semibold text-[var(--brand-text-muted)] uppercase tracking-wide">
|
|
Clientes
|
|
</h3>
|
|
<ClienteSelector
|
|
:clientes="clientes"
|
|
:selected-ids="selectedClienteIds"
|
|
@update:selected-ids="emit('update:selectedClienteIds', $event)"
|
|
/>
|
|
</div>
|
|
</Transition>
|
|
|
|
<!-- Fila 2: Selector de Rango de Fechas -->
|
|
<div class="flex flex-col gap-3">
|
|
<h3 class="text-sm font-semibold text-[var(--brand-text-muted)] uppercase tracking-wide">
|
|
Rango de Fechas
|
|
</h3>
|
|
<DateRangeSelector
|
|
:selected-preset="selectedPreset"
|
|
:fecha-desde="fechaDesde"
|
|
:fecha-hasta="fechaHasta"
|
|
@update:selected-preset="emit('update:selectedPreset', $event)"
|
|
@update:fecha-desde="emit('update:fechaDesde', $event)"
|
|
@update:fecha-hasta="emit('update:fechaHasta', $event)"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Fila 3: Filtros Avanzados (grid de 4 columnas) -->
|
|
<div v-if="hasAvailableAdvancedFilters" class="flex flex-col gap-3">
|
|
<h3 class="text-sm font-semibold text-[var(--brand-text-muted)] uppercase tracking-wide">
|
|
Filtros Avanzados
|
|
</h3>
|
|
<div class="grid grid-cols-2 gap-3">
|
|
<!-- Tipo de Café -->
|
|
<Transition
|
|
enter-active-class="transition-all duration-300 ease-out"
|
|
enter-from-class="opacity-0 scale-95"
|
|
enter-to-class="opacity-100 scale-100"
|
|
leave-active-class="transition-all duration-200 ease-in"
|
|
leave-from-class="opacity-100 scale-100"
|
|
leave-to-class="opacity-0 scale-95"
|
|
>
|
|
<div v-if="selectedTipos.length === 0" class="flex flex-col gap-2">
|
|
<label class="text-xs text-[var(--brand-text-muted)]">Tipo</label>
|
|
<UInputMenu
|
|
v-model="selectedTipos"
|
|
:items="tiposOptions"
|
|
value-key="value"
|
|
multiple
|
|
placeholder="Todos"
|
|
size="xs"
|
|
icon="i-lucide-coffee"
|
|
class="w-full"
|
|
:ui="{
|
|
root: 'focus-within:ring-1 focus-within:ring-[var(--brand-primary)] transition-shadow',
|
|
base: 'bg-[var(--brand-surface)] text-[var(--brand-text)] border border-[var(--brand-border)] focus:ring-1 focus:ring-[var(--brand-primary)] focus:border-[var(--brand-primary)]',
|
|
placeholder: 'placeholder-[var(--brand-text-muted)]',
|
|
leadingIcon: 'text-[var(--brand-text-muted)]',
|
|
content: 'bg-[var(--brand-surface)] border border-[var(--brand-border)]',
|
|
item: 'text-[var(--brand-text)] data-highlighted:bg-[rgba(224,192,128,0.12)] data-highlighted:text-[var(--brand-text)]',
|
|
tagsItem: 'bg-[rgba(224,192,128,0.14)] border border-[rgba(224,192,128,0.28)] text-[var(--brand-primary)]',
|
|
tagsItemText: 'text-[var(--brand-primary)]',
|
|
tagsItemDelete: 'text-[var(--brand-text-muted)] hover:text-[var(--brand-primary)] hover:bg-[rgba(224,192,128,0.2)]'
|
|
}"
|
|
/>
|
|
</div>
|
|
</Transition>
|
|
|
|
<!-- Estado -->
|
|
<Transition
|
|
enter-active-class="transition-all duration-300 ease-out"
|
|
enter-from-class="opacity-0 scale-95"
|
|
enter-to-class="opacity-100 scale-100"
|
|
leave-active-class="transition-all duration-200 ease-in"
|
|
leave-from-class="opacity-100 scale-100"
|
|
leave-to-class="opacity-0 scale-95"
|
|
>
|
|
<div v-if="selectedEstados.length === 0" class="flex flex-col gap-2">
|
|
<label class="text-xs text-[var(--brand-text-muted)]">Estado</label>
|
|
<UInputMenu
|
|
v-model="selectedEstados"
|
|
:items="estadosOptions"
|
|
value-key="value"
|
|
multiple
|
|
placeholder="Todos"
|
|
size="xs"
|
|
icon="i-lucide-check-circle"
|
|
class="w-full"
|
|
:ui="{
|
|
root: 'focus-within:ring-1 focus-within:ring-[var(--brand-primary)] transition-shadow',
|
|
base: 'bg-[var(--brand-surface)] text-[var(--brand-text)] border border-[var(--brand-border)] focus:ring-1 focus:ring-[var(--brand-primary)] focus:border-[var(--brand-primary)]',
|
|
placeholder: 'placeholder-[var(--brand-text-muted)]',
|
|
leadingIcon: 'text-[var(--brand-text-muted)]',
|
|
content: 'bg-[var(--brand-surface)] border border-[var(--brand-border)]',
|
|
item: 'text-[var(--brand-text)] data-highlighted:bg-[rgba(224,192,128,0.12)] data-highlighted:text-[var(--brand-text)]',
|
|
tagsItem: 'bg-[rgba(224,192,128,0.14)] border border-[rgba(224,192,128,0.28)] text-[var(--brand-primary)]',
|
|
tagsItemText: 'text-[var(--brand-primary)]',
|
|
tagsItemDelete: 'text-[var(--brand-text-muted)] hover:text-[var(--brand-primary)] hover:bg-[rgba(224,192,128,0.2)]'
|
|
}"
|
|
/>
|
|
</div>
|
|
</Transition>
|
|
|
|
<!-- Ubicación -->
|
|
<Transition
|
|
enter-active-class="transition-all duration-300 ease-out"
|
|
enter-from-class="opacity-0 scale-95"
|
|
enter-to-class="opacity-100 scale-100"
|
|
leave-active-class="transition-all duration-200 ease-in"
|
|
leave-from-class="opacity-100 scale-100"
|
|
leave-to-class="opacity-0 scale-95"
|
|
>
|
|
<div v-if="selectedUbicaciones.length === 0" class="flex flex-col gap-2">
|
|
<label class="text-xs text-[var(--brand-text-muted)]">Ubicación</label>
|
|
<UInputMenu
|
|
v-model="selectedUbicaciones"
|
|
:items="ubicacionesOptions"
|
|
value-key="value"
|
|
multiple
|
|
placeholder="Todas"
|
|
size="xs"
|
|
icon="i-lucide-map-pin"
|
|
class="w-full"
|
|
:ui="{
|
|
root: 'focus-within:ring-1 focus-within:ring-[var(--brand-primary)] transition-shadow',
|
|
base: 'bg-[var(--brand-surface)] text-[var(--brand-text)] border border-[var(--brand-border)] focus:ring-1 focus:ring-[var(--brand-primary)] focus:border-[var(--brand-primary)]',
|
|
placeholder: 'placeholder-[var(--brand-text-muted)]',
|
|
leadingIcon: 'text-[var(--brand-text-muted)]',
|
|
content: 'bg-[var(--brand-surface)] border border-[var(--brand-border)]',
|
|
item: 'text-[var(--brand-text)] data-highlighted:bg-[rgba(224,192,128,0.12)] data-highlighted:text-[var(--brand-text)]',
|
|
tagsItem: 'bg-[rgba(224,192,128,0.14)] border border-[rgba(224,192,128,0.28)] text-[var(--brand-primary)]',
|
|
tagsItemText: 'text-[var(--brand-primary)]',
|
|
tagsItemDelete: 'text-[var(--brand-text-muted)] hover:text-[var(--brand-primary)] hover:bg-[rgba(224,192,128,0.2)]'
|
|
}"
|
|
/>
|
|
</div>
|
|
</Transition>
|
|
|
|
<!-- Calidad -->
|
|
<Transition
|
|
enter-active-class="transition-all duration-300 ease-out"
|
|
enter-from-class="opacity-0 scale-95"
|
|
enter-to-class="opacity-100 scale-100"
|
|
leave-active-class="transition-all duration-200 ease-in"
|
|
leave-from-class="opacity-100 scale-100"
|
|
leave-to-class="opacity-0 scale-95"
|
|
>
|
|
<div v-if="selectedCalidades.length === 0" class="flex flex-col gap-2">
|
|
<label class="text-xs text-[var(--brand-text-muted)]">Calidad</label>
|
|
<UInputMenu
|
|
v-model="selectedCalidades"
|
|
:items="calidadesOptions"
|
|
value-key="value"
|
|
multiple
|
|
placeholder="Todas"
|
|
size="xs"
|
|
icon="i-lucide-star"
|
|
class="w-full"
|
|
:ui="{
|
|
root: 'focus-within:ring-1 focus-within:ring-[var(--brand-primary)] transition-shadow',
|
|
base: 'bg-[var(--brand-surface)] text-[var(--brand-text)] border border-[var(--brand-border)] focus:ring-1 focus:ring-[var(--brand-primary)] focus:border-[var(--brand-primary)]',
|
|
placeholder: 'placeholder-[var(--brand-text-muted)]',
|
|
leadingIcon: 'text-[var(--brand-text-muted)]',
|
|
content: 'bg-[var(--brand-surface)] border border-[var(--brand-border)]',
|
|
item: 'text-[var(--brand-text)] data-highlighted:bg-[rgba(224,192,128,0.12)] data-highlighted:text-[var(--brand-text)]',
|
|
tagsItem: 'bg-[rgba(224,192,128,0.14)] border border-[rgba(224,192,128,0.28)] text-[var(--brand-primary)]',
|
|
tagsItemText: 'text-[var(--brand-primary)]',
|
|
tagsItemDelete: 'text-[var(--brand-text-muted)] hover:text-[var(--brand-primary)] hover:bg-[rgba(224,192,128,0.2)]'
|
|
}"
|
|
/>
|
|
</div>
|
|
</Transition>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Checkbox de incluir anulados -->
|
|
<Transition
|
|
enter-active-class="transition-all duration-300 ease-out"
|
|
enter-from-class="opacity-0 -translate-y-2"
|
|
enter-to-class="opacity-100 translate-y-0"
|
|
leave-active-class="transition-all duration-200 ease-in"
|
|
leave-from-class="opacity-100 translate-y-0"
|
|
leave-to-class="opacity-0 -translate-y-2"
|
|
>
|
|
<div v-if="!includeAnulados" class="flex flex-col gap-3">
|
|
<UCheckbox v-model="includeAnulados" label="Incluir anulados" size="sm" />
|
|
</div>
|
|
</Transition>
|
|
|
|
<!-- Botón "Sin filtros" -->
|
|
<Transition
|
|
enter-active-class="transition-all duration-300 ease-out"
|
|
enter-from-class="opacity-0 -translate-y-2"
|
|
enter-to-class="opacity-100 translate-y-0"
|
|
leave-active-class="transition-all duration-200 ease-in"
|
|
leave-from-class="opacity-100 translate-y-0"
|
|
leave-to-class="opacity-0 -translate-y-2"
|
|
>
|
|
<div v-if="!noFilter && !hasAnyActiveFilter" class="flex flex-col gap-3">
|
|
<UButton
|
|
@click="handleNoFilter"
|
|
size="sm"
|
|
variant="outline"
|
|
color="neutral"
|
|
icon="i-lucide-ban"
|
|
block
|
|
>
|
|
Sin filtros
|
|
</UButton>
|
|
<p class="text-xs text-[var(--brand-text-muted)] text-center">
|
|
Confirma que no quieres aplicar ningún filtro
|
|
</p>
|
|
</div>
|
|
</Transition>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
interface Cliente {
|
|
id: number
|
|
name: string
|
|
cedula?: number
|
|
ubicacion?: string
|
|
telefono?: string
|
|
}
|
|
|
|
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 {
|
|
clientes: Cliente[]
|
|
selectedClienteIds: number[]
|
|
selectedPreset: PresetValue
|
|
fechaDesde: string | null
|
|
fechaHasta: string | null
|
|
selectedTipos: string[]
|
|
selectedEstados: string[]
|
|
selectedUbicaciones: string[]
|
|
selectedCalidades: string[]
|
|
tiposOptions: any[]
|
|
estadosOptions: any[]
|
|
ubicacionesOptions: any[]
|
|
calidadesOptions: any[]
|
|
includeAnulados: boolean
|
|
noFilter: boolean
|
|
}
|
|
|
|
const props = defineProps<Props>()
|
|
|
|
const emit = defineEmits<{
|
|
'update:selectedClienteIds': [value: number[]]
|
|
'update:selectedPreset': [value: PresetValue]
|
|
'update:fechaDesde': [value: string | null]
|
|
'update:fechaHasta': [value: string | null]
|
|
'update:selectedTipos': [value: string[]]
|
|
'update:selectedEstados': [value: string[]]
|
|
'update:selectedUbicaciones': [value: string[]]
|
|
'update:selectedCalidades': [value: string[]]
|
|
'update:includeAnulados': [value: boolean]
|
|
'update:noFilter': [value: boolean]
|
|
'hideFiltros': []
|
|
}>()
|
|
|
|
const selectedTipos = computed({
|
|
get: () => props.selectedTipos,
|
|
set: (value) => emit('update:selectedTipos', value)
|
|
})
|
|
|
|
const selectedEstados = computed({
|
|
get: () => props.selectedEstados,
|
|
set: (value) => emit('update:selectedEstados', value)
|
|
})
|
|
|
|
const selectedUbicaciones = computed({
|
|
get: () => props.selectedUbicaciones,
|
|
set: (value) => emit('update:selectedUbicaciones', value)
|
|
})
|
|
|
|
const selectedCalidades = computed({
|
|
get: () => props.selectedCalidades,
|
|
set: (value) => emit('update:selectedCalidades', value)
|
|
})
|
|
|
|
const includeAnulados = computed({
|
|
get: () => props.includeAnulados,
|
|
set: (value) => emit('update:includeAnulados', value)
|
|
})
|
|
|
|
const noFilter = computed({
|
|
get: () => props.noFilter,
|
|
set: (value) => emit('update:noFilter', value)
|
|
})
|
|
|
|
const hasAvailableAdvancedFilters = computed(() => {
|
|
return selectedTipos.value.length === 0 ||
|
|
selectedEstados.value.length === 0 ||
|
|
selectedUbicaciones.value.length === 0 ||
|
|
selectedCalidades.value.length === 0
|
|
})
|
|
|
|
const hasAnyActiveFilter = computed(() => {
|
|
return props.selectedClienteIds.length > 0 ||
|
|
props.fechaDesde !== null ||
|
|
props.fechaHasta !== null ||
|
|
props.selectedTipos.length > 0 ||
|
|
props.selectedEstados.length > 0 ||
|
|
props.selectedUbicaciones.length > 0 ||
|
|
props.selectedCalidades.length > 0 ||
|
|
props.includeAnulados
|
|
})
|
|
|
|
function clearDates() {
|
|
emit('update:fechaDesde', null)
|
|
emit('update:fechaHasta', null)
|
|
emit('update:selectedPreset', '')
|
|
}
|
|
|
|
function handleNoFilter() {
|
|
emit('update:noFilter', true)
|
|
emit('hideFiltros')
|
|
}
|
|
</script>
|