mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
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:
@@ -16,8 +16,12 @@ export default function AddChickinKandang() {
|
|||||||
data: projectFlockKandang,
|
data: projectFlockKandang,
|
||||||
isLoading: isLoading,
|
isLoading: isLoading,
|
||||||
mutate: refreshProjectFlockKandang,
|
mutate: refreshProjectFlockKandang,
|
||||||
} = useSWR(projectFlockKandangId, (id: number) =>
|
} = useSWR(
|
||||||
ProjectFlockKandangApi.getSingle(id)
|
`get-single-project-flock-kandang/${projectFlockKandangId}`,
|
||||||
|
async () =>
|
||||||
|
ProjectFlockKandangApi.getSingle(
|
||||||
|
parseInt(projectFlockKandangId as string)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!projectFlockKandangId) {
|
if (!projectFlockKandangId) {
|
||||||
|
|||||||
@@ -2,11 +2,8 @@
|
|||||||
|
|
||||||
import ProjectFlockForm from '@/components/pages/production/project-flock/form/ProjectFlockForm';
|
import ProjectFlockForm from '@/components/pages/production/project-flock/form/ProjectFlockForm';
|
||||||
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||||
import { FlockApi } from '@/services/api/master-data';
|
|
||||||
import { ProjectFlockApi } from '@/services/api/production/project-flock';
|
import { ProjectFlockApi } from '@/services/api/production/project-flock';
|
||||||
import { ProjectFlock } from '@/types/api/production/project-flock';
|
|
||||||
import { useRouter, useSearchParams } from 'next/navigation';
|
import { useRouter, useSearchParams } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
|
|
||||||
const ProjectFlockDetail = () => {
|
const ProjectFlockDetail = () => {
|
||||||
@@ -15,22 +12,12 @@ const ProjectFlockDetail = () => {
|
|||||||
|
|
||||||
const projectFlockId = searchParams.get('projectFlockId');
|
const projectFlockId = searchParams.get('projectFlockId');
|
||||||
|
|
||||||
const [projectName, setProjectName] = useState();
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: projectFlock,
|
data: projectFlock,
|
||||||
isLoading: isLoadingProjectFlock,
|
isLoading: isLoadingProjectFlock,
|
||||||
mutate: refreshProjectFlock,
|
mutate: refreshProjectFlock,
|
||||||
} = useSWR(projectFlockId, (id: number) => ProjectFlockApi.getSingle(id));
|
} = useSWR(projectFlockId, (id: number) => ProjectFlockApi.getSingle(id));
|
||||||
|
|
||||||
const {
|
|
||||||
data: approvalLines,
|
|
||||||
isLoading: isLoadingApprovalLines,
|
|
||||||
mutate: refreshApprovalLines,
|
|
||||||
} = useSWR('approvals', (id: number) =>
|
|
||||||
ProjectFlockApi.getApprovalLines((projectFlockId ?? 0) as number)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!projectFlockId) {
|
if (!projectFlockId) {
|
||||||
router.back();
|
router.back();
|
||||||
|
|
||||||
@@ -51,15 +38,13 @@ const ProjectFlockDetail = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-full p-4 flex flex-col justify-center'>
|
<div className='w-full p-4 flex flex-col justify-center'>
|
||||||
{isLoadingProjectFlock ||
|
{isLoadingProjectFlock && (
|
||||||
(isLoadingApprovalLines && (
|
|
||||||
<span className='loading loading-spinner loading-xl' />
|
<span className='loading loading-spinner loading-xl' />
|
||||||
))}
|
)}
|
||||||
{isResponseSuccess(projectFlock) && isResponseSuccess(approvalLines) && (
|
{isResponseSuccess(projectFlock) && (
|
||||||
<ProjectFlockForm
|
<ProjectFlockForm
|
||||||
formType='detail'
|
formType='detail'
|
||||||
initialValues={projectFlock.data}
|
initialValues={projectFlock.data}
|
||||||
initialApprovals={approvalLines.data}
|
|
||||||
refreshProjectFlocks={refreshProjectFlock}
|
refreshProjectFlocks={refreshProjectFlock}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export const useModal = () => {
|
|||||||
|
|
||||||
const openModal = useCallback(() => {
|
const openModal = useCallback(() => {
|
||||||
if (!ref.current) return;
|
if (!ref.current) return;
|
||||||
ref.current.showModal();
|
ref.current.show();
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -30,7 +30,6 @@ export const useModal = () => {
|
|||||||
open ? closeModal() : openModal();
|
open ? closeModal() : openModal();
|
||||||
}, [open, closeModal, openModal]);
|
}, [open, closeModal, openModal]);
|
||||||
|
|
||||||
// Gunakan useEffect agar event listener tidak didaftarkan berulang kali
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const dialog = ref.current;
|
const dialog = ref.current;
|
||||||
if (!dialog) return;
|
if (!dialog) return;
|
||||||
@@ -48,7 +47,6 @@ export const useModal = () => {
|
|||||||
|
|
||||||
interface ModalProps {
|
interface ModalProps {
|
||||||
ref: RefObject<HTMLDialogElement | null>;
|
ref: RefObject<HTMLDialogElement | null>;
|
||||||
id?: string;
|
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
closeOnBackdrop?: boolean;
|
closeOnBackdrop?: boolean;
|
||||||
className?: {
|
className?: {
|
||||||
@@ -57,13 +55,7 @@ interface ModalProps {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const Modal = ({
|
const Modal = ({ ref, children, closeOnBackdrop, className }: ModalProps) => {
|
||||||
ref,
|
|
||||||
id,
|
|
||||||
children,
|
|
||||||
closeOnBackdrop,
|
|
||||||
className,
|
|
||||||
}: ModalProps) => {
|
|
||||||
const handleBackdropClick = (e: React.MouseEvent<HTMLDialogElement>) => {
|
const handleBackdropClick = (e: React.MouseEvent<HTMLDialogElement>) => {
|
||||||
if (closeOnBackdrop && e.target === ref.current) {
|
if (closeOnBackdrop && e.target === ref.current) {
|
||||||
ref.current?.close();
|
ref.current?.close();
|
||||||
@@ -73,7 +65,6 @@ const Modal = ({
|
|||||||
return (
|
return (
|
||||||
<dialog
|
<dialog
|
||||||
ref={ref}
|
ref={ref}
|
||||||
id={id}
|
|
||||||
className={cn('modal', className?.modal)}
|
className={cn('modal', className?.modal)}
|
||||||
onClick={handleBackdropClick}
|
onClick={handleBackdropClick}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -4,8 +4,17 @@ import StepItem from '@/components/steps/StepItem';
|
|||||||
import Tooltip from '@/components/Tooltip';
|
import Tooltip from '@/components/Tooltip';
|
||||||
|
|
||||||
import { cn, formatDate } from '@/lib/helper';
|
import { cn, formatDate } from '@/lib/helper';
|
||||||
import { BaseApproval, BaseGroupedApproval } from '@/types/api/api-general';
|
import {
|
||||||
import { ApprovalLine } from '@/types/config/constant';
|
BaseApiResponse,
|
||||||
|
BaseApproval,
|
||||||
|
BaseGroupedApproval,
|
||||||
|
ModuleWithApproval,
|
||||||
|
} from '@/types/api/api-general';
|
||||||
|
import { AppConfigData, ApprovalLine } from '@/types/config/constant';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
import { httpClientFetcher } from '@/services/http/client';
|
||||||
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
export type ApprovalStepStatus = 'APPROVED' | 'REJECTED' | 'WAITING' | 'IDLE';
|
export type ApprovalStepStatus = 'APPROVED' | 'REJECTED' | 'WAITING' | 'IDLE';
|
||||||
|
|
||||||
@@ -120,7 +129,7 @@ export const formatGroupedApprovalsToApprovalSteps = (
|
|||||||
|
|
||||||
const currentStepNumber = approvalLineItem.step_number;
|
const currentStepNumber = approvalLineItem.step_number;
|
||||||
const lastStepNumber =
|
const lastStepNumber =
|
||||||
groupedApprovals[groupedApprovals.length - 1].step_number;
|
groupedApprovals[groupedApprovals.length - 1]?.step_number;
|
||||||
|
|
||||||
if (!approvalGroup && currentStepNumber <= lastStepNumber) {
|
if (!approvalGroup && currentStepNumber <= lastStepNumber) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -137,10 +146,11 @@ export const formatGroupedApprovalsToApprovalSteps = (
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let approvalStatus: ApprovalStepStatus;
|
let approvalStatus: ApprovalStepStatus = 'IDLE';
|
||||||
|
|
||||||
if (approvalGroup.step_number <= latestApproval.step_number) {
|
if (approvalGroup.step_number <= latestApproval.step_number) {
|
||||||
switch (approvalGroup.approvals[0].action) {
|
if (approvalGroup.approvals) {
|
||||||
|
switch (approvalGroup?.approvals[0]?.action) {
|
||||||
case 'CREATED':
|
case 'CREATED':
|
||||||
case 'APPROVED':
|
case 'APPROVED':
|
||||||
approvalStatus = 'APPROVED';
|
approvalStatus = 'APPROVED';
|
||||||
@@ -154,19 +164,20 @@ export const formatGroupedApprovalsToApprovalSteps = (
|
|||||||
approvalStatus = 'IDLE';
|
approvalStatus = 'IDLE';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else if (approvalGroup.step_number === latestApproval.step_number + 1) {
|
} else if (approvalGroup.step_number === latestApproval.step_number + 1) {
|
||||||
approvalStatus = 'WAITING';
|
approvalStatus = 'WAITING';
|
||||||
} else {
|
} else {
|
||||||
approvalStatus = 'IDLE';
|
approvalStatus = 'IDLE';
|
||||||
}
|
}
|
||||||
|
|
||||||
const approvalLogs: ApprovalStepLog[] = approvalGroup.approvals.map(
|
const approvalLogs: ApprovalStepLog[] = approvalGroup.approvals
|
||||||
(approval) => ({
|
? approvalGroup.approvals.map((approval) => ({
|
||||||
action_by: approval.action_by.name,
|
action_by: approval.action_by.name,
|
||||||
date: approval.action_at,
|
date: approval.action_at,
|
||||||
notes: approval.notes,
|
notes: approval.notes,
|
||||||
})
|
}))
|
||||||
);
|
: [];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: approvalGroup.step_name,
|
name: approvalGroup.step_name,
|
||||||
@@ -179,3 +190,113 @@ export const formatGroupedApprovalsToApprovalSteps = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default ApprovalSteps;
|
export default ApprovalSteps;
|
||||||
|
|
||||||
|
const useApprovalSteps = <T extends ModuleWithApproval>({
|
||||||
|
moduleUrl,
|
||||||
|
moduleName,
|
||||||
|
moduleId,
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
moduleUrl: string;
|
||||||
|
moduleName: string;
|
||||||
|
moduleId: string;
|
||||||
|
params?: {
|
||||||
|
page: number;
|
||||||
|
limit: number;
|
||||||
|
search?: string;
|
||||||
|
};
|
||||||
|
}) => {
|
||||||
|
const paramString = new URLSearchParams({
|
||||||
|
page: params?.page?.toString() || '',
|
||||||
|
limit: params?.limit?.toString() || '',
|
||||||
|
search: params?.search || '',
|
||||||
|
}).toString();
|
||||||
|
|
||||||
|
const SWR_KEY_CONSTANTS = '/constants';
|
||||||
|
const SWR_KEY_APPROVALS =
|
||||||
|
moduleName && moduleId
|
||||||
|
? `/approvals?module_name=${moduleName}&module_id=${moduleId}&group_step_number=true${
|
||||||
|
params ? `&${paramString}` : ''
|
||||||
|
}`
|
||||||
|
: null;
|
||||||
|
const SWR_KEY_CURRENT_DATA = moduleUrl;
|
||||||
|
|
||||||
|
// Get Approval Lines dari GET /constant
|
||||||
|
const { data: constData, isLoading: constIsLoading } = useSWR(
|
||||||
|
SWR_KEY_CONSTANTS,
|
||||||
|
async (url) => {
|
||||||
|
return await httpClientFetcher<AppConfigData>(url);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get Grouped Data dari GET /approvals
|
||||||
|
const {
|
||||||
|
data: approvalData,
|
||||||
|
isLoading: approvalIsLoading,
|
||||||
|
mutate: mutateApprovals,
|
||||||
|
} = useSWR(SWR_KEY_APPROVALS, async (url) => {
|
||||||
|
return await httpClientFetcher<BaseApiResponse<BaseGroupedApproval[]>>(url);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get latest approval
|
||||||
|
const {
|
||||||
|
data: currentData,
|
||||||
|
isLoading: currentIsLoading,
|
||||||
|
mutate: mutateCurrentData,
|
||||||
|
} = useSWR(SWR_KEY_CURRENT_DATA, async (url) => {
|
||||||
|
return await httpClientFetcher<BaseApiResponse<T>>(url);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fungsi Refresh
|
||||||
|
const refresh = useCallback(async () => {
|
||||||
|
await Promise.all([mutateApprovals(), mutateCurrentData()]);
|
||||||
|
}, [mutateApprovals, mutateCurrentData]);
|
||||||
|
|
||||||
|
const { approvalLine, groupedApprovals, latestApproval } = useMemo(() => {
|
||||||
|
const line = constData
|
||||||
|
? (constData.approval_workflows.find((approval) => {
|
||||||
|
return approval.key === moduleName;
|
||||||
|
})?.steps ?? [])
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const grouped = isResponseSuccess(approvalData) ? approvalData.data : [];
|
||||||
|
|
||||||
|
const latest = isResponseSuccess(currentData)
|
||||||
|
? currentData.data?.approval
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
approvalLine: line,
|
||||||
|
groupedApprovals: grouped,
|
||||||
|
latestApproval: latest,
|
||||||
|
};
|
||||||
|
}, [constData, approvalData, currentData, moduleName]);
|
||||||
|
|
||||||
|
const isLoading = constIsLoading || approvalIsLoading || currentIsLoading;
|
||||||
|
|
||||||
|
const approvals = useMemo(() => {
|
||||||
|
if (isLoading || !approvalLine.length || !latestApproval) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return formatGroupedApprovalsToApprovalSteps(
|
||||||
|
approvalLine,
|
||||||
|
groupedApprovals,
|
||||||
|
latestApproval as BaseApproval
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Gagal memformat approval steps:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}, [isLoading, approvalLine, groupedApprovals, latestApproval]);
|
||||||
|
|
||||||
|
// Return Hook
|
||||||
|
return {
|
||||||
|
approvals,
|
||||||
|
isLoading,
|
||||||
|
rawData: isResponseSuccess(currentData) ? currentData.data : undefined,
|
||||||
|
refresh,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export { useApprovalSteps };
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import Tabs from '@/components/Tabs';
|
|||||||
import ChickinFormView from './tabs/ChickinFormView';
|
import ChickinFormView from './tabs/ChickinFormView';
|
||||||
import ChickinLogsView from './tabs/ChickLogsView';
|
import ChickinLogsView from './tabs/ChickLogsView';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import ApprovalSteps, {
|
||||||
|
useApprovalSteps,
|
||||||
|
} from '@/components/pages/ApprovalSteps';
|
||||||
const ChickinFormKandang = ({
|
const ChickinFormKandang = ({
|
||||||
formType = 'add',
|
formType = 'add',
|
||||||
initialValues,
|
initialValues,
|
||||||
@@ -21,9 +24,20 @@ const ChickinFormKandang = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const [activeTabId, setActiveTabId] = useState<string>('formChickIn');
|
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 = () => {
|
const afterSubmitFormChickin = () => {
|
||||||
setActiveTabId('logsChickIn');
|
setActiveTabId('logsChickIn');
|
||||||
afterSubmit && afterSubmit();
|
afterSubmit && afterSubmit();
|
||||||
|
refreshApprovals();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -32,6 +46,11 @@ const ChickinFormKandang = ({
|
|||||||
title='Chick In DOC'
|
title='Chick In DOC'
|
||||||
backUrl={`/production/project-flock/chickin/add?projectFlockId=${initialValues?.project_flock?.id}`}
|
backUrl={`/production/project-flock/chickin/add?projectFlockId=${initialValues?.project_flock?.id}`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{approvals && !approvalsLoading && (
|
||||||
|
<ApprovalSteps approvals={approvals} />
|
||||||
|
)}
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
title='Informasi Kandang'
|
title='Informasi Kandang'
|
||||||
className={{
|
className={{
|
||||||
@@ -108,7 +127,7 @@ const ChickinFormKandang = ({
|
|||||||
content: (
|
content: (
|
||||||
<ChickinLogsView
|
<ChickinLogsView
|
||||||
initialValues={initialValues}
|
initialValues={initialValues}
|
||||||
afterSubmit={afterSubmit}
|
afterSubmit={afterSubmitFormChickin}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
id: 'logsChickIn',
|
id: 'logsChickIn',
|
||||||
|
|||||||
@@ -47,9 +47,7 @@ const ChickinLogsView = ({
|
|||||||
}
|
}
|
||||||
confirmModal.closeModal();
|
confirmModal.closeModal();
|
||||||
setIsApproveLoading(false);
|
setIsApproveLoading(false);
|
||||||
if (afterSubmit) {
|
afterSubmit && afterSubmit();
|
||||||
afterSubmit();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
+13
-2
@@ -58,7 +58,15 @@ const ProjectFlockChickinDetail = ({
|
|||||||
options: options,
|
options: options,
|
||||||
isLoadingOptions: isLoadingListProjectFlock,
|
isLoadingOptions: isLoadingListProjectFlock,
|
||||||
rawData: listProjectFlock,
|
rawData: listProjectFlock,
|
||||||
} = useSelect<ProjectFlock>(ProjectFlockApi.basePath, 'id', 'flock_name');
|
} = useSelect<ProjectFlock>(
|
||||||
|
ProjectFlockApi.basePath,
|
||||||
|
'id',
|
||||||
|
'flock_name',
|
||||||
|
'',
|
||||||
|
{
|
||||||
|
search: searchProjectFlock,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// Handle Function
|
// Handle Function
|
||||||
const handleChickinClick = async (
|
const handleChickinClick = async (
|
||||||
@@ -242,7 +250,7 @@ const ProjectFlockChickinDetail = ({
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
data={
|
data={
|
||||||
isResponseSuccess(listProjectFlockKandang)
|
projectFlock && isResponseSuccess(listProjectFlockKandang)
|
||||||
? listProjectFlockKandang.data
|
? listProjectFlockKandang.data
|
||||||
: []
|
: []
|
||||||
}
|
}
|
||||||
@@ -293,6 +301,8 @@ const ProjectFlockChickinDetail = ({
|
|||||||
.replace(/_/g, ' ')
|
.replace(/_/g, ' ')
|
||||||
.replace(/\b\w/g, (char) => char.toUpperCase())}
|
.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'} />
|
<PillBadge color='gray' content={'Belum Chick In'} />
|
||||||
);
|
);
|
||||||
@@ -310,6 +320,7 @@ const ProjectFlockChickinDetail = ({
|
|||||||
handleChickinClick(props.row.original);
|
handleChickinClick(props.row.original);
|
||||||
}}
|
}}
|
||||||
className='p-1'
|
className='p-1'
|
||||||
|
disabled={projectFlock?.approval?.step_number === 1}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
icon='mdi:home-import-outline'
|
icon='mdi:home-import-outline'
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { use, useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import useSWR, { KeyedMutator } from 'swr';
|
import useSWR, { KeyedMutator } from 'swr';
|
||||||
import {
|
import {
|
||||||
ProjectFlockFormSchema,
|
ProjectFlockFormSchema,
|
||||||
@@ -26,7 +26,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
ProjectFlockApprovalPayload,
|
ProjectFlockApprovalPayload,
|
||||||
CreateProjectFlockPayload,
|
CreateProjectFlockPayload,
|
||||||
PeriodFlock,
|
|
||||||
ProjectFlock,
|
ProjectFlock,
|
||||||
} from '@/types/api/production/project-flock';
|
} from '@/types/api/production/project-flock';
|
||||||
import toast from 'react-hot-toast';
|
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 { Kandang } from '@/types/api/master-data/kandang';
|
||||||
import Collapse from '@/components/Collapse';
|
import Collapse from '@/components/Collapse';
|
||||||
import { ProjectFlockApi } from '@/services/api/production/project-flock';
|
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 { APPROVAL_WORKFLOWS, FLOCK_CATEGORY_OPTIONS } from '@/config/constant';
|
||||||
import { useModal } from '@/components/Modal';
|
import { useModal } from '@/components/Modal';
|
||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
import ProjectFlockKandangTable from './ProjectFlockKandangTable';
|
import ProjectFlockKandangTable from './ProjectFlockKandangTable';
|
||||||
import ApprovalSteps from '@/components/pages/ApprovalSteps';
|
import ApprovalSteps, {
|
||||||
import Steps from '@/components/steps/Steps';
|
useApprovalSteps,
|
||||||
import StepItem from '@/components/steps/StepItem';
|
} from '@/components/pages/ApprovalSteps';
|
||||||
import Tooltip from '@/components/Tooltip';
|
|
||||||
import { id, is } from 'react-day-picker/locale';
|
|
||||||
import { formatDate } from '@/lib/helper';
|
|
||||||
import Card from '@/components/Card';
|
import Card from '@/components/Card';
|
||||||
|
|
||||||
interface ProjectFlockFormProps {
|
interface ProjectFlockFormProps {
|
||||||
formType?: 'add' | 'edit' | 'detail';
|
formType?: 'add' | 'edit' | 'detail';
|
||||||
initialValues?: ProjectFlock;
|
initialValues?: ProjectFlock;
|
||||||
initialApprovals?: BaseGroupedApproval[];
|
|
||||||
refreshProjectFlocks?: KeyedMutator<
|
refreshProjectFlocks?: KeyedMutator<
|
||||||
BaseApiResponse<ProjectFlock> | undefined
|
BaseApiResponse<ProjectFlock> | undefined
|
||||||
>;
|
>;
|
||||||
@@ -59,16 +54,11 @@ interface ProjectFlockFormProps {
|
|||||||
const ProjectFlockForm = ({
|
const ProjectFlockForm = ({
|
||||||
formType = 'add',
|
formType = 'add',
|
||||||
initialValues,
|
initialValues,
|
||||||
initialApprovals,
|
|
||||||
refreshProjectFlocks,
|
refreshProjectFlocks,
|
||||||
}: ProjectFlockFormProps) => {
|
}: ProjectFlockFormProps) => {
|
||||||
// State
|
// State
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const projectFlockSteps = APPROVAL_WORKFLOWS.find(
|
|
||||||
(step) => step.key === 'PROJECT_FLOCKS'
|
|
||||||
);
|
|
||||||
|
|
||||||
const [projectFlockFormErrorMessage, setProjectFlockFormErrorMessage] =
|
const [projectFlockFormErrorMessage, setProjectFlockFormErrorMessage] =
|
||||||
useState('');
|
useState('');
|
||||||
const [selectedArea, setSelectedArea] = useState('');
|
const [selectedArea, setSelectedArea] = useState('');
|
||||||
@@ -120,34 +110,28 @@ const ProjectFlockForm = ({
|
|||||||
}, [initialValues]);
|
}, [initialValues]);
|
||||||
|
|
||||||
// Fetch Data
|
// Fetch Data
|
||||||
const {
|
const { isLoadingOptions: isLoadingFlocks, options: optionsFlock } =
|
||||||
rawData: flocks,
|
useSelect(FlockApi.basePath, 'id', 'name');
|
||||||
isLoadingOptions: isLoadingFlocks,
|
|
||||||
options: optionsFlock,
|
|
||||||
} = useSelect(FlockApi.basePath, 'id', 'name');
|
|
||||||
|
|
||||||
const {
|
const { options: optionsArea, isLoadingOptions: isLoadingAreas } = useSelect(
|
||||||
options: optionsArea,
|
AreaApi.basePath,
|
||||||
isLoadingOptions: isLoadingAreas,
|
'id',
|
||||||
rawData: areas,
|
'name'
|
||||||
} = useSelect(AreaApi.basePath, 'id', 'name');
|
);
|
||||||
|
|
||||||
const {
|
const { options: optionsLocation, isLoadingOptions: isLoadingLocations } =
|
||||||
options: optionsLocation,
|
useSelect(LocationApi.basePath, 'id', 'name', '', {
|
||||||
isLoadingOptions: isLoadingLocations,
|
|
||||||
rawData: locations,
|
|
||||||
} = useSelect(LocationApi.basePath, 'id', 'name', '', {
|
|
||||||
area_id:
|
area_id:
|
||||||
selectedArea != ''
|
selectedArea != ''
|
||||||
? selectedArea
|
? selectedArea
|
||||||
: ((initialValues?.area?.id ?? '') as string),
|
: ((initialValues?.area?.id ?? '') as string),
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const { options: optionsFcr, isLoadingOptions: isLoadingFcrs } = useSelect(
|
||||||
options: optionsFcr,
|
FcrApi.basePath,
|
||||||
isLoadingOptions: isLoadingFcrs,
|
'id',
|
||||||
rawData: fcrs,
|
'name'
|
||||||
} = useSelect(FcrApi.basePath, 'id', 'name');
|
);
|
||||||
|
|
||||||
const kandangUrl = `${KandangApi.basePath}?${new URLSearchParams({
|
const kandangUrl = `${KandangApi.basePath}?${new URLSearchParams({
|
||||||
search: '',
|
search: '',
|
||||||
@@ -164,6 +148,16 @@ const ProjectFlockForm = ({
|
|||||||
(id: string) => ProjectFlockApi.getNextPeriod(id)
|
(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(() => {
|
useEffect(() => {
|
||||||
if (isResponseSuccess(kandang)) {
|
if (isResponseSuccess(kandang)) {
|
||||||
if (selectedLocation) {
|
if (selectedLocation) {
|
||||||
@@ -516,6 +510,7 @@ const ProjectFlockForm = ({
|
|||||||
if (isResponseError(approveProjectFlockRes)) {
|
if (isResponseError(approveProjectFlockRes)) {
|
||||||
toast.error(approveProjectFlockRes?.message as string);
|
toast.error(approveProjectFlockRes?.message as string);
|
||||||
}
|
}
|
||||||
|
refreshApprovals();
|
||||||
confirmModal.closeModal();
|
confirmModal.closeModal();
|
||||||
setIsApproveLoading(false);
|
setIsApproveLoading(false);
|
||||||
};
|
};
|
||||||
@@ -558,79 +553,8 @@ const ProjectFlockForm = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{formType == 'detail' && initialApprovals && (
|
{approvals && !approvalsLoading && (
|
||||||
<div className='w-full flex items-center gap-2 my-4'>
|
<ApprovalSteps approvals={approvals} />
|
||||||
<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>
|
|
||||||
)}
|
)}
|
||||||
{formType == 'detail' && (
|
{formType == 'detail' && (
|
||||||
<div className='w-full flex flex-col sm:flex-row gap-2 py-4'>
|
<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
|
Chickin
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</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
|
<form
|
||||||
className='w-auto h-auto'
|
className='w-auto h-auto'
|
||||||
onSubmit={formik.handleSubmit}
|
onSubmit={formik.handleSubmit}
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ const ProjectFlockKandangTable = ({
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{JSON.stringify(initialKandangIdSet)}
|
|
||||||
<Table<Kandang>
|
<Table<Kandang>
|
||||||
data={listKandang}
|
data={listKandang}
|
||||||
columns={[
|
columns={[
|
||||||
|
|||||||
@@ -246,6 +246,19 @@ export const APPROVAL_WORKFLOWS = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'PROJECT_FLOCK_KANDANGS',
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
step_number: 1,
|
||||||
|
step_name: 'Pengajuan',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step_number: 2,
|
||||||
|
step_name: 'Disetujui',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'RECORDINGS',
|
key: 'RECORDINGS',
|
||||||
steps: [
|
steps: [
|
||||||
|
|||||||
@@ -39,11 +39,6 @@ export class ProjectFlockService extends BaseApiService<
|
|||||||
> {
|
> {
|
||||||
const path = `/approvals`;
|
const path = `/approvals`;
|
||||||
try {
|
try {
|
||||||
console.log({
|
|
||||||
module_id: id,
|
|
||||||
module_name: 'PROJECT_FLOCKS',
|
|
||||||
group_step_number: true,
|
|
||||||
});
|
|
||||||
return await httpClient<SuccessApiResponse<BaseGroupedApproval[]>>(path, {
|
return await httpClient<SuccessApiResponse<BaseGroupedApproval[]>>(path, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
query: {
|
query: {
|
||||||
|
|||||||
Vendored
+4
@@ -112,6 +112,10 @@ export type BaseGroupedApproval = {
|
|||||||
approvals: BaseApproval[];
|
approvals: BaseApproval[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface ModuleWithApproval {
|
||||||
|
approval?: BaseApproval;
|
||||||
|
}
|
||||||
|
|
||||||
export type Approvals = BaseApiResponse<BaseApproval>;
|
export type Approvals = BaseApiResponse<BaseApproval>;
|
||||||
|
|
||||||
export type GroupedApprovals = BaseApiResponse<BaseGroupedApproval[]>;
|
export type GroupedApprovals = BaseApiResponse<BaseGroupedApproval[]>;
|
||||||
|
|||||||
Vendored
+14
@@ -2,3 +2,17 @@ export type ApprovalLine = {
|
|||||||
step_number: number;
|
step_number: number;
|
||||||
step_name: string;
|
step_name: string;
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
|
export interface ApprovalWorkflow {
|
||||||
|
key: string;
|
||||||
|
steps: ApprovalLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AppConfigData {
|
||||||
|
approval_workflows: ApprovalWorkflow[];
|
||||||
|
flags: string[];
|
||||||
|
warehouse_types: string[];
|
||||||
|
stock_log: string;
|
||||||
|
supplier_categories: string[];
|
||||||
|
customer_supplier_types: string[];
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user