Merge pull request 'Send Payment' (#8) from efril into main
Reviewed-on: #8
This commit is contained in:
commit
14efada9bb
@ -1,7 +1,7 @@
|
|||||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { api } from '../api'
|
import { api } from '../api'
|
||||||
import { PurchaseOrderRequest } from '@/types/services/purchaseOrder'
|
import { PurchaseOrderRequest, SendPaymentPurchaseOrderRequest } from '@/types/services/purchaseOrder'
|
||||||
|
|
||||||
export const usePurchaseOrdersMutation = () => {
|
export const usePurchaseOrdersMutation = () => {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
@ -20,5 +20,19 @@ export const usePurchaseOrdersMutation = () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return { createPurchaseOrder }
|
const sendPaymentPurchaseOrder = useMutation({
|
||||||
|
mutationFn: async ({ id, payload }: { id: string; payload: SendPaymentPurchaseOrderRequest }) => {
|
||||||
|
const response = await api.put(`/purchase-orders/${id}`, payload)
|
||||||
|
return response.data
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success('Purchase Order created successfully!')
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['purchase-orders'] })
|
||||||
|
},
|
||||||
|
onError: (error: any) => {
|
||||||
|
toast.error(error.response?.data?.errors?.[0]?.cause || 'Create failed')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return { createPurchaseOrder, sendPaymentPurchaseOrder }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -118,3 +118,9 @@ export interface PurchaseOrderFormItem {
|
|||||||
amount: number
|
amount: number
|
||||||
total: number // calculated field for UI
|
total: number // calculated field for UI
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SendPaymentPurchaseOrderRequest {
|
||||||
|
reference?: string
|
||||||
|
status?: 'received'
|
||||||
|
attachment_file_ids?: string[] // uuid.UUID[]
|
||||||
|
}
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import Loading from '@/components/layout/shared/Loading'
|
|||||||
const PurchaseDetailContent = () => {
|
const PurchaseDetailContent = () => {
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const { data, isLoading, error, isFetching } = usePurchaseOrderById(params.id as string)
|
const { data, isLoading, error, isFetching } = usePurchaseOrderById(params.id as string)
|
||||||
|
const total = (data?.items ?? []).reduce((sum, item) => sum + (item?.amount ?? 0) * item?.quantity, 0)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
@ -23,7 +24,7 @@ const PurchaseDetailContent = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
{data?.status == 'sent' && (
|
{data?.status == 'sent' && (
|
||||||
<Grid size={{ xs: 12 }}>
|
<Grid size={{ xs: 12 }}>
|
||||||
<PurchaseDetailSendPayment />
|
<PurchaseDetailSendPayment id={data?.id} totalAmount={total} purchaseOrderNumber={data?.po_number} />
|
||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
{/* <Grid size={{ xs: 12 }}>
|
{/* <Grid size={{ xs: 12 }}>
|
||||||
|
|||||||
@ -16,104 +16,130 @@ import {
|
|||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import Grid from '@mui/material/Grid2'
|
import Grid from '@mui/material/Grid2'
|
||||||
import CustomTextField from '@/@core/components/mui/TextField'
|
import CustomTextField from '@/@core/components/mui/TextField'
|
||||||
import CustomAutocomplete from '@/@core/components/mui/Autocomplete'
|
import ImageUpload from '@/components/ImageUpload'
|
||||||
|
import { useFilesMutation } from '@/services/mutations/files'
|
||||||
|
import { usePurchaseOrdersMutation } from '@/services/mutations/purchaseOrder'
|
||||||
|
|
||||||
|
// API Interface
|
||||||
|
export interface SendPaymentPurchaseOrderRequest {
|
||||||
|
reference?: string
|
||||||
|
status?: 'received'
|
||||||
|
attachment_file_ids?: string[] // uuid.UUID[]
|
||||||
|
}
|
||||||
|
|
||||||
interface PaymentFormData {
|
interface PaymentFormData {
|
||||||
totalDibayar: string
|
totalDibayar: string
|
||||||
tglTransaksi: string
|
|
||||||
referensi: string
|
referensi: string
|
||||||
nomor: string
|
nomor: string
|
||||||
dibayarDari: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PemotonganItem {
|
interface PurchaseDetailSendPaymentProps {
|
||||||
id: string
|
id?: string
|
||||||
dipotong: string
|
totalAmount?: number
|
||||||
persentase: string
|
purchaseOrderNumber?: string
|
||||||
nominal: string
|
onSubmit?: (data: SendPaymentPurchaseOrderRequest) => Promise<void>
|
||||||
tipe: 'persen' | 'rupiah'
|
loading?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const PurchaseDetailSendPayment: React.FC = () => {
|
const PurchaseDetailSendPayment: React.FC<PurchaseDetailSendPaymentProps> = ({
|
||||||
|
id,
|
||||||
|
totalAmount = 849000,
|
||||||
|
purchaseOrderNumber = 'PP/00025',
|
||||||
|
onSubmit,
|
||||||
|
loading = false
|
||||||
|
}) => {
|
||||||
const [formData, setFormData] = useState<PaymentFormData>({
|
const [formData, setFormData] = useState<PaymentFormData>({
|
||||||
totalDibayar: '849.000',
|
totalDibayar: totalAmount.toString(),
|
||||||
tglTransaksi: '10/09/2025',
|
|
||||||
referensi: '',
|
referensi: '',
|
||||||
nomor: 'PP/00025',
|
nomor: purchaseOrderNumber
|
||||||
dibayarDari: '1-10001 Kas'
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const [expanded, setExpanded] = useState<boolean>(false)
|
const [expanded, setExpanded] = useState<boolean>(false)
|
||||||
const [pemotonganItems, setPemotonganItems] = useState<PemotonganItem[]>([])
|
const [uploadedFileIds, setUploadedFileIds] = useState<string[]>([])
|
||||||
|
const [imageUrl, setImageUrl] = useState<string>('')
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState<boolean>(false)
|
||||||
|
|
||||||
const dibayarDariOptions = [
|
const { mutate, isPending } = useFilesMutation().uploadFile
|
||||||
{ label: '1-10001 Kas', value: '1-10001 Kas' },
|
const { sendPaymentPurchaseOrder } = usePurchaseOrdersMutation()
|
||||||
{ 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 =
|
const handleChange =
|
||||||
(field: keyof PaymentFormData) => (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement> | any) => {
|
(field: keyof PaymentFormData) => (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||||
setFormData(prev => ({
|
setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
[field]: event.target.value
|
[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 = () => {
|
const handleAccordionChange = () => {
|
||||||
setExpanded(!expanded)
|
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 formatCurrency = (amount: string | number): string => {
|
||||||
const numAmount = typeof amount === 'string' ? parseInt(amount.replace(/\D/g, '')) : amount
|
const numAmount = typeof amount === 'string' ? parseInt(amount.replace(/\D/g, '')) : amount
|
||||||
return new Intl.NumberFormat('id-ID').format(numAmount)
|
return new Intl.NumberFormat('id-ID').format(numAmount)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const upsertAttachment = (attachments: string[], newId: string, index = 0) => {
|
||||||
|
if (attachments.length === 0) {
|
||||||
|
return [newId]
|
||||||
|
}
|
||||||
|
return attachments.map((id, i) => (i === index ? newId : id))
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUpload = async (file: File): Promise<string> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file)
|
||||||
|
formData.append('file_type', 'image')
|
||||||
|
formData.append('description', 'image purchase order payment')
|
||||||
|
|
||||||
|
mutate(formData, {
|
||||||
|
onSuccess: r => {
|
||||||
|
setUploadedFileIds(prev => upsertAttachment(prev, r.id))
|
||||||
|
setImageUrl(r.file_url)
|
||||||
|
resolve(r.id)
|
||||||
|
},
|
||||||
|
onError: er => {
|
||||||
|
reject(er)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
setIsSubmitting(true)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const requestData: SendPaymentPurchaseOrderRequest = {
|
||||||
|
reference: formData.referensi || undefined,
|
||||||
|
status: 'received',
|
||||||
|
attachment_file_ids: uploadedFileIds.length > 0 ? uploadedFileIds : undefined
|
||||||
|
}
|
||||||
|
sendPaymentPurchaseOrder.mutate(
|
||||||
|
{
|
||||||
|
id: id as string,
|
||||||
|
payload: requestData
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
// Reset form after successful submission
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
referensi: ''
|
||||||
|
}))
|
||||||
|
setUploadedFileIds([])
|
||||||
|
setExpanded(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error submitting payment:', error)
|
||||||
|
// Handle error (you might want to show a toast or error message)
|
||||||
|
} finally {
|
||||||
|
setIsSubmitting(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader
|
<CardHeader
|
||||||
@ -129,7 +155,7 @@ const PurchaseDetailSendPayment: React.FC = () => {
|
|||||||
<Grid container spacing={3}>
|
<Grid container spacing={3}>
|
||||||
{/* Left Column */}
|
{/* Left Column */}
|
||||||
<Grid size={{ xs: 12, md: 6 }}>
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
{/* Total Dibayar */}
|
{/* Total Dibayar - DISABLED */}
|
||||||
<Box sx={{ mb: 3 }}>
|
<Box sx={{ mb: 3 }}>
|
||||||
<CustomTextField
|
<CustomTextField
|
||||||
fullWidth
|
fullWidth
|
||||||
@ -140,6 +166,7 @@ const PurchaseDetailSendPayment: React.FC = () => {
|
|||||||
}
|
}
|
||||||
value={formData.totalDibayar}
|
value={formData.totalDibayar}
|
||||||
onChange={handleChange('totalDibayar')}
|
onChange={handleChange('totalDibayar')}
|
||||||
|
disabled
|
||||||
sx={{
|
sx={{
|
||||||
'& .MuiInputBase-root': {
|
'& .MuiInputBase-root': {
|
||||||
textAlign: 'right'
|
textAlign: 'right'
|
||||||
@ -148,26 +175,6 @@ const PurchaseDetailSendPayment: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</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 */}
|
{/* Referensi */}
|
||||||
<Box sx={{ mb: 3 }}>
|
<Box sx={{ mb: 3 }}>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
|
||||||
@ -185,6 +192,7 @@ const PurchaseDetailSendPayment: React.FC = () => {
|
|||||||
placeholder='Referensi'
|
placeholder='Referensi'
|
||||||
value={formData.referensi}
|
value={formData.referensi}
|
||||||
onChange={handleChange('referensi')}
|
onChange={handleChange('referensi')}
|
||||||
|
disabled={loading || isSubmitting}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@ -192,6 +200,7 @@ const PurchaseDetailSendPayment: React.FC = () => {
|
|||||||
<Accordion
|
<Accordion
|
||||||
expanded={expanded}
|
expanded={expanded}
|
||||||
onChange={handleAccordionChange}
|
onChange={handleAccordionChange}
|
||||||
|
disabled={loading || isSubmitting}
|
||||||
sx={{
|
sx={{
|
||||||
boxShadow: 'none',
|
boxShadow: 'none',
|
||||||
border: '1px solid #e0e0e0',
|
border: '1px solid #e0e0e0',
|
||||||
@ -219,20 +228,24 @@ const PurchaseDetailSendPayment: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant='body2' sx={{ fontWeight: 'medium' }}>
|
<Typography variant='body2' sx={{ fontWeight: 'medium' }}>
|
||||||
Attachment
|
Attachment {uploadedFileIds.length > 0 && `(${uploadedFileIds.length})`}
|
||||||
</Typography>
|
</Typography>
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails sx={{ p: 2 }}>
|
<AccordionDetails sx={{ p: 2 }}>
|
||||||
<Typography variant='body2' color='text.secondary'>
|
<ImageUpload
|
||||||
Drag and drop files here or click to upload
|
onUpload={handleUpload}
|
||||||
</Typography>
|
maxFileSize={1 * 1024 * 1024} // 1MB
|
||||||
|
currentImageUrl={imageUrl}
|
||||||
|
dragDropText='Drop your image here'
|
||||||
|
browseButtonText='Choose Image'
|
||||||
|
/>
|
||||||
</AccordionDetails>
|
</AccordionDetails>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{/* Right Column */}
|
{/* Right Column */}
|
||||||
<Grid size={{ xs: 12, md: 6 }}>
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
{/* Nomor */}
|
{/* Nomor - DISABLED */}
|
||||||
<Box sx={{ mb: 3 }}>
|
<Box sx={{ mb: 3 }}>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
|
||||||
<Typography variant='body2' sx={{ color: 'text.secondary' }}>
|
<Typography variant='body2' sx={{ color: 'text.secondary' }}>
|
||||||
@ -246,140 +259,9 @@ const PurchaseDetailSendPayment: React.FC = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
<CustomTextField fullWidth value={formData.nomor} onChange={handleChange('nomor')} disabled />
|
<CustomTextField fullWidth value={formData.nomor} onChange={handleChange('nomor')} disabled />
|
||||||
</Box>
|
</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>
|
||||||
</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 */}
|
{/* Bottom Section */}
|
||||||
<Box sx={{ mt: 4, pt: 3, borderTop: '1px solid #e0e0e0' }}>
|
<Box sx={{ mt: 4, pt: 3, borderTop: '1px solid #e0e0e0' }}>
|
||||||
<Grid container spacing={2} alignItems='center'>
|
<Grid container spacing={2} alignItems='center'>
|
||||||
@ -398,13 +280,15 @@ const PurchaseDetailSendPayment: React.FC = () => {
|
|||||||
variant='contained'
|
variant='contained'
|
||||||
startIcon={<i className='tabler-plus' />}
|
startIcon={<i className='tabler-plus' />}
|
||||||
fullWidth
|
fullWidth
|
||||||
|
onClick={handleSubmit}
|
||||||
|
disabled={loading || isSubmitting}
|
||||||
sx={{
|
sx={{
|
||||||
py: 1.5,
|
py: 1.5,
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
fontWeight: 'medium'
|
fontWeight: 'medium'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Tambah Pembayaran
|
{isSubmitting ? 'Mengirim...' : 'Tambah Pembayaran'}
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user