mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 21:41:57 +00:00
feat(FE-170): add egg handling and validation to daily recording form
This commit is contained in:
@@ -15,14 +15,21 @@ import { FormActions } from '@/components/helper/form/FormActions';
|
||||
import { RecordingApi } from '@/services/api/production';
|
||||
import {
|
||||
CreateGrowingRecordingPayload,
|
||||
CreateLayingRecordingPayload,
|
||||
UpdateGrowingRecordingPayload,
|
||||
UpdateLayingRecordingPayload,
|
||||
Recording,
|
||||
} from '@/types/api/production/recording';
|
||||
import { type BaseApiResponse } from '@/types/api/api-general';
|
||||
import {
|
||||
RecordingGrowingFormSchema,
|
||||
RecordingLayingFormSchema,
|
||||
RecordingGrowingFormValues,
|
||||
RecordingLayingFormValues,
|
||||
getRecordingGrowingFormInitialValues,
|
||||
getRecordingLayingFormInitialValues,
|
||||
UpdateRecordingGrowingFormSchema,
|
||||
UpdateRecordingLayingFormSchema,
|
||||
} from './RecordingForm.schema';
|
||||
import { useRecordingFormHandlers } from './useRecordingFormHandlers';
|
||||
import { ProjectFlockApi } from '@/services/api/production';
|
||||
@@ -49,6 +56,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
const [selectedBodyWeights, setSelectedBodyWeights] = useState<number[]>([]);
|
||||
const [selectedStocks, setSelectedStocks] = useState<number[]>([]);
|
||||
const [selectedDepletions, setSelectedDepletions] = useState<number[]>([]);
|
||||
const [selectedEggs, setSelectedEggs] = useState<number[]>([]);
|
||||
|
||||
const [editingAverageIndex] = useState<number | null>(null);
|
||||
const [manuallyEditedRows, setManuallyEditedRows] = useState<Set<number>>(
|
||||
@@ -219,6 +227,20 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
const { data: depletionProductsData, isLoading: isLoadingDepletionProducts } =
|
||||
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 =====
|
||||
const locationOptions = useMemo(() => {
|
||||
if (!isResponseSuccess(locations)) return [];
|
||||
@@ -358,6 +380,27 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
return options;
|
||||
}, [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 =====
|
||||
const {
|
||||
deleteModal,
|
||||
@@ -369,46 +412,109 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
confirmationModalDeleteClickHandler,
|
||||
} = useRecordingFormHandlers(initialValues?.id);
|
||||
|
||||
const formikInitialValues = useMemo<RecordingGrowingFormValues>(
|
||||
() => getRecordingGrowingFormInitialValues(initialValues),
|
||||
[initialValues]
|
||||
);
|
||||
const isLayingCategory =
|
||||
projectFlockKandangLookup?.project_flock?.category === 'LAYING';
|
||||
|
||||
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,
|
||||
validationSchema:
|
||||
type === 'edit'
|
||||
validationSchema: (() => {
|
||||
if (isLayingCategory) {
|
||||
return type === 'edit'
|
||||
? UpdateRecordingLayingFormSchema
|
||||
: RecordingLayingFormSchema;
|
||||
}
|
||||
return type === 'edit'
|
||||
? UpdateRecordingGrowingFormSchema
|
||||
: RecordingGrowingFormSchema,
|
||||
: RecordingGrowingFormSchema;
|
||||
})(),
|
||||
validateOnChange: true,
|
||||
validateOnBlur: true,
|
||||
onSubmit: async (values) => {
|
||||
const payload: CreateGrowingRecordingPayload = {
|
||||
project_flock_kandangs_id: values.project_flock_kandangs_id,
|
||||
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,
|
||||
})),
|
||||
};
|
||||
if (isLayingCategory) {
|
||||
const layingValues = values as RecordingLayingFormValues;
|
||||
|
||||
switch (type) {
|
||||
case 'add':
|
||||
await createRecordingHandler(payload);
|
||||
break;
|
||||
case 'edit':
|
||||
await updateRecordingHandler(initialValues?.id as number, payload);
|
||||
break;
|
||||
const layingPayload = {
|
||||
project_flock_kandangs_id: layingValues.project_flock_kandangs_id,
|
||||
body_weights: (layingValues.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: (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(
|
||||
(productWarehouseId: number) => {
|
||||
if (type === 'detail') return 0;
|
||||
if ((type as 'add' | 'edit' | 'detail') === 'detail') return 0;
|
||||
if (!isResponseSuccess(stockProducts)) return 0;
|
||||
const productWarehouse = stockProducts.data.find(
|
||||
(pw) => pw.id === productWarehouseId
|
||||
@@ -448,7 +554,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
|
||||
const getStockUsageError = useCallback(
|
||||
(stockIdx: number) => {
|
||||
if (type === 'detail') return null;
|
||||
if ((type as 'add' | 'edit' | 'detail') === 'detail') return null;
|
||||
const stock = formik.values.stocks?.[stockIdx];
|
||||
if (!stock || !stock.product_warehouse_id) return null;
|
||||
const availableStock = getAvailableStock(stock.product_warehouse_id);
|
||||
@@ -463,7 +569,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
|
||||
const getStockUsageAdornment = useCallback(
|
||||
(stockIdx: number) => {
|
||||
if (type === 'detail') return null;
|
||||
if ((type as 'add' | 'edit' | 'detail') === 'detail') return null;
|
||||
const stock = formik.values.stocks?.[stockIdx];
|
||||
if (!stock || !stock.product_warehouse_id) return null;
|
||||
const availableStock = getAvailableStock(stock.product_warehouse_id);
|
||||
@@ -566,7 +672,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
);
|
||||
|
||||
const hasExceededStock = useMemo(() => {
|
||||
if (type === 'detail') return false;
|
||||
if ((type as 'add' | 'edit' | 'detail') === 'detail') return false;
|
||||
return (
|
||||
formik.values.stocks?.some((stock, idx) => {
|
||||
return getStockUsageError(idx) !== null;
|
||||
@@ -574,40 +680,35 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
);
|
||||
}, [formik.values.stocks, getStockUsageError, type]);
|
||||
|
||||
const isRepeaterInputError = <
|
||||
T extends 'body_weights' | 'stocks' | 'depletions',
|
||||
>(
|
||||
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,
|
||||
const isRepeaterInputError = (
|
||||
arrayName: 'body_weights' | 'stocks' | 'depletions' | 'eggs',
|
||||
column: string,
|
||||
idx: number
|
||||
) => {
|
||||
if (
|
||||
!formik.touched[arrayName] ||
|
||||
!Array.isArray(formik.touched[arrayName])
|
||||
) {
|
||||
const touched = formik.touched as Record<string, unknown>;
|
||||
const errors = formik.errors as Record<string, unknown>;
|
||||
|
||||
if (!touched[arrayName] || !Array.isArray(touched[arrayName])) {
|
||||
return {
|
||||
isError: false,
|
||||
errorMessage: '',
|
||||
};
|
||||
}
|
||||
|
||||
const touchedField = formik.touched[arrayName]?.[idx]?.[column as string];
|
||||
const errorField = formik.errors[arrayName]?.[idx] as Record<
|
||||
const touchedField = (touched[arrayName] as unknown[])?.[idx] as Record<
|
||||
string,
|
||||
string
|
||||
unknown
|
||||
>;
|
||||
const errorField = (errors[arrayName] as unknown[])?.[idx] as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
|
||||
return {
|
||||
isError: touchedField && Boolean(errorField?.[column as string]),
|
||||
isError: touchedField && Boolean(errorField?.[column]),
|
||||
errorMessage:
|
||||
touchedField && errorField?.[column as string]
|
||||
? errorField[column as string]
|
||||
touchedField && errorField?.[column]
|
||||
? (errorField[column] as string)
|
||||
: '',
|
||||
};
|
||||
};
|
||||
@@ -899,7 +1000,51 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
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 =====
|
||||
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(() => {
|
||||
if (formik.values.body_weights && editingAverageIndex === null) {
|
||||
const updatedBodyWeights = formik.values.body_weights.map(
|
||||
@@ -976,7 +1121,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
: 'grid grid-cols-3 gap-4'
|
||||
}
|
||||
>
|
||||
{type === 'detail' ? null : (
|
||||
{(type as 'add' | 'edit' | 'detail') === 'detail' ? null : (
|
||||
<>
|
||||
<SelectInput
|
||||
required
|
||||
@@ -1033,18 +1178,19 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
</>
|
||||
)}
|
||||
|
||||
{type === 'detail' && formik.values.project_flock_kandang && (
|
||||
<div className='form-control'>
|
||||
<label className='label'>
|
||||
<span className='label-text font-semibold'>
|
||||
Project Flock - Kandang
|
||||
</span>
|
||||
</label>
|
||||
<div className='input input-bordered bg-gray-50'>
|
||||
{formik.values.project_flock_kandang.label}
|
||||
{(type as 'add' | 'edit' | 'detail') === 'detail' &&
|
||||
formik.values.project_flock_kandang && (
|
||||
<div className='form-control'>
|
||||
<label className='label'>
|
||||
<span className='label-text font-semibold'>
|
||||
Project Flock - Kandang
|
||||
</span>
|
||||
</label>
|
||||
<div className='input input-bordered bg-gray-50'>
|
||||
{formik.values.project_flock_kandang.label}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -1060,7 +1206,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
<table className='table'>
|
||||
<thead>
|
||||
<tr>
|
||||
{type !== 'detail' && (
|
||||
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
||||
<th>
|
||||
<CheckboxInput
|
||||
name='select-all-body-weights'
|
||||
@@ -1116,13 +1262,15 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
<span className='text-error'>*</span>
|
||||
</span>
|
||||
</th>
|
||||
{type !== 'detail' && <th>Action</th>}
|
||||
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
||||
<th>Action</th>
|
||||
)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{formik.values.body_weights?.map((bw, idx) => (
|
||||
<tr key={`body-weight-${idx}`}>
|
||||
{type !== 'detail' && (
|
||||
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
||||
<td className='!align-middle'>
|
||||
<CheckboxInput
|
||||
name={`body-weight-${idx}`}
|
||||
@@ -1232,7 +1380,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
/>
|
||||
</td>
|
||||
|
||||
{type !== 'detail' && (
|
||||
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
||||
<td>
|
||||
<div className='flex items-center'>
|
||||
<Button
|
||||
@@ -1254,7 +1402,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{type !== 'detail' && (
|
||||
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
||||
<div className='flex justify-center items-center mt-4 gap-4'>
|
||||
{selectedBodyWeights.length > 0 && (
|
||||
<Button
|
||||
@@ -1293,7 +1441,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
<table className='table'>
|
||||
<thead>
|
||||
<tr>
|
||||
{type !== 'detail' && (
|
||||
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
||||
<th>
|
||||
<CheckboxInput
|
||||
name='select-all-stocks'
|
||||
@@ -1338,13 +1486,15 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
<span className='text-error'>*</span>
|
||||
</span>
|
||||
</th>
|
||||
{type !== 'detail' && <th>Action</th>}
|
||||
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
||||
<th>Action</th>
|
||||
)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{formik.values.stocks?.map((stock, idx) => (
|
||||
<tr key={`stock-${idx}`}>
|
||||
{type !== 'detail' && (
|
||||
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
||||
<td className='!align-middle'>
|
||||
<CheckboxInput
|
||||
name={`stock-${idx}`}
|
||||
@@ -1444,10 +1594,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
}}
|
||||
placeholder='Jumlah Pakai'
|
||||
/>
|
||||
{type !== 'detail' && getStockUsageAdornment(idx)}
|
||||
{(type as 'add' | 'edit' | 'detail') !== 'detail' &&
|
||||
getStockUsageAdornment(idx)}
|
||||
</div>
|
||||
</td>
|
||||
{type !== 'detail' && (
|
||||
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
||||
<td>
|
||||
<div className='flex items-center'>
|
||||
<Button
|
||||
@@ -1469,7 +1620,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{type !== 'detail' && (
|
||||
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
||||
<div className='flex justify-center items-center mt-4 gap-4'>
|
||||
{selectedStocks.length > 0 && (
|
||||
<Button
|
||||
@@ -1508,7 +1659,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
<table className='table'>
|
||||
<thead>
|
||||
<tr>
|
||||
{type !== 'detail' && (
|
||||
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
||||
<th>
|
||||
<CheckboxInput
|
||||
name='select-all-depletions'
|
||||
@@ -1555,13 +1706,15 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
<span className='text-error'>*</span>
|
||||
</span>
|
||||
</th>
|
||||
{type !== 'detail' && <th>Action</th>}
|
||||
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
||||
<th>Action</th>
|
||||
)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{formik.values.depletions?.map((depletion, idx) => (
|
||||
<tr key={`depletion-${idx}`}>
|
||||
{type !== 'detail' && (
|
||||
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
||||
<td className='!align-middle'>
|
||||
<CheckboxInput
|
||||
name={`depletion-${idx}`}
|
||||
@@ -1653,7 +1806,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
placeholder='Jumlah'
|
||||
/>
|
||||
</td>
|
||||
{type !== 'detail' && (
|
||||
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
||||
<td>
|
||||
<div className='flex items-center'>
|
||||
<Button
|
||||
@@ -1675,7 +1828,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{type !== 'detail' && (
|
||||
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
||||
<div className='flex justify-center items-center mt-4 gap-4'>
|
||||
{selectedDepletions.length > 0 && (
|
||||
<Button
|
||||
@@ -1702,6 +1855,220 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
)}
|
||||
</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 */}
|
||||
<FormActions<RecordingGrowingFormValues>
|
||||
type={type}
|
||||
@@ -1750,7 +2117,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
/>
|
||||
|
||||
{/* Approve Confirmation Modal */}
|
||||
{type === 'detail' && (
|
||||
{(type as 'add' | 'edit' | 'detail') === 'detail' && (
|
||||
<ConfirmationModal
|
||||
ref={approveModal.ref}
|
||||
type='success'
|
||||
@@ -1768,7 +2135,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
)}
|
||||
|
||||
{/* Reject Confirmation Modal */}
|
||||
{type === 'detail' && (
|
||||
{(type as 'add' | 'edit' | 'detail') === 'detail' && (
|
||||
<ConfirmationModal
|
||||
ref={rejectModal.ref}
|
||||
type='error'
|
||||
|
||||
Reference in New Issue
Block a user