refactor(FE): Refactor project flock api & implement approval component in project flock details

This commit is contained in:
randy-ar
2025-11-10 17:05:24 +07:00
parent 9f4f140018
commit f63d3d3870
3 changed files with 313 additions and 18 deletions
@@ -33,12 +33,18 @@ import toast from 'react-hot-toast';
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';
import { ProjectFlockApi } from '@/services/api/production/project-flock';
import { BaseApiResponse } from '@/types/api/api-general';
import { FLOCK_CATEGORY_OPTIONS } from '@/config/constant';
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';
interface ProjectFlockFormProps {
formType?: 'add' | 'edit' | 'detail';
@@ -55,20 +61,22 @@ const ProjectFlockForm = ({
}: ProjectFlockFormProps) => {
// State
const router = useRouter();
const projectFlockSteps = APPROVAL_WORKFLOWS.find(
(step) => step.key === 'PROJECT_FLOCKS'
);
const [projectFlockFormErrorMessage, setProjectFlockFormErrorMessage] =
useState('');
const [selectedArea, setSelectedArea] = useState('');
const [selectedLocation, setSelectedLocation] = useState('');
const [disabledLocation, setDisabledLocation] = useState(true);
const [openSelectKandangs, setOpenSelectKandangs] = useState(
initialValues?.kandangs && initialValues?.kandangs?.length > 0
);
const [optionsKandang, setOptionsKandang] = useState<Kandang[]>(
initialValues?.kandangs ?? []
);
const [selectedFlock, setSelectedFlock] = useState<number>(
initialValues?.flock?.id ?? 0
);
@@ -141,15 +149,14 @@ const ProjectFlockForm = ({
mutate: refreshKandang,
} = useSWR(kandangUrl, KandangApi.getAllFetcher);
const getPeriodFlocksUrl = `flocks/${selectedFlock}/periods`;
const { data: periodFlocks, isLoading: isLoadingPeriodFlocks } = useSWR(
getPeriodFlocksUrl,
() =>
ProjectFlockApi.customRequest<BaseApiResponse<PeriodFlock>, 'GET'>(
getPeriodFlocksUrl,
{ method: 'GET' }
)
`${selectedFlock.toString()}/periods`,
(id: string) => ProjectFlockApi.getNextPeriod(id)
);
const { data: approvalLines, isLoading: isLoadingApprovalLines } = useSWR(
selectedFlock.toString(),
(id: number) => ProjectFlockApi.getApprovalLines(id)
);
useEffect(() => {
@@ -387,7 +394,10 @@ const ProjectFlockForm = ({
if (isResponseSuccess(periodFlocks)) {
formik.setFieldValue('period', periodFlocks.data.next_period);
}
}, [periodFlocks]);
if (isResponseError(periodFlocks)) {
console.log(periodFlocks?.message as string);
}
}, [periodFlocks, toast]);
useEffect(() => {
const selectedRowIds = Object.keys(rowSelection)
@@ -485,6 +495,80 @@ const ProjectFlockForm = ({
</div>
</div>
)}
{formType == 'detail' && isResponseSuccess(approvalLines) && (
<div className='w-full flex items-center gap-2 my-4'>
<Steps className='w-full'>
{projectFlockSteps?.steps.map((step, idx) => {
const approvalLogs = approvalLines.data.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' && (
<div className='w-full flex flex-col sm:flex-row gap-2 py-4'>
<Button
+33
View File
@@ -231,3 +231,36 @@ export const RECORDING_FLAG_OPTIONS = [
{ label: 'Ayam Culling', value: 'Ayam Culling' },
{ label: 'Ayam Mati', value: 'Ayam Mati' },
];
export const APPROVAL_WORKFLOWS = [
{
key: 'PROJECT_FLOCKS',
steps: [
{
step_number: 1,
step_name: 'Pengajuan',
},
{
step_number: 2,
step_name: 'Aktif',
},
],
},
{
key: 'RECORDINGS',
steps: [
{
step_number: 1,
step_name: 'Grading-Telur',
},
{
step_number: 2,
step_name: 'Pengajuan',
},
{
step_number: 3,
step_name: 'Disetujui',
},
],
},
];
+183 -5
View File
@@ -4,10 +4,19 @@ import {
UpdateProjectFlockPayload,
} from '@/types/api/production/project-flock';
import { BaseApiService } from '../base';
import { BaseApiResponse } from '@/types/api/api-general';
import {
BaseApiResponse,
BaseGroupedApproval,
ErrorApiResponse,
GroupedApprovals,
SuccessApiResponse,
} from '@/types/api/api-general';
import { sleep } from '@/lib/helper';
import { httpClient } from '@/services/http/client';
import axios from 'axios';
import { Flock } from '@/types/api/master-data/flock';
import { Kandang } from '@/types/api/master-data/kandang';
import { RequestOptions } from '@/services/http/base';
export class ProjectFlockService extends BaseApiService<
ProjectFlock,
@@ -17,14 +26,183 @@ export class ProjectFlockService extends BaseApiService<
constructor(basePath: string = '') {
super(basePath);
}
async getSingleProjectFlockKandang(): Promise<BaseApiResponse | undefined> {
/**
* Get Approval Lines
*/
async getApprovalLines(
id: number
): Promise<
| BaseApiResponse<BaseGroupedApproval[]>
| ErrorApiResponse
| SuccessApiResponse<BaseGroupedApproval[]>
| undefined
> {
const path = `/approvals`;
try {
console.log({
module_id: id,
module_name: 'PROJECT_FLOCKS',
group_step_number: true,
});
return await httpClient<SuccessApiResponse<BaseGroupedApproval[]>>(path, {
method: 'GET',
query: {
module_id: id,
module_name: 'PROJECT_FLOCKS',
group_step_number: true,
},
} as RequestOptions);
} catch (error) {
if (axios.isAxiosError<BaseApiResponse>(error)) {
return error.response?.data;
}
return error.response?.data as ErrorApiResponse;
} else {
return undefined;
}
}
}
/**
* Lookup for Project Flock Kandang
*/
async lookupProjectFlockKandang(
projectFlockId: number,
kandangId: number
): Promise<
| BaseApiResponse<
| ErrorApiResponse
| SuccessApiResponse<{
id: number;
kandang_id: Kandang;
project_flock: ProjectFlock;
available_quantity: number;
}>
>
| undefined
> {
try {
const path = `${this.basePath}/kandangs/lookup`;
return await httpClient<
BaseApiResponse<
SuccessApiResponse<{
id: number;
kandang_id: Kandang;
project_flock: ProjectFlock;
available_quantity: number;
}>
>
>(path, {
method: 'GET',
body: {
project_flock_id: projectFlockId,
kandang_id: kandangId,
},
});
} catch (error) {
if (axios.isAxiosError<BaseApiResponse<ErrorApiResponse>>(error)) {
return error.response?.data;
} else {
return undefined;
}
}
}
/**
* Get Next Period of Project Flock
*/
async getNextPeriod(id: string): Promise<
| BaseApiResponse<{
flock: Flock;
next_period: number;
}>
| ErrorApiResponse
| SuccessApiResponse<{
flock: Flock;
next_period: number;
}>
| undefined
> {
try {
const path = `${this.basePath}/kandangs/${id}`;
return await httpClient<
SuccessApiResponse<{
flock: Flock;
next_period: number;
}>
>(path, {
method: 'GET',
});
} catch (error) {
if (axios.isAxiosError<BaseApiResponse<ErrorApiResponse>>(error)) {
return error.response?.data as ErrorApiResponse;
} else {
return undefined;
}
}
}
/**
* Approve single Project Flock
*/
async approve(
id: number
): Promise<BaseApiResponse<{ message: string }> | undefined> {
return await this.bulkApprovalAction([id], 'APPROVED');
}
/**
* Reject single Project Flock
*/
async reject(
id: number
): Promise<BaseApiResponse<{ message: string }> | undefined> {
return await this.bulkApprovalAction([id], 'REJECTED');
}
/**
* Approve Bulk Project Flock
*/
async bulkApprove(
ids: number[]
): Promise<BaseApiResponse<{ message: string }> | undefined> {
return await this.bulkApprovalAction(ids, 'APPROVED');
}
/**
* Reject Bulk Project Flock
*/
async bulkReject(
ids: number[]
): Promise<BaseApiResponse<{ message: string }> | undefined> {
return await this.bulkApprovalAction(ids, 'REJECTED');
}
/**
* Approve Bulk Project Flock
*/
async bulkApprovalAction(
ids: number[],
action: 'APPROVED' | 'REJECTED'
): Promise<BaseApiResponse<{ message: string }> | undefined> {
try {
const path = `${this.basePath}/approvals`;
return await httpClient<BaseApiResponse<{ message: string }>>(path, {
method: 'POST',
body: {
action: action,
approvable_ids: ids,
notes: `Bulk ${action} Project Flock ${ids.join(', ')}`,
},
});
} catch (error) {
if (axios.isAxiosError<BaseApiResponse<{ message: string }>>(error)) {
return error.response?.data;
} else {
return undefined;
}
}
}
}
export const ProjectFlockApi = new ProjectFlockService(
'/production/project-flocks'
);