From c32d08666eee8b23ae2d30b57db2da27366277c4 Mon Sep 17 00:00:00 2001 From: efrilm Date: Wed, 10 Sep 2025 03:23:54 +0700 Subject: [PATCH] Purchase Detail --- .../purchase-bills/[id]/detail/page.tsx | 18 + .../list/PurchaseBillListTable.tsx | 7 +- .../purchase-detail/PurchaseDetailContent.tsx | 26 ++ .../purchase-detail/PurchaseDetailHeader.tsx | 19 + .../PurchaseDetailInformation.tsx | 310 +++++++++++++ .../purchase-detail/PurchaseDetailLog.tsx | 59 +++ .../PurchaseDetailSendPayment.tsx | 417 ++++++++++++++++++ .../PurchaseDetailTransaction.tsx | 150 +++++++ 8 files changed, 1001 insertions(+), 5 deletions(-) create mode 100644 src/app/[lang]/(dashboard)/(private)/apps/purchase/purchase-bills/[id]/detail/page.tsx create mode 100644 src/views/apps/purchase/purchase-detail/PurchaseDetailContent.tsx create mode 100644 src/views/apps/purchase/purchase-detail/PurchaseDetailHeader.tsx create mode 100644 src/views/apps/purchase/purchase-detail/PurchaseDetailInformation.tsx create mode 100644 src/views/apps/purchase/purchase-detail/PurchaseDetailLog.tsx create mode 100644 src/views/apps/purchase/purchase-detail/PurchaseDetailSendPayment.tsx create mode 100644 src/views/apps/purchase/purchase-detail/PurchaseDetailTransaction.tsx diff --git a/src/app/[lang]/(dashboard)/(private)/apps/purchase/purchase-bills/[id]/detail/page.tsx b/src/app/[lang]/(dashboard)/(private)/apps/purchase/purchase-bills/[id]/detail/page.tsx new file mode 100644 index 0000000..2b95d65 --- /dev/null +++ b/src/app/[lang]/(dashboard)/(private)/apps/purchase/purchase-bills/[id]/detail/page.tsx @@ -0,0 +1,18 @@ +import PurchaseDetailContent from '@/views/apps/purchase/purchase-detail/PurchaseDetailContent' +import PurchaseDetailHeader from '@/views/apps/purchase/purchase-detail/PurchaseDetailHeader' +import Grid from '@mui/material/Grid2' + +const PurchaseBillDetailPage = () => { + return ( + + + + + + + + + ) +} + +export default PurchaseBillDetailPage diff --git a/src/views/apps/purchase/purchase-bills/list/PurchaseBillListTable.tsx b/src/views/apps/purchase/purchase-bills/list/PurchaseBillListTable.tsx index 09f9d44..199bc47 100644 --- a/src/views/apps/purchase/purchase-bills/list/PurchaseBillListTable.tsx +++ b/src/views/apps/purchase/purchase-bills/list/PurchaseBillListTable.tsx @@ -188,10 +188,6 @@ const PurchaseBillListTable = () => { setOpenConfirm(false) } - const handleBillClick = (billId: string) => { - console.log('Navigasi ke detail Bill:', billId) - } - const handleStatusFilter = (status: string) => { setStatusFilter(status) } @@ -227,7 +223,8 @@ const PurchaseBillListTable = () => { variant='text' color='primary' className='p-0 min-w-0 font-medium normal-case justify-start' - onClick={() => handleBillClick(row.original.id.toString())} + component={Link} + href={getLocalizedUrl(`/apps/purchase/purchase-bills/${row.original.number}/detail`, locale as Locale)} sx={{ textTransform: 'none', fontWeight: 500, diff --git a/src/views/apps/purchase/purchase-detail/PurchaseDetailContent.tsx b/src/views/apps/purchase/purchase-detail/PurchaseDetailContent.tsx new file mode 100644 index 0000000..391a0b3 --- /dev/null +++ b/src/views/apps/purchase/purchase-detail/PurchaseDetailContent.tsx @@ -0,0 +1,26 @@ +import Grid from '@mui/material/Grid2' +import PurchaseDetailInformation from './PurchaseDetailInformation' +import PurchaseDetailSendPayment from './PurchaseDetailSendPayment' +import PurchaseDetailLog from './PurchaseDetailLog' +import PurchaseDetailTransaction from './PurchaseDetailTransaction' + +const PurchaseDetailContent = () => { + return ( + + + + + + + + + + + + + + + ) +} + +export default PurchaseDetailContent diff --git a/src/views/apps/purchase/purchase-detail/PurchaseDetailHeader.tsx b/src/views/apps/purchase/purchase-detail/PurchaseDetailHeader.tsx new file mode 100644 index 0000000..26e56d9 --- /dev/null +++ b/src/views/apps/purchase/purchase-detail/PurchaseDetailHeader.tsx @@ -0,0 +1,19 @@ +import { Typography } from '@mui/material' + +interface Props { + title: string +} + +const PurchaseDetailHeader = ({ title }: Props) => { + return ( +
+
+ + {title} + +
+
+ ) +} + +export default PurchaseDetailHeader diff --git a/src/views/apps/purchase/purchase-detail/PurchaseDetailInformation.tsx b/src/views/apps/purchase/purchase-detail/PurchaseDetailInformation.tsx new file mode 100644 index 0000000..232b5c6 --- /dev/null +++ b/src/views/apps/purchase/purchase-detail/PurchaseDetailInformation.tsx @@ -0,0 +1,310 @@ +import React from 'react' +import { + Card, + CardHeader, + CardContent, + Typography, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Box, + Button, + IconButton +} from '@mui/material' +import Grid from '@mui/material/Grid2' + +interface Product { + produk: string + deskripsi: string + kuantitas: number + satuan: string + discount: string + harga: number + pajak: string + jumlah: number +} + +interface PurchaseData { + vendor: string + nomor: string + tglTransaksi: string + tglJatuhTempo: string + gudang: string + status: string +} + +const PurchaseDetailInformation: React.FC = () => { + const purchaseData: PurchaseData = { + vendor: 'Bagas Rizki Sihotang S.Farm Widodo', + nomor: 'PI/00053', + tglTransaksi: '08/09/2025', + tglJatuhTempo: '06/10/2025', + gudang: 'Unassigned', + status: 'Belum Dibayar' + } + + const products: Product[] = [ + { + produk: 'CB1 - Chelsea Boots', + deskripsi: 'Ukuran XS', + kuantitas: 3, + satuan: 'Pcs', + discount: '0%', + harga: 299000, + pajak: 'PPN', + jumlah: 897000 + }, + { + produk: 'CB1 - Chelsea Boots', + deskripsi: 'Ukuran M', + kuantitas: 1, + satuan: 'Pcs', + discount: '0%', + harga: 299000, + pajak: 'PPN', + jumlah: 299000 + }, + { + produk: 'KH1 - Kneel High Boots', + deskripsi: 'Ukuran XL', + kuantitas: 1, + satuan: 'Pcs', + discount: '0%', + harga: 299000, + pajak: 'PPN', + jumlah: 299000 + } + ] + + const totalKuantitas: number = products.reduce((sum, product) => sum + product.kuantitas, 0) + const subTotal: number = 1495000 + const ppn: number = 98670 + const total: number = 1593670 + const sisaTagihan: number = 1593670 + + const formatCurrency = (amount: number): string => { + return new Intl.NumberFormat('id-ID').format(amount) + } + + return ( + + + + Belum Dibayar + + + + + + + + + + } + /> + + + {/* Purchase Information */} + + + + + Vendor + + + {purchaseData.vendor} + + + + + + Tgl. Transaksi + + {purchaseData.tglTransaksi} + + + + + Gudang + + + {purchaseData.gudang} + + + + + + + + Nomor + + {purchaseData.nomor} + + + + + Tgl. Jatuh Tempo + + {purchaseData.tglJatuhTempo} + + + + + {/* Products Table */} + + + + + Produk + Deskripsi + Kuantitas + Satuan + Discount + Harga + Pajak + Jumlah + + + + {products.map((product, index) => ( + + + + {product.produk} + + + {product.deskripsi} + {product.kuantitas} + {product.satuan} + {product.discount} + {formatCurrency(product.harga)} + {product.pajak} + {formatCurrency(product.jumlah)} + + ))} + + {/* Total Kuantitas Row */} + + + Total Kuantitas + + + {totalKuantitas} + + + + + + + + +
+
+ + {/* Summary Section */} + + + {/* Empty space for left side */} + + + + + Sub Total + + + {formatCurrency(subTotal)} + + + + + + PPN + + + {formatCurrency(ppn)} + + + + + + Total + + + {formatCurrency(total)} + + + + + + Sisa Tagihan + + + {formatCurrency(sisaTagihan)} + + + + + + +
+
+ ) +} + +export default PurchaseDetailInformation diff --git a/src/views/apps/purchase/purchase-detail/PurchaseDetailLog.tsx b/src/views/apps/purchase/purchase-detail/PurchaseDetailLog.tsx new file mode 100644 index 0000000..20711ac --- /dev/null +++ b/src/views/apps/purchase/purchase-detail/PurchaseDetailLog.tsx @@ -0,0 +1,59 @@ +'use client' + +import React from 'react' +import { Card, CardContent, CardHeader, Typography, Box, Link } from '@mui/material' + +interface LogEntry { + id: string + action: string + timestamp: string + user: string +} + +const PurchaseDetailLog: React.FC = () => { + const logEntries: LogEntry[] = [ + { + id: '1', + action: 'Terakhir diubah oleh', + timestamp: '08 Sep 2025 18:26', + user: 'pada' + } + ] + + return ( + + + Pantau log perubahan data + + } + sx={{ pb: 1 }} + /> + + {logEntries.map(entry => ( + + + + + {entry.action} {entry.user} {entry.timestamp} + + + + ))} + + + ) +} + +export default PurchaseDetailLog diff --git a/src/views/apps/purchase/purchase-detail/PurchaseDetailSendPayment.tsx b/src/views/apps/purchase/purchase-detail/PurchaseDetailSendPayment.tsx new file mode 100644 index 0000000..6f64a4a --- /dev/null +++ b/src/views/apps/purchase/purchase-detail/PurchaseDetailSendPayment.tsx @@ -0,0 +1,417 @@ +'use client' + +import React, { useState } from 'react' +import { + Card, + CardContent, + Typography, + Button, + Box, + Accordion, + AccordionSummary, + AccordionDetails, + Tooltip, + IconButton, + CardHeader +} from '@mui/material' +import Grid from '@mui/material/Grid2' +import CustomTextField from '@/@core/components/mui/TextField' +import CustomAutocomplete from '@/@core/components/mui/Autocomplete' + +interface PaymentFormData { + totalDibayar: string + tglTransaksi: string + referensi: string + nomor: string + dibayarDari: string +} + +interface PemotonganItem { + id: string + dipotong: string + persentase: string + nominal: string + tipe: 'persen' | 'rupiah' +} + +const PurchaseDetailSendPayment: React.FC = () => { + const [formData, setFormData] = useState({ + totalDibayar: '849.000', + tglTransaksi: '10/09/2025', + referensi: '', + nomor: 'PP/00025', + dibayarDari: '1-10001 Kas' + }) + + const [expanded, setExpanded] = useState(false) + const [pemotonganItems, setPemotonganItems] = useState([]) + + const dibayarDariOptions = [ + { label: '1-10001 Kas', value: '1-10001 Kas' }, + { label: '1-10002 Bank BCA', value: '1-10002 Bank BCA' }, + { label: '1-10003 Bank Mandiri', value: '1-10003 Bank Mandiri' }, + { label: '1-10004 Petty Cash', value: '1-10004 Petty Cash' } + ] + + const pemotonganOptions = [ + { label: 'PPN 11%', value: 'ppn' }, + { label: 'PPh 21', value: 'pph21' }, + { label: 'PPh 23', value: 'pph23' }, + { label: 'Biaya Admin', value: 'admin' } + ] + + const handleChange = + (field: keyof PaymentFormData) => (event: React.ChangeEvent | any) => { + setFormData(prev => ({ + ...prev, + [field]: event.target.value + })) + } + + const handleDibayarDariChange = (value: { label: string; value: string } | null) => { + setFormData(prev => ({ + ...prev, + dibayarDari: value?.value || '' + })) + } + + const addPemotongan = () => { + const newItem: PemotonganItem = { + id: Date.now().toString(), + dipotong: '', + persentase: '0', + nominal: '', + tipe: 'persen' + } + setPemotonganItems(prev => [...prev, newItem]) + } + + const removePemotongan = (id: string) => { + setPemotonganItems(prev => prev.filter(item => item.id !== id)) + } + + const updatePemotongan = (id: string, field: keyof PemotonganItem, value: string) => { + setPemotonganItems(prev => prev.map(item => (item.id === id ? { ...item, [field]: value } : item))) + } + + const handleAccordionChange = () => { + setExpanded(!expanded) + } + + const calculatePemotongan = (item: PemotonganItem): number => { + const totalDibayar = parseInt(formData.totalDibayar.replace(/\D/g, '')) || 0 + const nilai = parseFloat(item.persentase) || 0 + + if (item.tipe === 'persen') { + return (totalDibayar * nilai) / 100 + } else { + return nilai + } + } + + const formatCurrency = (amount: string | number): string => { + const numAmount = typeof amount === 'string' ? parseInt(amount.replace(/\D/g, '')) : amount + return new Intl.NumberFormat('id-ID').format(numAmount) + } + + return ( + + + + Kirim Pembayaran + + + } + /> + + + {/* Left Column */} + + {/* Total Dibayar */} + + + * Total Dibayar + + } + value={formData.totalDibayar} + onChange={handleChange('totalDibayar')} + sx={{ + '& .MuiInputBase-root': { + textAlign: 'right' + } + }} + /> + + + {/* Tgl. Transaksi */} + + + * Tgl. Transaksi + + } + type='date' + value={formData.tglTransaksi.split('/').reverse().join('-')} + onChange={handleChange('tglTransaksi')} + slotProps={{ + input: { + endAdornment: + } + }} + /> + + + {/* Referensi */} + + + + Referensi + + + + + + + + + + + {/* Attachment Accordion */} + + + } + sx={{ + backgroundColor: '#f8f9fa', + borderRadius: '8px', + minHeight: '48px', + '& .MuiAccordionSummary-content': { + margin: '12px 0' + } + }} + > + + Attachment + + + + + Drag and drop files here or click to upload + + + + + + {/* Right Column */} + + {/* Nomor */} + + + + Nomor + + + + + + + + + + + {/* Dibayar Dari */} + + + * Dibayar Dari + + option.label || ''} + value={dibayarDariOptions.find(option => option.value === formData.dibayarDari) || null} + onChange={(_, value: { label: string; value: string } | null) => handleDibayarDariChange(value)} + renderInput={(params: any) => } + noOptionsText='Tidak ada pilihan' + /> + + + {/* Empty space to match Referensi height */} + {/* Empty space */} + + {/* Pemotongan Button - aligned with Attachment */} + + + + + + + {/* Pemotongan Items */} + {pemotonganItems.length > 0 && ( + + {pemotonganItems.map((item, index) => ( + + + + removePemotongan(item.id)} + sx={{ + backgroundColor: '#fff', + border: '1px solid #f44336', + '&:hover': { backgroundColor: '#ffebee' } + }} + > + + + + + + option.label || ''} + value={pemotonganOptions.find(option => option.value === item.dipotong) || null} + onChange={(_, value: { label: string; value: string } | null) => + updatePemotongan(item.id, 'dipotong', value?.value || '') + } + renderInput={(params: any) => } + noOptionsText='Tidak ada pilihan' + /> + + + + updatePemotongan(item.id, 'persentase', e.target.value)} + placeholder='0' + sx={{ + '& .MuiInputBase-root': { + textAlign: 'center' + } + }} + /> + + + + + + + + + + + + + {formatCurrency(calculatePemotongan(item))} + + + + + + ))} + + )} + + {/* Bottom Section */} + + + {/* Empty space */} + + + + Total + + + {formatCurrency(formData.totalDibayar)} + + + + + + + + + + ) +} + +export default PurchaseDetailSendPayment diff --git a/src/views/apps/purchase/purchase-detail/PurchaseDetailTransaction.tsx b/src/views/apps/purchase/purchase-detail/PurchaseDetailTransaction.tsx new file mode 100644 index 0000000..8be85b5 --- /dev/null +++ b/src/views/apps/purchase/purchase-detail/PurchaseDetailTransaction.tsx @@ -0,0 +1,150 @@ +'use client' + +import React from 'react' +import { + Card, + CardContent, + CardHeader, + Typography, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Link, + Box +} from '@mui/material' + +interface TransactionData { + id: string + tanggal: string + transaksi: string + nomor: string + tag: string + referensi: string + jumlah: number +} + +const PurchaseDetailTransaction: React.FC = () => { + const transactions: TransactionData[] = [ + { + id: '1', + tanggal: '10/09/2025', + transaksi: 'Pembayaran Pembelian', + nomor: 'PP/00024', + tag: '', + referensi: '', + jumlah: 1593670 + } + ] + + const formatCurrency = (amount: number): string => { + return new Intl.NumberFormat('id-ID').format(amount) + } + + return ( + + + Transaksi + + } + sx={{ pb: 1 }} + /> + + + + + + + + + Tanggal + + + + + + + + Transaksi + + + + + + + + Nomor + + + + + + + Tag + + + + + Referensi + + + + + + Jumlah + + + + + + + + {transactions.map(transaction => ( + + + {transaction.tanggal} + + + + {transaction.transaksi} + + + + {transaction.nomor} + + + {transaction.tag || '-'} + + + {transaction.referensi || '-'} + + + + {formatCurrency(transaction.jumlah)} + + + + ))} + +
+
+
+
+ ) +} + +export default PurchaseDetailTransaction