mejorar de UI
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m4s
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m4s
This commit is contained in:
@@ -1,16 +1,29 @@
|
||||
<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>
|
||||
<h3 class="text-lg font-semibold">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<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>
|
||||
|
||||
<UForm :state="formState" @submit="handleSubmit" class="space-y-4">
|
||||
<UFormGroup label="Código" name="codigo" help="Opcional - Si no se especifica, se generará automáticamente">
|
||||
<UForm :state="formState" @submit="handleSubmit" class="space-y-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<UFormGroup label="Código" name="codigo" help="Opcional · Se generará si lo dejas vacío">
|
||||
<UInput
|
||||
v-model="formState.codigo"
|
||||
placeholder="Ej: UVA-001, SEC-042"
|
||||
icon="i-heroicons-hashtag"
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
@@ -18,35 +31,48 @@
|
||||
<USelect
|
||||
v-model="formState.tipo"
|
||||
:options="TIPOS_LOTE"
|
||||
option-attribute="label"
|
||||
value-attribute="value"
|
||||
placeholder="Selecciona un tipo"
|
||||
searchable
|
||||
/>
|
||||
</UFormGroup>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<UFormGroup label="Cantidad (kg)" name="cantidad_kg">
|
||||
<UInput
|
||||
v-model.number="formState.cantidad_kg"
|
||||
type="number"
|
||||
step="0.01"
|
||||
placeholder="0.00"
|
||||
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
|
||||
v-model.number="formState.lugar_id"
|
||||
type="number"
|
||||
placeholder="1"
|
||||
icon="i-heroicons-map-pin"
|
||||
/>
|
||||
</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: { "humedad": 12.5, "notas": "café especial" }">
|
||||
<UTextarea
|
||||
v-model="metaText"
|
||||
placeholder='{"humedad": 12.5, "notas": "café especial"}'
|
||||
rows="3"
|
||||
rows="4"
|
||||
class="font-mono"
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
<div class="flex flex-col md:flex-row md:items-center justify-between gap-3 pt-2">
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
Los campos marcados como opcionales se pueden dejar vacíos.
|
||||
</p>
|
||||
<div class="flex gap-2 justify-end">
|
||||
<UButton
|
||||
type="button"
|
||||
@@ -56,10 +82,12 @@
|
||||
/>
|
||||
<UButton
|
||||
type="submit"
|
||||
color="primary"
|
||||
:loading="loading"
|
||||
:label="lote ? 'Actualizar' : 'Crear'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</UForm>
|
||||
</UCard>
|
||||
</template>
|
||||
|
||||
@@ -15,7 +15,10 @@
|
||||
<div class="flex gap-2">
|
||||
<USelect
|
||||
v-model="filtroTipo"
|
||||
:options="[{ value: '', label: 'Todos los tipos' }, ...TIPOS_LOTE]"
|
||||
:options="selectOptions"
|
||||
option-attribute="label"
|
||||
value-attribute="value"
|
||||
searchable
|
||||
placeholder="Filtrar por tipo"
|
||||
class="w-64"
|
||||
/>
|
||||
@@ -118,6 +121,10 @@ console.log('🟢 LotesTable: useLotes() completado')
|
||||
const lotes = ref<Lote[]>([])
|
||||
const loading = ref(false)
|
||||
const filtroTipo = ref('')
|
||||
const selectOptions = computed(() => [
|
||||
{ value: '', label: 'Todos los tipos' },
|
||||
...TIPOS_LOTE,
|
||||
])
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
const columns: ColumnDef<Lote>[] = [
|
||||
|
||||
@@ -1,28 +1,44 @@
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Paso 1: Tipo de Operación -->
|
||||
<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
|
||||
v-for="tipo in TIPOS_OPERACION"
|
||||
:key="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="{
|
||||
'border-primary bg-primary/10': formState.tipo === tipo.value,
|
||||
'border-gray-200': formState.tipo !== tipo.value,
|
||||
'border-primary bg-primary/10 ring-2 ring-primary/30': formState.tipo === tipo.value,
|
||||
'border-gray-200 dark:border-slate-700': formState.tipo !== tipo.value,
|
||||
}"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<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>
|
||||
</button>
|
||||
</div>
|
||||
@@ -35,6 +51,7 @@
|
||||
/>
|
||||
<UButton
|
||||
label="Siguiente"
|
||||
color="primary"
|
||||
:disabled="!formState.tipo"
|
||||
@click="step = 2"
|
||||
/>
|
||||
@@ -43,10 +60,15 @@
|
||||
|
||||
<!-- Paso 2: Seleccionar Lotes de Entrada (Inputs) -->
|
||||
<div v-else-if="step === 2" class="space-y-4">
|
||||
<h4 class="font-medium">Paso 2: Selecciona los lotes de entrada (inputs)</h4>
|
||||
<p class="text-sm text-gray-500">
|
||||
Estos son los lotes que se usarán en la operación de <strong>{{ getTipoLabel(formState.tipo) }}</strong>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h4 class="font-medium text-lg">2. Lotes de entrada</h4>
|
||||
<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">
|
||||
<UIcon name="i-heroicons-arrow-path" class="animate-spin w-6 h-6" />
|
||||
@@ -56,10 +78,10 @@
|
||||
<div
|
||||
v-for="lote in lotesDisponibles"
|
||||
: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="{
|
||||
'border-primary bg-primary/10': isLoteSeleccionado(lote.id),
|
||||
'border-gray-200': !isLoteSeleccionado(lote.id),
|
||||
'border-primary bg-primary/10 ring-1 ring-primary/30': isLoteSeleccionado(lote.id),
|
||||
'border-gray-200 dark:border-slate-700': !isLoteSeleccionado(lote.id),
|
||||
}"
|
||||
@click="toggleLoteInput(lote)"
|
||||
>
|
||||
@@ -69,7 +91,7 @@
|
||||
<UBadge :color="getTipoColor(lote.tipo)" variant="subtle" class="ml-2">
|
||||
{{ getTipoLabel(lote.tipo) }}
|
||||
</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
|
||||
</span>
|
||||
</div>
|
||||
@@ -90,6 +112,7 @@
|
||||
/>
|
||||
<UButton
|
||||
label="Siguiente"
|
||||
color="primary"
|
||||
:disabled="formState.inputs.length === 0"
|
||||
@click="step = 3"
|
||||
/>
|
||||
@@ -98,16 +121,19 @@
|
||||
|
||||
<!-- Paso 3: Definir Lotes de Salida (Outputs) -->
|
||||
<div v-else-if="step === 3" class="space-y-4">
|
||||
<h4 class="font-medium">Paso 3: Define los lotes de salida (outputs)</h4>
|
||||
<p class="text-sm text-gray-500">
|
||||
Estos son los lotes nuevos que se crearán como resultado de la operación
|
||||
</p>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h4 class="font-medium text-lg">3. Lotes de salida</h4>
|
||||
<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
|
||||
v-for="(output, index) in formState.outputs"
|
||||
: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">
|
||||
<h5 class="font-medium">Lote de salida {{ index + 1 }}</h5>
|
||||
@@ -120,17 +146,24 @@
|
||||
/>
|
||||
</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)">
|
||||
<UInput v-model="output.codigo" placeholder="Ej: PRIM-001" />
|
||||
<UInput v-model="output.codigo" placeholder="Ej: PRIM-001" icon="i-heroicons-hashtag" />
|
||||
</UFormGroup>
|
||||
|
||||
<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 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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -152,6 +185,7 @@
|
||||
/>
|
||||
<UButton
|
||||
label="Crear Operación"
|
||||
color="primary"
|
||||
:loading="loading"
|
||||
:disabled="formState.outputs.length === 0 || !allOutputsValid"
|
||||
@click="handleSubmit"
|
||||
|
||||
Reference in New Issue
Block a user