mejorar de UI
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m4s

This commit is contained in:
2025-11-22 01:44:17 -06:00
parent a395dcbd62
commit 990b87fca0
3 changed files with 142 additions and 73 deletions

View File

@@ -1,64 +1,92 @@
<template> <template>
<UCard> <UCard class="bg-gradient-to-br from-white to-slate-50 dark:from-slate-900 dark:to-slate-950 shadow-lg">
<template #header> <template #header>
<h3 class="text-lg font-semibold"> <div class="flex items-center justify-between">
{{ lote ? 'Editar Lote' : 'Nuevo Lote' }} <div>
</h3> <p class="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400">
{{ lote ? 'Edición' : 'Creación' }} · Lote
</p>
<h3 class="text-xl font-semibold">
{{ lote ? 'Editar Lote' : 'Nuevo Lote' }}
</h3>
<p class="text-sm text-gray-500 dark:text-gray-400">
Completa los campos para registrar el lote en la trazabilidad.
</p>
</div>
<UBadge color="blue" variant="subtle">{{ lote ? 'Edición' : 'Nuevo' }}</UBadge>
</div>
</template> </template>
<UForm :state="formState" @submit="handleSubmit" class="space-y-4"> <UForm :state="formState" @submit="handleSubmit" class="space-y-6">
<UFormGroup label="Código" name="codigo" help="Opcional - Si no se especifica, se generará automáticamente"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<UInput <UFormGroup label="Código" name="codigo" help="Opcional · Se generará si lo dejas vacío">
v-model="formState.codigo" <UInput
placeholder="Ej: UVA-001, SEC-042" v-model="formState.codigo"
/> placeholder="Ej: UVA-001, SEC-042"
</UFormGroup> icon="i-heroicons-hashtag"
/>
</UFormGroup>
<UFormGroup label="Tipo" name="tipo" required> <UFormGroup label="Tipo" name="tipo" required>
<USelect <USelect
v-model="formState.tipo" v-model="formState.tipo"
:options="TIPOS_LOTE" :options="TIPOS_LOTE"
placeholder="Selecciona un tipo" option-attribute="label"
/> value-attribute="value"
</UFormGroup> placeholder="Selecciona un tipo"
searchable
/>
</UFormGroup>
</div>
<UFormGroup label="Cantidad (kg)" name="cantidad_kg"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<UInput <UFormGroup label="Cantidad (kg)" name="cantidad_kg">
v-model.number="formState.cantidad_kg" <UInput
type="number" v-model.number="formState.cantidad_kg"
step="0.01" type="number"
placeholder="0.00" step="0.01"
/> placeholder="0.00"
</UFormGroup> icon="i-heroicons-scale"
/>
</UFormGroup>
<UFormGroup label="Lugar ID" name="lugar_id" help="Opcional - ID del lugar donde se encuentra"> <UFormGroup label="Lugar ID" name="lugar_id" help="Opcional · ID de ubicación">
<UInput <UInput
v-model.number="formState.lugar_id" v-model.number="formState.lugar_id"
type="number" type="number"
placeholder="1" placeholder="1"
/> icon="i-heroicons-map-pin"
</UFormGroup> />
</UFormGroup>
</div>
<UFormGroup label="Información Adicional (JSON)" name="meta" help="Opcional - Datos adicionales en formato JSON"> <UFormGroup label="Información adicional (JSON)" name="meta" help="Ej: { &quot;humedad&quot;: 12.5, &quot;notas&quot;: &quot;café especial&quot; }">
<UTextarea <UTextarea
v-model="metaText" v-model="metaText"
placeholder='{"humedad": 12.5, "notas": "café especial"}' placeholder='{"humedad": 12.5, "notas": "café especial"}'
rows="3" rows="4"
class="font-mono"
/> />
</UFormGroup> </UFormGroup>
<div class="flex gap-2 justify-end"> <div class="flex flex-col md:flex-row md:items-center justify-between gap-3 pt-2">
<UButton <p class="text-xs text-gray-500 dark:text-gray-400">
type="button" Los campos marcados como opcionales se pueden dejar vacíos.
variant="outline" </p>
label="Cancelar" <div class="flex gap-2 justify-end">
@click="$emit('cancel')" <UButton
/> type="button"
<UButton variant="outline"
type="submit" label="Cancelar"
:loading="loading" @click="$emit('cancel')"
:label="lote ? 'Actualizar' : 'Crear'" />
/> <UButton
type="submit"
color="primary"
:loading="loading"
:label="lote ? 'Actualizar' : 'Crear'"
/>
</div>
</div> </div>
</UForm> </UForm>
</UCard> </UCard>

View File

@@ -15,7 +15,10 @@
<div class="flex gap-2"> <div class="flex gap-2">
<USelect <USelect
v-model="filtroTipo" v-model="filtroTipo"
:options="[{ value: '', label: 'Todos los tipos' }, ...TIPOS_LOTE]" :options="selectOptions"
option-attribute="label"
value-attribute="value"
searchable
placeholder="Filtrar por tipo" placeholder="Filtrar por tipo"
class="w-64" class="w-64"
/> />
@@ -118,6 +121,10 @@ console.log('🟢 LotesTable: useLotes() completado')
const lotes = ref<Lote[]>([]) const lotes = ref<Lote[]>([])
const loading = ref(false) const loading = ref(false)
const filtroTipo = ref('') const filtroTipo = ref('')
const selectOptions = computed(() => [
{ value: '', label: 'Todos los tipos' },
...TIPOS_LOTE,
])
const error = ref<string | null>(null) const error = ref<string | null>(null)
const columns: ColumnDef<Lote>[] = [ const columns: ColumnDef<Lote>[] = [

View File

@@ -1,28 +1,44 @@
<template> <template>
<UCard> <UCard class="bg-gradient-to-br from-white to-slate-50 dark:from-slate-900 dark:to-slate-950 shadow-lg">
<template #header> <template #header>
<h3 class="text-lg font-semibold">Nueva Operación</h3> <div class="flex items-center justify-between">
<div>
<p class="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400">Flujo guiado</p>
<h3 class="text-xl font-semibold">Nueva Operación</h3>
<p class="text-sm text-gray-500 dark:text-gray-400">Define el tipo, selecciona inputs y crea los outputs.</p>
</div>
<UBadge color="indigo" variant="subtle">3 pasos</UBadge>
</div>
</template> </template>
<div class="space-y-6"> <div class="space-y-6">
<!-- Paso 1: Tipo de Operación --> <!-- Paso 1: Tipo de Operación -->
<div v-if="step === 1" class="space-y-4"> <div v-if="step === 1" class="space-y-4">
<h4 class="font-medium">Paso 1: Selecciona el tipo de operación</h4> <div class="flex items-center justify-between">
<div>
<h4 class="font-medium text-lg">1. Tipo de operación</h4>
<p class="text-sm text-gray-500 dark:text-gray-400">Selecciona el proceso que ejecutarás.</p>
</div>
<UBadge color="gray" variant="soft">Selecciona uno</UBadge>
</div>
<div class="grid grid-cols-2 gap-3"> <div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<button <button
v-for="tipo in TIPOS_OPERACION" v-for="tipo in TIPOS_OPERACION"
:key="tipo.value" :key="tipo.value"
@click="formState.tipo = tipo.value" @click="formState.tipo = tipo.value"
class="p-4 border-2 rounded-lg transition-all hover:border-primary" class="p-4 border-2 rounded-lg transition-all text-left hover:-translate-y-0.5 hover:shadow-md"
:class="{ :class="{
'border-primary bg-primary/10': formState.tipo === tipo.value, 'border-primary bg-primary/10 ring-2 ring-primary/30': formState.tipo === tipo.value,
'border-gray-200': formState.tipo !== tipo.value, 'border-gray-200 dark:border-slate-700': formState.tipo !== tipo.value,
}" }"
> >
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<UIcon :name="tipo.icon" class="w-6 h-6" /> <UIcon :name="tipo.icon" class="w-6 h-6" />
<span class="font-medium">{{ tipo.label }}</span> <div>
<p class="font-semibold">{{ tipo.label }}</p>
<p class="text-xs text-gray-500 dark:text-gray-400">Seleccionar</p>
</div>
</div> </div>
</button> </button>
</div> </div>
@@ -35,6 +51,7 @@
/> />
<UButton <UButton
label="Siguiente" label="Siguiente"
color="primary"
:disabled="!formState.tipo" :disabled="!formState.tipo"
@click="step = 2" @click="step = 2"
/> />
@@ -43,10 +60,15 @@
<!-- Paso 2: Seleccionar Lotes de Entrada (Inputs) --> <!-- Paso 2: Seleccionar Lotes de Entrada (Inputs) -->
<div v-else-if="step === 2" class="space-y-4"> <div v-else-if="step === 2" class="space-y-4">
<h4 class="font-medium">Paso 2: Selecciona los lotes de entrada (inputs)</h4> <div class="flex items-center justify-between">
<p class="text-sm text-gray-500"> <div>
Estos son los lotes que se usarán en la operación de <strong>{{ getTipoLabel(formState.tipo) }}</strong> <h4 class="font-medium text-lg">2. Lotes de entrada</h4>
</p> <p class="text-sm text-gray-500 dark:text-gray-400">
Usa lotes para la operación de <strong>{{ getTipoLabel(formState.tipo) }}</strong>.
</p>
</div>
<UBadge color="blue" variant="soft">{{ formState.inputs.length }} seleccionados</UBadge>
</div>
<div v-if="loadingLotes" class="text-center py-4"> <div v-if="loadingLotes" class="text-center py-4">
<UIcon name="i-heroicons-arrow-path" class="animate-spin w-6 h-6" /> <UIcon name="i-heroicons-arrow-path" class="animate-spin w-6 h-6" />
@@ -56,10 +78,10 @@
<div <div
v-for="lote in lotesDisponibles" v-for="lote in lotesDisponibles"
:key="lote.id" :key="lote.id"
class="p-3 border rounded-lg cursor-pointer transition-all hover:border-primary" class="p-3 border rounded-lg cursor-pointer transition-all hover:-translate-y-0.5 hover:shadow-sm"
:class="{ :class="{
'border-primary bg-primary/10': isLoteSeleccionado(lote.id), 'border-primary bg-primary/10 ring-1 ring-primary/30': isLoteSeleccionado(lote.id),
'border-gray-200': !isLoteSeleccionado(lote.id), 'border-gray-200 dark:border-slate-700': !isLoteSeleccionado(lote.id),
}" }"
@click="toggleLoteInput(lote)" @click="toggleLoteInput(lote)"
> >
@@ -69,7 +91,7 @@
<UBadge :color="getTipoColor(lote.tipo)" variant="subtle" class="ml-2"> <UBadge :color="getTipoColor(lote.tipo)" variant="subtle" class="ml-2">
{{ getTipoLabel(lote.tipo) }} {{ getTipoLabel(lote.tipo) }}
</UBadge> </UBadge>
<span v-if="lote.cantidad_kg" class="ml-2 text-sm text-gray-600"> <span v-if="lote.cantidad_kg" class="ml-2 text-sm text-gray-600 dark:text-gray-400">
{{ lote.cantidad_kg.toLocaleString('es-AR') }} kg {{ lote.cantidad_kg.toLocaleString('es-AR') }} kg
</span> </span>
</div> </div>
@@ -90,6 +112,7 @@
/> />
<UButton <UButton
label="Siguiente" label="Siguiente"
color="primary"
:disabled="formState.inputs.length === 0" :disabled="formState.inputs.length === 0"
@click="step = 3" @click="step = 3"
/> />
@@ -98,16 +121,19 @@
<!-- Paso 3: Definir Lotes de Salida (Outputs) --> <!-- Paso 3: Definir Lotes de Salida (Outputs) -->
<div v-else-if="step === 3" class="space-y-4"> <div v-else-if="step === 3" class="space-y-4">
<h4 class="font-medium">Paso 3: Define los lotes de salida (outputs)</h4> <div class="flex items-center justify-between">
<p class="text-sm text-gray-500"> <div>
Estos son los lotes nuevos que se crearán como resultado de la operación <h4 class="font-medium text-lg">3. Lotes de salida</h4>
</p> <p class="text-sm text-gray-500 dark:text-gray-400">Define los lotes resultantes de la operación.</p>
</div>
<UBadge color="emerald" variant="soft">{{ formState.outputs.length }} outputs</UBadge>
</div>
<div class="space-y-3"> <div class="space-y-3">
<div <div
v-for="(output, index) in formState.outputs" v-for="(output, index) in formState.outputs"
:key="index" :key="index"
class="p-4 border rounded-lg space-y-3" class="p-4 border rounded-lg space-y-3 bg-white/80 dark:bg-slate-900/60"
> >
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<h5 class="font-medium">Lote de salida {{ index + 1 }}</h5> <h5 class="font-medium">Lote de salida {{ index + 1 }}</h5>
@@ -120,17 +146,24 @@
/> />
</div> </div>
<div class="grid grid-cols-2 gap-3"> <div class="grid grid-cols-1 md:grid-cols-3 gap-3">
<UFormGroup label="Código (opcional)"> <UFormGroup label="Código (opcional)">
<UInput v-model="output.codigo" placeholder="Ej: PRIM-001" /> <UInput v-model="output.codigo" placeholder="Ej: PRIM-001" icon="i-heroicons-hashtag" />
</UFormGroup> </UFormGroup>
<UFormGroup label="Tipo" required> <UFormGroup label="Tipo" required>
<USelect v-model="output.tipo" :options="TIPOS_LOTE" placeholder="Selecciona tipo" /> <USelect
v-model="output.tipo"
:options="TIPOS_LOTE"
option-attribute="label"
value-attribute="value"
placeholder="Selecciona tipo"
searchable
/>
</UFormGroup> </UFormGroup>
<UFormGroup label="Cantidad (kg)"> <UFormGroup label="Cantidad (kg)">
<UInput v-model.number="output.cantidad_kg" type="number" step="0.01" placeholder="0.00" /> <UInput v-model.number="output.cantidad_kg" type="number" step="0.01" placeholder="0.00" icon="i-heroicons-scale" />
</UFormGroup> </UFormGroup>
</div> </div>
</div> </div>
@@ -152,6 +185,7 @@
/> />
<UButton <UButton
label="Crear Operación" label="Crear Operación"
color="primary"
:loading="loading" :loading="loading"
:disabled="formState.outputs.length === 0 || !allOutputsValid" :disabled="formState.outputs.length === 0 || !allOutputsValid"
@click="handleSubmit" @click="handleSubmit"