pos-dashboard-v2/src/views/apps/purchase/purchase-detail/PurchaseDetailInformation.tsx

374 lines
12 KiB
TypeScript
Raw Normal View History

2025-09-13 03:50:57 +07:00
'use client'
2025-09-25 16:20:13 +07:00
import React, { useState } from 'react'
2025-09-10 03:23:54 +07:00
import {
Card,
CardHeader,
CardContent,
Typography,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Box,
Button,
2025-09-25 16:20:13 +07:00
IconButton,
Menu,
MenuItem,
Dialog,
DialogTitle,
DialogContent,
DialogContentText,
DialogActions,
CircularProgress
2025-09-10 03:23:54 +07:00
} from '@mui/material'
import Grid from '@mui/material/Grid2'
2025-09-25 16:20:13 +07:00
import { PurchaseOrder, SendPaymentPurchaseOrderRequest } from '@/types/services/purchaseOrder'
import { usePurchaseOrdersMutation } from '@/services/mutations/purchaseOrder'
2025-09-10 03:23:54 +07:00
2025-09-13 03:50:57 +07:00
interface Props {
data?: PurchaseOrder
2025-09-10 03:23:54 +07:00
}
2025-09-13 03:50:57 +07:00
const PurchaseDetailInformation = ({ data }: Props) => {
const purchaseOrder = data
2025-09-25 16:20:13 +07:00
// 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: ''
})
}
2025-09-13 03:50:57 +07:00
// Helper functions
const formatDate = (dateString: string): string => {
const date = new Date(dateString)
return date.toLocaleDateString('id-ID', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
})
}
2025-09-10 03:23:54 +07:00
2025-09-13 03:50:57 +07:00
const formatCurrency = (amount: number): string => {
return new Intl.NumberFormat('id-ID').format(amount)
2025-09-10 03:23:54 +07:00
}
2025-09-13 03:50:57 +07:00
const getStatusLabel = (status: string): string => {
const statusMap: Record<string, string> = {
draft: 'Draft',
sent: 'Dikirim',
approved: 'Disetujui',
received: 'Diterima',
2025-09-25 16:20:13 +07:00
cancelled: 'Dibatalkan',
rejected: 'Ditolak'
2025-09-10 03:23:54 +07:00
}
2025-09-13 03:50:57 +07:00
return statusMap[status] || status
}
2025-09-10 03:23:54 +07:00
2025-09-13 03:50:57 +07:00
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',
2025-09-25 16:20:13 +07:00
cancelled: 'error',
rejected: 'error'
2025-09-13 03:50:57 +07:00
}
return colorMap[status] || 'info'
2025-09-10 03:23:54 +07:00
}
2025-09-13 03:50:57 +07:00
// 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)
2025-09-25 16:20:13 +07:00
// Check if actions should be available (only for certain statuses)
const canApproveOrReject = purchaseOrder?.status === 'received'
2025-09-10 03:23:54 +07:00
return (
2025-09-25 16:20:13 +07:00
<>
<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 ?? '')}
2025-09-10 03:23:54 +07:00
</Typography>
2025-09-25 16:20:13 +07:00
<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>
2025-09-10 03:23:54 +07:00
</Box>
2025-09-25 16:20:13 +07:00
}
/>
2025-09-10 03:23:54 +07:00
2025-09-25 16:20:13 +07:00
<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 ?? ''}
</Typography>
</Box>
2025-09-10 03:23:54 +07:00
2025-09-25 16:20:13 +07:00
<Box>
<Typography variant='subtitle2' color='text.secondary'>
Tgl. Transaksi
</Typography>
<Typography variant='body1'>{formatDate(purchaseOrder?.transaction_date ?? '')}</Typography>
</Box>
</Grid>
2025-09-10 03:23:54 +07:00
<Grid size={{ xs: 12, md: 6 }}>
2025-09-25 16:20:13 +07:00
<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>
2025-09-10 03:23:54 +07:00
</Box>
</Grid>
</Grid>
2025-09-25 16:20:13 +07:00
{/* 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>
</>
2025-09-10 03:23:54 +07:00
)
}
export default PurchaseDetailInformation