Files
cataRio/nuxt4/app/pages/cata/index.vue
josedario87 76dcff31d6
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m2s
Feat: Reorganizar UI - Sistema de catación como página principal
- Crear componente CataUserInfo con estilo mobile-first del sistema de catación
- Mover "/" para redirigir automáticamente a /cata
- Incluir información del usuario en página de catación
- Mantener estilo consistente con diseño outline/terminal del sistema
- Optimizar componente de usuario para mobile (compacto y funcional)
2025-10-18 02:05:54 -06:00

330 lines
9.9 KiB
Vue

<template>
<div class="cata-page cata-text">
<div class="container mx-auto px-4 py-8 max-w-4xl">
<!-- Información del usuario -->
<div v-if="isAuthenticated" class="mb-6 cata-fade-in">
<CataUserInfo />
</div>
<!-- Header -->
<div class="text-center mb-12">
<h1 class="text-4xl sm:text-5xl font-bold mb-3 cata-text dark:cata-glow">
RioCata
</h1>
<p class="text-lg cata-text opacity-75">
Sistema de Catación de Café
</p>
<hr class="cata-divider my-6 max-w-xs mx-auto">
</div>
<!-- Loading State -->
<div v-if="cargando" class="flex justify-center items-center py-20">
<div class="loading-spinner"></div>
</div>
<!-- Main Content -->
<div v-else class="space-y-6">
<!-- Sesión activa encontrada -->
<div v-if="tieneSecion" class="sesion-activa cata-fade-in">
<div class="cata-outline-box p-6 rounded-lg">
<div class="flex items-start justify-between mb-4">
<div>
<h2 class="text-2xl font-semibold mb-2 cata-text">
Sesión en Progreso
</h2>
<p class="text-sm cata-text opacity-75">
Encontramos una sesión de catación activa
</p>
</div>
<UIcon name="i-lucide-coffee" class="w-10 h-10 opacity-50" />
</div>
<!-- Información de la sesión -->
<div v-if="sesionActiva" class="space-y-3 mb-6">
<div class="info-row">
<span class="info-label cata-text">Catador:</span>
<span class="info-value cata-text font-semibold">{{ sesionActiva.catador }}</span>
</div>
<div class="info-row">
<span class="info-label cata-text">Fecha:</span>
<span class="info-value cata-text">{{ formatearFecha(sesionActiva.fecha) }}</span>
</div>
<div class="info-row">
<span class="info-label cata-text">Muestras:</span>
<span class="info-value cata-text">{{ sesionActiva.cantidadMuestras }}</span>
</div>
<div v-if="estadisticasSesion" class="info-row">
<span class="info-label cata-text">Progreso:</span>
<span class="info-value cata-text">{{ estadisticasSesion.porcentajeCompletitud }}%</span>
</div>
</div>
<!-- Botones de acción -->
<div class="flex flex-col sm:flex-row gap-3">
<button
class="cata-button flex-1"
@click="continuarSesion"
>
<UIcon name="i-lucide-play" class="w-4 h-4 inline mr-2" />
Continuar Sesión
</button>
<button
class="cata-button"
@click="mostrarDialogoNueva = true"
>
<UIcon name="i-lucide-file-plus" class="w-4 h-4 inline mr-2" />
Nueva Sesión
</button>
</div>
<!-- Advertencia de nueva sesión -->
<div v-if="mostrarDialogoNueva" class="mt-4 p-4 bg-transparent border-2 border-error/50 rounded-md">
<p class="text-sm cata-text text-error mb-3">
<UIcon name="i-lucide-alert-triangle" class="w-4 h-4 inline mr-1" />
Crear una nueva sesión eliminará la sesión actual permanentemente.
</p>
<div class="flex gap-2">
<button
class="cata-button px-3 py-1.5 text-sm"
@click="mostrarDialogoNueva = false"
>
Cancelar
</button>
<button
class="cata-button px-3 py-1.5 text-sm border-error text-error"
@click="mostrarFormNuevaSesion"
>
Continuar
</button>
</div>
</div>
</div>
</div>
<!-- No hay sesión activa -->
<div v-else class="no-sesion cata-fade-in">
<div class="cata-outline-box p-8 rounded-lg text-center">
<UIcon name="i-lucide-clipboard-list" class="w-16 h-16 mx-auto mb-4 opacity-50" />
<h2 class="text-2xl font-semibold mb-2 cata-text">
No hay sesión activa
</h2>
<p class="text-sm cata-text opacity-75 mb-6">
Comienza una nueva sesión de catación
</p>
<button
class="cata-button px-6 py-3"
@click="mostrarFormulario = true"
>
<UIcon name="i-lucide-plus-circle" class="w-5 h-5 inline mr-2" />
Nueva Sesión
</button>
</div>
</div>
<!-- Formulario de nueva sesión -->
<div v-if="mostrarFormulario" class="form-nueva-sesion cata-fade-in mt-6">
<div class="cata-outline-box p-6 rounded-lg">
<h3 class="text-xl font-semibold mb-4 cata-text">
Nueva Sesión de Catación
</h3>
<form @submit.prevent="crearSesion" class="space-y-4">
<!-- Nombre del catador -->
<div>
<label for="catador" class="block text-sm font-medium mb-2 cata-text">
Nombre del Catador <span class="text-error">*</span>
</label>
<input
id="catador"
v-model="formData.catador"
type="text"
required
class="cata-input w-full"
placeholder="Ingresa tu nombre"
>
</div>
<!-- Cantidad de muestras -->
<div>
<label for="cantidadMuestras" class="block text-sm font-medium mb-2 cata-text">
Cantidad de Muestras <span class="text-error">*</span>
</label>
<input
id="cantidadMuestras"
v-model.number="formData.cantidadMuestras"
type="number"
required
min="3"
max="7"
class="cata-input w-full"
placeholder="Entre 3 y 7 muestras"
>
<p class="text-xs cata-text opacity-60 mt-1">
Selecciona entre 3 y 7 muestras
</p>
</div>
<!-- Botones -->
<div class="flex gap-3 pt-2">
<button
type="button"
class="cata-button flex-1"
@click="cancelarFormulario"
>
Cancelar
</button>
<button
type="submit"
class="cata-button flex-1 border-2"
:disabled="creandoSesion"
>
<span v-if="creandoSesion">Creando...</span>
<span v-else>Crear Sesión</span>
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
const { isAuthenticated } = useAuthentik()
const {
sesionActiva,
cargando,
error,
estadisticasSesion,
crearNuevaSesion,
} = useCatacion()
const { inicializar, tieneSecion } = useIndexedDB()
// Estado del formulario
const mostrarFormulario = ref(false)
const mostrarDialogoNueva = ref(false)
const creandoSesion = ref(false)
const formData = reactive({
catador: '',
cantidadMuestras: 5,
})
// Inicializar al montar
onMounted(async () => {
await inicializar()
})
// Formatear fecha
const formatearFecha = (fecha: string): string => {
const date = new Date(fecha)
return date.toLocaleDateString('es-ES', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
})
}
// Continuar sesión existente
const continuarSesion = () => {
navigateTo('/cata/sesion')
}
// Mostrar formulario de nueva sesión
const mostrarFormNuevaSesion = () => {
mostrarDialogoNueva.value = false
mostrarFormulario.value = true
}
// Crear nueva sesión
const crearSesion = async () => {
try {
creandoSesion.value = true
// Validaciones
if (!formData.catador.trim()) {
alert('Por favor ingresa el nombre del catador')
return
}
if (formData.cantidadMuestras < 3 || formData.cantidadMuestras > 7) {
alert('La cantidad de muestras debe estar entre 3 y 7')
return
}
// Crear sesión
await crearNuevaSesion(formData.catador.trim(), formData.cantidadMuestras)
// Navegar a la sesión
navigateTo('/cata/sesion')
} catch (err) {
console.error('Error al crear sesión:', err)
alert('Error al crear la sesión. Por favor intenta de nuevo.')
} finally {
creandoSesion.value = false
}
}
// Cancelar formulario
const cancelarFormulario = () => {
mostrarFormulario.value = false
mostrarDialogoNueva.value = false
formData.catador = ''
formData.cantidadMuestras = 5
}
// Título de la página
useHead({
title: 'RioCata - Inicio',
})
</script>
<style scoped>
.info-row {
display: flex;
justify-content: space-between;
align-items: baseline;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid;
border-color: color-mix(in srgb, var(--cata-primary) 20%, transparent);
}
.info-row:last-child {
border-bottom: 0;
}
.info-label {
font-size: 0.875rem;
opacity: 0.75;
}
.info-value {
font-size: 1rem;
}
/* Loading spinner */
.loading-spinner {
width: 3rem;
height: 3rem;
border-radius: 9999px;
border: 3px solid color-mix(in srgb, var(--cata-primary) 20%, transparent);
border-top-color: var(--cata-primary);
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.dark .loading-spinner {
box-shadow: 0 0 20px color-mix(in srgb, var(--cata-primary) 30%, transparent);
}
</style>