mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 21:41:57 +00:00
refactor(FE-114): simplify CreateRecordingPayload structure and update validation in RecordingForm
This commit is contained in:
@@ -19,14 +19,6 @@ export const RecordingFormSchema = Yup.object({
|
||||
(value) => value !== undefined && value !== null && value > 0
|
||||
)
|
||||
.required('Project Flock Kandang wajib diisi!'),
|
||||
record_datetime: Yup.date()
|
||||
.required('Tanggal dan waktu recording wajib diisi')
|
||||
.typeError('Format tanggal dan waktu tidak valid'),
|
||||
status: Yup.number()
|
||||
.optional()
|
||||
.oneOf([0, 1, 2, 3], 'Status tidak valid')
|
||||
.typeError('Status harus berupa angka!'),
|
||||
ontime: Yup.boolean().optional(),
|
||||
body_weights: Yup.array()
|
||||
.of(
|
||||
Yup.object({
|
||||
@@ -44,7 +36,6 @@ export const RecordingFormSchema = Yup.object({
|
||||
.min(0, 'Rata-rata berat tidak boleh negatif!')
|
||||
.typeError('Rata-rata berat harus berupa angka!')
|
||||
.default(0),
|
||||
notes: Yup.string().optional(),
|
||||
})
|
||||
)
|
||||
.min(1, 'Minimal harus ada 1 data bobot badan!')
|
||||
@@ -56,14 +47,6 @@ export const RecordingFormSchema = Yup.object({
|
||||
.required('Produk wajib diisi!')
|
||||
.min(1, 'Produk wajib diisi!')
|
||||
.typeError('Produk harus berupa angka!'),
|
||||
increase: Yup.number()
|
||||
.optional()
|
||||
.min(0, 'Penambahan tidak boleh negatif!')
|
||||
.typeError('Penambahan harus berupa angka!'),
|
||||
decrease: Yup.number()
|
||||
.optional()
|
||||
.min(0, 'Pengurangan tidak boleh negatif!')
|
||||
.typeError('Pengurangan harus berupa angka!'),
|
||||
usage_amount: Yup.number()
|
||||
.optional()
|
||||
.min(0, 'Jumlah penggunaan tidak boleh negatif!')
|
||||
@@ -77,10 +60,14 @@ export const RecordingFormSchema = Yup.object({
|
||||
.of(
|
||||
Yup.object({
|
||||
product_warehouse_id: Yup.number()
|
||||
.required('Produk wajib diisi!')
|
||||
.optional()
|
||||
.min(1, 'Produk wajib diisi!')
|
||||
.typeError('Produk harus berupa angka!'),
|
||||
condition: Yup.string()
|
||||
total: Yup.number()
|
||||
.required('Jumlah depletions wajib diisi!')
|
||||
.min(1, 'Jumlah depletions minimal 1!')
|
||||
.typeError('Jumlah depletions harus berupa angka!'),
|
||||
notes: Yup.string()
|
||||
.required('Kondisi depletions wajib diisi!')
|
||||
.oneOf(
|
||||
RECORDING_FLAG_OPTIONS.map((option) => option.value),
|
||||
@@ -88,11 +75,6 @@ export const RecordingFormSchema = Yup.object({
|
||||
)
|
||||
.typeError('Kondisi depletions harus berupa teks!')
|
||||
.min(1, 'Kondisi depletions wajib diisi!'),
|
||||
total: Yup.number()
|
||||
.required('Jumlah depletions wajib diisi!')
|
||||
.min(1, 'Jumlah depletions minimal 1!')
|
||||
.typeError('Jumlah depletions harus berupa angka!'),
|
||||
notes: Yup.string().optional(),
|
||||
})
|
||||
)
|
||||
.min(1, 'Minimal harus ada 1 data depletions!')
|
||||
@@ -119,39 +101,28 @@ export const getRecordingFormInitialValues = (
|
||||
}
|
||||
: null,
|
||||
project_flock_kandang_id: initialValues?.project_flock_kandang_id ?? 0,
|
||||
record_datetime: initialValues?.record_datetime
|
||||
? new Date(initialValues.record_datetime)
|
||||
: new Date(),
|
||||
status: initialValues?.status ?? 1,
|
||||
ontime: initialValues?.ontime ?? true,
|
||||
body_weights: initialValues?.body_weights?.map(
|
||||
(bw: NonNullable<CreateRecordingPayload['body_weights']>[0]) => ({
|
||||
weight: bw.weight,
|
||||
qty: bw.qty,
|
||||
average_weight: bw.qty > 0 ? Math.round(bw.weight / bw.qty) : 0,
|
||||
notes: bw.notes || '',
|
||||
})
|
||||
) ?? [
|
||||
{
|
||||
weight: 0,
|
||||
qty: 1,
|
||||
average_weight: 0,
|
||||
notes: '',
|
||||
},
|
||||
],
|
||||
stocks: initialValues?.stocks?.map(
|
||||
(stock: NonNullable<CreateRecordingPayload['stocks']>[0]) => ({
|
||||
product_warehouse_id: stock.product_warehouse_id,
|
||||
increase: stock.increase,
|
||||
decrease: stock.decrease,
|
||||
usage_amount: stock.usage_amount,
|
||||
notes: stock.notes,
|
||||
})
|
||||
) ?? [
|
||||
{
|
||||
product_warehouse_id: 0,
|
||||
increase: 0,
|
||||
decrease: 0,
|
||||
usage_amount: 0,
|
||||
notes: '',
|
||||
},
|
||||
@@ -159,14 +130,12 @@ export const getRecordingFormInitialValues = (
|
||||
depletions: initialValues?.depletions?.map(
|
||||
(depletion: NonNullable<CreateRecordingPayload['depletions']>[0]) => ({
|
||||
product_warehouse_id: depletion.product_warehouse_id,
|
||||
condition: depletion.condition,
|
||||
total: depletion.total,
|
||||
notes: depletion.notes,
|
||||
})
|
||||
) ?? [
|
||||
{
|
||||
product_warehouse_id: 0,
|
||||
condition: '',
|
||||
total: 0,
|
||||
notes: '',
|
||||
},
|
||||
|
||||
@@ -28,6 +28,7 @@ import { ProjectFlockApi } from '@/services/api/production';
|
||||
import { LocationApi } from '@/services/api/master-data';
|
||||
import { ProductWarehouseApi } from '@/services/api/inventory';
|
||||
import { isResponseSuccess } from '@/lib/api-helper';
|
||||
import { RECORDING_FLAG_OPTIONS } from '@/config/constant';
|
||||
|
||||
import Card from '@/components/Card';
|
||||
|
||||
@@ -141,12 +142,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
onSubmit: async (values) => {
|
||||
const payload: CreateRecordingPayload = {
|
||||
project_flock_kandang_id: values.project_flock_kandang_id,
|
||||
record_datetime:
|
||||
values.record_datetime instanceof Date
|
||||
? values.record_datetime.toISOString()
|
||||
: '',
|
||||
status: values.status,
|
||||
ontime: values.ontime,
|
||||
body_weights: (values.body_weights ?? []).map((bw) => ({
|
||||
weight:
|
||||
typeof bw.weight === 'number'
|
||||
@@ -156,28 +151,16 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
typeof bw.qty === 'number'
|
||||
? bw.qty
|
||||
: parseFloat(String(bw.qty)) || 0,
|
||||
notes: bw.notes,
|
||||
// average_weight is not included in payload as it's calculated field only
|
||||
})),
|
||||
stocks: (values.stocks ?? []).map((stock) => ({
|
||||
product_warehouse_id: stock.product_warehouse_id,
|
||||
increase:
|
||||
typeof stock.increase === 'number'
|
||||
? stock.increase
|
||||
: parseFloat(String(stock.increase)) || 0,
|
||||
decrease:
|
||||
typeof stock.decrease === 'number'
|
||||
? stock.decrease
|
||||
: parseFloat(String(stock.decrease)) || 0,
|
||||
usage_amount:
|
||||
typeof stock.usage_amount === 'number'
|
||||
? stock.usage_amount
|
||||
: parseFloat(String(stock.usage_amount)) || 0,
|
||||
notes: stock.notes,
|
||||
notes: stock.notes || '',
|
||||
})),
|
||||
depletions: (values.depletions ?? []).map((depletion) => ({
|
||||
product_warehouse_id: depletion.product_warehouse_id,
|
||||
condition: depletion.condition,
|
||||
total:
|
||||
typeof depletion.total === 'number'
|
||||
? depletion.total
|
||||
@@ -236,42 +219,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
// EVENT HANDLERS - Date Time
|
||||
const recordDateTimeChangeHandler = (datetime: Date | null) => {
|
||||
formik.setFieldValue('record_datetime', datetime, false);
|
||||
|
||||
// Auto-set ontime based on date difference
|
||||
if (datetime) {
|
||||
const today = new Date();
|
||||
const recordDate = new Date(datetime);
|
||||
|
||||
// Reset time to compare only dates
|
||||
const todayDateOnly = new Date(today.getFullYear(), today.getMonth(), today.getDate());
|
||||
const recordDateOnly = new Date(recordDate.getFullYear(), recordDate.getMonth(), recordDate.getDate());
|
||||
|
||||
// Set ontime to true if recording date is today, false otherwise
|
||||
const isOnTime = todayDateOnly.getTime() === recordDateOnly.getTime();
|
||||
formik.setFieldValue('ontime', isOnTime, false);
|
||||
}
|
||||
};
|
||||
|
||||
// Set initial ontime value when form loads or record_datetime changes
|
||||
useEffect(() => {
|
||||
if (formik.values.record_datetime) {
|
||||
const today = new Date();
|
||||
const recordDate = new Date(formik.values.record_datetime);
|
||||
|
||||
// Reset time to compare only dates
|
||||
const todayDateOnly = new Date(today.getFullYear(), today.getMonth(), today.getDate());
|
||||
const recordDateOnly = new Date(recordDate.getFullYear(), recordDate.getMonth(), recordDate.getDate());
|
||||
|
||||
// Set ontime to true if recording date is today, false otherwise
|
||||
const isOnTime = todayDateOnly.getTime() === recordDateOnly.getTime();
|
||||
|
||||
// Only update if ontime is not set or different from calculated value
|
||||
if (formik.values.ontime !== isOnTime) {
|
||||
formik.setFieldValue('ontime', isOnTime, false);
|
||||
}
|
||||
}
|
||||
}, [formik.values.record_datetime]);
|
||||
|
||||
// Auto-calculate average weight when weight or qty changes (but not when editing average weight manually)
|
||||
useEffect(() => {
|
||||
if (formik.values.body_weights && editingAverageIndex === null) {
|
||||
@@ -315,7 +264,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
{
|
||||
weight: 0,
|
||||
qty: 1,
|
||||
notes: '',
|
||||
average_weight: 0,
|
||||
},
|
||||
];
|
||||
@@ -415,8 +363,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
...(formik.values.stocks || []),
|
||||
{
|
||||
product_warehouse_id: 0,
|
||||
increase: 0,
|
||||
decrease: 0,
|
||||
usage_amount: 0,
|
||||
notes: '',
|
||||
},
|
||||
@@ -476,8 +422,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
const newDepletions = [
|
||||
...(formik.values.depletions || []),
|
||||
{
|
||||
product_warehouse_id: 0,
|
||||
condition: '',
|
||||
total: 0,
|
||||
notes: '',
|
||||
},
|
||||
@@ -597,49 +541,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
isClearable
|
||||
isSearchable
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
required
|
||||
label='Tanggal & Waktu Recording'
|
||||
type='datetime-local'
|
||||
name='record_datetime'
|
||||
value={
|
||||
formik.values.record_datetime instanceof Date
|
||||
? formik.values.record_datetime
|
||||
.toISOString()
|
||||
.substring(0, 16)
|
||||
: ''
|
||||
}
|
||||
onChange={(e) => {
|
||||
const datetime = e.target.value
|
||||
? new Date(e.target.value)
|
||||
: null;
|
||||
recordDateTimeChangeHandler(datetime);
|
||||
}}
|
||||
onBlur={formik.handleBlur}
|
||||
isError={
|
||||
formik.touched.record_datetime &&
|
||||
Boolean(formik.errors.record_datetime)
|
||||
}
|
||||
errorMessage={formik.errors.record_datetime as string}
|
||||
readOnly={type === 'detail'}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label='Status'
|
||||
type='number'
|
||||
name='status'
|
||||
value={formik.values.status?.toString() || ''}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
formik.setFieldValue('status', parseInt(value) || 0);
|
||||
}}
|
||||
onBlur={formik.handleBlur}
|
||||
isError={formik.touched.status && Boolean(formik.errors.status)}
|
||||
errorMessage={formik.errors.status as string}
|
||||
readOnly={type === 'detail'}
|
||||
placeholder='Masukkan status (0-3)'
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -710,7 +611,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
<Icon icon="material-symbols:info-outline" width={16} height={16} />
|
||||
</span>
|
||||
</th>
|
||||
<th>Catatan</th>
|
||||
{type !== 'detail' && <th>Action</th>}
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -823,19 +723,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<TextInput
|
||||
name={`body_weights.${idx}.notes`}
|
||||
value={bw.notes || ''}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
placeholder='Catatan...'
|
||||
readOnly={type === 'detail'}
|
||||
className={{
|
||||
wrapper: 'w-full min-w-32',
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
{type !== 'detail' && (
|
||||
<td>
|
||||
<div className='flex justify-center'>
|
||||
@@ -928,8 +815,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
<span className='text-error'>*</span>
|
||||
</span>
|
||||
</th>
|
||||
<th>Penambahan</th>
|
||||
<th>Pengurangan</th>
|
||||
<th>Jumlah Pakai</th>
|
||||
<th>Catatan</th>
|
||||
{type !== 'detail' && <th>Action</th>}
|
||||
@@ -998,59 +883,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
isSearchable
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<NumberInput
|
||||
name={`stocks.${idx}.increase`}
|
||||
value={stock.increase || 0}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
maskType='number'
|
||||
decimals={0}
|
||||
min={0}
|
||||
thousandSeparator=','
|
||||
decimalSeparator='.'
|
||||
isError={
|
||||
isRepeaterInputError('stocks', 'increase', idx)
|
||||
.isError
|
||||
}
|
||||
errorMessage={
|
||||
isRepeaterInputError('stocks', 'increase', idx)
|
||||
.errorMessage
|
||||
}
|
||||
readOnly={type === 'detail'}
|
||||
className={{
|
||||
wrapper: 'w-full min-w-24',
|
||||
}}
|
||||
placeholder='Penambahan'
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<NumberInput
|
||||
name={`stocks.${idx}.decrease`}
|
||||
value={stock.decrease || 0}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
maskType='number'
|
||||
decimals={0}
|
||||
min={0}
|
||||
thousandSeparator=','
|
||||
decimalSeparator='.'
|
||||
isError={
|
||||
isRepeaterInputError('stocks', 'decrease', idx)
|
||||
.isError
|
||||
}
|
||||
errorMessage={
|
||||
isRepeaterInputError('stocks', 'decrease', idx)
|
||||
.errorMessage
|
||||
}
|
||||
readOnly={type === 'detail'}
|
||||
className={{
|
||||
wrapper: 'w-full min-w-24',
|
||||
}}
|
||||
placeholder='Pengurangan'
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<td>
|
||||
<NumberInput
|
||||
name={`stocks.${idx}.usage_amount`}
|
||||
value={stock.usage_amount || 0}
|
||||
@@ -1173,23 +1006,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
/>
|
||||
</th>
|
||||
)}
|
||||
<th>
|
||||
Product Warehouse ID
|
||||
<span
|
||||
className='tooltip tooltip-error tooltip-bottom z-[9999]'
|
||||
data-tip='required'
|
||||
>
|
||||
<span className='text-error'>*</span>
|
||||
</span>
|
||||
</th>
|
||||
<th>
|
||||
Kondisi
|
||||
<span
|
||||
className='tooltip tooltip-error tooltip-bottom z-[9999]'
|
||||
data-tip='required'
|
||||
>
|
||||
<span className='text-error'>*</span>
|
||||
</span>
|
||||
</th>
|
||||
<th>
|
||||
Total
|
||||
@@ -1198,9 +1016,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
data-tip='required'
|
||||
>
|
||||
<span className='text-error'>*</span>
|
||||
</span>
|
||||
</span>
|
||||
</th>
|
||||
<th>Catatan</th>
|
||||
{type !== 'detail' && <th>Action</th>}
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -1232,56 +1049,32 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
</td>
|
||||
)}
|
||||
<td>
|
||||
<TextInput
|
||||
required
|
||||
name={`depletions.${idx}.product_warehouse_id`}
|
||||
type='number'
|
||||
value={
|
||||
depletion.product_warehouse_id?.toString() || ''
|
||||
}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
isError={
|
||||
isRepeaterInputError(
|
||||
'depletions',
|
||||
'product_warehouse_id',
|
||||
idx
|
||||
).isError
|
||||
}
|
||||
errorMessage={
|
||||
isRepeaterInputError(
|
||||
'depletions',
|
||||
'product_warehouse_id',
|
||||
idx
|
||||
).errorMessage
|
||||
}
|
||||
readOnly={type === 'detail'}
|
||||
className={{
|
||||
wrapper: 'w-full min-w-32',
|
||||
<SelectInput
|
||||
value={RECORDING_FLAG_OPTIONS.find(
|
||||
(option) => option.value === depletion.notes
|
||||
) || null}
|
||||
onChange={(selectedOption) => {
|
||||
const option = selectedOption as OptionType | null;
|
||||
formik.setFieldValue(
|
||||
`depletions.${idx}.notes`,
|
||||
option?.value || ''
|
||||
);
|
||||
}}
|
||||
placeholder='Product Warehouse ID'
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<TextInput
|
||||
required
|
||||
name={`depletions.${idx}.condition`}
|
||||
value={depletion.condition || ''}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
options={RECORDING_FLAG_OPTIONS}
|
||||
placeholder='Pilih Kondisi'
|
||||
isError={
|
||||
isRepeaterInputError('depletions', 'condition', idx)
|
||||
isRepeaterInputError('depletions', 'notes', idx)
|
||||
.isError
|
||||
}
|
||||
errorMessage={
|
||||
isRepeaterInputError('depletions', 'condition', idx)
|
||||
isRepeaterInputError('depletions', 'notes', idx)
|
||||
.errorMessage
|
||||
}
|
||||
readOnly={type === 'detail'}
|
||||
isDisabled={type === 'detail'}
|
||||
className={{
|
||||
wrapper: 'w-full min-w-32',
|
||||
}}
|
||||
placeholder='Kondisi (MATI/SAKIT/HILANG)'
|
||||
isSearchable={false}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
@@ -1311,19 +1104,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
placeholder='Total'
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<TextInput
|
||||
name={`depletions.${idx}.notes`}
|
||||
value={depletion.notes || ''}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
placeholder='Catatan...'
|
||||
readOnly={type === 'detail'}
|
||||
className={{
|
||||
wrapper: 'w-full min-w-32',
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
{type !== 'detail' && (
|
||||
<td>
|
||||
<div className='flex justify-center'>
|
||||
|
||||
+5
-12
@@ -27,26 +27,19 @@ export type Recording = BaseMetadata & BaseRecording;
|
||||
|
||||
export type CreateRecordingPayload = {
|
||||
project_flock_kandang_id: number;
|
||||
record_datetime: string;
|
||||
status?: number;
|
||||
ontime?: boolean;
|
||||
body_weights?: {
|
||||
body_weights: {
|
||||
weight: number;
|
||||
qty: number;
|
||||
notes?: string;
|
||||
}[];
|
||||
stocks?: {
|
||||
product_warehouse_id: number;
|
||||
increase?: number;
|
||||
decrease?: number;
|
||||
usage_amount?: number;
|
||||
notes?: string;
|
||||
usage_amount: number;
|
||||
notes: string;
|
||||
}[];
|
||||
depletions?: {
|
||||
product_warehouse_id: number;
|
||||
condition: string;
|
||||
product_warehouse_id?: number;
|
||||
total: number;
|
||||
notes?: string;
|
||||
notes: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user