All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m6s
337 lines
10 KiB
Vue
337 lines
10 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">
|
|
<div class="flex items-center justify-center gap-3 mb-3">
|
|
<img
|
|
src="/icon-192x192.png"
|
|
alt="RioCata Logo"
|
|
class="w-12 h-12 sm:w-16 sm:h-16"
|
|
>
|
|
<h1 class="text-4xl sm:text-5xl font-bold cata-text dark:cata-glow">
|
|
RioCata
|
|
</h1>
|
|
</div>
|
|
<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>
|