Merge branch 'feat/FE/US-281-439/TASK-440-441-recording-page-and-form-adjustment' into 'development'

[FEAT/FE][US#281-439/TASK-440-441] Recording Page and Form Adjustment

See merge request mbugroup/lti-web-client!125
This commit is contained in:
Rivaldi A N S
2025-12-31 03:01:09 +00:00
3 changed files with 247 additions and 540 deletions
@@ -12,11 +12,6 @@ type RecordingGrowingFormSchemaType = {
label: string; label: string;
} | null; } | null;
project_flock_kandang_id: number; project_flock_kandang_id: number;
body_weights: {
weight: number | string;
avg_weight: number | string;
qty: number | string;
}[];
stocks: { stocks: {
product_warehouse_id: number; product_warehouse_id: number;
qty: number | string; qty: number | string;
@@ -35,12 +30,6 @@ type RecordingLayingFormSchemaType = RecordingGrowingFormSchemaType & {
}[]; }[];
}; };
export type BodyWeightSchema = {
weight: number | string;
avg_weight: number | string;
qty: number | string;
};
export type StockSchema = { export type StockSchema = {
product_warehouse_id: number; product_warehouse_id: number;
qty: number | string; qty: number | string;
@@ -57,20 +46,6 @@ export type EggSchema = {
weight: number | string; weight: number | string;
}; };
const BodyWeightObjectSchema: Yup.ObjectSchema<BodyWeightSchema> = Yup.object({
weight: Yup.number()
.required('Berat ayam total wajib diisi!')
.min(1, 'Berat ayam total minimal 1 gram!')
.typeError('Berat ayam total harus berupa angka!'),
avg_weight: Yup.number()
.required('Berat ayam rata-rata wajib diisi!')
.typeError('Berat ayam rata-rata harus berupa angka!'),
qty: Yup.number()
.required('Jumlah ayam wajib diisi!')
.min(1, 'Jumlah ayam minimal 1 ekor!')
.typeError('Jumlah ayam harus berupa angka!'),
});
const StockObjectSchema: Yup.ObjectSchema<StockSchema> = Yup.object({ const StockObjectSchema: Yup.ObjectSchema<StockSchema> = Yup.object({
product_warehouse_id: Yup.number() product_warehouse_id: Yup.number()
.required('Produk wajib diisi!') .required('Produk wajib diisi!')
@@ -140,10 +115,6 @@ export const RecordingGrowingFormSchema: Yup.ObjectSchema<RecordingGrowingFormSc
return true; return true;
} }
), ),
body_weights: Yup.array()
.of(BodyWeightObjectSchema)
.min(1, 'Minimal harus ada 1 data bobot badan!')
.required('Data bobot badan wajib diisi!'),
stocks: Yup.array() stocks: Yup.array()
.of(StockObjectSchema) .of(StockObjectSchema)
.min(1, 'Minimal harus ada 1 data stok!') .min(1, 'Minimal harus ada 1 data stok!')
@@ -196,7 +167,6 @@ export type RecordingLayingFormValues = Yup.InferType<
>; >;
type RecordingFormData = Partial<Recording> & { type RecordingFormData = Partial<Recording> & {
body_weights?: CreateGrowingRecordingPayload['body_weights'];
stocks?: CreateGrowingRecordingPayload['stocks'] | Recording['stocks']; stocks?: CreateGrowingRecordingPayload['stocks'] | Recording['stocks'];
depletions?: depletions?:
| CreateGrowingRecordingPayload['depletions'] | CreateGrowingRecordingPayload['depletions']
@@ -216,19 +186,6 @@ export const getRecordingGrowingFormInitialValues = (
} }
: null, : null,
project_flock_kandang_id: initialValues?.project_flock_kandang_id ?? 0, project_flock_kandang_id: initialValues?.project_flock_kandang_id ?? 0,
body_weights: initialValues?.body_weights?.map(
(bw: NonNullable<CreateGrowingRecordingPayload['body_weights']>[0]) => ({
weight: bw.avg_weight * bw.qty,
avg_weight: bw.avg_weight,
qty: bw.qty,
})
) ?? [
{
weight: '',
avg_weight: '',
qty: '',
},
],
stocks: initialValues?.stocks?.map((stock) => ({ stocks: initialValues?.stocks?.map((stock) => ({
product_warehouse_id: stock.product_warehouse_id, product_warehouse_id: stock.product_warehouse_id,
qty: qty:
@@ -60,6 +60,7 @@ import {
GROWING_RECORDING_APPROVAL_LINE, GROWING_RECORDING_APPROVAL_LINE,
LAYING_RECORDING_APPROVAL_LINE, LAYING_RECORDING_APPROVAL_LINE,
} from '@/config/approval-line'; } from '@/config/approval-line';
import Table from '@/components/Table';
interface RecordingFormProps { interface RecordingFormProps {
type?: 'add' | 'edit' | 'detail'; type?: 'add' | 'edit' | 'detail';
@@ -71,16 +72,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
const router = useRouter(); const router = useRouter();
// ===== STATE MANAGEMENT ===== // ===== STATE MANAGEMENT =====
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 [selectedEggs, setSelectedEggs] = useState<number[]>([]);
const [editingAverageIndex] = useState<number | null>(null);
const [manuallyEditedRows, setManuallyEditedRows] = useState<Set<number>>(
new Set()
);
const [locationSearchValue, setLocationSearchValue] = useState(''); const [locationSearchValue, setLocationSearchValue] = useState('');
const [selectedLocation, setSelectedLocation] = useState<OptionType | null>( const [selectedLocation, setSelectedLocation] = useState<OptionType | null>(
null null
@@ -122,19 +117,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
(values: RecordingGrowingFormValues) => { (values: RecordingGrowingFormValues) => {
return { return {
project_flock_kandang_id: values.project_flock_kandang_id, project_flock_kandang_id: values.project_flock_kandang_id,
body_weights: (values.body_weights ?? []).map((bw) => {
const qty = Number(bw.qty) || 0;
const weight = Number(bw.weight) || 0;
const totalWeight = qty * weight;
return {
avg_weight:
typeof bw.avg_weight === 'number'
? bw.avg_weight
: parseFloat(String(bw.avg_weight)) || 0,
qty: qty,
total_weight: parseFloat(String(totalWeight)) || 0,
};
}),
stocks: (values.stocks ?? []).map((stock) => ({ stocks: (values.stocks ?? []).map((stock) => ({
product_warehouse_id: stock.product_warehouse_id, product_warehouse_id: stock.product_warehouse_id,
qty: Number(stock.qty) || 0, qty: Number(stock.qty) || 0,
@@ -152,15 +134,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
(values: RecordingLayingFormValues) => { (values: RecordingLayingFormValues) => {
return { return {
project_flock_kandang_id: values.project_flock_kandang_id, project_flock_kandang_id: values.project_flock_kandang_id,
body_weights: (values.body_weights ?? []).map((bw) => {
return {
avg_weight:
typeof bw.avg_weight === 'number'
? bw.avg_weight
: parseFloat(String(bw.avg_weight)) || 0,
qty: Number(bw.qty) || 0,
};
}),
stocks: (values.stocks ?? []).map((stock) => ({ stocks: (values.stocks ?? []).map((stock) => ({
product_warehouse_id: stock.product_warehouse_id, product_warehouse_id: stock.product_warehouse_id,
qty: Number(stock.qty) || 0, qty: Number(stock.qty) || 0,
@@ -587,28 +560,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
return recordedIds; return recordedIds;
}, [existingRecordings, today]); }, [existingRecordings, today]);
const getLatestTotalChickQty = useCallback(
(projectFlockKandangId: number) => {
if (!isResponseSuccess(existingRecordings)) return null;
const projectFlockRecordings = existingRecordings.data.filter(
(recording) =>
recording.project_flock_kandang_id === projectFlockKandangId
);
if (projectFlockRecordings.length === 0) return null;
projectFlockRecordings.sort(
(a, b) =>
new Date(b.record_datetime).getTime() -
new Date(a.record_datetime).getTime()
);
return projectFlockRecordings[0].total_chick_qty;
},
[existingRecordings]
);
const unifiedStockProducts = useMemo(() => { const unifiedStockProducts = useMemo(() => {
const options: OptionType[] = []; const options: OptionType[] = [];
if (isResponseSuccess(stockProducts) && selectedKandang) { if (isResponseSuccess(stockProducts) && selectedKandang) {
@@ -808,25 +759,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
}); });
// ===== HELPER FUNCTIONS ===== // ===== HELPER FUNCTIONS =====
const getTotalChickQtyError = useCallback(
(qty: number) => {
if (type === 'detail') return null;
if (!formik.values.project_flock_kandang_id) return null;
const totalChickQty = getLatestTotalChickQty(
formik.values.project_flock_kandang_id
);
if (!totalChickQty) return null;
if (qty > totalChickQty) {
return `Jumlah ayam tidak boleh melebihi total ayam tersedia! Maksimal: ${formatNumber(totalChickQty)}`;
}
return null;
},
[formik.values.project_flock_kandang_id, getLatestTotalChickQty, type]
);
useCallback((): OptionType | null => { useCallback((): OptionType | null => {
if ( if (
!formik.values.project_flock_kandang || !formik.values.project_flock_kandang ||
@@ -1193,124 +1125,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
setIsRejectLoading(false); setIsRejectLoading(false);
}; };
const addBodyWeight = () => {
const newBodyWeights = [
...(formik.values.body_weights || []),
{
weight: '',
avg_weight: '',
qty: '',
},
];
formik.setFieldValue('body_weights', newBodyWeights);
};
const handleWeightChange = (idx: number, value: number) => {
formik.setFieldValue(`body_weights.${idx}.weight`, value);
setManuallyEditedRows((prev) => {
const newSet = new Set(prev);
newSet.delete(idx);
return newSet;
});
const currentWeight = formik.values.body_weights?.[idx];
if (currentWeight) {
const qty = Number(currentWeight.qty) || 0;
const totalWeight = qty * value;
if (qty > 0 && value > 0) {
const avgWeight = parseFloat((value / qty).toFixed(2));
formik.setFieldValue(`body_weights.${idx}.avg_weight`, avgWeight);
} else {
formik.setFieldValue(`body_weights.${idx}.avg_weight`, '');
}
formik.setFieldValue(`body_weights.${idx}.total_weight`, totalWeight);
}
};
const handleAvgWeightChange = (idx: number, value: number) => {
formik.setFieldValue(`body_weights.${idx}.avg_weight`, value);
setManuallyEditedRows((prev) => {
const newSet = new Set(prev);
newSet.add(idx);
return newSet;
});
const currentWeight = formik.values.body_weights?.[idx];
if (currentWeight) {
const qty = Number(currentWeight.qty) || 0;
if (qty > 0 && value > 0) {
const totalWeight = value * qty;
formik.setFieldValue(`body_weights.${idx}.weight`, totalWeight);
formik.setFieldValue(`body_weights.${idx}.total_weight`, totalWeight);
} else {
formik.setFieldValue(`body_weights.${idx}.weight`, 0);
formik.setFieldValue(`body_weights.${idx}.total_weight`, 0);
}
}
};
const handleQtyChange = (idx: number, value: number) => {
formik.setFieldValue(`body_weights.${idx}.qty`, value);
setManuallyEditedRows((prev) => {
const newSet = new Set(prev);
newSet.delete(idx);
return newSet;
});
const currentWeight = formik.values.body_weights?.[idx];
if (currentWeight) {
const weight = Number(currentWeight.weight) || 0;
const totalWeight = value * weight;
if (value > 0 && weight > 0) {
const avgWeight = parseFloat((weight / value).toFixed(2));
formik.setFieldValue(`body_weights.${idx}.avg_weight`, avgWeight);
} else {
formik.setFieldValue(`body_weights.${idx}.avg_weight`, '');
}
formik.setFieldValue(`body_weights.${idx}.total_weight`, totalWeight);
}
};
const handleWeightChangeWrapper =
(idx: number) => (e: React.ChangeEvent<HTMLInputElement>) => {
const value = parseFloat(e.target.value) || 0;
handleWeightChange(idx, value);
};
const handleAvgWeightChangeWrapper =
(idx: number) => (e: React.ChangeEvent<HTMLInputElement>) => {
const value = parseFloat(e.target.value) || 0;
handleAvgWeightChange(idx, value);
};
const handleQtyChangeWrapper =
(idx: number) => (e: React.ChangeEvent<HTMLInputElement>) => {
const value = parseFloat(e.target.value) || 0;
handleQtyChange(idx, value);
};
const removeBodyWeight = (idx: number) => {
const updatedBodyWeights = formik.values.body_weights?.filter(
(_, i) => i !== idx
);
formik.setFieldValue('body_weights', updatedBodyWeights);
};
const removeSelectedBodyWeights = () => {
const updatedBodyWeights = formik.values.body_weights?.filter(
(_, idx) => !selectedBodyWeights.includes(idx)
);
formik.setFieldValue('body_weights', updatedBodyWeights);
setSelectedBodyWeights([]);
};
const addStock = () => { const addStock = () => {
const newStocks = [ const newStocks = [
...(formik.values.stocks || []), ...(formik.values.stocks || []),
@@ -1435,45 +1249,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
} }
}, [isLayingCategory, type]); }, [isLayingCategory, type]);
const bodyWeightValues = useMemo(() => {
if (!formik.values.body_weights) return [];
return formik.values.body_weights.map((w) => ({
weight: w.weight,
qty: w.qty,
}));
}, [formik.values.body_weights]);
useEffect(() => {
if (formik.values.body_weights && editingAverageIndex === null) {
const updatedBodyWeights = formik.values.body_weights.map(
(weight, idx) => {
if (idx === editingAverageIndex || manuallyEditedRows.has(idx)) {
return weight;
}
const qty = Number(weight.qty) || 0;
const weightValue = Number(weight.weight) || 0;
return {
...weight,
avg_weight:
qty > 0 && weightValue > 0
? parseFloat((weightValue / qty).toFixed(2))
: 0,
};
}
);
const hasChanges = updatedBodyWeights.some(
(updated, idx) =>
idx !== editingAverageIndex &&
!manuallyEditedRows.has(idx) &&
updated.avg_weight !==
(formik.values.body_weights[idx]?.avg_weight || 0)
);
if (hasChanges) {
formik.setFieldValue('body_weights', updatedBodyWeights, false);
}
}
}, [bodyWeightValues, editingAverageIndex, manuallyEditedRows]);
return ( return (
<> <>
<section className='w-full'> <section className='w-full'>
@@ -1763,266 +1538,241 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
</Card> </Card>
)} )}
{/* Body Weights Table */} {/* FCR & Mortality Metrics - Detail View Only */}
<Card {type === 'detail' && initialValues && (
title='Bobot Badan' <div
className={{ className={`grid gap-6 mb-6 grid-cols-1 ${
wrapper: 'w-full mb-4 shadow', initialValues.project_flock_category === 'LAYING'
title: 'mb-4', ? 'xl:grid-cols-3'
}} : 'xl:grid-cols-2'
}`}
> >
<div className='overflow-x-auto'> {/* FCR Section */}
<table className='table'> <div className='border border-gray-200 rounded-lg bg-white'>
<div className='px-4 py-3 border-b border-gray-200'>
<span className='card-title font-bold text-xl'>FCR</span>
</div>
<div className='p-4'>
<table className='w-full text-sm'>
<thead> <thead>
<tr> <tr className='border-b border-gray-200'>
{(type as 'add' | 'edit' | 'detail') !== 'detail' && ( <th className='text-left py-2 font-semibold text-gray-600'></th>
<th> <th className='text-center py-2 font-semibold text-gray-600'>
<CheckboxInput AKTUAL
name='select-all-body-weights'
checked={
formik.values.body_weights?.length ===
selectedBodyWeights.length &&
formik.values.body_weights?.length > 0
}
onChange={(
e: React.ChangeEvent<HTMLInputElement>
) => {
if (e.target.checked) {
setSelectedBodyWeights(
formik.values.body_weights?.map(
(_, idx) => idx
) ?? []
);
} else {
setSelectedBodyWeights([]);
}
}}
classNames={{
wrapper: 'flex justify-center',
checkbox: 'checkbox checkbox-sm',
}}
/>
</th> </th>
)} <th className='text-center py-2 font-semibold text-gray-600'>
<th> STANDAR
Berat Ayam (gram)
<span
className='tooltip tooltip-error tooltip-bottom '
data-tip='required'
>
<span className='text-error'>*</span>
</span>
</th> </th>
<th>
Jumlah Ayam
<span
className='tooltip tooltip-error tooltip-bottom'
data-tip='required'
>
<span className='text-error'>*</span>
</span>
</th>
<th>
Rata-rata Berat Ayam (gram)
<span
className='tooltip tooltip-error tooltip-bottom '
data-tip='required'
>
<span className='text-error'>*</span>
</span>
</th>
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
<th>Action</th>
)}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{formik.values.body_weights?.map((bw, idx) => ( <tr>
<tr key={`body-weight-${idx}`}> <td className='py-3 font-medium'>FCR</td>
{(type as 'add' | 'edit' | 'detail') !== 'detail' && ( <td className='text-center py-3'>
<td className='align-middle!'> <span className='font-semibold'>
<CheckboxInput {initialValues.fcr_value &&
name={`body-weight-${idx}`} initialValues.fcr_value > 0
checked={selectedBodyWeights.includes(idx)} ? formatNumber(initialValues.fcr_value)
onChange={( : '-'}
e: React.ChangeEvent<HTMLInputElement> </span>
) => {
if (e.target.checked) {
setSelectedBodyWeights([
...selectedBodyWeights,
idx,
]);
} else {
setSelectedBodyWeights(
selectedBodyWeights.filter((i) => i !== idx)
);
}
}}
classNames={{
wrapper: 'flex justify-center',
checkbox: 'checkbox checkbox-sm',
}}
/>
</td> </td>
)} <td className='text-center py-3 text-gray-600'>
<td> {initialValues.fcr_std && initialValues.fcr_std > 0
<NumberInput ? formatNumber(initialValues.fcr_std)
required : '-'}
name={`body_weights.${idx}.weight`} </td>
value={bw.weight ?? ''} </tr>
onChange={handleWeightChangeWrapper(idx)} <tr>
onBlur={formik.handleBlur} <td className='py-3 font-medium'>Feed Intake (KG)</td>
decimalScale={2} <td className='text-center py-3'>
allowNegative={false} <span className='font-semibold'>
thousandSeparator=',' {initialValues.feed_intake &&
decimalSeparator='.' initialValues.feed_intake > 0
inputSuffix='gram' ? formatNumber(initialValues.feed_intake)
placeholder='Masukkan berat total...' : '-'}
isError={ </span>
isRepeaterInputError('body_weights', 'weight', idx) </td>
.isError <td className='text-center py-3 text-gray-600'>
} {initialValues.feed_intake_std &&
errorMessage={ initialValues.feed_intake_std > 0
isRepeaterInputError('body_weights', 'weight', idx) ? formatNumber(initialValues.feed_intake_std)
.errorMessage : '-'}
}
readOnly={type === 'detail'}
className={{
wrapper: 'w-full min-w-32',
}}
/>
</td> </td>
<td>
<NumberInput
required
name={`body_weights.${idx}.qty`}
value={bw.qty ?? ''}
onChange={handleQtyChangeWrapper(idx)}
onBlur={formik.handleBlur}
decimalScale={0}
allowNegative={false}
thousandSeparator=','
decimalSeparator='.'
placeholder='Masukkan jumlah ayam...'
bottomLabel={
formik.values.project_flock_kandang_id &&
type === 'add'
? `Total Ayam: ${
getLatestTotalChickQty(
formik.values.project_flock_kandang_id
) !== null
? formatNumber(
getLatestTotalChickQty(
formik.values.project_flock_kandang_id
)!
)
: 'N/A'
}`
: undefined
}
isError={
isRepeaterInputError('body_weights', 'qty', idx)
.isError ||
(bw.qty
? getTotalChickQtyError(Number(bw.qty)) !== null
: false)
}
errorMessage={
isRepeaterInputError('body_weights', 'qty', idx)
.errorMessage ||
(bw.qty
? getTotalChickQtyError(Number(bw.qty)) ||
undefined
: undefined)
}
readOnly={type === 'detail'}
className={{
wrapper: 'w-full min-w-24',
}}
/>
</td>
<td>
<NumberInput
required
name={`body_weights.${idx}.avg_weight`}
value={bw.avg_weight ?? ''}
onChange={handleAvgWeightChangeWrapper(idx)}
onBlur={formik.handleBlur}
decimalScale={2}
allowNegative={false}
thousandSeparator=','
decimalSeparator='.'
inputSuffix='gram'
placeholder='Masukkan berat rata-rata...'
isError={
isRepeaterInputError(
'body_weights',
'avg_weight',
idx
).isError
}
errorMessage={
isRepeaterInputError(
'body_weights',
'avg_weight',
idx
).errorMessage
}
readOnly={type === 'detail'}
className={{
wrapper: 'w-full min-w-32',
}}
/>
</td>
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
<td>
<div className='flex items-center'>
<Button
type='button'
color='error'
onClick={() => removeBodyWeight(idx)}
>
<Icon
icon='mdi:trash-can'
width={24}
height={24}
/>
</Button>
</div>
</td>
)}
</tr> </tr>
))}
</tbody> </tbody>
</table> </table>
</div> </div>
{(type as 'add' | 'edit' | 'detail') !== 'detail' && ( </div>
<div className='flex justify-center items-center mt-4 gap-4'>
{selectedBodyWeights.length > 0 && ( {/* Mortality Section */}
<Button <div className='border border-gray-200 rounded-lg bg-white'>
type='button' <div className='px-4 py-3 border-b border-gray-200'>
color='error' <span className='card-title font-bold text-xl'>
onClick={removeSelectedBodyWeights} Mortalitas
disabled={selectedBodyWeights.length === 0} </span>
className='w-fit' </div>
<div className='p-4'>
<table className='w-full text-sm'>
<thead>
<tr className='border-b border-gray-200'>
<th
colSpan={2}
className='text-center py-2 font-semibold text-gray-600'
> >
<Icon icon='mdi:trash-can' width={24} height={24} /> DEPLESI KUMULATIF
Hapus Terpilih ({selectedBodyWeights.length}) </th>
</Button> </tr>
)} <tr className='border-b border-gray-200'>
<Button <th className='text-center py-2 font-semibold text-xs text-gray-500'>
type='button' Total
color='success' </th>
onClick={addBodyWeight} <th className='text-center py-2 font-semibold text-xs text-gray-500'>
className='w-fit' (%)
</th>
</tr>
</thead>
<tbody>
<tr>
<td className='text-center py-3 border-r border-gray-100'>
<span className='font-semibold'>
{initialValues.total_depletion_qty &&
initialValues.total_depletion_qty > 0
? formatNumber(initialValues.total_depletion_qty)
: '-'}
</span>
</td>
<td className='text-center py-3 text-gray-600'>
{initialValues.cum_depletion_rate &&
initialValues.cum_depletion_rate > 0
? initialValues.cum_depletion_rate.toFixed(2)
: '-'}
</td>
</tr>
<tr>
<td
colSpan={2}
className='text-center py-3 border-r border-gray-200 text-gray-600'
> >
<Icon icon='ic:round-plus' width={24} height={24} /> Total Ayam
Tambah Bobot Badan </td>
</Button> </tr>
<tr>
<td
colSpan={2}
className='text-center py-3 font-semibold'
>
{initialValues.total_chick_qty &&
initialValues.total_chick_qty > 0
? formatNumber(initialValues.total_chick_qty)
: '-'}
</td>
</tr>
</tbody>
</table>
</div>
</div>
{/* Egg Production Section - Only for LAYING category */}
{type === 'detail' &&
initialValues &&
initialValues.project_flock_category === 'LAYING' && (
<div className='border border-gray-200 rounded-lg bg-white'>
<div className='px-4 py-3 border-b border-gray-200'>
<span className='card-title font-bold text-xl'>
Produksi
</span>
</div>
<div className='p-4'>
<table className='w-full text-sm'>
<thead>
<tr className='border-b border-gray-200'>
<th className='text-left py-2 font-semibold text-gray-600'></th>
<th className='text-center py-2 font-semibold text-gray-600'>
AKTUAL
</th>
<th className='text-center py-2 font-semibold text-gray-600'>
STANDAR
</th>
</tr>
</thead>
<tbody>
<tr>
<td className='py-3 font-medium'>Egg Mass</td>
<td className='text-center py-3'>
<span className='font-semibold'>
{initialValues.egg_mesh &&
initialValues.egg_mesh > 0
? formatNumber(initialValues.egg_mesh)
: '-'}
</span>
</td>
<td className='text-center py-3 text-gray-600'>
{initialValues.egg_mesh_std &&
initialValues.egg_mesh_std > 0
? formatNumber(initialValues.egg_mesh_std)
: '-'}
</td>
</tr>
<tr>
<td className='py-3 font-medium'>
Egg Weight (KG)
</td>
<td className='text-center py-3'>
<span className='font-semibold'>
{initialValues.egg_weight &&
initialValues.egg_weight > 0
? formatNumber(initialValues.egg_weight)
: '-'}
</span>
</td>
<td className='text-center py-3 text-gray-600'>
{initialValues.egg_weight_std &&
initialValues.egg_weight_std > 0
? formatNumber(initialValues.egg_weight_std)
: '-'}
</td>
</tr>
<tr>
<td className='py-3 font-medium'>Hen Day</td>
<td className='text-center py-3'>
<span className='font-semibold'>
{initialValues.hand_day &&
initialValues.hand_day > 0
? formatNumber(initialValues.hand_day)
: '-'}
</span>
</td>
<td className='text-center py-3 text-gray-600'>
{initialValues.hand_day_std !== undefined &&
initialValues.hand_day_std > 0
? `${initialValues.hand_day_std}%`
: '-'}
</td>
</tr>
<tr>
<td className='py-3 font-medium'>Hen House</td>
<td className='text-center py-3'>
<span className='font-semibold'>
{initialValues.hand_house &&
initialValues.hand_house > 0
? formatNumber(initialValues.hand_house)
: '-'}
</span>
</td>
<td className='text-center py-3 text-gray-600'>
{initialValues.hand_house_std !== undefined &&
initialValues.hand_house_std > 0
? `${initialValues.hand_house_std}%`
: '-'}
</td>
</tr>
</tbody>
</table>
</div>
</div>
)}
</div> </div>
)} )}
</Card>
{/* Stocks Table */} {/* Stocks Table */}
<Card <Card
+14 -14
View File
@@ -4,12 +4,23 @@ import { ProductWarehouse } from '@/types/api/inventory/product-warehouse';
export type ProductionMetrics = { export type ProductionMetrics = {
total_depletion_qty: number; total_depletion_qty: number;
cum_depletion_rate: number; cum_depletion_rate: number;
daily_gain: number;
avg_daily_gain: number;
cum_intake: number; cum_intake: number;
fcr_value: number; fcr_value: number;
fcr_std?: number;
total_chick_qty: number; total_chick_qty: number;
cum_depletion: number; hand_day?: number;
hand_house?: number;
feed_intake?: number;
egg_mesh?: number;
egg_weight?: number;
hand_day_std?: number;
hand_house_std?: number;
feed_intake_std?: number;
egg_mesh_std?: number;
egg_weight_std?: number;
daily_gain?: number;
avg_daily_gain?: number;
cum_depletion?: number;
}; };
export type BaseRecording = { export type BaseRecording = {
@@ -20,12 +31,6 @@ export type BaseRecording = {
project_flock_category?: 'GROWING' | 'LAYING'; project_flock_category?: 'GROWING' | 'LAYING';
} & ProductionMetrics; } & ProductionMetrics;
export type RecordingBW = {
avg_weight: number;
qty: number;
total_weight: number;
};
export type RecordingDepletion = { export type RecordingDepletion = {
product_warehouse_id: number; product_warehouse_id: number;
qty: number; qty: number;
@@ -63,7 +68,6 @@ export type Recording = BaseMetadata &
BaseRecording & { BaseRecording & {
approval?: BaseApproval; approval?: BaseApproval;
created_user: User; created_user: User;
body_weights?: RecordingBW[];
depletions?: RecordingDepletion[]; depletions?: RecordingDepletion[];
stocks?: RecordingStock[]; stocks?: RecordingStock[];
eggs?: RecordingEgg[]; eggs?: RecordingEgg[];
@@ -77,10 +81,6 @@ export type NextDayRecording = {
export type CreateGrowingRecordingPayload = { export type CreateGrowingRecordingPayload = {
project_flock_kandang_id: number; project_flock_kandang_id: number;
body_weights: {
avg_weight: number;
qty: number;
}[];
stocks?: { stocks?: {
product_warehouse_id: number; product_warehouse_id: number;
qty: number; qty: number;