Export Excel Invoice & Imporve Invoice

This commit is contained in:
ivan-sim
2025-12-18 09:53:01 +07:00
parent 9976e8e438
commit e178682fcf
13 changed files with 622 additions and 80 deletions

View File

@@ -440,7 +440,7 @@ import { DatePicker, MobileDatePicker } from '@mui/x-date-pickers';
setIsLoading(false)
enqueueSnackbar(response.data.message, { variant: 'success' });
setTimeout(() => {
window.location.reload();
navigate('/invoice-payment');
}, 1000);
})
.catch(({ response }) => {
@@ -911,7 +911,11 @@ const handleGetClaimDetails = async () => {
// Hitung Grand Total
const grandTotal = dataLogs.reduce((sum, item) => sum + item.totalBilling, 0);
const grandTotal = dataLogs.reduce(
(sum, item) => sum + Number(item.totalBilling),
0
);
const [fileBukti, setFileBukti] = useState<any>([]);
const removeBuktiFiles = (filesState:any, index:any) => {
setFileBukti(
@@ -976,7 +980,7 @@ const handleGetClaimDetails = async () => {
}
}, [invoicePayment]);
const [payments, setPayments] = useState([{ id: Date.now(), amount: "", existingFiles: [], files: [], paymentNumber: 1 }]);
const [payments, setPayments] = useState([{ id: Date.now(), amount: "", existingFiles: [], files: [], noReference: "", paymentNumber: 1 }]);
const fileInputRefs = useRef({});
useEffect(() => {
@@ -986,6 +990,7 @@ const handleGetClaimDetails = async () => {
amount: fCurrency(parseFloat(payment.amount_paid)),
existingFiles: Array.isArray(payment.files) ? payment.files : [],
files: [],
noReference: payment.no_reference,
paymentNumber: payment.payment_number
}));
setPayments(mappedPayments);
@@ -995,7 +1000,7 @@ const handleGetClaimDetails = async () => {
const handleAddPayment = () => {
const newId = Date.now();
setPayments([...payments, { id: newId, amount: "", existingFiles: [], files: [], paymentNumber: payments.length + 1 }]);
setPayments([...payments, { id: newId, amount: "", existingFiles: [], files: [], noReference: "", paymentNumber: payments.length + 1 }]);
fileInputRefs.current[newId] = null;
};
@@ -1005,6 +1010,12 @@ const handleGetClaimDetails = async () => {
setPayments(updatedPayments);
};
const handleReferenceChange = (index, value) => {
const updatedPayments = [...payments];
updatedPayments[index].noReference = value;
setPayments(updatedPayments);
};
const handleFileChange = (index, event) => {
const newFiles = Array.from(event.target.files);
const updatedPayments = [...payments];
@@ -1029,9 +1040,14 @@ const handleGetClaimDetails = async () => {
};
const totalAmount = payments.reduce((sum, payment) => {
const num = parseFloat(payment.amount.replace(/[^\d]/g, "")) || 0;
const num =
parseFloat(
(payment.amount ?? '0').toString().replace(/[^\d]/g, '')
) || 0;
return sum + num;
}, 0);
}, 0);
return (
@@ -1066,7 +1082,7 @@ const handleGetClaimDetails = async () => {
<Stack direction="row" spacing={2} width="100%">
<Stack spacing={2} sx={{ width: '50%' }}>
<Typography variant="subtitle1">Nomor Invoice *</Typography>
<Typography variant="subtitle1">Nomor Invoice <span style={{color: 'red'}}>*</span></Typography>
<TextField
id="nomor_invoice"
variant="outlined"
@@ -1191,7 +1207,7 @@ const handleGetClaimDetails = async () => {
{/* Input Jumlah Bayar */}
<Stack direction="row" spacing={2} width="100%">
<Stack spacing={2} sx={{ width: "50%" }}>
<Stack spacing={2} sx={{ width: "35%" }}>
<Typography variant="subtitle1">Nominal Pembayaran</Typography>
<TextField
variant="outlined"
@@ -1202,7 +1218,7 @@ const handleGetClaimDetails = async () => {
</Stack>
{/* Input File Bukti */}
<Stack spacing={2} sx={{ width: "50%" }}>
<Stack spacing={2} sx={{ width: "35%" }}>
<Typography variant="subtitle1">Upload Bukti Pembayaran</Typography>
<Stack>
<Stack divider={<Divider orientation="horizontal" flexItem />} spacing={1}>
@@ -1262,6 +1278,15 @@ const handleGetClaimDetails = async () => {
/>
</Stack>
</Stack>
<Stack spacing={2} sx={{ width: "30%" }}>
<Typography variant="subtitle1">No. Referensi</Typography>
<TextField
variant="outlined"
placeholder="Nomor Referensi"
value={payment.noReference}
onChange={(e) => handleReferenceChange(index, e.target.value)}
/>
</Stack>
</Stack>
</Stack>
))}

View File

@@ -268,13 +268,13 @@ export default function Detail() {
{/* Input Jumlah Bayar */}
<Stack direction="row" spacing={2} width="100%">
<Stack spacing={2} sx={{ width: "50%" }}>
<Stack spacing={2} sx={{ width: "35%" }}>
<Typography variant='subtitle2' sx={style1} gutterBottom>Nominal Pembayaran</Typography>
<Typography variant='subtitle2' sx={style2} gutterBottom>{fCurrency(parseFloat(file.amount_paid))}</Typography>
</Stack>
{/* Input File Bukti */}
<Stack spacing={2} sx={{ width: "50%" }}>
<Stack spacing={2} sx={{ width: "35%" }}>
<Typography variant='subtitle2' sx={style1} gutterBottom>Bukti Pembayaran</Typography>
<Stack>
<Stack divider={<Divider orientation="horizontal" flexItem />} spacing={1}>
@@ -292,6 +292,10 @@ export default function Detail() {
</Stack>
</Stack>
</Stack>
<Stack spacing={2} sx={{ width: "30%" }}>
<Typography variant='subtitle2' sx={style1} gutterBottom>No. Reference</Typography>
<Typography variant='subtitle2' sx={style2} gutterBottom>{file.no_reference}</Typography>
</Stack>
</Stack>
</Stack>
))}

View File

@@ -146,35 +146,60 @@ import { ClearOutlined, LoopOutlined } from "@mui/icons-material";
var filter = Object.fromEntries([...searchParams.entries()]);
setIsLoading(true)
await axios
.get('/invoice-payment/export',{
params: {
search: searchText,
start_date: formattedDate ? formattedDate : null,
end_date:formattedDate1,
provider: dataProvider,
status: dataStatus,
order: order,
orderBy: orderBy,
page: perPage,
}
})
.then((res) => {
enqueueSnackbar('Data berhasil di Export', {
variant: 'success',
anchorOrigin: { horizontal: 'right', vertical: 'top' },
});
setIsLoading(false)
document.location.href = res.data.data.file_url;
})
.catch((err) =>
enqueueSnackbar('Data Gagal di Export', {
variant: 'error',
anchorOrigin: { horizontal: 'right', vertical: 'top' },
})
const res = await axios.get('/invoice-payment/export', {
params: {
// search: searchText,
start_date: formattedDate ? formattedDate : null,
end_date: formattedDate1,
provider: dataProvider,
status: dataStatus,
order: order,
orderBy: orderBy,
},
responseType: 'blob',
});
);
const url = window.URL.createObjectURL(new Blob([res.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'Report-Vale-Payment.xlsx');
document.body.appendChild(link);
link.click();
setIsLoading(false);
// await axios
// .get('/invoice-payment/export',{
// params: {
// search: searchText,
// start_date: formattedDate ? formattedDate : null,
// end_date:formattedDate1,
// provider: dataProvider,
// status: dataStatus,
// order: order,
// orderBy: orderBy,
// page: perPage,
// }
// })
// .then((res) => {
// enqueueSnackbar('Data berhasil di Export', {
// variant: 'success',
// anchorOrigin: { horizontal: 'right', vertical: 'top' },
// });
// setIsLoading(false)
// document.location.href = res.data.data.file_url;
// })
// .catch((err) =>
// enqueueSnackbar('Data Gagal di Export', {
// variant: 'error',
// anchorOrigin: { horizontal: 'right', vertical: 'top' },
// })
// );
};

View File

@@ -140,7 +140,9 @@
"txtAttention": "Attention",
"txtAttentionInfo": "There are pending orders that require approval.",
"txtDPPJ": "DPJP",
"txtSpecialist": "Specialist"
"txtSpecialist": "Specialist",
"txtWarningDPPJ": "Please input DPJP",
"txtWarningSpecialist": "Please select Specialist"
}

View File

@@ -140,5 +140,7 @@
"txtAttention": "Perhatian",
"txtAttentionInfo": "Terdapat pesanan pending mohon untuk segera di approve.",
"txtDPPJ": "DPJP",
"txtSpecialist": "Spesialis"
"txtSpecialist": "Spesialis",
"txtWarningDPPJ": "Mohon isi DPJP",
"txtWarningSpecialist": "Mohon pilih Spesialis"
}

View File

@@ -95,6 +95,18 @@ export default function DialogFinalLog({ member, getData, onClose, handleSubmitS
enqueueSnackbar(localeData.txtWarningDischargeDate, { variant: 'warning' });
return false;
}
//cek spesialis
if(!idSpecialities)
{
enqueueSnackbar(localeData.txtWarningSpecialist, { variant: 'warning' });
return false;
}
//cek dpjp
if(!inputDppj)
{
enqueueSnackbar(localeData.txtWarningDPPJ, { variant: 'warning' });
return false;
}
setSubmitLoading(true);
const formData = makeFormData({
request_logs_id: member.id,
@@ -142,7 +154,7 @@ export default function DialogFinalLog({ member, getData, onClose, handleSubmitS
}).catch((error) => {
console.error('Error fetching ICD options:', error);
});
}, []);
console.log(serviceOptions, member, 'test')
@@ -210,7 +222,7 @@ export default function DialogFinalLog({ member, getData, onClose, handleSubmitS
placeItems: 'center',
gap: 1,
placeContent: 'center',
}}
>
@@ -271,7 +283,7 @@ export default function DialogFinalLog({ member, getData, onClose, handleSubmitS
placeItems: 'center',
gap: 1,
placeContent: 'center',
}}
>
@@ -332,7 +344,7 @@ export default function DialogFinalLog({ member, getData, onClose, handleSubmitS
placeItems: 'center',
gap: 1,
placeContent: 'center',
}}
>
@@ -357,7 +369,7 @@ export default function DialogFinalLog({ member, getData, onClose, handleSubmitS
<Stack direction="row" spacing={2} sx={{ width: '100%' }}>
{/* Kolom Tanggal Discharge */}
<Stack spacing={2} sx={{ flex: 1 }}>
<Typography variant="subtitle1">{localeData.txtDischargeDate} *</Typography>
<Typography variant="subtitle1">{localeData.txtDischargeDate} <span style={{ color: 'red' }}>*</span></Typography>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DatePicker
label={localeData.txtDischargeDate}
@@ -373,7 +385,7 @@ export default function DialogFinalLog({ member, getData, onClose, handleSubmitS
{/* Kolom Service Type */}
<Stack spacing={2} sx={{ flex: 1 }}>
<Typography variant="subtitle1">{localeData.txtDialogMember1} *</Typography>
<Typography variant="subtitle1">{localeData.txtDialogMember1} <span style={{ color: 'red' }}>*</span></Typography>
<Autocomplete
id="service_type"
options={serviceOptions}
@@ -393,7 +405,7 @@ export default function DialogFinalLog({ member, getData, onClose, handleSubmitS
{/* Specialist */}
<Stack direction="row" spacing={2}>
<Stack spacing={2} sx={{ width: '100%' }}>
<Typography variant='subtitle1'>{localeData.txtSpecialist}</Typography>
<Typography variant='subtitle1'>{localeData.txtSpecialist} <span style={{ color: 'red' }}>*</span></Typography>
<Autocomplete
id='specialities'
options={specialisOptions}
@@ -412,7 +424,7 @@ export default function DialogFinalLog({ member, getData, onClose, handleSubmitS
<Stack direction="row" spacing={2}>
<Stack spacing={2} sx={{ width: '100%' }}>
<Typography variant='subtitle1'>{ localeData.txtDPPJ }</Typography>
<Typography variant='subtitle1'>{ localeData.txtDPPJ } <span style={{ color: 'red' }}>*</span></Typography>
<TextField
id='dppj'
variant='outlined'
@@ -426,7 +438,7 @@ export default function DialogFinalLog({ member, getData, onClose, handleSubmitS
</Stack>
</Stack>
<LoadingButton
variant="contained"

View File

@@ -72,6 +72,18 @@ export default function FormRequestClaim({ member, handleSubmitSuccess }: FormRe
enqueueSnackbar(localeData.txtDialogMember6, { variant: 'warning' });
return false;
}
//cek spesialis
if(!idSpecialities)
{
enqueueSnackbar(localeData.txtWarningSpecialist, { variant: 'warning' });
return false;
}
//cek dpjp
if(!inputDppj)
{
enqueueSnackbar(localeData.txtWarningDPPJ, { variant: 'warning' });
return false;
}
setSubmitLoading(true);
const formData = {
member_id: member.members.id,
@@ -145,7 +157,7 @@ export default function FormRequestClaim({ member, handleSubmitSuccess }: FormRe
<Stack direction="row" spacing={2}>
<Stack spacing={2} sx={{ width: '100%' }}>
<Typography variant='subtitle1'>{localeData.txtProvider} *</Typography>
<Typography variant='subtitle1'>{localeData.txtProvider} <span style={{ color: 'red' }}>*</span></Typography>
<Autocomplete
id="provider"
options={[{ name: localeData.txtAddNew, id: 0 }, ...member?.providers || []]}
@@ -172,7 +184,7 @@ export default function FormRequestClaim({ member, handleSubmitSuccess }: FormRe
/>
{showAddNewForm && (
<Stack direction="column" spacing={1} padding={1}>
<Typography variant='body2'>{localeData.txtAddNew} *</Typography>
<Typography variant='body2'>{localeData.txtAddNew} <span style={{ color: 'red' }}>*</span></Typography>
<TextField
required={true}
label={localeData.txtName}
@@ -204,7 +216,7 @@ export default function FormRequestClaim({ member, handleSubmitSuccess }: FormRe
<Stack direction="row" spacing={2}>
<Stack spacing={2} sx={{ width: '100%' }}>
<Typography variant='subtitle1'>{localeData.txtDialogMember1} *</Typography>
<Typography variant='subtitle1'>{localeData.txtDialogMember1} <span style={{ color: 'red' }}>*</span></Typography>
<Autocomplete
id="service_type"
options={member?.services || []}
@@ -228,7 +240,7 @@ export default function FormRequestClaim({ member, handleSubmitSuccess }: FormRe
{/* Specialist */}
<Stack direction="row" spacing={2}>
<Stack spacing={2} sx={{ width: '100%' }}>
<Typography variant='subtitle1'>{localeData.txtSpecialist}</Typography>
<Typography variant='subtitle1'>{localeData.txtSpecialist} <span style={{ color: 'red' }}>*</span></Typography>
<Autocomplete
id='specialities'
options={member?.specialities || []}
@@ -251,7 +263,7 @@ export default function FormRequestClaim({ member, handleSubmitSuccess }: FormRe
<Stack direction="row" spacing={2}>
<Stack spacing={2} sx={{ width: '100%' }}>
<Typography variant='subtitle1'>{ localeData.txtDPPJ }</Typography>
<Typography variant='subtitle1'>{ localeData.txtDPPJ } <span style={{ color: 'red' }}>*</span></Typography>
<TextField
id='dppj'
variant='outlined'
@@ -266,7 +278,7 @@ export default function FormRequestClaim({ member, handleSubmitSuccess }: FormRe
<Stack direction="row" spacing={2}>
<Stack spacing={2} sx={{ width: '100%' }}>
<Typography variant='subtitle1'>{localeData.txtDialogMember5} *</Typography>
<Typography variant='subtitle1'>{localeData.txtDialogMember5} <span style={{ color: 'red' }}>*</span></Typography>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DatePicker
label={localeData.txtDialogMember5}