From 54c14f87c8cc3ee34df76f4aee66a48d14107195 Mon Sep 17 00:00:00 2001 From: rckrdmrd Date: Sun, 18 Jan 2026 10:19:43 -0600 Subject: [PATCH] feat(purchases): Add Purchasing module frontend (hooks + pages) - Create purchases hooks (usePurchaseOrders, usePurchaseReceipts) - Create PurchaseOrdersPage with order management and PDF download - Create PurchaseReceiptsPage with receipt validation - Update feature index to export hooks MGN-012 Purchasing frontend implementation Co-Authored-By: Claude Opus 4.5 --- src/features/purchases/hooks/index.ts | 11 + src/features/purchases/hooks/usePurchases.ts | 306 ++++++++++++ src/features/purchases/index.ts | 1 + src/pages/purchases/PurchaseOrdersPage.tsx | 460 +++++++++++++++++++ src/pages/purchases/PurchaseReceiptsPage.tsx | 399 ++++++++++++++++ src/pages/purchases/index.ts | 2 + 6 files changed, 1179 insertions(+) create mode 100644 src/features/purchases/hooks/index.ts create mode 100644 src/features/purchases/hooks/usePurchases.ts create mode 100644 src/pages/purchases/PurchaseOrdersPage.tsx create mode 100644 src/pages/purchases/PurchaseReceiptsPage.tsx create mode 100644 src/pages/purchases/index.ts diff --git a/src/features/purchases/hooks/index.ts b/src/features/purchases/hooks/index.ts new file mode 100644 index 0000000..ec91d49 --- /dev/null +++ b/src/features/purchases/hooks/index.ts @@ -0,0 +1,11 @@ +export { + usePurchaseOrders, + usePurchaseOrder, + usePurchaseReceipts, + usePurchaseReceipt, +} from './usePurchases'; + +export type { + UsePurchaseOrdersOptions, + UsePurchaseReceiptsOptions, +} from './usePurchases'; diff --git a/src/features/purchases/hooks/usePurchases.ts b/src/features/purchases/hooks/usePurchases.ts new file mode 100644 index 0000000..50072bb --- /dev/null +++ b/src/features/purchases/hooks/usePurchases.ts @@ -0,0 +1,306 @@ +import { useState, useEffect, useCallback } from 'react'; +import { purchasesApi } from '../api/purchases.api'; +import type { + PurchaseOrder, + PurchaseOrderFilters, + CreatePurchaseOrderDto, + UpdatePurchaseOrderDto, + PurchaseReceipt, + PurchaseReceiptFilters, + CreatePurchaseReceiptDto, +} from '../types'; + +// ==================== Purchase Orders Hook ==================== + +export interface UsePurchaseOrdersOptions extends PurchaseOrderFilters { + autoFetch?: boolean; +} + +export function usePurchaseOrders(options: UsePurchaseOrdersOptions = {}) { + const { autoFetch = true, ...filters } = options; + const [orders, setOrders] = useState([]); + const [total, setTotal] = useState(0); + const [page, setPage] = useState(filters.page || 1); + const [totalPages, setTotalPages] = useState(1); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const fetchOrders = useCallback(async () => { + setIsLoading(true); + setError(null); + try { + const response = await purchasesApi.getOrders({ ...filters, page }); + setOrders(response.data); + setTotal(response.meta.total); + setTotalPages(response.meta.totalPages); + } catch (err) { + setError(err instanceof Error ? err.message : 'Error al cargar ordenes de compra'); + } finally { + setIsLoading(false); + } + }, [filters.companyId, filters.partnerId, filters.status, filters.dateFrom, filters.dateTo, filters.search, filters.limit, filters.sortBy, filters.sortOrder, page]); + + useEffect(() => { + if (autoFetch) { + fetchOrders(); + } + }, [fetchOrders, autoFetch]); + + const createOrder = async (data: CreatePurchaseOrderDto) => { + setIsLoading(true); + try { + const newOrder = await purchasesApi.createOrder(data); + await fetchOrders(); + return newOrder; + } catch (err) { + setError(err instanceof Error ? err.message : 'Error al crear orden de compra'); + throw err; + } finally { + setIsLoading(false); + } + }; + + const updateOrder = async (id: string, data: UpdatePurchaseOrderDto) => { + setIsLoading(true); + try { + const updated = await purchasesApi.updateOrder(id, data); + await fetchOrders(); + return updated; + } catch (err) { + setError(err instanceof Error ? err.message : 'Error al actualizar orden'); + throw err; + } finally { + setIsLoading(false); + } + }; + + const deleteOrder = async (id: string) => { + setIsLoading(true); + try { + await purchasesApi.deleteOrder(id); + await fetchOrders(); + } catch (err) { + setError(err instanceof Error ? err.message : 'Error al eliminar orden'); + throw err; + } finally { + setIsLoading(false); + } + }; + + const confirmOrder = async (id: string) => { + setIsLoading(true); + try { + await purchasesApi.confirmOrder(id); + await fetchOrders(); + } catch (err) { + setError(err instanceof Error ? err.message : 'Error al confirmar orden'); + throw err; + } finally { + setIsLoading(false); + } + }; + + const cancelOrder = async (id: string) => { + setIsLoading(true); + try { + await purchasesApi.cancelOrder(id); + await fetchOrders(); + } catch (err) { + setError(err instanceof Error ? err.message : 'Error al cancelar orden'); + throw err; + } finally { + setIsLoading(false); + } + }; + + const downloadPdf = async (id: string, fileName?: string) => { + try { + await purchasesApi.downloadOrderPdf(id, fileName); + } catch (err) { + setError(err instanceof Error ? err.message : 'Error al descargar PDF'); + throw err; + } + }; + + return { + orders, + total, + page, + totalPages, + isLoading, + error, + setPage, + refresh: fetchOrders, + createOrder, + updateOrder, + deleteOrder, + confirmOrder, + cancelOrder, + downloadPdf, + }; +} + +// ==================== Single Purchase Order Hook ==================== + +export function usePurchaseOrder(orderId: string | null) { + const [order, setOrder] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const fetchOrder = useCallback(async () => { + if (!orderId) { + setOrder(null); + return; + } + + setIsLoading(true); + setError(null); + try { + const data = await purchasesApi.getOrderById(orderId); + setOrder(data); + } catch (err) { + setError(err instanceof Error ? err.message : 'Error al cargar orden de compra'); + } finally { + setIsLoading(false); + } + }, [orderId]); + + useEffect(() => { + fetchOrder(); + }, [fetchOrder]); + + return { + order, + isLoading, + error, + refresh: fetchOrder, + }; +} + +// ==================== Purchase Receipts Hook ==================== + +export interface UsePurchaseReceiptsOptions extends PurchaseReceiptFilters { + autoFetch?: boolean; +} + +export function usePurchaseReceipts(options: UsePurchaseReceiptsOptions = {}) { + const { autoFetch = true, ...filters } = options; + const [receipts, setReceipts] = useState([]); + const [total, setTotal] = useState(0); + const [page, setPage] = useState(filters.page || 1); + const [totalPages, setTotalPages] = useState(1); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const fetchReceipts = useCallback(async () => { + setIsLoading(true); + setError(null); + try { + const response = await purchasesApi.getReceipts({ ...filters, page }); + setReceipts(response.data); + setTotal(response.meta.total); + setTotalPages(response.meta.totalPages); + } catch (err) { + setError(err instanceof Error ? err.message : 'Error al cargar recepciones'); + } finally { + setIsLoading(false); + } + }, [filters.purchaseOrderId, filters.partnerId, filters.status, filters.dateFrom, filters.dateTo, filters.search, filters.limit, page]); + + useEffect(() => { + if (autoFetch) { + fetchReceipts(); + } + }, [fetchReceipts, autoFetch]); + + const createReceipt = async (data: CreatePurchaseReceiptDto) => { + setIsLoading(true); + try { + const newReceipt = await purchasesApi.createReceipt(data); + await fetchReceipts(); + return newReceipt; + } catch (err) { + setError(err instanceof Error ? err.message : 'Error al crear recepcion'); + throw err; + } finally { + setIsLoading(false); + } + }; + + const confirmReceipt = async (id: string) => { + setIsLoading(true); + try { + await purchasesApi.confirmReceipt(id); + await fetchReceipts(); + } catch (err) { + setError(err instanceof Error ? err.message : 'Error al confirmar recepcion'); + throw err; + } finally { + setIsLoading(false); + } + }; + + const cancelReceipt = async (id: string) => { + setIsLoading(true); + try { + await purchasesApi.cancelReceipt(id); + await fetchReceipts(); + } catch (err) { + setError(err instanceof Error ? err.message : 'Error al cancelar recepcion'); + throw err; + } finally { + setIsLoading(false); + } + }; + + return { + receipts, + total, + page, + totalPages, + isLoading, + error, + setPage, + refresh: fetchReceipts, + createReceipt, + confirmReceipt, + cancelReceipt, + }; +} + +// ==================== Single Purchase Receipt Hook ==================== + +export function usePurchaseReceipt(receiptId: string | null) { + const [receipt, setReceipt] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const fetchReceipt = useCallback(async () => { + if (!receiptId) { + setReceipt(null); + return; + } + + setIsLoading(true); + setError(null); + try { + const data = await purchasesApi.getReceiptById(receiptId); + setReceipt(data); + } catch (err) { + setError(err instanceof Error ? err.message : 'Error al cargar recepcion'); + } finally { + setIsLoading(false); + } + }, [receiptId]); + + useEffect(() => { + fetchReceipt(); + }, [fetchReceipt]); + + return { + receipt, + isLoading, + error, + refresh: fetchReceipt, + }; +} diff --git a/src/features/purchases/index.ts b/src/features/purchases/index.ts index a4b7077..9f0e7e5 100644 --- a/src/features/purchases/index.ts +++ b/src/features/purchases/index.ts @@ -1,2 +1,3 @@ export * from './api/purchases.api'; export * from './types'; +export * from './hooks'; diff --git a/src/pages/purchases/PurchaseOrdersPage.tsx b/src/pages/purchases/PurchaseOrdersPage.tsx new file mode 100644 index 0000000..d92c284 --- /dev/null +++ b/src/pages/purchases/PurchaseOrdersPage.tsx @@ -0,0 +1,460 @@ +import { useState } from 'react'; +import { + ShoppingBag, + Plus, + MoreVertical, + Eye, + CheckCircle, + XCircle, + Calendar, + DollarSign, + RefreshCw, + Search, + FileText, + Package, + Download, +} from 'lucide-react'; +import { Button } from '@components/atoms/Button'; +import { Card, CardHeader, CardTitle, CardContent } from '@components/molecules/Card'; +import { DataTable, type Column } from '@components/organisms/DataTable'; +import { Dropdown, type DropdownItem } from '@components/organisms/Dropdown'; +import { Breadcrumbs } from '@components/organisms/Breadcrumbs'; +import { ConfirmModal } from '@components/organisms/Modal'; +import { NoDataEmptyState, ErrorEmptyState } from '@components/templates/EmptyState'; +import { usePurchaseOrders } from '@features/purchases/hooks'; +import type { PurchaseOrder, PurchaseOrderStatus } from '@features/purchases/types'; +import { formatDate, formatNumber } from '@utils/formatters'; + +const statusLabels: Record = { + draft: 'Borrador', + sent: 'Enviado', + confirmed: 'Confirmado', + done: 'Completado', + cancelled: 'Cancelado', +}; + +const statusColors: Record = { + draft: 'bg-gray-100 text-gray-700', + sent: 'bg-blue-100 text-blue-700', + confirmed: 'bg-green-100 text-green-700', + done: 'bg-purple-100 text-purple-700', + cancelled: 'bg-red-100 text-red-700', +}; + +// Helper function to format currency with 2 decimals +const formatCurrency = (value: number): string => { + return formatNumber(value, 'es-MX', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); +}; + +export function PurchaseOrdersPage() { + const [selectedStatus, setSelectedStatus] = useState(''); + const [searchTerm, setSearchTerm] = useState(''); + const [dateFrom, setDateFrom] = useState(''); + const [dateTo, setDateTo] = useState(''); + const [orderToConfirm, setOrderToConfirm] = useState(null); + const [orderToCancel, setOrderToCancel] = useState(null); + + const { + orders, + total, + page, + totalPages, + isLoading, + error, + setPage, + refresh, + confirmOrder, + cancelOrder, + downloadPdf, + } = usePurchaseOrders({ + status: selectedStatus || undefined, + search: searchTerm || undefined, + dateFrom: dateFrom || undefined, + dateTo: dateTo || undefined, + limit: 20, + }); + + const getActionsMenu = (order: PurchaseOrder): DropdownItem[] => { + const items: DropdownItem[] = [ + { + key: 'view', + label: 'Ver detalle', + icon: , + onClick: () => console.log('View', order.id), + }, + { + key: 'pdf', + label: 'Descargar PDF', + icon: , + onClick: () => downloadPdf(order.id, `OC-${order.name}.pdf`), + }, + ]; + + if (order.status === 'draft') { + items.push({ + key: 'confirm', + label: 'Confirmar orden', + icon: , + onClick: () => setOrderToConfirm(order), + }); + items.push({ + key: 'cancel', + label: 'Cancelar', + icon: , + danger: true, + onClick: () => setOrderToCancel(order), + }); + } + + if (order.status === 'confirmed') { + items.push({ + key: 'receive', + label: 'Recibir productos', + icon: , + onClick: () => console.log('Receive', order.id), + }); + items.push({ + key: 'invoice', + label: 'Crear factura', + icon: , + onClick: () => console.log('Invoice', order.id), + }); + } + + return items; + }; + + const columns: Column[] = [ + { + key: 'name', + header: 'Orden', + render: (order) => ( +
+
+ +
+
+
{order.name}
+ {order.ref && ( +
Ref: {order.ref}
+ )} +
+
+ ), + }, + { + key: 'partner', + header: 'Proveedor', + render: (order) => ( +
+
{order.partnerName || order.partnerId}
+
+ ), + }, + { + key: 'date', + header: 'Fecha', + sortable: true, + render: (order) => ( + + {formatDate(order.orderDate, 'short')} + + ), + }, + { + key: 'expectedDate', + header: 'Entrega Esperada', + render: (order) => ( + + {order.expectedDate ? formatDate(order.expectedDate, 'short') : '-'} + + ), + }, + { + key: 'amount', + header: 'Total', + sortable: true, + render: (order) => ( +
+
+ ${formatCurrency(order.amountTotal)} +
+ {order.currencyCode && order.currencyCode !== 'MXN' && ( +
{order.currencyCode}
+ )} +
+ ), + }, + { + key: 'receiptStatus', + header: 'Recepcion', + render: (order) => { + const status = order.receiptStatus || 'pending'; + const colors: Record = { + pending: 'bg-amber-100 text-amber-700', + partial: 'bg-blue-100 text-blue-700', + received: 'bg-green-100 text-green-700', + }; + const labels: Record = { + pending: 'Pendiente', + partial: 'Parcial', + received: 'Recibido', + }; + return ( + + {labels[status] || status} + + ); + }, + }, + { + key: 'status', + header: 'Estado', + render: (order) => ( + + {statusLabels[order.status]} + + ), + }, + { + key: 'actions', + header: '', + render: (order) => ( + + + + } + items={getActionsMenu(order)} + align="right" + /> + ), + }, + ]; + + const handleConfirm = async () => { + if (orderToConfirm) { + await confirmOrder(orderToConfirm.id); + setOrderToConfirm(null); + } + }; + + const handleCancel = async () => { + if (orderToCancel) { + await cancelOrder(orderToCancel.id); + setOrderToCancel(null); + } + }; + + // Calculate summary stats + const draftCount = orders.filter(o => o.status === 'draft').length; + const confirmedCount = orders.filter(o => o.status === 'confirmed').length; + const totalAmount = orders.reduce((sum, o) => sum + o.amountTotal, 0); + const pendingReceipt = orders.filter(o => o.receiptStatus === 'pending' && o.status === 'confirmed').length; + + if (error) { + return ( +
+ +
+ ); + } + + return ( +
+ + +
+
+

Ordenes de Compra

+

+ Gestiona ordenes de compra y recepciones de proveedores +

+
+
+ + +
+
+ + {/* Summary Stats */} +
+ setSelectedStatus('draft')}> + +
+
+ +
+
+
Borradores
+
{draftCount}
+
+
+
+
+ + setSelectedStatus('confirmed')}> + +
+
+ +
+
+
Confirmadas
+
{confirmedCount}
+
+
+
+
+ + + +
+
+ +
+
+
Por Recibir
+
{pendingReceipt}
+
+
+
+
+ + + +
+
+ +
+
+
Total Compras
+
${formatCurrency(totalAmount)}
+
+
+
+
+
+ + + + Lista de Ordenes + + +
+ {/* Filters */} +
+
+ + setSearchTerm(e.target.value)} + className="w-full rounded-md border border-gray-300 py-2 pl-10 pr-4 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" + /> +
+ + + +
+ + setDateFrom(e.target.value)} + className="rounded-md border border-gray-300 px-3 py-2 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" + /> + - + setDateTo(e.target.value)} + className="rounded-md border border-gray-300 px-3 py-2 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" + /> +
+ + {(selectedStatus || searchTerm || dateFrom || dateTo) && ( + + )} +
+ + {/* Table */} + {orders.length === 0 && !isLoading ? ( + + ) : ( + + )} +
+
+
+ + {/* Confirm Order Modal */} + setOrderToConfirm(null)} + onConfirm={handleConfirm} + title="Confirmar orden de compra" + message={`¿Confirmar la orden ${orderToConfirm?.name}? Esta accion enviara la orden al proveedor.`} + variant="success" + confirmText="Confirmar" + /> + + {/* Cancel Order Modal */} + setOrderToCancel(null)} + onConfirm={handleCancel} + title="Cancelar orden de compra" + message={`¿Cancelar la orden ${orderToCancel?.name}? Esta accion no se puede deshacer.`} + variant="danger" + confirmText="Cancelar orden" + /> +
+ ); +} + +export default PurchaseOrdersPage; diff --git a/src/pages/purchases/PurchaseReceiptsPage.tsx b/src/pages/purchases/PurchaseReceiptsPage.tsx new file mode 100644 index 0000000..da9269a --- /dev/null +++ b/src/pages/purchases/PurchaseReceiptsPage.tsx @@ -0,0 +1,399 @@ +import { useState } from 'react'; +import { + Package, + Plus, + MoreVertical, + Eye, + CheckCircle, + XCircle, + Calendar, + RefreshCw, + Search, + Truck, + ClipboardList, +} from 'lucide-react'; +import { Button } from '@components/atoms/Button'; +import { Card, CardHeader, CardTitle, CardContent } from '@components/molecules/Card'; +import { DataTable, type Column } from '@components/organisms/DataTable'; +import { Dropdown, type DropdownItem } from '@components/organisms/Dropdown'; +import { Breadcrumbs } from '@components/organisms/Breadcrumbs'; +import { ConfirmModal } from '@components/organisms/Modal'; +import { NoDataEmptyState, ErrorEmptyState } from '@components/templates/EmptyState'; +import { usePurchaseReceipts } from '@features/purchases/hooks'; +import type { PurchaseReceipt } from '@features/purchases/types'; +import { formatDate } from '@utils/formatters'; + +type ReceiptStatus = 'draft' | 'done' | 'cancelled'; + +const statusLabels: Record = { + draft: 'Borrador', + done: 'Completado', + cancelled: 'Cancelado', +}; + +const statusColors: Record = { + draft: 'bg-gray-100 text-gray-700', + done: 'bg-green-100 text-green-700', + cancelled: 'bg-red-100 text-red-700', +}; + +// Helper function to get current date +const getToday = (): string => { + const today = new Date().toISOString().split('T')[0]; + return today || ''; +}; + +export function PurchaseReceiptsPage() { + const [selectedStatus, setSelectedStatus] = useState(''); + const [searchTerm, setSearchTerm] = useState(''); + const [dateFrom, setDateFrom] = useState(''); + const [dateTo, setDateTo] = useState(''); + const [receiptToConfirm, setReceiptToConfirm] = useState(null); + const [receiptToCancel, setReceiptToCancel] = useState(null); + + const { + receipts, + total, + page, + totalPages, + isLoading, + error, + setPage, + refresh, + confirmReceipt, + cancelReceipt, + } = usePurchaseReceipts({ + status: selectedStatus || undefined, + search: searchTerm || undefined, + dateFrom: dateFrom || undefined, + dateTo: dateTo || undefined, + limit: 20, + }); + + const getActionsMenu = (receipt: PurchaseReceipt): DropdownItem[] => { + const items: DropdownItem[] = [ + { + key: 'view', + label: 'Ver detalle', + icon: , + onClick: () => console.log('View', receipt.id), + }, + ]; + + if (receipt.status === 'draft') { + items.push({ + key: 'confirm', + label: 'Validar recepcion', + icon: , + onClick: () => setReceiptToConfirm(receipt), + }); + items.push({ + key: 'cancel', + label: 'Cancelar', + icon: , + danger: true, + onClick: () => setReceiptToCancel(receipt), + }); + } + + return items; + }; + + const columns: Column[] = [ + { + key: 'receiptNumber', + header: 'Recepcion', + render: (receipt) => ( +
+
+ +
+
+
{receipt.receiptNumber}
+
OC: {receipt.purchaseOrderName || receipt.purchaseOrderId}
+
+
+ ), + }, + { + key: 'partner', + header: 'Proveedor', + render: (receipt) => ( +
+
{receipt.partnerName || receipt.partnerId}
+
+ ), + }, + { + key: 'date', + header: 'Fecha Recepcion', + sortable: true, + render: (receipt) => ( + + {formatDate(receipt.receiptDate, 'short')} + + ), + }, + { + key: 'items', + header: 'Productos', + render: (receipt) => ( +
+ + + {receipt.lines?.length || 0} lineas + +
+ ), + }, + { + key: 'status', + header: 'Estado', + render: (receipt) => ( + + {statusLabels[receipt.status]} + + ), + }, + { + key: 'actions', + header: '', + render: (receipt) => ( + + + + } + items={getActionsMenu(receipt)} + align="right" + /> + ), + }, + ]; + + const handleConfirm = async () => { + if (receiptToConfirm) { + await confirmReceipt(receiptToConfirm.id); + setReceiptToConfirm(null); + } + }; + + const handleCancel = async () => { + if (receiptToCancel) { + await cancelReceipt(receiptToCancel.id); + setReceiptToCancel(null); + } + }; + + // Calculate summary stats + const draftCount = receipts.filter(r => r.status === 'draft').length; + const doneCount = receipts.filter(r => r.status === 'done').length; + const todayReceipts = receipts.filter(r => r.receiptDate === getToday()).length; + const totalItems = receipts.reduce((sum, r) => sum + (r.lines?.length || 0), 0); + + if (error) { + return ( +
+ +
+ ); + } + + return ( +
+ + +
+
+

Recepciones de Compra

+

+ Registra y valida la recepcion de productos de proveedores +

+
+
+ + +
+
+ + {/* Summary Stats */} +
+ setSelectedStatus('draft')}> + +
+
+ +
+
+
Pendientes
+
{draftCount}
+
+
+
+
+ + setSelectedStatus('done')}> + +
+
+ +
+
+
Completadas
+
{doneCount}
+
+
+
+
+ + + +
+
+ +
+
+
Hoy
+
{todayReceipts}
+
+
+
+
+ + + +
+
+ +
+
+
Items Recibidos
+
{totalItems}
+
+
+
+
+
+ + + + Lista de Recepciones + + +
+ {/* Filters */} +
+
+ + setSearchTerm(e.target.value)} + className="w-full rounded-md border border-gray-300 py-2 pl-10 pr-4 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" + /> +
+ + + +
+ + setDateFrom(e.target.value)} + className="rounded-md border border-gray-300 px-3 py-2 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" + /> + - + setDateTo(e.target.value)} + className="rounded-md border border-gray-300 px-3 py-2 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" + /> +
+ + {(selectedStatus || searchTerm || dateFrom || dateTo) && ( + + )} +
+ + {/* Table */} + {receipts.length === 0 && !isLoading ? ( + + ) : ( + + )} +
+
+
+ + {/* Confirm Receipt Modal */} + setReceiptToConfirm(null)} + onConfirm={handleConfirm} + title="Validar recepcion" + message={`¿Validar la recepcion ${receiptToConfirm?.receiptNumber}? Esto actualizara el inventario.`} + variant="success" + confirmText="Validar" + /> + + {/* Cancel Receipt Modal */} + setReceiptToCancel(null)} + onConfirm={handleCancel} + title="Cancelar recepcion" + message={`¿Cancelar la recepcion ${receiptToCancel?.receiptNumber}? Esta accion no se puede deshacer.`} + variant="danger" + confirmText="Cancelar recepcion" + /> +
+ ); +} + +export default PurchaseReceiptsPage; diff --git a/src/pages/purchases/index.ts b/src/pages/purchases/index.ts new file mode 100644 index 0000000..868e6b7 --- /dev/null +++ b/src/pages/purchases/index.ts @@ -0,0 +1,2 @@ +export { PurchaseOrdersPage, default as PurchaseOrdersPageDefault } from './PurchaseOrdersPage'; +export { PurchaseReceiptsPage, default as PurchaseReceiptsPageDefault } from './PurchaseReceiptsPage';