refactor(FE-174): enhance GradingForm and RecordingForm with improved error handling and modal integration for delete actions

This commit is contained in:
rstubryan
2025-11-03 10:22:35 +07:00
parent 2ba23654ce
commit e9e8ad771e
4 changed files with 126 additions and 191 deletions
@@ -31,11 +31,10 @@ import {
UpdateRecordingGrowingFormSchema, UpdateRecordingGrowingFormSchema,
UpdateRecordingLayingFormSchema, UpdateRecordingLayingFormSchema,
} from './RecordingForm.schema'; } from './RecordingForm.schema';
import { useRecordingFormHandlers } from './useRecordingFormHandlers';
import { ProjectFlockApi } from '@/services/api/production'; import { ProjectFlockApi } from '@/services/api/production';
import { LocationApi } from '@/services/api/master-data'; import { LocationApi } from '@/services/api/master-data';
import { ProductWarehouseApi } from '@/services/api/inventory'; 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 { cn } from '@/lib/helper';
import { ProjectFlockKandangLookup } from '@/types/api/production/project-flock'; import { ProjectFlockKandangLookup } from '@/types/api/production/project-flock';
import { useModal } from '@/components/Modal'; import { useModal } from '@/components/Modal';
@@ -77,9 +76,61 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
const [isApproveLoading, setIsApproveLoading] = useState(false); const [isApproveLoading, setIsApproveLoading] = useState(false);
const [isRejectLoading, setIsRejectLoading] = useState(false); const [isRejectLoading, setIsRejectLoading] = useState(false);
const [formSteps, setFormSteps] = useState<FormStepStatus[] | null>(null); const [formSteps, setFormSteps] = useState<FormStepStatus[] | null>(null);
const [recordingFormErrorMessage, setRecordingFormErrorMessage] =
useState('');
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const approveModal = useModal(); const approveModal = useModal();
const rejectModal = 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 ===== // ===== API DATA FETCHING =====
const locationsUrl = `${LocationApi.basePath}?${new URLSearchParams({ const locationsUrl = `${LocationApi.basePath}?${new URLSearchParams({
@@ -336,17 +387,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
return options; return options;
}, [eggProductsData]); }, [eggProductsData]);
// ===== FORM HANDLERS =====
const {
deleteModal,
recordingFormErrorMessage,
isDeleteLoading,
createRecordingHandler,
updateRecordingHandler,
deleteRecordingClickHandler,
confirmationModalDeleteClickHandler,
} = useRecordingFormHandlers(initialValues?.id);
const isLayingCategory = const isLayingCategory =
projectFlockKandangLookup?.project_flock?.category === 'LAYING'; projectFlockKandangLookup?.project_flock?.category === 'LAYING';
@@ -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,
};
};
@@ -17,7 +17,10 @@ import {
RecordingEgg, RecordingEgg,
GradingEgg, GradingEgg,
} from '@/types/api/production/recording'; } from '@/types/api/production/recording';
import { type FormStepStatus } from '@/types/api/api-general'; import {
type FormStepStatus,
type BaseApiResponse,
} from '@/types/api/api-general';
import { import {
RecordingGradingFormSchema, RecordingGradingFormSchema,
RecordingGradingFormValues, RecordingGradingFormValues,
@@ -26,10 +29,12 @@ import {
} from '../../form/RecordingForm.schema'; } from '../../form/RecordingForm.schema';
import { cn } from '@/lib/helper'; import { cn } from '@/lib/helper';
import toast from 'react-hot-toast'; 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 Card from '@/components/Card';
import StepItem from '@/components/steps/StepItem'; import StepItem from '@/components/steps/StepItem';
import { useGradingFormHandlers } from './useGradingFormHandlers';
interface GradingFormProps { interface GradingFormProps {
type?: 'add' | 'edit' | 'detail'; type?: 'add' | 'edit' | 'detail';
@@ -45,6 +50,9 @@ const GradingForm = ({ type = 'add', initialValues }: GradingFormProps) => {
); );
const [formSteps, setFormSteps] = useState<FormStepStatus[] | null>(null); const [formSteps, setFormSteps] = useState<FormStepStatus[] | null>(null);
const [gradingFormErrorMessage, setGradingFormErrorMessage] = useState('');
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const deleteModal = useModal();
// ===== API DATA FETCHING ===== // ===== API DATA FETCHING =====
// const existingGradingsUrl = useMemo(() => { // const existingGradingsUrl = useMemo(() => {
@@ -61,15 +69,67 @@ const GradingForm = ({ type = 'add', initialValues }: GradingFormProps) => {
// ===== DATA PROCESSING ===== // ===== DATA PROCESSING =====
// ===== FORM HANDLERS ===== // ===== FORM HANDLERS =====
const { const createGradingHandler = useCallback(
deleteModal, async (payload: CreateGradingPayload) => {
recordingFormErrorMessage, const res = (await RecordingApi.createGrading(payload)) as
isDeleteLoading, | BaseApiResponse<unknown>
createGradingHandler, | undefined;
updateGradingHandler,
deleteRecordingClickHandler, if (!res || isResponseError(res)) {
confirmationModalDeleteClickHandler, setGradingFormErrorMessage(res?.message || 'Failed to add Grading');
} = useGradingFormHandlers(initialValues?.id); 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<unknown>
| 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<unknown>
| 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(() => { const formikInitialValues = useMemo(() => {
let recordingEggId: number | undefined = initialValues?.id; let recordingEggId: number | undefined = initialValues?.id;
@@ -599,14 +659,14 @@ const GradingForm = ({ type = 'add', initialValues }: GradingFormProps) => {
</div> </div>
)} )}
</div> </div>
{recordingFormErrorMessage && ( {gradingFormErrorMessage && (
<div role='alert' className='alert alert-error'> <div role='alert' className='alert alert-error'>
<Icon <Icon
icon='material-symbols:error-outline' icon='material-symbols:error-outline'
width={24} width={24}
height={24} height={24}
/> />
<span>{recordingFormErrorMessage}</span> <span>{gradingFormErrorMessage}</span>
</div> </div>
)} )}
</form> </form>
@@ -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<unknown>
| 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<unknown>
| 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<unknown>
| 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,
};
};