mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +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()
|
||||
.of(
|
||||
Yup.object({
|
||||
feed_name: Yup.string().required('Nama pakan wajib diisi!'),
|
||||
feed_id: Yup.string().required('Nama pakan wajib diisi!'),
|
||||
feed_qty: Yup.number()
|
||||
.required('Qty pakan wajib diisi!')
|
||||
.min(1, 'Qty minimal 1!')
|
||||
@@ -56,7 +56,15 @@ export const RecordingFormSchema = Yup.object({
|
||||
feed_stock: Yup.number()
|
||||
.required('Stock pakan wajib diisi!')
|
||||
.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!')
|
||||
@@ -80,7 +88,7 @@ export const RecordingFormSchema = Yup.object({
|
||||
vaccination: Yup.array()
|
||||
.of(
|
||||
Yup.object({
|
||||
vaccine_name: Yup.string().required('Nama vaksin wajib diisi!'),
|
||||
vaccine_id: Yup.string().required('Nama vaksin wajib diisi!'),
|
||||
total_stock: Yup.number()
|
||||
.required('Total stock wajib diisi!')
|
||||
.min(1, 'Total stock minimal 1!')
|
||||
@@ -88,7 +96,15 @@ export const RecordingFormSchema = Yup.object({
|
||||
used_stock: Yup.number()
|
||||
.required('Jumlah stock wajib diisi!')
|
||||
.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!')
|
||||
@@ -142,13 +158,19 @@ export const getRecordingFormInitialValues = (
|
||||
recording_date: initialValues?.recording_date
|
||||
? new Date(initialValues.recording_date)
|
||||
: new Date(),
|
||||
feed_data: initialValues?.feed_data ?? [
|
||||
{
|
||||
feed_name: '',
|
||||
feed_qty: 0,
|
||||
feed_stock: 0,
|
||||
},
|
||||
],
|
||||
feed_data: initialValues?.feed_data
|
||||
? initialValues.feed_data.map((feed) => ({
|
||||
feed_id: feed.feed_name,
|
||||
feed_qty: feed.feed_qty,
|
||||
feed_stock: feed.feed_stock,
|
||||
}))
|
||||
: [
|
||||
{
|
||||
feed_id: '',
|
||||
feed_qty: 0,
|
||||
feed_stock: 0,
|
||||
},
|
||||
],
|
||||
body_weight: initialValues?.body_weight ?? [
|
||||
{
|
||||
chicken_weight: 0,
|
||||
@@ -156,13 +178,19 @@ export const getRecordingFormInitialValues = (
|
||||
average_chicken_weight: 0,
|
||||
},
|
||||
],
|
||||
vaccination: initialValues?.vaccination ?? [
|
||||
{
|
||||
vaccine_name: '',
|
||||
total_stock: 0,
|
||||
used_stock: 0,
|
||||
},
|
||||
],
|
||||
vaccination: initialValues?.vaccination
|
||||
? initialValues.vaccination.map((vaccine) => ({
|
||||
vaccine_id: vaccine.vaccine_name,
|
||||
total_stock: vaccine.total_stock,
|
||||
used_stock: vaccine.used_stock,
|
||||
}))
|
||||
: [
|
||||
{
|
||||
vaccine_id: '',
|
||||
total_stock: 0,
|
||||
used_stock: 0,
|
||||
},
|
||||
],
|
||||
mortality: initialValues?.mortality ?? [
|
||||
{
|
||||
condition: '',
|
||||
|
||||
@@ -23,6 +23,7 @@ import { isResponseSuccess } from '@/lib/api-helper';
|
||||
import { RECORDING_FLAG_OPTIONS } from '@/config/constant';
|
||||
import useSWR from 'swr';
|
||||
import { KandangApi, LocationApi } from '@/services/api/master-data';
|
||||
import { ProductWarehouseApi } from '@/services/api/inventory';
|
||||
|
||||
interface RecordingFormProps {
|
||||
type?: 'add' | 'edit' | 'detail';
|
||||
@@ -71,7 +72,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
? values.recording_date.toISOString()
|
||||
: '',
|
||||
feed_data: (values.feed_data ?? []).map((p) => ({
|
||||
feed_name: p.feed_name,
|
||||
feed_id: p.feed_id,
|
||||
feed_qty: p.feed_qty,
|
||||
feed_stock: p.feed_stock,
|
||||
})),
|
||||
@@ -81,7 +82,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
average_chicken_weight: b.average_chicken_weight,
|
||||
})),
|
||||
vaccination: (values.vaccination ?? []).map((v) => ({
|
||||
vaccine_name: v.vaccine_name,
|
||||
vaccine_id: v.vaccine_id,
|
||||
total_stock: v.total_stock,
|
||||
used_stock: v.used_stock,
|
||||
})),
|
||||
@@ -116,6 +117,50 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
: [];
|
||||
|
||||
// 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 { data: locations, isLoading: isLoadingLocations } = useSWR(
|
||||
@@ -209,7 +254,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
const newFeedData = [
|
||||
...(formik.values.feed_data || []),
|
||||
{
|
||||
feed_name: '',
|
||||
feed: null,
|
||||
feed_id: 0,
|
||||
feed_qty: 0,
|
||||
feed_stock: 0,
|
||||
},
|
||||
@@ -263,7 +309,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
const newVaccination = [
|
||||
...(formik.values.vaccination || []),
|
||||
{
|
||||
vaccine_name: '',
|
||||
vaccine: null,
|
||||
vaccine_id: 0,
|
||||
total_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 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
|
||||
required
|
||||
label='Flock'
|
||||
@@ -473,8 +493,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
</th>
|
||||
)}
|
||||
<th>Feed Name</th>
|
||||
<th>Feed Qty</th>
|
||||
<th>Feed Stock</th>
|
||||
<th>Feed Qty (Available Stock)</th>
|
||||
<th>Feed Stock (Used)</th>
|
||||
{type !== 'detail' && <th>Action</th>}
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -500,27 +520,47 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
</td>
|
||||
)}
|
||||
<td>
|
||||
<TextInput
|
||||
<SelectInput
|
||||
required
|
||||
name={`feed_data.${idx}.feed_name`}
|
||||
value={feed.feed_name}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
value={
|
||||
pakanOptions.find(
|
||||
(opt: OptionType) =>
|
||||
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={
|
||||
isRepeaterInputError(
|
||||
'feed_data',
|
||||
'feed_name',
|
||||
idx
|
||||
).isError
|
||||
isRepeaterInputError('feed_data', 'feed_id', idx)
|
||||
.isError
|
||||
}
|
||||
errorMessage={
|
||||
isRepeaterInputError(
|
||||
'feed_data',
|
||||
'feed_name',
|
||||
idx
|
||||
).errorMessage
|
||||
isRepeaterInputError('feed_data', 'feed_id', idx)
|
||||
.errorMessage
|
||||
}
|
||||
readOnly={type === 'detail'}
|
||||
isDisabled={type === 'detail'}
|
||||
isClearable
|
||||
className={{
|
||||
wrapper: 'w-full min-w-24',
|
||||
}}
|
||||
@@ -542,7 +582,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
isRepeaterInputError('feed_data', 'feed_qty', idx)
|
||||
.errorMessage
|
||||
}
|
||||
readOnly={type === 'detail'}
|
||||
readOnly={true}
|
||||
className={{
|
||||
wrapper: 'w-full min-w-24',
|
||||
}}
|
||||
@@ -847,7 +887,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
</th>
|
||||
)}
|
||||
<th>Vaccine Name</th>
|
||||
<th>Total Stock</th>
|
||||
<th>Total Stock (Available Stock)</th>
|
||||
<th>Used Stock</th>
|
||||
{type !== 'detail' && <th>Action</th>}
|
||||
</tr>
|
||||
@@ -874,27 +914,53 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
</td>
|
||||
)}
|
||||
<td>
|
||||
<TextInput
|
||||
<SelectInput
|
||||
required
|
||||
name={`vaccination.${idx}.vaccine_name`}
|
||||
value={vaccine.vaccine_name}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
value={
|
||||
ovkOptions.find(
|
||||
(opt: OptionType) =>
|
||||
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={
|
||||
isRepeaterInputError(
|
||||
'vaccination',
|
||||
'vaccine_name',
|
||||
'vaccine_id',
|
||||
idx
|
||||
).isError
|
||||
}
|
||||
errorMessage={
|
||||
isRepeaterInputError(
|
||||
'vaccination',
|
||||
'vaccine_name',
|
||||
'vaccine_id',
|
||||
idx
|
||||
).errorMessage
|
||||
}
|
||||
readOnly={type === 'detail'}
|
||||
isDisabled={type === 'detail'}
|
||||
isClearable
|
||||
className={{
|
||||
wrapper: 'w-full min-w-24',
|
||||
}}
|
||||
@@ -922,7 +988,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
idx
|
||||
).errorMessage
|
||||
}
|
||||
readOnly={type === 'detail'}
|
||||
readOnly={true}
|
||||
className={{
|
||||
wrapper: 'w-full min-w-24',
|
||||
}}
|
||||
|
||||
Vendored
+2
-2
@@ -38,7 +38,7 @@ export type CreateRecordingPayload = {
|
||||
location_id: number;
|
||||
coop_id: number;
|
||||
feed_data: {
|
||||
feed_name: string;
|
||||
feed_id: string;
|
||||
feed_qty: number;
|
||||
feed_stock: number;
|
||||
}[];
|
||||
@@ -48,7 +48,7 @@ export type CreateRecordingPayload = {
|
||||
average_chicken_weight: number;
|
||||
}[];
|
||||
vaccination: {
|
||||
vaccine_name: string;
|
||||
vaccine_id: string;
|
||||
total_stock: number;
|
||||
used_stock: number;
|
||||
}[];
|
||||
|
||||
Reference in New Issue
Block a user