Merge branch 'development' into fix/transfer-to-laying

This commit is contained in:
ValdiANS
2026-01-20 17:37:06 +07:00
55 changed files with 1824 additions and 923 deletions
@@ -686,10 +686,18 @@ const RecordingTable = () => {
1,
},
{
header: 'Nama Project',
header: 'Lokasi',
cell: (props) => props.row.original.location?.name || '-',
},
{
header: 'Flock',
cell: (props) =>
props.row.original.project_flock?.flock_name || '-',
},
{
header: 'Kandang',
cell: (props) => props.row.original.kandang?.name || '-',
},
{
header: 'Periode',
cell: (props) => props.row.original.project_flock?.period || '-',
@@ -722,12 +730,6 @@ const RecordingTable = () => {
},
},
{
accessorKey: 'warehouse.name',
header: 'Gudang',
cell: (props) => props.row.original.warehouse?.name,
},
{
accessorKey: 'record_date',
header: 'Waktu Recording',
cell: (props) =>
formatDate(props.row.original.record_datetime, 'DD MMMM YYYY'),
@@ -1011,21 +1013,6 @@ const RecordingTable = () => {
approvalHistoryModal.openModal();
};
const getStatusText = (action: string) => {
switch (action) {
case 'APPROVED':
return 'Disetujui';
case 'REJECTED':
return 'Ditolak';
case 'CREATED':
return 'Dibuat';
case 'UPDATED':
return 'Diperbarui';
default:
return action;
}
};
return (
<Badge
variant='soft'
@@ -1036,7 +1023,7 @@ const RecordingTable = () => {
}}
onClick={openApprovalHistory}
>
{getStatusText(approval.action)}
{approval.step_name || approval.action}
</Badge>
);
},
@@ -33,16 +33,16 @@ type RecordingGrowingFormSchemaType = {
qty: number | string;
}[];
depletions: {
product_warehouse_id: number;
qty: number | string;
product_warehouse_id?: number;
qty?: number | string;
}[];
};
type RecordingLayingFormSchemaType = RecordingGrowingFormSchemaType & {
eggs: {
product_warehouse_id: number;
qty: number | string;
weight: number | string;
product_warehouse_id?: number;
qty?: number | string;
weight?: number | string;
}[];
};
@@ -52,14 +52,14 @@ export type StockSchema = {
};
export type DepletionSchema = {
product_warehouse_id: number;
qty: number | string;
product_warehouse_id?: number;
qty?: number | string;
};
export type EggSchema = {
product_warehouse_id: number;
qty: number | string;
weight: number | string;
product_warehouse_id?: number;
qty?: number | string;
weight?: number | string;
};
const StockObjectSchema: Yup.ObjectSchema<StockSchema> = Yup.object({
@@ -75,28 +75,19 @@ const StockObjectSchema: Yup.ObjectSchema<StockSchema> = Yup.object({
const DepletionObjectSchema: Yup.ObjectSchema<DepletionSchema> = Yup.object({
product_warehouse_id: Yup.number()
.required('Produk depletions wajib diisi!')
.min(1, 'Produk depletions wajib diisi!')
.typeError('Produk depletions harus berupa angka!'),
.optional()
.typeError('Depletions harus berupa angka!'),
qty: Yup.number()
.required('Jumlah depletions wajib diisi!')
.min(1, 'Jumlah depletions minimal 1!')
.optional()
.typeError('Jumlah depletions harus berupa angka!'),
});
const EggObjectSchema: Yup.ObjectSchema<EggSchema> = Yup.object({
product_warehouse_id: Yup.number()
.required('Kondisi telur wajib diisi!')
.min(1, 'Kondisi telur wajib diisi!')
.optional()
.typeError('Kondisi telur harus berupa angka!'),
qty: Yup.number()
.required('Jumlah telur wajib diisi!')
.min(1, 'Jumlah telur tidak boleh 0!')
.typeError('Jumlah telur harus berupa angka!'),
weight: Yup.number()
.required('Berat telur wajib diisi!')
.min(1, 'Berat telur minimal 1 gram!')
.typeError('Berat telur harus berupa angka!'),
qty: Yup.number().optional().typeError('Jumlah telur harus berupa angka!'),
weight: Yup.number().optional().typeError('Berat telur harus berupa angka!'),
});
export const RecordingGrowingFormSchema: Yup.ObjectSchema<RecordingGrowingFormSchemaType> =
@@ -163,18 +154,12 @@ export const RecordingGrowingFormSchema: Yup.ObjectSchema<RecordingGrowingFormSc
.of(StockObjectSchema)
.min(1, 'Minimal harus ada 1 data stok!')
.required('Data stok wajib diisi!'),
depletions: Yup.array()
.of(DepletionObjectSchema)
.min(1, 'Minimal harus ada 1 data depletions!')
.required('Data depletions wajib diisi!'),
depletions: Yup.array().of(DepletionObjectSchema).default([]),
});
export const RecordingLayingFormSchema: Yup.ObjectSchema<RecordingLayingFormSchemaType> =
RecordingGrowingFormSchema.shape({
eggs: Yup.array()
.of(EggObjectSchema)
.min(1, 'Minimal harus ada 1 data telur!')
.required('Data telur wajib diisi!'),
eggs: Yup.array().of(EggObjectSchema).default([]),
});
export const UpdateRecordingGrowingFormSchema =
@@ -79,6 +79,7 @@ import {
GROWING_RECORDING_APPROVAL_LINE,
LAYING_RECORDING_APPROVAL_LINE,
} from '@/config/approval-line';
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
interface RecordingFormProps {
type?: 'add' | 'edit' | 'detail';
@@ -227,7 +228,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
const [, setApprovalNotes] = useState('');
const [recordingFormErrorMessage, setRecordingFormErrorMessage] =
useState('');
const [formErrorList, setFormErrorList] = useState<string[]>([]);
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const [, setNewRecordingData] = useState<Recording | null>(null);
const [nextDayRecording, setNextDayRecording] =
@@ -309,6 +309,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
// ===== PAYLOAD CREATION HELPERS =====
const createGrowingPayload = useCallback(
(values: RecordingGrowingFormValues) => {
const depletions = values.depletions
?.filter((d) => d.product_warehouse_id && d.qty)
.map((depletion) => ({
product_warehouse_id: depletion.product_warehouse_id!,
qty: Number(depletion.qty) || 0,
}));
return {
project_flock_kandang_id: values.project_flock_kandang_id,
record_date: values.record_date,
@@ -316,10 +323,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
product_warehouse_id: stock.product_warehouse_id,
qty: Number(stock.qty) || 0,
})),
depletions: (values.depletions ?? []).map((depletion) => ({
product_warehouse_id: depletion.product_warehouse_id,
qty: Number(depletion.qty) || 0,
})),
...(depletions && depletions.length > 0 && { depletions }),
};
},
[]
@@ -327,25 +331,33 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
const createLayingPayload = useCallback(
(values: RecordingLayingFormValues) => {
return {
project_flock_kandang_id: values.project_flock_kandang_id,
record_date: values.record_date,
stocks: (values.stocks ?? []).map((stock) => ({
product_warehouse_id: stock.product_warehouse_id,
qty: Number(stock.qty) || 0,
})),
depletions: (values.depletions ?? []).map((depletion) => ({
product_warehouse_id: depletion.product_warehouse_id,
const depletions = values.depletions
?.filter((d) => d.product_warehouse_id && d.qty)
.map((depletion) => ({
product_warehouse_id: depletion.product_warehouse_id!,
qty: Number(depletion.qty) || 0,
})),
eggs: (values.eggs ?? []).map((egg) => ({
product_warehouse_id: egg.product_warehouse_id,
}));
const eggs = values.eggs
?.filter((e) => e.product_warehouse_id && e.qty && e.weight)
.map((egg) => ({
product_warehouse_id: egg.product_warehouse_id!,
qty: Number(egg.qty) || 0,
weight:
typeof egg.weight === 'number'
? egg.weight
: parseFloat(String(egg.weight)) || 0,
}));
return {
project_flock_kandang_id: values.project_flock_kandang_id,
record_date: values.record_date,
stocks: values.stocks.map((stock) => ({
product_warehouse_id: stock.product_warehouse_id,
qty: Number(stock.qty) || 0,
})),
...(depletions && depletions.length > 0 && { depletions }),
...(eggs && eggs.length > 0 && { eggs }),
};
},
[]
@@ -905,10 +917,58 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
baseValues = getRecordingGrowingFormInitialValues(initialValues);
}
if (type === 'add') {
baseValues.location = selectedLocation
? {
value: Number(selectedLocation.value),
label: selectedLocation.label,
}
: null;
baseValues.location_id = selectedLocation
? Number(selectedLocation.value)
: 0;
baseValues.project_flock = selectedProjectFlock
? {
value: Number(selectedProjectFlock.value),
label: selectedProjectFlock.label,
}
: null;
baseValues.project_flock_id = selectedProjectFlock
? Number(selectedProjectFlock.value)
: 0;
baseValues.kandang = selectedKandang
? {
value: Number(selectedKandang.value),
label: selectedKandang.label,
}
: null;
baseValues.kandang_id = selectedKandang
? Number(selectedKandang.value)
: 0;
}
if (projectFlockKandangDetail && (type === 'edit' || type === 'detail')) {
baseValues.project_flock_kandang = {
value: projectFlockKandangDetail.project_flock.id,
label: projectFlockKandangDetail.project_flock.flock_name || '',
baseValues = {
...baseValues,
project_flock_kandang: {
value: projectFlockKandangDetail.project_flock?.id,
label: projectFlockKandangDetail.project_flock?.flock_name || '',
},
project_flock: {
value: projectFlockKandangDetail.project_flock?.id,
label: projectFlockKandangDetail.project_flock?.flock_name || '',
},
project_flock_id: projectFlockKandangDetail.project_flock?.id,
location: {
value: projectFlockKandangDetail.project_flock?.location?.id,
label: projectFlockKandangDetail.project_flock?.location?.name || '',
},
location_id: projectFlockKandangDetail.project_flock?.location?.id,
kandang: {
value: projectFlockKandangDetail.kandang?.id,
label: projectFlockKandangDetail.kandang?.name || '',
},
kandang_id: projectFlockKandangDetail.kandang?.id,
};
}
@@ -995,22 +1055,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
},
});
const handleValidateForm = async () => {
const errors = await formik.validateForm();
if (Object.keys(errors).length > 0) {
const errorMessages = getUniqueFormikErrors(errors);
setFormErrorList(errorMessages);
return;
}
};
const handleFormSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
handleValidateForm();
formik.handleSubmit(e);
};
// ===== HELPER FUNCTIONS =====
const getAvailableStock = useCallback(
(productWarehouseId: number) => {
@@ -1266,6 +1310,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
[formik, duplicateErrorShown]
);
const { formErrorList, handleFormSubmit, close } = useFormikErrorList(formik);
useEffect(() => {
if (projectFlockKandangLookup?.project_flock_kandang_id) {
const projectFlockKandangId =
@@ -1655,10 +1701,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
{/* Error List Alert */}
{formErrorList.length > 0 && (
<AlertErrorList
formErrorList={formErrorList}
onClose={() => setFormErrorList([])}
/>
<AlertErrorList formErrorList={formErrorList} onClose={close} />
)}
{/* Basic Info Card */}
@@ -2520,24 +2563,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
/>
</th>
)}
<th>
Kondisi
<span
className='tooltip tooltip-error tooltip-bottom '
data-tip='required'
>
<span className='text-error'>*</span>
</span>
</th>
<th>
Jumlah
<span
className='tooltip tooltip-error tooltip-bottom '
data-tip='required'
>
<span className='text-error'>*</span>
</span>
</th>
<th>Kondisi</th>
<th>Jumlah</th>
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
<th>Action</th>
)}
@@ -2615,7 +2642,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
</td>
<td>
<NumberInput
required
name={`depletions.${idx}.qty`}
value={depletion.qty ?? ''}
onChange={handleDepletionQtyChangeWrapper(idx)}
@@ -2731,33 +2757,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
/>
</th>
)}
<th>
Kondisi Telur
<span
className='tooltip tooltip-error tooltip-bottom '
data-tip='required'
>
<span className='text-error'>*</span>
</span>
</th>
<th>
Jumlah
<span
className='tooltip tooltip-error tooltip-bottom '
data-tip='required'
>
<span className='text-error'>*</span>
</span>
</th>
<th>
Berat (gram)
<span
className='tooltip tooltip-error tooltip-bottom '
data-tip='required'
>
<span className='text-error'>*</span>
</span>
</th>
<th>Kondisi Telur</th>
<th>Jumlah</th>
<th>Berat (gram)</th>
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
<th>Action</th>
)}
@@ -2792,7 +2794,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
)}
<td>
<SelectInput
required
value={
eggProducts.find(
(product) =>
@@ -2835,7 +2836,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
</td>
<td>
<NumberInput
required
name={`eggs.${idx}.qty`}
value={egg.qty ?? ''}
onChange={handleEggQtyChangeWrapper(idx)}
@@ -2860,7 +2860,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
</td>
<td>
<NumberInput
required
name={`eggs.${idx}.weight`}
value={egg.weight ?? ''}
onChange={handleEggWeightChangeWrapper(idx)}