From d317e8d06f1063168f2001800d178cad08dda3fa Mon Sep 17 00:00:00 2001 From: efrilm Date: Thu, 25 Sep 2025 16:20:13 +0700 Subject: [PATCH] update purchase order --- package-lock.json | 84 ++-- package.json | 4 +- src/services/mutations/purchaseOrder.ts | 18 +- .../PurchaseDetailInformation.tsx | 433 ++++++++++++------ 4 files changed, 370 insertions(+), 169 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8a79567..6eec3bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,7 +53,8 @@ "fs-extra": "11.2.0", "html2canvas": "^1.4.1", "input-otp": "1.4.1", - "jspdf": "^3.0.1", + "jspdf": "^3.0.3", + "jspdf-autotable": "^5.0.2", "keen-slider": "6.8.6", "lucide-react": "^0.544.0", "mapbox-gl": "3.9.0", @@ -83,6 +84,7 @@ "@iconify/types": "2.0.0", "@iconify/utils": "2.2.1", "@types/fs-extra": "^11.0.4", + "@types/jspdf": "^1.3.3", "@types/mapbox-gl": "^3.4.1", "@types/negotiator": "^0.6.3", "@types/node": "^22.10.2", @@ -3508,6 +3510,13 @@ "@types/node": "*" } }, + "node_modules/@types/jspdf": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@types/jspdf/-/jspdf-1.3.3.tgz", + "integrity": "sha512-DqwyAKpVuv+7DniCp2Deq1xGvfdnKSNgl9Agun2w6dFvR5UKamiv4VfYUgcypd8S9ojUyARFIlZqBrYrBMQlew==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", @@ -3573,6 +3582,12 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/pako": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz", + "integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==", + "license": "MIT" + }, "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", @@ -4281,18 +4296,6 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "license": "(MIT OR Apache-2.0)", - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, "node_modules/attr-accept": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz", @@ -4510,18 +4513,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/btoa": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", - "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", - "license": "(MIT OR Apache-2.0)", - "bin": { - "btoa": "bin/btoa.js" - }, - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -6642,6 +6633,17 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-png": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.4.0.tgz", + "integrity": "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==", + "license": "MIT", + "dependencies": { + "@types/pako": "^2.0.3", + "iobuffer": "^5.3.2", + "pako": "^2.1.0" + } + }, "node_modules/fast-shallow-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz", @@ -7683,6 +7685,12 @@ "node": ">=12" } }, + "node_modules/iobuffer": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz", + "integrity": "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==", + "license": "MIT" + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -8304,14 +8312,13 @@ } }, "node_modules/jspdf": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.1.tgz", - "integrity": "sha512-qaGIxqxetdoNnFQQXxTKUD9/Z7AloLaw94fFsOiJMxbfYdBbrBuhWmbzI8TVjrw7s3jBY1PFHofBKMV/wZPapg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.3.tgz", + "integrity": "sha512-eURjAyz5iX1H8BOYAfzvdPfIKK53V7mCpBTe7Kb16PaM8JSXEcUQNBQaiWMI8wY5RvNOPj4GccMjTlfwRBd+oQ==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.26.7", - "atob": "^2.1.2", - "btoa": "^1.2.1", + "@babel/runtime": "^7.26.9", + "fast-png": "^6.2.0", "fflate": "^0.8.1" }, "optionalDependencies": { @@ -8321,6 +8328,15 @@ "html2canvas": "^1.0.0-rc.5" } }, + "node_modules/jspdf-autotable": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/jspdf-autotable/-/jspdf-autotable-5.0.2.tgz", + "integrity": "sha512-YNKeB7qmx3pxOLcNeoqAv3qTS7KuvVwkFe5AduCawpop3NOkBUtqDToxNc225MlNecxT4kP2Zy3z/y/yvGdXUQ==", + "license": "MIT", + "peerDependencies": { + "jspdf": "^2 || ^3" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -9196,6 +9212,12 @@ "quansync": "^0.2.7" } }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "license": "(MIT AND Zlib)" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", diff --git a/package.json b/package.json index 315044a..504dbd6 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,8 @@ "fs-extra": "11.2.0", "html2canvas": "^1.4.1", "input-otp": "1.4.1", - "jspdf": "^3.0.1", + "jspdf": "^3.0.3", + "jspdf-autotable": "^5.0.2", "keen-slider": "6.8.6", "lucide-react": "^0.544.0", "mapbox-gl": "3.9.0", @@ -89,6 +90,7 @@ "@iconify/types": "2.0.0", "@iconify/utils": "2.2.1", "@types/fs-extra": "^11.0.4", + "@types/jspdf": "^1.3.3", "@types/mapbox-gl": "^3.4.1", "@types/negotiator": "^0.6.3", "@types/node": "^22.10.2", diff --git a/src/services/mutations/purchaseOrder.ts b/src/services/mutations/purchaseOrder.ts index 20356b8..cb08044 100644 --- a/src/services/mutations/purchaseOrder.ts +++ b/src/services/mutations/purchaseOrder.ts @@ -26,7 +26,7 @@ export const usePurchaseOrdersMutation = () => { return response.data }, onSuccess: () => { - toast.success('Purchase Order created successfully!') + toast.success('Purchase Order Payment successfully!') queryClient.invalidateQueries({ queryKey: ['purchase-orders'] }) }, onError: (error: any) => { @@ -34,5 +34,19 @@ export const usePurchaseOrdersMutation = () => { } }) - return { createPurchaseOrder, sendPaymentPurchaseOrder } + const updateStatus = useMutation({ + mutationFn: async ({ id, payload }: { id: string; payload: 'approved' | 'rejected' }) => { + const response = await api.put(`/purchase-orders/${id}`, { status: payload }) + return response.data + }, + onSuccess: () => { + toast.success('Purchase Order Status successfully!') + queryClient.invalidateQueries({ queryKey: ['purchase-orders'] }) + }, + onError: (error: any) => { + toast.error(error.response?.data?.errors?.[0]?.cause || 'Create failed') + } + }) + + return { createPurchaseOrder, sendPaymentPurchaseOrder, updateStatus } } diff --git a/src/views/apps/purchase/purchase-detail/PurchaseDetailInformation.tsx b/src/views/apps/purchase/purchase-detail/PurchaseDetailInformation.tsx index dfdae30..e6b5180 100644 --- a/src/views/apps/purchase/purchase-detail/PurchaseDetailInformation.tsx +++ b/src/views/apps/purchase/purchase-detail/PurchaseDetailInformation.tsx @@ -1,6 +1,6 @@ 'use client' -import React from 'react' +import React, { useState } from 'react' import { Card, CardHeader, @@ -14,10 +14,19 @@ import { TableRow, Box, Button, - IconButton + IconButton, + Menu, + MenuItem, + Dialog, + DialogTitle, + DialogContent, + DialogContentText, + DialogActions, + CircularProgress } from '@mui/material' import Grid from '@mui/material/Grid2' -import { PurchaseOrder } from '@/types/services/purchaseOrder' +import { PurchaseOrder, SendPaymentPurchaseOrderRequest } from '@/types/services/purchaseOrder' +import { usePurchaseOrdersMutation } from '@/services/mutations/purchaseOrder' interface Props { data?: PurchaseOrder @@ -26,6 +35,88 @@ interface Props { const PurchaseDetailInformation = ({ data }: Props) => { const purchaseOrder = data + // State for menu and dialog + const [anchorEl, setAnchorEl] = useState(null) + const [isSubmitting, setIsSubmitting] = useState(false) + const [confirmDialog, setConfirmDialog] = useState<{ + open: boolean + type: 'approve' | 'reject' | null + title: string + message: string + }>({ + open: false, + type: null, + title: '', + message: '' + }) + + const { updateStatus } = usePurchaseOrdersMutation() + + const handleMenuClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget) + } + + const handleMenuClose = () => { + setAnchorEl(null) + } + + const handleApproveClick = () => { + setConfirmDialog({ + open: true, + type: 'approve', + title: 'Konfirmasi Persetujuan', + message: 'Apakah Anda yakin ingin menyetujui purchase order ini?' + }) + handleMenuClose() + } + + const handleRejectClick = () => { + setConfirmDialog({ + open: true, + type: 'reject', + title: 'Konfirmasi Penolakan', + message: 'Apakah Anda yakin ingin menolak purchase order ini?' + }) + handleMenuClose() + } + + const handleConfirmAction = async () => { + if (!purchaseOrder?.id) return + + setIsSubmitting(true) + + try { + const status = confirmDialog.type === 'approve' ? 'approved' : 'rejected' + + updateStatus.mutate({ + id: purchaseOrder.id, + payload: status + }) + + // Close dialog after successful submission + setConfirmDialog({ + open: false, + type: null, + title: '', + message: '' + }) + } catch (error) { + console.error('Error updating status:', error) + // Handle error (you might want to show a toast or error message) + } finally { + setIsSubmitting(false) + } + } + + const handleCancelAction = () => { + setConfirmDialog({ + open: false, + type: null, + title: '', + message: '' + }) + } + // Helper functions const formatDate = (dateString: string): string => { const date = new Date(dateString) @@ -46,7 +137,8 @@ const PurchaseDetailInformation = ({ data }: Props) => { sent: 'Dikirim', approved: 'Disetujui', received: 'Diterima', - cancelled: 'Dibatalkan' + cancelled: 'Dibatalkan', + rejected: 'Ditolak' } return statusMap[status] || status } @@ -57,7 +149,8 @@ const PurchaseDetailInformation = ({ data }: Props) => { sent: 'warning', approved: 'success', received: 'info', - cancelled: 'error' + cancelled: 'error', + rejected: 'error' } return colorMap[status] || 'info' } @@ -66,144 +159,214 @@ const PurchaseDetailInformation = ({ data }: Props) => { const totalQuantity = (purchaseOrder?.items ?? []).reduce((sum, item) => sum + (item?.quantity ?? 0), 0) const total = (purchaseOrder?.items ?? []).reduce((sum, item) => sum + (item?.amount ?? 0) * item?.quantity, 0) + // Check if actions should be available (only for certain statuses) + const canApproveOrReject = purchaseOrder?.status === 'received' + return ( - - - - {getStatusLabel(purchaseOrder?.status ?? '')} - - - - - - - - - - } - /> - - - {/* Purchase Information */} - - - - - Vendor - - - {purchaseOrder?.vendor?.name ?? ''} + <> + + + + {getStatusLabel(purchaseOrder?.status ?? '')} + + + + + + + + } + /> - - - Tgl. Transaksi - - {formatDate(purchaseOrder?.transaction_date ?? '')} - - - - - - - Nomor - - {purchaseOrder?.po_number} - - - - - Tgl. Jatuh Tempo - - {formatDate(purchaseOrder?.due_date ?? '')} - - - - - {/* Products Table */} - - - - - Produk - Deskripsi - Kuantitas - Satuan - Harga - Jumlah - - - - {(purchaseOrder?.items ?? []).map((item, index) => { - return ( - - - - {item.ingredient.name} - - - {item.description} - {item.quantity} - {item.unit.name} - {formatCurrency(item.amount)} - {formatCurrency(item.amount * item.quantity)} - - ) - })} - - {/* Total Quantity Row */} - - - Total Kuantitas - - - {totalQuantity} - - - - - -
-
- - {/* Summary Section */} - - - {/* Empty space for left side */} + + {/* Purchase Information */} + - - - - Total - - - {formatCurrency(total)} - - + + + Vendor + + + {purchaseOrder?.vendor?.name ?? ''} + + + + + + Tgl. Transaksi + + {formatDate(purchaseOrder?.transaction_date ?? '')} + + + + + + + Nomor + + {purchaseOrder?.po_number} + + + + + Tgl. Jatuh Tempo + + {formatDate(purchaseOrder?.due_date ?? '')} - -
-
+ + {/* Products Table */} + + + + + Produk + Deskripsi + Kuantitas + Satuan + Harga + Jumlah + + + + {(purchaseOrder?.items ?? []).map((item, index) => { + return ( + + + + {item.ingredient.name} + + + {item.description} + {item.quantity} + {item.unit.name} + {formatCurrency(item.amount)} + {formatCurrency(item.amount * item.quantity)} + + ) + })} + + {/* Total Quantity Row */} + + + Total Kuantitas + + + {totalQuantity} + + + + + + +
+
+ + {/* Summary Section */} + + + {/* Empty space for left side */} + + + + + Total + + + {formatCurrency(total)} + + + + + + + + + + {/* Menu */} + + + + Disetujui + + + + Ditolak + + + + {/* Confirmation Dialog */} + + {confirmDialog.title} + + {confirmDialog.message} + + + + + + + ) }