From cdf0442a2b28df322677d4e7dc7651beede39ab4 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 9 Mar 2026 09:04:14 +0700 Subject: [PATCH] refactor(FE): Add transition restrictions for recording operations --- .../production/recording/RecordingTable.tsx | 65 ++++-- .../recording/form/RecordingForm.tsx | 213 ++++++++++++++---- .../production/recording/recording-utils.ts | 60 +++++ src/types/api/production/project-flock.d.ts | 1 + src/types/api/production/recording.d.ts | 2 +- 5 files changed, 279 insertions(+), 62 deletions(-) create mode 100644 src/components/pages/production/recording/recording-utils.ts diff --git a/src/components/pages/production/recording/RecordingTable.tsx b/src/components/pages/production/recording/RecordingTable.tsx index 1b7a326d..3cd64344 100644 --- a/src/components/pages/production/recording/RecordingTable.tsx +++ b/src/components/pages/production/recording/RecordingTable.tsx @@ -21,6 +21,7 @@ import SelectInput, { useSelect } from '@/components/input/SelectInput'; import DebouncedTextInput from '@/components/input/DebouncedTextInput'; import PopoverButton from '@/components/popover/PopoverButton'; import PopoverContent from '@/components/popover/PopoverContent'; +import Tooltip from '@/components/Tooltip'; import { useFormik } from 'formik'; import { AreaApi } from '@/services/api/master-data'; import { LocationApi } from '@/services/api/master-data'; @@ -36,6 +37,7 @@ import { import RecordingTableSkeleton from '@/components/pages/production/recording/skeleton/RecordingTableSkeleton'; import Table from '@/components/Table'; import { type Recording } from '@/types/api/production/recording'; +import { getRecordingRestriction } from './recording-utils'; import { RecordingApi } from '@/services/api/production'; import { isResponseSuccess } from '@/lib/api-helper'; import { useTableFilter } from '@/services/hooks/useTableFilter'; @@ -105,30 +107,57 @@ const RowOptionsMenu = ({ }; const isRecordingEditable = (recording: Recording) => { - if ( - recording.executed_at && - recording.project_flock?.project_flock_category === 'GROWING' - ) { + const category = recording.project_flock?.project_flock_category; + const isTransition = recording.is_transition; + + const restriction = getRecordingRestriction( + category || 'GROWING', + isTransition + ); + + if (restriction.isLocked) { return false; } return true; }; + const getRecordingRestrictionInfo = (recording: Recording) => { + const category = recording.project_flock?.project_flock_category; + const isTransition = recording.is_transition; + + return getRecordingRestriction(category || 'GROWING', isTransition); + }; + const isApproved = isRecordingApproved(props.row.original); const isRejected = isRecordingRejected(props.row.original); const isEditable = isRecordingEditable(props.row.original); + const restrictionInfo = getRecordingRestrictionInfo(props.row.original); return (
- - - + + + + { cell: (props) => { const category = props.row.original.project_flock?.project_flock_category; + const isTransition = props.row.original.is_transition; if (!category) return '-'; const color = category === 'LAYING' ? 'info' : 'warning'; - return ; + return ( +
+ + {isTransition && ( + + (Transisi) + + )} +
+ ); }, }, { diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index af4ab78b..c744d768 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -70,7 +70,7 @@ import { } from '@/components/pages/production/recording/form/RecordingForm.schema'; import { isResponseSuccess, isResponseError } from '@/lib/api-helper'; -import { formatDate, formatNumber } from '@/lib/helper'; +import { formatDate, formatNumber, cn } from '@/lib/helper'; import toast from 'react-hot-toast'; import ApprovalSteps, { useApprovalSteps, @@ -80,6 +80,7 @@ import { LAYING_RECORDING_APPROVAL_LINE, } from '@/config/approval-line'; import { useFormikErrorList } from '@/services/hooks/useFormikErrorList'; +import { getRecordingRestriction } from '../recording-utils'; interface RecordingFormProps { type?: 'add' | 'edit' | 'detail'; @@ -272,16 +273,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { return recording?.approval?.action === 'REJECTED'; }, []); - const isRecordingEditable = useCallback((recording?: Recording) => { - if ( - recording?.executed_at && - recording?.project_flock?.project_flock_category === 'GROWING' - ) { - return false; - } - return true; - }, []); - // ===== PAYLOAD CREATION HELPERS ===== const createGrowingPayload = useCallback( (values: RecordingGrowingFormValues) => { @@ -476,6 +467,60 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { ? projectFlockKandangDetailData.data : undefined; + // ===== TRANSITION RESTRICTION LOGIC ===== + const isTransitionPeriod = useMemo(() => { + return initialValues?.is_transition ?? false; + }, [initialValues]); + + const recordingRestriction = useMemo(() => { + const category = + initialValues?.project_flock?.project_flock_category || + projectFlockKandangLookup?.project_flock?.category || + projectFlockKandangDetail?.project_flock?.category || + 'GROWING'; + + const isTransition = initialValues?.is_transition ?? false; + + const currentFlockCategory = projectFlockKandangDetail?.project_flock + ?.category as 'GROWING' | 'LAYING' | undefined; + + return getRecordingRestriction( + category as 'GROWING' | 'LAYING', + isTransition, + type === 'edit' ? currentFlockCategory : undefined + ); + }, [ + initialValues, + projectFlockKandangLookup, + projectFlockKandangDetail, + type, + ]); + + const isRecordingEditable = useCallback( + (recording?: Recording) => { + if (!recording) return true; + + const category = recording.project_flock?.project_flock_category; + const isTransition = recording.is_transition; + + const currentFlockCategory = projectFlockKandangDetail?.project_flock + ?.category as 'GROWING' | 'LAYING' | undefined; + + const restriction = getRecordingRestriction( + category || 'GROWING', + isTransition, + currentFlockCategory + ); + + if (restriction.isLocked) { + return false; + } + + return true; + }, + [projectFlockKandangDetail] + ); + const { options: stockProductOptions, rawData: stockProducts, @@ -2324,6 +2369,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { setSelectedStocks([]); } }} + disabled={!recordingRestriction.canEditStock} classNames={{ wrapper: 'flex justify-center', checkbox: 'checkbox checkbox-sm', @@ -2373,6 +2419,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { ); } }} + disabled={!recordingRestriction.canEditStock} classNames={{ wrapper: 'flex justify-center', checkbox: 'checkbox checkbox-sm', @@ -2425,7 +2472,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { isSearchable isDisabled={ type === 'detail' || - !formik.values.project_flock_kandang_id + !formik.values.project_flock_kandang_id || + !recordingRestriction.canEditStock } isClearable={type !== 'detail'} inputPrefix={ @@ -2472,7 +2520,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { ) : null } - disabled={type === 'detail'} + disabled={ + type === 'detail' || + !recordingRestriction.canEditStock + } /> {getStockUsageAdornment(idx)}
@@ -2484,6 +2535,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { type='button' color='error' onClick={() => removeStock(idx)} + disabled={!recordingRestriction.canEditStock} > { {(type as 'add' | 'edit' | 'detail') !== 'detail' && (
- {selectedStocks.length > 0 && ( + {selectedStocks.length > 0 && + recordingRestriction.canEditStock && ( + + )} + - )} - +
)} + {/* Transition Warning Banner -- MOVED UP -- */} + {isTransitionPeriod && ( +
+ + + {isLayingCategory + ? 'Masa Transisi Laying: Hanya Deplesi yang dapat diisi. Stock (Pakan/OVK) tidak dapat diinput.' + : 'Masa Transisi Growing: Hanya Stock (Pakan/OVK) yang dapat diisi. Deplesi tidak dapat diinput.'} + +
+ )} + + {/* Locked Recording Warning */} + {recordingRestriction.isLocked && ( +
+ + {recordingRestriction.lockReason} +
+ )} + {/* Depletions Table */} {((type as 'add' | 'edit' | 'detail') !== 'detail' || (formik.values.depletions?.length ?? 0) > 0) && ( @@ -2562,6 +2657,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { setSelectedDepletions([]); } }} + disabled={!recordingRestriction.canEditDepletion} classNames={{ wrapper: 'flex justify-center', checkbox: 'checkbox checkbox-sm', @@ -2598,6 +2694,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { ); } }} + disabled={!recordingRestriction.canEditDepletion} classNames={{ wrapper: 'flex justify-center', checkbox: 'checkbox checkbox-sm', @@ -2640,7 +2737,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { idx ).errorMessage } - isDisabled={type === 'detail'} + isDisabled={ + type === 'detail' || + !recordingRestriction.canEditDepletion + } className={{ wrapper: 'w-full min-w-48', }} @@ -2679,7 +2779,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { ) : null } - disabled={type === 'detail'} + disabled={ + type === 'detail' || + !recordingRestriction.canEditDepletion + } /> {(type as 'add' | 'edit' | 'detail') !== 'detail' && ( @@ -2689,6 +2792,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { type='button' color='error' onClick={() => removeDepletion(idx)} + disabled={ + !recordingRestriction.canEditDepletion + } > { {(type as 'add' | 'edit' | 'detail') !== 'detail' && (
- {selectedDepletions.length > 0 && ( + {selectedDepletions.length > 0 && + recordingRestriction.canEditDepletion && ( + + )} + - )} - +
)}
diff --git a/src/components/pages/production/recording/recording-utils.ts b/src/components/pages/production/recording/recording-utils.ts new file mode 100644 index 00000000..3b7530c9 --- /dev/null +++ b/src/components/pages/production/recording/recording-utils.ts @@ -0,0 +1,60 @@ +export type RecordingRestriction = { + canEditStock: boolean; + canEditDepletion: boolean; + canEditEgg: boolean; + isLocked: boolean; + lockReason?: string; +}; + +export const getRecordingRestriction = ( + category: 'GROWING' | 'LAYING', + isTransition: boolean, + currentCategory?: 'GROWING' | 'LAYING' +): RecordingRestriction => { + if (currentCategory === 'LAYING' && category === 'GROWING') { + return { + canEditStock: false, + canEditDepletion: false, + canEditEgg: false, + isLocked: true, + lockReason: + 'Recording Growing telah terkunci karena Project Flock sudah masuk fase Laying', + }; + } + + if (category === 'GROWING') { + if (isTransition) { + return { + canEditStock: true, + canEditDepletion: false, + canEditEgg: false, + isLocked: false, + lockReason: undefined, + }; + } + return { + canEditStock: true, + canEditDepletion: true, + canEditEgg: false, + isLocked: false, + lockReason: undefined, + }; + } + + if (isTransition) { + return { + canEditStock: false, + canEditDepletion: true, + canEditEgg: false, + isLocked: false, + lockReason: undefined, + }; + } + return { + canEditStock: true, + canEditDepletion: true, + canEditEgg: true, + isLocked: false, + lockReason: undefined, + }; +}; diff --git a/src/types/api/production/project-flock.d.ts b/src/types/api/production/project-flock.d.ts index 204e7b49..557aebc8 100644 --- a/src/types/api/production/project-flock.d.ts +++ b/src/types/api/production/project-flock.d.ts @@ -74,6 +74,7 @@ export type ProjectFlockKandangLookup = { available_quantity?: number; population: number; chick_in_date: string; + is_transition: boolean; }; export type ProjectFlockAvailableQuantity = { diff --git a/src/types/api/production/recording.d.ts b/src/types/api/production/recording.d.ts index b78642b8..23093169 100644 --- a/src/types/api/production/recording.d.ts +++ b/src/types/api/production/recording.d.ts @@ -49,7 +49,7 @@ export type BaseRecording = { project_flock: ProjectFlock; record_datetime: string; day: number; - executed_at: string; + is_transition: boolean; } & ProductionMetrics; export type RecordingDepletion = {