'use client'; import AlertErrorList from '@/components/helper/form/FormErrors'; import Modal, { useModal } from '@/components/Modal'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; import { DeliveryOrderProductFormValues } from '@/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.schema'; import { isResponseSuccess, isResponseError } from '@/lib/api-helper'; import { formatCurrency, formatDate, formatTitleCase } from '@/lib/helper'; import { DeliveryOrderApi, MarketingApi, SalesOrderApi, } from '@/services/api/marketing/marketing'; import { useFormikErrorList } from '@/services/hooks/useFormikErrorList'; import { BaseApproval } from '@/types/api/api-general'; import { CreateDeliveryOrderPayload, Marketing, UpdateDeliveryOrderPayload, } from '@/types/api/marketing/marketing'; import { useFormik } from 'formik'; import { Icon } from '@iconify/react'; import { useRouter, useSearchParams } from 'next/navigation'; import { useState, useRef, useMemo, useCallback, useEffect } from 'react'; import toast from 'react-hot-toast'; import useSWR, { useSWRConfig } from 'swr'; import Button from '@/components/Button'; import { memo } from 'react'; import SalesOrderExport from '@/components/pages/marketing/pdf/SalesOrderExport'; import ApprovalStepsV2 from '@/components/helper/ApprovalStepsV2'; import { useApprovalSteps } from '@/components/pages/ApprovalSteps'; import { MARKETING_APPROVAL_LINE } from '@/config/approval-line'; import DeliveryOrderProductForm from '@/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct'; import DeliveryOrderProductTable from '@/components/pages/marketing/form/table-view/DeliveryOrderProductTable'; import { DeliveryOrderFormValues, DeliveryOrderSchema, getFilledMarketingFormInitialValues, SalesOrderFormValues, mergeSOwithDO, SalesProductToFieldValues, DeliveryProductToFieldValues, } from '@/components/pages/marketing/form/MarketingForm.schema'; import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes'; import RequirePermission from '@/components/helper/RequirePermission'; const MemoizedDeliveryOrderProductTable = memo(DeliveryOrderProductTable); const MemoizedDeliveryOrderProductForm = memo(DeliveryOrderProductForm); const DeliveryOrderFormModal = ({}: { initialValues?: Marketing }) => { const router = useRouter(); const searchParams = useSearchParams(); const modalAction = searchParams.get('action'); const marketingId = searchParams.get('id'); const [currentModalAction, setCurrentModalAction] = useState( modalAction ); const isModalActionForForm = modalAction === 'add_delivery' || modalAction === 'edit_delivery' || modalAction === 'detail'; const { mutate } = useSWRConfig(); const refreshMarketing = () => { mutate( (key) => typeof key === 'string' && key.includes(MarketingApi.basePath) ); }; const { data: marketing } = useSWR( isModalActionForForm && marketingId ? `detail-marketing-${marketingId}` : undefined, () => MarketingApi.getSingle(Number(marketingId)) ); const { rawDataApprovals, refresh: refreshApproval } = useApprovalSteps({ latestApproval: isResponseSuccess(marketing) ? marketing?.data.latest_approval : undefined, approvalLines: MARKETING_APPROVAL_LINE, moduleName: 'MARKETINGS', moduleId: marketingId as string, params: { group_step_number: false, order_by_date: 'ASC', }, }); /** * Step 1: View Details * Step 2: Edit Delivery */ const [step, setStep] = useState(1); const formModal = useModal(); const successModal = useModal(); const rejectModal = useModal(); const deleteModal = useModal(); const approveModal = useModal(); const formRef = useRef(null); const textareaRef = useRef(null); const [formErrorMessage, setFormErrorMessage] = useState(null); const [isLoading, setIsLoading] = useState(false); const [selectedDeliveryProduct, setSelectedDeliveryProduct] = useState(null); const [deliveryOrderValues, setDeliveryOrderValues] = useState< DeliveryOrderProductFormValues[] >([]); const getDeliveryOrderValues = useCallback( (marketingData: Marketing): DeliveryOrderProductFormValues[] => { const hasDeliveryOrder = marketingData.delivery_order && marketingData.delivery_order.length > 0 && marketingData.delivery_order.some( (doItem) => doItem.deliveries && doItem.deliveries.length > 0 ); if (hasDeliveryOrder) { return ( marketingData.delivery_order?.flatMap((delivery) => DeliveryProductToFieldValues(marketingData.sales_order, delivery) ) ?? [] ); } return mergeSOwithDO( marketingData.sales_order?.map(SalesProductToFieldValues) ?? [], marketingData.delivery_order?.flatMap((delivery) => DeliveryProductToFieldValues(marketingData.sales_order, delivery) ) ?? [], true ); }, [] ); // ================== SETUP FORMIK ================== const formikInitialValues = useMemo< SalesOrderFormValues & DeliveryOrderFormValues >(() => { if (!isResponseSuccess(marketing)) return {} as SalesOrderFormValues & DeliveryOrderFormValues; const deliveryValues = getDeliveryOrderValues(marketing.data); setDeliveryOrderValues(deliveryValues); return { so_date: marketing?.data?.so_date || undefined, notes: marketing?.data?.notes || undefined, customer_id: marketing?.data?.customer?.id || undefined, sales_person_id: marketing?.data?.sales_person?.id || undefined, sales_person: marketing?.data?.sales_person ? { value: marketing?.data.sales_person.id, label: marketing?.data.sales_person.name, } : null, customer: marketing?.data?.customer ? { value: marketing?.data.customer.id, label: marketing?.data.customer.name, } : null, sales_order: marketing?.data?.sales_order?.map((product) => SalesProductToFieldValues(product) ) ?? [], delivery_order: deliveryValues, }; }, [marketing, getDeliveryOrderValues]); const formik = useFormik({ enableReinitialize: true, initialValues: formikInitialValues, validationSchema: DeliveryOrderSchema, validateOnMount: true, onSubmit: async (values) => { const payload = { marketing_id: Number(marketingId), delivery_products: values.delivery_order .map((product) => { if (Boolean(product.delivery_date)) { return { marketing_product_id: product.marketing_product_id as number, unit_price: parseFloat(product.unit_price as string), total_weight: parseFloat(product.total_weight as string), qty: parseFloat(product.qty as string), avg_weight: parseFloat(product.avg_weight as string), total_price: parseFloat(product.total_price as string), delivery_date: formatDate( product.delivery_date as string, 'yyyy-MM-DD' ), vehicle_number: product.vehicle_number, weight_per_convertion: parseFloat( String(product.weight_per_convertion ?? 0) ), }; } }) .filter((item) => Boolean(item)), } as UpdateDeliveryOrderPayload; switch (modalAction) { case 'add_delivery': await createDeliveryHandler(payload as CreateDeliveryOrderPayload); break; case 'edit_delivery': await updateDeliveryHandler(payload as UpdateDeliveryOrderPayload); break; case 'detail': const dataMarketing = isResponseSuccess(marketing) ? marketing.data : null; if (!dataMarketing) break; if ( dataMarketing.delivery_order && dataMarketing.delivery_order.length > 0 ) { await updateDeliveryHandler(payload as UpdateDeliveryOrderPayload); break; } else { await createDeliveryHandler(payload as CreateDeliveryOrderPayload); } break; default: break; } }, }); // ===== Formik Error List ===== const { formErrorList, setFormErrorList, close, handleFormSubmit } = useFormikErrorList(formik); // ================== FORM HANDLER ================== const createDeliveryHandler = async (values: CreateDeliveryOrderPayload) => { setIsLoading(true); const createDeliveryRes = await DeliveryOrderApi.create(values); if (isResponseSuccess(createDeliveryRes)) { toast.success(createDeliveryRes?.message as string); setDeliveryOrderValues( createDeliveryRes.data?.delivery_order?.flatMap((delivery) => DeliveryProductToFieldValues( createDeliveryRes.data?.sales_order, delivery ) ) ?? [] ); closeModalHandler(false); successModal.openModal(); } if (isResponseError(createDeliveryRes)) { setFormErrorMessage(createDeliveryRes?.message as string); } setIsLoading(false); }; const updateDeliveryHandler = async (values: UpdateDeliveryOrderPayload) => { setIsLoading(true); const updateDeliveryRes = await DeliveryOrderApi.update( Number(marketingId), values ); if (isResponseSuccess(updateDeliveryRes)) { toast.success(updateDeliveryRes?.message as string); setDeliveryOrderValues( mergeSOwithDO( formik.values.sales_order, updateDeliveryRes.data?.delivery_order?.flatMap((delivery) => DeliveryProductToFieldValues( updateDeliveryRes.data?.sales_order, delivery ) ) ?? [] ) ); closeModalHandler(false); successModal.openModal(); } if (isResponseError(updateDeliveryRes)) { setFormErrorMessage(updateDeliveryRes?.message as string); } setIsLoading(false); }; // ================== HANDLER ================== const prevButtonHandler = () => { setStep(step - 1); }; const rejectMarketingHandler = async (notes: string) => { if (!marketingId) { toast.error(`Tidak ada data yang valid untuk di reject.`); rejectModal.closeModal(); return; } const rejectMarketingRes = await SalesOrderApi.singleApproval( Number(marketingId), 'REJECTED', notes ); if (isResponseSuccess(rejectMarketingRes)) { rejectModal.closeModal(); toast.success(rejectMarketingRes?.message as string); closeModalHandler(); router.push('/marketing'); } if (isResponseError(rejectMarketingRes)) { rejectModal.closeModal(); toast.error(rejectMarketingRes?.message as string); } refreshMarketing(); refreshApproval(); }; const approveMarketingHandler = async (notes: string) => { if (!marketingId) { toast.error(`Tidak ada data yang valid untuk di approve.`); approveModal.closeModal(); return; } const approveMarketingRes = await SalesOrderApi.singleApproval( Number(marketingId), 'APPROVED', notes ); if (isResponseSuccess(approveMarketingRes)) { approveModal.closeModal(); toast.success(approveMarketingRes?.message as string); closeModalHandler(); router.push('/marketing'); } if (isResponseError(approveMarketingRes)) { approveModal.closeModal(); toast.error(approveMarketingRes?.message as string); } refreshMarketing(); refreshApproval(); }; const deleteClickHandler = () => { deleteModal.openModal(); }; const confirmationModalDeleteClickHandler = async () => { if (!marketingId) { toast.error(`Tidak ada data yang valid untuk di hapus.`); deleteModal.closeModal(); return; } setIsLoading(true); const res = await MarketingApi.delete(Number(marketingId)); deleteModal.closeModal(); toast.success(res?.message as string); setIsLoading(false); router.replace('/marketing'); }; // ================== DELIVERY ORDER HANDLER ================== const handleEditDO = useCallback( (id: number, values?: DeliveryOrderProductFormValues) => { const currentProducts = deliveryOrderValues?.find( (product) => product.id == id ); setSelectedDeliveryProduct(currentProducts ?? values ?? null); if (id) { setStep(2); } }, [deliveryOrderValues, setSelectedDeliveryProduct, setStep] ); const handleAddDOClick = useCallback(() => { setSelectedDeliveryProduct(null); }, []); const handleAddSubmitDO = useCallback( async (values: DeliveryOrderProductFormValues) => { const newValues = { ...values, id: values.id ?? Date.now(), }; setDeliveryOrderValues((prev) => [...prev, newValues]); setSelectedDeliveryProduct(null); prevButtonHandler(); }, [prevButtonHandler] ); const isApprovalStep3Approved = useMemo(() => { return ( isResponseSuccess(marketing) && marketing.data.latest_approval?.step_number === 3 && marketing.data.latest_approval?.action === 'APPROVED' ); }, [marketing]); const handleUpdateDOWithAPI = useCallback( async (id: number, values: DeliveryOrderProductFormValues) => { if (!marketingId) { toast.error('Marketing ID tidak ditemukan'); return; } setIsLoading(true); const updatedDeliveryValues = deliveryOrderValues.map((product) => product.id === id ? { ...product, ...values } : product ); const payload = { marketing_id: Number(marketingId), delivery_products: updatedDeliveryValues .map((product) => { if (Boolean(product.delivery_date)) { return { marketing_product_id: product.marketing_product_id as number, unit_price: parseFloat(product.unit_price as string), total_weight: parseFloat(product.total_weight as string), qty: parseFloat(product.qty as string), avg_weight: parseFloat(product.avg_weight as string), total_price: parseFloat(product.total_price as string), delivery_date: formatDate( product.delivery_date as string, 'yyyy-MM-DD' ), vehicle_number: product.vehicle_number, weight_per_convertion: parseFloat( String(product.weight_per_convertion ?? 0) ), }; } }) .filter((item) => Boolean(item)), } as UpdateDeliveryOrderPayload; const updateDeliveryRes = await DeliveryOrderApi.update( Number(marketingId), payload ); if (isResponseSuccess(updateDeliveryRes)) { toast.success(updateDeliveryRes?.message as string); closeModalHandler(); } if (isResponseError(updateDeliveryRes)) { setFormErrorMessage(updateDeliveryRes?.message as string); } setIsLoading(false); }, [ marketingId, deliveryOrderValues, formik.values.sales_order, prevButtonHandler, refreshMarketing, ] ); const handleUpdateDOLocal = useCallback( async (id: number, values: DeliveryOrderProductFormValues) => { setDeliveryOrderValues((prev) => prev.map((product) => product.id === id ? { ...product, ...values } : product ) ); setSelectedDeliveryProduct(null); prevButtonHandler(); }, [prevButtonHandler] ); const handleDeleteDO = useCallback(async (id: number) => { setDeliveryOrderValues((prev) => prev.map((product) => product.id === id ? { ...product, ...{ delivery_date: '', }, } : product ) ); setSelectedDeliveryProduct(null); }, []); // ================== MEMOIZED ================== // const isNextButtonDisabled = useMemo(() => { // if (step === 1) { // return Boolean( // !formik.values.customer_id || // !formik.values.sales_person_id || // !formik.values.so_date || // !formik.values.notes // ); // } // return true; // }, [step, formik.values]); const deliveryRejected = useMemo(() => { return ( isResponseSuccess(marketing) && marketing.data.latest_approval.action === 'REJECTED' ); }, [marketing]); // ================== EFFECT ================== useEffect(() => { if ( modalAction === 'add_delivery' || modalAction === 'edit_delivery' || modalAction === 'detail' ) { setCurrentModalAction(modalAction); formModal.openModal(); } }, [modalAction]); const closeModalHandler = (shouldPushToRoute: boolean = true) => { refreshMarketing(); if (shouldPushToRoute) { formik.resetForm(); textareaRef.current?.setAttribute('value', ''); successModal.closeModal(); router.push('/marketing'); } setStep(1); setFormErrorMessage(''); formModal.closeModal(); }; useEffect(() => { const getFilledInitialValues = async () => { if (marketingId && isResponseSuccess(marketing)) { const filledInitialValues = await getFilledMarketingFormInitialValues( marketing.data ); formik.setValues(filledInitialValues); if (modalAction === 'add_delivery') { // add delivery const firstDeliveryItem = filledInitialValues.delivery_order?.[0]; if (firstDeliveryItem) { setSelectedDeliveryProduct(firstDeliveryItem); } setStep(2); // Langsung ke form delivery } else if (modalAction === 'edit_delivery') { // edit delivery const firstDeliveryItem = filledInitialValues.delivery_order?.[0]; if (firstDeliveryItem) { setSelectedDeliveryProduct(firstDeliveryItem); setStep(2); // Langsung ke form edit } else { setStep(1); // Jika belum ada data, tampilkan detail view } } else { setStep(1); // Detail view } } if (isResponseError(marketing)) { router.push('/marketing'); closeModalHandler(); toast.error(marketing.message); } }; getFilledInitialValues(); }, [marketingId, marketing, modalAction]); // Reset error message when step changes useEffect(() => { setFormErrorList([]); setFormErrorMessage(''); }, [step]); // sync delivery order values to formik useEffect(() => { formik.setFieldValue('delivery_order', deliveryOrderValues); }, [deliveryOrderValues]); const grandTotal = useMemo(() => { return deliveryOrderValues.reduce( (total, product) => total + parseFloat((product.total_price as string) || '0'), 0 ); }, [deliveryOrderValues]); return ( <>
{ e.preventDefault(); }} className='w-full min-h-full flex flex-col sm:flex-row items-stretch bg-base-100 rounded-xl overflow-y-auto' > {step <= 2 && isResponseSuccess(marketing) && ( <>

View Details

Informasi Penjualan

Label
Value
No. Order {marketing.data.do_number || marketing.data.so_number}
Nama Pelanggan {marketing.data.customer.name}
Status {formatTitleCase( marketing.data.latest_approval.step_name )}
Tanggal Penjualan {formatDate(marketing.data.so_date, 'DD MMM yyyy')}
Total Penjualan {formatCurrency(grandTotal || 0)}
Dokumen Penjualan
{step === 2 && ( <>
)}

Informasi

{step === 1 && ( )}
{step === 1 && ( )}

{step == 2 && 'Ubah '} Informasi{' '} {step == 2 ? 'Delivery' : 'Produk'}

{step === 1 && (
)} {step === 2 && ( )}
{formErrorMessage && (
setFormErrorMessage('')} />
)} {formErrorList && (
)} {step === 1 && marketing?.data?.latest_approval?.step_number !== 3 && (
)}
)}
{ closeModalHandler(); }, }} >
); }; export default DeliveryOrderFormModal;