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>
|
<script setup>
|
||||||
import { onMounted } from 'vue';
|
import { onMounted } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
import useLoader from '@Stores/Loader';
|
import useLoader from '@Stores/Loader';
|
||||||
import { hasToken } from '@Services/Api';
|
import { hasToken } from '@Services/Api';
|
||||||
|
|
||||||
/** Definidores */
|
/** Definidores */
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const route = useRoute();
|
||||||
const loader = useLoader();
|
const loader = useLoader();
|
||||||
|
|
||||||
|
/** Rutas públicas que no requieren autenticación */
|
||||||
|
const publicRoutes = [
|
||||||
|
'facturacion.index',
|
||||||
|
'auth.index',
|
||||||
|
'auth.forgot-password',
|
||||||
|
'auth.reset-password'
|
||||||
|
];
|
||||||
|
|
||||||
/** Ciclos */
|
/** Ciclos */
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
// No redirigir si estamos en una ruta pública
|
||||||
|
if (publicRoutes.includes(route.name)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(!hasToken()) {
|
if(!hasToken()) {
|
||||||
return router.push({ name: 'auth.index' })
|
return router.push({ name: 'auth.index' })
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,7 +18,7 @@ const router = useRouter();
|
|||||||
const inventoryId = computed(() => route.params.id);
|
const inventoryId = computed(() => route.params.id);
|
||||||
const inventory = ref(null);
|
const inventory = ref(null);
|
||||||
const serials = ref({ data: [], total: 0 });
|
const serials = ref({ data: [], total: 0 });
|
||||||
const statusFilter = ref('');
|
const activeTab = ref('disponible');
|
||||||
|
|
||||||
// Modales
|
// Modales
|
||||||
const showCreateModal = ref(false);
|
const showCreateModal = ref(false);
|
||||||
@ -62,16 +62,17 @@ const loadSerials = (filters = {}) => {
|
|||||||
url: apiURL(`inventario/${inventoryId.value}/serials`),
|
url: apiURL(`inventario/${inventoryId.value}/serials`),
|
||||||
filters: {
|
filters: {
|
||||||
...filters,
|
...filters,
|
||||||
status: statusFilter.value || undefined
|
status: activeTab.value
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSearch = (query) => {
|
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 });
|
loadSerials({ q: searcher.query });
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -92,7 +93,7 @@ const createSerial = () => {
|
|||||||
|
|
||||||
form.post(url, {
|
form.post(url, {
|
||||||
onSuccess: (response) => {
|
onSuccess: (response) => {
|
||||||
Notify.success('Número de serie creado exitosamente');
|
window.Notify.success('Número de serie creado exitosamente');
|
||||||
if (response.inventory) {
|
if (response.inventory) {
|
||||||
inventory.value = response.inventory;
|
inventory.value = response.inventory;
|
||||||
}
|
}
|
||||||
@ -100,7 +101,7 @@ const createSerial = () => {
|
|||||||
loadSerials();
|
loadSerials();
|
||||||
},
|
},
|
||||||
onError: () => {
|
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 = () => {
|
const updateSerial = () => {
|
||||||
editForm.put(apiURL(`inventario/${inventoryId.value}/serials/${editingSerial.value.id}`), {
|
editForm.put(apiURL(`inventario/${inventoryId.value}/serials/${editingSerial.value.id}`), {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
Notify.success('Número de serie actualizado');
|
window.Notify.success('Número de serie actualizado');
|
||||||
closeEditModal();
|
closeEditModal();
|
||||||
loadSerials();
|
loadSerials();
|
||||||
},
|
},
|
||||||
onError: () => {
|
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 () => {
|
const confirmDelete = async () => {
|
||||||
try {
|
try {
|
||||||
await serialService.deleteSerial(inventoryId.value, deletingSerial.value.id);
|
await serialService.deleteSerial(inventoryId.value, deletingSerial.value.id);
|
||||||
Notify.success('Número de serie eliminado');
|
window.Notify.success('Número de serie eliminado');
|
||||||
closeDeleteModal();
|
closeDeleteModal();
|
||||||
loadSerials();
|
loadSerials();
|
||||||
} catch (error) {
|
} 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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Buscador y filtros -->
|
<!-- Buscador -->
|
||||||
<SearcherHead
|
<SearcherHead
|
||||||
title="Números de Serie"
|
title="Números de Serie"
|
||||||
placeholder="Buscar por número de serie..."
|
placeholder="Buscar por número de serie..."
|
||||||
@search="onSearch"
|
@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
|
<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"
|
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"
|
@click="openCreateModal"
|
||||||
>
|
>
|
||||||
@ -240,12 +232,54 @@ onMounted(() => {
|
|||||||
</button>
|
</button>
|
||||||
</SearcherHead>
|
</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 -->
|
<!-- Tabla de seriales -->
|
||||||
<div class="pt-2 w-full">
|
<div class="pt-2 w-full">
|
||||||
<Table
|
<Table
|
||||||
:items="serials"
|
:items="serials"
|
||||||
:processing="searcher.processing"
|
:processing="searcher.processing"
|
||||||
@send-pagination="(page) => searcher.pagination(page, { status: statusFilter || undefined })"
|
@send-pagination="(page) => searcher.pagination(page, { status: activeTab })"
|
||||||
>
|
>
|
||||||
<template #head>
|
<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>
|
<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">
|
<td colspan="5" class="table-cell text-center">
|
||||||
<div class="flex flex-col items-center justify-center py-8 text-gray-500">
|
<div class="flex flex-col items-center justify-center py-8 text-gray-500">
|
||||||
<GoogleIcon
|
<GoogleIcon
|
||||||
name="qr_code_2"
|
:name="activeTab === 'disponible' ? 'qr_code_2' : 'shopping_cart'"
|
||||||
class="text-6xl mb-2 opacity-50"
|
class="text-6xl mb-2 opacity-50"
|
||||||
/>
|
/>
|
||||||
<p class="font-semibold">
|
<p class="font-semibold">
|
||||||
No hay números de serie registrados
|
{{ activeTab === 'disponible' ? 'No hay seriales disponibles' : 'No hay seriales vendidos' }}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm mt-1">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user