refactor(FE-Storyless): remove UpdateMovementPayload type and related schema, streamline MovementForm handling

This commit is contained in:
rstubryan
2025-11-01 10:43:43 +07:00
parent 393f8a6d1b
commit f70433d901
5 changed files with 36 additions and 162 deletions
@@ -133,8 +133,6 @@ export const MovementFormSchema = Yup.object({
.required('Pengiriman wajib diisi!'),
});
export const UpdateMovementFormSchema = MovementFormSchema;
export type MovementFormValues = Yup.InferType<typeof MovementFormSchema>;
export const getMovementFormInitialValues = (
@@ -13,19 +13,19 @@ import {
CreateMovementPayload,
Movement,
} from '@/types/api/inventory/movement';
import { isResponseSuccess } from '@/lib/api-helper';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { useRouter } from 'next/navigation';
import {
MovementFormSchema,
MovementFormValues,
UpdateMovementFormSchema,
getMovementFormInitialValues,
ProductSchema,
DeliverySchema,
} from '@/components/pages/inventory/movement/form/MovementForm.schema';
import { useMovementFormHandlers } from './useMovementFormHandlers';
import { SupplierApi, WarehouseApi } from '@/services/api/master-data';
import { ProductWarehouseApi } from '@/services/api/inventory';
import { toast } from 'react-hot-toast';
import { MovementApi } from '@/services/api/inventory';
import FileInput from '@/components/input/FileInput';
import CheckboxInput from '@/components/input/CheckboxInput';
import Badge from '@/components/Badge';
@@ -36,8 +36,10 @@ interface MovementFormProps {
}
const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
const router = useRouter();
// ===== STATE MANAGEMENT =====
const [, setMovementFormErrorMessage] = useState('');
const [movementFormErrorMessage, setMovementFormErrorMessage] = useState('');
const [
productWarehouseSelectInputValue,
setProductWarehouseSelectInputValue,
@@ -49,11 +51,26 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
const [supplierSelectInputValue, setSupplierSelectInputValue] = useState('');
// ===== FORM HANDLERS =====
const {
movementFormErrorMessage,
createMovementHandler,
updateMovementHandler,
} = useMovementFormHandlers(initialValues?.id);
const createMovementHandler = useCallback(
async (payload: CreateMovementPayload, documents: File[] = []) => {
const formData = new FormData();
formData.append('data', JSON.stringify(payload));
documents.forEach((file, index) => {
formData.append(`documents[${index}]`, file);
});
const res = await MovementApi.create(
formData as unknown as CreateMovementPayload
);
if (isResponseError(res)) {
setMovementFormErrorMessage(res.message);
return;
}
toast.success(res?.message as string);
router.push('/inventory/movement');
},
[router]
);
// ===== INTERFACES =====
interface WarehouseOptionType extends OptionType {
@@ -139,8 +156,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
const formik = useFormik<MovementFormValues>({
initialValues: formikInitialValues,
validationSchema:
type === 'edit' ? UpdateMovementFormSchema : MovementFormSchema,
validationSchema: MovementFormSchema,
validateOnChange: true,
validateOnBlur: true,
validateOnMount: false,
@@ -148,7 +164,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
onSubmit: async (values) => {
setMovementFormErrorMessage('');
const documents: File[] = [];
const deliveriesPayload = values.deliveries.map((d, idx) => {
const deliveriesPayload = values.deliveries.map((d) => {
let documentIndex = 0;
if (d.document && d.document instanceof File) {
@@ -187,13 +203,6 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
case 'add':
await createMovementHandler(payload, documents);
break;
case 'edit':
await updateMovementHandler(
initialValues?.id as number,
payload,
documents
);
break;
}
},
});
@@ -1591,49 +1600,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
{/* Action buttons */}
<div className='flex flex-row justify-between gap-2 flex-wrap'>
{type !== 'add' && (
<div className='flex flex-row justify-start gap-2'>
<Button
type='button'
color='error'
onClick={() => console.log('Delete clicked')}
className='px-4'
>
<Icon
icon='material-symbols:delete-outline-rounded'
width={24}
height={24}
className='justify-start text-sm'
/>
Delete
</Button>
{type !== 'edit' && (
<Button
type='button'
color='warning'
href={`/inventory/movement/detail/edit/?movementId=${initialValues?.id}`}
className='px-4'
>
<Icon
icon='material-symbols:edit-outline'
width={24}
height={24}
className='justify-start text-sm'
/>
Edit
</Button>
)}
</div>
)}
{type !== 'detail' && (
<div className='flex flex-row justify-end gap-2 w-full'>
<Button
type='reset'
color='warning'
className='px-4'
>
<Button type='reset' color='warning' className='px-4'>
Reset
</Button>
@@ -1642,7 +1611,12 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
color='primary'
className='px-4'
isLoading={formik.isSubmitting}
disabled={hasInvalidQty || hasExceededStock || !formik.isValid || formik.isSubmitting}
disabled={
hasInvalidQty ||
hasExceededStock ||
!formik.isValid ||
formik.isSubmitting
}
>
Submit
</Button>
@@ -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 { MovementApi } from '@/services/api/inventory';
import {
CreateMovementPayload,
UpdateMovementPayload,
} from '@/types/api/inventory/movement';
import { isResponseError } from '@/lib/api-helper';
export const useMovementFormHandlers = (initialValuesId?: number) => {
const router = useRouter();
const deleteModal = useModal();
const [movementFormErrorMessage, setMovementFormErrorMessage] = useState('');
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const createMovementHandler = useCallback(
async (payload: CreateMovementPayload, documents: File[] = []) => {
const formData = new FormData();
formData.append('data', JSON.stringify(payload));
documents.forEach((file, index) => {
formData.append(`documents[${index}]`, file);
});
const res = await MovementApi.create(
formData as unknown as CreateMovementPayload
);
if (isResponseError(res)) {
setMovementFormErrorMessage(res.message);
return;
}
toast.success(res?.message as string);
router.push('/inventory/movement');
},
[router]
);
const updateMovementHandler = useCallback(
async (
movementId: number,
payload: UpdateMovementPayload,
documents: File[] = []
) => {
let finalPayload: UpdateMovementPayload | FormData;
if (documents.length > 0) {
const formData = new FormData();
formData.append('data', JSON.stringify(payload));
documents.forEach((file, index) => {
formData.append(`documents[${index}]`, file);
});
finalPayload = formData as unknown as UpdateMovementPayload;
} else {
finalPayload = payload;
}
const res = await MovementApi.update(movementId, finalPayload);
if (res?.status === 'error') {
setMovementFormErrorMessage(res.message);
return;
}
toast.success(res?.message as string);
router.refresh();
router.push('/inventory/movement');
},
[router]
);
const deleteMovementClickHandler = useCallback(() => {
deleteModal.openModal();
}, [deleteModal]);
const confirmationModalDeleteClickHandler = useCallback(async () => {
if (!initialValuesId) return;
setIsDeleteLoading(true);
await MovementApi.delete(initialValuesId);
deleteModal.closeModal();
toast.success('Successfully delete Movement!');
setIsDeleteLoading(false);
router.push('/inventory/movement');
}, [deleteModal, initialValuesId, router]);
return {
deleteModal,
movementFormErrorMessage,
isDeleteLoading,
createMovementHandler,
updateMovementHandler,
deleteMovementClickHandler,
confirmationModalDeleteClickHandler,
};
};