feat(FE-181-179-220): Slicing UI, Client Side Validation and API Integration for Delivery Order

This commit is contained in:
randy-ar
2025-11-20 00:57:07 +07:00
parent 429f2b9109
commit b33e7a1919
24 changed files with 1358 additions and 191 deletions
@@ -11,13 +11,25 @@ import ApprovalSteps, {
} from '@/components/pages/ApprovalSteps';
import Table from '@/components/Table';
import { MARKETING_APPROVAL_LINE } from '@/config/approval-line';
import { cn, formatCurrency, formatDate, formatNumber } from '@/lib/helper';
import {
cn,
formatCurrency,
formatDate,
formatNumber,
formatVechicleNumber,
} from '@/lib/helper';
import {
MarketingApi,
SalesOrderApi,
} from '@/services/api/marketing/marketing';
import { BaseSalesOrder, Marketing } from '@/types/api/marketing/marketing';
import {
BaseDelivery,
BaseDeliveryOrder,
BaseSalesOrder,
Marketing,
} from '@/types/api/marketing/marketing';
import { Icon } from '@iconify/react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import toast from 'react-hot-toast';
@@ -28,6 +40,7 @@ const SalesOrderDetail = ({
initialValues?: Marketing;
refresh?: () => void;
}) => {
const router = useRouter();
const [approvalAction, setApprovalAction] = useState<'APPROVED' | 'REJECTED'>(
'APPROVED'
);
@@ -57,10 +70,10 @@ const SalesOrderDetail = ({
confirmationModal.openModal();
};
const rejectClickHandler = () => {
setApprovalAction('REJECTED');
confirmationModal.openModal();
};
// const rejectClickHandler = () => {
// setApprovalAction('REJECTED');
// confirmationModal.openModal();
// };
const deliveryClickHandler = () => {
deliveryModal.openModal();
@@ -104,6 +117,9 @@ const SalesOrderDetail = ({
toast.success(res?.message as string);
refresh?.();
refreshApproval?.();
router.push(
`/marketing/sales-orders/detail/edit/delivery?salesOrderId=${initialValues?.id}`
);
};
return (
@@ -127,26 +143,30 @@ const SalesOrderDetail = ({
<Icon icon='mdi:check' width={24} height={24} />
Approve
</Button>
<Button
{/* <Button
color='error'
onClick={rejectClickHandler}
disabled={initialValues?.latest_approval?.step_number != 2}
>
<Icon icon='mdi:close' width={24} height={24} />
Reject
</Button>
</Button> */}
</>
)}
{initialValues?.latest_approval?.step_number == 2 && (
<Button color='success' onClick={deliveryClickHandler}>
<Icon icon='mdi:check' width={24} height={24} />
<Button
color='success'
// href={`/marketing/sales-orders/detail/edit/delivery?salesOrderId=${initialValues?.id}`}
onClick={deliveryClickHandler}
>
<Icon icon='mdi:truck' width={24} height={24} />
Delivery Order
</Button>
)}
</div>
<Card
title='Informasi Sales Order'
title='Informasi Penjualan'
className={{
wrapper: 'w-full bg-white',
}}
@@ -159,7 +179,7 @@ const SalesOrderDetail = ({
No. Sales Order
</td>
<td>:</td>
<td width='50%'>{initialValues?.name}</td>
<td width='50%'>{initialValues?.so_number}</td>
</tr>
<tr>
<td className='font-semibold'>Nama Pelanggan</td>
@@ -186,13 +206,23 @@ const SalesOrderDetail = ({
<td>:</td>
<td>{initialValues?.notes ?? '-'}</td>
</tr>
<tr>
<td className='font-semibold'>Dokumen</td>
<td>:</td>
<td>
<Button className='py-2 px-3 font-medium text-md'>
<Icon icon='mdi:file-pdf' width={16} height={16} />
{initialValues?.so_number}
</Button>
</td>
</tr>
</tbody>
</table>
</div>
</Card>
{initialValues?.sales_order && (
<Card
title='Daftar Produk'
title='Informasi Produk'
className={{
wrapper: 'w-full bg-white',
}}
@@ -262,6 +292,117 @@ const SalesOrderDetail = ({
/>
</Card>
)}
{initialValues?.delivery_order && (
<Card
title='Informasi Pengiriman'
className={{
wrapper: 'w-full bg-white',
}}
>
{initialValues?.delivery_order.map((delivery, index) => {
return (
<div key={index}>
<Card
className={{
wrapper: 'w-full',
}}
>
<div className='flex flex-row gap-3'>
<div className='font-semibold'>
Nomor DO : {delivery.do_number}
</div>
</div>
<Table<BaseDelivery>
data={delivery.deliveries}
columns={[
{
header: 'Tanggal Pengiriman',
accessorFn() {
return formatDate(
delivery.delivery_date,
'DD MMM yyyy'
);
},
},
{
header: 'No. Polisi',
accessorFn(row) {
return formatVechicleNumber(row.vehicle_number);
},
},
{
header: 'Kandang',
accessorFn(row) {
return row.product_warehouse.warehouse.name;
},
},
{
header: 'Produk',
accessorFn(row) {
return row.product_warehouse.product.name;
},
},
{
header: 'Harga Satuan (Rp)',
accessorFn(row) {
return formatCurrency(row.unit_price);
},
},
{
header: 'Total Bobot (Kg)',
accessorFn(row) {
return formatNumber(row.total_weight);
},
},
{
header: 'Kuantitas',
accessorFn(row) {
return formatNumber(row.qty);
},
},
{
header: 'Avg. Bobot (Kg)',
accessorFn(row) {
return formatNumber(row.avg_weight);
},
},
{
header: 'Total Penjualan (Rp)',
accessorFn(row) {
return formatCurrency(row.total_price);
},
},
]}
className={{
containerClassName: cn({
'mb-20':
initialValues?.sales_order &&
initialValues?.sales_order?.length === 0,
}),
tableWrapperClassName: 'overflow-x-auto min-h-full!',
tableClassName:
'font-inter w-full table-auto min-h-full!',
headerRowClassName: 'border-b border-b-gray-200',
headerColumnClassName:
'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end',
bodyRowClassName: 'border-b border-b-gray-200',
bodyColumnClassName:
'px-6 py-3 last:flex last:flex-row last:justify-end',
paginationClassName: 'hidden',
}}
/>
</Card>
<div className='flex flex-row gap-3 my-3'>
<Button className='py-2 px-3 font-medium text-md'>
<Icon icon='mdi:file-pdf' width={16} height={16} />
{delivery.do_number}
</Button>
</div>
</div>
);
})}
</Card>
)}
<div className='flex flex-row gap-3'>
<Button
color='warning'