From b33e7a19190a779d24085ff942b6f6acb65fce4a Mon Sep 17 00:00:00 2001 From: randy-ar Date: Thu, 20 Nov 2025 00:57:07 +0700 Subject: [PATCH] feat(FE-181-179-220): Slicing UI, Client Side Validation and API Integration for Delivery Order --- .../detail/edit/delivery/page.tsx | 52 +++ .../sales-orders/detail/edit/layout.tsx | 11 + .../sales-orders/detail/edit/page.tsx | 17 +- src/components/input/DateInput.tsx | 5 +- .../pages/marketing/MarketingTable.tsx | 39 +- .../marketing/detail/MarketingDetail.tsx | 167 +++++++- .../marketing/form/MarketingForm.schema.ts | 18 + .../pages/marketing/form/MarketingForm.tsx | 386 +++++++++++++++--- .../DeliverOrderProduct.schema.ts | 60 +++ .../delivery-order/DeliverOrderProduct.tsx | 345 ++++++++++++++++ .../table-view/DeliveryOrderProductTable.tsx | 233 +++++++++++ .../table-view/SalesOrderProductTable.tsx | 54 ++- .../pages/production/chickin/ChickinTable.tsx | 1 - .../chickin/form/tabs/ChickinFormView.tsx | 5 +- .../chickin/ProjectFlockChickinDetail.tsx | 6 +- .../form/ProjectFlockForm.schema.ts | 5 - .../project-flock/form/ProjectFlockForm.tsx | 42 +- .../form/ProjectFlockKandangTable.tsx | 16 + src/dummy/marketing.dummy.ts | 9 +- src/lib/helper.ts | 1 - src/services/api/marketing/marketing.ts | 9 +- src/services/api/production/project-flock.ts | 40 +- src/types/api/marketing/marketing.d.ts | 21 +- src/types/api/production/project-flock.d.ts | 7 +- 24 files changed, 1358 insertions(+), 191 deletions(-) create mode 100644 src/app/marketing/sales-orders/detail/edit/delivery/page.tsx create mode 100644 src/app/marketing/sales-orders/detail/edit/layout.tsx create mode 100644 src/components/pages/marketing/form/table-view/DeliveryOrderProductTable.tsx diff --git a/src/app/marketing/sales-orders/detail/edit/delivery/page.tsx b/src/app/marketing/sales-orders/detail/edit/delivery/page.tsx new file mode 100644 index 00000000..3a5f7a91 --- /dev/null +++ b/src/app/marketing/sales-orders/detail/edit/delivery/page.tsx @@ -0,0 +1,52 @@ +'use client'; + +import SalesForm from '@/components/pages/marketing/form/MarketingForm'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; +import { MarketingApi } from '@/services/api/marketing/marketing'; +import { useRouter, useSearchParams } from 'next/navigation'; +import useSWR from 'swr'; + +const EditMarketingDelivery = () => { + const router = useRouter(); + const searchParams = useSearchParams(); + + const soId = searchParams.get('salesOrderId'); + + const { + data: marketing, + isLoading: isLoading, + mutate: refreshMarketing, + } = useSWR(`get-so-${soId}`, () => + MarketingApi.getSingle(soId ? parseInt(soId) : 0) + ); + + if (!soId) { + router.back(); + + return ( +
+ +
+ ); + } + + if (!isLoading && (!marketing || isResponseError(marketing))) { + router.replace('/404'); + return; + } + return ( +
+ {isLoading && } + {!isLoading && isResponseSuccess(marketing) && ( + { + refreshMarketing(); + }} + /> + )} +
+ ); +}; +export default EditMarketingDelivery; diff --git a/src/app/marketing/sales-orders/detail/edit/layout.tsx b/src/app/marketing/sales-orders/detail/edit/layout.tsx new file mode 100644 index 00000000..7220dfa1 --- /dev/null +++ b/src/app/marketing/sales-orders/detail/edit/layout.tsx @@ -0,0 +1,11 @@ +import SuspenseHelper from '@/components/helper/SuspenseHelper'; + +const Layout = ({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) => { + return {children}; +}; + +export default Layout; diff --git a/src/app/marketing/sales-orders/detail/edit/page.tsx b/src/app/marketing/sales-orders/detail/edit/page.tsx index 2b41f144..d05f4d39 100644 --- a/src/app/marketing/sales-orders/detail/edit/page.tsx +++ b/src/app/marketing/sales-orders/detail/edit/page.tsx @@ -12,9 +12,12 @@ const EditSalesOrder = () => { const soId = searchParams.get('salesOrderId'); - const { data: marketing, isLoading: isLoading } = useSWR( - `get-so-${soId}`, - () => MarketingApi.getSingle(soId ? parseInt(soId) : 0) + const { + data: marketing, + isLoading: isLoading, + mutate: refreshMarketing, + } = useSWR(`get-so-${soId}`, () => + MarketingApi.getSingle(soId ? parseInt(soId) : 0) ); if (!soId) { @@ -35,7 +38,13 @@ const EditSalesOrder = () => {
{isLoading && } {!isLoading && isResponseSuccess(marketing) && ( - + { + refreshMarketing(); + }} + /> )}
); diff --git a/src/components/input/DateInput.tsx b/src/components/input/DateInput.tsx index a85c1f10..8c3dd35d 100644 --- a/src/components/input/DateInput.tsx +++ b/src/components/input/DateInput.tsx @@ -78,7 +78,10 @@ const DateInput = ({ // --- Sync value props --- useEffect(() => { - if (!value) return; + if (!value) { + setDisplayValue(''); + return; + } if (isRange && typeof value === 'object') { const from = value.from ? new Date(value.from) : undefined; const to = value.to ? new Date(value.to) : undefined; diff --git a/src/components/pages/marketing/MarketingTable.tsx b/src/components/pages/marketing/MarketingTable.tsx index ea8e369a..2bad4c97 100644 --- a/src/components/pages/marketing/MarketingTable.tsx +++ b/src/components/pages/marketing/MarketingTable.tsx @@ -56,15 +56,28 @@ const RowsOptionsMenu = ({ Detail - + {props.row.original.latest_approval.step_number != 1 && ( + + )} + {props.row.original.latest_approval.step_number != 3 && ( + + )} - + */} 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 = ({ Approve - + */} )} {initialValues?.latest_approval?.step_number == 2 && ( - )} - + @@ -186,13 +206,23 @@ const SalesOrderDetail = ({ + + + + +
:{initialValues?.name}{initialValues?.so_number}
Nama Pelanggan: {initialValues?.notes ?? '-'}
Dokumen: + +
{initialValues?.sales_order && ( )} + {initialValues?.delivery_order && ( + + {initialValues?.delivery_order.map((delivery, index) => { + return ( +
+ +
+
+ Nomor DO : {delivery.do_number} +
+
+ + 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', + }} + /> +
+
+ +
+
+ ); + })} +
+ )}
-
{JSON.stringify(initialValues)}
+ {/*
{JSON.stringify(initialValues)}
{JSON.stringify(formik.values)}
-
{JSON.stringify(formik.errors)}
+
{JSON.stringify(formik.errors)}
*/}
+ {formType == 'deliver' && + initialValues?.sales_order && + initialValues?.sales_order.length > 0 && ( + + {/* {JSON.stringify(memoSalesOrder)} + {JSON.stringify(memoDeliveryOrder)} */} + + + )} +