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 && (
+
+
+ Hapus Terpilih ({selectedStocks.length})
+
+ )}
+
-
- Hapus Terpilih ({selectedStocks.length})
+
+ Tambah Stok
- )}
-
-
- Tambah Stok
-
+
)}
+ {/* 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 && (
+
+
+ Hapus Terpilih ({selectedDepletions.length})
+
+ )}
+
-
- Hapus Terpilih ({selectedDepletions.length})
+
+ Tambah Depletion
- )}
-
-
- Tambah Depletion
-
+
)}
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 = {