diff --git a/src/components/modal/ConfirmationModalWithNotes.tsx b/src/components/modal/ConfirmationModalWithNotes.tsx index a5551571..e862dffc 100644 --- a/src/components/modal/ConfirmationModalWithNotes.tsx +++ b/src/components/modal/ConfirmationModalWithNotes.tsx @@ -13,6 +13,7 @@ interface ConfirmationModalWithNotesProps extends Omit { rows?: number; placeholder?: string; + onClose?: () => void; primaryButton?: { text?: string; @@ -32,6 +33,7 @@ const ConfirmationModalWithNotes: React.FC = ({ className, rows = 3, placeholder = 'Catatan...', + onClose, ...props }) => { const randomId = useId(); @@ -41,6 +43,11 @@ const ConfirmationModalWithNotes: React.FC = ({ setNotes(e.target.value); }; + const closeModalHandler = () => { + onClose?.(); + ref.current?.close(); + }; + return ( = ({ closeOnBackdrop={closeOnBackdrop} primaryButton={{ ...primaryButton, - onClick: () => { - primaryButton?.onClick?.(notes); + onClick: (e) => { + if (primaryButton && primaryButton?.onClick) { + primaryButton?.onClick?.(notes); + } else { + closeModalHandler(); + } + setNotes(''); }, }} - secondaryButton={secondaryButton} + secondaryButton={ + secondaryButton + ? { + text: secondaryButton?.text ?? 'Tidak', + onClick: (e) => { + if (secondaryButton && secondaryButton?.onClick) { + secondaryButton.onClick?.(e); + } else { + closeModalHandler(); + } + + setNotes(''); + }, + } + : undefined + } className={className} {...props} > diff --git a/src/components/pages/expense/ExpenseRequestContent.tsx b/src/components/pages/expense/ExpenseRequestContent.tsx index 6513956e..8fbc81d7 100644 --- a/src/components/pages/expense/ExpenseRequestContent.tsx +++ b/src/components/pages/expense/ExpenseRequestContent.tsx @@ -100,6 +100,7 @@ const ExpenseRequestContent = ({ const [isCompleteLoading, setIsCompleteLoading] = useState(false); const [isApproveLoading, setIsApproveLoading] = useState(false); const [isRejectLoading, setIsRejectLoading] = useState(false); + const [, setApprovalNotes] = useState(''); const formik = useFormik({ initialValues: { @@ -130,10 +131,12 @@ const ExpenseRequestContent = ({ }; const approveClickHandler = () => { + setApprovalNotes(''); approveModal.openModal(); }; const rejectClickHandler = () => { + setApprovalNotes(''); rejectModal.openModal(); }; @@ -200,6 +203,7 @@ const ExpenseRequestContent = ({ approveModal.closeModal(); toast.success(approveResponse?.message); + setApprovalNotes(''); router.push('/expense'); } else { approveModal.closeModal(); @@ -234,6 +238,7 @@ const ExpenseRequestContent = ({ rejectModal.closeModal(); toast.success(rejectResponse.message); + setApprovalNotes(''); router.push('/expense'); } else { rejectModal.closeModal(); @@ -710,6 +715,10 @@ const ExpenseRequestContent = ({ text='Apakah anda yakin ingin approve data biaya operasional ini?' secondaryButton={{ text: 'Tidak', + onClick: () => { + setApprovalNotes(''); + approveModal.closeModal(); + }, }} primaryButton={{ text: 'Ya', @@ -725,6 +734,10 @@ const ExpenseRequestContent = ({ text='Apakah anda yakin ingin reject data biaya operasional ini?' secondaryButton={{ text: 'Tidak', + onClick: () => { + setApprovalNotes(''); + rejectModal.closeModal(); + }, }} primaryButton={{ text: 'Ya', diff --git a/src/components/pages/expense/ExpensesTable.tsx b/src/components/pages/expense/ExpensesTable.tsx index 69376992..d2d4754c 100644 --- a/src/components/pages/expense/ExpensesTable.tsx +++ b/src/components/pages/expense/ExpensesTable.tsx @@ -185,6 +185,7 @@ const ExpensesTable = () => { const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [isApproveLoading, setIsApproveLoading] = useState(false); const [isRejectLoading, setIsRejectLoading] = useState(false); + const [, setApprovalNotes] = useState(''); const [sorting, setSorting] = useState([]); const [rowSelection, setRowSelection] = useState>({}); @@ -342,6 +343,7 @@ const ExpensesTable = () => { [String(props.row.original.id)]: true, }); + setApprovalNotes(''); approveModal.openModal(); }; @@ -353,6 +355,7 @@ const ExpensesTable = () => { [String(props.row.original.id)]: true, }); + setApprovalNotes(''); rejectModal.openModal(); }; @@ -412,10 +415,12 @@ const ExpensesTable = () => { // }; const bulkApproveClickHandler = () => { + setApprovalNotes(''); approveModal.openModal(); }; const bulkRejectClickHandler = () => { + setApprovalNotes(''); rejectModal.openModal(); }; @@ -468,6 +473,7 @@ const ExpensesTable = () => { `Berhasil approve ${selectedRowIds.length} data biaya operasional!` ); + setApprovalNotes(''); setRowSelection({}); } else { approveModal.closeModal(); @@ -509,6 +515,7 @@ const ExpensesTable = () => { toast.success( `Berhasil reject ${selectedRowIds.length} data biaya operasional!` ); + setApprovalNotes(''); setRowSelection({}); } else { rejectModal.closeModal(); @@ -787,6 +794,10 @@ const ExpensesTable = () => { text='Apakah anda yakin ingin approve data biaya operasional ini?' secondaryButton={{ text: 'Tidak', + onClick: () => { + setApprovalNotes(''); + approveModal.closeModal(); + }, }} primaryButton={{ text: 'Ya', @@ -802,6 +813,10 @@ const ExpensesTable = () => { text='Apakah anda yakin ingin reject data biaya operasional ini?' secondaryButton={{ text: 'Tidak', + onClick: () => { + setApprovalNotes(''); + rejectModal.closeModal(); + }, }} primaryButton={{ text: 'Ya', diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index f3d6d2f9..94f078c1 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -241,6 +241,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { new Date().toISOString().split('T')[0] ); const [duplicateErrorShown, setDuplicateErrorShown] = useState(false); + const [nextDayErrorShown, setNextDayErrorShown] = useState(false); useEffect(() => { return () => { @@ -553,6 +554,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const nextDayRecordingUrl = useMemo(() => { if (!projectFlockKandangLookup) return null; + if (!selectedRecordDate) return null; const projectFlockKandangId = projectFlockKandangLookup.id; return `${RecordingApi.basePath}/next-day?project_flock_kandang_id=${projectFlockKandangId}&record_date=${selectedRecordDate}`; }, [projectFlockKandangLookup, selectedRecordDate]); @@ -575,10 +577,28 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { setNextDayRecording( nextDayRecordingData.data as unknown as NextDayRecording ); + if (nextDayErrorShown) { + toast.dismiss(); + setNextDayErrorShown(false); + } + } else if (nextDayRecordingData?.status === 'error') { + setNextDayRecording(null); + if (!nextDayErrorShown) { + toast.error( + nextDayRecordingData.message || + 'Terjadi kesalahan saat memuat data hari berikutnya', + { duration: Infinity } + ); + setNextDayErrorShown(true); + } } else { setNextDayRecording(null); + if (nextDayErrorShown) { + toast.dismiss(); + setNextDayErrorShown(false); + } } - }, [nextDayRecordingData]); + }, [nextDayRecordingData, nextDayErrorShown]); const { rawData: eggProductsData, @@ -1196,6 +1216,66 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { [stockProducts, depletionProductsData, eggProductsData, initialValues, type] ); + const getAvailableStockProductOptions = useCallback( + (currentIdx: number) => { + const selectedProductIds = + formik.values.stocks + ?.filter((s, idx) => { + return ( + idx !== currentIdx && + s.product_warehouse_id && + s.product_warehouse_id !== 0 + ); + }) + .map((s) => s.product_warehouse_id) || []; + + return unifiedStockProducts.filter( + (opt) => !selectedProductIds.includes(Number(opt.value)) + ); + }, + [formik.values.stocks, unifiedStockProducts] + ); + + const getAvailableDepletionProductOptions = useCallback( + (currentIdx: number) => { + const selectedProductIds = + formik.values.depletions + ?.filter((d, idx) => { + return ( + idx !== currentIdx && + d.product_warehouse_id && + d.product_warehouse_id !== 0 + ); + }) + .map((d) => d.product_warehouse_id) || []; + + return depletionProducts.filter( + (opt) => !selectedProductIds.includes(Number(opt.value)) + ); + }, + [formik.values.depletions, depletionProducts] + ); + + const getAvailableEggProductOptions = useCallback( + (currentIdx: number) => { + const selectedProductIds = + (formik.values as RecordingLayingFormValues).eggs + ?.filter((e, idx) => { + return ( + idx !== currentIdx && + e.product_warehouse_id && + e.product_warehouse_id !== 0 + ); + }) + .map((e) => e.product_warehouse_id) || []; + + return eggProducts.filter( + (opt) => !selectedProductIds.includes(Number(opt.value)) + ); + }, + [formik.values, eggProducts] + ); + const hasExceededStock = useMemo(() => { if ((type as 'add' | 'edit' | 'detail') === 'detail') return false; return ( @@ -1255,6 +1335,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { toast.dismiss(); setDuplicateErrorShown(false); } + if (nextDayErrorShown) { + toast.dismiss(); + setNextDayErrorShown(false); + } setSelectedProjectFlockLocationId( location ? location.value.toString() : '' ); @@ -1275,6 +1359,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { toast.dismiss(); setDuplicateErrorShown(false); } + if (nextDayErrorShown) { + toast.dismiss(); + setNextDayErrorShown(false); + } }; const kandangChangeHandler = (val: OptionType | OptionType[] | null) => { @@ -1291,6 +1379,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { toast.dismiss(); setDuplicateErrorShown(false); } + if (nextDayErrorShown) { + toast.dismiss(); + setNextDayErrorShown(false); + } if (selectedLocation && kandang) { setStockProductsLocationId(selectedLocation.value.toString()); setStockProductsKandangId(kandang.value.toString()); @@ -1320,11 +1412,15 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { toast.dismiss(); setDuplicateErrorShown(false); } + if (nextDayErrorShown) { + toast.dismiss(); + setNextDayErrorShown(false); + } setTimeout(() => { formik.validateField('project_flock_kandang_id'); }, 0); }, - [formik, duplicateErrorShown] + [formik, duplicateErrorShown, nextDayErrorShown] ); const { formErrorList, handleFormSubmit, close } = useFormikErrorList(formik); @@ -1350,28 +1446,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { setDuplicateErrorShown(false); } } - - if ( - nextDayRecording && - nextDayRecording.project_flock_kandang_id === projectFlockKandangId - ) { - const hasSameDayRecording = isResponseSuccess(existingRecordings) - ? existingRecordings.data?.some( - (recording: Recording) => - recording.project_flock.project_flock_kandang_id === - projectFlockKandangId && - recording.day === nextDayRecording.next_day - ) - : false; - - if (hasSameDayRecording) { - toast.error( - `Recording untuk hari ke-${nextDayRecording.next_day} sudah ada datanya. - Tidak bisa membuat recording di hari yang sama dengan project flock yang sama, mohon perbarui recording yang sudah ada terlebih dahulu.` - ); - return; - } - } } if (formik.values.project_flock_kandang_id !== projectFlockKandangId) { @@ -1831,8 +1905,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
Umur

- {nextDayRecording - ? `Hari ke-${nextDayRecording.next_day} (Minggu ke-${Math.ceil(nextDayRecording.next_day / 7)})` + {type === 'add' + ? nextDayRecording + ? `Hari ke-${nextDayRecording.next_day} (Minggu ke-${Math.ceil(nextDayRecording.next_day / 7)})` + : '-' : initialValues?.day ? `Hari ke-${initialValues.day} (Minggu ke-${Math.ceil(initialValues.day / 7)})` : '-'} @@ -2398,7 +2474,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { option?.value || 0 ); }} - options={unifiedStockProducts} + options={getAvailableStockProductOptions(idx)} placeholder={ !formik.values.project_flock_kandang_id ? 'Pilih kandang terlebih dahulu' @@ -2619,7 +2695,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { option?.value || 0 ); }} - options={depletionProducts} + options={getAvailableDepletionProductOptions(idx)} placeholder='Pilih Kondisi' isLoading={isLoadingDepletionProducts} onMenuScrollToBottom={loadMoreDepletionProducts} @@ -2837,7 +2913,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { option?.value || 0 ); }} - options={eggProducts} + options={getAvailableEggProductOptions(idx)} placeholder='Pilih Kondisi Telur' isLoading={isLoadingEggProducts} onMenuScrollToBottom={loadMoreEggProducts} @@ -3116,7 +3192,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { text='Apakah anda yakin ingin menyetujui data Recording ini?' secondaryButton={{ text: 'Tidak', - onClick: () => setApprovalNotes(''), + onClick: () => { + setApprovalNotes(''); + approveModal.closeModal(); + }, }} primaryButton={{ text: 'Ya', @@ -3138,7 +3217,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { text='Apakah anda yakin ingin menolak data Recording ini?' secondaryButton={{ text: 'Tidak', - onClick: () => setApprovalNotes(''), + onClick: () => { + setApprovalNotes(''); + rejectModal.closeModal(); + }, }} primaryButton={{ text: 'Ya', diff --git a/src/components/pages/purchase/order/PurchaseOrderDetail.tsx b/src/components/pages/purchase/order/PurchaseOrderDetail.tsx index 1f8dd3c9..5324db03 100644 --- a/src/components/pages/purchase/order/PurchaseOrderDetail.tsx +++ b/src/components/pages/purchase/order/PurchaseOrderDetail.tsx @@ -105,6 +105,7 @@ const PurchaseOrderDetail = ({ const [rowSelection, setRowSelection] = useState>({}); const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [selectedItem, setSelectedItem] = useState(null); + const [, setApprovalNotes] = useState(''); const selectedRowIds = Object.keys(rowSelection).map((item) => parseInt(item) @@ -207,12 +208,15 @@ const PurchaseOrderDetail = ({ switch (approvalStep) { case 1: + setApprovalNotes(''); staffApprovalModal.openModal(); break; case 2: + setApprovalNotes(''); confirmationModalWithNotes.openModal(); break; case 3: + setApprovalNotes(''); acceptApprovalModal.openModal(); break; default: @@ -225,12 +229,15 @@ const PurchaseOrderDetail = ({ switch (approvalStep) { case 1: + setApprovalNotes(''); staffRejectionModal.openModal(); break; case 2: + setApprovalNotes(''); managerRejectionModal.openModal(); break; case 3: + setApprovalNotes(''); acceptRejectionModal.openModal(); break; default: @@ -406,6 +413,56 @@ const PurchaseOrderDetail = ({ refetchData, ]); + // ===== APPROVAL/REJECTION HANDLERS ===== + const managerApprovalHandler = async (notes: string) => { + const payload: CreateManagerApprovalRequestPayload = { + action: 'APPROVED', + notes: notes || null, + }; + + await createManagerApprovalHandler(payload); + await refreshApprovals(); + await refetchData?.(); + setApprovalNotes(''); + confirmationModalWithNotes.closeModal(); + }; + + const staffRejectionHandler = async (notes: string) => { + const payload: CreateStaffApprovalRequestPayload = { + action: 'REJECTED', + notes: notes || null, + }; + + await createStaffApprovalHandler(payload); + await refetchData?.(); + setApprovalNotes(''); + staffRejectionModal.closeModal(); + }; + + const acceptRejectionHandler = async (notes: string) => { + const payload: CreateAcceptApprovalRequestPayload = { + action: 'REJECTED', + notes: notes || null, + }; + + await createAcceptApprovalHandler(payload); + await refetchData?.(); + setApprovalNotes(''); + acceptRejectionModal.closeModal(); + }; + + const managerRejectionHandler = async (notes: string) => { + const payload: CreateManagerApprovalRequestPayload = { + action: 'REJECTED', + notes: notes || null, + }; + + await createManagerApprovalHandler(payload); + await refetchData?.(); + setApprovalNotes(''); + managerRejectionModal.closeModal(); + }; + if (!initialValues) { return null; } @@ -969,20 +1026,14 @@ const PurchaseOrderDetail = ({ primaryButton={{ text: 'Ya, Lanjutkan', color: 'success', - onClick: async (notes) => { - const payload: CreateManagerApprovalRequestPayload = { - action: 'APPROVED', - notes: notes || null, - }; - - await createManagerApprovalHandler(payload); - await refreshApprovals(); - await refetchData?.(); - confirmationModalWithNotes.closeModal(); - }, + onClick: managerApprovalHandler, }} secondaryButton={{ text: 'Batal', + onClick: () => { + setApprovalNotes(''); + confirmationModalWithNotes.closeModal(); + }, }} /> @@ -1071,19 +1122,14 @@ const PurchaseOrderDetail = ({ primaryButton={{ text: 'Ya, Tolak', color: 'error', - onClick: async (notes) => { - const payload: CreateStaffApprovalRequestPayload = { - action: 'REJECTED', - notes: notes || null, - }; - - await createStaffApprovalHandler(payload); - await refetchData?.(); - staffRejectionModal.closeModal(); - }, + onClick: staffRejectionHandler, }} secondaryButton={{ text: 'Batal', + onClick: () => { + setApprovalNotes(''); + staffRejectionModal.closeModal(); + }, }} /> @@ -1098,19 +1144,14 @@ const PurchaseOrderDetail = ({ primaryButton={{ text: 'Ya, Tolak', color: 'error', - onClick: async (notes) => { - const payload: CreateAcceptApprovalRequestPayload = { - action: 'REJECTED', - notes: notes || null, - }; - - await createAcceptApprovalHandler(payload); - await refetchData?.(); - acceptRejectionModal.closeModal(); - }, + onClick: acceptRejectionHandler, }} secondaryButton={{ text: 'Batal', + onClick: () => { + setApprovalNotes(''); + acceptRejectionModal.closeModal(); + }, }} /> @@ -1125,19 +1166,14 @@ const PurchaseOrderDetail = ({ primaryButton={{ text: 'Ya, Tolak', color: 'error', - onClick: async (notes) => { - const payload: CreateManagerApprovalRequestPayload = { - action: 'REJECTED', - notes: notes || null, - }; - - await createManagerApprovalHandler(payload); - await refetchData?.(); - managerRejectionModal.closeModal(); - }, + onClick: managerRejectionHandler, }} secondaryButton={{ text: 'Batal', + onClick: () => { + setApprovalNotes(''); + managerRejectionModal.closeModal(); + }, }} />