mejoras UI 5
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m5s
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m5s
This commit is contained in:
@@ -80,7 +80,7 @@
|
||||
<!-- Contenido principal -->
|
||||
<div v-if="isAuthenticated">
|
||||
<!-- Navegación por Tabs -->
|
||||
<UTabs v-model="selectedTab" :items="tabs" class="mb-6">
|
||||
<UTabs v-model="selectedTab" :items="tabs" class="mb-6">
|
||||
<!-- Tab: Lotes -->
|
||||
<template #lotes>
|
||||
<div class="py-4">
|
||||
@@ -105,10 +105,51 @@
|
||||
@create="showCreateOperacionModal = true"
|
||||
@view="handleViewOperacion"
|
||||
/>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</template>
|
||||
</UTabs>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Tab: Grafos -->
|
||||
<template #grafos>
|
||||
<div class="py-4 space-y-4">
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-3">
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold">Grafo de trazabilidad</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Visualiza el grafo desde el lote seleccionado (por defecto el más reciente).</p>
|
||||
</div>
|
||||
<div class="flex gap-2 items-center">
|
||||
<USelect
|
||||
v-model="selectedGraphLoteId"
|
||||
:items="graphSelectItems"
|
||||
label-key="label"
|
||||
value-key="value"
|
||||
searchable
|
||||
placeholder="Selecciona lote"
|
||||
class="w-72"
|
||||
:loading="graphLoading"
|
||||
:disabled="graphLoading || graphSelectItems.length === 0"
|
||||
/>
|
||||
<UButton icon="i-heroicons-arrow-path" variant="outline" @click="loadGraphLotes">
|
||||
Recargar
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<UAlert v-if="graphError" color="red" variant="soft" title="Error cargando lotes">
|
||||
{{ graphError }}
|
||||
</UAlert>
|
||||
|
||||
<ClientOnly>
|
||||
<div v-if="selectedGraphLoteId" class="mt-2">
|
||||
<LotesTrazabilidad :lote-id="selectedGraphLoteId" @close="selectedGraphLoteId = null" />
|
||||
</div>
|
||||
<template #fallback>
|
||||
<USkeleton class="h-64" />
|
||||
</template>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</template>
|
||||
</UTabs>
|
||||
</div>
|
||||
|
||||
<!-- Mensaje si no está autenticado -->
|
||||
@@ -181,11 +222,44 @@
|
||||
<!-- Modal: Ver Detalle de Operación -->
|
||||
<UModal v-model:open="showOperacionDetailModal">
|
||||
<template #content>
|
||||
<OperacionesCard
|
||||
v-if="selectedOperacion"
|
||||
:operacion="selectedOperacion"
|
||||
@close="closeOperacionDetailModal"
|
||||
/>
|
||||
<UCard>
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold">Detalle de Operación</h3>
|
||||
<p class="text-sm text-gray-500" v-if="selectedOperacion">
|
||||
{{ getTipoLabel(selectedOperacion.tipo) }} · {{ formatDate(selectedOperacion.fecha) }}
|
||||
</p>
|
||||
</div>
|
||||
<UButton icon="i-heroicons-x-mark" variant="ghost" @click="closeOperacionDetailModal" />
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="selectedOperacion" class="space-y-3">
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<p class="text-xs uppercase text-gray-500">ID</p>
|
||||
<p class="font-mono text-sm">{{ selectedOperacion.id }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs uppercase text-gray-500">Fecha</p>
|
||||
<p class="text-sm">{{ formatDate(selectedOperacion.fecha) }}</p>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<p class="text-xs uppercase text-gray-500">Tipo</p>
|
||||
<UBadge color="blue" variant="subtle">{{ getTipoLabel(selectedOperacion.tipo) }}</UBadge>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs uppercase text-gray-500 mb-1">Meta</p>
|
||||
<div class="rounded-lg border border-gray-200 dark:border-slate-800 bg-gray-50 dark:bg-slate-900/60 p-2">
|
||||
<pre class="text-xs text-gray-800 dark:text-slate-200 whitespace-pre-wrap overflow-x-auto">
|
||||
{{ JSON.stringify(selectedOperacion.meta || {}, null, 2) }}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="text-sm text-gray-500">No hay operación seleccionada.</div>
|
||||
</UCard>
|
||||
</template>
|
||||
</UModal>
|
||||
</UApp>
|
||||
@@ -195,12 +269,14 @@
|
||||
import type { Lote, Operacion } from '~/composables/useLotes'
|
||||
|
||||
const { isAuthenticated } = useAuthentik()
|
||||
const { fetchLotes: fetchLotesComposable } = useLotes()
|
||||
|
||||
// Navegación
|
||||
const selectedTab = ref('lotes')
|
||||
const tabs = [
|
||||
{ label: 'Lotes', icon: 'i-heroicons-cube', slot: 'lotes', value: 'lotes' },
|
||||
{ label: 'Operaciones', icon: 'i-heroicons-beaker', slot: 'operaciones', value: 'operaciones' },
|
||||
{ label: 'Grafos', icon: 'i-heroicons-share', slot: 'grafos', value: 'grafos' },
|
||||
]
|
||||
|
||||
// Estados de modales
|
||||
@@ -215,6 +291,10 @@ const showOperacionDetailModal = ref(false)
|
||||
const selectedLote = ref<Lote | null>(null)
|
||||
const selectedOperacion = ref<Operacion | null>(null)
|
||||
const trazabilidadLoteId = ref<string | null>(null)
|
||||
const graphLotes = ref<Lote[]>([])
|
||||
const graphLoading = ref(false)
|
||||
const graphError = ref<string | null>(null)
|
||||
const selectedGraphLoteId = ref<string | null>(null)
|
||||
|
||||
// Handlers para Lotes
|
||||
const handleViewLote = (lote: Lote) => {
|
||||
@@ -271,6 +351,36 @@ const closeOperacionDetailModal = () => {
|
||||
selectedOperacion.value = null
|
||||
}
|
||||
|
||||
// Datos para grafos
|
||||
const graphSelectItems = computed(() =>
|
||||
graphLotes.value.map((lote) => ({
|
||||
label: `${lote.codigo || lote.id} · ${getTipoLabel(lote.tipo)}`,
|
||||
value: lote.id,
|
||||
}))
|
||||
)
|
||||
|
||||
const loadGraphLotes = async () => {
|
||||
graphLoading.value = true
|
||||
graphError.value = null
|
||||
try {
|
||||
const lotes = await fetchLotesComposable()
|
||||
graphLotes.value = lotes.sort((a, b) =>
|
||||
new Date(b.fecha_creado).getTime() - new Date(a.fecha_creado).getTime()
|
||||
)
|
||||
if (!selectedGraphLoteId.value && graphLotes.value.length > 0) {
|
||||
selectedGraphLoteId.value = graphLotes.value[0].id
|
||||
}
|
||||
} catch (err: any) {
|
||||
graphError.value = err?.message || 'Error cargando lotes'
|
||||
} finally {
|
||||
graphLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadGraphLotes()
|
||||
})
|
||||
|
||||
// ⚠️⚠️⚠️ FUNCIONES DE DEBUG - TEMPORALES ⚠️⚠️⚠️
|
||||
// NO ELIMINAR SIN CONSULTAR A DARIO/DRAGANEL/NUCLEO000
|
||||
const resettingDB = ref(false)
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
/>
|
||||
</g>
|
||||
|
||||
<!-- Aristas -->
|
||||
<!-- Aristas con codo (vertical + horizontal) -->
|
||||
<g stroke="url(#edgeGradient)" stroke-width="2.5" stroke-linecap="round">
|
||||
<template v-for="edge in edges" :key="`${edge.from}-${edge.to}`">
|
||||
<!-- tramo vertical -->
|
||||
@@ -68,6 +68,15 @@
|
||||
:x1="edge.fromPos.x"
|
||||
:y1="edge.midPos.y"
|
||||
:x2="edge.toPos.x"
|
||||
:y2="edge.midPos.y"
|
||||
class="transition-opacity duration-200"
|
||||
:opacity="0.9"
|
||||
/>
|
||||
<!-- tramo final vertical -->
|
||||
<line
|
||||
:x1="edge.toPos.x"
|
||||
:y1="edge.midPos.y"
|
||||
:x2="edge.toPos.x"
|
||||
:y2="edge.toPos.y"
|
||||
class="transition-opacity duration-200"
|
||||
:opacity="0.9"
|
||||
|
||||
Reference in New Issue
Block a user