From e9e8ad771eafac1db87fe02b0c175b283282ed65 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 3 Nov 2025 10:22:35 +0700 Subject: [PATCH] refactor(FE-174): enhance GradingForm and RecordingForm with improved error handling and modal integration for delete actions --- .../recording/form/RecordingForm.tsx | 66 ++++++++++--- .../form/useRecordingFormHandlers.ts | 70 -------------- .../recording/grading/form/GradingForm.tsx | 86 ++++++++++++++--- .../grading/form/useGradingFormHandlers.ts | 95 ------------------- 4 files changed, 126 insertions(+), 191 deletions(-) delete mode 100644 src/components/pages/production/recording/form/useRecordingFormHandlers.ts delete mode 100644 src/components/pages/production/recording/grading/form/useGradingFormHandlers.ts diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index 7e888cd5..e6eba21c 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -31,11 +31,10 @@ import { UpdateRecordingGrowingFormSchema, UpdateRecordingLayingFormSchema, } from './RecordingForm.schema'; -import { useRecordingFormHandlers } from './useRecordingFormHandlers'; import { ProjectFlockApi } from '@/services/api/production'; import { LocationApi } from '@/services/api/master-data'; import { ProductWarehouseApi } from '@/services/api/inventory'; -import { isResponseSuccess } from '@/lib/api-helper'; +import { isResponseSuccess, isResponseError } from '@/lib/api-helper'; import { cn } from '@/lib/helper'; import { ProjectFlockKandangLookup } from '@/types/api/production/project-flock'; import { useModal } from '@/components/Modal'; @@ -77,9 +76,61 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const [isApproveLoading, setIsApproveLoading] = useState(false); const [isRejectLoading, setIsRejectLoading] = useState(false); const [formSteps, setFormSteps] = useState(null); + const [recordingFormErrorMessage, setRecordingFormErrorMessage] = + useState(''); + const [isDeleteLoading, setIsDeleteLoading] = useState(false); const approveModal = useModal(); const rejectModal = useModal(); + const deleteModal = useModal(); + + // ===== FORM HANDLERS ===== + const createRecordingHandler = useCallback( + async ( + payload: CreateGrowingRecordingPayload | CreateLayingRecordingPayload + ) => { + const res = await RecordingApi.create(payload); + if (isResponseError(res)) { + setRecordingFormErrorMessage(res.message); + return; + } + toast.success(res?.message as string); + router.push('/production/recording'); + }, + [router] + ); + + const updateRecordingHandler = useCallback( + async ( + recordingId: number, + payload: UpdateGrowingRecordingPayload | UpdateLayingRecordingPayload + ) => { + const res = await RecordingApi.update(recordingId, payload); + if (res?.status === 'error') { + setRecordingFormErrorMessage(res.message); + return; + } + toast.success(res?.message as string); + router.refresh(); + router.push('/production/recording'); + }, + [router] + ); + + const deleteRecordingClickHandler = useCallback(() => { + deleteModal.openModal(); + }, [deleteModal]); + + const confirmationModalDeleteClickHandler = useCallback(async () => { + if (!initialValues?.id) return; + + setIsDeleteLoading(true); + await RecordingApi.delete(initialValues.id); + deleteModal.closeModal(); + toast.success('Successfully delete Recording!'); + setIsDeleteLoading(false); + router.push('/production/recording'); + }, [deleteModal, initialValues?.id, router]); // ===== API DATA FETCHING ===== const locationsUrl = `${LocationApi.basePath}?${new URLSearchParams({ @@ -336,17 +387,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { return options; }, [eggProductsData]); - // ===== FORM HANDLERS ===== - const { - deleteModal, - recordingFormErrorMessage, - isDeleteLoading, - createRecordingHandler, - updateRecordingHandler, - deleteRecordingClickHandler, - confirmationModalDeleteClickHandler, - } = useRecordingFormHandlers(initialValues?.id); - const isLayingCategory = projectFlockKandangLookup?.project_flock?.category === 'LAYING'; diff --git a/src/components/pages/production/recording/form/useRecordingFormHandlers.ts b/src/components/pages/production/recording/form/useRecordingFormHandlers.ts deleted file mode 100644 index 58893ce1..00000000 --- a/src/components/pages/production/recording/form/useRecordingFormHandlers.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { useCallback, useState } from 'react'; -import { useRouter } from 'next/navigation'; -import { toast } from 'react-hot-toast'; -import { useModal } from '@/components/Modal'; -import { RecordingApi } from '@/services/api/production'; -import { - CreateRecordingPayload, - UpdateRecordingPayload, -} from '@/types/api/production/recording'; -import { isResponseError } from '@/lib/api-helper'; - -export const useRecordingFormHandlers = (initialValuesId?: number) => { - const router = useRouter(); - const deleteModal = useModal(); - const [recordingFormErrorMessage, setRecordingFormErrorMessage] = - useState(''); - const [isDeleteLoading, setIsDeleteLoading] = useState(false); - - const createRecordingHandler = useCallback( - async (payload: CreateRecordingPayload) => { - const res = await RecordingApi.create(payload); - if (isResponseError(res)) { - setRecordingFormErrorMessage(res.message); - return; - } - toast.success(res?.message as string); - router.push('/production/recording'); - }, - [router] - ); - - const updateRecordingHandler = useCallback( - async (recordingId: number, payload: UpdateRecordingPayload) => { - const res = await RecordingApi.update(recordingId, payload); - if (res?.status === 'error') { - setRecordingFormErrorMessage(res.message); - return; - } - toast.success(res?.message as string); - router.refresh(); - router.push('/production/recording'); - }, - [router] - ); - - const deleteRecordingClickHandler = useCallback(() => { - deleteModal.openModal(); - }, [deleteModal]); - - const confirmationModalDeleteClickHandler = useCallback(async () => { - if (!initialValuesId) return; - - setIsDeleteLoading(true); - await RecordingApi.delete(initialValuesId); - deleteModal.closeModal(); - toast.success('Successfully delete Recording!'); - setIsDeleteLoading(false); - router.push('/production/recording'); - }, [deleteModal, initialValuesId, router]); - - return { - deleteModal, - recordingFormErrorMessage, - isDeleteLoading, - createRecordingHandler, - updateRecordingHandler, - deleteRecordingClickHandler, - confirmationModalDeleteClickHandler, - }; -}; diff --git a/src/components/pages/production/recording/grading/form/GradingForm.tsx b/src/components/pages/production/recording/grading/form/GradingForm.tsx index 4f41fc16..9979c2cc 100644 --- a/src/components/pages/production/recording/grading/form/GradingForm.tsx +++ b/src/components/pages/production/recording/grading/form/GradingForm.tsx @@ -17,7 +17,10 @@ import { RecordingEgg, GradingEgg, } from '@/types/api/production/recording'; -import { type FormStepStatus } from '@/types/api/api-general'; +import { + type FormStepStatus, + type BaseApiResponse, +} from '@/types/api/api-general'; import { RecordingGradingFormSchema, RecordingGradingFormValues, @@ -26,10 +29,12 @@ import { } from '../../form/RecordingForm.schema'; import { cn } from '@/lib/helper'; import toast from 'react-hot-toast'; +import { RecordingApi } from '@/services/api/production'; +import { isResponseError } from '@/lib/api-helper'; +import { useModal } from '@/components/Modal'; import Card from '@/components/Card'; import StepItem from '@/components/steps/StepItem'; -import { useGradingFormHandlers } from './useGradingFormHandlers'; interface GradingFormProps { type?: 'add' | 'edit' | 'detail'; @@ -45,6 +50,9 @@ const GradingForm = ({ type = 'add', initialValues }: GradingFormProps) => { ); const [formSteps, setFormSteps] = useState(null); + const [gradingFormErrorMessage, setGradingFormErrorMessage] = useState(''); + const [isDeleteLoading, setIsDeleteLoading] = useState(false); + const deleteModal = useModal(); // ===== API DATA FETCHING ===== // const existingGradingsUrl = useMemo(() => { @@ -61,15 +69,67 @@ const GradingForm = ({ type = 'add', initialValues }: GradingFormProps) => { // ===== DATA PROCESSING ===== // ===== FORM HANDLERS ===== - const { - deleteModal, - recordingFormErrorMessage, - isDeleteLoading, - createGradingHandler, - updateGradingHandler, - deleteRecordingClickHandler, - confirmationModalDeleteClickHandler, - } = useGradingFormHandlers(initialValues?.id); + const createGradingHandler = useCallback( + async (payload: CreateGradingPayload) => { + const res = (await RecordingApi.createGrading(payload)) as + | BaseApiResponse + | undefined; + + if (!res || isResponseError(res)) { + setGradingFormErrorMessage(res?.message || 'Failed to add Grading'); + return; + } + + toast.success(res?.message || 'Successfully added Grading!'); + router.push('/production/recording'); + }, + [router] + ); + + const updateGradingHandler = useCallback( + async (gradingId: number, payload: UpdateGradingPayload) => { + const res = (await RecordingApi.updateGrading(gradingId, payload)) as + | BaseApiResponse + | undefined; + + if (!res || isResponseError(res)) { + setGradingFormErrorMessage(res?.message || 'Failed to update Grading'); + return; + } + toast.success(res?.message || 'Successfully updated Grading!'); + router.refresh(); + router.push('/production/recording'); + }, + [router] + ); + + const deleteRecordingClickHandler = useCallback(() => { + deleteModal.openModal(); + }, [deleteModal]); + + const confirmationModalDeleteClickHandler = useCallback(async () => { + if (!initialValues?.id) return; + + setIsDeleteLoading(true); + try { + const res = (await RecordingApi.deleteGrading(initialValues.id)) as + | BaseApiResponse + | undefined; + + if (!res || isResponseError(res)) { + setGradingFormErrorMessage(res?.message || 'Failed to delete Grading'); + return; + } + deleteModal.closeModal(); + toast.success(res?.message || 'Successfully delete Grading!'); + router.push('/production/recording'); + } catch (err) { + console.error(err); + setGradingFormErrorMessage('Failed to delete Grading'); + } finally { + setIsDeleteLoading(false); + } + }, [deleteModal, initialValues?.id, router]); const formikInitialValues = useMemo(() => { let recordingEggId: number | undefined = initialValues?.id; @@ -599,14 +659,14 @@ const GradingForm = ({ type = 'add', initialValues }: GradingFormProps) => { )} - {recordingFormErrorMessage && ( + {gradingFormErrorMessage && (
- {recordingFormErrorMessage} + {gradingFormErrorMessage}
)} diff --git a/src/components/pages/production/recording/grading/form/useGradingFormHandlers.ts b/src/components/pages/production/recording/grading/form/useGradingFormHandlers.ts deleted file mode 100644 index 6cc5c8b0..00000000 --- a/src/components/pages/production/recording/grading/form/useGradingFormHandlers.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { useCallback, useState } from 'react'; -import { useRouter } from 'next/navigation'; -import { toast } from 'react-hot-toast'; -import { useModal } from '@/components/Modal'; -import { RecordingApi } from '@/services/api/production'; -import { - CreateGradingPayload, - UpdateGradingPayload, -} from '@/types/api/production/recording'; -import { isResponseError } from '@/lib/api-helper'; -import { type BaseApiResponse } from '@/types/api/api-general'; - -export const useGradingFormHandlers = (gradingId?: number) => { - const router = useRouter(); - const deleteModal = useModal(); - const [recordingFormErrorMessage, setRecordingFormErrorMessage] = - useState(''); - const [isDeleteLoading, setIsDeleteLoading] = useState(false); - - const createGradingHandler = useCallback( - async (payload: CreateGradingPayload) => { - const res = (await RecordingApi.createGrading(payload)) as - | BaseApiResponse - | undefined; - - if (!res || isResponseError(res)) { - setRecordingFormErrorMessage(res?.message || 'Failed to add Grading'); - return; - } - - toast.success(res?.message || 'Successfully added Grading!'); - router.push('/production/recording'); - }, - [router] - ); - - const updateGradingHandler = useCallback( - async (gradingId: number, payload: UpdateGradingPayload) => { - const res = (await RecordingApi.updateGrading(gradingId, payload)) as - | BaseApiResponse - | undefined; - - if (!res || isResponseError(res)) { - setRecordingFormErrorMessage( - res?.message || 'Failed to update Grading' - ); - return; - } - toast.success(res?.message || 'Successfully updated Grading!'); - router.refresh(); - router.push('/production/recording'); - }, - [router] - ); - - const deleteRecordingClickHandler = useCallback(() => { - deleteModal.openModal(); - }, [deleteModal]); - - const confirmationModalDeleteClickHandler = useCallback(async () => { - if (!gradingId) return; - - setIsDeleteLoading(true); - try { - const res = (await RecordingApi.deleteGrading(gradingId)) as - | BaseApiResponse - | undefined; - - if (!res || isResponseError(res)) { - setRecordingFormErrorMessage( - res?.message || 'Failed to delete Grading' - ); - return; - } - deleteModal.closeModal(); - toast.success(res?.message || 'Successfully delete Grading!'); - router.push('/production/recording'); - } catch (err) { - console.error(err); - setRecordingFormErrorMessage('Failed to delete Grading'); - } finally { - setIsDeleteLoading(false); - } - }, [deleteModal, gradingId, router]); - - return { - deleteModal, - recordingFormErrorMessage, - isDeleteLoading, - createGradingHandler, - updateGradingHandler, - deleteRecordingClickHandler, - confirmationModalDeleteClickHandler, - }; -};