mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-24 15:25:46 +00:00
refactor(FE-114,136,137): update feed and vaccination fields to use IDs instead of names and add stock validation
This commit is contained in:
@@ -48,7 +48,7 @@ export const RecordingFormSchema = Yup.object({
|
|||||||
feed_data: Yup.array()
|
feed_data: Yup.array()
|
||||||
.of(
|
.of(
|
||||||
Yup.object({
|
Yup.object({
|
||||||
feed_name: Yup.string().required('Nama pakan wajib diisi!'),
|
feed_id: Yup.string().required('Nama pakan wajib diisi!'),
|
||||||
feed_qty: Yup.number()
|
feed_qty: Yup.number()
|
||||||
.required('Qty pakan wajib diisi!')
|
.required('Qty pakan wajib diisi!')
|
||||||
.min(1, 'Qty minimal 1!')
|
.min(1, 'Qty minimal 1!')
|
||||||
@@ -56,7 +56,15 @@ export const RecordingFormSchema = Yup.object({
|
|||||||
feed_stock: Yup.number()
|
feed_stock: Yup.number()
|
||||||
.required('Stock pakan wajib diisi!')
|
.required('Stock pakan wajib diisi!')
|
||||||
.min(1, 'Stock minimal 1!')
|
.min(1, 'Stock minimal 1!')
|
||||||
.typeError('Stock pakan wajib diisi!'),
|
.typeError('Stock pakan wajib diisi!')
|
||||||
|
.test(
|
||||||
|
'is-not-exceed-qty',
|
||||||
|
'Feed stock tidak boleh melebihi feed qty yang tersedia!',
|
||||||
|
function (value) {
|
||||||
|
const { feed_qty } = this.parent;
|
||||||
|
return value === undefined || value <= feed_qty;
|
||||||
|
}
|
||||||
|
),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.min(1, 'Minimal harus ada 1 data pakan!')
|
.min(1, 'Minimal harus ada 1 data pakan!')
|
||||||
@@ -80,7 +88,7 @@ export const RecordingFormSchema = Yup.object({
|
|||||||
vaccination: Yup.array()
|
vaccination: Yup.array()
|
||||||
.of(
|
.of(
|
||||||
Yup.object({
|
Yup.object({
|
||||||
vaccine_name: Yup.string().required('Nama vaksin wajib diisi!'),
|
vaccine_id: Yup.string().required('Nama vaksin wajib diisi!'),
|
||||||
total_stock: Yup.number()
|
total_stock: Yup.number()
|
||||||
.required('Total stock wajib diisi!')
|
.required('Total stock wajib diisi!')
|
||||||
.min(1, 'Total stock minimal 1!')
|
.min(1, 'Total stock minimal 1!')
|
||||||
@@ -88,7 +96,15 @@ export const RecordingFormSchema = Yup.object({
|
|||||||
used_stock: Yup.number()
|
used_stock: Yup.number()
|
||||||
.required('Jumlah stock wajib diisi!')
|
.required('Jumlah stock wajib diisi!')
|
||||||
.min(1, 'Jumlah stock minimal 1!')
|
.min(1, 'Jumlah stock minimal 1!')
|
||||||
.typeError('Jumlah stock wajib diisi!'),
|
.typeError('Jumlah stock wajib diisi!')
|
||||||
|
.test(
|
||||||
|
'is-not-exceed-total',
|
||||||
|
'Used stock tidak boleh melebihi total stock yang tersedia!',
|
||||||
|
function (value) {
|
||||||
|
const { total_stock } = this.parent;
|
||||||
|
return value === undefined || value <= total_stock;
|
||||||
|
}
|
||||||
|
),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.min(1, 'Minimal harus ada 1 data vaksinasi!')
|
.min(1, 'Minimal harus ada 1 data vaksinasi!')
|
||||||
@@ -142,13 +158,19 @@ export const getRecordingFormInitialValues = (
|
|||||||
recording_date: initialValues?.recording_date
|
recording_date: initialValues?.recording_date
|
||||||
? new Date(initialValues.recording_date)
|
? new Date(initialValues.recording_date)
|
||||||
: new Date(),
|
: new Date(),
|
||||||
feed_data: initialValues?.feed_data ?? [
|
feed_data: initialValues?.feed_data
|
||||||
{
|
? initialValues.feed_data.map((feed) => ({
|
||||||
feed_name: '',
|
feed_id: feed.feed_name,
|
||||||
feed_qty: 0,
|
feed_qty: feed.feed_qty,
|
||||||
feed_stock: 0,
|
feed_stock: feed.feed_stock,
|
||||||
},
|
}))
|
||||||
],
|
: [
|
||||||
|
{
|
||||||
|
feed_id: '',
|
||||||
|
feed_qty: 0,
|
||||||
|
feed_stock: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
body_weight: initialValues?.body_weight ?? [
|
body_weight: initialValues?.body_weight ?? [
|
||||||
{
|
{
|
||||||
chicken_weight: 0,
|
chicken_weight: 0,
|
||||||
@@ -156,13 +178,19 @@ export const getRecordingFormInitialValues = (
|
|||||||
average_chicken_weight: 0,
|
average_chicken_weight: 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
vaccination: initialValues?.vaccination ?? [
|
vaccination: initialValues?.vaccination
|
||||||
{
|
? initialValues.vaccination.map((vaccine) => ({
|
||||||
vaccine_name: '',
|
vaccine_id: vaccine.vaccine_name,
|
||||||
total_stock: 0,
|
total_stock: vaccine.total_stock,
|
||||||
used_stock: 0,
|
used_stock: vaccine.used_stock,
|
||||||
},
|
}))
|
||||||
],
|
: [
|
||||||
|
{
|
||||||
|
vaccine_id: '',
|
||||||
|
total_stock: 0,
|
||||||
|
used_stock: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
mortality: initialValues?.mortality ?? [
|
mortality: initialValues?.mortality ?? [
|
||||||
{
|
{
|
||||||
condition: '',
|
condition: '',
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import { isResponseSuccess } from '@/lib/api-helper';
|
|||||||
import { RECORDING_FLAG_OPTIONS } from '@/config/constant';
|
import { RECORDING_FLAG_OPTIONS } from '@/config/constant';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import { KandangApi, LocationApi } from '@/services/api/master-data';
|
import { KandangApi, LocationApi } from '@/services/api/master-data';
|
||||||
|
import { ProductWarehouseApi } from '@/services/api/inventory';
|
||||||
|
|
||||||
interface RecordingFormProps {
|
interface RecordingFormProps {
|
||||||
type?: 'add' | 'edit' | 'detail';
|
type?: 'add' | 'edit' | 'detail';
|
||||||
@@ -71,7 +72,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
? values.recording_date.toISOString()
|
? values.recording_date.toISOString()
|
||||||
: '',
|
: '',
|
||||||
feed_data: (values.feed_data ?? []).map((p) => ({
|
feed_data: (values.feed_data ?? []).map((p) => ({
|
||||||
feed_name: p.feed_name,
|
feed_id: p.feed_id,
|
||||||
feed_qty: p.feed_qty,
|
feed_qty: p.feed_qty,
|
||||||
feed_stock: p.feed_stock,
|
feed_stock: p.feed_stock,
|
||||||
})),
|
})),
|
||||||
@@ -81,7 +82,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
average_chicken_weight: b.average_chicken_weight,
|
average_chicken_weight: b.average_chicken_weight,
|
||||||
})),
|
})),
|
||||||
vaccination: (values.vaccination ?? []).map((v) => ({
|
vaccination: (values.vaccination ?? []).map((v) => ({
|
||||||
vaccine_name: v.vaccine_name,
|
vaccine_id: v.vaccine_id,
|
||||||
total_stock: v.total_stock,
|
total_stock: v.total_stock,
|
||||||
used_stock: v.used_stock,
|
used_stock: v.used_stock,
|
||||||
})),
|
})),
|
||||||
@@ -116,6 +117,50 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
: [];
|
: [];
|
||||||
|
|
||||||
// Pakan selection
|
// Pakan selection
|
||||||
|
const pakanUrl = `${ProductWarehouseApi.basePath}?${new URLSearchParams({ flag: 'PAKAN', search: '' }).toString()}`;
|
||||||
|
const { data: pakanProducts } = useSWR(
|
||||||
|
pakanUrl,
|
||||||
|
ProductWarehouseApi.getAllFetcher
|
||||||
|
);
|
||||||
|
const pakanOptions = isResponseSuccess(pakanProducts)
|
||||||
|
? pakanProducts?.data.map((product) => ({
|
||||||
|
value: product.id,
|
||||||
|
label: product.product.name,
|
||||||
|
}))
|
||||||
|
: [];
|
||||||
|
|
||||||
|
// Create stock mapping for pakan (Feed)
|
||||||
|
const pakanStockMap = useMemo(() => {
|
||||||
|
if (!isResponseSuccess(pakanProducts)) return new Map<number, number>();
|
||||||
|
const map = new Map<number, number>();
|
||||||
|
pakanProducts.data.forEach((product) => {
|
||||||
|
map.set(product.id, product.quantity);
|
||||||
|
});
|
||||||
|
return map;
|
||||||
|
}, [pakanProducts]);
|
||||||
|
|
||||||
|
// OVK selection
|
||||||
|
const ovkUrl = `${ProductWarehouseApi.basePath}?${new URLSearchParams({ flag: 'OVK', search: '' }).toString()}`;
|
||||||
|
const { data: ovkProducts } = useSWR(
|
||||||
|
ovkUrl,
|
||||||
|
ProductWarehouseApi.getAllFetcher
|
||||||
|
);
|
||||||
|
const ovkOptions = isResponseSuccess(ovkProducts)
|
||||||
|
? ovkProducts?.data.map((product) => ({
|
||||||
|
value: product.id,
|
||||||
|
label: product.product.name,
|
||||||
|
}))
|
||||||
|
: [];
|
||||||
|
|
||||||
|
// Create stock mapping for OVK (Vaccination)
|
||||||
|
const ovkStockMap = useMemo(() => {
|
||||||
|
if (!isResponseSuccess(ovkProducts)) return new Map<number, number>();
|
||||||
|
const map = new Map<number, number>();
|
||||||
|
ovkProducts.data.forEach((product) => {
|
||||||
|
map.set(product.id, product.quantity);
|
||||||
|
});
|
||||||
|
return map;
|
||||||
|
}, [ovkProducts]);
|
||||||
|
|
||||||
const locationsUrl = `${LocationApi.basePath}?${new URLSearchParams({ search: locationSelectInputValue ?? '' }).toString()}`;
|
const locationsUrl = `${LocationApi.basePath}?${new URLSearchParams({ search: locationSelectInputValue ?? '' }).toString()}`;
|
||||||
const { data: locations, isLoading: isLoadingLocations } = useSWR(
|
const { data: locations, isLoading: isLoadingLocations } = useSWR(
|
||||||
@@ -209,7 +254,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
const newFeedData = [
|
const newFeedData = [
|
||||||
...(formik.values.feed_data || []),
|
...(formik.values.feed_data || []),
|
||||||
{
|
{
|
||||||
feed_name: '',
|
feed: null,
|
||||||
|
feed_id: 0,
|
||||||
feed_qty: 0,
|
feed_qty: 0,
|
||||||
feed_stock: 0,
|
feed_stock: 0,
|
||||||
},
|
},
|
||||||
@@ -263,7 +309,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
const newVaccination = [
|
const newVaccination = [
|
||||||
...(formik.values.vaccination || []),
|
...(formik.values.vaccination || []),
|
||||||
{
|
{
|
||||||
vaccine_name: '',
|
vaccine: null,
|
||||||
|
vaccine_id: 0,
|
||||||
total_stock: 0,
|
total_stock: 0,
|
||||||
used_stock: 0,
|
used_stock: 0,
|
||||||
},
|
},
|
||||||
@@ -327,33 +374,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
|
|
||||||
<div className='flex flex-col gap-6'>
|
<div className='flex flex-col gap-6'>
|
||||||
<div className='flex gap-4'>
|
<div className='flex gap-4'>
|
||||||
{/*<SelectInput*/}
|
|
||||||
{/* required*/}
|
|
||||||
{/* label='Flock'*/}
|
|
||||||
{/* value={*/}
|
|
||||||
{/* formik.values.flock_id*/}
|
|
||||||
{/* ? {*/}
|
|
||||||
{/* value: formik.values.flock_id,*/}
|
|
||||||
{/* label: initialValues?.flock?.name,*/}
|
|
||||||
{/* }*/}
|
|
||||||
{/* : null*/}
|
|
||||||
{/* }*/}
|
|
||||||
{/* onChange={(val) => {*/}
|
|
||||||
{/* formik.setFieldValue(*/}
|
|
||||||
{/* 'flock_id',*/}
|
|
||||||
{/* (val as OptionType)?.value*/}
|
|
||||||
{/* );*/}
|
|
||||||
{/* }}*/}
|
|
||||||
{/* options={flockOptions}*/}
|
|
||||||
{/* onInputChange={setFlockSelectInputValue}*/}
|
|
||||||
{/* isLoading={isLoadingFlocks}*/}
|
|
||||||
{/* isError={*/}
|
|
||||||
{/* formik.touched.flock_id && Boolean(formik.errors.flock_id)*/}
|
|
||||||
{/* }*/}
|
|
||||||
{/* errorMessage={formik.errors.flock_id as string}*/}
|
|
||||||
{/* isDisabled={type === 'detail'}*/}
|
|
||||||
{/* isClearable*/}
|
|
||||||
{/*/>*/}
|
|
||||||
<SelectInput
|
<SelectInput
|
||||||
required
|
required
|
||||||
label='Flock'
|
label='Flock'
|
||||||
@@ -473,8 +493,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
</th>
|
</th>
|
||||||
)}
|
)}
|
||||||
<th>Feed Name</th>
|
<th>Feed Name</th>
|
||||||
<th>Feed Qty</th>
|
<th>Feed Qty (Available Stock)</th>
|
||||||
<th>Feed Stock</th>
|
<th>Feed Stock (Used)</th>
|
||||||
{type !== 'detail' && <th>Action</th>}
|
{type !== 'detail' && <th>Action</th>}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -500,27 +520,47 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
</td>
|
</td>
|
||||||
)}
|
)}
|
||||||
<td>
|
<td>
|
||||||
<TextInput
|
<SelectInput
|
||||||
required
|
required
|
||||||
name={`feed_data.${idx}.feed_name`}
|
value={
|
||||||
value={feed.feed_name}
|
pakanOptions.find(
|
||||||
onChange={formik.handleChange}
|
(opt: OptionType) =>
|
||||||
onBlur={formik.handleBlur}
|
Number(opt.value) === Number(feed.feed_id)
|
||||||
|
) ?? null
|
||||||
|
}
|
||||||
|
onChange={(val) => {
|
||||||
|
const productWarehouseId =
|
||||||
|
(val as OptionType)?.value ?? 0;
|
||||||
|
const stock =
|
||||||
|
pakanStockMap.get(
|
||||||
|
productWarehouseId as number
|
||||||
|
) ?? 0;
|
||||||
|
|
||||||
|
formik.setFieldValue(
|
||||||
|
`feed_data.${idx}.feed`,
|
||||||
|
val
|
||||||
|
);
|
||||||
|
formik.setFieldValue(
|
||||||
|
`feed_data.${idx}.feed_id`,
|
||||||
|
productWarehouseId
|
||||||
|
);
|
||||||
|
formik.setFieldValue(
|
||||||
|
`feed_data.${idx}.feed_qty`,
|
||||||
|
stock
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
options={pakanOptions}
|
||||||
|
isLoading={false}
|
||||||
isError={
|
isError={
|
||||||
isRepeaterInputError(
|
isRepeaterInputError('feed_data', 'feed_id', idx)
|
||||||
'feed_data',
|
.isError
|
||||||
'feed_name',
|
|
||||||
idx
|
|
||||||
).isError
|
|
||||||
}
|
}
|
||||||
errorMessage={
|
errorMessage={
|
||||||
isRepeaterInputError(
|
isRepeaterInputError('feed_data', 'feed_id', idx)
|
||||||
'feed_data',
|
.errorMessage
|
||||||
'feed_name',
|
|
||||||
idx
|
|
||||||
).errorMessage
|
|
||||||
}
|
}
|
||||||
readOnly={type === 'detail'}
|
isDisabled={type === 'detail'}
|
||||||
|
isClearable
|
||||||
className={{
|
className={{
|
||||||
wrapper: 'w-full min-w-24',
|
wrapper: 'w-full min-w-24',
|
||||||
}}
|
}}
|
||||||
@@ -542,7 +582,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
isRepeaterInputError('feed_data', 'feed_qty', idx)
|
isRepeaterInputError('feed_data', 'feed_qty', idx)
|
||||||
.errorMessage
|
.errorMessage
|
||||||
}
|
}
|
||||||
readOnly={type === 'detail'}
|
readOnly={true}
|
||||||
className={{
|
className={{
|
||||||
wrapper: 'w-full min-w-24',
|
wrapper: 'w-full min-w-24',
|
||||||
}}
|
}}
|
||||||
@@ -847,7 +887,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
</th>
|
</th>
|
||||||
)}
|
)}
|
||||||
<th>Vaccine Name</th>
|
<th>Vaccine Name</th>
|
||||||
<th>Total Stock</th>
|
<th>Total Stock (Available Stock)</th>
|
||||||
<th>Used Stock</th>
|
<th>Used Stock</th>
|
||||||
{type !== 'detail' && <th>Action</th>}
|
{type !== 'detail' && <th>Action</th>}
|
||||||
</tr>
|
</tr>
|
||||||
@@ -874,27 +914,53 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
</td>
|
</td>
|
||||||
)}
|
)}
|
||||||
<td>
|
<td>
|
||||||
<TextInput
|
<SelectInput
|
||||||
required
|
required
|
||||||
name={`vaccination.${idx}.vaccine_name`}
|
value={
|
||||||
value={vaccine.vaccine_name}
|
ovkOptions.find(
|
||||||
onChange={formik.handleChange}
|
(opt: OptionType) =>
|
||||||
onBlur={formik.handleBlur}
|
Number(opt.value) ===
|
||||||
|
Number(vaccine.vaccine_id)
|
||||||
|
) ?? null
|
||||||
|
}
|
||||||
|
onChange={(val) => {
|
||||||
|
const productWarehouseId =
|
||||||
|
(val as OptionType)?.value ?? 0;
|
||||||
|
const stock =
|
||||||
|
ovkStockMap.get(productWarehouseId as number) ??
|
||||||
|
0;
|
||||||
|
|
||||||
|
formik.setFieldValue(
|
||||||
|
`vaccination.${idx}.vaccine`,
|
||||||
|
val
|
||||||
|
);
|
||||||
|
formik.setFieldValue(
|
||||||
|
`vaccination.${idx}.vaccine_id`,
|
||||||
|
productWarehouseId
|
||||||
|
);
|
||||||
|
formik.setFieldValue(
|
||||||
|
`vaccination.${idx}.total_stock`,
|
||||||
|
stock
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
options={ovkOptions}
|
||||||
|
isLoading={false}
|
||||||
isError={
|
isError={
|
||||||
isRepeaterInputError(
|
isRepeaterInputError(
|
||||||
'vaccination',
|
'vaccination',
|
||||||
'vaccine_name',
|
'vaccine_id',
|
||||||
idx
|
idx
|
||||||
).isError
|
).isError
|
||||||
}
|
}
|
||||||
errorMessage={
|
errorMessage={
|
||||||
isRepeaterInputError(
|
isRepeaterInputError(
|
||||||
'vaccination',
|
'vaccination',
|
||||||
'vaccine_name',
|
'vaccine_id',
|
||||||
idx
|
idx
|
||||||
).errorMessage
|
).errorMessage
|
||||||
}
|
}
|
||||||
readOnly={type === 'detail'}
|
isDisabled={type === 'detail'}
|
||||||
|
isClearable
|
||||||
className={{
|
className={{
|
||||||
wrapper: 'w-full min-w-24',
|
wrapper: 'w-full min-w-24',
|
||||||
}}
|
}}
|
||||||
@@ -922,7 +988,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
idx
|
idx
|
||||||
).errorMessage
|
).errorMessage
|
||||||
}
|
}
|
||||||
readOnly={type === 'detail'}
|
readOnly={true}
|
||||||
className={{
|
className={{
|
||||||
wrapper: 'w-full min-w-24',
|
wrapper: 'w-full min-w-24',
|
||||||
}}
|
}}
|
||||||
|
|||||||
Vendored
+2
-2
@@ -38,7 +38,7 @@ export type CreateRecordingPayload = {
|
|||||||
location_id: number;
|
location_id: number;
|
||||||
coop_id: number;
|
coop_id: number;
|
||||||
feed_data: {
|
feed_data: {
|
||||||
feed_name: string;
|
feed_id: string;
|
||||||
feed_qty: number;
|
feed_qty: number;
|
||||||
feed_stock: number;
|
feed_stock: number;
|
||||||
}[];
|
}[];
|
||||||
@@ -48,7 +48,7 @@ export type CreateRecordingPayload = {
|
|||||||
average_chicken_weight: number;
|
average_chicken_weight: number;
|
||||||
}[];
|
}[];
|
||||||
vaccination: {
|
vaccination: {
|
||||||
vaccine_name: string;
|
vaccine_id: string;
|
||||||
total_stock: number;
|
total_stock: number;
|
||||||
used_stock: number;
|
used_stock: number;
|
||||||
}[];
|
}[];
|
||||||
|
|||||||
Reference in New Issue
Block a user