refactor(FE-238-239-240): implement approval workflow chickin & project flock, membuat custom hook useApprovals, dan handling error format approvals

This commit is contained in:
randy-ar
2025-11-12 15:24:44 +07:00
parent 5dccaf40cb
commit b2f4317c08
13 changed files with 258 additions and 200 deletions
@@ -10,6 +10,9 @@ import Tabs from '@/components/Tabs';
import ChickinFormView from './tabs/ChickinFormView';
import ChickinLogsView from './tabs/ChickLogsView';
import { useState } from 'react';
import ApprovalSteps, {
useApprovalSteps,
} from '@/components/pages/ApprovalSteps';
const ChickinFormKandang = ({
formType = 'add',
initialValues,
@@ -21,9 +24,20 @@ const ChickinFormKandang = ({
}) => {
const [activeTabId, setActiveTabId] = useState<string>('formChickIn');
const {
approvals,
isLoading: approvalsLoading,
refresh: refreshApprovals,
} = useApprovalSteps({
moduleUrl: `/production/project-flock-kandangs/${initialValues?.id}`,
moduleName: 'PROJECT_FLOCK_KANDANGS',
moduleId: initialValues?.id.toString() ?? '',
});
const afterSubmitFormChickin = () => {
setActiveTabId('logsChickIn');
afterSubmit && afterSubmit();
refreshApprovals();
};
return (
@@ -32,6 +46,11 @@ const ChickinFormKandang = ({
title='Chick In DOC'
backUrl={`/production/project-flock/chickin/add?projectFlockId=${initialValues?.project_flock?.id}`}
/>
{approvals && !approvalsLoading && (
<ApprovalSteps approvals={approvals} />
)}
<Card
title='Informasi Kandang'
className={{
@@ -108,7 +127,7 @@ const ChickinFormKandang = ({
content: (
<ChickinLogsView
initialValues={initialValues}
afterSubmit={afterSubmit}
afterSubmit={afterSubmitFormChickin}
/>
),
id: 'logsChickIn',
@@ -47,9 +47,7 @@ const ChickinLogsView = ({
}
confirmModal.closeModal();
setIsApproveLoading(false);
if (afterSubmit) {
afterSubmit();
}
afterSubmit && afterSubmit();
};
return (
@@ -58,7 +58,15 @@ const ProjectFlockChickinDetail = ({
options: options,
isLoadingOptions: isLoadingListProjectFlock,
rawData: listProjectFlock,
} = useSelect<ProjectFlock>(ProjectFlockApi.basePath, 'id', 'flock_name');
} = useSelect<ProjectFlock>(
ProjectFlockApi.basePath,
'id',
'flock_name',
'',
{
search: searchProjectFlock,
}
);
// Handle Function
const handleChickinClick = async (
@@ -242,7 +250,7 @@ const ProjectFlockChickinDetail = ({
</div>
}
data={
isResponseSuccess(listProjectFlockKandang)
projectFlock && isResponseSuccess(listProjectFlockKandang)
? listProjectFlockKandang.data
: []
}
@@ -293,6 +301,8 @@ const ProjectFlockChickinDetail = ({
.replace(/_/g, ' ')
.replace(/\b\w/g, (char) => char.toUpperCase())}
/>
) : projectFlock?.approval?.step_number === 1 ? (
<PillBadge color='red' content={'Tidak Dapat Chick In'} />
) : (
<PillBadge color='gray' content={'Belum Chick In'} />
);
@@ -310,6 +320,7 @@ const ProjectFlockChickinDetail = ({
handleChickinClick(props.row.original);
}}
className='p-1'
disabled={projectFlock?.approval?.step_number === 1}
>
<Icon
icon='mdi:home-import-outline'
@@ -16,7 +16,7 @@ import {
import { Icon } from '@iconify/react';
import { useFormik } from 'formik';
import { useRouter } from 'next/navigation';
import { use, useEffect, useMemo, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import useSWR, { KeyedMutator } from 'swr';
import {
ProjectFlockFormSchema,
@@ -26,7 +26,6 @@ import {
import {
ProjectFlockApprovalPayload,
CreateProjectFlockPayload,
PeriodFlock,
ProjectFlock,
} from '@/types/api/production/project-flock';
import toast from 'react-hot-toast';
@@ -34,23 +33,19 @@ import TextInput from '@/components/input/TextInput';
import { Kandang } from '@/types/api/master-data/kandang';
import Collapse from '@/components/Collapse';
import { ProjectFlockApi } from '@/services/api/production/project-flock';
import { BaseApiResponse, BaseGroupedApproval } from '@/types/api/api-general';
import { BaseApiResponse } from '@/types/api/api-general';
import { APPROVAL_WORKFLOWS, FLOCK_CATEGORY_OPTIONS } from '@/config/constant';
import { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal';
import ProjectFlockKandangTable from './ProjectFlockKandangTable';
import ApprovalSteps from '@/components/pages/ApprovalSteps';
import Steps from '@/components/steps/Steps';
import StepItem from '@/components/steps/StepItem';
import Tooltip from '@/components/Tooltip';
import { id, is } from 'react-day-picker/locale';
import { formatDate } from '@/lib/helper';
import ApprovalSteps, {
useApprovalSteps,
} from '@/components/pages/ApprovalSteps';
import Card from '@/components/Card';
interface ProjectFlockFormProps {
formType?: 'add' | 'edit' | 'detail';
initialValues?: ProjectFlock;
initialApprovals?: BaseGroupedApproval[];
refreshProjectFlocks?: KeyedMutator<
BaseApiResponse<ProjectFlock> | undefined
>;
@@ -59,16 +54,11 @@ interface ProjectFlockFormProps {
const ProjectFlockForm = ({
formType = 'add',
initialValues,
initialApprovals,
refreshProjectFlocks,
}: ProjectFlockFormProps) => {
// State
const router = useRouter();
const projectFlockSteps = APPROVAL_WORKFLOWS.find(
(step) => step.key === 'PROJECT_FLOCKS'
);
const [projectFlockFormErrorMessage, setProjectFlockFormErrorMessage] =
useState('');
const [selectedArea, setSelectedArea] = useState('');
@@ -120,34 +110,28 @@ const ProjectFlockForm = ({
}, [initialValues]);
// Fetch Data
const {
rawData: flocks,
isLoadingOptions: isLoadingFlocks,
options: optionsFlock,
} = useSelect(FlockApi.basePath, 'id', 'name');
const { isLoadingOptions: isLoadingFlocks, options: optionsFlock } =
useSelect(FlockApi.basePath, 'id', 'name');
const {
options: optionsArea,
isLoadingOptions: isLoadingAreas,
rawData: areas,
} = useSelect(AreaApi.basePath, 'id', 'name');
const { options: optionsArea, isLoadingOptions: isLoadingAreas } = useSelect(
AreaApi.basePath,
'id',
'name'
);
const {
options: optionsLocation,
isLoadingOptions: isLoadingLocations,
rawData: locations,
} = useSelect(LocationApi.basePath, 'id', 'name', '', {
area_id:
selectedArea != ''
? selectedArea
: ((initialValues?.area?.id ?? '') as string),
});
const { options: optionsLocation, isLoadingOptions: isLoadingLocations } =
useSelect(LocationApi.basePath, 'id', 'name', '', {
area_id:
selectedArea != ''
? selectedArea
: ((initialValues?.area?.id ?? '') as string),
});
const {
options: optionsFcr,
isLoadingOptions: isLoadingFcrs,
rawData: fcrs,
} = useSelect(FcrApi.basePath, 'id', 'name');
const { options: optionsFcr, isLoadingOptions: isLoadingFcrs } = useSelect(
FcrApi.basePath,
'id',
'name'
);
const kandangUrl = `${KandangApi.basePath}?${new URLSearchParams({
search: '',
@@ -164,6 +148,16 @@ const ProjectFlockForm = ({
(id: string) => ProjectFlockApi.getNextPeriod(id)
);
const {
approvals,
isLoading: approvalsLoading,
refresh: refreshApprovals,
} = useApprovalSteps({
moduleUrl: `/production/project-flocks/${initialValues?.id}`,
moduleName: 'PROJECT_FLOCKS',
moduleId: initialValues?.id.toString() ?? '',
});
useEffect(() => {
if (isResponseSuccess(kandang)) {
if (selectedLocation) {
@@ -516,6 +510,7 @@ const ProjectFlockForm = ({
if (isResponseError(approveProjectFlockRes)) {
toast.error(approveProjectFlockRes?.message as string);
}
refreshApprovals();
confirmModal.closeModal();
setIsApproveLoading(false);
};
@@ -558,79 +553,8 @@ const ProjectFlockForm = ({
</div>
</div>
)}
{formType == 'detail' && initialApprovals && (
<div className='w-full flex items-center gap-2 my-4'>
<Steps className='w-full'>
{projectFlockSteps?.steps.map((step, idx) => {
const approvalLogs = initialApprovals.find(
(approve) => approve.step_number == step.step_number
);
return (
<StepItem
key={step.step_number}
color={
step.step_number <=
(initialValues?.approval.step_number ?? 0)
? 'success'
: undefined
}
icon={
<Tooltip
color={
step.step_number <=
(initialValues?.approval.step_number ?? 0)
? 'success'
: undefined
}
position='bottom'
content={
<ul>
{approvalLogs &&
approvalLogs?.approvals?.map((approval, idx) => {
return (
<li className='mb-2' key={`key-logs-${idx}`}>
<div className='flex flex-col w-full text-start text-base'>
<span>Status: {approval.step_name}</span>
<span>
Oleh: {approval.action_by.name}
</span>
<span>
Tanggal:{' '}
{formatDate(
approval.action_at,
'DD-MM-yyyy HH:mm:ss'
)}
</span>
</div>
</li>
);
})}
</ul>
}
>
{step.step_number <=
(initialValues?.approval.step_number ?? 0) ? (
<Icon
icon='material-symbols:check-rounded'
width={24}
height={24}
/>
) : (
<Icon
icon='material-symbols:check-rounded'
width={24}
height={24}
/>
)}
</Tooltip>
}
>
{step.step_name}
</StepItem>
);
})}
</Steps>
</div>
{approvals && !approvalsLoading && (
<ApprovalSteps approvals={approvals} />
)}
{formType == 'detail' && (
<div className='w-full flex flex-col sm:flex-row gap-2 py-4'>
@@ -675,32 +599,12 @@ const ProjectFlockForm = ({
);
}}
>
<Icon icon='mdi:home-import-outline' width={18} height={18} />
Chickin
</Button>
)}
</div>
)}
<Card
className={{
body: 'text-primary',
}}
>
{JSON.stringify(formik.values)}
</Card>
<Card
className={{
body: 'text-success',
}}
>
{JSON.stringify(formik.initialValues)}
</Card>
<Card
className={{
body: 'text-error',
}}
>
{JSON.stringify(formik.errors)}
</Card>
<form
className='w-auto h-auto'
onSubmit={formik.handleSubmit}
@@ -36,7 +36,6 @@ const ProjectFlockKandangTable = ({
};
return (
<>
{JSON.stringify(initialKandangIdSet)}
<Table<Kandang>
data={listKandang}
columns={[