Purchase Detail

This commit is contained in:
efrilm 2025-09-13 03:50:57 +07:00
parent e1084277e0
commit 2d3274d3bf
5 changed files with 128 additions and 184 deletions

View File

@ -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 PurchaseOrderDetailPage = () => {
return (
<Grid container spacing={6}>
<Grid size={{ xs: 12 }}>
<PurchaseDetailHeader title='Detail Pesanan Pembelian' />
</Grid>
<Grid size={{ xs: 12 }}>
<PurchaseDetailContent />
</Grid>
</Grid>
)
}
export default PurchaseOrderDetailPage

View File

@ -1,4 +1,4 @@
import { PurchaseOrders } from '@/types/services/purchaseOrder' import { PurchaseOrder, PurchaseOrders } from '@/types/services/purchaseOrder'
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { api } from '../api' import { api } from '../api'
@ -39,3 +39,13 @@ export function usePurchaseOrders(params: PurchaseOrderQueryParams = {}) {
} }
}) })
} }
export function usePurchaseOrderById(id: string) {
return useQuery<PurchaseOrder>({
queryKey: ['purchase-orders', id],
queryFn: async () => {
const res = await api.get(`/purchase-orders/${id}`)
return res.data.data
}
})
}

View File

@ -1,25 +1,40 @@
'use client'
import Grid from '@mui/material/Grid2' import Grid from '@mui/material/Grid2'
import PurchaseDetailInformation from './PurchaseDetailInformation' import PurchaseDetailInformation from './PurchaseDetailInformation'
import PurchaseDetailSendPayment from './PurchaseDetailSendPayment' import PurchaseDetailSendPayment from './PurchaseDetailSendPayment'
import PurchaseDetailLog from './PurchaseDetailLog' import PurchaseDetailLog from './PurchaseDetailLog'
import PurchaseDetailTransaction from './PurchaseDetailTransaction' import PurchaseDetailTransaction from './PurchaseDetailTransaction'
import { useParams } from 'next/navigation'
import { usePurchaseOrderById } from '@/services/queries/purchaseOrder'
import Loading from '@/components/layout/shared/Loading'
const PurchaseDetailContent = () => { const PurchaseDetailContent = () => {
const params = useParams()
const { data, isLoading, error, isFetching } = usePurchaseOrderById(params.id as string)
return ( return (
<Grid container spacing={6}> <>
<Grid size={{ xs: 12 }}> {isLoading ? (
<PurchaseDetailInformation /> <Loading />
</Grid> ) : (
<Grid size={{ xs: 12 }}> <Grid container spacing={6}>
<PurchaseDetailSendPayment /> <Grid size={{ xs: 12 }}>
</Grid> <PurchaseDetailInformation data={data} />
<Grid size={{ xs: 12 }}> </Grid>
<PurchaseDetailTransaction /> {data?.status == 'sent' && (
</Grid> <Grid size={{ xs: 12 }}>
<Grid size={{ xs: 12 }}> <PurchaseDetailSendPayment />
<PurchaseDetailLog /> </Grid>
</Grid> )}
</Grid> {/* <Grid size={{ xs: 12 }}>
<PurchaseDetailTransaction />
</Grid>
<Grid size={{ xs: 12 }}>
<PurchaseDetailLog />
</Grid> */}
</Grid>
)}
</>
) )
} }

View File

@ -1,3 +1,5 @@
'use client'
import React from 'react' import React from 'react'
import { import {
Card, Card,
@ -15,87 +17,62 @@ import {
IconButton IconButton
} from '@mui/material' } from '@mui/material'
import Grid from '@mui/material/Grid2' import Grid from '@mui/material/Grid2'
import { PurchaseOrder } from '@/types/services/purchaseOrder'
interface Product { interface Props {
produk: string data?: PurchaseOrder
deskripsi: string
kuantitas: number
satuan: string
discount: string
harga: number
pajak: string
jumlah: number
} }
interface PurchaseData { const PurchaseDetailInformation = ({ data }: Props) => {
vendor: string const purchaseOrder = data
nomor: string
tglTransaksi: string
tglJatuhTempo: string
gudang: string
status: string
}
const PurchaseDetailInformation: React.FC = () => { // Helper functions
const purchaseData: PurchaseData = { const formatDate = (dateString: string): string => {
vendor: 'Bagas Rizki Sihotang S.Farm Widodo', const date = new Date(dateString)
nomor: 'PI/00053', return date.toLocaleDateString('id-ID', {
tglTransaksi: '08/09/2025', day: '2-digit',
tglJatuhTempo: '06/10/2025', month: '2-digit',
gudang: 'Unassigned', year: 'numeric'
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 => { const formatCurrency = (amount: number): string => {
return new Intl.NumberFormat('id-ID').format(amount) return new Intl.NumberFormat('id-ID').format(amount)
} }
const getStatusLabel = (status: string): string => {
const statusMap: Record<string, string> = {
draft: 'Draft',
sent: 'Dikirim',
approved: 'Disetujui',
received: 'Diterima',
cancelled: 'Dibatalkan'
}
return statusMap[status] || status
}
const getStatusColor = (status: string): 'error' | 'success' | 'warning' | 'info' | 'default' => {
const colorMap: Record<string, 'error' | 'success' | 'warning' | 'info' | 'default'> = {
draft: 'default',
sent: 'warning',
approved: 'success',
received: 'info',
cancelled: 'error'
}
return colorMap[status] || 'info'
}
// Calculations
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)
return ( return (
<Card sx={{ width: '100%' }}> <Card sx={{ width: '100%' }}>
<CardHeader <CardHeader
title={ title={
<Box display='flex' justifyContent='space-between' alignItems='center'> <Box display='flex' justifyContent='space-between' alignItems='center'>
<Typography variant='h5' color='error' sx={{ fontWeight: 'bold' }}> <Typography variant='h5' color={getStatusColor(purchaseOrder?.status ?? '')} sx={{ fontWeight: 'bold' }}>
Belum Dibayar {getStatusLabel(purchaseOrder?.status ?? '')}
</Typography> </Typography>
<Box> <Box>
<Button startIcon={<i className='tabler-share' />} variant='outlined' size='small' sx={{ mr: 1 }}> <Button startIcon={<i className='tabler-share' />} variant='outlined' size='small' sx={{ mr: 1 }}>
@ -121,24 +98,15 @@ const PurchaseDetailInformation: React.FC = () => {
Vendor Vendor
</Typography> </Typography>
<Typography variant='body1' color='primary' sx={{ fontWeight: 'medium', cursor: 'pointer' }}> <Typography variant='body1' color='primary' sx={{ fontWeight: 'medium', cursor: 'pointer' }}>
{purchaseData.vendor} {purchaseOrder?.vendor?.name ?? ''}
</Typography> </Typography>
</Box> </Box>
<Box sx={{ mb: 2 }}>
<Typography variant='subtitle2' color='text.secondary'>
Tgl. Transaksi
</Typography>
<Typography variant='body1'>{purchaseData.tglTransaksi}</Typography>
</Box>
<Box> <Box>
<Typography variant='subtitle2' color='text.secondary'> <Typography variant='subtitle2' color='text.secondary'>
Gudang Tgl. Transaksi
</Typography>
<Typography variant='body1' color='primary' sx={{ cursor: 'pointer' }}>
{purchaseData.gudang}
</Typography> </Typography>
<Typography variant='body1'>{formatDate(purchaseOrder?.transaction_date ?? '')}</Typography>
</Box> </Box>
</Grid> </Grid>
@ -147,14 +115,14 @@ const PurchaseDetailInformation: React.FC = () => {
<Typography variant='subtitle2' color='text.secondary'> <Typography variant='subtitle2' color='text.secondary'>
Nomor Nomor
</Typography> </Typography>
<Typography variant='body1'>{purchaseData.nomor}</Typography> <Typography variant='body1'>{purchaseOrder?.po_number}</Typography>
</Box> </Box>
<Box> <Box>
<Typography variant='subtitle2' color='text.secondary'> <Typography variant='subtitle2' color='text.secondary'>
Tgl. Jatuh Tempo Tgl. Jatuh Tempo
</Typography> </Typography>
<Typography variant='body1'>{purchaseData.tglJatuhTempo}</Typography> <Typography variant='body1'>{formatDate(purchaseOrder?.due_date ?? '')}</Typography>
</Box> </Box>
</Grid> </Grid>
</Grid> </Grid>
@ -168,43 +136,38 @@ const PurchaseDetailInformation: React.FC = () => {
<TableCell>Deskripsi</TableCell> <TableCell>Deskripsi</TableCell>
<TableCell align='center'>Kuantitas</TableCell> <TableCell align='center'>Kuantitas</TableCell>
<TableCell align='center'>Satuan</TableCell> <TableCell align='center'>Satuan</TableCell>
<TableCell align='center'>Discount</TableCell>
<TableCell align='right'>Harga</TableCell> <TableCell align='right'>Harga</TableCell>
<TableCell align='center'>Pajak</TableCell>
<TableCell align='right'>Jumlah</TableCell> <TableCell align='right'>Jumlah</TableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{products.map((product, index) => ( {(purchaseOrder?.items ?? []).map((item, index) => {
<TableRow key={index}> return (
<TableCell> <TableRow key={item.id}>
<Typography variant='body2' color='primary' sx={{ cursor: 'pointer' }}> <TableCell>
{product.produk} <Typography variant='body2' color='primary' sx={{ cursor: 'pointer' }}>
</Typography> {item.ingredient.name}
</TableCell> </Typography>
<TableCell>{product.deskripsi}</TableCell> </TableCell>
<TableCell align='center'>{product.kuantitas}</TableCell> <TableCell>{item.description}</TableCell>
<TableCell align='center'>{product.satuan}</TableCell> <TableCell align='center'>{item.quantity}</TableCell>
<TableCell align='center'>{product.discount}</TableCell> <TableCell align='center'>{item.unit.name}</TableCell>
<TableCell align='right'>{formatCurrency(product.harga)}</TableCell> <TableCell align='right'>{formatCurrency(item.amount)}</TableCell>
<TableCell align='center'>{product.pajak}</TableCell> <TableCell align='right'>{formatCurrency(item.amount * item.quantity)}</TableCell>
<TableCell align='right'>{formatCurrency(product.jumlah)}</TableCell> </TableRow>
</TableRow> )
))} })}
{/* Total Kuantitas Row */} {/* Total Quantity Row */}
<TableRow> <TableRow>
<TableCell colSpan={2} sx={{ fontWeight: 'bold', borderTop: '2px solid #e0e0e0' }}> <TableCell colSpan={2} sx={{ fontWeight: 'bold', borderTop: '2px solid #e0e0e0' }}>
Total Kuantitas Total Kuantitas
</TableCell> </TableCell>
<TableCell align='center' sx={{ fontWeight: 'bold', borderTop: '2px solid #e0e0e0' }}> <TableCell align='center' sx={{ fontWeight: 'bold', borderTop: '2px solid #e0e0e0' }}>
{totalKuantitas} {totalQuantity}
</TableCell> </TableCell>
<TableCell sx={{ borderTop: '2px solid #e0e0e0' }}></TableCell> <TableCell sx={{ borderTop: '2px solid #e0e0e0' }}></TableCell>
<TableCell sx={{ borderTop: '2px solid #e0e0e0' }}></TableCell> <TableCell sx={{ borderTop: '2px solid #e0e0e0' }}></TableCell>
<TableCell sx={{ borderTop: '2px solid #e0e0e0' }}></TableCell>
<TableCell sx={{ borderTop: '2px solid #e0e0e0' }}></TableCell>
<TableCell sx={{ borderTop: '2px solid #e0e0e0' }}></TableCell>
</TableRow> </TableRow>
</TableBody> </TableBody>
</Table> </Table>
@ -222,82 +185,19 @@ const PurchaseDetailInformation: React.FC = () => {
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center', alignItems: 'center',
py: 2, py: 2,
borderBottom: '1px solid #e0e0e0',
'&:hover': { '&:hover': {
backgroundColor: 'rgba(0, 0, 0, 0.04)', backgroundColor: 'rgba(0, 0, 0, 0.04)',
transition: 'background-color 0.15s ease' transition: 'background-color 0.15s ease'
} }
}} }}
> >
<Typography variant='body1' sx={{ fontWeight: 'medium' }}> <Typography variant='h6' sx={{ fontWeight: 'bold' }}>
Sub Total
</Typography>
<Typography variant='body1' sx={{ fontWeight: 'medium' }}>
{formatCurrency(subTotal)}
</Typography>
</Box>
<Box
sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
py: 2,
borderBottom: '1px solid #e0e0e0',
'&:hover': {
backgroundColor: 'rgba(0, 0, 0, 0.04)',
transition: 'background-color 0.15s ease'
}
}}
>
<Typography variant='body1' sx={{ fontWeight: 'medium' }}>
PPN
</Typography>
<Typography variant='body1' sx={{ fontWeight: 'medium' }}>
{formatCurrency(ppn)}
</Typography>
</Box>
<Box
sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
py: 2,
borderBottom: '1px solid #e0e0e0',
'&:hover': {
backgroundColor: 'rgba(0, 0, 0, 0.04)',
transition: 'background-color 0.15s ease'
}
}}
>
<Typography variant='body1' sx={{ fontWeight: 'bold' }}>
Total Total
</Typography> </Typography>
<Typography variant='body1' sx={{ fontWeight: 'bold' }}> <Typography variant='h6' sx={{ fontWeight: 'bold' }}>
{formatCurrency(total)} {formatCurrency(total)}
</Typography> </Typography>
</Box> </Box>
<Box
sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
py: 2,
'&:hover': {
backgroundColor: 'rgba(0, 0, 0, 0.04)',
transition: 'background-color 0.15s ease'
}
}}
>
<Typography variant='h6' sx={{ fontWeight: 'bold' }}>
Sisa Tagihan
</Typography>
<Typography variant='h6' sx={{ fontWeight: 'bold' }}>
{formatCurrency(sisaTagihan)}
</Typography>
</Box>
</Box> </Box>
</Grid> </Grid>
</Grid> </Grid>

View File

@ -212,7 +212,8 @@ const PurchaseOrderListTable = () => {
variant='text' variant='text'
color='primary' color='primary'
className='p-0 min-w-0 font-medium normal-case justify-start' className='p-0 min-w-0 font-medium normal-case justify-start'
onClick={() => handlePOClick(row.original.id.toString())} component={Link}
href={getLocalizedUrl(`/apps/purchase/purchase-orders/${row.original.id}/detail`, locale as Locale)}
sx={{ sx={{
textTransform: 'none', textTransform: 'none',
fontWeight: 500, fontWeight: 500,