refactor(FE-87-106): refactor api integration untuk project flock dan project flock kandang

This commit is contained in:
randy-ar
2025-11-10 04:08:08 +07:00
parent fcc2fced06
commit e0c347c3d5
19 changed files with 961 additions and 506 deletions
@@ -1,3 +1,37 @@
import * as Yup from 'yup';
import { Product } from '@/types/api/master-data/product';
import { Supplier } from '@/types/api/master-data/supplier';
type ChickinRequestSchemaType = {
chick_in_date: string;
note?: string | undefined | null;
product_warehouse_id: number;
};
type ChickinSchemaType = {
project_flock_kandang_id: number;
chickin_requests: ChickinRequestSchemaType[];
};
export const ChickinRequestSchema: Yup.ObjectSchema<ChickinRequestSchemaType> =
Yup.object({
chick_in_date: Yup.string().nullable().required('Tanggal wajib diisi!'),
note: Yup.string().nullable(),
product_warehouse_id: Yup.number()
.min(1, 'Produk wajib diisi!')
.required('Produk wajib diisi!'),
});
export const ChickinSchema: Yup.ObjectSchema<ChickinSchemaType> = Yup.object({
project_flock_kandang_id: Yup.number()
.min(1, 'Project Flock Kandang wajib diisi!')
.required('Project Flock Kandang wajib diisi!'),
chickin_requests: Yup.array()
.of(ChickinRequestSchema)
.min(1, 'Minimal harus ada 1 produk!')
.required('Produk wajib diisi!'),
});
export type ChickinRequestFormValues = Yup.InferType<
typeof ChickinRequestSchema
>;
export type ChickinFormValues = Yup.InferType<typeof ChickinSchema>;
@@ -6,7 +6,7 @@ import { FormHeader } from '@/components/helper/form/FormHeader';
import DateInput from '@/components/input/DateInput';
import FileInput from '@/components/input/FileInput';
import NumberInput from '@/components/input/NumberInput';
import SelectInput from '@/components/input/SelectInput';
import SelectInput, { OptionType } from '@/components/input/SelectInput';
import TextInput from '@/components/input/TextInput';
import Table from '@/components/Table';
import { formatNumber } from '@/lib/helper';
@@ -15,8 +15,19 @@ import {
AvailableQty,
ProjectFlockKandang,
} from '@/types/api/production/project-flock-kandang';
import { useFormik } from 'formik';
import {
ChickinFormValues,
ChickinRequestFormValues,
ChickinSchema,
} from './ChickinForm.schema';
import { useCallback, useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import { CreateChickinPayload } from '@/types/api/production/chickin';
import { ChickinApi } from '@/services/api/production';
import { isResponseError } from '@/lib/api-helper';
import toast from 'react-hot-toast';
import { flushSync } from 'react-dom';
const ChickinFormKandang = ({
formType = 'add',
initialValues,
@@ -27,199 +38,263 @@ const ChickinFormKandang = ({
afterSubmit?: () => void;
}) => {
const router = useRouter();
const [chickinErrorMessage, setChickinErrorMessage] = useState('');
const createChickin = useCallback(
async (payload: CreateChickinPayload) => {
const createChickinRes = await ChickinApi.create(payload);
if (isResponseError(createChickinRes)) {
setChickinErrorMessage(createChickinRes.message);
return;
}
toast.success(createChickinRes?.message as string);
router.push(
`/production/project-flock/chickin/add?projectFlockId=${initialValues?.project_flock?.id}`
);
},
[router]
);
const handleReset = async () => {
flushSync(() => {
formik.resetForm({
values: {
project_flock_kandang_id: initialValues?.id,
chickin_requests: initialValues?.available_qtys
? initialValues.available_qtys.map((availableQty) => ({
chick_in_date: '',
product_warehouse_id: availableQty.product_warehouse.id,
available_qty: availableQty.available_qty,
note: `Chickin project-flock-kandang-${initialValues?.id} product-warehouse-${availableQty.product_warehouse.id}`,
}))
: [],
},
});
});
formik.setTouched({
chickin_requests: initialValues?.available_qtys?.map(() => ({
chick_in_date: true,
})),
});
const errors = await formik.validateForm();
formik.setErrors(errors);
};
const formik = useFormik<ChickinFormValues>({
enableReinitialize: true,
validationSchema: ChickinSchema,
initialValues: {
project_flock_kandang_id: initialValues?.id,
chickin_requests: initialValues?.available_qtys
? initialValues.available_qtys.map((availableQty) => ({
chick_in_date: '',
product_warehouse_id: availableQty.product_warehouse.id,
available_qty: availableQty.available_qty,
note: `Chickin project-flock-kandang-${initialValues?.id} product-warehouse-${availableQty.product_warehouse.id}`,
}))
: [],
},
onSubmit: (values) => {
setChickinErrorMessage('');
createChickin(values as CreateChickinPayload);
if (afterSubmit) {
afterSubmit();
}
},
});
const { setValues: formikSetValues } = formik;
useEffect(() => {
formikSetValues({
project_flock_kandang_id: initialValues?.id,
chickin_requests: initialValues?.available_qtys
? initialValues.available_qtys.map((availableQty) => ({
chick_in_date: '',
product_warehouse_id: availableQty.product_warehouse.id,
available_qty: availableQty.available_qty,
note: `Chickin project-flock-kandang-${initialValues?.id} product-warehouse-${availableQty.product_warehouse.id}`,
}))
: [],
});
}, [formikSetValues, initialValues]);
return (
<div className='flex flex-col gap-4'>
<FormHeader
type='add'
title='Chick In DOC'
backUrl={`/production/project-flock/chickin/add?projectFlockId=${initialValues.project_flock.id}`}
backUrl={`/production/project-flock/chickin/add?projectFlockId=${initialValues?.project_flock?.id}`}
/>
<Card
title='Informasi Kandang'
className={{
wrapper: 'w-full bg-white mt-4',
<form
className='flex flex-col gap-4'
onReset={(e) => {
handleReset();
}}
onSubmit={formik.handleSubmit}
>
<Table<Kandang>
emptyContent={
<div className='w-full p-5 text-center'>
<span className='text-lg opacity-50'>
Informasi Kandang belum tersedia...
</span>
</div>
}
data={[initialValues.kandang]}
columns={[
{
header: 'Area',
accessorFn: () => initialValues.project_flock?.area.name || '-',
},
{
header: 'Lokasi',
accessorFn: () =>
initialValues.project_flock?.location.name || '-',
},
{
header: 'Flock',
accessorFn: () => initialValues.project_flock?.flock.name || '-',
},
{
header: 'Kandang',
accessorFn: (row) => row?.name || '-',
},
{
header: 'Kapasitas',
accessorFn: (row) =>
(row?.capacity && formatNumber(row?.capacity)) || '-',
},
{
header: 'Penanggung Jawab',
accessorFn: (row) => row?.pic?.name || '-',
},
]}
<Card
title='Informasi Kandang'
className={{
tableWrapperClassName: 'overflow-x-auto min-h-full!',
tableClassName: 'font-inter w-full table-auto min-h-full!',
headerRowClassName: 'border-b border-b-gray-200',
headerColumnClassName:
'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end',
bodyRowClassName: 'border-b border-b-gray-200',
bodyColumnClassName:
'px-6 py-3 last:flex last:flex-row last:justify-end',
paginationClassName: 'hidden',
wrapper: 'w-full bg-white mt-4',
}}
/>
</Card>
<Card
title='Informasi Chick In DOC'
className={{
wrapper: 'w-full bg-white',
}}
>
<Table<AvailableQty>
data={initialValues.available_qtys || []}
columns={[
{
accessorFn: (row) => row.chick_in_date,
header: 'Tanggal Chick In',
cell(props) {
return (
<DateInput
name='chick_in_date[]'
value={props.row.original.chick_in_date}
onChange={(e) => {
props.row.original.chick_in_date = e.target.value;
}}
/>
);
>
<Table<Kandang>
emptyContent={
<div className='w-full p-5 text-center'>
<span className='text-lg opacity-50'>
Informasi Kandang belum tersedia...
</span>
</div>
}
data={[initialValues?.kandang]}
columns={[
{
header: 'Area',
accessorFn: () =>
initialValues?.project_flock?.area.name || '-',
},
},
{
accessorFn: (row) => row.po_number,
header: 'No. Surat Jalan',
cell(props) {
return (
<TextInput
name='po_number[]'
value={props.row.original.po_number}
onChange={(e) => {
props.row.original.po_number = e.target.value;
}}
/>
);
{
header: 'Lokasi',
accessorFn: () =>
initialValues?.project_flock?.location.name || '-',
},
},
{
header: 'Dokumen Surat Jalan',
cell(props) {
return (
<FileInput
name='document_path[]'
onChange={(e) => {
props.row.original.document_path = e.target.value;
}}
/>
);
{
header: 'Flock',
accessorFn: () =>
initialValues?.project_flock?.flock_name || '-',
},
},
{
accessorFn: (row) => row.supplier?.name,
header: 'Supplier',
cell(props) {
return (
<SelectInput
value={
props.row.original.supplier?.name &&
props.row.original.supplier?.id
? {
label: props.row.original.supplier.name,
value: props.row.original.supplier.id,
}
: undefined
}
options={[]}
isDisabled
/>
);
{
header: 'Kandang',
accessorFn: (row) => row?.name || '-',
},
},
{
accessorFn: (row) => row.product_warehouse.product.name,
header: 'Produk',
cell(props) {
return (
<SelectInput
value={{
label: props.row.original.product_warehouse.product.name,
value: props.row.original.product_warehouse.product.id,
}}
options={[]}
isDisabled
/>
);
{
header: 'Kapasitas',
accessorFn: (row) =>
(row?.capacity && formatNumber(row?.capacity)) || '-',
},
},
{
accessorFn: (row) => row.product_warehouse.quantity,
header: 'Jumlah (ekor)',
cell(props) {
return (
<NumberInput
name='qty[]'
value={props.row.original.product_warehouse.quantity}
/>
);
{
header: 'Penanggung Jawab',
accessorFn: (row) => row?.pic?.name || '-',
},
},
]}
]}
className={{
tableWrapperClassName: 'overflow-x-auto min-h-full!',
tableClassName: 'font-inter w-full table-auto min-h-full!',
headerRowClassName: 'border-b border-b-gray-200',
headerColumnClassName:
'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end',
bodyRowClassName: 'border-b border-b-gray-200',
bodyColumnClassName:
'px-6 py-3 last:flex last:flex-row last:justify-end',
paginationClassName: 'hidden',
}}
/>
</Card>
<Card
title='Informasi Chick In DOC'
className={{
tableWrapperClassName: 'overflow-x-auto min-h-full!',
tableClassName: 'font-inter w-full table-auto min-h-full!',
headerRowClassName: 'border-b border-b-gray-200',
headerColumnClassName:
'px-2 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end',
bodyRowClassName: 'border-b border-b-gray-200',
bodyColumnClassName:
'px-2 py-2 last:flex last:flex-row last:justify-end',
paginationClassName: 'hidden',
wrapper: 'w-full bg-white',
}}
emptyContent={
<div className='w-full p-5 text-center'>
<span className='text-lg opacity-50'>
Isi persediaan DOC untuk kandang belum tersedia...
</span>
</div>
}
/>
</Card>
<div className='flex flex-row justify-center gap-3'>
<Button type='reset' color='warning'>
Reset
</Button>
<Button type='submit' color='primary'>
Submit
</Button>
</div>
>
<Table<ChickinRequestFormValues>
data={formik.values.chickin_requests || []}
columns={[
{
accessorFn: (row) => row.chick_in_date,
header: 'Tanggal Chick In',
cell(props) {
return (
<DateInput
name={`chickin_requests[${props.row.index}].chick_in_date`}
value={
formik.values.chickin_requests[props.row.index]
?.chick_in_date as string
}
onChange={formik.handleChange}
/>
);
},
},
{
accessorFn: (row) => row.product_warehouse_id,
header: 'Produk',
cell(props) {
const availableQty = initialValues?.available_qtys?.find(
(availableQty) =>
availableQty.product_warehouse.id ===
props.row.original.product_warehouse_id
);
return (
<SelectInput
value={
{
label: availableQty?.product_warehouse?.product?.name,
value: availableQty?.product_warehouse?.product?.id,
} as OptionType
}
options={[]}
isDisabled
/>
);
},
},
{
accessorFn: (row) => row.product_warehouse_id,
header: 'Jumlah (ekor)',
cell(props) {
const availableQty = initialValues?.available_qtys?.find(
(availableQty) =>
availableQty.product_warehouse.id ===
props.row.original.product_warehouse_id
);
return (
<NumberInput
name='qty[]'
value={availableQty?.available_qty}
/>
);
},
},
]}
className={{
tableWrapperClassName: 'overflow-x-auto min-h-full!',
tableClassName: 'font-inter w-full table-auto min-h-full!',
headerRowClassName: 'border-b border-b-gray-200',
headerColumnClassName:
'px-2 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end',
bodyRowClassName: 'border-b border-b-gray-200',
bodyColumnClassName:
'px-2 py-2 last:flex last:flex-row last:justify-end',
paginationClassName: 'hidden',
}}
emptyContent={
<div className='w-full p-5 text-center'>
<span className='text-lg opacity-50'>
Isi persediaan DOC untuk kandang belum tersedia...
</span>
</div>
}
/>
</Card>
{JSON.stringify(formik.values)}
<div className='flex flex-row justify-center gap-3'>
<Button type='reset' color='warning' disabled={formik.isSubmitting}>
Reset
</Button>
<Button
type='submit'
color='primary'
disabled={!formik.isValid || formik.isSubmitting}
>
Submit
</Button>
</div>
{JSON.stringify(formik.errors)}
</form>
</div>
);
};