update purchase order
This commit is contained in:
@@ -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 | HTMLElement>(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<HTMLElement>) => {
|
||||
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 (
|
||||
<Card sx={{ width: '100%' }}>
|
||||
<CardHeader
|
||||
title={
|
||||
<Box display='flex' justifyContent='space-between' alignItems='center'>
|
||||
<Typography variant='h5' color={getStatusColor(purchaseOrder?.status ?? '')} sx={{ fontWeight: 'bold' }}>
|
||||
{getStatusLabel(purchaseOrder?.status ?? '')}
|
||||
</Typography>
|
||||
<Box>
|
||||
<Button startIcon={<i className='tabler-share' />} variant='outlined' size='small' sx={{ mr: 1 }}>
|
||||
Bagikan
|
||||
</Button>
|
||||
<Button startIcon={<i className='tabler-printer' />} variant='outlined' size='small' sx={{ mr: 1 }}>
|
||||
Print
|
||||
</Button>
|
||||
<IconButton>
|
||||
<i className='tabler-dots-vertical' />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
|
||||
<CardContent>
|
||||
{/* Purchase Information */}
|
||||
<Grid container spacing={3} sx={{ mb: 4 }}>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography variant='subtitle2' color='text.secondary'>
|
||||
Vendor
|
||||
</Typography>
|
||||
<Typography variant='body1' color='primary' sx={{ fontWeight: 'medium', cursor: 'pointer' }}>
|
||||
{purchaseOrder?.vendor?.name ?? ''}
|
||||
<>
|
||||
<Card sx={{ width: '100%' }}>
|
||||
<CardHeader
|
||||
title={
|
||||
<Box display='flex' justifyContent='space-between' alignItems='center'>
|
||||
<Typography variant='h5' color={getStatusColor(purchaseOrder?.status ?? '')} sx={{ fontWeight: 'bold' }}>
|
||||
{getStatusLabel(purchaseOrder?.status ?? '')}
|
||||
</Typography>
|
||||
<Box>
|
||||
<Button startIcon={<i className='tabler-share' />} variant='outlined' size='small' sx={{ mr: 1 }}>
|
||||
Bagikan
|
||||
</Button>
|
||||
<Button startIcon={<i className='tabler-printer' />} variant='outlined' size='small' sx={{ mr: 1 }}>
|
||||
Print
|
||||
</Button>
|
||||
<IconButton onClick={handleMenuClick}>
|
||||
<i className='tabler-dots-vertical' />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
|
||||
<Box>
|
||||
<Typography variant='subtitle2' color='text.secondary'>
|
||||
Tgl. Transaksi
|
||||
</Typography>
|
||||
<Typography variant='body1'>{formatDate(purchaseOrder?.transaction_date ?? '')}</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography variant='subtitle2' color='text.secondary'>
|
||||
Nomor
|
||||
</Typography>
|
||||
<Typography variant='body1'>{purchaseOrder?.po_number}</Typography>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Typography variant='subtitle2' color='text.secondary'>
|
||||
Tgl. Jatuh Tempo
|
||||
</Typography>
|
||||
<Typography variant='body1'>{formatDate(purchaseOrder?.due_date ?? '')}</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
{/* Products Table */}
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Produk</TableCell>
|
||||
<TableCell>Deskripsi</TableCell>
|
||||
<TableCell align='center'>Kuantitas</TableCell>
|
||||
<TableCell align='center'>Satuan</TableCell>
|
||||
<TableCell align='right'>Harga</TableCell>
|
||||
<TableCell align='right'>Jumlah</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{(purchaseOrder?.items ?? []).map((item, index) => {
|
||||
return (
|
||||
<TableRow key={item.id}>
|
||||
<TableCell>
|
||||
<Typography variant='body2' color='primary' sx={{ cursor: 'pointer' }}>
|
||||
{item.ingredient.name}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>{item.description}</TableCell>
|
||||
<TableCell align='center'>{item.quantity}</TableCell>
|
||||
<TableCell align='center'>{item.unit.name}</TableCell>
|
||||
<TableCell align='right'>{formatCurrency(item.amount)}</TableCell>
|
||||
<TableCell align='right'>{formatCurrency(item.amount * item.quantity)}</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
})}
|
||||
|
||||
{/* Total Quantity Row */}
|
||||
<TableRow>
|
||||
<TableCell colSpan={2} sx={{ fontWeight: 'bold', borderTop: '2px solid #e0e0e0' }}>
|
||||
Total Kuantitas
|
||||
</TableCell>
|
||||
<TableCell align='center' sx={{ fontWeight: 'bold', borderTop: '2px solid #e0e0e0' }}>
|
||||
{totalQuantity}
|
||||
</TableCell>
|
||||
<TableCell sx={{ borderTop: '2px solid #e0e0e0' }}></TableCell>
|
||||
<TableCell sx={{ borderTop: '2px solid #e0e0e0' }}></TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
{/* Summary Section */}
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid size={{ xs: 12, md: 6 }}>{/* Empty space for left side */}</Grid>
|
||||
<CardContent>
|
||||
{/* Purchase Information */}
|
||||
<Grid container spacing={3} sx={{ mb: 4 }}>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<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' }}>
|
||||
Total
|
||||
</Typography>
|
||||
<Typography variant='h6' sx={{ fontWeight: 'bold' }}>
|
||||
{formatCurrency(total)}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography variant='subtitle2' color='text.secondary'>
|
||||
Vendor
|
||||
</Typography>
|
||||
<Typography variant='body1' color='primary' sx={{ fontWeight: 'medium', cursor: 'pointer' }}>
|
||||
{purchaseOrder?.vendor?.name ?? ''}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Typography variant='subtitle2' color='text.secondary'>
|
||||
Tgl. Transaksi
|
||||
</Typography>
|
||||
<Typography variant='body1'>{formatDate(purchaseOrder?.transaction_date ?? '')}</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography variant='subtitle2' color='text.secondary'>
|
||||
Nomor
|
||||
</Typography>
|
||||
<Typography variant='body1'>{purchaseOrder?.po_number}</Typography>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Typography variant='subtitle2' color='text.secondary'>
|
||||
Tgl. Jatuh Tempo
|
||||
</Typography>
|
||||
<Typography variant='body1'>{formatDate(purchaseOrder?.due_date ?? '')}</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Products Table */}
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Produk</TableCell>
|
||||
<TableCell>Deskripsi</TableCell>
|
||||
<TableCell align='center'>Kuantitas</TableCell>
|
||||
<TableCell align='center'>Satuan</TableCell>
|
||||
<TableCell align='right'>Harga</TableCell>
|
||||
<TableCell align='right'>Jumlah</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{(purchaseOrder?.items ?? []).map((item, index) => {
|
||||
return (
|
||||
<TableRow key={item.id}>
|
||||
<TableCell>
|
||||
<Typography variant='body2' color='primary' sx={{ cursor: 'pointer' }}>
|
||||
{item.ingredient.name}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>{item.description}</TableCell>
|
||||
<TableCell align='center'>{item.quantity}</TableCell>
|
||||
<TableCell align='center'>{item.unit.name}</TableCell>
|
||||
<TableCell align='right'>{formatCurrency(item.amount)}</TableCell>
|
||||
<TableCell align='right'>{formatCurrency(item.amount * item.quantity)}</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
})}
|
||||
|
||||
{/* Total Quantity Row */}
|
||||
<TableRow>
|
||||
<TableCell colSpan={2} sx={{ fontWeight: 'bold', borderTop: '2px solid #e0e0e0' }}>
|
||||
Total Kuantitas
|
||||
</TableCell>
|
||||
<TableCell align='center' sx={{ fontWeight: 'bold', borderTop: '2px solid #e0e0e0' }}>
|
||||
{totalQuantity}
|
||||
</TableCell>
|
||||
<TableCell sx={{ borderTop: '2px solid #e0e0e0' }}></TableCell>
|
||||
<TableCell sx={{ borderTop: '2px solid #e0e0e0' }}></TableCell>
|
||||
<TableCell sx={{ borderTop: '2px solid #e0e0e0' }}></TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
{/* Summary Section */}
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid size={{ xs: 12, md: 6 }}>{/* Empty space for left side */}</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<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' }}>
|
||||
Total
|
||||
</Typography>
|
||||
<Typography variant='h6' sx={{ fontWeight: 'bold' }}>
|
||||
{formatCurrency(total)}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Menu */}
|
||||
<Menu
|
||||
anchorEl={anchorEl}
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={handleMenuClose}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
minWidth: 160
|
||||
}
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
onClick={handleApproveClick}
|
||||
disabled={!canApproveOrReject}
|
||||
sx={{
|
||||
color: 'success.main',
|
||||
'&:hover': {
|
||||
backgroundColor: 'success.light',
|
||||
color: 'success.dark'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<i className='tabler-check' style={{ marginRight: 8 }} />
|
||||
Disetujui
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={handleRejectClick}
|
||||
disabled={!canApproveOrReject}
|
||||
sx={{
|
||||
color: 'error.main',
|
||||
'&:hover': {
|
||||
backgroundColor: 'error.light',
|
||||
color: 'error.dark'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<i className='tabler-x' style={{ marginRight: 8 }} />
|
||||
Ditolak
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
{/* Confirmation Dialog */}
|
||||
<Dialog open={confirmDialog.open} onClose={handleCancelAction} maxWidth='sm' fullWidth>
|
||||
<DialogTitle>{confirmDialog.title}</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>{confirmDialog.message}</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleCancelAction} color='inherit' disabled={isSubmitting}>
|
||||
Batal
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleConfirmAction}
|
||||
variant='contained'
|
||||
color={confirmDialog.type === 'approve' ? 'success' : 'error'}
|
||||
disabled={isSubmitting}
|
||||
startIcon={isSubmitting ? <CircularProgress size={16} /> : null}
|
||||
autoFocus
|
||||
>
|
||||
{isSubmitting ? 'Memproses...' : confirmDialog.type === 'approve' ? 'Setujui' : 'Tolak'}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user