From 4381e42aaf66b292bad0189afad8e4a37ebbb4a0 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 24 Oct 2025 09:24:28 +0700 Subject: [PATCH] refactor(FE-114): update input handling for vaccination stock, mortality count, and feed stock with improved parsing and formatting --- src/components/helper/FieldMessage.tsx | 65 - .../recording/form/RecordingForm.tsx | 1412 ++++++++--------- 2 files changed, 706 insertions(+), 771 deletions(-) delete mode 100644 src/components/helper/FieldMessage.tsx diff --git a/src/components/helper/FieldMessage.tsx b/src/components/helper/FieldMessage.tsx deleted file mode 100644 index e43d0a63..00000000 --- a/src/components/helper/FieldMessage.tsx +++ /dev/null @@ -1,65 +0,0 @@ -'use client'; - -import { ReactNode } from 'react'; - -import { cn } from '@/lib/helper'; - -type FieldMessageTone = 'error' | 'info' | 'success'; - -export interface FieldMessageProps { - message?: ReactNode; - tone?: FieldMessageTone; - isVisible?: boolean; - persistent?: boolean; - className?: string; - ariaLive?: 'off' | 'polite' | 'assertive'; -} - -const toneClassName: Record = { - error: 'text-error', - info: 'text-base-content/60', - success: 'text-success', -}; - -/** - * Shared helper to render bottom field feedback without causing layout shift. - * Keeps a minimal slot height, but expands when the content wraps onto multiple lines. - */ -export const FieldMessage = ({ - message, - tone = 'info', - isVisible, - persistent = true, - className, - ariaLive, -}: FieldMessageProps) => { - const hasMessage = Boolean(message); - const visible = isVisible ?? hasMessage; - const liveRegion = ariaLive ?? (tone === 'error' ? 'assertive' : 'polite'); - - return ( -
- - {visible || persistent ? (message ?? '\u00A0') : message} - -
- ); -}; - -export default FieldMessage; diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index 99b6ad00..4d8ef9ca 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -214,9 +214,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const flockOptions = isResponseSuccess(projectFlocks) ? projectFlocks.data.map((flock) => ({ - value: flock.id, - label: flock.flock.name, - })) + value: flock.id, + label: flock.flock.name, + })) : []; const coopOptions = useMemo(() => { @@ -698,8 +698,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { value={ formik.values.recording_date instanceof Date ? formik.values.recording_date - .toISOString() - .substring(0, 10) + .toISOString() + .substring(0, 10) : '' } onChange={(e) => { @@ -781,26 +781,74 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
- - {type !== 'detail' && ( - + {type !== 'detail' && ( + + )} + + + + {type !== 'detail' && } + + + + {formik.values.feed_data?.map((feed, idx) => ( + + {type !== 'detail' && ( + )} - - - - {type !== 'detail' && } - - - - {formik.values.feed_data?.map((feed, idx) => ( - - {type !== 'detail' && ( - - )} - + }, 0); + }} + options={pakanOptions} + isLoading={isLoadingPakan} + isError={ + isRepeaterInputError('feed_data', 'feed_id', idx) + .isError + } + errorMessage={ + isRepeaterInputError('feed_data', 'feed_id', idx) + .errorMessage + } + isDisabled={type === 'detail'} + isClearable + className={{ + wrapper: 'w-full min-w-52 md:min-w-72 lg:min-w-80', + }} + /> + + + + {type !== 'detail' && ( - - {type !== 'detail' && ( - - )} - - ))} + )} + + ))}
- 0 +
+ 0 + } + onChange={( + e: React.ChangeEvent + ) => { + if (e.target.checked) { + setSelectedFeed( + formik.values.feed_data?.map((_, idx) => idx) ?? + [] + ); + } else { + setSelectedFeed([]); } + }} + classNames={{ + wrapper: 'flex justify-center', + checkbox: 'checkbox checkbox-sm', + }} + /> + + Nama Pakan + + * + + Total Stock pada saat ini + Jumlah Stock yang digunakan + + * + + Action
+ + e: React.ChangeEvent, ) => { if (e.target.checked) { - setSelectedFeed( - formik.values.feed_data?.map((_, idx) => idx) ?? - [] - ); + setSelectedFeed([...selectedFeed, idx]); } else { - setSelectedFeed([]); + setSelectedFeed( + selectedFeed.filter((i) => i !== idx), + ); } }} classNames={{ @@ -808,178 +856,130 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { checkbox: 'checkbox checkbox-sm', }} /> - + - Nama Pakan - - * - - Total Stock pada saat ini - Jumlah Stock yang digunakan - - * - - Action
- , - ) => { - if (e.target.checked) { - setSelectedFeed([...selectedFeed, idx]); - } else { - setSelectedFeed( - selectedFeed.filter((i) => i !== idx), - ); - } - }} - classNames={{ - wrapper: 'flex justify-center', - checkbox: 'checkbox checkbox-sm', - }} - /> - + - Number(opt.value) === Number(feed.feed_id) - ) ?? null - } - onChange={(val) => { - const productWarehouseId = - (val as OptionType)?.value ?? 0; - const stock = productWarehouseId - ? (pakanStockMap.get( - productWarehouseId as number - ) ?? '') - : ''; + required + value={ + pakanOptions.find( + (opt: OptionType) => + Number(opt.value) === Number(feed.feed_id) + ) ?? null + } + onChange={(val) => { + const productWarehouseId = + (val as OptionType)?.value ?? 0; + const stock = productWarehouseId + ? (pakanStockMap.get( + productWarehouseId as number + ) ?? '') + : ''; - formik.setFieldValue(`feed_data.${idx}.feed`, val); - formik.setFieldValue( + formik.setFieldValue(`feed_data.${idx}.feed`, val); + formik.setFieldValue( + `feed_data.${idx}.feed_id`, + productWarehouseId || '' + ); + formik.setFieldValue( + `feed_data.${idx}.feed_qty`, + stock + ); + formik.setFieldValue( + `feed_data.${idx}.feed_stock`, + 0 + ); + setTimeout(() => { + formik.setFieldTouched( + `feed_data.${idx}.feed`, + true + ); + formik.setFieldTouched( `feed_data.${idx}.feed_id`, - productWarehouseId || '' + true ); - formik.setFieldValue( - `feed_data.${idx}.feed_qty`, - stock - ); - formik.setFieldValue( - `feed_data.${idx}.feed_stock`, - 0 - ); - setTimeout(() => { - formik.setFieldTouched( - `feed_data.${idx}.feed`, - true - ); - formik.setFieldTouched( - `feed_data.${idx}.feed_id`, - true - ); - }, 0); - }} - options={pakanOptions} - isLoading={isLoadingPakan} - isError={ - isRepeaterInputError('feed_data', 'feed_id', idx) - .isError - } - errorMessage={ - isRepeaterInputError('feed_data', 'feed_id', idx) - .errorMessage - } - isDisabled={type === 'detail'} - isClearable - className={{ - wrapper: 'w-full min-w-52 md:min-w-72 lg:min-w-80', - }} - /> - + + + + - +
+ +
- - -
- -
-
@@ -1025,100 +1025,164 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
- - {type !== 'detail' && ( - + {type !== 'detail' && ( + - )} - + )} + - + - + - {type !== 'detail' && } - + + {type !== 'detail' && } + - {formik.values.body_weight?.map((weight, idx) => ( - - {type !== 'detail' && ( - - )} - + {type !== 'detail' && ( + + )} + + + + + + {type !== 'detail' && ( - - {type !== 'detail' && ( - - )} - - ))} + )} + + ))}
-
- 0 +
+
+ 0 + } + onChange={( + e: React.ChangeEvent + ) => { + if (e.target.checked) { + setSelectedWeight( + formik.values.body_weight?.map( + (_, idx) => idx + ) ?? [] + ); + } else { + setSelectedWeight([]); } - onChange={( - e: React.ChangeEvent - ) => { - if (e.target.checked) { - setSelectedWeight( - formik.values.body_weight?.map( - (_, idx) => idx - ) ?? [] - ); - } else { - setSelectedWeight([]); - } - }} - classNames={{ - wrapper: 'flex justify-center items-center h-full', - checkbox: 'checkbox checkbox-sm', - }} - /> -
-
- Berat (Gram) - + }} + classNames={{ + wrapper: 'flex justify-center items-center h-full', + checkbox: 'checkbox checkbox-sm', + }} + /> + + + Berat (Gram) + * - - Jumlah Ayam - + + Jumlah Ayam + * - - Rata-rata berat Ayam - + + Rata-rata berat Ayam + * - Action
Action
- - ) => { - if (e.target.checked) { - setSelectedWeight([...selectedWeight, idx]); - } else { - setSelectedWeight( - selectedWeight.filter((i) => i !== idx) - ); - } - }} - classNames={{ - wrapper: 'flex justify-center items-center', - checkbox: 'checkbox checkbox-sm', - }} - /> - + {formik.values.body_weight?.map((weight, idx) => ( +
+ + ) => { + if (e.target.checked) { + setSelectedWeight([...selectedWeight, idx]); + } else { + setSelectedWeight( + selectedWeight.filter((i) => i !== idx) + ); + } + }} + classNames={{ + wrapper: 'flex justify-center items-center', + checkbox: 'checkbox checkbox-sm', + }} + /> + + + + + +
{ isError={ isRepeaterInputError( 'body_weight', - 'chicken_weight', + 'average_chicken_weight', idx ).isError } errorMessage={ isRepeaterInputError( 'body_weight', - 'chicken_weight', + 'average_chicken_weight', idx ).errorMessage } @@ -1144,93 +1208,29 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { className={{ wrapper: 'w-full min-w-24', }} + placeholder='' /> -
- - -
- +
+
-
- -
-
@@ -1276,224 +1276,224 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
- - {type !== 'detail' && ( - + {type !== 'detail' && ( + - )} - + )} + - - + + - {type !== 'detail' && } - + + {type !== 'detail' && } + - {formik.values.vaccination?.map((vaccine, idx) => ( - - {type !== 'detail' && ( - - )} - + {type !== 'detail' && ( + + )} + + }, 0); + }} + options={ovkOptions} + isLoading={isLoadingOvk} + isError={ + isRepeaterInputError( + 'vaccination', + 'vaccine_id', + idx + ).isError + } + errorMessage={ + isRepeaterInputError( + 'vaccination', + 'vaccine_id', + idx + ).errorMessage + } + isDisabled={type === 'detail'} + isClearable + className={{ + wrapper: 'w-full min-w-52 md:min-w-72 lg:min-w-80', + }} + /> + + + + {type !== 'detail' && ( - - {type !== 'detail' && ( - - )} - - ))} + )} + + ))}
-
- 0 +
+
+ 0 + } + onChange={( + e: React.ChangeEvent + ) => { + if (e.target.checked) { + setSelectedVaccine( + formik.values.vaccination?.map( + (_, idx) => idx + ) ?? [] + ); + } else { + setSelectedVaccine([]); } - onChange={( - e: React.ChangeEvent - ) => { - if (e.target.checked) { - setSelectedVaccine( - formik.values.vaccination?.map( - (_, idx) => idx - ) ?? [] - ); - } else { - setSelectedVaccine([]); - } - }} - classNames={{ - wrapper: 'flex justify-center items-center h-full', - checkbox: 'checkbox checkbox-sm', - }} - /> -
-
- Name Vaksin - + }} + classNames={{ + wrapper: 'flex justify-center items-center h-full', + checkbox: 'checkbox checkbox-sm', + }} + /> + + + Name Vaksin + * - Total Stock pada saat ini - Jumlah Stock yang digunakan - + Total Stock pada saat ini + Jumlah Stock yang digunakan + * - Action
Action
- , - ) => { - if (e.target.checked) { - setSelectedVaccine([...selectedVaccine, idx]); - } else { - setSelectedVaccine( - selectedVaccine.filter((i) => i !== idx), - ); - } - }} - classNames={{ - wrapper: 'flex justify-center', - checkbox: 'checkbox checkbox-sm', - }} - /> - + {formik.values.vaccination?.map((vaccine, idx) => ( +
+ , + ) => { + if (e.target.checked) { + setSelectedVaccine([...selectedVaccine, idx]); + } else { + setSelectedVaccine( + selectedVaccine.filter((i) => i !== idx), + ); + } + }} + classNames={{ + wrapper: 'flex justify-center', + checkbox: 'checkbox checkbox-sm', + }} + /> + - Number(opt.value) === Number(vaccine.vaccine_id) - ) ?? null - } - onChange={(val) => { - const productWarehouseId = - (val as OptionType)?.value ?? 0; - const stock = productWarehouseId - ? (ovkStockMap.get( - productWarehouseId as number - ) ?? '') - : ''; - formik.setFieldValue( + required + value={ + ovkOptions.find( + (opt: OptionType) => + Number(opt.value) === Number(vaccine.vaccine_id) + ) ?? null + } + onChange={(val) => { + const productWarehouseId = + (val as OptionType)?.value ?? 0; + const stock = productWarehouseId + ? (ovkStockMap.get( + productWarehouseId as number + ) ?? '') + : ''; + formik.setFieldValue( + `vaccination.${idx}.vaccine`, + val + ); + formik.setFieldValue( + `vaccination.${idx}.vaccine_id`, + productWarehouseId || '' + ); + formik.setFieldValue( + `vaccination.${idx}.total_stock`, + stock + ); + formik.setFieldValue( + `vaccination.${idx}.used_stock`, + 0 + ); + // Set touched after setting values to trigger validation + setTimeout(() => { + formik.setFieldTouched( `vaccination.${idx}.vaccine`, - val + true ); - formik.setFieldValue( + formik.setFieldTouched( `vaccination.${idx}.vaccine_id`, - productWarehouseId || '' + true ); - formik.setFieldValue( - `vaccination.${idx}.total_stock`, - stock - ); - formik.setFieldValue( - `vaccination.${idx}.used_stock`, - 0 - ); - // Set touched after setting values to trigger validation - setTimeout(() => { - formik.setFieldTouched( - `vaccination.${idx}.vaccine`, - true - ); - formik.setFieldTouched( - `vaccination.${idx}.vaccine_id`, - true - ); - }, 0); - }} - options={ovkOptions} - isLoading={isLoadingOvk} - isError={ - isRepeaterInputError( - 'vaccination', - 'vaccine_id', - idx - ).isError - } - errorMessage={ - isRepeaterInputError( - 'vaccination', - 'vaccine_id', - idx - ).errorMessage - } - isDisabled={type === 'detail'} - isClearable - className={{ - wrapper: 'w-full min-w-52 md:min-w-72 lg:min-w-80', - }} - /> - + + + + - +
+ +
- - -
- -
-
@@ -1539,165 +1539,165 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
- - {type !== 'detail' && ( - + {type !== 'detail' && ( + - )} - + )} + - + - {type !== 'detail' && } - + + {type !== 'detail' && } + - {formik.values.mortality?.map((mortality, idx) => ( - - {type !== 'detail' && ( - - )} - + {type !== 'detail' && ( + + )} + + required + value={RECORDING_FLAG_OPTIONS.find( + (opt) => opt.value === mortality.condition + )} + onChange={(val) => { + formik.setFieldTouched( + `mortality.${idx}.condition`, + true + ); + formik.setFieldValue( + `mortality.${idx}.condition`, + (val as OptionType)?.value + ); + }} + isError={ + isRepeaterInputError('mortality', 'condition', idx) + .isError + } + errorMessage={ + isRepeaterInputError('mortality', 'condition', idx) + .errorMessage + } + options={RECORDING_FLAG_OPTIONS} + isDisabled={type === 'detail'} + isClearable + className={{ + wrapper: 'w-full min-w-52 md:min-w-72 lg:min-w-80', + }} + /> + + + {type !== 'detail' && ( - {type !== 'detail' && ( - - )} - - ))} + )} + + ))}
-
- 0 +
+
+ 0 + } + onChange={( + e: React.ChangeEvent + ) => { + if (e.target.checked) { + setSelectedMortality( + formik.values.mortality?.map( + (_, idx) => idx + ) ?? [] + ); + } else { + setSelectedMortality([]); } - onChange={( - e: React.ChangeEvent - ) => { - if (e.target.checked) { - setSelectedMortality( - formik.values.mortality?.map( - (_, idx) => idx - ) ?? [] - ); - } else { - setSelectedMortality([]); - } - }} - classNames={{ - wrapper: 'flex justify-center items-center h-full', - checkbox: 'checkbox checkbox-sm', - }} - /> -
-
- Kondisi/Alasan Mortalitas - + }} + classNames={{ + wrapper: 'flex justify-center items-center h-full', + checkbox: 'checkbox checkbox-sm', + }} + /> + + + Kondisi/Alasan Mortalitas + * - - Jumlah - + + Jumlah + * - Action
Action
- , - ) => { - if (e.target.checked) { - setSelectedMortality([ - ...selectedMortality, - idx, - ]); - } else { - setSelectedMortality( - selectedMortality.filter((i) => i !== idx), - ); - } - }} - classNames={{ - wrapper: 'flex justify-center', - checkbox: 'checkbox checkbox-sm', - }} - /> - + {formik.values.mortality?.map((mortality, idx) => ( +
+ , + ) => { + if (e.target.checked) { + setSelectedMortality([ + ...selectedMortality, + idx, + ]); + } else { + setSelectedMortality( + selectedMortality.filter((i) => i !== idx), + ); + } + }} + classNames={{ + wrapper: 'flex justify-center', + checkbox: 'checkbox checkbox-sm', + }} + /> + opt.value === mortality.condition - )} - onChange={(val) => { - formik.setFieldTouched( - `mortality.${idx}.condition`, - true - ); - formik.setFieldValue( - `mortality.${idx}.condition`, - (val as OptionType)?.value - ); - }} - isError={ - isRepeaterInputError('mortality', 'condition', idx) - .isError - } - errorMessage={ - isRepeaterInputError('mortality', 'condition', idx) - .errorMessage - } - options={RECORDING_FLAG_OPTIONS} - isDisabled={type === 'detail'} - isClearable - className={{ - wrapper: 'w-full min-w-52 md:min-w-72 lg:min-w-80', - }} - /> - + + - +
+ +
-
- -
-