Feat: Reorganizar tabs y permitir selección múltiple de categorías en notas
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m1s
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m1s
- **Nuevas tabs reorganizadas:** - Organoléptica: Selectores de familia de fragancia-aroma y sabor - Descriptiva/Afectiva: Todos los sliders de intensidad (incluye impresión global) - Defectos: Tazas no uniformes, defectuosas y tipo de defecto - Impresión Global: Vista completa con todos los componentes - **Selector de categorías mejorado:** - Permitir selección múltiple de categorías padre - Las subcategorías son la unión de las subcategorías de los padres seleccionados - Permitir selección múltiple de subcategorías - Actualizar resumen visual de selección - **Tipos actualizados:** - NotaSeleccionada ahora usa arrays para categorias y subcategorias - TabCatacion actualizado con las nuevas tabs - Funciones de actualización modificadas para trabajar con arrays - **Correcciones TypeScript:** - Usar JSON.parse(JSON.stringify()) para crear copias mutables de arrays readonly - Resolver incompatibilidades de tipos entre readonly y mutable arrays
This commit is contained in:
@@ -1,9 +1,36 @@
|
||||
<template>
|
||||
<div class="formulario-muestra p-4 space-y-6">
|
||||
<!-- Tab 1: Fragancia y Aroma -->
|
||||
<div v-if="tabActiva === 'fragancia-aroma'" class="tab-content cata-fade-in">
|
||||
<!-- Tab 1: Organoléptica (solo selectores de familia) -->
|
||||
<div v-if="tabActiva === 'organoleptica'" class="tab-content cata-fade-in">
|
||||
<h4 class="tab-section-title cata-text mb-4">
|
||||
Fragancia y Aroma
|
||||
Características Organolépticas
|
||||
</h4>
|
||||
|
||||
<!-- Selector de Familia de Fragancia/Aroma -->
|
||||
<div class="form-section">
|
||||
<CataSelectorFamilia
|
||||
tipo="fragancia-aroma"
|
||||
label="Familia de Fragancia y Aroma"
|
||||
:model-value="muestra.fraganciaAromaNotas"
|
||||
@update:model-value="actualizarFraganciaAroma"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Selector de Familia de Sabor -->
|
||||
<div class="form-section">
|
||||
<CataSelectorFamilia
|
||||
tipo="sabor"
|
||||
label="Familia de Sabor"
|
||||
:model-value="muestra.saborNotas"
|
||||
@update:model-value="actualizarSabor"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab 2: Descriptiva/Afectiva (todos los sliders incluyendo impresión global) -->
|
||||
<div v-if="tabActiva === 'descriptiva-afectiva'" class="tab-content cata-fade-in">
|
||||
<h4 class="tab-section-title cata-text mb-4">
|
||||
Intensidades Descriptivas y Afectivas
|
||||
</h4>
|
||||
|
||||
<!-- Sliders de Fragancia -->
|
||||
@@ -44,23 +71,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Selector de Familia de Fragancia/Aroma -->
|
||||
<div class="form-section">
|
||||
<CataSelectorFamilia
|
||||
tipo="fragancia-aroma"
|
||||
label="Familia de Fragancia y Aroma"
|
||||
:model-value="muestra.fraganciaAromaNotas"
|
||||
@update:model-value="actualizarFraganciaAroma"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab 2: Sabor -->
|
||||
<div v-if="tabActiva === 'sabor'" class="tab-content cata-fade-in">
|
||||
<h4 class="tab-section-title cata-text mb-4">
|
||||
Sabor y Características
|
||||
</h4>
|
||||
|
||||
<!-- Sliders de Sabor -->
|
||||
<div class="form-section">
|
||||
<h5 class="form-section-title cata-text">Sabor</h5>
|
||||
@@ -156,23 +166,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Selector de Familia de Sabor -->
|
||||
<div class="form-section">
|
||||
<CataSelectorFamilia
|
||||
tipo="sabor"
|
||||
label="Familia de Sabor"
|
||||
:model-value="muestra.saborNotas"
|
||||
@update:model-value="actualizarSabor"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab 3: Impresión Global -->
|
||||
<div v-if="tabActiva === 'impresion-global'" class="tab-content cata-fade-in">
|
||||
<h4 class="tab-section-title cata-text mb-4">
|
||||
Impresión Global y Detalles Finales
|
||||
</h4>
|
||||
|
||||
<!-- Sliders de Impresión Global -->
|
||||
<div class="form-section">
|
||||
<h5 class="form-section-title cata-text">Impresión Global</h5>
|
||||
@@ -191,6 +184,13 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab 3: Defectos (tazas y defectos) -->
|
||||
<div v-if="tabActiva === 'defectos'" class="tab-content cata-fade-in">
|
||||
<h4 class="tab-section-title cata-text mb-4">
|
||||
Defectos y Uniformidad
|
||||
</h4>
|
||||
|
||||
<!-- Tazas No Uniformes -->
|
||||
<div class="form-section">
|
||||
@@ -232,66 +232,306 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sensaciones en Boca (selección múltiple) -->
|
||||
<div class="form-section">
|
||||
<label class="block text-sm font-medium mb-2 cata-text">
|
||||
Sensaciones en la Boca (múltiples)
|
||||
</label>
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 gap-2">
|
||||
<button
|
||||
v-for="sensacion in sensacionesBoca"
|
||||
:key="sensacion"
|
||||
type="button"
|
||||
:class="[
|
||||
'cata-checkbox',
|
||||
{ 'cata-checkbox-checked': muestra.sensacionEnBoca.includes(sensacion) },
|
||||
]"
|
||||
@click="toggleSensacionBoca(sensacion)"
|
||||
>
|
||||
<span class="cata-text text-sm">{{ sensacion }}</span>
|
||||
</button>
|
||||
<!-- Tab 4: Impresión Global (muestra TODOS los componentes) -->
|
||||
<div v-if="tabActiva === 'impresion-global'" class="tab-content cata-fade-in">
|
||||
<h4 class="tab-section-title cata-text mb-4">
|
||||
Visión Global Completa
|
||||
</h4>
|
||||
|
||||
<!-- Sección: Organoléptica -->
|
||||
<div class="global-section mb-6 p-4 cata-outline-box rounded-lg">
|
||||
<h5 class="global-section-title cata-text mb-4">Características Organolépticas</h5>
|
||||
|
||||
<!-- Selector de Familia de Fragancia/Aroma -->
|
||||
<div class="form-section mb-4">
|
||||
<CataSelectorFamilia
|
||||
tipo="fragancia-aroma"
|
||||
label="Familia de Fragancia y Aroma"
|
||||
:model-value="muestra.fraganciaAromaNotas"
|
||||
@update:model-value="actualizarFraganciaAroma"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Selector de Familia de Sabor -->
|
||||
<div class="form-section">
|
||||
<CataSelectorFamilia
|
||||
tipo="sabor"
|
||||
label="Familia de Sabor"
|
||||
:model-value="muestra.saborNotas"
|
||||
@update:model-value="actualizarSabor"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Gustos Predominantes (máx 2) -->
|
||||
<div class="form-section">
|
||||
<label class="block text-sm font-medium mb-2 cata-text">
|
||||
Gustos Predominantes (mín 1, máx 2)
|
||||
</label>
|
||||
<div class="grid grid-cols-2 sm:grid-cols-5 gap-2">
|
||||
<button
|
||||
v-for="gusto in gustosPredominantes"
|
||||
:key="gusto"
|
||||
type="button"
|
||||
:class="[
|
||||
'cata-checkbox',
|
||||
{ 'cata-checkbox-checked': muestra.gustosPredominantes.includes(gusto) },
|
||||
]"
|
||||
:disabled="!muestra.gustosPredominantes.includes(gusto) && muestra.gustosPredominantes.length >= 2"
|
||||
@click="toggleGustoPredominante(gusto)"
|
||||
>
|
||||
<span class="cata-text">{{ gusto }}</span>
|
||||
</button>
|
||||
<!-- Sección: Intensidades -->
|
||||
<div class="global-section mb-6 p-4 cata-outline-box rounded-lg">
|
||||
<h5 class="global-section-title cata-text mb-4">Intensidades Descriptivas y Afectivas</h5>
|
||||
|
||||
<!-- Fragancia -->
|
||||
<div class="form-section mb-4">
|
||||
<h6 class="form-subsection-title cata-text">Fragancia</h6>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<CataSliderIntensidad
|
||||
tipo="descriptiva"
|
||||
label="Descriptiva"
|
||||
:model-value="muestra.intensidades.fragancia.descriptiva"
|
||||
@update:model-value="(v) => actualizarIntensidad('fragancia', 'descriptiva', v)"
|
||||
/>
|
||||
<CataSliderIntensidad
|
||||
tipo="afectiva"
|
||||
label="Afectiva"
|
||||
:model-value="muestra.intensidades.fragancia.afectiva"
|
||||
@update:model-value="(v) => actualizarIntensidad('fragancia', 'afectiva', v)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Aroma -->
|
||||
<div class="form-section mb-4">
|
||||
<h6 class="form-subsection-title cata-text">Aroma</h6>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<CataSliderIntensidad
|
||||
tipo="descriptiva"
|
||||
label="Descriptiva"
|
||||
:model-value="muestra.intensidades.aroma.descriptiva"
|
||||
@update:model-value="(v) => actualizarIntensidad('aroma', 'descriptiva', v)"
|
||||
/>
|
||||
<CataSliderIntensidad
|
||||
tipo="afectiva"
|
||||
label="Afectiva"
|
||||
:model-value="muestra.intensidades.aroma.afectiva"
|
||||
@update:model-value="(v) => actualizarIntensidad('aroma', 'afectiva', v)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sabor -->
|
||||
<div class="form-section mb-4">
|
||||
<h6 class="form-subsection-title cata-text">Sabor</h6>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<CataSliderIntensidad
|
||||
tipo="descriptiva"
|
||||
label="Descriptiva"
|
||||
:model-value="muestra.intensidades.sabor.descriptiva"
|
||||
@update:model-value="(v) => actualizarIntensidad('sabor', 'descriptiva', v)"
|
||||
/>
|
||||
<CataSliderIntensidad
|
||||
tipo="afectiva"
|
||||
label="Afectiva"
|
||||
:model-value="muestra.intensidades.sabor.afectiva"
|
||||
@update:model-value="(v) => actualizarIntensidad('sabor', 'afectiva', v)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sabor Residual -->
|
||||
<div class="form-section mb-4">
|
||||
<h6 class="form-subsection-title cata-text">Sabor Residual</h6>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<CataSliderIntensidad
|
||||
tipo="descriptiva"
|
||||
label="Descriptiva"
|
||||
:model-value="muestra.intensidades.saborResidual.descriptiva"
|
||||
@update:model-value="(v) => actualizarIntensidad('saborResidual', 'descriptiva', v)"
|
||||
/>
|
||||
<CataSliderIntensidad
|
||||
tipo="afectiva"
|
||||
label="Afectiva"
|
||||
:model-value="muestra.intensidades.saborResidual.afectiva"
|
||||
@update:model-value="(v) => actualizarIntensidad('saborResidual', 'afectiva', v)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Acidez -->
|
||||
<div class="form-section mb-4">
|
||||
<h6 class="form-subsection-title cata-text">Acidez</h6>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<CataSliderIntensidad
|
||||
tipo="descriptiva"
|
||||
label="Descriptiva"
|
||||
:model-value="muestra.intensidades.acidez.descriptiva"
|
||||
@update:model-value="(v) => actualizarIntensidad('acidez', 'descriptiva', v)"
|
||||
/>
|
||||
<CataSliderIntensidad
|
||||
tipo="afectiva"
|
||||
label="Afectiva"
|
||||
:model-value="muestra.intensidades.acidez.afectiva"
|
||||
@update:model-value="(v) => actualizarIntensidad('acidez', 'afectiva', v)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dulzor -->
|
||||
<div class="form-section mb-4">
|
||||
<h6 class="form-subsection-title cata-text">Dulzor</h6>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<CataSliderIntensidad
|
||||
tipo="descriptiva"
|
||||
label="Descriptiva"
|
||||
:model-value="muestra.intensidades.dulzor.descriptiva"
|
||||
@update:model-value="(v) => actualizarIntensidad('dulzor', 'descriptiva', v)"
|
||||
/>
|
||||
<CataSliderIntensidad
|
||||
tipo="afectiva"
|
||||
label="Afectiva"
|
||||
:model-value="muestra.intensidades.dulzor.afectiva"
|
||||
@update:model-value="(v) => actualizarIntensidad('dulzor', 'afectiva', v)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sensación en Boca -->
|
||||
<div class="form-section mb-4">
|
||||
<h6 class="form-subsection-title cata-text">Sensación en la Boca</h6>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<CataSliderIntensidad
|
||||
tipo="descriptiva"
|
||||
label="Descriptiva"
|
||||
:model-value="muestra.intensidades.sensacionBoca.descriptiva"
|
||||
@update:model-value="(v) => actualizarIntensidad('sensacionBoca', 'descriptiva', v)"
|
||||
/>
|
||||
<CataSliderIntensidad
|
||||
tipo="afectiva"
|
||||
label="Afectiva"
|
||||
:model-value="muestra.intensidades.sensacionBoca.afectiva"
|
||||
@update:model-value="(v) => actualizarIntensidad('sensacionBoca', 'afectiva', v)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Impresión Global -->
|
||||
<div class="form-section">
|
||||
<h6 class="form-subsection-title cata-text">Impresión Global</h6>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<CataSliderIntensidad
|
||||
tipo="descriptiva"
|
||||
label="Descriptiva"
|
||||
:model-value="muestra.intensidades.impresionGlobal.descriptiva"
|
||||
@update:model-value="(v) => actualizarIntensidad('impresionGlobal', 'descriptiva', v)"
|
||||
/>
|
||||
<CataSliderIntensidad
|
||||
tipo="afectiva"
|
||||
label="Afectiva"
|
||||
:model-value="muestra.intensidades.impresionGlobal.afectiva"
|
||||
@update:model-value="(v) => actualizarIntensidad('impresionGlobal', 'afectiva', v)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Otras Notas -->
|
||||
<div class="form-section">
|
||||
<label class="block text-sm font-medium mb-2 cata-text">
|
||||
Otras Notas
|
||||
</label>
|
||||
<textarea
|
||||
v-model="otrasNotasLocal"
|
||||
class="cata-input w-full min-h-[100px] resize-y"
|
||||
placeholder="Notas adicionales sobre el café, cuerpo, balance, etc..."
|
||||
@blur="actualizarOtrasNotas"
|
||||
/>
|
||||
<!-- Sección: Defectos -->
|
||||
<div class="global-section mb-6 p-4 cata-outline-box rounded-lg">
|
||||
<h5 class="global-section-title cata-text mb-4">Defectos y Uniformidad</h5>
|
||||
|
||||
<!-- Tazas No Uniformes -->
|
||||
<div class="form-section mb-4">
|
||||
<CataSelectorTazas
|
||||
tipo="uniformes"
|
||||
label="Tazas NO Uniformes"
|
||||
:model-value="muestra.tazasNoUniformes"
|
||||
@update:model-value="actualizarTazasNoUniformes"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Tazas Defectuosas -->
|
||||
<div class="form-section mb-4">
|
||||
<CataSelectorTazas
|
||||
tipo="defectuosas"
|
||||
label="Tazas Defectuosas"
|
||||
:model-value="muestra.tazasDefectuosas"
|
||||
@update:model-value="actualizarTazasDefectuosas"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Tipo de Defecto -->
|
||||
<div v-if="muestra.tazasDefectuosas.length > 0" class="form-section">
|
||||
<label class="block text-sm font-medium mb-2 cata-text">
|
||||
Tipo de Defecto
|
||||
</label>
|
||||
<div class="grid grid-cols-2 sm:grid-cols-4 gap-2">
|
||||
<button
|
||||
v-for="tipo in tiposDefectos"
|
||||
:key="tipo || 'ninguno'"
|
||||
type="button"
|
||||
:class="[
|
||||
'cata-checkbox',
|
||||
{ 'cata-checkbox-checked': muestra.defecto === tipo },
|
||||
]"
|
||||
@click="actualizarDefecto(tipo)"
|
||||
>
|
||||
<span class="cata-text">{{ tipo || 'Ninguno' }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sección: Detalles Adicionales -->
|
||||
<div class="global-section mb-6 p-4 cata-outline-box rounded-lg">
|
||||
<h5 class="global-section-title cata-text mb-4">Detalles Adicionales</h5>
|
||||
|
||||
<!-- Sensaciones en Boca (selección múltiple) -->
|
||||
<div class="form-section mb-4">
|
||||
<label class="block text-sm font-medium mb-2 cata-text">
|
||||
Sensaciones en la Boca (múltiples)
|
||||
</label>
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 gap-2">
|
||||
<button
|
||||
v-for="sensacion in sensacionesBoca"
|
||||
:key="sensacion"
|
||||
type="button"
|
||||
:class="[
|
||||
'cata-checkbox',
|
||||
{ 'cata-checkbox-checked': muestra.sensacionEnBoca.includes(sensacion) },
|
||||
]"
|
||||
@click="toggleSensacionBoca(sensacion)"
|
||||
>
|
||||
<span class="cata-text text-sm">{{ sensacion }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Gustos Predominantes (máx 2) -->
|
||||
<div class="form-section mb-4">
|
||||
<label class="block text-sm font-medium mb-2 cata-text">
|
||||
Gustos Predominantes (mín 1, máx 2)
|
||||
</label>
|
||||
<div class="grid grid-cols-2 sm:grid-cols-5 gap-2">
|
||||
<button
|
||||
v-for="gusto in gustosPredominantes"
|
||||
:key="gusto"
|
||||
type="button"
|
||||
:class="[
|
||||
'cata-checkbox',
|
||||
{ 'cata-checkbox-checked': muestra.gustosPredominantes.includes(gusto) },
|
||||
]"
|
||||
:disabled="!muestra.gustosPredominantes.includes(gusto) && muestra.gustosPredominantes.length >= 2"
|
||||
@click="toggleGustoPredominante(gusto)"
|
||||
>
|
||||
<span class="cata-text">{{ gusto }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Otras Notas -->
|
||||
<div class="form-section">
|
||||
<label class="block text-sm font-medium mb-2 cata-text">
|
||||
Otras Notas
|
||||
</label>
|
||||
<textarea
|
||||
v-model="otrasNotasLocal"
|
||||
class="cata-input w-full min-h-[100px] resize-y"
|
||||
placeholder="Notas adicionales sobre el café, cuerpo, balance, etc..."
|
||||
@blur="actualizarOtrasNotas"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Puntaje Final (solo lectura) -->
|
||||
<div class="form-section">
|
||||
<div class="puntaje-final cata-outline-box p-4 rounded-md">
|
||||
<div class="global-section p-4 cata-outline-box rounded-lg">
|
||||
<div class="puntaje-final">
|
||||
<div class="flex items-baseline justify-between">
|
||||
<span class="text-sm cata-text opacity-75">Puntaje Final:</span>
|
||||
<span class="text-3xl font-bold cata-text">{{ muestra.puntajeFinal }}</span>
|
||||
@@ -343,8 +583,8 @@ const { actualizarFraganciaAroma: actualizarFraganciaAromaCatacion } = useCataci
|
||||
const actualizarFraganciaAroma = async (nota: NotaSeleccionada) => {
|
||||
await actualizarFraganciaAromaCatacion(
|
||||
props.muestra.muestraId,
|
||||
nota.categoria,
|
||||
nota.subcategoria,
|
||||
nota.categorias,
|
||||
nota.subcategorias,
|
||||
nota.notaEspecifica
|
||||
)
|
||||
}
|
||||
@@ -354,8 +594,8 @@ const { actualizarSabor: actualizarSaborCatacion } = useCatacion()
|
||||
const actualizarSabor = async (nota: NotaSeleccionada) => {
|
||||
await actualizarSaborCatacion(
|
||||
props.muestra.muestraId,
|
||||
nota.categoria,
|
||||
nota.subcategoria,
|
||||
nota.categorias,
|
||||
nota.subcategorias,
|
||||
nota.notaEspecifica
|
||||
)
|
||||
}
|
||||
@@ -437,6 +677,14 @@ watch(() => props.muestra.otrasNotas, (newVal) => {
|
||||
border-color: color-mix(in srgb, var(--cata-primary) 30%, transparent);
|
||||
}
|
||||
|
||||
.global-section-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -451,6 +699,13 @@ watch(() => props.muestra.otrasNotas, (newVal) => {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.form-subsection-title {
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
opacity: 0.7;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.puntaje-final {
|
||||
text-align: center;
|
||||
}
|
||||
@@ -467,5 +722,9 @@ watch(() => props.muestra.otrasNotas, (newVal) => {
|
||||
.tab-section-title {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.global-section-title {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
<span v-if="required" class="text-error">*</span>
|
||||
</label>
|
||||
|
||||
<!-- Nivel 1: Categorías principales -->
|
||||
<!-- Nivel 1: Categorías principales (selección múltiple) -->
|
||||
<div class="nivel-container">
|
||||
<h4 class="nivel-title cata-text">Categoría</h4>
|
||||
<h4 class="nivel-title cata-text">Categorías (selección múltiple)</h4>
|
||||
<div class="categorias-grid">
|
||||
<button
|
||||
v-for="categoria in categoriasDisponibles"
|
||||
@@ -18,16 +18,16 @@
|
||||
'categoria-item',
|
||||
'cata-checkbox',
|
||||
{
|
||||
'cata-checkbox-checked': modelValue.categoria === categoria,
|
||||
'cata-checkbox-checked': modelValue.categorias.includes(categoria),
|
||||
'disabled': disabled,
|
||||
},
|
||||
]"
|
||||
:disabled="disabled"
|
||||
@click="seleccionarCategoria(categoria)"
|
||||
@click="toggleCategoria(categoria)"
|
||||
>
|
||||
<span class="categoria-text cata-text">{{ categoria }}</span>
|
||||
<UIcon
|
||||
v-if="modelValue.categoria === categoria"
|
||||
v-if="modelValue.categorias.includes(categoria)"
|
||||
name="i-lucide-check-circle"
|
||||
class="categoria-check"
|
||||
/>
|
||||
@@ -35,9 +35,9 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Nivel 2: Subcategorías (si aplica) -->
|
||||
<!-- Nivel 2: Subcategorías (unión de todas las categorías seleccionadas, selección múltiple) -->
|
||||
<div v-if="subcategoriasDisponibles.length > 0" class="nivel-container mt-4">
|
||||
<h4 class="nivel-title cata-text">Subcategoría</h4>
|
||||
<h4 class="nivel-title cata-text">Subcategorías (selección múltiple)</h4>
|
||||
<div class="subcategorias-grid">
|
||||
<button
|
||||
v-for="subcategoria in subcategoriasDisponibles"
|
||||
@@ -47,16 +47,16 @@
|
||||
'subcategoria-item',
|
||||
'cata-checkbox',
|
||||
{
|
||||
'cata-checkbox-checked': modelValue.subcategoria === subcategoria,
|
||||
'cata-checkbox-checked': modelValue.subcategorias.includes(subcategoria),
|
||||
'disabled': disabled,
|
||||
},
|
||||
]"
|
||||
:disabled="disabled"
|
||||
@click="seleccionarSubcategoria(subcategoria)"
|
||||
@click="toggleSubcategoria(subcategoria)"
|
||||
>
|
||||
<span class="subcategoria-text cata-text">{{ subcategoria }}</span>
|
||||
<UIcon
|
||||
v-if="modelValue.subcategoria === subcategoria"
|
||||
v-if="modelValue.subcategorias.includes(subcategoria)"
|
||||
name="i-lucide-check"
|
||||
class="subcategoria-check"
|
||||
/>
|
||||
@@ -65,7 +65,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Nivel 3: Nota específica (input libre) -->
|
||||
<div v-if="modelValue.categoria" class="nivel-container mt-4">
|
||||
<div v-if="modelValue.categorias.length > 0" class="nivel-container mt-4">
|
||||
<h4 class="nivel-title cata-text">Nota Específica</h4>
|
||||
<input
|
||||
v-model="notaEspecificaLocal"
|
||||
@@ -81,13 +81,13 @@
|
||||
<div v-if="seleccionCompleta" class="mt-4 p-3 cata-outline-box rounded-md">
|
||||
<p class="text-xs font-semibold cata-text mb-1">Selección actual:</p>
|
||||
<p class="text-sm cata-text">
|
||||
{{ modelValue.categoria }}
|
||||
<template v-if="modelValue.subcategoria">
|
||||
→ {{ modelValue.subcategoria }}
|
||||
</template>
|
||||
<template v-if="modelValue.notaEspecifica">
|
||||
→ <span class="font-semibold">{{ modelValue.notaEspecifica }}</span>
|
||||
</template>
|
||||
<span class="font-semibold">Categorías:</span> {{ modelValue.categorias.join(', ') }}
|
||||
</p>
|
||||
<p v-if="modelValue.subcategorias.length > 0" class="text-sm cata-text mt-1">
|
||||
<span class="font-semibold">Subcategorías:</span> {{ modelValue.subcategorias.join(', ') }}
|
||||
</p>
|
||||
<p v-if="modelValue.notaEspecifica" class="text-sm cata-text mt-1">
|
||||
<span class="font-semibold">Nota:</span> {{ modelValue.notaEspecifica }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -134,62 +134,94 @@ const categoriasDisponibles = computed<CategoriaNotaPrincipal[]>(() => {
|
||||
return Object.keys(FAMILIAS_NOTAS_ESTRUCTURA) as CategoriaNotaPrincipal[]
|
||||
})
|
||||
|
||||
// Subcategorías disponibles según la categoría seleccionada
|
||||
// Subcategorías disponibles (unión de todas las categorías seleccionadas)
|
||||
const subcategoriasDisponibles = computed<string[]>(() => {
|
||||
if (!props.modelValue.categoria) return []
|
||||
if (props.modelValue.categorias.length === 0) return []
|
||||
|
||||
const familia = FAMILIAS_NOTAS_ESTRUCTURA[props.modelValue.categoria as CategoriaNotaPrincipal]
|
||||
if (!familia || typeof familia !== 'object') return []
|
||||
const subcategoriasSet = new Set<string>()
|
||||
|
||||
return Object.keys(familia)
|
||||
// Iterar sobre cada categoría seleccionada y agregar sus subcategorías
|
||||
props.modelValue.categorias.forEach((categoria) => {
|
||||
const familia = FAMILIAS_NOTAS_ESTRUCTURA[categoria as CategoriaNotaPrincipal]
|
||||
if (familia && typeof familia === 'object') {
|
||||
Object.keys(familia).forEach((subcategoria) => {
|
||||
subcategoriasSet.add(subcategoria)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return Array.from(subcategoriasSet).sort()
|
||||
})
|
||||
|
||||
// Verifica si la selección está completa
|
||||
const seleccionCompleta = computed(() => {
|
||||
return props.modelValue.categoria !== null
|
||||
return props.modelValue.categorias.length > 0
|
||||
})
|
||||
|
||||
// Seleccionar categoría
|
||||
const seleccionarCategoria = (categoria: CategoriaNotaPrincipal) => {
|
||||
// Toggle categoría (agregar o quitar)
|
||||
const toggleCategoria = (categoria: CategoriaNotaPrincipal) => {
|
||||
if (props.disabled) return
|
||||
|
||||
// Si se selecciona la misma categoría, deseleccionar todo
|
||||
if (props.modelValue.categoria === categoria) {
|
||||
emit('update:modelValue', {
|
||||
categoria: null,
|
||||
subcategoria: null,
|
||||
notaEspecifica: null,
|
||||
})
|
||||
notaEspecificaLocal.value = ''
|
||||
return
|
||||
const categorias = [...props.modelValue.categorias]
|
||||
const index = categorias.indexOf(categoria)
|
||||
|
||||
if (index > -1) {
|
||||
// Quitar categoría
|
||||
categorias.splice(index, 1)
|
||||
|
||||
// Si ya no hay categorías, limpiar subcategorías también
|
||||
if (categorias.length === 0) {
|
||||
emit('update:modelValue', {
|
||||
categorias: [],
|
||||
subcategorias: [],
|
||||
notaEspecifica: null,
|
||||
})
|
||||
notaEspecificaLocal.value = ''
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Agregar categoría
|
||||
categorias.push(categoria)
|
||||
}
|
||||
|
||||
// Seleccionar nueva categoría y resetear subcategoría y nota
|
||||
emit('update:modelValue', {
|
||||
categoria,
|
||||
subcategoria: null,
|
||||
notaEspecifica: null,
|
||||
// Filtrar subcategorías: mantener solo las que siguen siendo válidas
|
||||
const subcategoriasValidas = new Set<string>()
|
||||
categorias.forEach((cat) => {
|
||||
const familia = FAMILIAS_NOTAS_ESTRUCTURA[cat as CategoriaNotaPrincipal]
|
||||
if (familia && typeof familia === 'object') {
|
||||
Object.keys(familia).forEach((sub) => subcategoriasValidas.add(sub))
|
||||
}
|
||||
})
|
||||
notaEspecificaLocal.value = ''
|
||||
}
|
||||
|
||||
// Seleccionar subcategoría
|
||||
const seleccionarSubcategoria = (subcategoria: string) => {
|
||||
if (props.disabled) return
|
||||
const subcategoriasFiltradas = props.modelValue.subcategorias.filter((sub) =>
|
||||
subcategoriasValidas.has(sub)
|
||||
)
|
||||
|
||||
// Si se selecciona la misma subcategoría, deseleccionar
|
||||
if (props.modelValue.subcategoria === subcategoria) {
|
||||
emit('update:modelValue', {
|
||||
...props.modelValue,
|
||||
subcategoria: null,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Seleccionar nueva subcategoría
|
||||
emit('update:modelValue', {
|
||||
...props.modelValue,
|
||||
subcategoria,
|
||||
categorias,
|
||||
subcategorias: subcategoriasFiltradas,
|
||||
})
|
||||
}
|
||||
|
||||
// Toggle subcategoría (agregar o quitar)
|
||||
const toggleSubcategoria = (subcategoria: string) => {
|
||||
if (props.disabled) return
|
||||
|
||||
const subcategorias = [...props.modelValue.subcategorias]
|
||||
const index = subcategorias.indexOf(subcategoria)
|
||||
|
||||
if (index > -1) {
|
||||
// Quitar subcategoría
|
||||
subcategorias.splice(index, 1)
|
||||
} else {
|
||||
// Agregar subcategoría
|
||||
subcategorias.push(subcategoria)
|
||||
}
|
||||
|
||||
emit('update:modelValue', {
|
||||
...props.modelValue,
|
||||
subcategorias,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user