diff --git a/src/components/App.vue b/src/components/App.vue index 2cb333f..8a87b9c 100644 --- a/src/components/App.vue +++ b/src/components/App.vue @@ -4,12 +4,20 @@ import { useRouter } from 'vue-router'; import useLoader from '@Stores/Loader'; import { hasToken } from '@Services/Api'; +const PUBLIC_PATHS = ['/factura/']; + /** Definidores */ const router = useRouter(); const loader = useLoader(); /** Ciclos */ -onMounted(() => { +onMounted(async () => { + await router.isReady(); + + const currentPath = router.currentRoute.value.path; + + if (PUBLIC_PATHS.some(prefix => currentPath.startsWith(prefix))) return; + if(!hasToken()) { return router.push({ name: 'auth.index' }) } diff --git a/src/layouts/AppLayout.vue b/src/layouts/AppLayout.vue index 426374d..b2e3964 100644 --- a/src/layouts/AppLayout.vue +++ b/src/layouts/AppLayout.vue @@ -59,6 +59,11 @@ onMounted(() => { name="Entrega de caja" to="checkout.index" /> +
+import { ref, onMounted, watch } from 'vue'; +import { useSearcher, useApi } from '@Services/Api'; +import { apiTo } from './Module'; + +import Table from '@Holos/Table.vue'; +import IconButton from '@Holos/Button/Icon.vue'; +import PageHeader from '@Holos/PageHeader.vue'; +import ShowModal from './Modal/Show.vue'; +import ModalController from '@Controllers/ModalController.js'; + +/** Controladores */ +const Modal = new ModalController(); +const api = useApi(); +const showModal = ref(Modal.showModal); +const modelModal = ref(Modal.modelModal); + +/** Estado */ +const models = ref({ data: [], total: 0, current_page: 1, last_page: 1 }); +const statusFilter = ref(''); + +const statusConfig = { + pending: { label: 'Pendiente', cls: 'bg-yellow-100 text-yellow-800 border-yellow-200' }, + processing: { label: 'En proceso', cls: 'bg-blue-100 text-blue-800 border-blue-200' }, + completed: { label: 'Completada', cls: 'bg-green-100 text-green-800 border-green-200' }, + rejected: { label: 'Rechazada', cls: 'bg-red-100 text-red-800 border-red-200' }, +}; + +const statusTabs = [ + { value: '', label: 'Todas' }, + { value: 'pending', label: 'Pendientes' }, + { value: 'processing', label: 'En proceso' }, + { value: 'completed', label: 'Completadas'}, + { value: 'rejected', label: 'Rechazadas' }, +]; + +/** Buscador */ +const searcher = useSearcher({ + url: apiTo(), + filters: {}, + onSuccess: (data) => { + models.value = data.models; + }, +}); + +const doSearch = (page = 1) => { + const filters = { page }; + if (statusFilter.value) filters.status = statusFilter.value; + searcher.search('', filters); +}; + +onMounted(() => doSearch()); + +watch(statusFilter, () => doSearch(1)); + +/** Paginación */ +const pages = () => { + const pages = []; + for (let i = 1; i <= models.value.last_page; i++) { + pages.push(i); + } + return pages; +}; + +/** Abrir detalle — carga info completa */ +const openDetail = (model) => { + Modal.switchShowModal(model); + api.get(apiTo(model.id), { + onSuccess: (data) => { + Modal.modelModal.value = data.model; + } + }); +}; + +/** Actualiza fila en la lista al recibir cambio del modal */ +const handleUpdated = (updatedModel) => { + const idx = models.value.data.findIndex(m => m.id === updatedModel.id); + if (idx > -1) { + models.value.data[idx] = updatedModel; + } +}; + +const formatDate = (iso) => { + if (!iso) return '—'; + return new Date(iso).toLocaleDateString('es-MX', { + year: 'numeric', month: 'short', day: 'numeric', + hour: '2-digit', minute: '2-digit' + }); +}; + +const formatAmount = (amount) => { + if (!amount) return '—'; + return new Intl.NumberFormat('es-MX', { style: 'currency', currency: 'MXN' }).format(amount); +}; + + + diff --git a/src/pages/App/InvoiceRequest/Modal/Show.vue b/src/pages/App/InvoiceRequest/Modal/Show.vue new file mode 100644 index 0000000..a5ef4ba --- /dev/null +++ b/src/pages/App/InvoiceRequest/Modal/Show.vue @@ -0,0 +1,356 @@ + + +