mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
refactor(FE): Refactor project flock api & implement approval component in project flock details
This commit is contained in:
@@ -33,12 +33,18 @@ import toast from 'react-hot-toast';
|
|||||||
import TextInput from '@/components/input/TextInput';
|
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';
|
import { ProjectFlockApi } from '@/services/api/production/project-flock';
|
||||||
import { BaseApiResponse } from '@/types/api/api-general';
|
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 { 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 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 {
|
interface ProjectFlockFormProps {
|
||||||
formType?: 'add' | 'edit' | 'detail';
|
formType?: 'add' | 'edit' | 'detail';
|
||||||
@@ -55,20 +61,22 @@ const ProjectFlockForm = ({
|
|||||||
}: 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('');
|
||||||
|
|
||||||
const [selectedLocation, setSelectedLocation] = useState('');
|
const [selectedLocation, setSelectedLocation] = useState('');
|
||||||
const [disabledLocation, setDisabledLocation] = useState(true);
|
const [disabledLocation, setDisabledLocation] = useState(true);
|
||||||
|
|
||||||
const [openSelectKandangs, setOpenSelectKandangs] = useState(
|
const [openSelectKandangs, setOpenSelectKandangs] = useState(
|
||||||
initialValues?.kandangs && initialValues?.kandangs?.length > 0
|
initialValues?.kandangs && initialValues?.kandangs?.length > 0
|
||||||
);
|
);
|
||||||
const [optionsKandang, setOptionsKandang] = useState<Kandang[]>(
|
const [optionsKandang, setOptionsKandang] = useState<Kandang[]>(
|
||||||
initialValues?.kandangs ?? []
|
initialValues?.kandangs ?? []
|
||||||
);
|
);
|
||||||
|
|
||||||
const [selectedFlock, setSelectedFlock] = useState<number>(
|
const [selectedFlock, setSelectedFlock] = useState<number>(
|
||||||
initialValues?.flock?.id ?? 0
|
initialValues?.flock?.id ?? 0
|
||||||
);
|
);
|
||||||
@@ -141,15 +149,14 @@ const ProjectFlockForm = ({
|
|||||||
mutate: refreshKandang,
|
mutate: refreshKandang,
|
||||||
} = useSWR(kandangUrl, KandangApi.getAllFetcher);
|
} = useSWR(kandangUrl, KandangApi.getAllFetcher);
|
||||||
|
|
||||||
const getPeriodFlocksUrl = `flocks/${selectedFlock}/periods`;
|
|
||||||
|
|
||||||
const { data: periodFlocks, isLoading: isLoadingPeriodFlocks } = useSWR(
|
const { data: periodFlocks, isLoading: isLoadingPeriodFlocks } = useSWR(
|
||||||
getPeriodFlocksUrl,
|
`${selectedFlock.toString()}/periods`,
|
||||||
() =>
|
(id: string) => ProjectFlockApi.getNextPeriod(id)
|
||||||
ProjectFlockApi.customRequest<BaseApiResponse<PeriodFlock>, 'GET'>(
|
);
|
||||||
getPeriodFlocksUrl,
|
|
||||||
{ method: 'GET' }
|
const { data: approvalLines, isLoading: isLoadingApprovalLines } = useSWR(
|
||||||
)
|
selectedFlock.toString(),
|
||||||
|
(id: number) => ProjectFlockApi.getApprovalLines(id)
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -387,7 +394,10 @@ const ProjectFlockForm = ({
|
|||||||
if (isResponseSuccess(periodFlocks)) {
|
if (isResponseSuccess(periodFlocks)) {
|
||||||
formik.setFieldValue('period', periodFlocks.data.next_period);
|
formik.setFieldValue('period', periodFlocks.data.next_period);
|
||||||
}
|
}
|
||||||
}, [periodFlocks]);
|
if (isResponseError(periodFlocks)) {
|
||||||
|
console.log(periodFlocks?.message as string);
|
||||||
|
}
|
||||||
|
}, [periodFlocks, toast]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const selectedRowIds = Object.keys(rowSelection)
|
const selectedRowIds = Object.keys(rowSelection)
|
||||||
@@ -485,6 +495,80 @@ const ProjectFlockForm = ({
|
|||||||
</div>
|
</div>
|
||||||
</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' && (
|
{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'>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -231,3 +231,36 @@ export const RECORDING_FLAG_OPTIONS = [
|
|||||||
{ label: 'Ayam Culling', value: 'Ayam Culling' },
|
{ label: 'Ayam Culling', value: 'Ayam Culling' },
|
||||||
{ label: 'Ayam Mati', value: 'Ayam Mati' },
|
{ 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',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|||||||
@@ -4,10 +4,19 @@ import {
|
|||||||
UpdateProjectFlockPayload,
|
UpdateProjectFlockPayload,
|
||||||
} from '@/types/api/production/project-flock';
|
} from '@/types/api/production/project-flock';
|
||||||
import { BaseApiService } from '../base';
|
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 { sleep } from '@/lib/helper';
|
||||||
import { httpClient } from '@/services/http/client';
|
import { httpClient } from '@/services/http/client';
|
||||||
import axios from 'axios';
|
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<
|
export class ProjectFlockService extends BaseApiService<
|
||||||
ProjectFlock,
|
ProjectFlock,
|
||||||
@@ -17,14 +26,183 @@ export class ProjectFlockService extends BaseApiService<
|
|||||||
constructor(basePath: string = '') {
|
constructor(basePath: string = '') {
|
||||||
super(basePath);
|
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 {
|
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) {
|
} catch (error) {
|
||||||
if (axios.isAxiosError<BaseApiResponse>(error)) {
|
if (axios.isAxiosError<BaseApiResponse>(error)) {
|
||||||
return error.response?.data;
|
return error.response?.data as ErrorApiResponse;
|
||||||
}
|
} else {
|
||||||
|
|
||||||
return undefined;
|
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'
|
||||||
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user