Files
lti-web-client/src/components/pages/expense/ExpenseRealizationContent.tsx
T

328 lines
12 KiB
TypeScript

import { useFormik } from 'formik';
import toast from 'react-hot-toast';
import Link from 'next/link';
import { Icon } from '@iconify/react';
import Button from '@/components/Button';
import Card from '@/components/Card';
import DropFileInput from '@/components/input/DropFileInput';
import { Expense } from '@/types/api/expense';
import { formatCurrency, formatDate } from '@/lib/helper';
import {
UploadRequestDocumentsFormSchema,
UploadRequestDocumentsFormValues,
} from '@/components/pages/expense/form/ExpenseRequestForm.schema';
import { ExpenseApi } from '@/services/api/expense';
import { isResponseSuccess } from '@/lib/api-helper';
import { ACCEPTED_FILE_TYPE } from '@/config/constant';
interface ExpenseRealizationContentProps {
initialValues?: Expense;
}
const ExpenseRealizationContent = ({
initialValues,
}: ExpenseRealizationContentProps) => {
const formik = useFormik<UploadRequestDocumentsFormValues>({
initialValues: {
documents: [],
},
validationSchema: UploadRequestDocumentsFormSchema,
onSubmit: async (values) => {
const addRealizationDocumentsRes =
await ExpenseApi.uploadRealizationDocuments(
initialValues?.id as number,
values.documents
);
if (isResponseSuccess(addRealizationDocumentsRes)) {
toast.success(addRealizationDocumentsRes.message);
window.location.reload();
} else {
toast.error(String(addRealizationDocumentsRes?.message));
}
},
});
const realizationDocumentsChangeHandler = (val: File[]) => {
formik.setFieldTouched('documents', true);
formik.setFieldValue('documents', val);
};
const realizationDocumentsDeleteHandler = (deletedFileIdx: number) => {
const newRealizationDocuments = formik.values.documents;
newRealizationDocuments?.splice(deletedFileIdx, 1);
formik.setFieldValue('documents', newRealizationDocuments);
};
return (
<div>
<div className='w-full max-w-5xl mx-auto flex flex-col sm:flex-row justify-end gap-2'>
<div className='w-full sm:w-fit sm:ml-2 flex flex-row gap-2 items-center'>
{/* TODO: apply RBAC */}
<Button
type='button'
color='warning'
href={`/expense/realization/edit/?expenseId=${initialValues?.id}`}
className='px-4 grow sm:grow-0'
>
<Icon icon='mdi:pencil-outline' width={24} height={24} />
Edit Realisasi
</Button>
</div>
</div>
<div className='overflow-x-auto w-full max-w-5xl mx-auto'>
<table className='table table-sm table-zebra'>
<tbody>
<tr>
<th>Tanggal Realisasi</th>
<th>:</th>
<td>
{initialValues?.realization_date
? formatDate(initialValues?.realization_date, 'DD MMMM YYYY')
: '-'}
</td>
</tr>
<tr>
<th>Dokumen Realisasi</th>
<th>:</th>
<td>
<div>
{!initialValues?.realization_docs ||
(initialValues?.realization_docs &&
initialValues?.realization_docs.length === 0 &&
'-')}
{initialValues?.realization_docs &&
initialValues?.realization_docs.length > 0 && (
<ul className='list-disc'>
{initialValues?.realization_docs.map(
(realizationDocument, realizationDocumentIdx) => (
<li key={realizationDocumentIdx}>
<Link
href={realizationDocument.path}
target='_blank'
rel='noopener noreferrer'
className='text-blue-500 underline'
>
{realizationDocument.path}{' '}
<Icon
icon='cuida:open-in-new-tab-outline'
width={12}
height={12}
className='inline'
/>
</Link>
</li>
)
)}
</ul>
)}
</div>
<div className='flex flex-col gap-2'>
<DropFileInput
name='documents'
values={formik.values.documents}
onChange={realizationDocumentsChangeHandler}
onDelete={realizationDocumentsDeleteHandler}
accept={{
...ACCEPTED_FILE_TYPE.PDF,
...ACCEPTED_FILE_TYPE.IMAGE,
}}
maxFiles={10}
className={{
wrapper: 'mt-2',
inputWrapper: 'flex items-center',
}}
/>
{formik.values.documents &&
formik.values.documents.length > 0 && (
<Button
onClick={formik.submitForm}
disabled={formik.isSubmitting}
isLoading={formik.isSubmitting}
className='w-fit self-end'
>
<Icon icon='ic:round-plus' width={24} height={24} />
Tambah
</Button>
)}
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div className='w-full max-w-5xl mt-8 mx-auto'>
<div className='flex flex-row gap-4'>
<Card variant='bordered' size='sm' className={{ wrapper: 'grow' }}>
<div className='w-full flex flex-col gap-2'>
<h3 className='text-sm'>Nominal Pengajuan</h3>
<span className='text-xl'>
{formatCurrency(initialValues?.total_pengajuan as number)}
</span>
<span className='text-sm'>
Terbayar{' '}
{formatCurrency(initialValues?.total_realisasi as number)}
</span>
</div>
</Card>
<Card variant='bordered' size='sm' className={{ wrapper: 'grow' }}>
<div className='w-full flex flex-col gap-2'>
<h3 className='text-sm'>Nominal Realisasi</h3>
<span className='text-xl'>
{formatCurrency(initialValues?.total_realisasi as number)}
</span>
<span className='text-sm'>
Selisih{' '}
{formatCurrency(
(initialValues?.total_realisasi as number) -
(initialValues?.total_pengajuan as number)
)}
</span>
</div>
</Card>
</div>
</div>
<div className='w-full max-w-5xl mt-8 mx-auto'>
<h2 className='font-bold text-xl text-center'>
Rincian Pengajuan Biaya Operasional
</h2>
<div className='w-full mt-2 flex flex-col gap-4'>
{initialValues?.kandangs.map((kandangExpense, kandangExpenseIdx) => {
let expenseGrandTotal = 0;
kandangExpense.pengajuans?.forEach(
(item) => (expenseGrandTotal += item.price)
);
return (
<div
key={kandangExpenseIdx}
className='overflow-x-auto w-full mx-auto'
>
<table className='table table-sm table-zebra'>
<thead>
<tr>
<th
colSpan={5}
className='font-bold text-center text-base-content text-lg'
>
Biaya {kandangExpense.name}
</th>
</tr>
<tr>
<th>Nonstock</th>
<th>Total Kuantitas</th>
<th>Total Biaya</th>
<th>Catatan</th>
</tr>
</thead>
<tbody>
{kandangExpense.pengajuans?.map(
(pengajuanItem, pengajuanIdx) => (
<tr key={pengajuanIdx}>
<td>{pengajuanItem.nonstock.name}</td>
<td>{pengajuanItem.qty}</td>
<td>{formatCurrency(pengajuanItem.price)}</td>
<td className='w-xs'>{pengajuanItem.note ?? '-'}</td>
</tr>
)
)}
</tbody>
<tfoot>
<tr className='border-y'>
<th colSpan={2} className='text-right'>
Total Biaya Keseluruhan:
</th>
<th colSpan={2}>{formatCurrency(expenseGrandTotal)}</th>
</tr>
</tfoot>
</table>
</div>
);
})}
</div>
</div>
<div className='w-full max-w-5xl mt-8 mx-auto'>
<h2 className='font-bold text-xl text-center'>
Rincian Realisasi Biaya Operasional
</h2>
<div className='w-full mt-2 flex flex-col gap-4'>
{initialValues?.kandangs.map((kandangExpense, kandangExpenseIdx) => {
let expenseGrandTotal = 0;
kandangExpense.realisasi?.forEach(
(item) => (expenseGrandTotal += item.price)
);
return (
<div
key={kandangExpenseIdx}
className='overflow-x-auto w-full mx-auto'
>
<table className='table table-sm table-zebra'>
<thead>
<tr>
<th
colSpan={5}
className='font-bold text-center text-base-content text-lg'
>
Biaya {kandangExpense.name}
</th>
</tr>
<tr>
<th>Nonstock</th>
<th>Total Kuantitas</th>
<th>Total Biaya</th>
<th>Catatan</th>
</tr>
</thead>
<tbody>
{kandangExpense.realisasi?.map(
(realisasiItem, realisasiIdx) => (
<tr key={realisasiIdx}>
<td>{realisasiItem.nonstock.name}</td>
<td>{realisasiItem.qty}</td>
<td>{formatCurrency(realisasiItem.price)}</td>
<td className='w-xs'>{realisasiItem.note ?? '-'}</td>
</tr>
)
)}
</tbody>
<tfoot>
<tr className='border-y'>
<th colSpan={2} className='text-right'>
Total Biaya Keseluruhan:
</th>
<th colSpan={2}>{formatCurrency(expenseGrandTotal)}</th>
</tr>
</tfoot>
</table>
</div>
);
})}
</div>
</div>
</div>
);
};
export default ExpenseRealizationContent;