feat: implementar gestión de pestañas para números de serie y mejorar lógica de estado

This commit is contained in:
Juan Felipe Zapata Moreno 2026-01-27 21:33:26 -06:00
parent d469b18bf5
commit 5b7b6f2343
2 changed files with 74 additions and 26 deletions

View File

@ -1,15 +1,29 @@
<script setup>
import { onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { useRouter, useRoute } from 'vue-router';
import useLoader from '@Stores/Loader';
import { hasToken } from '@Services/Api';
/** Definidores */
const router = useRouter();
const route = useRoute();
const loader = useLoader();
/** Rutas públicas que no requieren autenticación */
const publicRoutes = [
'facturacion.index',
'auth.index',
'auth.forgot-password',
'auth.reset-password'
];
/** Ciclos */
onMounted(() => {
// No redirigir si estamos en una ruta pública
if (publicRoutes.includes(route.name)) {
return;
}
if(!hasToken()) {
return router.push({ name: 'auth.index' })
}

View File

@ -18,7 +18,7 @@ const router = useRouter();
const inventoryId = computed(() => route.params.id);
const inventory = ref(null);
const serials = ref({ data: [], total: 0 });
const statusFilter = ref('');
const activeTab = ref('disponible');
// Modales
const showCreateModal = ref(false);
@ -62,16 +62,17 @@ const loadSerials = (filters = {}) => {
url: apiURL(`inventario/${inventoryId.value}/serials`),
filters: {
...filters,
status: statusFilter.value || undefined
status: activeTab.value
}
});
};
const onSearch = (query) => {
searcher.search(query, { status: statusFilter.value || undefined });
searcher.search(query, { status: activeTab.value });
};
const onStatusChange = () => {
const switchTab = (tab) => {
activeTab.value = tab;
loadSerials({ q: searcher.query });
};
@ -92,7 +93,7 @@ const createSerial = () => {
form.post(url, {
onSuccess: (response) => {
Notify.success('Número de serie creado exitosamente');
window.Notify.success('Número de serie creado exitosamente');
if (response.inventory) {
inventory.value = response.inventory;
}
@ -100,7 +101,7 @@ const createSerial = () => {
loadSerials();
},
onError: () => {
Notify.error('Error al crear el número de serie');
window.Notify.error('Error al crear el número de serie');
}
});
};
@ -122,12 +123,12 @@ const closeEditModal = () => {
const updateSerial = () => {
editForm.put(apiURL(`inventario/${inventoryId.value}/serials/${editingSerial.value.id}`), {
onSuccess: () => {
Notify.success('Número de serie actualizado');
window.Notify.success('Número de serie actualizado');
closeEditModal();
loadSerials();
},
onError: () => {
Notify.error('Error al actualizar el número de serie');
window.Notify.error('Error al actualizar el número de serie');
}
});
};
@ -146,11 +147,11 @@ const closeDeleteModal = () => {
const confirmDelete = async () => {
try {
await serialService.deleteSerial(inventoryId.value, deletingSerial.value.id);
Notify.success('Número de serie eliminado');
window.Notify.success('Número de serie eliminado');
closeDeleteModal();
loadSerials();
} catch (error) {
Notify.error('Error al eliminar el número de serie');
window.Notify.error('Error al eliminar el número de serie');
}
};
@ -215,23 +216,14 @@ onMounted(() => {
</div>
</div>
<!-- Buscador y filtros -->
<!-- Buscador -->
<SearcherHead
title="Números de Serie"
placeholder="Buscar por número de serie..."
@search="onSearch"
>
<!-- Filtro de estado -->
<select
v-model="statusFilter"
@change="onStatusChange"
class="px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-100"
>
<option value="">Todos los estados</option>
<option value="disponible">Disponible</option>
<option value="vendido">Vendido</option>
</select>
<button
v-if="activeTab === 'disponible'"
class="flex items-center gap-2 px-3 py-2 bg-indigo-600 hover:bg-indigo-700 text-white text-sm font-semibold rounded-lg transition-colors shadow-sm"
@click="openCreateModal"
>
@ -240,12 +232,54 @@ onMounted(() => {
</button>
</SearcherHead>
<!-- Pestañas -->
<div class="mt-4 border-b border-gray-200 dark:border-gray-700">
<nav class="-mb-px flex space-x-8" aria-label="Tabs">
<button
@click="switchTab('disponible')"
:class="[
activeTab === 'disponible'
? 'border-indigo-500 text-indigo-600 dark:text-indigo-400'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300',
'group inline-flex items-center py-4 px-1 border-b-2 font-medium text-sm transition-colors'
]"
>
<GoogleIcon
name="check_circle"
:class="[
activeTab === 'disponible' ? 'text-indigo-500 dark:text-indigo-400' : 'text-gray-400 group-hover:text-gray-500',
'mr-2 text-xl'
]"
/>
Disponibles
</button>
<button
@click="switchTab('vendido')"
:class="[
activeTab === 'vendido'
? 'border-indigo-500 text-indigo-600 dark:text-indigo-400'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300',
'group inline-flex items-center py-4 px-1 border-b-2 font-medium text-sm transition-colors'
]"
>
<GoogleIcon
name="shopping_cart"
:class="[
activeTab === 'vendido' ? 'text-indigo-500 dark:text-indigo-400' : 'text-gray-400 group-hover:text-gray-500',
'mr-2 text-xl'
]"
/>
Vendidos
</button>
</nav>
</div>
<!-- Tabla de seriales -->
<div class="pt-2 w-full">
<Table
:items="serials"
:processing="searcher.processing"
@send-pagination="(page) => searcher.pagination(page, { status: statusFilter || undefined })"
@send-pagination="(page) => searcher.pagination(page, { status: activeTab })"
>
<template #head>
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">NÚMERO DE SERIE</th>
@ -316,14 +350,14 @@ onMounted(() => {
<td colspan="5" class="table-cell text-center">
<div class="flex flex-col items-center justify-center py-8 text-gray-500">
<GoogleIcon
name="qr_code_2"
:name="activeTab === 'disponible' ? 'qr_code_2' : 'shopping_cart'"
class="text-6xl mb-2 opacity-50"
/>
<p class="font-semibold">
No hay números de serie registrados
{{ activeTab === 'disponible' ? 'No hay seriales disponibles' : 'No hay seriales vendidos' }}
</p>
<p class="text-sm mt-1">
Agrega seriales individuales o importa múltiples
{{ activeTab === 'disponible' ? 'Agrega seriales para comenzar' : 'Los seriales vendidos aparecerán aquí' }}
</p>
</div>
</td>