feat: implementar gestión de pestañas para números de serie y mejorar lógica de estado
This commit is contained in:
parent
d469b18bf5
commit
5b7b6f2343
@ -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' })
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user