mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 15:55:48 +00:00
feat(FE-170,174,175): implement approval lines for growing and laying recording categories
This commit is contained in:
@@ -158,6 +158,7 @@ export const formatGroupedApprovalsToApprovalSteps = (
|
|||||||
if (approvalGroup.approvals) {
|
if (approvalGroup.approvals) {
|
||||||
switch (approvalGroup?.approvals[0]?.action) {
|
switch (approvalGroup?.approvals[0]?.action) {
|
||||||
case 'CREATED':
|
case 'CREATED':
|
||||||
|
case 'UPDATED':
|
||||||
case 'APPROVED':
|
case 'APPROVED':
|
||||||
approvalStatus = 'APPROVED';
|
approvalStatus = 'APPROVED';
|
||||||
break;
|
break;
|
||||||
@@ -256,7 +257,7 @@ const useApprovalSteps = ({
|
|||||||
moduleName: string;
|
moduleName: string;
|
||||||
moduleId: string;
|
moduleId: string;
|
||||||
params?: {
|
params?: {
|
||||||
page: number;
|
page?: number;
|
||||||
limit: number;
|
limit: number;
|
||||||
search?: string;
|
search?: string;
|
||||||
group_step_number?: boolean;
|
group_step_number?: boolean;
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import {
|
|||||||
} from '@/services/api/production';
|
} from '@/services/api/production';
|
||||||
import { LocationApi } from '@/services/api/master-data';
|
import { LocationApi } from '@/services/api/master-data';
|
||||||
import { ProductWarehouseApi } from '@/services/api/inventory';
|
import { ProductWarehouseApi } from '@/services/api/inventory';
|
||||||
import { ApprovalApi } from '@/services/api/approval';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CreateGrowingRecordingPayload,
|
CreateGrowingRecordingPayload,
|
||||||
@@ -32,11 +31,7 @@ import {
|
|||||||
UpdateLayingRecordingPayload,
|
UpdateLayingRecordingPayload,
|
||||||
Recording,
|
Recording,
|
||||||
} from '@/types/api/production/recording';
|
} from '@/types/api/production/recording';
|
||||||
import {
|
import { type BaseApiResponse } from '@/types/api/api-general';
|
||||||
type BaseApiResponse,
|
|
||||||
BaseApproval,
|
|
||||||
BaseGroupedApproval,
|
|
||||||
} from '@/types/api/api-general';
|
|
||||||
import { ProjectFlockKandangLookup } from '@/types/api/production/project-flock';
|
import { ProjectFlockKandangLookup } from '@/types/api/production/project-flock';
|
||||||
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
|
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
|
||||||
import { Kandang } from '@/types/api/master-data/kandang';
|
import { Kandang } from '@/types/api/master-data/kandang';
|
||||||
@@ -55,8 +50,13 @@ import {
|
|||||||
import { isResponseSuccess, isResponseError } from '@/lib/api-helper';
|
import { isResponseSuccess, isResponseError } from '@/lib/api-helper';
|
||||||
import { formatDate } from '@/lib/helper';
|
import { formatDate } from '@/lib/helper';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import ApprovalSteps from '@/components/pages/ApprovalSteps';
|
import ApprovalSteps, {
|
||||||
import { RECORDING_APPROVAL_LINE } from '@/config/approval-line';
|
useApprovalSteps,
|
||||||
|
} from '@/components/pages/ApprovalSteps';
|
||||||
|
import {
|
||||||
|
GROWING_RECORDING_APPROVAL_LINE,
|
||||||
|
LAYING_RECORDING_APPROVAL_LINE,
|
||||||
|
} from '@/config/approval-line';
|
||||||
|
|
||||||
interface RecordingFormProps {
|
interface RecordingFormProps {
|
||||||
type?: 'add' | 'edit' | 'detail';
|
type?: 'add' | 'edit' | 'detail';
|
||||||
@@ -434,213 +434,41 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
return approvedProjectFlockKandangsData.data;
|
return approvedProjectFlockKandangsData.data;
|
||||||
}, [approvedProjectFlockKandangsData]);
|
}, [approvedProjectFlockKandangsData]);
|
||||||
|
|
||||||
// ===== APPROVAL DATA FETCHING =====
|
const isLayingCategory =
|
||||||
const approvalHistoryUrl = useMemo(() => {
|
initialValues?.project_flock_category === 'LAYING' ||
|
||||||
if (!initialValues?.id || type !== 'detail') return null;
|
projectFlockKandangLookup?.project_flock?.category === 'LAYING' ||
|
||||||
const params = new URLSearchParams({
|
projectFlockKandangDetail?.project_flock?.category === 'LAYING';
|
||||||
module_name: 'RECORDINGS',
|
|
||||||
module_id: initialValues.id.toString(),
|
|
||||||
group_step_number: 'true',
|
|
||||||
limit: '100',
|
|
||||||
});
|
|
||||||
return `${ApprovalApi.basePath}?${params.toString()}`;
|
|
||||||
}, [initialValues?.id, type]);
|
|
||||||
|
|
||||||
const { data: approvalHistoryData } = useSWR(
|
const isGrowingCategory =
|
||||||
approvalHistoryUrl,
|
initialValues?.project_flock_category === 'GROWING' ||
|
||||||
approvalHistoryUrl ? ApprovalApi.getAllFetcher : null
|
projectFlockKandangLookup?.project_flock?.category === 'GROWING' ||
|
||||||
);
|
projectFlockKandangDetail?.project_flock?.category === 'GROWING';
|
||||||
|
|
||||||
// Helper functions for approval data processing
|
const recordingApprovalLines = useMemo(() => {
|
||||||
const groupApprovalsByStep = useCallback(
|
if (isLayingCategory) {
|
||||||
(approvals: BaseApproval[]): BaseGroupedApproval[] => {
|
return LAYING_RECORDING_APPROVAL_LINE;
|
||||||
const groups: Record<number, BaseGroupedApproval> = {};
|
}
|
||||||
for (const approval of approvals) {
|
if (isGrowingCategory) {
|
||||||
if (!groups[approval.step_number]) {
|
return GROWING_RECORDING_APPROVAL_LINE;
|
||||||
groups[approval.step_number] = {
|
}
|
||||||
step_number: approval.step_number,
|
return GROWING_RECORDING_APPROVAL_LINE;
|
||||||
step_name: approval.step_name,
|
}, [isLayingCategory, isGrowingCategory]);
|
||||||
approvals: [],
|
|
||||||
};
|
// ===== APPROVAL DATA FETCHING USING HOOK =====
|
||||||
}
|
const {
|
||||||
groups[approval.step_number].approvals.push(approval);
|
approvals,
|
||||||
}
|
isLoading: approvalsLoading,
|
||||||
return Object.values(groups);
|
refresh: refreshApprovals,
|
||||||
|
} = useApprovalSteps({
|
||||||
|
latestApproval: initialValues?.approval,
|
||||||
|
approvalLines: recordingApprovalLines,
|
||||||
|
moduleName: 'RECORDINGS',
|
||||||
|
moduleId: initialValues?.id?.toString() ?? '',
|
||||||
|
params: {
|
||||||
|
limit: 100,
|
||||||
|
group_step_number: true,
|
||||||
},
|
},
|
||||||
[]
|
});
|
||||||
);
|
|
||||||
|
|
||||||
const isGroupedApprovalData = useCallback(
|
|
||||||
(
|
|
||||||
data: BaseApproval[] | BaseGroupedApproval[]
|
|
||||||
): data is BaseGroupedApproval[] => {
|
|
||||||
if (!data || data.length === 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const firstElement = data[0];
|
|
||||||
return (
|
|
||||||
typeof firstElement === 'object' &&
|
|
||||||
firstElement !== null &&
|
|
||||||
'approvals' in firstElement &&
|
|
||||||
Array.isArray(firstElement.approvals)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Process approval data
|
|
||||||
const { groupedApprovals, latestApproval } = useMemo(() => {
|
|
||||||
const latest = initialValues?.approval;
|
|
||||||
|
|
||||||
const rawData = isResponseSuccess(approvalHistoryData)
|
|
||||||
? approvalHistoryData.data
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
let processedGroupedApprovals: BaseGroupedApproval[] = [];
|
|
||||||
|
|
||||||
if (rawData) {
|
|
||||||
if (isGroupedApprovalData(rawData)) {
|
|
||||||
processedGroupedApprovals = rawData as BaseGroupedApproval[];
|
|
||||||
} else {
|
|
||||||
processedGroupedApprovals = groupApprovalsByStep(
|
|
||||||
rawData as BaseApproval[]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
latest &&
|
|
||||||
!processedGroupedApprovals.find(
|
|
||||||
(g) => g.step_number === latest.step_number
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
processedGroupedApprovals.push({
|
|
||||||
step_number: latest.step_number,
|
|
||||||
step_name: latest.step_name,
|
|
||||||
approvals: [latest],
|
|
||||||
});
|
|
||||||
|
|
||||||
processedGroupedApprovals.sort((a, b) => a.step_number - b.step_number);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
groupedApprovals: processedGroupedApprovals,
|
|
||||||
latestApproval: latest,
|
|
||||||
};
|
|
||||||
}, [
|
|
||||||
approvalHistoryData,
|
|
||||||
initialValues?.approval,
|
|
||||||
isGroupedApprovalData,
|
|
||||||
groupApprovalsByStep,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Format approval steps for display
|
|
||||||
const approvalStepsData = useMemo(() => {
|
|
||||||
if (type !== 'detail') {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return RECORDING_APPROVAL_LINE.map(
|
|
||||||
(
|
|
||||||
approvalLineItem
|
|
||||||
): {
|
|
||||||
name: string;
|
|
||||||
status: 'APPROVED' | 'REJECTED' | 'WAITING' | 'IDLE';
|
|
||||||
logs: Array<{
|
|
||||||
action_by?: string;
|
|
||||||
date?: string;
|
|
||||||
notes?: string | null;
|
|
||||||
}>;
|
|
||||||
} => {
|
|
||||||
const approvalGroup = groupedApprovals.find(
|
|
||||||
(approvalGroupItem) =>
|
|
||||||
approvalGroupItem.step_number === approvalLineItem.step_number
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!latestApproval) {
|
|
||||||
return {
|
|
||||||
name: approvalLineItem.step_name,
|
|
||||||
status: 'IDLE',
|
|
||||||
logs: approvalGroup
|
|
||||||
? approvalGroup.approvals.map((approval) => ({
|
|
||||||
action_by: approval.action_by.name,
|
|
||||||
date: approval.action_at,
|
|
||||||
notes: approval.notes,
|
|
||||||
}))
|
|
||||||
: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentStepNumber = approvalLineItem.step_number;
|
|
||||||
const latestStepNumber = latestApproval.step_number;
|
|
||||||
|
|
||||||
if (!approvalGroup) {
|
|
||||||
if (currentStepNumber === latestStepNumber + 1) {
|
|
||||||
return {
|
|
||||||
name: approvalLineItem.step_name,
|
|
||||||
status: 'WAITING',
|
|
||||||
logs: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
name: approvalLineItem.step_name,
|
|
||||||
status: 'IDLE',
|
|
||||||
logs: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let approvalStatus: 'APPROVED' | 'REJECTED' | 'WAITING' | 'IDLE' =
|
|
||||||
'IDLE';
|
|
||||||
|
|
||||||
if (currentStepNumber <= latestStepNumber) {
|
|
||||||
if (approvalGroup.approvals && approvalGroup.approvals.length > 0) {
|
|
||||||
const latestApprovalInGroup = approvalGroup.approvals.sort(
|
|
||||||
(a, b) =>
|
|
||||||
new Date(b.action_at).getTime() -
|
|
||||||
new Date(a.action_at).getTime()
|
|
||||||
)[0];
|
|
||||||
|
|
||||||
switch (latestApprovalInGroup.action) {
|
|
||||||
case 'CREATED':
|
|
||||||
case 'APPROVED':
|
|
||||||
approvalStatus = 'APPROVED';
|
|
||||||
break;
|
|
||||||
case 'REJECTED':
|
|
||||||
approvalStatus = 'REJECTED';
|
|
||||||
break;
|
|
||||||
case 'UPDATED':
|
|
||||||
approvalStatus = 'APPROVED';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
approvalStatus = 'APPROVED';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (currentStepNumber === latestStepNumber + 1) {
|
|
||||||
approvalStatus = 'WAITING';
|
|
||||||
} else {
|
|
||||||
approvalStatus = 'IDLE';
|
|
||||||
}
|
|
||||||
|
|
||||||
const approvalLogs = approvalGroup.approvals.map((approval) => ({
|
|
||||||
action_by: approval.action_by.name,
|
|
||||||
date: approval.action_at,
|
|
||||||
notes: approval.notes,
|
|
||||||
}));
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: approvalGroup.step_name,
|
|
||||||
status: approvalStatus,
|
|
||||||
logs: approvalLogs,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Gagal memformat approval steps:', error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}, [groupedApprovals, latestApproval, type]);
|
|
||||||
|
|
||||||
// ===== DATA PROCESSING =====
|
// ===== DATA PROCESSING =====
|
||||||
const locationOptions = useMemo(() => {
|
const locationOptions = useMemo(() => {
|
||||||
@@ -889,11 +717,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
return options;
|
return options;
|
||||||
}, [eggProductsData, initialValues, type, selectedKandang]);
|
}, [eggProductsData, initialValues, type, selectedKandang]);
|
||||||
|
|
||||||
const isLayingCategory =
|
|
||||||
initialValues?.project_flock_category === 'LAYING' ||
|
|
||||||
projectFlockKandangLookup?.project_flock?.category === 'LAYING' ||
|
|
||||||
projectFlockKandangDetail?.project_flock?.category === 'LAYING';
|
|
||||||
|
|
||||||
// ===== FORMIK SETUP =====
|
// ===== FORMIK SETUP =====
|
||||||
const formikInitialValues = useMemo(() => {
|
const formikInitialValues = useMemo(() => {
|
||||||
let baseValues;
|
let baseValues;
|
||||||
@@ -1276,6 +1099,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
if (isResponseSuccess(approveResponse)) {
|
if (isResponseSuccess(approveResponse)) {
|
||||||
toast.success('Recording berhasil disetujui!');
|
toast.success('Recording berhasil disetujui!');
|
||||||
approveModal.closeModal();
|
approveModal.closeModal();
|
||||||
|
await refreshApprovals();
|
||||||
router.push('/production/recording');
|
router.push('/production/recording');
|
||||||
} else {
|
} else {
|
||||||
toast.error(
|
toast.error(
|
||||||
@@ -1298,6 +1122,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
if (isResponseSuccess(rejectResponse)) {
|
if (isResponseSuccess(rejectResponse)) {
|
||||||
toast.success('Recording berhasil ditolak!');
|
toast.success('Recording berhasil ditolak!');
|
||||||
rejectModal.closeModal();
|
rejectModal.closeModal();
|
||||||
|
await refreshApprovals();
|
||||||
router.push('/production/recording');
|
router.push('/production/recording');
|
||||||
} else {
|
} else {
|
||||||
toast.error(
|
toast.error(
|
||||||
@@ -1586,7 +1411,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
}
|
}
|
||||||
}, [bodyWeightValues, editingAverageIndex, manuallyEditedRows]);
|
}, [bodyWeightValues, editingAverageIndex, manuallyEditedRows]);
|
||||||
|
|
||||||
// ===== RENDER =====
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<section className='w-full'>
|
<section className='w-full'>
|
||||||
@@ -1645,37 +1469,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
</h1>
|
</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{/* Project Flock Info Card */}
|
{type === 'detail' && approvals && !approvalsLoading && (
|
||||||
{(projectFlockKandangLookup || projectFlockKandangDetail) && (
|
<ApprovalSteps approvals={approvals} />
|
||||||
<div className='flex items-center gap-2 mb-4'>
|
|
||||||
{/* Approval Steps for all categories */}
|
|
||||||
{type === 'detail' && approvalStepsData.length > 0 && (
|
|
||||||
<div className='flex-1 mt-4'>
|
|
||||||
<ApprovalSteps approvals={approvalStepsData} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{/* Default approval steps for add/edit modes */}
|
|
||||||
{type !== 'detail' && (
|
|
||||||
<div className='flex-1 mt-4'>
|
|
||||||
<ApprovalSteps
|
|
||||||
approvals={[
|
|
||||||
{
|
|
||||||
name: RECORDING_APPROVAL_LINE[0].step_name,
|
|
||||||
status: 'APPROVED',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: RECORDING_APPROVAL_LINE[1].step_name,
|
|
||||||
status: 'WAITING',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: RECORDING_APPROVAL_LINE[2].step_name,
|
|
||||||
status: 'IDLE',
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<form
|
<form
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
|||||||
import CheckboxInput from '@/components/input/CheckboxInput';
|
import CheckboxInput from '@/components/input/CheckboxInput';
|
||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
import Card from '@/components/Card';
|
import Card from '@/components/Card';
|
||||||
import StepItem from '@/components/steps/StepItem';
|
|
||||||
import Badge from '@/components/Badge';
|
import Badge from '@/components/Badge';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -22,11 +21,7 @@ import {
|
|||||||
Recording,
|
Recording,
|
||||||
} from '@/types/api/production/recording';
|
} from '@/types/api/production/recording';
|
||||||
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
|
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
|
||||||
import {
|
import { type BaseApiResponse } from '@/types/api/api-general';
|
||||||
type BaseApiResponse,
|
|
||||||
BaseApproval,
|
|
||||||
BaseGroupedApproval,
|
|
||||||
} from '@/types/api/api-general';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
RecordingGradingFormSchema,
|
RecordingGradingFormSchema,
|
||||||
@@ -37,24 +32,15 @@ import {
|
|||||||
|
|
||||||
import { cn, formatDate } from '@/lib/helper';
|
import { cn, formatDate } from '@/lib/helper';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { isResponseSuccess, isResponseError } from '@/lib/api-helper';
|
import { isResponseError } from '@/lib/api-helper';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
RecordingApi,
|
RecordingApi,
|
||||||
ProjectFlockKandangApi,
|
ProjectFlockKandangApi,
|
||||||
} from '@/services/api/production';
|
} from '@/services/api/production';
|
||||||
import { ApprovalApi } from '@/services/api/approval';
|
|
||||||
|
|
||||||
import { useModal } from '@/components/Modal';
|
import { useModal } from '@/components/Modal';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import ApprovalSteps from '@/components/pages/ApprovalSteps';
|
|
||||||
import { RECORDING_APPROVAL_LINE } from '@/config/approval-line';
|
|
||||||
|
|
||||||
type FormStepStatus = {
|
|
||||||
name: string;
|
|
||||||
isCompleted: boolean;
|
|
||||||
isCurrent: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
// INTERFACES & PROPS
|
// INTERFACES & PROPS
|
||||||
interface GradingFormProps {
|
interface GradingFormProps {
|
||||||
@@ -75,7 +61,6 @@ const GradingForm = ({ type = 'add', initialValues }: GradingFormProps) => {
|
|||||||
const [selectedGradingItems, setSelectedGradingItems] = useState<number[]>(
|
const [selectedGradingItems, setSelectedGradingItems] = useState<number[]>(
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
const [formSteps, setFormSteps] = useState<FormStepStatus[] | null>(null);
|
|
||||||
const [gradingFormErrorMessage, setGradingFormErrorMessage] = useState('');
|
const [gradingFormErrorMessage, setGradingFormErrorMessage] = useState('');
|
||||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||||
const deleteModal = useModal();
|
const deleteModal = useModal();
|
||||||
@@ -113,213 +98,6 @@ const GradingForm = ({ type = 'add', initialValues }: GradingFormProps) => {
|
|||||||
? (projectFlockKandangData.data as unknown as ProjectFlockKandang)
|
? (projectFlockKandangData.data as unknown as ProjectFlockKandang)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
// ===== APPROVAL DATA FETCHING =====
|
|
||||||
const approvalHistoryUrl = useMemo(() => {
|
|
||||||
if (!recording?.id || type !== 'detail') return null;
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
module_name: 'RECORDINGS',
|
|
||||||
module_id: recording.id.toString(),
|
|
||||||
group_step_number: 'true',
|
|
||||||
});
|
|
||||||
return `${ApprovalApi.basePath}?${params.toString()}`;
|
|
||||||
}, [recording?.id, type]);
|
|
||||||
|
|
||||||
const { data: approvalHistoryData } = useSWR(
|
|
||||||
approvalHistoryUrl,
|
|
||||||
approvalHistoryUrl ? ApprovalApi.getAllFetcher : null
|
|
||||||
);
|
|
||||||
|
|
||||||
// Helper functions for approval data processing
|
|
||||||
const groupApprovalsByStep = useCallback(
|
|
||||||
(approvals: BaseApproval[]): BaseGroupedApproval[] => {
|
|
||||||
const groups: Record<number, BaseGroupedApproval> = {};
|
|
||||||
for (const approval of approvals) {
|
|
||||||
if (!groups[approval.step_number]) {
|
|
||||||
groups[approval.step_number] = {
|
|
||||||
step_number: approval.step_number,
|
|
||||||
step_name: approval.step_name,
|
|
||||||
approvals: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
groups[approval.step_number].approvals.push(approval);
|
|
||||||
}
|
|
||||||
return Object.values(groups);
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const isGroupedApprovalData = useCallback(
|
|
||||||
(
|
|
||||||
data: BaseApproval[] | BaseGroupedApproval[]
|
|
||||||
): data is BaseGroupedApproval[] => {
|
|
||||||
if (!data || data.length === 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const firstElement = data[0];
|
|
||||||
return (
|
|
||||||
typeof firstElement === 'object' &&
|
|
||||||
firstElement !== null &&
|
|
||||||
'approvals' in firstElement &&
|
|
||||||
Array.isArray(firstElement.approvals)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Process approval data
|
|
||||||
const { groupedApprovals, latestApproval } = useMemo(() => {
|
|
||||||
const latest = recording?.approval;
|
|
||||||
|
|
||||||
const rawData = isResponseSuccess(approvalHistoryData)
|
|
||||||
? approvalHistoryData.data
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
let processedGroupedApprovals: BaseGroupedApproval[] = [];
|
|
||||||
|
|
||||||
if (rawData) {
|
|
||||||
if (isGroupedApprovalData(rawData)) {
|
|
||||||
processedGroupedApprovals = rawData as BaseGroupedApproval[];
|
|
||||||
} else {
|
|
||||||
processedGroupedApprovals = groupApprovalsByStep(
|
|
||||||
rawData as BaseApproval[]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
latest &&
|
|
||||||
!processedGroupedApprovals.find(
|
|
||||||
(g) => g.step_number === latest.step_number
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
processedGroupedApprovals.push({
|
|
||||||
step_number: latest.step_number,
|
|
||||||
step_name: latest.step_name,
|
|
||||||
approvals: [latest],
|
|
||||||
});
|
|
||||||
|
|
||||||
processedGroupedApprovals.sort((a, b) => a.step_number - b.step_number);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
groupedApprovals: processedGroupedApprovals,
|
|
||||||
latestApproval: latest,
|
|
||||||
};
|
|
||||||
}, [
|
|
||||||
approvalHistoryData,
|
|
||||||
recording?.approval,
|
|
||||||
isGroupedApprovalData,
|
|
||||||
groupApprovalsByStep,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Format approval steps for display
|
|
||||||
const approvalStepsData = useMemo(() => {
|
|
||||||
if (!latestApproval || type !== 'detail') {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return RECORDING_APPROVAL_LINE.map(
|
|
||||||
(
|
|
||||||
approvalLineItem
|
|
||||||
): {
|
|
||||||
name: string;
|
|
||||||
status: 'APPROVED' | 'REJECTED' | 'WAITING' | 'IDLE';
|
|
||||||
logs: Array<{
|
|
||||||
action_by?: string;
|
|
||||||
date?: string;
|
|
||||||
notes?: string | null;
|
|
||||||
}>;
|
|
||||||
} => {
|
|
||||||
const approvalGroup = groupedApprovals.find(
|
|
||||||
(approvalGroupItem) =>
|
|
||||||
approvalGroupItem.step_number === approvalLineItem.step_number
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!latestApproval) {
|
|
||||||
return {
|
|
||||||
name: approvalLineItem.step_name,
|
|
||||||
status: 'IDLE',
|
|
||||||
logs: approvalGroup
|
|
||||||
? approvalGroup.approvals.map((approval) => ({
|
|
||||||
action_by: approval.action_by.name,
|
|
||||||
date: approval.action_at,
|
|
||||||
notes: approval.notes,
|
|
||||||
}))
|
|
||||||
: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentStepNumber = approvalLineItem.step_number;
|
|
||||||
const latestStepNumber = latestApproval.step_number;
|
|
||||||
|
|
||||||
if (!approvalGroup) {
|
|
||||||
if (currentStepNumber === latestStepNumber + 1) {
|
|
||||||
return {
|
|
||||||
name: approvalLineItem.step_name,
|
|
||||||
status: 'WAITING',
|
|
||||||
logs: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
name: approvalLineItem.step_name,
|
|
||||||
status: 'IDLE',
|
|
||||||
logs: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let approvalStatus: 'APPROVED' | 'REJECTED' | 'WAITING' | 'IDLE' =
|
|
||||||
'IDLE';
|
|
||||||
|
|
||||||
if (currentStepNumber <= latestStepNumber) {
|
|
||||||
if (approvalGroup.approvals && approvalGroup.approvals.length > 0) {
|
|
||||||
const latestApprovalInGroup = approvalGroup.approvals.sort(
|
|
||||||
(a, b) =>
|
|
||||||
new Date(b.action_at).getTime() -
|
|
||||||
new Date(a.action_at).getTime()
|
|
||||||
)[0];
|
|
||||||
|
|
||||||
switch (latestApprovalInGroup.action) {
|
|
||||||
case 'CREATED':
|
|
||||||
case 'APPROVED':
|
|
||||||
approvalStatus = 'APPROVED';
|
|
||||||
break;
|
|
||||||
case 'REJECTED':
|
|
||||||
approvalStatus = 'REJECTED';
|
|
||||||
break;
|
|
||||||
case 'UPDATED':
|
|
||||||
approvalStatus = 'APPROVED';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
approvalStatus = 'APPROVED';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (currentStepNumber === latestStepNumber + 1) {
|
|
||||||
approvalStatus = 'WAITING';
|
|
||||||
} else {
|
|
||||||
approvalStatus = 'IDLE';
|
|
||||||
}
|
|
||||||
|
|
||||||
const approvalLogs = approvalGroup.approvals.map((approval) => ({
|
|
||||||
action_by: approval.action_by.name,
|
|
||||||
date: approval.action_at,
|
|
||||||
notes: approval.notes,
|
|
||||||
}));
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: approvalGroup.step_name,
|
|
||||||
status: approvalStatus,
|
|
||||||
logs: approvalLogs,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Gagal memformat approval steps:', error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}, [groupedApprovals, latestApproval, type]);
|
|
||||||
|
|
||||||
const konsumsiBaikEggData = useMemo(() => {
|
const konsumsiBaikEggData = useMemo(() => {
|
||||||
if (!recording?.eggs) return null;
|
if (!recording?.eggs) return null;
|
||||||
|
|
||||||
@@ -568,22 +346,6 @@ const GradingForm = ({ type = 'add', initialValues }: GradingFormProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// EFFECTS
|
// EFFECTS
|
||||||
useEffect(() => {
|
|
||||||
const steps: FormStepStatus[] = [
|
|
||||||
{
|
|
||||||
name: 'Recording',
|
|
||||||
isCompleted: true,
|
|
||||||
isCurrent: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Grading',
|
|
||||||
isCompleted: type !== 'add',
|
|
||||||
isCurrent: type === 'add',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
setFormSteps(steps);
|
|
||||||
}, [type]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isGradingExceedsAvailable && currentGradingTotal > 0) {
|
if (isGradingExceedsAvailable && currentGradingTotal > 0) {
|
||||||
toast.error(
|
toast.error(
|
||||||
@@ -629,37 +391,6 @@ const GradingForm = ({ type = 'add', initialValues }: GradingFormProps) => {
|
|||||||
</h1>
|
</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{/* Project Flock Info Card */}
|
|
||||||
<div className='flex items-center gap-2 mb-4'>
|
|
||||||
{/* Approval Steps */}
|
|
||||||
{type === 'detail' && approvalStepsData.length > 0 && (
|
|
||||||
<div className='flex-1 mt-4'>
|
|
||||||
<ApprovalSteps approvals={approvalStepsData} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{/* Default approval steps for add/edit modes */}
|
|
||||||
{type !== 'detail' && (
|
|
||||||
<div className='flex-1 mt-4'>
|
|
||||||
<ApprovalSteps
|
|
||||||
approvals={[
|
|
||||||
{
|
|
||||||
name: RECORDING_APPROVAL_LINE[0].step_name,
|
|
||||||
status: 'APPROVED',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: RECORDING_APPROVAL_LINE[1].step_name,
|
|
||||||
status: 'APPROVED',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: RECORDING_APPROVAL_LINE[2].step_name,
|
|
||||||
status: 'WAITING',
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form
|
<form
|
||||||
onSubmit={formik.handleSubmit}
|
onSubmit={formik.handleSubmit}
|
||||||
className='w-full mt-8 flex flex-col gap-6'
|
className='w-full mt-8 flex flex-col gap-6'
|
||||||
|
|||||||
@@ -47,3 +47,33 @@ export const RECORDING_APPROVAL_LINE: ApprovalLine = [
|
|||||||
step_name: 'Disetujui',
|
step_name: 'Disetujui',
|
||||||
},
|
},
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
export const GROWING_RECORDING_APPROVAL_LINE: ApprovalLine = [
|
||||||
|
{
|
||||||
|
step_number: 1,
|
||||||
|
step_name: 'Grading-Telur',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step_number: 2,
|
||||||
|
step_name: 'Pengajuan',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step_number: 3,
|
||||||
|
step_name: 'Disetujui',
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export const LAYING_RECORDING_APPROVAL_LINE: ApprovalLine = [
|
||||||
|
{
|
||||||
|
step_number: 1,
|
||||||
|
step_name: 'Grading-Telur',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step_number: 2,
|
||||||
|
step_name: 'Pengajuan',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step_number: 3,
|
||||||
|
step_name: 'Disetujui',
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|||||||
Reference in New Issue
Block a user