418 lines
14 KiB
TypeScript
418 lines
14 KiB
TypeScript
|
|
'use client'
|
||
|
|
|
||
|
|
import React, { useState } from 'react'
|
||
|
|
import {
|
||
|
|
Card,
|
||
|
|
CardContent,
|
||
|
|
Typography,
|
||
|
|
Button,
|
||
|
|
Box,
|
||
|
|
Accordion,
|
||
|
|
AccordionSummary,
|
||
|
|
AccordionDetails,
|
||
|
|
Tooltip,
|
||
|
|
IconButton,
|
||
|
|
CardHeader
|
||
|
|
} from '@mui/material'
|
||
|
|
import Grid from '@mui/material/Grid2'
|
||
|
|
import CustomTextField from '@/@core/components/mui/TextField'
|
||
|
|
import CustomAutocomplete from '@/@core/components/mui/Autocomplete'
|
||
|
|
|
||
|
|
interface PaymentFormData {
|
||
|
|
totalDibayar: string
|
||
|
|
tglTransaksi: string
|
||
|
|
referensi: string
|
||
|
|
nomor: string
|
||
|
|
dibayarDari: string
|
||
|
|
}
|
||
|
|
|
||
|
|
interface PemotonganItem {
|
||
|
|
id: string
|
||
|
|
dipotong: string
|
||
|
|
persentase: string
|
||
|
|
nominal: string
|
||
|
|
tipe: 'persen' | 'rupiah'
|
||
|
|
}
|
||
|
|
|
||
|
|
const PurchaseDetailSendPayment: React.FC = () => {
|
||
|
|
const [formData, setFormData] = useState<PaymentFormData>({
|
||
|
|
totalDibayar: '849.000',
|
||
|
|
tglTransaksi: '10/09/2025',
|
||
|
|
referensi: '',
|
||
|
|
nomor: 'PP/00025',
|
||
|
|
dibayarDari: '1-10001 Kas'
|
||
|
|
})
|
||
|
|
|
||
|
|
const [expanded, setExpanded] = useState<boolean>(false)
|
||
|
|
const [pemotonganItems, setPemotonganItems] = useState<PemotonganItem[]>([])
|
||
|
|
|
||
|
|
const dibayarDariOptions = [
|
||
|
|
{ label: '1-10001 Kas', value: '1-10001 Kas' },
|
||
|
|
{ label: '1-10002 Bank BCA', value: '1-10002 Bank BCA' },
|
||
|
|
{ label: '1-10003 Bank Mandiri', value: '1-10003 Bank Mandiri' },
|
||
|
|
{ label: '1-10004 Petty Cash', value: '1-10004 Petty Cash' }
|
||
|
|
]
|
||
|
|
|
||
|
|
const pemotonganOptions = [
|
||
|
|
{ label: 'PPN 11%', value: 'ppn' },
|
||
|
|
{ label: 'PPh 21', value: 'pph21' },
|
||
|
|
{ label: 'PPh 23', value: 'pph23' },
|
||
|
|
{ label: 'Biaya Admin', value: 'admin' }
|
||
|
|
]
|
||
|
|
|
||
|
|
const handleChange =
|
||
|
|
(field: keyof PaymentFormData) => (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement> | any) => {
|
||
|
|
setFormData(prev => ({
|
||
|
|
...prev,
|
||
|
|
[field]: event.target.value
|
||
|
|
}))
|
||
|
|
}
|
||
|
|
|
||
|
|
const handleDibayarDariChange = (value: { label: string; value: string } | null) => {
|
||
|
|
setFormData(prev => ({
|
||
|
|
...prev,
|
||
|
|
dibayarDari: value?.value || ''
|
||
|
|
}))
|
||
|
|
}
|
||
|
|
|
||
|
|
const addPemotongan = () => {
|
||
|
|
const newItem: PemotonganItem = {
|
||
|
|
id: Date.now().toString(),
|
||
|
|
dipotong: '',
|
||
|
|
persentase: '0',
|
||
|
|
nominal: '',
|
||
|
|
tipe: 'persen'
|
||
|
|
}
|
||
|
|
setPemotonganItems(prev => [...prev, newItem])
|
||
|
|
}
|
||
|
|
|
||
|
|
const removePemotongan = (id: string) => {
|
||
|
|
setPemotonganItems(prev => prev.filter(item => item.id !== id))
|
||
|
|
}
|
||
|
|
|
||
|
|
const updatePemotongan = (id: string, field: keyof PemotonganItem, value: string) => {
|
||
|
|
setPemotonganItems(prev => prev.map(item => (item.id === id ? { ...item, [field]: value } : item)))
|
||
|
|
}
|
||
|
|
|
||
|
|
const handleAccordionChange = () => {
|
||
|
|
setExpanded(!expanded)
|
||
|
|
}
|
||
|
|
|
||
|
|
const calculatePemotongan = (item: PemotonganItem): number => {
|
||
|
|
const totalDibayar = parseInt(formData.totalDibayar.replace(/\D/g, '')) || 0
|
||
|
|
const nilai = parseFloat(item.persentase) || 0
|
||
|
|
|
||
|
|
if (item.tipe === 'persen') {
|
||
|
|
return (totalDibayar * nilai) / 100
|
||
|
|
} else {
|
||
|
|
return nilai
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const formatCurrency = (amount: string | number): string => {
|
||
|
|
const numAmount = typeof amount === 'string' ? parseInt(amount.replace(/\D/g, '')) : amount
|
||
|
|
return new Intl.NumberFormat('id-ID').format(numAmount)
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Card>
|
||
|
|
<CardHeader
|
||
|
|
title={
|
||
|
|
<Box display='flex' justifyContent='space-between' alignItems='center'>
|
||
|
|
<Typography variant='h5' color='error' sx={{ fontWeight: 'bold' }}>
|
||
|
|
Kirim Pembayaran
|
||
|
|
</Typography>
|
||
|
|
</Box>
|
||
|
|
}
|
||
|
|
/>
|
||
|
|
<CardContent>
|
||
|
|
<Grid container spacing={3}>
|
||
|
|
{/* Left Column */}
|
||
|
|
<Grid size={{ xs: 12, md: 6 }}>
|
||
|
|
{/* Total Dibayar */}
|
||
|
|
<Box sx={{ mb: 3 }}>
|
||
|
|
<CustomTextField
|
||
|
|
fullWidth
|
||
|
|
label={
|
||
|
|
<span>
|
||
|
|
<span style={{ color: 'red' }}>*</span> Total Dibayar
|
||
|
|
</span>
|
||
|
|
}
|
||
|
|
value={formData.totalDibayar}
|
||
|
|
onChange={handleChange('totalDibayar')}
|
||
|
|
sx={{
|
||
|
|
'& .MuiInputBase-root': {
|
||
|
|
textAlign: 'right'
|
||
|
|
}
|
||
|
|
}}
|
||
|
|
/>
|
||
|
|
</Box>
|
||
|
|
|
||
|
|
{/* Tgl. Transaksi */}
|
||
|
|
<Box sx={{ mb: 3 }}>
|
||
|
|
<CustomTextField
|
||
|
|
fullWidth
|
||
|
|
label={
|
||
|
|
<span>
|
||
|
|
<span style={{ color: 'red' }}>*</span> Tgl. Transaksi
|
||
|
|
</span>
|
||
|
|
}
|
||
|
|
type='date'
|
||
|
|
value={formData.tglTransaksi.split('/').reverse().join('-')}
|
||
|
|
onChange={handleChange('tglTransaksi')}
|
||
|
|
slotProps={{
|
||
|
|
input: {
|
||
|
|
endAdornment: <i className='tabler-calendar' style={{ color: '#666' }} />
|
||
|
|
}
|
||
|
|
}}
|
||
|
|
/>
|
||
|
|
</Box>
|
||
|
|
|
||
|
|
{/* Referensi */}
|
||
|
|
<Box sx={{ mb: 3 }}>
|
||
|
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
|
||
|
|
<Typography variant='body2' sx={{ color: 'text.secondary' }}>
|
||
|
|
Referensi
|
||
|
|
</Typography>
|
||
|
|
<Tooltip title='Informasi referensi pembayaran'>
|
||
|
|
<IconButton size='small' sx={{ ml: 0.5 }}>
|
||
|
|
<i className='tabler-info-circle' style={{ fontSize: '16px', color: '#666' }} />
|
||
|
|
</IconButton>
|
||
|
|
</Tooltip>
|
||
|
|
</Box>
|
||
|
|
<CustomTextField
|
||
|
|
fullWidth
|
||
|
|
placeholder='Referensi'
|
||
|
|
value={formData.referensi}
|
||
|
|
onChange={handleChange('referensi')}
|
||
|
|
/>
|
||
|
|
</Box>
|
||
|
|
|
||
|
|
{/* Attachment Accordion */}
|
||
|
|
<Accordion
|
||
|
|
expanded={expanded}
|
||
|
|
onChange={handleAccordionChange}
|
||
|
|
sx={{
|
||
|
|
boxShadow: 'none',
|
||
|
|
border: '1px solid #e0e0e0',
|
||
|
|
borderRadius: '8px',
|
||
|
|
'&:before': { display: 'none' }
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<AccordionSummary
|
||
|
|
expandIcon={
|
||
|
|
<i
|
||
|
|
className='tabler-chevron-right'
|
||
|
|
style={{
|
||
|
|
transform: expanded ? 'rotate(90deg)' : 'rotate(0deg)',
|
||
|
|
transition: 'transform 0.2s'
|
||
|
|
}}
|
||
|
|
/>
|
||
|
|
}
|
||
|
|
sx={{
|
||
|
|
backgroundColor: '#f8f9fa',
|
||
|
|
borderRadius: '8px',
|
||
|
|
minHeight: '48px',
|
||
|
|
'& .MuiAccordionSummary-content': {
|
||
|
|
margin: '12px 0'
|
||
|
|
}
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<Typography variant='body2' sx={{ fontWeight: 'medium' }}>
|
||
|
|
Attachment
|
||
|
|
</Typography>
|
||
|
|
</AccordionSummary>
|
||
|
|
<AccordionDetails sx={{ p: 2 }}>
|
||
|
|
<Typography variant='body2' color='text.secondary'>
|
||
|
|
Drag and drop files here or click to upload
|
||
|
|
</Typography>
|
||
|
|
</AccordionDetails>
|
||
|
|
</Accordion>
|
||
|
|
</Grid>
|
||
|
|
|
||
|
|
{/* Right Column */}
|
||
|
|
<Grid size={{ xs: 12, md: 6 }}>
|
||
|
|
{/* Nomor */}
|
||
|
|
<Box sx={{ mb: 3 }}>
|
||
|
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
|
||
|
|
<Typography variant='body2' sx={{ color: 'text.secondary' }}>
|
||
|
|
Nomor
|
||
|
|
</Typography>
|
||
|
|
<Tooltip title='Nomor pembayaran otomatis'>
|
||
|
|
<IconButton size='small' sx={{ ml: 0.5 }}>
|
||
|
|
<i className='tabler-info-circle' style={{ fontSize: '16px', color: '#666' }} />
|
||
|
|
</IconButton>
|
||
|
|
</Tooltip>
|
||
|
|
</Box>
|
||
|
|
<CustomTextField fullWidth value={formData.nomor} onChange={handleChange('nomor')} disabled />
|
||
|
|
</Box>
|
||
|
|
|
||
|
|
{/* Dibayar Dari */}
|
||
|
|
<Box sx={{ mb: 3 }}>
|
||
|
|
<Typography variant='body2' sx={{ color: 'text.secondary', mb: 1 }}>
|
||
|
|
<span style={{ color: 'red' }}>*</span> Dibayar Dari
|
||
|
|
</Typography>
|
||
|
|
<CustomAutocomplete
|
||
|
|
fullWidth
|
||
|
|
options={dibayarDariOptions}
|
||
|
|
getOptionLabel={(option: { label: string; value: string }) => option.label || ''}
|
||
|
|
value={dibayarDariOptions.find(option => option.value === formData.dibayarDari) || null}
|
||
|
|
onChange={(_, value: { label: string; value: string } | null) => handleDibayarDariChange(value)}
|
||
|
|
renderInput={(params: any) => <CustomTextField {...params} placeholder='Pilih akun pembayaran' />}
|
||
|
|
noOptionsText='Tidak ada pilihan'
|
||
|
|
/>
|
||
|
|
</Box>
|
||
|
|
|
||
|
|
{/* Empty space to match Referensi height */}
|
||
|
|
<Box sx={{ mb: 3, height: '74px' }}>{/* Empty space */}</Box>
|
||
|
|
|
||
|
|
{/* Pemotongan Button - aligned with Attachment */}
|
||
|
|
<Box
|
||
|
|
sx={{
|
||
|
|
display: 'flex',
|
||
|
|
justifyContent: 'flex-end',
|
||
|
|
alignItems: 'flex-start',
|
||
|
|
minHeight: '48px'
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<Button
|
||
|
|
startIcon={<i className='tabler-plus' />}
|
||
|
|
variant='text'
|
||
|
|
color='primary'
|
||
|
|
sx={{ textTransform: 'none' }}
|
||
|
|
onClick={addPemotongan}
|
||
|
|
>
|
||
|
|
Pemotongan
|
||
|
|
</Button>
|
||
|
|
</Box>
|
||
|
|
</Grid>
|
||
|
|
</Grid>
|
||
|
|
|
||
|
|
{/* Pemotongan Items */}
|
||
|
|
{pemotonganItems.length > 0 && (
|
||
|
|
<Box sx={{ mt: 3 }}>
|
||
|
|
{pemotonganItems.map((item, index) => (
|
||
|
|
<Box
|
||
|
|
key={item.id}
|
||
|
|
sx={{
|
||
|
|
mb: 2,
|
||
|
|
p: 2,
|
||
|
|
border: '1px solid #e0e0e0',
|
||
|
|
borderRadius: 1,
|
||
|
|
position: 'relative'
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<Grid container spacing={2} alignItems='center'>
|
||
|
|
<Grid size={{ xs: 12, md: 1 }}>
|
||
|
|
<IconButton
|
||
|
|
color='error'
|
||
|
|
size='small'
|
||
|
|
onClick={() => removePemotongan(item.id)}
|
||
|
|
sx={{
|
||
|
|
backgroundColor: '#fff',
|
||
|
|
border: '1px solid #f44336',
|
||
|
|
'&:hover': { backgroundColor: '#ffebee' }
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<i className='tabler-minus' style={{ fontSize: '16px' }} />
|
||
|
|
</IconButton>
|
||
|
|
</Grid>
|
||
|
|
|
||
|
|
<Grid size={{ xs: 12, md: 4 }}>
|
||
|
|
<CustomAutocomplete
|
||
|
|
fullWidth
|
||
|
|
options={pemotonganOptions}
|
||
|
|
getOptionLabel={(option: { label: string; value: string }) => option.label || ''}
|
||
|
|
value={pemotonganOptions.find(option => option.value === item.dipotong) || null}
|
||
|
|
onChange={(_, value: { label: string; value: string } | null) =>
|
||
|
|
updatePemotongan(item.id, 'dipotong', value?.value || '')
|
||
|
|
}
|
||
|
|
renderInput={(params: any) => <CustomTextField {...params} placeholder='Pilih dipotong...' />}
|
||
|
|
noOptionsText='Tidak ada pilihan'
|
||
|
|
/>
|
||
|
|
</Grid>
|
||
|
|
|
||
|
|
<Grid size={{ xs: 12, md: 2 }}>
|
||
|
|
<CustomTextField
|
||
|
|
fullWidth
|
||
|
|
value={item.persentase}
|
||
|
|
onChange={e => updatePemotongan(item.id, 'persentase', e.target.value)}
|
||
|
|
placeholder='0'
|
||
|
|
sx={{
|
||
|
|
'& .MuiInputBase-root': {
|
||
|
|
textAlign: 'center'
|
||
|
|
}
|
||
|
|
}}
|
||
|
|
/>
|
||
|
|
</Grid>
|
||
|
|
|
||
|
|
<Grid size={{ xs: 12, md: 1 }}>
|
||
|
|
<Box sx={{ display: 'flex', gap: 0.5 }}>
|
||
|
|
<Button
|
||
|
|
variant={item.tipe === 'persen' ? 'contained' : 'outlined'}
|
||
|
|
size='small'
|
||
|
|
onClick={() => updatePemotongan(item.id, 'tipe', 'persen')}
|
||
|
|
sx={{ minWidth: '40px', px: 1 }}
|
||
|
|
>
|
||
|
|
%
|
||
|
|
</Button>
|
||
|
|
<Button
|
||
|
|
variant={item.tipe === 'rupiah' ? 'contained' : 'outlined'}
|
||
|
|
size='small'
|
||
|
|
onClick={() => updatePemotongan(item.id, 'tipe', 'rupiah')}
|
||
|
|
sx={{ minWidth: '40px', px: 1 }}
|
||
|
|
>
|
||
|
|
Rp
|
||
|
|
</Button>
|
||
|
|
</Box>
|
||
|
|
</Grid>
|
||
|
|
|
||
|
|
<Grid size={{ xs: 12, md: 4 }}>
|
||
|
|
<Box sx={{ display: 'flex', justifyContent: 'flex-end', alignItems: 'center' }}>
|
||
|
|
<Typography variant='h6' sx={{ fontWeight: 'bold' }}>
|
||
|
|
{formatCurrency(calculatePemotongan(item))}
|
||
|
|
</Typography>
|
||
|
|
</Box>
|
||
|
|
</Grid>
|
||
|
|
</Grid>
|
||
|
|
</Box>
|
||
|
|
))}
|
||
|
|
</Box>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* Bottom Section */}
|
||
|
|
<Box sx={{ mt: 4, pt: 3, borderTop: '1px solid #e0e0e0' }}>
|
||
|
|
<Grid container spacing={2} alignItems='center'>
|
||
|
|
<Grid size={{ xs: 12, md: 6 }}>{/* Empty space */}</Grid>
|
||
|
|
<Grid size={{ xs: 12, md: 6 }}>
|
||
|
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
|
||
|
|
<Typography variant='h6' sx={{ fontWeight: 'bold' }}>
|
||
|
|
Total
|
||
|
|
</Typography>
|
||
|
|
<Typography variant='h6' sx={{ fontWeight: 'bold' }}>
|
||
|
|
{formatCurrency(formData.totalDibayar)}
|
||
|
|
</Typography>
|
||
|
|
</Box>
|
||
|
|
|
||
|
|
<Button
|
||
|
|
variant='contained'
|
||
|
|
startIcon={<i className='tabler-plus' />}
|
||
|
|
fullWidth
|
||
|
|
sx={{
|
||
|
|
py: 1.5,
|
||
|
|
textTransform: 'none',
|
||
|
|
fontWeight: 'medium'
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
Tambah Pembayaran
|
||
|
|
</Button>
|
||
|
|
</Grid>
|
||
|
|
</Grid>
|
||
|
|
</Box>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
export default PurchaseDetailSendPayment
|