mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
refactor(FE): Add Unit VP approval and rename Manager
This commit is contained in:
@@ -59,34 +59,40 @@ const ExpenseRequestContent = ({
|
||||
|
||||
const isLatestApprovalRejectedOrDone =
|
||||
isLatestApprovalRejected ||
|
||||
initialValues?.latest_approval.step_number === 5;
|
||||
initialValues?.latest_approval.step_number === 6;
|
||||
|
||||
const isCurrentApprovalOnManager =
|
||||
const isCurrentApprovalOnHeadArea =
|
||||
!isLatestApprovalRejected &&
|
||||
initialValues?.latest_approval.step_number === 1;
|
||||
|
||||
const isCurrentApprovalOnFinance =
|
||||
const isCurrentApprovalOnUnitVicePresident =
|
||||
!isLatestApprovalRejected &&
|
||||
initialValues?.latest_approval.step_number === 2;
|
||||
|
||||
const isCurrentApprovalOnFinance =
|
||||
!isLatestApprovalRejected &&
|
||||
initialValues?.latest_approval.step_number === 3;
|
||||
|
||||
const isCurrentApprovalOnRealization =
|
||||
!isLatestApprovalRejected &&
|
||||
initialValues?.latest_approval.step_number === 4;
|
||||
initialValues?.latest_approval.step_number === 5;
|
||||
|
||||
const showEditButton =
|
||||
initialValues?.latest_approval.step_number !== 5 &&
|
||||
initialValues?.latest_approval.step_number !== 6 &&
|
||||
(initialValues?.latest_approval.step_number === 1 ||
|
||||
initialValues?.latest_approval.step_number === 2 ||
|
||||
initialValues?.latest_approval.step_number === 3);
|
||||
initialValues?.latest_approval.step_number === 3 ||
|
||||
initialValues?.latest_approval.step_number === 4);
|
||||
|
||||
const showRejectButton =
|
||||
!isLatestApprovalRejected &&
|
||||
(initialValues?.latest_approval.step_number === 1 ||
|
||||
initialValues?.latest_approval.step_number === 2);
|
||||
initialValues?.latest_approval.step_number === 2 ||
|
||||
initialValues?.latest_approval.step_number === 3);
|
||||
|
||||
const isExpenseCanBeRealized =
|
||||
!isLatestApprovalRejected &&
|
||||
initialValues?.latest_approval.step_number === 3;
|
||||
initialValues?.latest_approval.step_number === 4;
|
||||
|
||||
// Modal hooks
|
||||
const deleteModal = useModal();
|
||||
@@ -174,8 +180,15 @@ const ExpenseRequestContent = ({
|
||||
|
||||
let approveResponse: BaseApiResponse<Expense> | undefined = undefined;
|
||||
|
||||
if (isCurrentApprovalOnManager) {
|
||||
approveResponse = await ExpenseApi.approveManager(
|
||||
if (isCurrentApprovalOnHeadArea) {
|
||||
approveResponse = await ExpenseApi.approveHeadArea(
|
||||
initialValues.id,
|
||||
notes
|
||||
);
|
||||
}
|
||||
|
||||
if (isCurrentApprovalOnUnitVicePresident) {
|
||||
approveResponse = await ExpenseApi.approveUnitVicePresident(
|
||||
initialValues.id,
|
||||
notes
|
||||
);
|
||||
@@ -207,8 +220,15 @@ const ExpenseRequestContent = ({
|
||||
|
||||
let rejectResponse: BaseApiResponse<Expense> | undefined = undefined;
|
||||
|
||||
if (isCurrentApprovalOnManager) {
|
||||
rejectResponse = await ExpenseApi.rejectManager(initialValues.id, notes);
|
||||
if (isCurrentApprovalOnHeadArea) {
|
||||
rejectResponse = await ExpenseApi.rejectHeadArea(initialValues.id, notes);
|
||||
}
|
||||
|
||||
if (isCurrentApprovalOnUnitVicePresident) {
|
||||
rejectResponse = await ExpenseApi.rejectUnitVicePresident(
|
||||
initialValues.id,
|
||||
notes
|
||||
);
|
||||
}
|
||||
|
||||
if (isCurrentApprovalOnFinance) {
|
||||
@@ -255,8 +275,8 @@ const ExpenseRequestContent = ({
|
||||
{/* TODO: apply RBAC */}
|
||||
|
||||
<div className='w-full max-w-5xl mx-auto flex flex-col sm:flex-row justify-end gap-2'>
|
||||
{isCurrentApprovalOnManager && (
|
||||
<RequirePermission permissions='lti.expense.approve.manager'>
|
||||
{isCurrentApprovalOnHeadArea && (
|
||||
<RequirePermission permissions='lti.expense.approve.head_area'>
|
||||
<Button
|
||||
variant='outline'
|
||||
color='info'
|
||||
@@ -264,7 +284,21 @@ const ExpenseRequestContent = ({
|
||||
className='w-full sm:w-fit'
|
||||
>
|
||||
<Icon icon='lucide-lab:farm' width={24} height={24} />
|
||||
Approve Manager
|
||||
Approve Head Area
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
)}
|
||||
|
||||
{isCurrentApprovalOnUnitVicePresident && (
|
||||
<RequirePermission permissions='lti.expense.approve.unit_vice_president'>
|
||||
<Button
|
||||
variant='outline'
|
||||
color='success'
|
||||
onClick={approveClickHandler}
|
||||
className='w-full sm:w-fit'
|
||||
>
|
||||
<Icon icon='tdesign:money' width={24} height={24} />
|
||||
Approve Unit Vice President
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
)}
|
||||
@@ -304,7 +338,8 @@ const ExpenseRequestContent = ({
|
||||
{showRejectButton && (
|
||||
<RequirePermission
|
||||
permissions={[
|
||||
'lti.expense.approve.manager',
|
||||
'lti.expense.approve.head_area',
|
||||
'lti.expense.approve.unit_vice_president',
|
||||
'lti.expense.approve.finance',
|
||||
]}
|
||||
>
|
||||
@@ -454,8 +489,8 @@ const ExpenseRequestContent = ({
|
||||
<th>:</th>
|
||||
<td>
|
||||
{formatCurrency(
|
||||
initialValues?.latest_approval.step_number === 4 ||
|
||||
initialValues?.latest_approval.step_number === 5
|
||||
initialValues?.latest_approval.step_number === 5 ||
|
||||
initialValues?.latest_approval.step_number === 6
|
||||
? (initialValues?.total_realisasi ?? 0)
|
||||
: (initialValues?.total_pengajuan ?? 0)
|
||||
)}
|
||||
|
||||
@@ -55,15 +55,16 @@ const RowOptionsMenu = ({
|
||||
deleteClickHandler: () => void;
|
||||
}) => {
|
||||
const showEditButton =
|
||||
props.row.original.latest_approval.step_number !== 5 &&
|
||||
props.row.original.latest_approval.step_number !== 6 &&
|
||||
(props.row.original.latest_approval.step_number === 1 ||
|
||||
props.row.original.latest_approval.step_number === 2 ||
|
||||
props.row.original.latest_approval.step_number === 3);
|
||||
props.row.original.latest_approval.step_number === 3 ||
|
||||
props.row.original.latest_approval.step_number === 4);
|
||||
|
||||
// TODO: apply RBAC
|
||||
const showRealizationButton =
|
||||
props.row.original.latest_approval.action !== 'REJECTED' &&
|
||||
props.row.original.latest_approval.step_number === 3;
|
||||
props.row.original.latest_approval.step_number === 4;
|
||||
|
||||
return (
|
||||
<RowOptionsMenuWrapper type={type}>
|
||||
@@ -193,7 +194,7 @@ const ExpensesTable = () => {
|
||||
parseInt(item)
|
||||
);
|
||||
|
||||
const isAllSelectedRowLatestApprovalOnManager = useMemo(() => {
|
||||
const isAllSelectedRowLatestApprovalOnHeadArea = useMemo(() => {
|
||||
return selectedRowIds.every((rowId) => {
|
||||
if (!isResponseSuccess(expenses)) return false;
|
||||
|
||||
@@ -202,11 +203,28 @@ const ExpensesTable = () => {
|
||||
const isLatestApprovalRejected =
|
||||
expenseItem?.latest_approval.action === 'REJECTED';
|
||||
|
||||
const isCurrentApprovalOnManager =
|
||||
const isCurrentApprovalOnHeadArea =
|
||||
!isLatestApprovalRejected &&
|
||||
expenseItem?.latest_approval.step_number === 1;
|
||||
|
||||
return isCurrentApprovalOnManager;
|
||||
return isCurrentApprovalOnHeadArea;
|
||||
});
|
||||
}, [expenses, selectedRowIds]);
|
||||
|
||||
const isAllSelectedRowLatestApprovalOnUnitVicePresident = useMemo(() => {
|
||||
return selectedRowIds.every((rowId) => {
|
||||
if (!isResponseSuccess(expenses)) return false;
|
||||
|
||||
const expenseItem = expenses.data.find((item) => item.id === rowId);
|
||||
|
||||
const isLatestApprovalRejected =
|
||||
expenseItem?.latest_approval.action === 'REJECTED';
|
||||
|
||||
const isCurrentApprovalOnUnitVicePresident =
|
||||
!isLatestApprovalRejected &&
|
||||
expenseItem?.latest_approval.step_number === 2;
|
||||
|
||||
return isCurrentApprovalOnUnitVicePresident;
|
||||
});
|
||||
}, [expenses, selectedRowIds]);
|
||||
|
||||
@@ -221,7 +239,7 @@ const ExpensesTable = () => {
|
||||
|
||||
const isCurrentApprovalOnFinance =
|
||||
!isLatestApprovalRejected &&
|
||||
expenseItem?.latest_approval.step_number === 2;
|
||||
expenseItem?.latest_approval.step_number === 3;
|
||||
|
||||
return isCurrentApprovalOnFinance;
|
||||
});
|
||||
@@ -238,7 +256,7 @@ const ExpensesTable = () => {
|
||||
|
||||
const isCurrentApprovalOnRealization =
|
||||
!isLatestApprovalRejected &&
|
||||
expenseItem?.latest_approval.step_number === 4;
|
||||
expenseItem?.latest_approval.step_number === 5;
|
||||
|
||||
return isCurrentApprovalOnRealization;
|
||||
});
|
||||
@@ -397,7 +415,7 @@ const ExpensesTable = () => {
|
||||
) => {
|
||||
return (
|
||||
row.original.latest_approval.action !== 'REJECTED' &&
|
||||
row.original.latest_approval.step_number !== 5
|
||||
row.original.latest_approval.step_number !== 6
|
||||
);
|
||||
};
|
||||
|
||||
@@ -441,8 +459,13 @@ const ExpensesTable = () => {
|
||||
|
||||
let bulkApproveResponse: BaseApiResponse<Expense> | undefined = undefined;
|
||||
|
||||
if (isAllSelectedRowLatestApprovalOnManager) {
|
||||
bulkApproveResponse = await ExpenseApi.bulkApproveManager(
|
||||
if (isAllSelectedRowLatestApprovalOnHeadArea) {
|
||||
bulkApproveResponse = await ExpenseApi.bulkApproveHeadArea(
|
||||
selectedRowIds,
|
||||
notes
|
||||
);
|
||||
} else if (isAllSelectedRowLatestApprovalOnUnitVicePresident) {
|
||||
bulkApproveResponse = await ExpenseApi.bulkApproveUnitVicePresident(
|
||||
selectedRowIds,
|
||||
notes
|
||||
);
|
||||
@@ -478,8 +501,13 @@ const ExpensesTable = () => {
|
||||
|
||||
let bulkRejectResponse: BaseApiResponse<Expense> | undefined = undefined;
|
||||
|
||||
if (isAllSelectedRowLatestApprovalOnManager) {
|
||||
bulkRejectResponse = await ExpenseApi.bulkRejectManager(
|
||||
if (isAllSelectedRowLatestApprovalOnHeadArea) {
|
||||
bulkRejectResponse = await ExpenseApi.bulkRejectHeadArea(
|
||||
selectedRowIds,
|
||||
notes
|
||||
);
|
||||
} else if (isAllSelectedRowLatestApprovalOnUnitVicePresident) {
|
||||
bulkRejectResponse = await ExpenseApi.bulkRejectUnitVicePresident(
|
||||
selectedRowIds,
|
||||
notes
|
||||
);
|
||||
@@ -594,16 +622,31 @@ const ExpensesTable = () => {
|
||||
|
||||
{selectedRowIds.length > 0 && (
|
||||
<>
|
||||
<RequirePermission permissions='lti.expense.approve.manager'>
|
||||
<RequirePermission permissions='lti.expense.approve.head_area'>
|
||||
<Button
|
||||
variant='outline'
|
||||
color='info'
|
||||
onClick={bulkApproveClickHandler}
|
||||
disabled={!isAllSelectedRowLatestApprovalOnManager}
|
||||
disabled={!isAllSelectedRowLatestApprovalOnHeadArea}
|
||||
className='w-full sm:w-fit'
|
||||
>
|
||||
<Icon icon='lucide-lab:farm' width={24} height={24} />
|
||||
Approve Manager
|
||||
Approve Head Area
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
|
||||
<RequirePermission permissions='lti.expense.approve.unit_vice_president'>
|
||||
<Button
|
||||
variant='outline'
|
||||
color='success'
|
||||
onClick={bulkApproveClickHandler}
|
||||
disabled={
|
||||
!isAllSelectedRowLatestApprovalOnUnitVicePresident
|
||||
}
|
||||
className='w-full sm:w-fit'
|
||||
>
|
||||
<Icon icon='tdesign:money' width={24} height={24} />
|
||||
Approve Unit Vice President
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
|
||||
@@ -622,7 +665,8 @@ const ExpensesTable = () => {
|
||||
|
||||
<RequirePermission
|
||||
permissions={[
|
||||
'lti.expense.approve.manager',
|
||||
'lti.expense.approve.head_area',
|
||||
'lti.expense.approve.unit_vice_president',
|
||||
'lti.expense.approve.finance',
|
||||
]}
|
||||
>
|
||||
@@ -631,7 +675,8 @@ const ExpensesTable = () => {
|
||||
color='error'
|
||||
onClick={bulkRejectClickHandler}
|
||||
disabled={
|
||||
!isAllSelectedRowLatestApprovalOnManager &&
|
||||
!isAllSelectedRowLatestApprovalOnHeadArea &&
|
||||
!isAllSelectedRowLatestApprovalOnUnitVicePresident &&
|
||||
!isAllSelectedRowLatestApprovalOnFinance
|
||||
}
|
||||
className='w-full sm:w-fit'
|
||||
|
||||
@@ -130,18 +130,22 @@ export const EXPENSE_REQUEST_APPROVAL_LINE: ApprovalLine = [
|
||||
},
|
||||
{
|
||||
step_number: 2,
|
||||
step_name: 'Approval Manager',
|
||||
step_name: 'Head Area',
|
||||
},
|
||||
{
|
||||
step_number: 3,
|
||||
step_name: 'Approval Finance',
|
||||
step_name: 'Business Unit Vice President',
|
||||
},
|
||||
{
|
||||
step_number: 4,
|
||||
step_name: 'Realisasi',
|
||||
step_name: 'Finance',
|
||||
},
|
||||
{
|
||||
step_number: 5,
|
||||
step_name: 'Realisasi',
|
||||
},
|
||||
{
|
||||
step_number: 6,
|
||||
step_name: 'Selesai',
|
||||
},
|
||||
] as const;
|
||||
|
||||
+116
-8
@@ -169,13 +169,13 @@ export class ExpenseApiService extends BaseApiService<
|
||||
}
|
||||
}
|
||||
|
||||
async approveManager(
|
||||
async approveHeadArea(
|
||||
id: number,
|
||||
notes?: string
|
||||
): Promise<BaseApiResponse<Expense> | undefined> {
|
||||
try {
|
||||
const approveRes = await httpClient<BaseApiResponse<Expense>>(
|
||||
`${this.basePath}/approvals/manager`,
|
||||
`${this.basePath}/approvals/head-area`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: {
|
||||
@@ -196,13 +196,67 @@ export class ExpenseApiService extends BaseApiService<
|
||||
}
|
||||
}
|
||||
|
||||
async bulkApproveManager(
|
||||
async bulkApproveHeadArea(
|
||||
ids: number[],
|
||||
notes?: string
|
||||
): Promise<BaseApiResponse<Expense> | undefined> {
|
||||
try {
|
||||
const bulkApproveRes = await httpClient<BaseApiResponse<Expense>>(
|
||||
`${this.basePath}/approvals/manager`,
|
||||
`${this.basePath}/approvals/head-area`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: {
|
||||
action: 'APPROVED',
|
||||
approvable_ids: ids,
|
||||
notes: notes,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return bulkApproveRes;
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
|
||||
return error.response?.data;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async approveUnitVicePresident(
|
||||
id: number,
|
||||
notes?: string
|
||||
): Promise<BaseApiResponse<Expense> | undefined> {
|
||||
try {
|
||||
const approveRes = await httpClient<BaseApiResponse<Expense>>(
|
||||
`${this.basePath}/approvals/unit-vice-president`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: {
|
||||
action: 'APPROVED',
|
||||
approvable_ids: [id],
|
||||
notes: notes,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return approveRes;
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
|
||||
return error.response?.data;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async bulkApproveUnitVicePresident(
|
||||
ids: number[],
|
||||
notes?: string
|
||||
): Promise<BaseApiResponse<Expense> | undefined> {
|
||||
try {
|
||||
const bulkApproveRes = await httpClient<BaseApiResponse<Expense>>(
|
||||
`${this.basePath}/approvals/unit-vice-president`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: {
|
||||
@@ -277,13 +331,13 @@ export class ExpenseApiService extends BaseApiService<
|
||||
}
|
||||
}
|
||||
|
||||
async rejectManager(
|
||||
async rejectHeadArea(
|
||||
id: number,
|
||||
notes?: string
|
||||
): Promise<BaseApiResponse<Expense> | undefined> {
|
||||
try {
|
||||
const rejectRes = await httpClient<BaseApiResponse<Expense>>(
|
||||
`${this.basePath}/approvals/manager`,
|
||||
`${this.basePath}/approvals/head-area`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: {
|
||||
@@ -304,13 +358,67 @@ export class ExpenseApiService extends BaseApiService<
|
||||
}
|
||||
}
|
||||
|
||||
async bulkRejectManager(
|
||||
async bulkRejectHeadArea(
|
||||
ids: number[],
|
||||
notes?: string
|
||||
): Promise<BaseApiResponse<Expense> | undefined> {
|
||||
try {
|
||||
const bulkRejectRes = await httpClient<BaseApiResponse<Expense>>(
|
||||
`${this.basePath}/approvals/manager`,
|
||||
`${this.basePath}/approvals/head-area`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: {
|
||||
action: 'REJECTED',
|
||||
approvable_ids: ids,
|
||||
notes: notes,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return bulkRejectRes;
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
|
||||
return error.response?.data;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async rejectUnitVicePresident(
|
||||
id: number,
|
||||
notes?: string
|
||||
): Promise<BaseApiResponse<Expense> | undefined> {
|
||||
try {
|
||||
const rejectRes = await httpClient<BaseApiResponse<Expense>>(
|
||||
`${this.basePath}/approvals/unit-vice-president`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: {
|
||||
action: 'REJECTED',
|
||||
approvable_ids: [id],
|
||||
notes: notes,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return rejectRes;
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
|
||||
return error.response?.data;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async bulkRejectUnitVicePresident(
|
||||
ids: number[],
|
||||
notes?: string
|
||||
): Promise<BaseApiResponse<Expense> | undefined> {
|
||||
try {
|
||||
const bulkRejectRes = await httpClient<BaseApiResponse<Expense>>(
|
||||
`${this.basePath}/approvals/unit-vice-president`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: {
|
||||
|
||||
Reference in New Issue
Block a user