Files
seguidorDeLotes/nuxt4/app/components/lotes/Form.vue
josedario87 1478ad9e09
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m6s
mejoras de UI 2
2025-11-22 01:50:46 -06:00

176 lines
4.9 KiB
Vue

<template>
<UCard class="bg-gradient-to-br from-white to-slate-50 dark:from-slate-900 dark:to-slate-950 shadow-lg">
<template #header>
<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-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>
<UFormGroup label="Tipo" name="tipo" required>
<USelect
v-model="formState.tipo"
:items="TIPOS_LOTE"
label-key="label"
value-key="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 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="Ej: { &quot;humedad&quot;: 12.5, &quot;notas&quot;: &quot;café especial&quot; }">
<UTextarea
v-model="metaText"
placeholder='{"humedad": 12.5, "notas": "café especial"}'
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"
variant="outline"
label="Cancelar"
@click="$emit('cancel')"
/>
<UButton
type="submit"
color="primary"
:loading="loading"
:label="lote ? 'Actualizar' : 'Crear'"
/>
</div>
</div>
</UForm>
</UCard>
</template>
<script setup lang="ts">
import type { Lote } from '~/composables/useLotes'
const props = defineProps<{
lote?: Lote | null
}>()
const emit = defineEmits<{
cancel: []
success: [lote: Lote]
}>()
const { createLote, updateLote, TIPOS_LOTE } = useLotes()
const loading = ref(false)
const metaText = ref('')
const formState = ref({
codigo: '',
tipo: '',
cantidad_kg: undefined as number | undefined,
lugar_id: undefined as number | undefined,
})
// Si hay un lote para editar, cargar sus datos
watchEffect(() => {
if (props.lote) {
formState.value = {
codigo: props.lote.codigo || '',
tipo: props.lote.tipo,
cantidad_kg: props.lote.cantidad_kg || undefined,
lugar_id: props.lote.lugar_id || undefined,
}
metaText.value = props.lote.meta ? JSON.stringify(props.lote.meta, null, 2) : ''
}
})
const handleSubmit = async () => {
loading.value = true
try {
// Parsear meta si existe
let meta = null
if (metaText.value.trim()) {
try {
meta = JSON.parse(metaText.value)
} catch (err) {
useToast().add({
title: 'Error',
description: 'El formato JSON de información adicional no es válido',
color: 'red',
})
return
}
}
const data = {
codigo: formState.value.codigo || undefined,
tipo: formState.value.tipo,
cantidad_kg: formState.value.cantidad_kg,
lugar_id: formState.value.lugar_id,
meta,
}
let result: Lote | null = null
if (props.lote) {
// Actualizar lote existente
result = await updateLote(props.lote.id, data)
} else {
// Crear nuevo lote
result = await createLote(data)
}
if (result) {
emit('success', result)
}
} finally {
loading.value = false
}
}
</script>