feat(FE-170): add egg handling and validation to daily recording form

This commit is contained in:
rstubryan
2025-10-31 00:02:04 +07:00
parent 0e77597a70
commit 59b0eeea2b
@@ -15,14 +15,21 @@ import { FormActions } from '@/components/helper/form/FormActions';
import { RecordingApi } from '@/services/api/production'; import { RecordingApi } from '@/services/api/production';
import { import {
CreateGrowingRecordingPayload, CreateGrowingRecordingPayload,
CreateLayingRecordingPayload,
UpdateGrowingRecordingPayload,
UpdateLayingRecordingPayload,
Recording, Recording,
} from '@/types/api/production/recording'; } from '@/types/api/production/recording';
import { type BaseApiResponse } from '@/types/api/api-general'; import { type BaseApiResponse } from '@/types/api/api-general';
import { import {
RecordingGrowingFormSchema, RecordingGrowingFormSchema,
RecordingLayingFormSchema,
RecordingGrowingFormValues, RecordingGrowingFormValues,
RecordingLayingFormValues,
getRecordingGrowingFormInitialValues, getRecordingGrowingFormInitialValues,
getRecordingLayingFormInitialValues,
UpdateRecordingGrowingFormSchema, UpdateRecordingGrowingFormSchema,
UpdateRecordingLayingFormSchema,
} from './RecordingForm.schema'; } from './RecordingForm.schema';
import { useRecordingFormHandlers } from './useRecordingFormHandlers'; import { useRecordingFormHandlers } from './useRecordingFormHandlers';
import { ProjectFlockApi } from '@/services/api/production'; import { ProjectFlockApi } from '@/services/api/production';
@@ -49,6 +56,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
const [selectedBodyWeights, setSelectedBodyWeights] = useState<number[]>([]); const [selectedBodyWeights, setSelectedBodyWeights] = useState<number[]>([]);
const [selectedStocks, setSelectedStocks] = useState<number[]>([]); const [selectedStocks, setSelectedStocks] = useState<number[]>([]);
const [selectedDepletions, setSelectedDepletions] = useState<number[]>([]); const [selectedDepletions, setSelectedDepletions] = useState<number[]>([]);
const [selectedEggs, setSelectedEggs] = useState<number[]>([]);
const [editingAverageIndex] = useState<number | null>(null); const [editingAverageIndex] = useState<number | null>(null);
const [manuallyEditedRows, setManuallyEditedRows] = useState<Set<number>>( const [manuallyEditedRows, setManuallyEditedRows] = useState<Set<number>>(
@@ -219,6 +227,20 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
const { data: depletionProductsData, isLoading: isLoadingDepletionProducts } = const { data: depletionProductsData, isLoading: isLoadingDepletionProducts } =
useSWR(depletionProductsUrl, ProductWarehouseApi.getAllFetcher); useSWR(depletionProductsUrl, ProductWarehouseApi.getAllFetcher);
const eggProductsUrl = useMemo(() => {
if (!selectedLocation) return null;
const params = new URLSearchParams({
search: '',
location_id: selectedLocation.value.toString(),
});
return `${ProductWarehouseApi.basePath}?${params.toString()}`;
}, [selectedLocation]);
const { data: eggProductsData, isLoading: isLoadingEggProducts } = useSWR(
eggProductsUrl,
ProductWarehouseApi.getAllFetcher
);
// ===== DATA PROCESSING ===== // ===== DATA PROCESSING =====
const locationOptions = useMemo(() => { const locationOptions = useMemo(() => {
if (!isResponseSuccess(locations)) return []; if (!isResponseSuccess(locations)) return [];
@@ -358,6 +380,27 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
return options; return options;
}, [depletionProductsData]); }, [depletionProductsData]);
const eggProducts = useMemo(() => {
const options: OptionType[] = [];
if (isResponseSuccess(eggProductsData)) {
eggProductsData.data.forEach((product) => {
const productName = product.product.name;
if (
productName.toLowerCase().includes('telur') ||
productName.toLowerCase().includes('egg')
) {
options.push({
value: product.id,
label: product.product.name,
});
}
});
}
return options;
}, [eggProductsData]);
// ===== FORM HANDLERS ===== // ===== FORM HANDLERS =====
const { const {
deleteModal, deleteModal,
@@ -369,46 +412,109 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
confirmationModalDeleteClickHandler, confirmationModalDeleteClickHandler,
} = useRecordingFormHandlers(initialValues?.id); } = useRecordingFormHandlers(initialValues?.id);
const formikInitialValues = useMemo<RecordingGrowingFormValues>( const isLayingCategory =
() => getRecordingGrowingFormInitialValues(initialValues), projectFlockKandangLookup?.project_flock?.category === 'LAYING';
[initialValues]
);
const formik = useFormik<RecordingGrowingFormValues>({ const formikInitialValues = useMemo(() => {
if (isLayingCategory) {
return getRecordingLayingFormInitialValues(
initialValues
) as RecordingLayingFormValues;
}
return getRecordingGrowingFormInitialValues(initialValues);
}, [initialValues, isLayingCategory]);
const formik = useFormik<
RecordingGrowingFormValues | RecordingLayingFormValues
>({
initialValues: formikInitialValues, initialValues: formikInitialValues,
validationSchema: validationSchema: (() => {
type === 'edit' if (isLayingCategory) {
return type === 'edit'
? UpdateRecordingLayingFormSchema
: RecordingLayingFormSchema;
}
return type === 'edit'
? UpdateRecordingGrowingFormSchema ? UpdateRecordingGrowingFormSchema
: RecordingGrowingFormSchema, : RecordingGrowingFormSchema;
})(),
validateOnChange: true, validateOnChange: true,
validateOnBlur: true, validateOnBlur: true,
onSubmit: async (values) => { onSubmit: async (values) => {
const payload: CreateGrowingRecordingPayload = { if (isLayingCategory) {
project_flock_kandangs_id: values.project_flock_kandangs_id, const layingValues = values as RecordingLayingFormValues;
body_weights: (values.body_weights ?? []).map((bw) => ({
avg_weight:
typeof bw.avg_weight === 'number'
? bw.avg_weight
: parseFloat(String(bw.avg_weight)) || 0,
qty: bw.qty || 0,
})),
stocks: (values.stocks ?? []).map((stock) => ({
product_warehouse_id: stock.product_warehouse_id,
usage_qty: stock.usage_qty || 0,
})),
depletions: (values.depletions ?? []).map((depletion) => ({
product_warehouse_id: depletion.product_warehouse_id,
qty: depletion.qty || 0,
})),
};
switch (type) { const layingPayload = {
case 'add': project_flock_kandangs_id: layingValues.project_flock_kandangs_id,
await createRecordingHandler(payload); body_weights: (layingValues.body_weights ?? []).map((bw) => ({
break; avg_weight:
case 'edit': typeof bw.avg_weight === 'number'
await updateRecordingHandler(initialValues?.id as number, payload); ? bw.avg_weight
break; : parseFloat(String(bw.avg_weight)) || 0,
qty: bw.qty || 0,
})),
stocks: (layingValues.stocks ?? []).map((stock) => ({
product_warehouse_id: stock.product_warehouse_id,
usage_qty: stock.usage_qty || 0,
})),
depletions: (layingValues.depletions ?? []).map((depletion) => ({
product_warehouse_id: depletion.product_warehouse_id,
qty: depletion.qty || 0,
})),
eggs: (layingValues.eggs ?? []).map((egg) => ({
product_warehouse_id: egg.product_warehouse_id,
qty: egg.qty || 0,
})),
};
switch (type) {
case 'add':
await createRecordingHandler(
layingPayload as CreateLayingRecordingPayload
);
break;
case 'edit':
await updateRecordingHandler(
initialValues?.id as number,
layingPayload as UpdateLayingRecordingPayload
);
break;
}
} else {
const growingValues = values as RecordingGrowingFormValues;
const growingPayload = {
project_flock_kandangs_id: growingValues.project_flock_kandangs_id,
body_weights: (growingValues.body_weights ?? []).map((bw) => ({
avg_weight:
typeof bw.avg_weight === 'number'
? bw.avg_weight
: parseFloat(String(bw.avg_weight)) || 0,
qty: bw.qty || 0,
})),
stocks: (growingValues.stocks ?? []).map((stock) => ({
product_warehouse_id: stock.product_warehouse_id,
usage_qty: stock.usage_qty || 0,
})),
depletions: (growingValues.depletions ?? []).map((depletion) => ({
product_warehouse_id: depletion.product_warehouse_id,
qty: depletion.qty || 0,
})),
};
switch (type) {
case 'add':
await createRecordingHandler(
growingPayload as CreateGrowingRecordingPayload
);
break;
case 'edit':
await updateRecordingHandler(
initialValues?.id as number,
growingPayload as UpdateGrowingRecordingPayload
);
break;
}
} }
}, },
}); });
@@ -436,7 +542,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
const getAvailableStock = useCallback( const getAvailableStock = useCallback(
(productWarehouseId: number) => { (productWarehouseId: number) => {
if (type === 'detail') return 0; if ((type as 'add' | 'edit' | 'detail') === 'detail') return 0;
if (!isResponseSuccess(stockProducts)) return 0; if (!isResponseSuccess(stockProducts)) return 0;
const productWarehouse = stockProducts.data.find( const productWarehouse = stockProducts.data.find(
(pw) => pw.id === productWarehouseId (pw) => pw.id === productWarehouseId
@@ -448,7 +554,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
const getStockUsageError = useCallback( const getStockUsageError = useCallback(
(stockIdx: number) => { (stockIdx: number) => {
if (type === 'detail') return null; if ((type as 'add' | 'edit' | 'detail') === 'detail') return null;
const stock = formik.values.stocks?.[stockIdx]; const stock = formik.values.stocks?.[stockIdx];
if (!stock || !stock.product_warehouse_id) return null; if (!stock || !stock.product_warehouse_id) return null;
const availableStock = getAvailableStock(stock.product_warehouse_id); const availableStock = getAvailableStock(stock.product_warehouse_id);
@@ -463,7 +569,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
const getStockUsageAdornment = useCallback( const getStockUsageAdornment = useCallback(
(stockIdx: number) => { (stockIdx: number) => {
if (type === 'detail') return null; if ((type as 'add' | 'edit' | 'detail') === 'detail') return null;
const stock = formik.values.stocks?.[stockIdx]; const stock = formik.values.stocks?.[stockIdx];
if (!stock || !stock.product_warehouse_id) return null; if (!stock || !stock.product_warehouse_id) return null;
const availableStock = getAvailableStock(stock.product_warehouse_id); const availableStock = getAvailableStock(stock.product_warehouse_id);
@@ -566,7 +672,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
); );
const hasExceededStock = useMemo(() => { const hasExceededStock = useMemo(() => {
if (type === 'detail') return false; if ((type as 'add' | 'edit' | 'detail') === 'detail') return false;
return ( return (
formik.values.stocks?.some((stock, idx) => { formik.values.stocks?.some((stock, idx) => {
return getStockUsageError(idx) !== null; return getStockUsageError(idx) !== null;
@@ -574,40 +680,35 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
); );
}, [formik.values.stocks, getStockUsageError, type]); }, [formik.values.stocks, getStockUsageError, type]);
const isRepeaterInputError = < const isRepeaterInputError = (
T extends 'body_weights' | 'stocks' | 'depletions', arrayName: 'body_weights' | 'stocks' | 'depletions' | 'eggs',
>( column: string,
arrayName: T,
column: T extends 'body_weights'
? keyof RecordingGrowingFormValues['body_weights'][0]
: T extends 'stocks'
? keyof RecordingGrowingFormValues['stocks'][0]
: T extends 'depletions'
? keyof RecordingGrowingFormValues['depletions'][0]
: never,
idx: number idx: number
) => { ) => {
if ( const touched = formik.touched as Record<string, unknown>;
!formik.touched[arrayName] || const errors = formik.errors as Record<string, unknown>;
!Array.isArray(formik.touched[arrayName])
) { if (!touched[arrayName] || !Array.isArray(touched[arrayName])) {
return { return {
isError: false, isError: false,
errorMessage: '', errorMessage: '',
}; };
} }
const touchedField = formik.touched[arrayName]?.[idx]?.[column as string]; const touchedField = (touched[arrayName] as unknown[])?.[idx] as Record<
const errorField = formik.errors[arrayName]?.[idx] as Record<
string, string,
string unknown
>;
const errorField = (errors[arrayName] as unknown[])?.[idx] as Record<
string,
unknown
>; >;
return { return {
isError: touchedField && Boolean(errorField?.[column as string]), isError: touchedField && Boolean(errorField?.[column]),
errorMessage: errorMessage:
touchedField && errorField?.[column as string] touchedField && errorField?.[column]
? errorField[column as string] ? (errorField[column] as string)
: '', : '',
}; };
}; };
@@ -899,7 +1000,51 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
setSelectedDepletions([]); setSelectedDepletions([]);
}; };
// Eggs Handlers
const addEgg = () => {
const newEggs = [
...((formik.values as RecordingLayingFormValues).eggs || []),
{
product_warehouse_id: 0,
qty: 0,
},
];
formik.setFieldValue('eggs', newEggs);
};
const handleEggQtyChangeWrapper = useCallback(
(idx: number) => (e: React.ChangeEvent<HTMLInputElement>) => {
const value = parseFloat(e.target.value) || 0;
formik.setFieldValue(`eggs.${idx}.qty`, value);
},
[formik]
);
const removeEgg = (idx: number) => {
const updatedEggs = (
formik.values as RecordingLayingFormValues
).eggs?.filter((_, i) => i !== idx);
formik.setFieldValue('eggs', updatedEggs);
};
const removeSelectedEggs = () => {
const updatedEggs = (
formik.values as RecordingLayingFormValues
).eggs?.filter((_, idx) => !selectedEggs.includes(idx));
formik.setFieldValue('eggs', updatedEggs);
setSelectedEggs([]);
};
// ===== EFFECTS ===== // ===== EFFECTS =====
useEffect(() => {
if (isLayingCategory && (type as 'add' | 'edit' | 'detail') !== 'detail') {
const layingValues = formik.values as RecordingLayingFormValues;
if (!layingValues.eggs || layingValues.eggs.length === 0) {
formik.setFieldValue('eggs', [{ product_warehouse_id: 0, qty: 0 }]);
}
}
}, [isLayingCategory, type, formik]);
useEffect(() => { useEffect(() => {
if (formik.values.body_weights && editingAverageIndex === null) { if (formik.values.body_weights && editingAverageIndex === null) {
const updatedBodyWeights = formik.values.body_weights.map( const updatedBodyWeights = formik.values.body_weights.map(
@@ -976,7 +1121,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
: 'grid grid-cols-3 gap-4' : 'grid grid-cols-3 gap-4'
} }
> >
{type === 'detail' ? null : ( {(type as 'add' | 'edit' | 'detail') === 'detail' ? null : (
<> <>
<SelectInput <SelectInput
required required
@@ -1033,18 +1178,19 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
</> </>
)} )}
{type === 'detail' && formik.values.project_flock_kandang && ( {(type as 'add' | 'edit' | 'detail') === 'detail' &&
<div className='form-control'> formik.values.project_flock_kandang && (
<label className='label'> <div className='form-control'>
<span className='label-text font-semibold'> <label className='label'>
Project Flock - Kandang <span className='label-text font-semibold'>
</span> Project Flock - Kandang
</label> </span>
<div className='input input-bordered bg-gray-50'> </label>
{formik.values.project_flock_kandang.label} <div className='input input-bordered bg-gray-50'>
{formik.values.project_flock_kandang.label}
</div>
</div> </div>
</div> )}
)}
</div> </div>
</Card> </Card>
@@ -1060,7 +1206,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
<table className='table'> <table className='table'>
<thead> <thead>
<tr> <tr>
{type !== 'detail' && ( {(type as 'add' | 'edit' | 'detail') !== 'detail' && (
<th> <th>
<CheckboxInput <CheckboxInput
name='select-all-body-weights' name='select-all-body-weights'
@@ -1116,13 +1262,15 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
<span className='text-error'>*</span> <span className='text-error'>*</span>
</span> </span>
</th> </th>
{type !== 'detail' && <th>Action</th>} {(type as 'add' | 'edit' | 'detail') !== 'detail' && (
<th>Action</th>
)}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{formik.values.body_weights?.map((bw, idx) => ( {formik.values.body_weights?.map((bw, idx) => (
<tr key={`body-weight-${idx}`}> <tr key={`body-weight-${idx}`}>
{type !== 'detail' && ( {(type as 'add' | 'edit' | 'detail') !== 'detail' && (
<td className='!align-middle'> <td className='!align-middle'>
<CheckboxInput <CheckboxInput
name={`body-weight-${idx}`} name={`body-weight-${idx}`}
@@ -1232,7 +1380,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
/> />
</td> </td>
{type !== 'detail' && ( {(type as 'add' | 'edit' | 'detail') !== 'detail' && (
<td> <td>
<div className='flex items-center'> <div className='flex items-center'>
<Button <Button
@@ -1254,7 +1402,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
</tbody> </tbody>
</table> </table>
</div> </div>
{type !== 'detail' && ( {(type as 'add' | 'edit' | 'detail') !== 'detail' && (
<div className='flex justify-center items-center mt-4 gap-4'> <div className='flex justify-center items-center mt-4 gap-4'>
{selectedBodyWeights.length > 0 && ( {selectedBodyWeights.length > 0 && (
<Button <Button
@@ -1293,7 +1441,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
<table className='table'> <table className='table'>
<thead> <thead>
<tr> <tr>
{type !== 'detail' && ( {(type as 'add' | 'edit' | 'detail') !== 'detail' && (
<th> <th>
<CheckboxInput <CheckboxInput
name='select-all-stocks' name='select-all-stocks'
@@ -1338,13 +1486,15 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
<span className='text-error'>*</span> <span className='text-error'>*</span>
</span> </span>
</th> </th>
{type !== 'detail' && <th>Action</th>} {(type as 'add' | 'edit' | 'detail') !== 'detail' && (
<th>Action</th>
)}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{formik.values.stocks?.map((stock, idx) => ( {formik.values.stocks?.map((stock, idx) => (
<tr key={`stock-${idx}`}> <tr key={`stock-${idx}`}>
{type !== 'detail' && ( {(type as 'add' | 'edit' | 'detail') !== 'detail' && (
<td className='!align-middle'> <td className='!align-middle'>
<CheckboxInput <CheckboxInput
name={`stock-${idx}`} name={`stock-${idx}`}
@@ -1444,10 +1594,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
}} }}
placeholder='Jumlah Pakai' placeholder='Jumlah Pakai'
/> />
{type !== 'detail' && getStockUsageAdornment(idx)} {(type as 'add' | 'edit' | 'detail') !== 'detail' &&
getStockUsageAdornment(idx)}
</div> </div>
</td> </td>
{type !== 'detail' && ( {(type as 'add' | 'edit' | 'detail') !== 'detail' && (
<td> <td>
<div className='flex items-center'> <div className='flex items-center'>
<Button <Button
@@ -1469,7 +1620,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
</tbody> </tbody>
</table> </table>
</div> </div>
{type !== 'detail' && ( {(type as 'add' | 'edit' | 'detail') !== 'detail' && (
<div className='flex justify-center items-center mt-4 gap-4'> <div className='flex justify-center items-center mt-4 gap-4'>
{selectedStocks.length > 0 && ( {selectedStocks.length > 0 && (
<Button <Button
@@ -1508,7 +1659,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
<table className='table'> <table className='table'>
<thead> <thead>
<tr> <tr>
{type !== 'detail' && ( {(type as 'add' | 'edit' | 'detail') !== 'detail' && (
<th> <th>
<CheckboxInput <CheckboxInput
name='select-all-depletions' name='select-all-depletions'
@@ -1555,13 +1706,15 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
<span className='text-error'>*</span> <span className='text-error'>*</span>
</span> </span>
</th> </th>
{type !== 'detail' && <th>Action</th>} {(type as 'add' | 'edit' | 'detail') !== 'detail' && (
<th>Action</th>
)}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{formik.values.depletions?.map((depletion, idx) => ( {formik.values.depletions?.map((depletion, idx) => (
<tr key={`depletion-${idx}`}> <tr key={`depletion-${idx}`}>
{type !== 'detail' && ( {(type as 'add' | 'edit' | 'detail') !== 'detail' && (
<td className='!align-middle'> <td className='!align-middle'>
<CheckboxInput <CheckboxInput
name={`depletion-${idx}`} name={`depletion-${idx}`}
@@ -1653,7 +1806,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
placeholder='Jumlah' placeholder='Jumlah'
/> />
</td> </td>
{type !== 'detail' && ( {(type as 'add' | 'edit' | 'detail') !== 'detail' && (
<td> <td>
<div className='flex items-center'> <div className='flex items-center'>
<Button <Button
@@ -1675,7 +1828,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
</tbody> </tbody>
</table> </table>
</div> </div>
{type !== 'detail' && ( {(type as 'add' | 'edit' | 'detail') !== 'detail' && (
<div className='flex justify-center items-center mt-4 gap-4'> <div className='flex justify-center items-center mt-4 gap-4'>
{selectedDepletions.length > 0 && ( {selectedDepletions.length > 0 && (
<Button <Button
@@ -1702,6 +1855,220 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
)} )}
</Card> </Card>
{/* Eggs Table - Only for LAYING Category */}
{isLayingCategory && (
<Card
title='Telur'
className={{
wrapper: 'w-full mb-4 shadow',
title: 'mb-4',
}}
>
<div className='overflow-x-auto'>
<table className='table'>
<thead>
<tr>
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
<th>
<CheckboxInput
name='select-all-eggs'
checked={
((formik.values as RecordingLayingFormValues).eggs
?.length ?? 0) === selectedEggs.length &&
((formik.values as RecordingLayingFormValues).eggs
?.length ?? 0) > 0
}
onChange={(
e: React.ChangeEvent<HTMLInputElement>
) => {
if (e.target.checked) {
setSelectedEggs(
(
formik.values as RecordingLayingFormValues
).eggs?.map((_, idx) => idx) ?? []
);
} else {
setSelectedEggs([]);
}
}}
classNames={{
wrapper: 'flex justify-center',
checkbox: 'checkbox checkbox-sm',
}}
/>
</th>
)}
<th>
Produk Telur
<span
className='tooltip tooltip-error tooltip-bottom z-[9999]'
data-tip='required'
>
<span className='text-error'>*</span>
</span>
</th>
<th>
Jumlah
<span
className='tooltip tooltip-error tooltip-bottom z-[9999]'
data-tip='required'
>
<span className='text-error'>*</span>
</span>
</th>
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
<th>Action</th>
)}
</tr>
</thead>
<tbody>
{(formik.values as RecordingLayingFormValues).eggs?.map(
(egg, idx) => (
<tr key={`egg-${idx}`}>
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
<td className='!align-middle'>
<CheckboxInput
name={`egg-${idx}`}
checked={selectedEggs.includes(idx)}
onChange={(
e: React.ChangeEvent<HTMLInputElement>
) => {
if (e.target.checked) {
setSelectedEggs([...selectedEggs, idx]);
} else {
setSelectedEggs(
selectedEggs.filter((i) => i !== idx)
);
}
}}
classNames={{
wrapper: 'flex justify-center',
checkbox: 'checkbox checkbox-sm',
}}
/>
</td>
)}
<td>
<SelectInput
required
value={
depletionProducts.find(
(product) =>
product.value === egg.product_warehouse_id
) || null
}
onChange={(selectedOption) => {
const option =
selectedOption as OptionType | null;
formik.setFieldValue(
`eggs.${idx}.product_warehouse_id`,
option?.value || 0
);
}}
options={depletionProducts}
placeholder='Pilih Produk Telur'
isLoading={isLoadingEggProducts}
isError={
isRepeaterInputError(
'eggs',
'product_warehouse_id',
idx
).isError
}
errorMessage={
isRepeaterInputError(
'eggs',
'product_warehouse_id',
idx
).errorMessage
}
isDisabled={type === 'detail'}
className={{
wrapper: 'w-full min-w-48',
}}
isSearchable
isClearable={type !== 'detail'}
/>
</td>
<td>
<div className='flex flex-col gap-1'>
<NumberInput
required
name={`eggs.${idx}.qty`}
value={egg.qty}
onChange={handleEggQtyChangeWrapper(idx)}
onBlur={formik.handleBlur}
decimalScale={0}
allowNegative={false}
thousandSeparator=','
decimalSeparator='.'
isError={
isRepeaterInputError('eggs', 'qty', idx)
.isError
}
errorMessage={
isRepeaterInputError('eggs', 'qty', idx)
.errorMessage
}
readOnly={type === 'detail'}
className={{
wrapper: 'w-full min-w-24',
}}
placeholder='Jumlah'
/>
</div>
</td>
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
<td>
<div className='flex items-center'>
<Button
type='button'
color='error'
onClick={() => removeEgg(idx)}
>
<Icon
icon='mdi:trash-can'
width={24}
height={24}
/>
</Button>
</div>
</td>
)}
</tr>
)
)}
</tbody>
</table>
</div>
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
<div className='flex justify-center items-center mt-4 gap-4'>
{selectedEggs.length > 0 && (
<Button
type='button'
color='error'
onClick={removeSelectedEggs}
disabled={selectedEggs.length === 0}
className='w-fit'
>
<Icon icon='mdi:trash-can' width={24} height={24} />
Hapus Terpilih ({selectedEggs.length})
</Button>
)}
<Button
type='button'
color='success'
onClick={addEgg}
className='w-fit'
>
<Icon icon='ic:round-plus' width={24} height={24} />
Tambah Telur
</Button>
</div>
)}
</Card>
)}
{/* Action buttons */} {/* Action buttons */}
<FormActions<RecordingGrowingFormValues> <FormActions<RecordingGrowingFormValues>
type={type} type={type}
@@ -1750,7 +2117,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
/> />
{/* Approve Confirmation Modal */} {/* Approve Confirmation Modal */}
{type === 'detail' && ( {(type as 'add' | 'edit' | 'detail') === 'detail' && (
<ConfirmationModal <ConfirmationModal
ref={approveModal.ref} ref={approveModal.ref}
type='success' type='success'
@@ -1768,7 +2135,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
)} )}
{/* Reject Confirmation Modal */} {/* Reject Confirmation Modal */}
{type === 'detail' && ( {(type as 'add' | 'edit' | 'detail') === 'detail' && (
<ConfirmationModal <ConfirmationModal
ref={rejectModal.ref} ref={rejectModal.ref}
type='error' type='error'