'use client'; import Button from '@/components/Button'; import AlertErrorList from '@/components/helper/form/FormErrors'; import RequirePermission from '@/components/helper/RequirePermission'; import DateInput from '@/components/input/DateInput'; import DebouncedTextArea from '@/components/input/DebouncedTextArea'; import NumberInput from '@/components/input/NumberInput'; import { OptionType, useSelect } from '@/components/input/SelectInput'; import SelectInputRadio from '@/components/input/SelectInputRadio'; import Modal, { useModal } from '@/components/Modal'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; import { DeliveryProductToFieldValues, mergeSOwithDO, SalesProductToFieldValues, DeliveryOrderFormValues, DeliveryOrderSchema, getFilledMarketingFormInitialValues, SalesOrderFormValues, SalesOrderSchema, } from '@/components/pages/marketing/form/MarketingForm.schema'; import { DeliveryOrderProductFormValues } from '@/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.schema'; import { SalesOrderProductFormValues } from '@/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema'; import SalesOrderProductForm from '@/components/pages/marketing/form/repeater/sales-order/SalesOrderProductForm'; import SalesOrderProductTable from '@/components/pages/marketing/form/table-view/SalesOrderProductTable'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { formatDate } from '@/lib/helper'; import { MarketingApi, SalesOrderApi, } from '@/services/api/marketing/marketing'; import { CustomerApi } from '@/services/api/master-data'; import { UserApi } from '@/services/api/user'; import { useFormikErrorList } from '@/services/hooks/useFormikErrorList'; import { CreatedUser } from '@/types/api/api-general'; import { CreateSalesOrderPayload, CreateSalesOrderProductPayload, Marketing, UpdateDeliveryOrderPayload, UpdateSalesOrderPayload, } from '@/types/api/marketing/marketing'; import { Customer } from '@/types/api/master-data/customer'; import { Icon } from '@iconify/react'; import { useFormik } from 'formik'; import { useRouter, useSearchParams } from 'next/navigation'; import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import toast from 'react-hot-toast'; import useSWR, { useSWRConfig } from 'swr'; const MemoizedSalesOrderProductTable = memo(SalesOrderProductTable); const MemoizedSalesOrderProductForm = memo(SalesOrderProductForm); const SalesOrderFormModal = ({ initialValues, }: { 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' || modalAction === 'edit' || modalAction === 'add_delivery' || modalAction === 'edit_delivery'; 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)) ); // ================== FETCH OPTIONS ================== const { options: customerOptions, isLoadingOptions: isLoadingCustomerOptions, setInputValue: setInputCustomerValue, loadMore: loadMoreCustomer, } = useSelect(CustomerApi.basePath, 'id', 'name'); const { options: salesOptions, isLoadingOptions: isLoadingSalesOptions, setInputValue: setInputSalesValue, loadMore: loadMoreSales, } = useSelect(UserApi.basePath, 'id', 'name'); /** * Step 1: General Information * Step 2: Repeater Add Product * Step 3: Submit */ const [step, setStep] = useState(1); const formModal = useModal(); const successModal = useModal(); const deleteModal = useModal(); const formRef = useRef(null); const textareaRef = useRef(null); const [formikLastValues, setFormikLastValues] = useState< SalesOrderFormValues | undefined >(undefined); const [formErrorMessage, setFormErrorMessage] = useState(null); const [isLoading, setIsLoading] = useState(false); const [selectedMarketingProduct, setSelectedMarketingProduct] = useState(null); const [selectedDeliveryProduct, setSelectedDeliveryProduct] = useState(null); const [deliveryFormState, setDeliveryFormState] = useState<'add' | 'edit'>( 'add' ); const [deliveryOrderValues, setDeliveryOrderValues] = useState< DeliveryOrderProductFormValues[] >( mergeSOwithDO( initialValues?.sales_order?.map(SalesProductToFieldValues) ?? [], initialValues?.delivery_order?.flatMap((delivery) => DeliveryProductToFieldValues(initialValues.sales_order, delivery) ) ?? [] ) ); // ================== SETUP FORMIK ================== const formikInitialValues = useMemo< SalesOrderFormValues & DeliveryOrderFormValues >(() => { return { so_date: initialValues?.so_date || undefined, notes: initialValues?.notes || undefined, customer_id: initialValues?.customer?.id || undefined, sales_person_id: initialValues?.sales_person?.id || undefined, sales_person: initialValues?.sales_person ? { value: initialValues.sales_person.id, label: initialValues.sales_person.name, } : null, customer: initialValues?.customer ? { value: initialValues.customer.id, label: initialValues.customer.name, } : null, sales_order: initialValues?.sales_order?.map((product) => SalesProductToFieldValues(product) ) ?? [], delivery_order: mergeSOwithDO( initialValues?.sales_order?.map(SalesProductToFieldValues) ?? [], initialValues?.delivery_order?.flatMap((delivery) => DeliveryProductToFieldValues(initialValues.sales_order, delivery) ) ?? [] ), }; }, [initialValues]); const formik = useFormik({ enableReinitialize: true, initialValues: formikInitialValues, validationSchema: modalAction == 'add_deliver' || modalAction == 'edit_deliver' ? DeliveryOrderSchema : SalesOrderSchema, validateOnMount: true, onSubmit: async (values) => { const payload = modalAction != 'add_deliver' && modalAction != 'edit_deliver' ? ({ customer_id: values.customer_id as number, sales_person_id: values.sales_person_id as number, date: formatDate(values.so_date as string, 'yyyy-MM-DD'), notes: values.notes as string, marketing_products: values.sales_order.map((product) => { // Workaround untuk TELUR + QTY: kirim "KG" karena BE tidak support "QTY" const convertionUnitValue = product.convertion_unit?.value?.toUpperCase(); const normalizedConvertionUnit = product.marketing_type?.value?.toLowerCase() === 'telur' ? convertionUnitValue === 'PETI' ? 'PETI' : convertionUnitValue === 'QTY' ? 'QTY' : 'KG' : undefined; // Jika value dari data product ada week, kirim "AYAM_PULLET, jika tidak ada kirim "AYAM" let marketingTypeValue = product.marketing_type?.value?.toUpperCase() || ''; if (marketingTypeValue === 'AYAM,AYAM_PULLET') { marketingTypeValue = product.week ? 'AYAM_PULLET' : 'AYAM'; } return { vehicle_number: product.vehicle_number as string, product_warehouse_id: product.product_warehouse_id as number, unit_price: parseFloat(String(product.unit_price || 0)), total_weight: parseFloat(String(product.total_weight || 0)), qty: parseFloat(String(product.qty || 0)), avg_weight: parseFloat(String(product.avg_weight || 0)), total_price: parseFloat(String(product.total_price || 0)), marketing_type: marketingTypeValue, convertion_unit: normalizedConvertionUnit, weight_per_convertion: product.weight_per_convertion ?? undefined, week: product.week ?? undefined, } as CreateSalesOrderProductPayload; }), } as CreateSalesOrderPayload) : ({ marketing_id: initialValues?.id as number, 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, }; } }) .filter((item) => Boolean(item)), } as UpdateDeliveryOrderPayload); switch (modalAction) { case 'add': await createMarketingHandler(payload as CreateSalesOrderPayload); break; case 'edit': await updateMarketingHandler(payload as UpdateSalesOrderPayload); break; default: break; } }, }); // ===== Formik Error List ===== const { formErrorList, setFormErrorList, close, handleFormSubmit } = useFormikErrorList(formik); // ================== FORM REPEATER HANDLER ================== const createMarketingHandler = async (values: CreateSalesOrderPayload) => { setIsLoading(true); const createMarketingRes = await SalesOrderApi.create(values); if (isResponseSuccess(createMarketingRes)) { closeModalHandler(false); successModal.openModal(); refreshMarketing(); } if (isResponseError(createMarketingRes)) { toast.error(createMarketingRes?.message as string); } setIsLoading(false); }; const updateMarketingHandler = async (values: UpdateSalesOrderPayload) => { setIsLoading(true); if (!marketingId) { toast.error('Marketing ID is required'); setIsLoading(false); return; } const updateMarketingRes = await SalesOrderApi.update( Number(marketingId), values ); if (isResponseSuccess(updateMarketingRes)) { closeModalHandler(false); successModal.openModal(); refreshMarketing(); } if (isResponseError(updateMarketingRes)) { toast.error(updateMarketingRes?.message as string); } setIsLoading(false); }; const memoSalesOrder = formik.values.sales_order; // ================== HANDLER ================== const nextButtonHandler = () => { setSelectedMarketingProduct(null); setStep(step + 1); }; const prevButtonHandler = () => { setStep(step - 1); }; const handleChangeCustomer = useCallback( (val: OptionType | OptionType[] | null) => { formik.setFieldValue('customer_id', (val as OptionType)?.value); formik.setFieldValue('customer', val as OptionType); }, [] ); const handleChangeSalesPerson = useCallback( (val: OptionType | OptionType[] | null) => { formik.setFieldValue('sales_person_id', (val as OptionType)?.value); formik.setFieldValue('sales_person', val as OptionType); }, [] ); 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); refreshMarketing(); closeModalHandler(); router.replace('/marketing'); }; // ================== SALES ORDER HANDLER ================== const handleDeleteSO = useCallback( (id: number) => { const currentProducts = formik.values.sales_order; formik.setFieldValue( 'sales_order', currentProducts.filter((p) => p.id != id) ); }, [memoSalesOrder] ); const handleDeleteAllSO = useCallback(() => { formik.setFieldValue('sales_order', []); }, [memoSalesOrder]); const handleEditSO = useCallback( (id: number) => { const currentProducts = formik.values.sales_order; const selectedProduct = currentProducts.find((p) => p.id == id); setSelectedMarketingProduct(selectedProduct ?? null); setStep(2); }, [memoSalesOrder] ); const handleAddSOClick = useCallback(() => { setSelectedMarketingProduct(null); if (step === 3) { setStep(2); } else { prevButtonHandler(); } }, [step]); const handleAddSubmitSO = useCallback( async (values: SalesOrderProductFormValues, id?: number) => { const currentProducts = formik.values.sales_order; const newValues = { ...values, id: values.id ?? Date.now(), }; let updatedProducts = []; if (id) { // Overwrite updatedProducts = currentProducts.map((item) => item.id === id ? newValues : item ); } else { // Add new item updatedProducts = [...currentProducts, newValues]; } formik.setFieldValue('sales_order', updatedProducts); setSelectedMarketingProduct(null); nextButtonHandler(); }, [memoSalesOrder, nextButtonHandler] ); 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]); // ================== EFFECT ================== useEffect(() => { if (modalAction === 'add' || modalAction === 'edit') { setCurrentModalAction(modalAction); formModal.openModal(); if (modalAction === 'add') { formik.resetForm(); setStep(1); setSelectedMarketingProduct(null); setSelectedDeliveryProduct(null); setFormErrorMessage(''); setFormErrorList([]); } } }, [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(); }; const grandTotal = useMemo(() => { return memoSalesOrder.reduce( (total, product) => total + parseFloat((product.total_price as string) || '0'), 0 ); }, [memoSalesOrder]); useEffect(() => { const getFilledInitialValues = async () => { if (marketingId && isResponseSuccess(marketing)) { const filledInitialValues = await getFilledMarketingFormInitialValues( marketing.data ); formik.setValues(filledInitialValues); setStep(3); } if (isResponseError(marketing)) { router.push('/marketing'); closeModalHandler(); toast.error(marketing.message); } }; getFilledInitialValues(); }, [marketingId, marketing]); // Reset error message when step changes useEffect(() => { setFormErrorList([]); setFormErrorMessage(''); }, [step]); useEffect(() => { if (memoSalesOrder.length === 0) { setStep(1); } }, [memoSalesOrder]); 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 <= 3 && (

{modalAction === 'add' ? 'Add' : 'Edit'} Sales Order

Informasi Order

Rp } />
{step === 1 && ( )}
)} {step === 2 && (
{memoSalesOrder.length > 0 && ( <>
)}

{selectedMarketingProduct?.id ? 'Ubah' : 'Tambah'} Produk

)} {step === 3 && (

Informasi Produk

{memoSalesOrder.length > 0 && (
)}
)}
{ closeModalHandler(); }, }} >
); }; export default SalesOrderFormModal;