579 lines
21 KiB
TypeScript
579 lines
21 KiB
TypeScript
'use client'
|
|
|
|
import React from 'react'
|
|
import { Button, Typography, Box, ToggleButton, ToggleButtonGroup, InputAdornment, IconButton } from '@mui/material'
|
|
import Grid from '@mui/material/Grid2'
|
|
import CustomTextField from '@/@core/components/mui/TextField'
|
|
import { PurchaseOrderFormData, TransactionCost } from '@/types/apps/purchaseOrderTypes'
|
|
import CustomAutocomplete from '@/@core/components/mui/Autocomplete'
|
|
import ImageUpload from '@/components/ImageUpload'
|
|
|
|
interface PurchaseSummaryProps {
|
|
formData: PurchaseOrderFormData
|
|
handleInputChange: (field: keyof PurchaseOrderFormData, value: any) => void
|
|
}
|
|
|
|
const PurchaseSummary: React.FC<PurchaseSummaryProps> = ({ formData, handleInputChange }) => {
|
|
// Initialize transaction costs if not exist
|
|
const transactionCosts = formData.transactionCosts || []
|
|
|
|
// Options for transaction cost types
|
|
const transactionCostOptions = [
|
|
{ label: 'Biaya Admin', value: 'admin' },
|
|
{ label: 'Pajak', value: 'pajak' },
|
|
{ label: 'Materai', value: 'materai' },
|
|
{ label: 'Lainnya', value: 'lainnya' }
|
|
]
|
|
|
|
// Add new transaction cost
|
|
const addTransactionCost = () => {
|
|
const newCost: TransactionCost = {
|
|
id: Date.now().toString(),
|
|
type: '',
|
|
name: '',
|
|
amount: ''
|
|
}
|
|
handleInputChange('transactionCosts', [...transactionCosts, newCost])
|
|
}
|
|
|
|
// Remove transaction cost
|
|
const removeTransactionCost = (id: string) => {
|
|
const filtered = transactionCosts.filter((cost: TransactionCost) => cost.id !== id)
|
|
handleInputChange('transactionCosts', filtered)
|
|
}
|
|
|
|
// Update transaction cost
|
|
const updateTransactionCost = (id: string, field: keyof TransactionCost, value: string) => {
|
|
const updated = transactionCosts.map((cost: TransactionCost) =>
|
|
cost.id === id ? { ...cost, [field]: value } : cost
|
|
)
|
|
handleInputChange('transactionCosts', updated)
|
|
}
|
|
|
|
// Calculate discount amount based on percentage or fixed amount
|
|
const calculateDiscount = () => {
|
|
if (!formData.discountValue) return 0
|
|
|
|
const subtotal = formData.subtotal || 0
|
|
if (formData.discountType === 'percentage') {
|
|
return (subtotal * parseFloat(formData.discountValue)) / 100
|
|
}
|
|
return parseFloat(formData.discountValue)
|
|
}
|
|
|
|
const discountAmount = calculateDiscount()
|
|
const shippingCost = parseFloat(formData.shippingCost || '0')
|
|
|
|
// Calculate total transaction costs
|
|
const totalTransactionCost = transactionCosts.reduce((sum: number, cost: TransactionCost) => {
|
|
return sum + parseFloat(cost.amount || '0')
|
|
}, 0)
|
|
|
|
const downPayment = parseFloat(formData.downPayment || '0')
|
|
|
|
// Calculate total (subtotal - discount + shipping + transaction costs)
|
|
const total = (formData.subtotal || 0) - discountAmount + shippingCost + totalTransactionCost
|
|
|
|
// Calculate remaining balance (total - down payment)
|
|
const remainingBalance = total - downPayment
|
|
|
|
const handleUpload = async (file: File): Promise<string> => {
|
|
// Simulate upload
|
|
return new Promise(resolve => {
|
|
setTimeout(() => {
|
|
resolve(URL.createObjectURL(file))
|
|
}, 1000)
|
|
})
|
|
}
|
|
|
|
return (
|
|
<Grid size={12} sx={{ mt: 4 }}>
|
|
<Grid container spacing={3}>
|
|
{/* Left Side - Pesan and Attachment */}
|
|
<Grid size={{ xs: 12, md: 7 }}>
|
|
{/* Pesan Section */}
|
|
<Box sx={{ mb: 3 }}>
|
|
<Button
|
|
variant='text'
|
|
color='inherit'
|
|
onClick={() => handleInputChange('showPesan', !formData.showPesan)}
|
|
sx={{
|
|
textTransform: 'none',
|
|
fontSize: '14px',
|
|
fontWeight: 500,
|
|
padding: '12px 16px',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'flex-start',
|
|
width: '100%',
|
|
backgroundColor: '#f5f5f5',
|
|
border: '1px solid #e0e0e0',
|
|
borderRadius: '4px',
|
|
color: 'text.primary',
|
|
'&:hover': {
|
|
backgroundColor: '#eeeeee'
|
|
}
|
|
}}
|
|
>
|
|
<Box component='span' sx={{ mr: 1 }}>
|
|
{formData.showPesan ? (
|
|
<i className='tabler-chevron-down w-4 h-4' />
|
|
) : (
|
|
<i className='tabler-chevron-right w-4 h-4' />
|
|
)}
|
|
</Box>
|
|
Pesan
|
|
</Button>
|
|
{formData.showPesan && (
|
|
<Box sx={{ mt: 2 }}>
|
|
<CustomTextField
|
|
fullWidth
|
|
multiline
|
|
rows={3}
|
|
placeholder='Tambahkan pesan...'
|
|
value={formData.pesan || ''}
|
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleInputChange('pesan', e.target.value)}
|
|
/>
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
|
|
{/* Attachment Section */}
|
|
<Box>
|
|
<Button
|
|
variant='text'
|
|
color='inherit'
|
|
onClick={() => handleInputChange('showAttachment', !formData.showAttachment)}
|
|
sx={{
|
|
textTransform: 'none',
|
|
fontSize: '14px',
|
|
fontWeight: 500,
|
|
padding: '12px 16px',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'flex-start',
|
|
width: '100%',
|
|
backgroundColor: '#f5f5f5',
|
|
border: '1px solid #e0e0e0',
|
|
borderRadius: '4px',
|
|
color: 'text.primary',
|
|
'&:hover': {
|
|
backgroundColor: '#eeeeee'
|
|
}
|
|
}}
|
|
>
|
|
<Box component='span' sx={{ mr: 1 }}>
|
|
{formData.showAttachment ? (
|
|
<i className='tabler-chevron-down w-4 h-4' />
|
|
) : (
|
|
<i className='tabler-chevron-right w-4 h-4' />
|
|
)}
|
|
</Box>
|
|
Attachment
|
|
</Button>
|
|
{formData.showAttachment && (
|
|
<ImageUpload
|
|
onUpload={handleUpload}
|
|
maxFileSize={1 * 1024 * 1024} // 1MB
|
|
showUrlOption={false}
|
|
dragDropText='Drop your image here'
|
|
browseButtonText='Choose Image'
|
|
/>
|
|
)}
|
|
</Box>
|
|
</Grid>
|
|
|
|
{/* Right Side - Totals */}
|
|
<Grid size={{ xs: 12, md: 5 }}>
|
|
<Box sx={{ backgroundColor: '#ffffff', p: 3, borderRadius: '8px' }}>
|
|
{/* Sub Total */}
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
py: 2,
|
|
borderBottom: '1px solid #e0e0e0',
|
|
'&:hover': {
|
|
backgroundColor: '#f8f8f8'
|
|
}
|
|
}}
|
|
>
|
|
<Typography variant='body1' color='text.secondary' sx={{ fontSize: '16px' }}>
|
|
Sub Total
|
|
</Typography>
|
|
<Typography variant='body1' fontWeight={600} sx={{ fontSize: '16px', textAlign: 'right' }}>
|
|
{new Intl.NumberFormat('id-ID', {
|
|
style: 'currency',
|
|
currency: 'IDR',
|
|
minimumFractionDigits: 0
|
|
}).format(formData.subtotal || 0)}
|
|
</Typography>
|
|
</Box>
|
|
|
|
{/* Additional Options */}
|
|
<Box>
|
|
{/* Tambah Diskon */}
|
|
<Box
|
|
sx={{
|
|
py: 2,
|
|
borderBottom: '1px solid #e0e0e0',
|
|
'&:hover': {
|
|
backgroundColor: '#f8f8f8'
|
|
}
|
|
}}
|
|
>
|
|
<Button
|
|
variant='text'
|
|
color='primary'
|
|
size='small'
|
|
sx={{ textTransform: 'none', fontSize: '14px', p: 0, textAlign: 'left' }}
|
|
onClick={() => handleInputChange('showTambahDiskon', !formData.showTambahDiskon)}
|
|
>
|
|
{formData.showTambahDiskon ? '- Sembunyikan Diskon' : '+ Tambahan Diskon'}
|
|
</Button>
|
|
|
|
{/* Show input form when showTambahDiskon is true */}
|
|
{formData.showTambahDiskon && (
|
|
<Box sx={{ mt: 2 }}>
|
|
<Box sx={{ display: 'flex', gap: 1, alignItems: 'center', mb: 1 }}>
|
|
<CustomTextField
|
|
size='small'
|
|
placeholder='0'
|
|
value={formData.discountValue || ''}
|
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
|
handleInputChange('discountValue', e.target.value)
|
|
}
|
|
sx={{ flex: 1 }}
|
|
InputProps={{
|
|
endAdornment:
|
|
formData.discountType === 'percentage' ? (
|
|
<InputAdornment position='end'>%</InputAdornment>
|
|
) : undefined
|
|
}}
|
|
/>
|
|
<ToggleButtonGroup
|
|
value={formData.discountType || 'percentage'}
|
|
exclusive
|
|
onChange={(_, newValue) => {
|
|
if (newValue) handleInputChange('discountType', newValue)
|
|
}}
|
|
size='small'
|
|
>
|
|
<ToggleButton value='percentage' sx={{ px: 2 }}>
|
|
%
|
|
</ToggleButton>
|
|
<ToggleButton value='fixed' sx={{ px: 2 }}>
|
|
Rp
|
|
</ToggleButton>
|
|
</ToggleButtonGroup>
|
|
</Box>
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
|
|
{/* Biaya Pengiriman */}
|
|
<Box
|
|
sx={{
|
|
py: 2,
|
|
borderBottom: '1px solid #e0e0e0',
|
|
'&:hover': {
|
|
backgroundColor: '#f8f8f8'
|
|
}
|
|
}}
|
|
>
|
|
<Button
|
|
variant='text'
|
|
color='primary'
|
|
size='small'
|
|
sx={{ textTransform: 'none', fontSize: '14px', p: 0, textAlign: 'left' }}
|
|
onClick={() => handleInputChange('showBiayaPengiriman', !formData.showBiayaPengiriman)}
|
|
>
|
|
{formData.showBiayaPengiriman ? '- Sembunyikan Biaya Pengiriman' : '+ Biaya pengiriman'}
|
|
</Button>
|
|
|
|
{/* Show input form when showBiayaPengiriman is true */}
|
|
{formData.showBiayaPengiriman && (
|
|
<Box sx={{ mt: 2, display: 'flex', alignItems: 'center', gap: 2 }}>
|
|
<Typography variant='body2' sx={{ minWidth: '140px' }}>
|
|
Biaya pengiriman
|
|
</Typography>
|
|
<CustomTextField
|
|
size='small'
|
|
placeholder='0'
|
|
value={formData.shippingCost || ''}
|
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
|
handleInputChange('shippingCost', e.target.value)
|
|
}
|
|
sx={{ flex: 1 }}
|
|
InputProps={{
|
|
startAdornment: <InputAdornment position='start'>Rp</InputAdornment>
|
|
}}
|
|
/>
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
|
|
{/* Biaya Transaksi - Multiple */}
|
|
<Box
|
|
sx={{
|
|
py: 2,
|
|
borderBottom: '1px solid #e0e0e0',
|
|
'&:hover': {
|
|
backgroundColor: '#f8f8f8'
|
|
}
|
|
}}
|
|
>
|
|
<Button
|
|
variant='text'
|
|
color='primary'
|
|
size='small'
|
|
sx={{ textTransform: 'none', fontSize: '14px', p: 0, textAlign: 'left' }}
|
|
onClick={() => {
|
|
if (!formData.showBiayaTransaksi) {
|
|
handleInputChange('showBiayaTransaksi', true)
|
|
if (transactionCosts.length === 0) {
|
|
addTransactionCost()
|
|
}
|
|
} else {
|
|
handleInputChange('showBiayaTransaksi', false)
|
|
}
|
|
}}
|
|
>
|
|
{formData.showBiayaTransaksi ? '- Sembunyikan Biaya Transaksi' : '+ Biaya Transaksi'}
|
|
</Button>
|
|
|
|
{/* Show multiple transaction cost inputs */}
|
|
{formData.showBiayaTransaksi && (
|
|
<Box sx={{ mt: 2 }}>
|
|
{transactionCosts.map((cost: TransactionCost, index: number) => (
|
|
<Box key={cost.id} sx={{ display: 'flex', gap: 1, alignItems: 'center', mb: 2 }}>
|
|
{/* Remove button */}
|
|
<IconButton
|
|
size='small'
|
|
onClick={() => removeTransactionCost(cost.id)}
|
|
sx={{
|
|
color: 'error.main',
|
|
border: '1px solid',
|
|
borderColor: 'error.main',
|
|
borderRadius: '50%',
|
|
width: 28,
|
|
height: 28,
|
|
'&:hover': {
|
|
backgroundColor: 'error.lighter'
|
|
}
|
|
}}
|
|
>
|
|
<i className='tabler-trash' />
|
|
</IconButton>
|
|
|
|
{/* Type AutoComplete */}
|
|
<CustomAutocomplete
|
|
size='small'
|
|
options={transactionCostOptions}
|
|
getOptionLabel={option => (typeof option === 'string' ? option : option.label)}
|
|
value={transactionCostOptions.find(option => option.value === cost.type) || null}
|
|
onChange={(_, newValue) => {
|
|
updateTransactionCost(cost.id, 'type', newValue ? newValue.value : '')
|
|
}}
|
|
renderInput={params => (
|
|
<CustomTextField {...params} size='small' placeholder='Pilih biaya transaksi...' />
|
|
)}
|
|
sx={{ minWidth: 180 }}
|
|
noOptionsText='Tidak ada pilihan'
|
|
/>
|
|
|
|
{/* Name input */}
|
|
<CustomTextField
|
|
size='small'
|
|
placeholder='Nama'
|
|
value={cost.name}
|
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
|
updateTransactionCost(cost.id, 'name', e.target.value)
|
|
}
|
|
sx={{ flex: 1 }}
|
|
/>
|
|
|
|
{/* Amount input */}
|
|
<CustomTextField
|
|
size='small'
|
|
placeholder='0'
|
|
value={cost.amount}
|
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
|
updateTransactionCost(cost.id, 'amount', e.target.value)
|
|
}
|
|
sx={{ width: 120 }}
|
|
InputProps={{
|
|
startAdornment: <InputAdornment position='start'>Rp</InputAdornment>
|
|
}}
|
|
/>
|
|
</Box>
|
|
))}
|
|
|
|
{/* Add more button */}
|
|
<Button
|
|
variant='text'
|
|
color='primary'
|
|
size='small'
|
|
onClick={addTransactionCost}
|
|
sx={{
|
|
textTransform: 'none',
|
|
fontSize: '13px',
|
|
mt: 1
|
|
}}
|
|
>
|
|
+ Tambah biaya transaksi lain
|
|
</Button>
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
</Box>
|
|
|
|
{/* Total */}
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
py: 2,
|
|
borderBottom: '1px solid #e0e0e0',
|
|
'&:hover': {
|
|
backgroundColor: '#f8f8f8'
|
|
}
|
|
}}
|
|
>
|
|
<Typography variant='h6' fontWeight={600} sx={{ fontSize: '18px' }}>
|
|
Total
|
|
</Typography>
|
|
<Typography variant='h6' fontWeight={600} sx={{ fontSize: '18px', textAlign: 'right' }}>
|
|
{new Intl.NumberFormat('id-ID', {
|
|
style: 'currency',
|
|
currency: 'IDR',
|
|
minimumFractionDigits: 0
|
|
}).format(total)}
|
|
</Typography>
|
|
</Box>
|
|
|
|
{/* Uang Muka */}
|
|
<Box
|
|
sx={{
|
|
py: 2,
|
|
borderBottom: '1px solid #e0e0e0',
|
|
'&:hover': {
|
|
backgroundColor: '#f8f8f8'
|
|
}
|
|
}}
|
|
>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
{/* Dropdown */}
|
|
<CustomAutocomplete
|
|
size='small'
|
|
options={[{ label: '1-10003 Gi...', value: '1-10003' }]}
|
|
getOptionLabel={option => (typeof option === 'string' ? option : option.label)}
|
|
value={{ label: '1-10003 Gi...', value: '1-10003' }}
|
|
onChange={(_, newValue) => {
|
|
// Handle change if needed
|
|
}}
|
|
renderInput={params => <CustomTextField {...params} size='small' />}
|
|
sx={{ minWidth: 120 }}
|
|
/>
|
|
|
|
{/* Amount input */}
|
|
<CustomTextField
|
|
size='small'
|
|
placeholder='0'
|
|
value={formData.downPayment || '0'}
|
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
|
handleInputChange('downPayment', e.target.value)
|
|
}
|
|
sx={{ width: '80px' }}
|
|
inputProps={{
|
|
style: { textAlign: 'center' }
|
|
}}
|
|
/>
|
|
|
|
{/* Percentage/Fixed toggle */}
|
|
<ToggleButtonGroup
|
|
value={formData.downPaymentType || 'fixed'}
|
|
exclusive
|
|
onChange={(_, newValue) => {
|
|
if (newValue) handleInputChange('downPaymentType', newValue)
|
|
}}
|
|
size='small'
|
|
>
|
|
<ToggleButton value='percentage' sx={{ px: 1.5 }}>
|
|
%
|
|
</ToggleButton>
|
|
<ToggleButton value='fixed' sx={{ px: 1.5 }}>
|
|
Rp
|
|
</ToggleButton>
|
|
</ToggleButtonGroup>
|
|
</Box>
|
|
|
|
{/* Right side text */}
|
|
<Typography
|
|
variant='body1'
|
|
sx={{
|
|
fontSize: '16px',
|
|
fontWeight: 400
|
|
}}
|
|
>
|
|
Uang muka {downPayment > 0 ? downPayment.toLocaleString('id-ID') : '0'}
|
|
</Typography>
|
|
</Box>
|
|
</Box>
|
|
|
|
{/* Sisa Tagihan */}
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
py: 2,
|
|
backgroundColor: '#f5f5f5',
|
|
borderRadius: '4px',
|
|
mb: 3,
|
|
'&:hover': {
|
|
backgroundColor: '#eeeeee'
|
|
}
|
|
}}
|
|
>
|
|
<Typography variant='body1' color='text.primary' sx={{ fontSize: '16px', fontWeight: 600 }}>
|
|
Sisa Tagihan
|
|
</Typography>
|
|
<Typography variant='body1' fontWeight={600} sx={{ fontSize: '16px', textAlign: 'right' }}>
|
|
{new Intl.NumberFormat('id-ID', {
|
|
style: 'currency',
|
|
currency: 'IDR',
|
|
minimumFractionDigits: 0
|
|
}).format(remainingBalance)}
|
|
</Typography>
|
|
</Box>
|
|
|
|
{/* Save Button */}
|
|
<Button
|
|
variant='contained'
|
|
color='primary'
|
|
fullWidth
|
|
sx={{
|
|
textTransform: 'none',
|
|
fontWeight: 600,
|
|
py: 1.5,
|
|
boxShadow: 'none',
|
|
'&:hover': {
|
|
boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
|
|
}
|
|
}}
|
|
>
|
|
Simpan
|
|
</Button>
|
|
</Box>
|
|
</Grid>
|
|
</Grid>
|
|
</Grid>
|
|
)
|
|
}
|
|
|
|
export default PurchaseSummary
|