mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-24 23:35:45 +00:00
refactor(US-170,174): update recording types and validation schema for daily recording form
This commit is contained in:
@@ -1,16 +1,15 @@
|
|||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import { RECORDING_FLAG_OPTIONS } from '@/config/constant';
|
|
||||||
import {
|
import {
|
||||||
Recording,
|
Recording,
|
||||||
CreateRecordingPayload,
|
CreateGrowingRecordingPayload,
|
||||||
} from '@/types/api/production/recording';
|
} from '@/types/api/production/recording';
|
||||||
|
|
||||||
export const RecordingFormSchema = Yup.object({
|
export const RecordingGrowingFormSchema = Yup.object({
|
||||||
project_flock_kandang: Yup.object({
|
project_flock_kandang: Yup.object({
|
||||||
value: Yup.number().min(1).required(),
|
value: Yup.number().min(1).required(),
|
||||||
label: Yup.string().required(),
|
label: Yup.string().required(),
|
||||||
}).nullable(),
|
}).nullable(),
|
||||||
project_flock_kandang_id: Yup.number()
|
project_flock_kandangs_id: Yup.number()
|
||||||
.default(0)
|
.default(0)
|
||||||
.typeError('Project Flock Kandang wajib diisi!')
|
.typeError('Project Flock Kandang wajib diisi!')
|
||||||
.test(
|
.test(
|
||||||
@@ -22,9 +21,13 @@ export const RecordingFormSchema = Yup.object({
|
|||||||
.test(
|
.test(
|
||||||
'not-already-recorded',
|
'not-already-recorded',
|
||||||
'Project Flock ini sudah direcord hari ini!',
|
'Project Flock ini sudah direcord hari ini!',
|
||||||
function(value) {
|
function (value) {
|
||||||
const recordedProjectFlockIds = this.options.context?.recordedProjectFlockIds as Set<number>;
|
const recordedProjectFlockIds = this.options.context
|
||||||
const formType = this.options.context?.type as 'add' | 'edit' | 'detail';
|
?.recordedProjectFlockIds as Set<number>;
|
||||||
|
const formType = this.options.context?.type as
|
||||||
|
| 'add'
|
||||||
|
| 'edit'
|
||||||
|
| 'detail';
|
||||||
if (formType !== 'add') return true;
|
if (formType !== 'add') return true;
|
||||||
if (value && recordedProjectFlockIds?.has(value)) {
|
if (value && recordedProjectFlockIds?.has(value)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -35,20 +38,15 @@ export const RecordingFormSchema = Yup.object({
|
|||||||
body_weights: Yup.array()
|
body_weights: Yup.array()
|
||||||
.of(
|
.of(
|
||||||
Yup.object({
|
Yup.object({
|
||||||
weight: Yup.number()
|
avg_weight: Yup.number()
|
||||||
.required('Berat ayam wajib diisi!')
|
.required('Berat ayam rata-rata wajib diisi!')
|
||||||
.min(1, 'Berat ayam minimal 1 gram!')
|
.min(1, 'Berat ayam rata-rata minimal 1 gram!')
|
||||||
.typeError('Berat ayam harus berupa angka!'),
|
.typeError('Berat ayam rata-rata harus berupa angka!'),
|
||||||
qty: Yup.number()
|
qty: Yup.number()
|
||||||
.required('Jumlah ayam wajib diisi!')
|
.required('Jumlah ayam wajib diisi!')
|
||||||
.min(1, 'Jumlah ayam minimal 1 ekor!')
|
.min(1, 'Jumlah ayam minimal 1 ekor!')
|
||||||
.typeError('Jumlah ayam harus berupa angka!')
|
.typeError('Jumlah ayam harus berupa angka!')
|
||||||
.default(1),
|
.default(1),
|
||||||
average_weight: Yup.number()
|
|
||||||
.optional()
|
|
||||||
.min(0, 'Rata-rata berat tidak boleh negatif!')
|
|
||||||
.typeError('Rata-rata berat harus berupa angka!')
|
|
||||||
.default(0),
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.min(1, 'Minimal harus ada 1 data bobot badan!')
|
.min(1, 'Minimal harus ada 1 data bobot badan!')
|
||||||
@@ -60,163 +58,97 @@ export const RecordingFormSchema = Yup.object({
|
|||||||
.required('Produk wajib diisi!')
|
.required('Produk wajib diisi!')
|
||||||
.min(1, 'Produk wajib diisi!')
|
.min(1, 'Produk wajib diisi!')
|
||||||
.typeError('Produk harus berupa angka!'),
|
.typeError('Produk harus berupa angka!'),
|
||||||
usage_amount: Yup.number()
|
usage_qty: Yup.number()
|
||||||
.required('Jumlah penggunaan wajib diisi!')
|
.required('Jumlah penggunaan wajib diisi!')
|
||||||
.min(0, 'Jumlah penggunaan tidak boleh negatif!')
|
.min(0, 'Jumlah penggunaan tidak boleh negatif!')
|
||||||
.typeError('Jumlah penggunaan harus berupa angka!'),
|
.typeError('Jumlah penggunaan harus berupa angka!'),
|
||||||
notes: Yup.string().optional(),
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.min(1, 'Minimal harus ada 1 data stok!')
|
.min(1, 'Minimal harus ada 1 data stok!')
|
||||||
.required('Data stok wajib diisi!'),
|
.required('Data stok wajib diisi!'),
|
||||||
depletions: Yup.array()
|
depletions: Yup.array()
|
||||||
.of(
|
|
||||||
Yup.object({
|
|
||||||
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),
|
|
||||||
'Kondisi depletions tidak valid!'
|
|
||||||
)
|
|
||||||
.typeError('Kondisi depletions harus berupa teks!')
|
|
||||||
.min(1, 'Kondisi depletions wajib diisi!'),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.min(1, 'Minimal harus ada 1 data depletions!')
|
|
||||||
.required('Data depletions wajib diisi!'),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const UpdateRecordingFormSchema = Yup.object({
|
|
||||||
project_flock_kandang: Yup.object({
|
|
||||||
value: Yup.number().min(1).required(),
|
|
||||||
label: Yup.string().required(),
|
|
||||||
}).nullable(),
|
|
||||||
project_flock_kandang_id: Yup.number()
|
|
||||||
.default(0)
|
|
||||||
.typeError('Project Flock Kandang wajib diisi!')
|
|
||||||
.test(
|
|
||||||
'is-valid-project-flock-kandang',
|
|
||||||
'Project Flock Kandang wajib diisi!',
|
|
||||||
(value) => value !== undefined && value !== null && value > 0
|
|
||||||
)
|
|
||||||
.required('Project Flock Kandang wajib diisi!'),
|
|
||||||
body_weights: Yup.array()
|
|
||||||
.of(
|
|
||||||
Yup.object({
|
|
||||||
weight: Yup.number()
|
|
||||||
.required('Berat ayam wajib diisi!')
|
|
||||||
.min(1, 'Berat ayam minimal 1 gram!')
|
|
||||||
.typeError('Berat ayam harus berupa angka!'),
|
|
||||||
qty: Yup.number()
|
|
||||||
.required('Jumlah ayam wajib diisi!')
|
|
||||||
.min(1, 'Jumlah ayam minimal 1 ekor!')
|
|
||||||
.typeError('Jumlah ayam harus berupa angka!')
|
|
||||||
.default(1),
|
|
||||||
average_weight: Yup.number()
|
|
||||||
.optional()
|
|
||||||
.min(0, 'Rata-rata berat tidak boleh negatif!')
|
|
||||||
.typeError('Rata-rata berat harus berupa angka!')
|
|
||||||
.default(0),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.min(1, 'Minimal harus ada 1 data bobot badan!')
|
|
||||||
.required('Data bobot badan wajib diisi!'),
|
|
||||||
stocks: Yup.array()
|
|
||||||
.of(
|
.of(
|
||||||
Yup.object({
|
Yup.object({
|
||||||
product_warehouse_id: Yup.number()
|
product_warehouse_id: Yup.number()
|
||||||
.required('Produk wajib diisi!')
|
.required('Produk depletions wajib diisi!')
|
||||||
.min(1, 'Produk wajib diisi!')
|
.min(1, 'Produk depletions wajib diisi!')
|
||||||
.typeError('Produk harus berupa angka!'),
|
.typeError('Produk depletions harus berupa angka!'),
|
||||||
usage_amount: Yup.number()
|
qty: Yup.number()
|
||||||
.required('Jumlah penggunaan wajib diisi!')
|
|
||||||
.min(0, 'Jumlah penggunaan tidak boleh negatif!')
|
|
||||||
.typeError('Jumlah penggunaan harus berupa angka!'),
|
|
||||||
notes: Yup.string().optional(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.min(1, 'Minimal harus ada 1 data stok!')
|
|
||||||
.required('Data stok wajib diisi!'),
|
|
||||||
depletions: Yup.array()
|
|
||||||
.of(
|
|
||||||
Yup.object({
|
|
||||||
total: Yup.number()
|
|
||||||
.required('Jumlah depletions wajib diisi!')
|
.required('Jumlah depletions wajib diisi!')
|
||||||
.min(1, 'Jumlah depletions minimal 1!')
|
.min(1, 'Jumlah depletions minimal 1!')
|
||||||
.typeError('Jumlah depletions harus berupa angka!'),
|
.typeError('Jumlah depletions harus berupa angka!'),
|
||||||
notes: Yup.string()
|
|
||||||
.required('Kondisi depletions wajib diisi!')
|
|
||||||
.oneOf(
|
|
||||||
RECORDING_FLAG_OPTIONS.map((option) => option.value),
|
|
||||||
'Kondisi depletions tidak valid!'
|
|
||||||
)
|
|
||||||
.typeError('Kondisi depletions harus berupa teks!')
|
|
||||||
.min(1, 'Kondisi depletions wajib diisi!'),
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.min(1, 'Minimal harus ada 1 data depletions!')
|
.min(1, 'Minimal harus ada 1 data depletions!')
|
||||||
.required('Data depletions wajib diisi!'),
|
.required('Data depletions wajib diisi!'),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type RecordingFormValues = Yup.InferType<typeof RecordingFormSchema>;
|
export const UpdateRecordingGrowingFormSchema =
|
||||||
|
RecordingGrowingFormSchema.shape({
|
||||||
|
project_flock_kandangs_id: Yup.number()
|
||||||
|
.default(0)
|
||||||
|
.typeError('Project Flock Kandang wajib diisi!')
|
||||||
|
.test(
|
||||||
|
'is-valid-project-flock-kandang',
|
||||||
|
'Project Flock Kandang wajib diisi!',
|
||||||
|
(value) => value !== undefined && value !== null && value > 0
|
||||||
|
)
|
||||||
|
.required('Project Flock Kandang wajib diisi!'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type RecordingGrowingFormValues = Yup.InferType<
|
||||||
|
typeof RecordingGrowingFormSchema
|
||||||
|
>;
|
||||||
|
|
||||||
type RecordingFormData = Partial<Recording> & {
|
type RecordingFormData = Partial<Recording> & {
|
||||||
body_weights?: CreateRecordingPayload['body_weights'];
|
body_weights?: CreateGrowingRecordingPayload['body_weights'];
|
||||||
stocks?: CreateRecordingPayload['stocks'];
|
stocks?: CreateGrowingRecordingPayload['stocks'];
|
||||||
depletions?: CreateRecordingPayload['depletions'];
|
depletions?: CreateGrowingRecordingPayload['depletions'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getRecordingFormInitialValues = (
|
export const getRecordingGrowingFormInitialValues = (
|
||||||
initialValues?: RecordingFormData
|
initialValues?: RecordingFormData
|
||||||
): RecordingFormValues => ({
|
): RecordingGrowingFormValues => ({
|
||||||
project_flock_kandang: initialValues?.project_flock_kandang_id
|
project_flock_kandang: initialValues?.project_flock_kandangs_id
|
||||||
? {
|
? {
|
||||||
value: initialValues.project_flock_kandang_id,
|
value: initialValues.project_flock_kandangs_id,
|
||||||
label: `Project Flock #${initialValues.project_flock_kandang_id}`,
|
label: `Project Flock #${initialValues.project_flock_kandangs_id}`,
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
project_flock_kandang_id: initialValues?.project_flock_kandang_id ?? 0,
|
project_flock_kandangs_id: initialValues?.project_flock_kandangs_id ?? 0,
|
||||||
body_weights: initialValues?.body_weights?.map(
|
body_weights: initialValues?.body_weights?.map(
|
||||||
(bw: NonNullable<CreateRecordingPayload['body_weights']>[0]) => ({
|
(bw: NonNullable<CreateGrowingRecordingPayload['body_weights']>[0]) => ({
|
||||||
weight: bw.weight,
|
avg_weight: bw.avg_weight,
|
||||||
qty: bw.qty,
|
qty: bw.qty,
|
||||||
average_weight: bw.qty > 0 ? Math.round(bw.weight / bw.qty) : 0,
|
|
||||||
})
|
})
|
||||||
) ?? [
|
) ?? [
|
||||||
{
|
{
|
||||||
weight: 0,
|
avg_weight: 0,
|
||||||
qty: 0,
|
qty: 0,
|
||||||
average_weight: 0,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
stocks: initialValues?.stocks?.map(
|
stocks: initialValues?.stocks?.map(
|
||||||
(stock: NonNullable<CreateRecordingPayload['stocks']>[0]) => ({
|
(stock: NonNullable<CreateGrowingRecordingPayload['stocks']>[0]) => ({
|
||||||
product_warehouse_id: stock.product_warehouse_id,
|
product_warehouse_id: stock.product_warehouse_id,
|
||||||
usage_amount: stock.usage_amount,
|
usage_qty: stock.usage_qty,
|
||||||
notes: stock.notes,
|
|
||||||
})
|
})
|
||||||
) ?? [
|
) ?? [
|
||||||
{
|
{
|
||||||
product_warehouse_id: 0,
|
product_warehouse_id: 0,
|
||||||
usage_amount: 0,
|
usage_qty: 0,
|
||||||
notes: '',
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
depletions: initialValues?.depletions?.map(
|
depletions: initialValues?.depletions?.map(
|
||||||
(depletion: NonNullable<CreateRecordingPayload['depletions']>[0]) => ({
|
(
|
||||||
|
depletion: NonNullable<CreateGrowingRecordingPayload['depletions']>[0]
|
||||||
|
) => ({
|
||||||
product_warehouse_id: depletion.product_warehouse_id,
|
product_warehouse_id: depletion.product_warehouse_id,
|
||||||
total: depletion.total,
|
qty: depletion.qty,
|
||||||
notes: depletion.notes,
|
|
||||||
})
|
})
|
||||||
) ?? [
|
) ?? [
|
||||||
{
|
{
|
||||||
product_warehouse_id: 0,
|
product_warehouse_id: 0,
|
||||||
total: 0,
|
qty: 0,
|
||||||
notes: '',
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useMemo, useState, useEffect, useCallback } from 'react';
|
import { useMemo, useState, useCallback } from 'react';
|
||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
@@ -14,22 +14,21 @@ import { FormHeader } from '@/components/helper/form/FormHeader';
|
|||||||
import { FormActions } from '@/components/helper/form/FormActions';
|
import { FormActions } from '@/components/helper/form/FormActions';
|
||||||
import { RecordingApi } from '@/services/api/production';
|
import { RecordingApi } from '@/services/api/production';
|
||||||
import {
|
import {
|
||||||
CreateRecordingPayload,
|
CreateGrowingRecordingPayload,
|
||||||
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 {
|
||||||
RecordingFormSchema,
|
RecordingGrowingFormSchema,
|
||||||
RecordingFormValues,
|
RecordingGrowingFormValues,
|
||||||
getRecordingFormInitialValues,
|
getRecordingGrowingFormInitialValues,
|
||||||
UpdateRecordingFormSchema,
|
UpdateRecordingGrowingFormSchema,
|
||||||
} 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';
|
||||||
import { LocationApi } from '@/services/api/master-data';
|
import { LocationApi } from '@/services/api/master-data';
|
||||||
import { ProductWarehouseApi } from '@/services/api/inventory';
|
import { ProductWarehouseApi } from '@/services/api/inventory';
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
import { RECORDING_FLAG_OPTIONS } from '@/config/constant';
|
|
||||||
import { PeriodFlock } from '@/types/api/production/project-flock';
|
import { PeriodFlock } from '@/types/api/production/project-flock';
|
||||||
import { useModal } from '@/components/Modal';
|
import { useModal } from '@/components/Modal';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
@@ -47,13 +46,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
const [selectedStocks, setSelectedStocks] = useState<number[]>([]);
|
const [selectedStocks, setSelectedStocks] = useState<number[]>([]);
|
||||||
const [selectedDepletions, setSelectedDepletions] = useState<number[]>([]);
|
const [selectedDepletions, setSelectedDepletions] = useState<number[]>([]);
|
||||||
|
|
||||||
const [editingAverageIndex, setEditingAverageIndex] = 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
|
||||||
@@ -132,7 +124,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
const recordedIds = new Set<number>();
|
const recordedIds = new Set<number>();
|
||||||
|
|
||||||
todayRecordings.forEach((recording) => {
|
todayRecordings.forEach((recording) => {
|
||||||
const recordingDate = recording.record_date?.split('T')[0];
|
const recordingDate = recording.record_datetime?.split('T')[0];
|
||||||
|
|
||||||
const isRecordedToday = recordingDate === today;
|
const isRecordedToday = recordingDate === today;
|
||||||
|
|
||||||
@@ -143,7 +135,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
isResponseSuccess(projectFlocks)
|
isResponseSuccess(projectFlocks)
|
||||||
) {
|
) {
|
||||||
const flockIndex = projectFlocks.data.findIndex(
|
const flockIndex = projectFlocks.data.findIndex(
|
||||||
(pf) => pf.id === recording.project_flock_kandang_id
|
(pf) => pf.id === recording.project_flock_kandangs_id
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
flockIndex !== undefined &&
|
flockIndex !== undefined &&
|
||||||
@@ -165,7 +157,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isRecordedToday && (isCorrectPeriod || !flockPeriodsData)) {
|
if (isRecordedToday && (isCorrectPeriod || !flockPeriodsData)) {
|
||||||
recordedIds.add(recording.project_flock_kandang_id);
|
recordedIds.add(recording.project_flock_kandangs_id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -283,45 +275,36 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
confirmationModalDeleteClickHandler,
|
confirmationModalDeleteClickHandler,
|
||||||
} = useRecordingFormHandlers(initialValues?.id);
|
} = useRecordingFormHandlers(initialValues?.id);
|
||||||
|
|
||||||
const formikInitialValues = useMemo<RecordingFormValues>(
|
const formikInitialValues = useMemo<RecordingGrowingFormValues>(
|
||||||
() => getRecordingFormInitialValues(initialValues),
|
() => getRecordingGrowingFormInitialValues(initialValues),
|
||||||
[initialValues]
|
[initialValues]
|
||||||
);
|
);
|
||||||
|
|
||||||
const formik = useFormik<RecordingFormValues>({
|
const formik = useFormik<RecordingGrowingFormValues>({
|
||||||
initialValues: formikInitialValues,
|
initialValues: formikInitialValues,
|
||||||
validationSchema:
|
validationSchema:
|
||||||
type === 'edit' ? UpdateRecordingFormSchema : RecordingFormSchema,
|
type === 'edit'
|
||||||
|
? UpdateRecordingGrowingFormSchema
|
||||||
|
: RecordingGrowingFormSchema,
|
||||||
validateOnChange: true,
|
validateOnChange: true,
|
||||||
validateOnBlur: true,
|
validateOnBlur: true,
|
||||||
onSubmit: async (values) => {
|
onSubmit: async (values) => {
|
||||||
const payload: CreateRecordingPayload = {
|
const payload: CreateGrowingRecordingPayload = {
|
||||||
project_flock_kandang_id: values.project_flock_kandang_id,
|
project_flock_kandangs_id: values.project_flock_kandangs_id,
|
||||||
body_weights: (values.body_weights ?? []).map((bw) => ({
|
body_weights: (values.body_weights ?? []).map((bw) => ({
|
||||||
weight:
|
avg_weight:
|
||||||
typeof bw.weight === 'number'
|
typeof bw.avg_weight === 'number'
|
||||||
? bw.weight
|
? bw.avg_weight
|
||||||
: parseFloat(String(bw.weight)) || 0,
|
: parseFloat(String(bw.avg_weight)) || 0,
|
||||||
qty:
|
qty: bw.qty || 0,
|
||||||
typeof bw.qty === 'number'
|
|
||||||
? bw.qty
|
|
||||||
: parseFloat(String(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,
|
||||||
usage_amount:
|
usage_qty: stock.usage_qty || 0,
|
||||||
typeof stock.usage_amount === 'number'
|
|
||||||
? stock.usage_amount
|
|
||||||
: parseFloat(String(stock.usage_amount)) || 0,
|
|
||||||
notes: stock.notes || '',
|
|
||||||
})),
|
})),
|
||||||
depletions: (values.depletions ?? []).map((depletion) => ({
|
depletions: (values.depletions ?? []).map((depletion) => ({
|
||||||
product_warehouse_id: 1,
|
product_warehouse_id: depletion.product_warehouse_id,
|
||||||
total:
|
qty: depletion.qty || 0,
|
||||||
typeof depletion.total === 'number'
|
|
||||||
? depletion.total
|
|
||||||
: parseFloat(String(depletion.total)) || 0,
|
|
||||||
notes: depletion.notes,
|
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -375,7 +358,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
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);
|
||||||
const requestedUsage = Number(stock.usage_amount) || 0;
|
const requestedUsage = Number(stock.usage_qty) || 0;
|
||||||
if (requestedUsage > availableStock) {
|
if (requestedUsage > availableStock) {
|
||||||
return `Jumlah pakai melebihi stok tersedia! Maksimal: ${availableStock.toLocaleString('en-US')}`;
|
return `Jumlah pakai melebihi stok tersedia! Maksimal: ${availableStock.toLocaleString('en-US')}`;
|
||||||
}
|
}
|
||||||
@@ -390,7 +373,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
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);
|
||||||
const requestedUsage = Number(stock.usage_amount) || 0;
|
const requestedUsage = Number(stock.usage_qty) || 0;
|
||||||
const remainingStock = availableStock - requestedUsage;
|
const remainingStock = availableStock - requestedUsage;
|
||||||
if (requestedUsage > 0) {
|
if (requestedUsage > 0) {
|
||||||
return (
|
return (
|
||||||
@@ -432,7 +415,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
color={color}
|
color={color}
|
||||||
size='sm'
|
size='sm'
|
||||||
className={{
|
className={{
|
||||||
badge: 'whitespace-nowrap font-semibold text-xs px-2 py-0.5',
|
badge: 'whitespace-nowrap font-semibold text-xs px-2',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Periode {projectFlock.period}
|
Periode {projectFlock.period}
|
||||||
@@ -461,7 +444,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
color='info'
|
color='info'
|
||||||
size='sm'
|
size='sm'
|
||||||
className={{
|
className={{
|
||||||
badge: 'whitespace-nowrap font-semibold text-xs px-2 py-0.5',
|
badge: 'whitespace-nowrap font-semibold text-xs px-2',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
PAKAN
|
PAKAN
|
||||||
@@ -476,7 +459,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
color='secondary'
|
color='secondary'
|
||||||
size='sm'
|
size='sm'
|
||||||
className={{
|
className={{
|
||||||
badge: 'whitespace-nowrap font-semibold text-xs px-2 py-0.5',
|
badge: 'whitespace-nowrap font-semibold text-xs px-2',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
OVK
|
OVK
|
||||||
@@ -503,11 +486,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
>(
|
>(
|
||||||
arrayName: T,
|
arrayName: T,
|
||||||
column: T extends 'body_weights'
|
column: T extends 'body_weights'
|
||||||
? keyof RecordingFormValues['body_weights'][0]
|
? keyof RecordingGrowingFormValues['body_weights'][0]
|
||||||
: T extends 'stocks'
|
: T extends 'stocks'
|
||||||
? keyof RecordingFormValues['stocks'][0]
|
? keyof RecordingGrowingFormValues['stocks'][0]
|
||||||
: T extends 'depletions'
|
: T extends 'depletions'
|
||||||
? keyof RecordingFormValues['depletions'][0]
|
? keyof RecordingGrowingFormValues['depletions'][0]
|
||||||
: never,
|
: never,
|
||||||
idx: number
|
idx: number
|
||||||
) => {
|
) => {
|
||||||
@@ -540,7 +523,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
const locationChangeHandler = (val: OptionType | OptionType[] | null) => {
|
const locationChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
setSelectedLocation(val as OptionType);
|
setSelectedLocation(val as OptionType);
|
||||||
formik.setFieldValue('project_flock_kandang', null);
|
formik.setFieldValue('project_flock_kandang', null);
|
||||||
formik.setFieldValue('project_flock_kandang_id', 0);
|
formik.setFieldValue('project_flock_kandangs_id', 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
const projectFlockKandangChangeHandler = (
|
const projectFlockKandangChangeHandler = (
|
||||||
@@ -557,9 +540,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
|
|
||||||
formik.setFieldTouched('project_flock_kandang', true);
|
formik.setFieldTouched('project_flock_kandang', true);
|
||||||
formik.setFieldValue('project_flock_kandang', val);
|
formik.setFieldValue('project_flock_kandang', val);
|
||||||
formik.setFieldTouched('project_flock_kandang_id', true);
|
formik.setFieldTouched('project_flock_kandangs_id', true);
|
||||||
formik.setFieldValue(
|
formik.setFieldValue(
|
||||||
'project_flock_kandang_id',
|
'project_flock_kandangs_id',
|
||||||
(val as OptionType)?.value || 0
|
(val as OptionType)?.value || 0
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -629,81 +612,25 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
const newBodyWeights = [
|
const newBodyWeights = [
|
||||||
...(formik.values.body_weights || []),
|
...(formik.values.body_weights || []),
|
||||||
{
|
{
|
||||||
weight: 0,
|
avg_weight: 0,
|
||||||
qty: 1,
|
qty: 1,
|
||||||
average_weight: 0,
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
formik.setFieldValue('body_weights', newBodyWeights);
|
formik.setFieldValue('body_weights', newBodyWeights);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleWeightChange = (idx: number, value: number) => {
|
const handleAvgWeightChange = (idx: number, value: number) => {
|
||||||
formik.setFieldValue(`body_weights.${idx}.weight`, value);
|
formik.setFieldValue(`body_weights.${idx}.avg_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 = currentWeight.qty;
|
|
||||||
if (qty > 0 && value > 0) {
|
|
||||||
const averageWeight = parseFloat((value / qty).toFixed(2));
|
|
||||||
formik.setFieldValue(
|
|
||||||
`body_weights.${idx}.average_weight`,
|
|
||||||
averageWeight
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
formik.setFieldValue(`body_weights.${idx}.average_weight`, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleQtyChange = (idx: number, value: number) => {
|
const handleQtyChange = (idx: number, value: number) => {
|
||||||
formik.setFieldValue(`body_weights.${idx}.qty`, value);
|
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 = currentWeight.weight;
|
|
||||||
if (value > 0 && weight > 0) {
|
|
||||||
const averageWeight = parseFloat((weight / value).toFixed(2));
|
|
||||||
formik.setFieldValue(
|
|
||||||
`body_weights.${idx}.average_weight`,
|
|
||||||
averageWeight
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
formik.setFieldValue(`body_weights.${idx}.average_weight`, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAverageWeightChange = (idx: number, value: number) => {
|
const handleAvgWeightChangeWrapper =
|
||||||
formik.setFieldValue(`body_weights.${idx}.average_weight`, value);
|
|
||||||
|
|
||||||
const currentWeight = formik.values.body_weights?.[idx];
|
|
||||||
if (currentWeight) {
|
|
||||||
const qty = currentWeight.qty;
|
|
||||||
if (qty > 0 && value > 0) {
|
|
||||||
const totalWeight = value * qty;
|
|
||||||
formik.setFieldValue(`body_weights.${idx}.weight`, totalWeight);
|
|
||||||
} else {
|
|
||||||
formik.setFieldValue(`body_weights.${idx}.weight`, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleWeightChangeWrapper =
|
|
||||||
(idx: number) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
(idx: number) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const value = parseFloat(e.target.value) || 0;
|
const value = parseFloat(e.target.value) || 0;
|
||||||
handleWeightChange(idx, value);
|
handleAvgWeightChange(idx, value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleQtyChangeWrapper =
|
const handleQtyChangeWrapper =
|
||||||
@@ -712,19 +639,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
handleQtyChange(idx, value);
|
handleQtyChange(idx, value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAverageWeightChangeWrapper =
|
|
||||||
(idx: number) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
setEditingAverageIndex(idx);
|
|
||||||
setManuallyEditedRows((prev) => new Set(prev).add(idx));
|
|
||||||
|
|
||||||
const value = parseFloat(e.target.value) || 0;
|
|
||||||
handleAverageWeightChange(idx, value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAverageWeightBlur = (idx: number) => {
|
|
||||||
setEditingAverageIndex(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeBodyWeight = (idx: number) => {
|
const removeBodyWeight = (idx: number) => {
|
||||||
const updatedBodyWeights = formik.values.body_weights?.filter(
|
const updatedBodyWeights = formik.values.body_weights?.filter(
|
||||||
(_, i) => i !== idx
|
(_, i) => i !== idx
|
||||||
@@ -746,17 +660,16 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
...(formik.values.stocks || []),
|
...(formik.values.stocks || []),
|
||||||
{
|
{
|
||||||
product_warehouse_id: 0,
|
product_warehouse_id: 0,
|
||||||
usage_amount: 0,
|
usage_qty: 0,
|
||||||
notes: '',
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
formik.setFieldValue('stocks', newStocks);
|
formik.setFieldValue('stocks', newStocks);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleStockUsageAmountChangeWrapper = useCallback(
|
const handleStockUsageQtyChangeWrapper = useCallback(
|
||||||
(idx: number) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
(idx: number) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const value = parseFloat(e.target.value) || 0;
|
const value = parseFloat(e.target.value) || 0;
|
||||||
formik.setFieldValue(`stocks.${idx}.usage_amount`, value);
|
formik.setFieldValue(`stocks.${idx}.usage_qty`, value);
|
||||||
},
|
},
|
||||||
[formik]
|
[formik]
|
||||||
);
|
);
|
||||||
@@ -779,17 +692,17 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
const newDepletions = [
|
const newDepletions = [
|
||||||
...(formik.values.depletions || []),
|
...(formik.values.depletions || []),
|
||||||
{
|
{
|
||||||
total: 0,
|
product_warehouse_id: 0,
|
||||||
notes: '',
|
qty: 0,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
formik.setFieldValue('depletions', newDepletions);
|
formik.setFieldValue('depletions', newDepletions);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDepletionTotalChangeWrapper = useCallback(
|
const handleDepletionQtyChangeWrapper = useCallback(
|
||||||
(idx: number) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
(idx: number) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const value = parseFloat(e.target.value) || 0;
|
const value = parseFloat(e.target.value) || 0;
|
||||||
formik.setFieldValue(`depletions.${idx}.total`, value);
|
formik.setFieldValue(`depletions.${idx}.qty`, value);
|
||||||
},
|
},
|
||||||
[formik]
|
[formik]
|
||||||
);
|
);
|
||||||
@@ -809,41 +722,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
setSelectedDepletions([]);
|
setSelectedDepletions([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ===== EFFECTS =====
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...weight,
|
|
||||||
average_weight:
|
|
||||||
weight.qty > 0 && weight.weight > 0
|
|
||||||
? parseFloat((weight.weight / weight.qty).toFixed(2))
|
|
||||||
: 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const hasChanges = updatedBodyWeights.some(
|
|
||||||
(updated, idx) =>
|
|
||||||
idx !== editingAverageIndex &&
|
|
||||||
!manuallyEditedRows.has(idx) &&
|
|
||||||
updated.average_weight !==
|
|
||||||
(formik.values.body_weights[idx]?.average_weight || 0)
|
|
||||||
);
|
|
||||||
if (hasChanges) {
|
|
||||||
formik.setFieldValue('body_weights', updatedBodyWeights, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
formik.values.body_weights?.map((w) => w.weight),
|
|
||||||
formik.values.body_weights?.map((w) => w.qty),
|
|
||||||
editingAverageIndex,
|
|
||||||
manuallyEditedRows,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<section className='w-full'>
|
<section className='w-full'>
|
||||||
@@ -889,6 +767,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
<div>
|
<div>
|
||||||
<SelectInput
|
<SelectInput
|
||||||
required
|
required
|
||||||
|
key={`project-flock-${formik.values.project_flock_kandangs_id}`}
|
||||||
label='Project Flock'
|
label='Project Flock'
|
||||||
value={formik.values.project_flock_kandang ?? undefined}
|
value={formik.values.project_flock_kandang ?? undefined}
|
||||||
onChange={projectFlockKandangChangeHandler}
|
onChange={projectFlockKandangChangeHandler}
|
||||||
@@ -896,11 +775,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
onInputChange={setProjectFlockSearchValue}
|
onInputChange={setProjectFlockSearchValue}
|
||||||
isLoading={isLoadingProjectFlocks}
|
isLoading={isLoadingProjectFlocks}
|
||||||
isError={
|
isError={
|
||||||
formik.touched.project_flock_kandang_id &&
|
formik.touched.project_flock_kandangs_id &&
|
||||||
Boolean(formik.errors.project_flock_kandang_id)
|
Boolean(formik.errors.project_flock_kandangs_id)
|
||||||
}
|
}
|
||||||
errorMessage={
|
errorMessage={
|
||||||
formik.errors.project_flock_kandang_id as string
|
formik.errors.project_flock_kandangs_id as string
|
||||||
}
|
}
|
||||||
isDisabled={type === 'detail' || !selectedLocation}
|
isDisabled={type === 'detail' || !selectedLocation}
|
||||||
placeholder={
|
placeholder={
|
||||||
@@ -911,9 +790,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
isClearable
|
isClearable
|
||||||
isSearchable
|
isSearchable
|
||||||
startAdornment={
|
startAdornment={
|
||||||
formik.values.project_flock_kandang_id
|
formik.values.project_flock_kandangs_id
|
||||||
? getProjectFlockBadgeAdornment(
|
? getProjectFlockBadgeAdornment(
|
||||||
formik.values.project_flock_kandang_id
|
formik.values.project_flock_kandangs_id
|
||||||
)
|
)
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
@@ -964,7 +843,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
</th>
|
</th>
|
||||||
)}
|
)}
|
||||||
<th>
|
<th>
|
||||||
Berat Ayam (gram)
|
Rata-rata Berat Ayam (gram)
|
||||||
<span
|
<span
|
||||||
className='tooltip tooltip-error tooltip-bottom z-[9999]'
|
className='tooltip tooltip-error tooltip-bottom z-[9999]'
|
||||||
data-tip='required'
|
data-tip='required'
|
||||||
@@ -981,19 +860,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
<span className='text-error'>*</span>
|
<span className='text-error'>*</span>
|
||||||
</span>
|
</span>
|
||||||
</th>
|
</th>
|
||||||
<th>
|
|
||||||
Rata-rata Berat Ayam (gram)
|
|
||||||
<span
|
|
||||||
className='tooltip tooltip-info tooltip-bottom z-[9999]'
|
|
||||||
data-tip='Otomatis dihitung: Total Berat ÷ Jumlah Ayam'
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='material-symbols:info-outline'
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</th>
|
|
||||||
{type !== 'detail' && <th>Action</th>}
|
{type !== 'detail' && <th>Action</th>}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -1029,9 +895,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
<td>
|
<td>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
required
|
required
|
||||||
name={`body_weights.${idx}.weight`}
|
name={`body_weights.${idx}.avg_weight`}
|
||||||
value={bw.weight}
|
value={bw.avg_weight}
|
||||||
onChange={handleWeightChangeWrapper(idx)}
|
onChange={handleAvgWeightChangeWrapper(idx)}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
decimalScale={2}
|
decimalScale={2}
|
||||||
allowNegative={false}
|
allowNegative={false}
|
||||||
@@ -1039,12 +905,18 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
decimalSeparator='.'
|
decimalSeparator='.'
|
||||||
inputSuffix='gram'
|
inputSuffix='gram'
|
||||||
isError={
|
isError={
|
||||||
isRepeaterInputError('body_weights', 'weight', idx)
|
isRepeaterInputError(
|
||||||
.isError
|
'body_weights',
|
||||||
|
'avg_weight',
|
||||||
|
idx
|
||||||
|
).isError
|
||||||
}
|
}
|
||||||
errorMessage={
|
errorMessage={
|
||||||
isRepeaterInputError('body_weights', 'weight', idx)
|
isRepeaterInputError(
|
||||||
.errorMessage
|
'body_weights',
|
||||||
|
'avg_weight',
|
||||||
|
idx
|
||||||
|
).errorMessage
|
||||||
}
|
}
|
||||||
readOnly={type === 'detail'}
|
readOnly={type === 'detail'}
|
||||||
className={{
|
className={{
|
||||||
@@ -1077,43 +949,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
|
||||||
<NumberInput
|
|
||||||
name={`body_weights.${idx}.average_weight`}
|
|
||||||
value={bw.average_weight || 0}
|
|
||||||
onChange={handleAverageWeightChangeWrapper(idx)}
|
|
||||||
onBlur={(e) => {
|
|
||||||
handleAverageWeightBlur(idx);
|
|
||||||
formik.handleBlur(e);
|
|
||||||
}}
|
|
||||||
decimalScale={2}
|
|
||||||
allowNegative={false}
|
|
||||||
thousandSeparator=','
|
|
||||||
decimalSeparator='.'
|
|
||||||
inputSuffix='gram'
|
|
||||||
isError={
|
|
||||||
isRepeaterInputError(
|
|
||||||
'body_weights',
|
|
||||||
'average_weight',
|
|
||||||
idx
|
|
||||||
).isError
|
|
||||||
}
|
|
||||||
errorMessage={
|
|
||||||
isRepeaterInputError(
|
|
||||||
'body_weights',
|
|
||||||
'average_weight',
|
|
||||||
idx
|
|
||||||
).errorMessage
|
|
||||||
}
|
|
||||||
readOnly={type === 'detail'}
|
|
||||||
className={{
|
|
||||||
wrapper: 'w-full min-w-32',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
{type !== 'detail' && (
|
{type !== 'detail' && (
|
||||||
<td>
|
<td>
|
||||||
<div className='flex justify-center'>
|
<div className='flex items-center'>
|
||||||
<Button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
color='error'
|
color='error'
|
||||||
@@ -1249,6 +1087,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
<td>
|
<td>
|
||||||
<SelectInput
|
<SelectInput
|
||||||
required
|
required
|
||||||
|
key={`stock-product-${idx}-${stock.product_warehouse_id}`}
|
||||||
value={
|
value={
|
||||||
unifiedStockProducts.find(
|
unifiedStockProducts.find(
|
||||||
(product) =>
|
(product) =>
|
||||||
@@ -1261,21 +1100,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
`stocks.${idx}.product_warehouse_id`,
|
`stocks.${idx}.product_warehouse_id`,
|
||||||
option?.value || 0
|
option?.value || 0
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
|
||||||
option?.value &&
|
|
||||||
isResponseSuccess(stockProducts)
|
|
||||||
) {
|
|
||||||
const selectedProduct = stockProducts.data.find(
|
|
||||||
(product) => product.id === option.value
|
|
||||||
);
|
|
||||||
if (selectedProduct) {
|
|
||||||
formik.setFieldValue(
|
|
||||||
`stocks.${idx}.notes`,
|
|
||||||
selectedProduct.product.name
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
options={unifiedStockProducts}
|
options={unifiedStockProducts}
|
||||||
placeholder='Pilih Produk'
|
placeholder='Pilih Produk'
|
||||||
@@ -1313,27 +1137,21 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
<div className='flex flex-col gap-1'>
|
<div className='flex flex-col gap-1'>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
required
|
required
|
||||||
name={`stocks.${idx}.usage_amount`}
|
name={`stocks.${idx}.usage_qty`}
|
||||||
value={stock.usage_amount}
|
value={stock.usage_qty}
|
||||||
onChange={handleStockUsageAmountChangeWrapper(idx)}
|
onChange={handleStockUsageQtyChangeWrapper(idx)}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
decimalScale={0}
|
decimalScale={0}
|
||||||
allowNegative={false}
|
allowNegative={false}
|
||||||
thousandSeparator=','
|
thousandSeparator=','
|
||||||
decimalSeparator='.'
|
decimalSeparator='.'
|
||||||
isError={
|
isError={
|
||||||
isRepeaterInputError(
|
isRepeaterInputError('stocks', 'usage_qty', idx)
|
||||||
'stocks',
|
.isError || Boolean(getStockUsageError(idx))
|
||||||
'usage_amount',
|
|
||||||
idx
|
|
||||||
).isError || Boolean(getStockUsageError(idx))
|
|
||||||
}
|
}
|
||||||
errorMessage={
|
errorMessage={
|
||||||
isRepeaterInputError(
|
isRepeaterInputError('stocks', 'usage_qty', idx)
|
||||||
'stocks',
|
.errorMessage ||
|
||||||
'usage_amount',
|
|
||||||
idx
|
|
||||||
).errorMessage ||
|
|
||||||
getStockUsageError(idx) ||
|
getStockUsageError(idx) ||
|
||||||
undefined
|
undefined
|
||||||
}
|
}
|
||||||
@@ -1348,7 +1166,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
</td>
|
</td>
|
||||||
{type !== 'detail' && (
|
{type !== 'detail' && (
|
||||||
<td>
|
<td>
|
||||||
<div className='flex justify-center'>
|
<div className='flex items-center'>
|
||||||
<Button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
color='error'
|
color='error'
|
||||||
@@ -1446,7 +1264,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
</span>
|
</span>
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
Total
|
Jumlah
|
||||||
<span
|
<span
|
||||||
className='tooltip tooltip-error tooltip-bottom z-[9999]'
|
className='tooltip tooltip-error tooltip-bottom z-[9999]'
|
||||||
data-tip='required'
|
data-tip='required'
|
||||||
@@ -1489,64 +1307,72 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
<td>
|
<td>
|
||||||
<SelectInput
|
<SelectInput
|
||||||
value={
|
value={
|
||||||
RECORDING_FLAG_OPTIONS.find(
|
unifiedStockProducts.find(
|
||||||
(option) => option.value === depletion.notes
|
(product) =>
|
||||||
|
product.value === depletion.product_warehouse_id
|
||||||
) || null
|
) || null
|
||||||
}
|
}
|
||||||
onChange={(selectedOption) => {
|
onChange={(selectedOption) => {
|
||||||
const option = selectedOption as OptionType | null;
|
const option = selectedOption as OptionType | null;
|
||||||
formik.setFieldValue(
|
formik.setFieldValue(
|
||||||
`depletions.${idx}.notes`,
|
`depletions.${idx}.product_warehouse_id`,
|
||||||
option?.value || ''
|
option?.value || 0
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
options={RECORDING_FLAG_OPTIONS}
|
options={unifiedStockProducts}
|
||||||
placeholder='Pilih Kondisi'
|
placeholder='Pilih Kondisi'
|
||||||
|
isLoading={isLoadingStockProducts}
|
||||||
isError={
|
isError={
|
||||||
isRepeaterInputError('depletions', 'notes', idx)
|
isRepeaterInputError(
|
||||||
.isError
|
'depletions',
|
||||||
|
'product_warehouse_id',
|
||||||
|
idx
|
||||||
|
).isError
|
||||||
}
|
}
|
||||||
errorMessage={
|
errorMessage={
|
||||||
isRepeaterInputError('depletions', 'notes', idx)
|
isRepeaterInputError(
|
||||||
.errorMessage
|
'depletions',
|
||||||
|
'product_warehouse_id',
|
||||||
|
idx
|
||||||
|
).errorMessage
|
||||||
}
|
}
|
||||||
isDisabled={type === 'detail'}
|
isDisabled={type === 'detail'}
|
||||||
className={{
|
className={{
|
||||||
wrapper: 'w-full min-w-32',
|
wrapper: 'w-full min-w-48',
|
||||||
}}
|
}}
|
||||||
isSearchable={false}
|
isSearchable
|
||||||
isClearable={type !== 'detail'}
|
isClearable={type !== 'detail'}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
required
|
required
|
||||||
name={`depletions.${idx}.total`}
|
name={`depletions.${idx}.qty`}
|
||||||
value={depletion.total}
|
value={depletion.qty}
|
||||||
onChange={handleDepletionTotalChangeWrapper(idx)}
|
onChange={handleDepletionQtyChangeWrapper(idx)}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
decimalScale={0}
|
decimalScale={0}
|
||||||
allowNegative={false}
|
allowNegative={false}
|
||||||
thousandSeparator=','
|
thousandSeparator=','
|
||||||
decimalSeparator='.'
|
decimalSeparator='.'
|
||||||
isError={
|
isError={
|
||||||
isRepeaterInputError('depletions', 'total', idx)
|
isRepeaterInputError('depletions', 'qty', idx)
|
||||||
.isError
|
.isError
|
||||||
}
|
}
|
||||||
errorMessage={
|
errorMessage={
|
||||||
isRepeaterInputError('depletions', 'total', idx)
|
isRepeaterInputError('depletions', 'qty', idx)
|
||||||
.errorMessage
|
.errorMessage
|
||||||
}
|
}
|
||||||
readOnly={type === 'detail'}
|
readOnly={type === 'detail'}
|
||||||
className={{
|
className={{
|
||||||
wrapper: 'w-full min-w-24',
|
wrapper: 'w-full min-w-24',
|
||||||
}}
|
}}
|
||||||
placeholder='Total'
|
placeholder='Jumlah'
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
{type !== 'detail' && (
|
{type !== 'detail' && (
|
||||||
<td>
|
<td>
|
||||||
<div className='flex justify-center'>
|
<div className='flex items-center'>
|
||||||
<Button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
color='error'
|
color='error'
|
||||||
@@ -1594,7 +1420,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Action buttons */}
|
{/* Action buttons */}
|
||||||
<FormActions<RecordingFormValues>
|
<FormActions<RecordingGrowingFormValues>
|
||||||
type={type}
|
type={type}
|
||||||
formik={formik}
|
formik={formik}
|
||||||
editUrl={
|
editUrl={
|
||||||
|
|||||||
+90
-16
@@ -1,46 +1,120 @@
|
|||||||
import { BaseMetadata, User } from '@/types/api/api-general';
|
import { BaseMetadata, User } from '@/types/api/api-general';
|
||||||
|
|
||||||
export type ProductionMetrics = {
|
export type ProductionMetrics = {
|
||||||
total_depletion: number;
|
total_depletion_qty: number;
|
||||||
cum_depletion_rate: number;
|
cum_depletion_rate: number;
|
||||||
daily_gain: number;
|
daily_gain: number;
|
||||||
avg_daily_gain: number;
|
avg_daily_gain: number;
|
||||||
cum_intake: number;
|
cum_intake: number;
|
||||||
fcr_value: number;
|
fcr_value: number;
|
||||||
total_chick: number;
|
total_chick_qty: number;
|
||||||
daily_depletion_rate: number;
|
daily_depletion_rate: number;
|
||||||
cum_depletion: number;
|
cum_depletion: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BaseRecording = {
|
export type BaseRecording = {
|
||||||
id: number;
|
id: number;
|
||||||
project_flock_kandang_id: number;
|
project_flock_kandangs_id: number;
|
||||||
record_datetime: string;
|
record_datetime: string;
|
||||||
record_date: string;
|
|
||||||
status: number;
|
|
||||||
ontime: boolean;
|
|
||||||
day: number;
|
day: number;
|
||||||
created_user: User;
|
created_by: User;
|
||||||
} & ProductionMetrics;
|
} & ProductionMetrics;
|
||||||
|
|
||||||
export type Recording = BaseMetadata & BaseRecording;
|
export type RecordingBW = {
|
||||||
|
id: number;
|
||||||
|
recording_id: number;
|
||||||
|
avg_weight: number;
|
||||||
|
qty: number;
|
||||||
|
total_weight: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type CreateRecordingPayload = {
|
export type RecordingDepletion = {
|
||||||
project_flock_kandang_id: number;
|
id: number;
|
||||||
|
recording_id: number;
|
||||||
|
product_warehouse_id: number;
|
||||||
|
qty: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RecordingStock = {
|
||||||
|
id: number;
|
||||||
|
recording_id: number;
|
||||||
|
product_warehouse_id: number;
|
||||||
|
usage_qty: number;
|
||||||
|
pending_qty: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RecordingEgg = {
|
||||||
|
id: number;
|
||||||
|
recording_id: number;
|
||||||
|
product_warehouse_id: number;
|
||||||
|
qty: number;
|
||||||
|
created_by: User;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GradingEgg = {
|
||||||
|
id: number;
|
||||||
|
recording_egg_id: number;
|
||||||
|
qty: number;
|
||||||
|
grade: string;
|
||||||
|
created_by: User;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Recording = BaseMetadata &
|
||||||
|
BaseRecording & {
|
||||||
|
recording_bws?: RecordingBW[];
|
||||||
|
recording_depletions?: RecordingDepletion[];
|
||||||
|
recording_stocks?: RecordingStock[];
|
||||||
|
recording_eggs?: RecordingEgg[];
|
||||||
|
grading_eggs?: GradingEgg[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CreateGrowingRecordingPayload = {
|
||||||
|
project_flock_kandangs_id: number;
|
||||||
body_weights: {
|
body_weights: {
|
||||||
weight: number;
|
avg_weight: number;
|
||||||
qty: number;
|
qty: number;
|
||||||
}[];
|
}[];
|
||||||
stocks?: {
|
stocks?: {
|
||||||
product_warehouse_id: number;
|
product_warehouse_id: number;
|
||||||
usage_amount: number;
|
usage_qty: number;
|
||||||
notes: string;
|
pending_qty?: number;
|
||||||
}[];
|
}[];
|
||||||
depletions?: {
|
depletions?: {
|
||||||
product_warehouse_id?: number;
|
product_warehouse_id: number;
|
||||||
total: number;
|
qty: number;
|
||||||
notes: string;
|
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type CreateLayingRecordingPayload = {
|
||||||
|
project_flock_kandangs_id: number;
|
||||||
|
body_weights: {
|
||||||
|
avg_weight: number;
|
||||||
|
qty: number;
|
||||||
|
}[];
|
||||||
|
stocks?: {
|
||||||
|
product_warehouse_id: number;
|
||||||
|
usage_qty: number;
|
||||||
|
pending_qty?: number;
|
||||||
|
}[];
|
||||||
|
depletions?: {
|
||||||
|
product_warehouse_id: number;
|
||||||
|
qty: number;
|
||||||
|
}[];
|
||||||
|
eggs: {
|
||||||
|
product_warehouse_id: number;
|
||||||
|
qty: number;
|
||||||
|
grading?: {
|
||||||
|
grade: string;
|
||||||
|
qty: number;
|
||||||
|
}[];
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CreateRecordingPayload =
|
||||||
|
| CreateGrowingRecordingPayload
|
||||||
|
| CreateLayingRecordingPayload;
|
||||||
|
|
||||||
|
export type UpdateGrowingRecordingPayload = CreateGrowingRecordingPayload;
|
||||||
|
export type UpdateLayingRecordingPayload = CreateLayingRecordingPayload;
|
||||||
|
|
||||||
export type UpdateRecordingPayload = CreateRecordingPayload;
|
export type UpdateRecordingPayload = CreateRecordingPayload;
|
||||||
|
|||||||
Reference in New Issue
Block a user