178 lines
7.4 KiB
Vue
178 lines
7.4 KiB
Vue
<script setup>
|
|
import { ref, onMounted } from 'vue';
|
|
import { useSearcher } from '@Services/Api';
|
|
import { formatCurrency } from '@/utils/formatters';
|
|
import { can, apiTo } from './Module.js';
|
|
|
|
import SearcherHead from '@Holos/Searcher.vue';
|
|
import Table from '@Holos/Table.vue';
|
|
import GoogleIcon from '@Shared/GoogleIcon.vue';
|
|
import Create from './Create.vue';
|
|
import Edit from './Edit.vue';
|
|
import Delete from './Delete.vue';
|
|
|
|
/** Estado */
|
|
const bundles = ref({ data: [] });
|
|
const showCreateModal = ref(false);
|
|
const showEditModal = ref(false);
|
|
const showDeleteModal = ref(false);
|
|
const bundleToEdit = ref(null);
|
|
const bundleToDelete = ref(null);
|
|
|
|
/** Buscador */
|
|
const searcher = useSearcher({
|
|
url: apiTo('index'),
|
|
onSuccess: (data) => {
|
|
bundles.value = data.bundles || { data: [] };
|
|
},
|
|
onError: () => {
|
|
Notify.error('Error al cargar los paquetes');
|
|
}
|
|
});
|
|
|
|
/** Métodos */
|
|
const openEditModal = (bundle) => {
|
|
bundleToEdit.value = bundle;
|
|
showEditModal.value = true;
|
|
};
|
|
|
|
const openDeleteModal = (bundle) => {
|
|
bundleToDelete.value = bundle;
|
|
showDeleteModal.value = true;
|
|
};
|
|
|
|
const closeEditModal = () => {
|
|
showEditModal.value = false;
|
|
bundleToEdit.value = null;
|
|
};
|
|
|
|
const closeDeleteModal = () => {
|
|
showDeleteModal.value = false;
|
|
bundleToDelete.value = null;
|
|
};
|
|
|
|
const handleDelete = (bundleId) => {
|
|
window.axios.delete(apiTo('destroy', { bundle: bundleId })).then(() => {
|
|
Notify.success('Paquete eliminado exitosamente');
|
|
closeDeleteModal();
|
|
searcher.refresh();
|
|
}).catch(() => {
|
|
Notify.error('Error al eliminar el paquete');
|
|
});
|
|
};
|
|
|
|
/** Ciclos */
|
|
onMounted(() => {
|
|
searcher.search('');
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<SearcherHead
|
|
title="Paquetes / Kits"
|
|
placeholder="Buscar por nombre, SKU o código de barras..."
|
|
@search="(q) => searcher.search(q)"
|
|
>
|
|
<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="showCreateModal = true"
|
|
>
|
|
<GoogleIcon name="add" class="text-xl" />
|
|
Nuevo Paquete
|
|
</button>
|
|
</SearcherHead>
|
|
|
|
<div class="pt-2 w-full">
|
|
<Table
|
|
:items="bundles"
|
|
:processing="searcher.processing"
|
|
@send-pagination="(page) => searcher.pagination(page)"
|
|
>
|
|
<template #head>
|
|
<th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">NOMBRE</th>
|
|
<th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">SKU</th>
|
|
<th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">COMPONENTES</th>
|
|
<th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">PAQ. ESTIMADO</th>
|
|
<th class="px-6 py-3 text-center 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">ACCIONES</th>
|
|
</template>
|
|
<template #body="{ items }">
|
|
<tr
|
|
v-for="bundle in items"
|
|
:key="bundle.id"
|
|
class="hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
|
>
|
|
<td class="px-6 py-4 text-center">
|
|
<p class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{ bundle.name }}</p>
|
|
<p v-if="bundle.barcode" class="text-xs text-gray-500 dark:text-gray-400">{{ bundle.barcode }}</p>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-center">
|
|
<span class="text-sm font-mono text-gray-600 dark:text-gray-400">{{ bundle.sku }}</span>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-center">
|
|
<span class="text-sm text-gray-700 dark:text-gray-300">{{ bundle.items?.length || 0 }} productos</span>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-center">
|
|
<span
|
|
class="px-2 py-1 text-xs font-semibold rounded-full"
|
|
:class="bundle.available_stock > 0
|
|
? 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400'
|
|
: 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400'"
|
|
>
|
|
{{ bundle.available_stock }} paq.
|
|
</span>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-center">
|
|
<p class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{ formatCurrency(bundle.price?.retail_price) }}</p>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400">Costo: {{ formatCurrency(bundle.total_cost) }}</p>
|
|
</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(bundle)"
|
|
class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300 transition-colors"
|
|
title="Editar"
|
|
>
|
|
<GoogleIcon name="edit" class="text-xl" />
|
|
</button>
|
|
<button
|
|
v-if="can('destroy')"
|
|
@click="openDeleteModal(bundle)"
|
|
class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300 transition-colors"
|
|
title="Eliminar"
|
|
>
|
|
<GoogleIcon name="delete" class="text-xl" />
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</template>
|
|
</Table>
|
|
</div>
|
|
|
|
<!-- Modales -->
|
|
<Create
|
|
:show="showCreateModal"
|
|
@close="showCreateModal = false"
|
|
@created="showCreateModal = false; searcher.refresh()"
|
|
/>
|
|
|
|
<Edit
|
|
:show="showEditModal"
|
|
:bundle="bundleToEdit"
|
|
@close="closeEditModal"
|
|
@updated="closeEditModal(); searcher.refresh()"
|
|
/>
|
|
|
|
<Delete
|
|
:show="showDeleteModal"
|
|
:bundle="bundleToDelete"
|
|
@close="closeDeleteModal"
|
|
@confirm="handleDelete"
|
|
/>
|
|
</div>
|
|
</template>
|