Feat: Implementar selección múltiple y filtrado de subcategorías
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m5s
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m5s
- Cambiar subcategoriaActiva a subcategoriasActivas (array) para permitir selección múltiple - Agregar flex-wrap a barra de subcategorías en lugar de scroll horizontal - Implementar toggleSubcategoria para agregar/quitar subcategorías - Pasar subcategoriasActivas a FormularioMuestra como prop - Agregar helpers deberMostrarSeccion para filtrar inputs - Aplicar v-if a todas las secciones según subcategorías activas - Botón Limpiar Todo para resetear filtros
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
</h4>
|
||||
|
||||
<!-- Selector de Familia de Fragancia/Aroma -->
|
||||
<div class="form-section">
|
||||
<div v-if="mostrarFraganciaAroma" class="form-section">
|
||||
<CataSelectorFamilia
|
||||
tipo="fragancia-aroma"
|
||||
label="Familia de Fragancia y Aroma"
|
||||
@@ -17,7 +17,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Selector de Familia de Sabor -->
|
||||
<div class="form-section">
|
||||
<div v-if="mostrarSaborOrganoleptica" class="form-section">
|
||||
<CataSelectorFamilia
|
||||
tipo="sabor"
|
||||
label="Familia de Sabor"
|
||||
@@ -34,7 +34,7 @@
|
||||
</h4>
|
||||
|
||||
<!-- Sliders de Fragancia -->
|
||||
<div class="form-section">
|
||||
<div v-if="mostrarFraganciaSlider" class="form-section">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h5 class="form-section-title cata-text" :style="{ color: getCategoryColor('fragancia') }">Fragancia</h5>
|
||||
<div class="flex gap-2">
|
||||
@@ -63,7 +63,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Sliders de Aroma -->
|
||||
<div class="form-section">
|
||||
<div v-if="mostrarAromaSlider" class="form-section">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h5 class="form-section-title cata-text" :style="{ color: getCategoryColor('aroma') }">Aroma</h5>
|
||||
<div class="flex gap-2">
|
||||
@@ -92,7 +92,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Sliders de Sabor -->
|
||||
<div class="form-section">
|
||||
<div v-if="mostrarSaborSlider" class="form-section">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h5 class="form-section-title cata-text" :style="{ color: getCategoryColor('sabor') }">Sabor</h5>
|
||||
<div class="flex gap-2">
|
||||
@@ -121,7 +121,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Sliders de Sabor Residual -->
|
||||
<div class="form-section">
|
||||
<div v-if="mostrarSaborResidualSlider" class="form-section">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h5 class="form-section-title cata-text" :style="{ color: getCategoryColor('saborResidual') }">Sabor Residual</h5>
|
||||
<div class="flex gap-2">
|
||||
@@ -150,7 +150,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Sliders de Acidez -->
|
||||
<div class="form-section">
|
||||
<div v-if="mostrarAcidezSlider" class="form-section">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h5 class="form-section-title cata-text" :style="{ color: getCategoryColor('acidez') }">Acidez</h5>
|
||||
<div class="flex gap-2">
|
||||
@@ -179,7 +179,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Sliders de Dulzor -->
|
||||
<div class="form-section">
|
||||
<div v-if="mostrarDulzorSlider" class="form-section">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h5 class="form-section-title cata-text" :style="{ color: getCategoryColor('dulzor') }">Dulzor</h5>
|
||||
<div class="flex gap-2">
|
||||
@@ -208,7 +208,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Sliders de Sensación en Boca -->
|
||||
<div class="form-section">
|
||||
<div v-if="mostrarSensacionBocaSlider" class="form-section">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h5 class="form-section-title cata-text" :style="{ color: getCategoryColor('sensacionBoca') }">Sensación en la Boca</h5>
|
||||
<div class="flex gap-2">
|
||||
@@ -237,7 +237,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Sliders de Impresión Global -->
|
||||
<div class="form-section">
|
||||
<div v-if="mostrarImpresionGlobalSlider" class="form-section">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h5 class="form-section-title cata-text" :style="{ color: getCategoryColor('impresionGlobal') }">Impresión Global</h5>
|
||||
<div class="flex gap-2">
|
||||
@@ -707,7 +707,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Muestra, NotaSeleccionada, TipoDefecto, SensacionBoca, GustoPredominante } from '~/types/catacion'
|
||||
import type { TabCatacion } from '~/composables/useCatacion'
|
||||
import type { TabCatacion, Subcategoria } from '~/composables/useCatacion'
|
||||
import { SENSACIONES_BOCA, GUSTOS_PREDOMINANTES, TIPOS_DEFECTOS } from '~/types/catacion'
|
||||
|
||||
interface FormularioMuestraProps {
|
||||
@@ -715,9 +715,13 @@ interface FormularioMuestraProps {
|
||||
muestra: Muestra
|
||||
/** Tab activa */
|
||||
tabActiva: TabCatacion
|
||||
/** Subcategorías activas (filtros) */
|
||||
subcategoriasActivas?: Subcategoria[]
|
||||
}
|
||||
|
||||
const props = defineProps<FormularioMuestraProps>()
|
||||
const props = withDefaults(defineProps<FormularioMuestraProps>(), {
|
||||
subcategoriasActivas: () => [],
|
||||
})
|
||||
|
||||
const { actualizarIntensidad: actualizarIntensidadCatacion } = useCatacion()
|
||||
const { getCategoryColor } = useCategoryColors()
|
||||
@@ -727,6 +731,31 @@ const sensacionesBoca = SENSACIONES_BOCA
|
||||
const gustosPredominantes = GUSTOS_PREDOMINANTES
|
||||
const tiposDefectos = TIPOS_DEFECTOS
|
||||
|
||||
// Helpers para filtrado por subcategorías
|
||||
const deberMostrarSeccion = (subcategorias: Subcategoria[]): boolean => {
|
||||
// Si no hay filtros activos, mostrar todo
|
||||
if (!props.subcategoriasActivas || props.subcategoriasActivas.length === 0) return true
|
||||
|
||||
// Si hay filtros, verificar si alguno coincide
|
||||
return subcategorias.some(sub => props.subcategoriasActivas?.includes(sub))
|
||||
}
|
||||
|
||||
// Para Organoléptica
|
||||
const mostrarFraganciaAroma = computed(() => deberMostrarSeccion(['fragancia-aroma']))
|
||||
const mostrarSaborOrganoleptica = computed(() => deberMostrarSeccion(['sabor']))
|
||||
const mostrarSensacionBocaOrganoleptica = computed(() => deberMostrarSeccion(['sensacion-boca']))
|
||||
const mostrarGustosPredominantes = computed(() => deberMostrarSeccion(['gustos-predominantes']))
|
||||
|
||||
// Para Descriptiva/Afectiva
|
||||
const mostrarFraganciaSlider = computed(() => deberMostrarSeccion(['descriptiva', 'afectiva', 'fragancia']))
|
||||
const mostrarAromaSlider = computed(() => deberMostrarSeccion(['descriptiva', 'afectiva', 'aroma']))
|
||||
const mostrarSaborSlider = computed(() => deberMostrarSeccion(['descriptiva', 'afectiva', 'sabor']))
|
||||
const mostrarSaborResidualSlider = computed(() => deberMostrarSeccion(['descriptiva', 'afectiva', 'sabor-residual']))
|
||||
const mostrarAcidezSlider = computed(() => deberMostrarSeccion(['descriptiva', 'afectiva', 'acidez']))
|
||||
const mostrarDulzorSlider = computed(() => deberMostrarSeccion(['descriptiva', 'afectiva', 'dulzor']))
|
||||
const mostrarSensacionBocaSlider = computed(() => deberMostrarSeccion(['descriptiva', 'afectiva', 'sensacion-boca']))
|
||||
const mostrarImpresionGlobalSlider = computed(() => deberMostrarSeccion(['descriptiva', 'afectiva', 'impresion-global']))
|
||||
|
||||
// Estado local para otras notas
|
||||
const otrasNotasLocal = ref(props.muestra.otrasNotas)
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ export const useCatacion = () => {
|
||||
|
||||
// Estado de la UI
|
||||
const tabActiva = useState<TabCatacion>('tab-activa', () => 'organoleptica')
|
||||
const subcategoriaActiva = useState<Subcategoria>('subcategoria-activa', () => null)
|
||||
const subcategoriasActivas = useState<Subcategoria[]>('subcategorias-activas', () => [])
|
||||
const accordionAbierto = useState<string[]>('accordion-abierto', () => [])
|
||||
|
||||
/**
|
||||
@@ -303,7 +303,7 @@ export const useCatacion = () => {
|
||||
|
||||
// Estado de la UI
|
||||
tabActiva,
|
||||
subcategoriaActiva,
|
||||
subcategoriasActivas,
|
||||
accordionAbierto,
|
||||
|
||||
// Estadísticas
|
||||
|
||||
@@ -129,25 +129,25 @@
|
||||
<!-- Subcategorías -->
|
||||
<div v-if="subcategoriasDisponibles.length > 0" class="subcategorias-container border-t">
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="flex overflow-x-auto gap-1 py-1.5">
|
||||
<div class="flex flex-wrap gap-1 py-1.5">
|
||||
<button
|
||||
v-for="subcategoria in subcategoriasDisponibles"
|
||||
:key="subcategoria.value"
|
||||
:class="[
|
||||
'subcategoria-chip',
|
||||
{ 'subcategoria-chip-active': subcategoriaActiva === subcategoria.value },
|
||||
{ 'subcategoria-chip-active': subcategoriasActivas.includes(subcategoria.value) },
|
||||
]"
|
||||
@click="subcategoriaActiva = subcategoria.value"
|
||||
@click="toggleSubcategoria(subcategoria.value)"
|
||||
>
|
||||
{{ subcategoria.label }}
|
||||
</button>
|
||||
<button
|
||||
v-if="subcategoriaActiva !== null"
|
||||
v-if="subcategoriasActivas.length > 0"
|
||||
class="subcategoria-chip subcategoria-chip-clear"
|
||||
@click="subcategoriaActiva = null"
|
||||
@click="subcategoriasActivas = []"
|
||||
>
|
||||
<UIcon name="i-lucide-x" class="w-3 h-3 inline mr-1" />
|
||||
Limpiar
|
||||
Limpiar Todo
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -180,6 +180,7 @@
|
||||
v-if="sesionActiva && obtenerMuestraPorValue(item.value)"
|
||||
:muestra="obtenerMuestraPorValue(item.value)!"
|
||||
:tab-activa="tabActiva"
|
||||
:subcategorias-activas="subcategoriasActivas"
|
||||
/>
|
||||
<div v-else class="text-error">
|
||||
Error: No se pudo cargar la muestra (value: {{ item.value }})
|
||||
@@ -215,7 +216,7 @@ const {
|
||||
cargando,
|
||||
error,
|
||||
tabActiva,
|
||||
subcategoriaActiva,
|
||||
subcategoriasActivas,
|
||||
accordionAbierto,
|
||||
exportarSesion,
|
||||
eliminarSesionActual,
|
||||
@@ -337,10 +338,24 @@ onMounted(async () => {
|
||||
// Cambiar tab
|
||||
const cambiarTab = (tab: TabCatacion) => {
|
||||
tabActiva.value = tab
|
||||
subcategoriaActiva.value = null // Limpiar subcategoría al cambiar de tab
|
||||
subcategoriasActivas.value = [] // Limpiar subcategorías al cambiar de tab
|
||||
mostrarMenu.value = false
|
||||
}
|
||||
|
||||
// Toggle subcategoría (selección múltiple)
|
||||
const toggleSubcategoria = (subcategoria: Subcategoria) => {
|
||||
if (!subcategoria) return
|
||||
|
||||
const index = subcategoriasActivas.value.indexOf(subcategoria)
|
||||
if (index > -1) {
|
||||
// Ya está seleccionada, removerla
|
||||
subcategoriasActivas.value = subcategoriasActivas.value.filter(s => s !== subcategoria)
|
||||
} else {
|
||||
// No está seleccionada, agregarla
|
||||
subcategoriasActivas.value = [...subcategoriasActivas.value, subcategoria]
|
||||
}
|
||||
}
|
||||
|
||||
// Formatear fecha
|
||||
const formatearFecha = (fecha: string): string => {
|
||||
const date = new Date(fecha)
|
||||
|
||||
Reference in New Issue
Block a user