2026-01-16 17:11:24 -06:00

262 lines
9.8 KiB
Vue

<script setup>
import { onMounted, ref } from 'vue';
import { useSearcher, apiURL } from '@Services/Api';
import { formatCurrency } from '@/utils/formatters';
import { can } from './Module.js';
import SearcherHead from '@Holos/Searcher.vue';
import Table from '@Holos/Table.vue';
import GoogleIcon from '@Shared/GoogleIcon.vue';
import CreateModal from './CreateModal.vue';
import EditModal from './EditModal.vue';
import DeleteModal from './DeleteModal.vue';
import ImportModal from './ImportModal.vue';
/** Estado */
const models = ref([]);
const showCreateModal = ref(false);
const showEditModal = ref(false);
const showDeleteModal = ref(false);
const showImportModal = ref(false);
const editingProduct = ref(null);
const deletingProduct = ref(null);
/** Métodos */
const searcher = useSearcher({
url: apiURL('inventario'),
onSuccess: (r) => {
models.value = r.products || { data: [], total: 0 };
},
onError: () => {
models.value = { data: [], total: 0 };
}
});
const openCreateModal = () => {
showCreateModal.value = true;
};
const closeCreateModal = () => {
showCreateModal.value = false;
};
const openEditModal = (product) => {
editingProduct.value = product;
showEditModal.value = true;
};
const closeEditModal = () => {
showEditModal.value = false;
editingProduct.value = null;
};
const openDeleteModal = (product) => {
deletingProduct.value = product;
showDeleteModal.value = true;
};
const closeDeleteModal = () => {
showDeleteModal.value = false;
deletingProduct.value = null;
};
const openImportModal = () => {
showImportModal.value = true;
};
const closeImportModal = () => {
showImportModal.value = false;
};
const onProductSaved = () => {
searcher.search();
};
const onProductsImported = () => {
searcher.search();
};
const confirmDelete = async (id) => {
try {
const response = await fetch(apiURL(`inventario/${id}`), {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${sessionStorage.token}`,
'Accept': 'application/json'
}
});
if (response.ok) {
Notify.success('Producto eliminado exitosamente');
closeDeleteModal();
searcher.search();
} else {
Notify.error('Error al eliminar el producto');
}
} catch (error) {
console.error('Error:', error);
Notify.error('Error al eliminar el producto');
}
};
/** Ciclos */
onMounted(() => {
searcher.search();
});
</script>
<template>
<div>
<SearcherHead
:title="$t('inventory.title')"
placeholder="Buscar por nombre o SKU..."
@search="(x) => searcher.search(x)"
>
<button
v-if="can('import')"
class="flex items-center gap-2 px-3 py-2 bg-green-600 hover:bg-green-700 text-white text-sm font-semibold rounded-lg transition-colors shadow-sm"
@click="openImportModal"
title="Importar productos desde Excel"
>
<GoogleIcon name="upload" class="text-xl" />
Importar
</button>
<button
v-if="can('create')"
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"
>
<GoogleIcon name="add" class="text-xl" />
Nuevo Producto
</button>
</SearcherHead>
<div class="pt-2 w-full">
<Table
:items="models"
:processing="searcher.processing"
@send-pagination="(page) => searcher.pagination(page)"
>
<template #head>
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">SKU / CÓDIGO</th>
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">PRODUCTO</th>
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">CATEGORÍA</th>
<th class="px-6 py-3 text-right text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">PRECIO</th>
<th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">STOCK</th>
<th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">ACCIONES</th>
</template>
<template #body="{items}">
<tr
v-for="model in items"
:key="model.id"
class="hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
>
<td class="px-6 py-4 whitespace-nowrap">
<span class="text-sm font-mono text-gray-600 dark:text-gray-400">{{ model.sku }}</span>
</td>
<td class="px-6 py-4">
<div>
<p class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{ model.name }}</p>
<p v-if="model.description" class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">{{ model.description }}</p>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="text-sm text-gray-700 dark:text-gray-300">
{{ model.category?.name || '-' }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-right">
<div class="text-sm">
<p class="font-semibold text-gray-900 dark:text-gray-100">
{{ formatCurrency(model.price?.retail_price) }}
</p>
<p class="text-xs text-gray-500 dark:text-gray-400">
Costo: {{ formatCurrency(model.price?.cost) }}
</p>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-center">
<span
class="font-bold text-base"
:class="{
'text-red-500': model.stock < 10,
'text-green-600': model.stock >= 10
}"
>
{{ model.stock }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-center">
<div class="flex items-center justify-center gap-2">
<button
v-if="can('edit')"
@click="openEditModal(model)"
class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300 transition-colors"
title="Editar producto"
>
<GoogleIcon name="edit" class="text-xl" />
</button>
<button
v-if="can('destroy')"
@click="openDeleteModal(model)"
class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300 transition-colors"
title="Eliminar producto"
>
<GoogleIcon name="delete" class="text-xl" />
</button>
</div>
</td>
</tr>
</template>
<template #empty>
<td colspan="6" class="table-cell text-center">
<div class="flex flex-col items-center justify-center py-8 text-gray-500">
<GoogleIcon
name="inventory_2"
class="text-6xl mb-2 opacity-50"
/>
<p class="font-semibold">
{{ $t('registers.empty') }}
</p>
</div>
</td>
</template>
</Table>
</div>
<!-- Modal de Crear Producto -->
<CreateModal
v-if="can('create')"
:show="showCreateModal"
@close="closeCreateModal"
@created="onProductSaved"
/>
<!-- Modal de Editar Producto -->
<EditModal
v-if="can('edit')"
:show="showEditModal"
:product="editingProduct"
@close="closeEditModal"
@updated="onProductSaved"
/>
<!-- Modal de Eliminar Producto -->
<DeleteModal
v-if="can('destroy')"
:show="showDeleteModal"
:product="deletingProduct"
@close="closeDeleteModal"
@confirm="confirmDelete"
/>
<!-- Modal de Importar Productos -->
<ImportModal
v-if="can('import')"
:show="showImportModal"
@close="closeImportModal"
@imported="onProductsImported"
/>
</div>
</template>