Purchase Detail
This commit is contained in:
parent
e1084277e0
commit
2d3274d3bf
@ -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
|
||||||
@ -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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -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 (
|
||||||
|
<>
|
||||||
|
{isLoading ? (
|
||||||
|
<Loading />
|
||||||
|
) : (
|
||||||
<Grid container spacing={6}>
|
<Grid container spacing={6}>
|
||||||
<Grid size={{ xs: 12 }}>
|
<Grid size={{ xs: 12 }}>
|
||||||
<PurchaseDetailInformation />
|
<PurchaseDetailInformation data={data} />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
{data?.status == 'sent' && (
|
||||||
<Grid size={{ xs: 12 }}>
|
<Grid size={{ xs: 12 }}>
|
||||||
<PurchaseDetailSendPayment />
|
<PurchaseDetailSendPayment />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 12 }}>
|
)}
|
||||||
|
{/* <Grid size={{ xs: 12 }}>
|
||||||
<PurchaseDetailTransaction />
|
<PurchaseDetailTransaction />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 12 }}>
|
<Grid size={{ xs: 12 }}>
|
||||||
<PurchaseDetailLog />
|
<PurchaseDetailLog />
|
||||||
|
</Grid> */}
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
)}
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 (
|
||||||
|
<TableRow key={item.id}>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Typography variant='body2' color='primary' sx={{ cursor: 'pointer' }}>
|
<Typography variant='body2' color='primary' sx={{ cursor: 'pointer' }}>
|
||||||
{product.produk}
|
{item.ingredient.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>{product.deskripsi}</TableCell>
|
<TableCell>{item.description}</TableCell>
|
||||||
<TableCell align='center'>{product.kuantitas}</TableCell>
|
<TableCell align='center'>{item.quantity}</TableCell>
|
||||||
<TableCell align='center'>{product.satuan}</TableCell>
|
<TableCell align='center'>{item.unit.name}</TableCell>
|
||||||
<TableCell align='center'>{product.discount}</TableCell>
|
<TableCell align='right'>{formatCurrency(item.amount)}</TableCell>
|
||||||
<TableCell align='right'>{formatCurrency(product.harga)}</TableCell>
|
<TableCell align='right'>{formatCurrency(item.amount * item.quantity)}</TableCell>
|
||||||
<TableCell align='center'>{product.pajak}</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>
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user