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
@@ -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}
/> />
)} )}
+2 -11
View File
@@ -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}
> >
+130 -9
View File
@@ -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 (
@@ -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={[
+13
View File
@@ -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: {
+4
View File
@@ -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[]>;
+14
View File
@@ -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[];
}