All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m6s
Cambios realizados:
1. Favicon:
- Actualizar configuración en app.vue para usar iconos PNG correctos
- Agregar links con tamaños 32x32 y 16x16
- Actualizar theme-color a #16a34a (verde del proyecto)
2. Modal de Crear Operación:
- Reestructurar con slots #header y #body para scroll correcto
- Extraer header del Form.vue y moverlo al modal
- Eliminar UCard del componente Form.vue
- Agregar max-h-[80vh] para limitar altura
- Ahora muestra scrollbar vertical cuando el contenido excede el espacio
3. USelect de filtro de operaciones:
- Corregir de :options a :items (API correcta de NuxtUI v4)
- Agregar label-key y value-key
- Agregar computed selectOptions (igual que en Lotes)
- Cambiar filtroTipo de ref('') a ref<string | null>(null)
- Ahora el filtro funciona correctamente
Archivos modificados:
- app/app.vue: Configuración favicon y modal operaciones
- app/components/operaciones/Form.vue: Eliminar UCard
- app/components/operaciones/Table.vue: Corregir USelect
193 lines
5.4 KiB
Vue
193 lines
5.4 KiB
Vue
<template>
|
|
<div class="space-y-4">
|
|
<div class="flex justify-between items-center">
|
|
<div>
|
|
<h2 class="text-2xl font-bold">Operaciones</h2>
|
|
<p class="text-gray-500">Historial de operaciones de proceso</p>
|
|
</div>
|
|
<UButton
|
|
icon="i-heroicons-plus"
|
|
label="Nueva Operación"
|
|
@click="$emit('create')"
|
|
/>
|
|
</div>
|
|
|
|
<div class="flex gap-2">
|
|
<USelect
|
|
v-model="filtroTipo"
|
|
:items="selectOptions"
|
|
label-key="label"
|
|
value-key="value"
|
|
searchable
|
|
class="w-64"
|
|
/>
|
|
<UButton
|
|
icon="i-heroicons-arrow-path"
|
|
label="Refrescar"
|
|
variant="outline"
|
|
@click="loadOperaciones"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Mensaje de error -->
|
|
<UCard v-if="error" class="bg-red-50 dark:bg-red-950 border-red-500">
|
|
<div class="flex items-start gap-3">
|
|
<UIcon name="i-heroicons-exclamation-triangle" class="w-6 h-6 text-red-600 flex-shrink-0 mt-0.5" />
|
|
<div class="flex-1">
|
|
<h3 class="font-semibold text-red-600">Error cargando operaciones</h3>
|
|
<p class="text-sm text-red-700 dark:text-red-400 mt-1">{{ error }}</p>
|
|
<p class="text-xs text-red-600 dark:text-red-500 mt-2">Ver consola del navegador (F12) para más detalles</p>
|
|
</div>
|
|
</div>
|
|
</UCard>
|
|
|
|
<UCard>
|
|
<UTable
|
|
:data="operaciones"
|
|
:columns="columns"
|
|
:loading="loading"
|
|
:empty-state="{
|
|
icon: 'i-heroicons-inbox',
|
|
label: 'No hay operaciones registradas'
|
|
}"
|
|
>
|
|
<template #tipo-cell="{ row, getValue }">
|
|
<div class="flex items-center gap-2">
|
|
<UIcon :name="getTipoIcon(getValue())" class="w-4 h-4" />
|
|
<UBadge :color="getTipoColor(getValue())" variant="subtle">
|
|
{{ getTipoLabel(getValue()) }}
|
|
</UBadge>
|
|
</div>
|
|
</template>
|
|
|
|
<template #fecha-cell="{ getValue }">
|
|
{{ formatDate(getValue()) }}
|
|
</template>
|
|
|
|
<template #actions-cell="{ row }">
|
|
<div class="flex gap-1">
|
|
<UButton
|
|
icon="i-heroicons-eye"
|
|
size="xs"
|
|
variant="ghost"
|
|
@click="$emit('view', row.original as Operacion)"
|
|
/>
|
|
</div>
|
|
</template>
|
|
</UTable>
|
|
</UCard>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { ColumnDef } from '@tanstack/vue-table'
|
|
import type { Operacion } from '~/composables/useLotes'
|
|
|
|
console.log('🟡 OperacionesTable: <script setup> ejecutándose')
|
|
|
|
const emit = defineEmits<{
|
|
create: []
|
|
view: [operacion: Operacion]
|
|
}>()
|
|
|
|
console.log('🟡 OperacionesTable: Llamando a useLotes()...')
|
|
const { fetchOperaciones, TIPOS_OPERACION } = useLotes()
|
|
console.log('🟡 OperacionesTable: useLotes() completado, TIPOS_OPERACION:', TIPOS_OPERACION?.length)
|
|
|
|
const operaciones = ref<Operacion[]>([])
|
|
const loading = ref(false)
|
|
const filtroTipo = ref<string | null>(null)
|
|
const error = ref<string | null>(null)
|
|
|
|
const selectOptions = computed(() => [
|
|
{ value: null, label: 'Todos los tipos' },
|
|
...TIPOS_OPERACION,
|
|
])
|
|
|
|
const columns: ColumnDef<Operacion>[] = [
|
|
{ accessorKey: 'tipo', id: 'tipo', header: 'Tipo de Operación' },
|
|
{ accessorKey: 'fecha', id: 'fecha', header: 'Fecha' },
|
|
{ accessorKey: 'lugar_id', id: 'lugar_id', header: 'Lugar' },
|
|
{ id: 'actions', header: 'Acciones' },
|
|
]
|
|
|
|
const loadOperaciones = async () => {
|
|
loading.value = true
|
|
error.value = null
|
|
try {
|
|
console.log('🟡 OperacionesTable: Iniciando carga de operaciones...')
|
|
operaciones.value = await fetchOperaciones({
|
|
tipo: filtroTipo.value || undefined,
|
|
})
|
|
console.log('🟡 OperacionesTable: Operaciones cargadas exitosamente:', operaciones.value.length)
|
|
} catch (err: any) {
|
|
const errorMsg = err?.message || String(err)
|
|
error.value = errorMsg
|
|
console.error('❌ OperacionesTable: Error cargando operaciones:', {
|
|
message: errorMsg,
|
|
stack: err?.stack,
|
|
error: err
|
|
})
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
const getTipoLabel = (tipo: string) => {
|
|
const found = TIPOS_OPERACION.find((t) => t.value === tipo)
|
|
return found?.label || tipo
|
|
}
|
|
|
|
const getTipoIcon = (tipo: string): string => {
|
|
const found = TIPOS_OPERACION.find((t) => t.value === tipo)
|
|
return found?.icon || 'i-heroicons-cube'
|
|
}
|
|
|
|
const getTipoColor = (tipo: string): string => {
|
|
const colorMap: Record<string, string> = {
|
|
ingreso: 'blue',
|
|
despulpado: 'green',
|
|
oreado: 'orange',
|
|
presecado: 'amber',
|
|
reposo: 'purple',
|
|
secado: 'emerald',
|
|
traslado: 'gray',
|
|
mezcla: 'yellow',
|
|
ajuste_merma: 'red',
|
|
ajuste_cantidad: 'orange',
|
|
ajuste_tipo: 'yellow',
|
|
}
|
|
return colorMap[tipo] || 'gray'
|
|
}
|
|
|
|
const formatDate = (dateString: string) => {
|
|
return new Date(dateString).toLocaleDateString('es-AR', {
|
|
day: '2-digit',
|
|
month: '2-digit',
|
|
year: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
})
|
|
}
|
|
|
|
// Cargar operaciones al montar
|
|
onMounted(() => {
|
|
try {
|
|
console.log('🟡 OperacionesTable: Componente montado, cargando operaciones...')
|
|
loadOperaciones()
|
|
} catch (err: any) {
|
|
console.error('❌ OperacionesTable: Error en onMounted:', err)
|
|
}
|
|
})
|
|
|
|
// Recargar cuando cambia el filtro
|
|
watch(filtroTipo, () => {
|
|
try {
|
|
console.log('🟡 OperacionesTable: Filtro cambiado, recargando operaciones...')
|
|
loadOperaciones()
|
|
} catch (err: any) {
|
|
console.error('❌ OperacionesTable: Error en watch:', err)
|
|
}
|
|
})
|
|
</script>
|