Agregar sistema de vinculaciones con registros externos de Metabase
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 2m46s
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 2m46s
- Nuevo schema BD para vinculaciones_externas con constraint único por período - Cliente Metabase para consultar Ingresos, Carretas, Salidas y Rechazos - Endpoints API para registros externos (/api/externos/*) y vinculaciones (/api/vinculaciones/*) - Composable useRegistrosExternos con lógica de vinculación individual y masiva - Componentes: TablaRegistros, ModalAsignar, ProgressDashboard - Tab "Externos" en app.vue con sub-tabs y dashboard de progreso - LotesCard.vue ahora muestra registros vinculados al lote
This commit is contained in:
@@ -53,6 +53,85 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sección de Vinculaciones Externas -->
|
||||
<div class="pt-4 border-t">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<p class="text-sm text-gray-500 font-medium">Registros Externos Vinculados</p>
|
||||
<UButton
|
||||
v-if="!vinculacionesLoading && !vinculacionesCargadas"
|
||||
icon="i-heroicons-link"
|
||||
size="xs"
|
||||
variant="outline"
|
||||
label="Cargar vinculaciones"
|
||||
@click="cargarVinculaciones"
|
||||
/>
|
||||
<UButton
|
||||
v-if="vinculacionesCargadas"
|
||||
icon="i-heroicons-arrow-path"
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
@click="cargarVinculaciones"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Loading -->
|
||||
<div v-if="vinculacionesLoading" class="flex items-center gap-2 text-gray-500">
|
||||
<UIcon name="i-heroicons-arrow-path" class="w-4 h-4 animate-spin" />
|
||||
<span class="text-sm">Cargando vinculaciones...</span>
|
||||
</div>
|
||||
|
||||
<!-- Contenido de vinculaciones -->
|
||||
<div v-else-if="vinculacionesCargadas">
|
||||
<!-- Sin vinculaciones -->
|
||||
<div v-if="vinculacionesMeta?.total === 0" class="text-center py-4 text-gray-400">
|
||||
<UIcon name="i-heroicons-link-slash" class="w-8 h-8 mx-auto mb-2" />
|
||||
<p class="text-sm">No hay registros externos vinculados</p>
|
||||
</div>
|
||||
|
||||
<!-- Con vinculaciones -->
|
||||
<div v-else class="space-y-3">
|
||||
<!-- Resumen por tipo -->
|
||||
<div class="grid grid-cols-4 gap-2">
|
||||
<div
|
||||
v-for="(count, tipo) in vinculacionesMeta?.por_tipo"
|
||||
:key="tipo"
|
||||
class="text-center p-2 rounded-lg bg-gray-50 dark:bg-gray-800"
|
||||
>
|
||||
<UIcon :name="getTipoRegistroIcon(tipo as string)" class="w-5 h-5 mx-auto mb-1" :class="`text-${getTipoRegistroColor(tipo as string)}-500`" />
|
||||
<p class="text-lg font-bold">{{ count }}</p>
|
||||
<p class="text-xs text-gray-500 capitalize">{{ tipo }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lista de vinculaciones agrupadas -->
|
||||
<div class="space-y-2">
|
||||
<template v-for="(items, tipo) in vinculacionesAgrupados" :key="tipo">
|
||||
<div v-if="items && items.length > 0" class="border rounded-lg p-2">
|
||||
<p class="text-xs font-medium text-gray-500 mb-1 capitalize flex items-center gap-1">
|
||||
<UIcon :name="getTipoRegistroIcon(tipo as string)" class="w-3 h-3" />
|
||||
{{ tipo }} ({{ items.length }})
|
||||
</p>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<UBadge
|
||||
v-for="v in items.slice(0, 5)"
|
||||
:key="v.id"
|
||||
:color="getTipoRegistroColor(tipo as string)"
|
||||
variant="subtle"
|
||||
size="xs"
|
||||
>
|
||||
#{{ v.registro_id }}
|
||||
</UBadge>
|
||||
<UBadge v-if="items.length > 5" color="gray" variant="subtle" size="xs">
|
||||
+{{ items.length - 5 }} más
|
||||
</UBadge>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="lote.meta" class="pt-4 border-t">
|
||||
<p class="text-sm text-gray-500 mb-2">Información Adicional</p>
|
||||
<UCard class="bg-gray-50 dark:bg-slate-900/70 border border-gray-200 dark:border-slate-800">
|
||||
@@ -67,6 +146,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Lote } from '~/composables/useLotes'
|
||||
import type { Vinculacion } from '~/composables/useRegistrosExternos'
|
||||
|
||||
const props = defineProps<{
|
||||
lote: Lote
|
||||
@@ -78,6 +158,41 @@ const emit = defineEmits<{
|
||||
}>()
|
||||
|
||||
const { TIPOS_LOTE } = useLotes()
|
||||
const { fetchVinculacionesByLote, getTipoIcon, getTipoColor: getTipoRegistroColor } = useRegistrosExternos()
|
||||
|
||||
// Estado de vinculaciones
|
||||
const vinculacionesLoading = ref(false)
|
||||
const vinculacionesCargadas = ref(false)
|
||||
const vinculacionesAgrupados = ref<{
|
||||
ingresos: Vinculacion[]
|
||||
carretas: Vinculacion[]
|
||||
salidas: Vinculacion[]
|
||||
rechazos: Vinculacion[]
|
||||
} | null>(null)
|
||||
const vinculacionesMeta = ref<{
|
||||
lote_id: string
|
||||
total: number
|
||||
por_tipo: {
|
||||
ingresos: number
|
||||
carretas: number
|
||||
salidas: number
|
||||
rechazos: number
|
||||
}
|
||||
} | null>(null)
|
||||
|
||||
const cargarVinculaciones = async () => {
|
||||
vinculacionesLoading.value = true
|
||||
try {
|
||||
const result = await fetchVinculacionesByLote(props.lote.id)
|
||||
vinculacionesAgrupados.value = result.agrupados || null
|
||||
vinculacionesMeta.value = result.meta || null
|
||||
vinculacionesCargadas.value = true
|
||||
} catch (error) {
|
||||
console.error('Error cargando vinculaciones:', error)
|
||||
} finally {
|
||||
vinculacionesLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const getTipoLabel = (tipo: string) => {
|
||||
const found = TIPOS_LOTE.find((t) => t.value === tipo)
|
||||
@@ -98,6 +213,16 @@ const getTipoColor = (tipo: string): string => {
|
||||
return colorMap[tipo] || 'gray'
|
||||
}
|
||||
|
||||
const getTipoRegistroIcon = (tipo: string): string => {
|
||||
const iconMap: Record<string, string> = {
|
||||
ingresos: 'i-heroicons-inbox-arrow-down',
|
||||
carretas: 'i-heroicons-truck',
|
||||
salidas: 'i-heroicons-arrow-up-tray',
|
||||
rechazos: 'i-heroicons-x-circle',
|
||||
}
|
||||
return iconMap[tipo] || 'i-heroicons-document'
|
||||
}
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
return new Date(dateString).toLocaleDateString('es-AR', {
|
||||
day: '2-digit',
|
||||
@@ -107,4 +232,9 @@ const formatDate = (dateString: string) => {
|
||||
minute: '2-digit',
|
||||
})
|
||||
}
|
||||
|
||||
// Cargar vinculaciones automáticamente al montar
|
||||
onMounted(() => {
|
||||
cargarVinculaciones()
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user