feat(FE-331): implement permission guard in expense

This commit is contained in:
ValdiANS
2025-12-24 11:08:37 +07:00
parent dda29e10d1
commit 6ed7dcfa6d
5 changed files with 362 additions and 294 deletions
@@ -4,6 +4,7 @@ import toast from 'react-hot-toast';
import Link from 'next/link';
import { Icon } from '@iconify/react';
import Button from '@/components/Button';
import RequirePermission from '@/components/helper/RequirePermission';
import Card from '@/components/Card';
import DropFileInput from '@/components/input/DropFileInput';
@@ -62,7 +63,7 @@ const ExpenseRealizationContent = ({
<div>
<div className='w-full max-w-5xl mx-auto flex flex-col sm:flex-row justify-end gap-2'>
<div className='w-full sm:w-fit sm:ml-2 flex flex-row gap-2 items-center'>
{/* TODO: apply RBAC */}
<RequirePermission permissions='lti.expense.update.realization'>
<Button
type='button'
color='warning'
@@ -72,6 +73,7 @@ const ExpenseRealizationContent = ({
<Icon icon='mdi:pencil-outline' width={24} height={24} />
Edit Realisasi
</Button>
</RequirePermission>
</div>
</div>
@@ -124,6 +126,7 @@ const ExpenseRealizationContent = ({
)}
</div>
<RequirePermission permissions='lti.expense.document.realization'>
<div className='flex flex-col gap-2'>
<DropFileInput
name='documents'
@@ -154,6 +157,7 @@ const ExpenseRealizationContent = ({
</Button>
)}
</div>
</RequirePermission>
</td>
</tr>
</tbody>
@@ -19,6 +19,7 @@ import { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal';
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
import ExpensePDFPreviewButton from '@/components/pages/expense//pdf/ExpensePDFButton';
import RequirePermission from '@/components/helper/RequirePermission';
import { Expense } from '@/types/api/expense';
import { formatCurrency, formatDate } from '@/lib/helper';
@@ -255,6 +256,7 @@ const ExpenseRequestContent = ({
<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'>
<Button
variant='outline'
color='info'
@@ -264,9 +266,11 @@ const ExpenseRequestContent = ({
<Icon icon='lucide-lab:farm' width={24} height={24} />
Approve Manager
</Button>
</RequirePermission>
)}
{isCurrentApprovalOnFinance && (
<RequirePermission permissions='lti.expense.approve.finance'>
<Button
variant='outline'
color='success'
@@ -276,9 +280,11 @@ const ExpenseRequestContent = ({
<Icon icon='tdesign:money' width={24} height={24} />
Approve Finance
</Button>
</RequirePermission>
)}
{isCurrentApprovalOnRealization && (
<RequirePermission permissions='lti.expense.complete.expense'>
<Button
variant='outline'
color='success'
@@ -292,9 +298,16 @@ const ExpenseRequestContent = ({
/>
Selesai
</Button>
</RequirePermission>
)}
{showRejectButton && (
<RequirePermission
permissions={[
'lti.expense.approve.manager',
'lti.expense.approve.finance',
]}
>
<Button
variant='outline'
color='error'
@@ -304,9 +317,11 @@ const ExpenseRequestContent = ({
<Icon icon='material-symbols:close' width={24} height={24} />
Reject
</Button>
</RequirePermission>
)}
{isExpenseCanBeRealized && (
<RequirePermission permissions='lti.expense.create.realization'>
<Button
variant='outline'
color='info'
@@ -320,10 +335,12 @@ const ExpenseRequestContent = ({
/>
Realisasi
</Button>
</RequirePermission>
)}
<div className='w-full sm:w-fit sm:ml-2 flex flex-row gap-2 items-center'>
{showEditButton && (
<RequirePermission permissions='lti.expense.update'>
<Button
type='button'
color='warning'
@@ -333,8 +350,10 @@ const ExpenseRequestContent = ({
<Icon icon='mdi:pencil-outline' width={24} height={24} />
Edit
</Button>
</RequirePermission>
)}
<RequirePermission permissions='lti.expense.delete'>
<Button
type='button'
color='error'
@@ -349,6 +368,7 @@ const ExpenseRequestContent = ({
/>
Delete
</Button>
</RequirePermission>
</div>
</div>
@@ -485,6 +505,7 @@ const ExpenseRequestContent = ({
)}
</div>
<RequirePermission permissions='lti.expense.document'>
<div className='flex flex-col gap-2'>
<DropFileInput
name='documents'
@@ -510,11 +531,16 @@ const ExpenseRequestContent = ({
isLoading={formik.isSubmitting}
className='w-fit self-end'
>
<Icon icon='ic:round-plus' width={24} height={24} />
<Icon
icon='ic:round-plus'
width={24}
height={24}
/>
Tambah
</Button>
)}
</div>
</RequirePermission>
</td>
</tr>
</tbody>
+27 -1
View File
@@ -28,6 +28,7 @@ import ExpenseStatusBadge from '@/components/pages/expense/ExpenseStatusBadge';
import CheckboxInput from '@/components/input/CheckboxInput';
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
import DateInput from '@/components/input/DateInput';
import RequirePermission from '@/components/helper/RequirePermission';
import { Expense } from '@/types/api/expense';
import { ExpenseApi } from '@/services/api/expense';
@@ -67,6 +68,7 @@ const RowOptionsMenu = ({
return (
<RowOptionsMenuWrapper type={type}>
<div className='w-full max-h-40 overflow-auto flex flex-col gap-1'>
<RequirePermission permissions='lti.expense.detail'>
<Button
href={`/expense/detail/?expenseId=${props.row.original.id}`}
variant='ghost'
@@ -76,20 +78,28 @@ const RowOptionsMenu = ({
<Icon icon='mdi:eye-outline' width={16} height={16} />
Detail
</Button>
</RequirePermission>
{showEditButton && (
<RequirePermission permissions='lti.expense.update'>
<Button
href={`/expense/detail/edit/?expenseId=${props.row.original.id}`}
variant='ghost'
color='warning'
className='justify-start text-sm'
>
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
<Icon
icon='material-symbols:edit-outline'
width={16}
height={16}
/>
Edit
</Button>
</RequirePermission>
)}
{showRealizationButton && (
<RequirePermission permissions='lti.expense.create.realization'>
<Button
href={`/expense/realization/?expenseId=${props.row.original.id}`}
variant='ghost'
@@ -103,8 +113,10 @@ const RowOptionsMenu = ({
/>
Realisasi
</Button>
</RequirePermission>
)}
<RequirePermission permissions='lti.expense.delete'>
<Button
onClick={deleteClickHandler}
variant='ghost'
@@ -119,6 +131,7 @@ const RowOptionsMenu = ({
/>
Delete
</Button>
</RequirePermission>
</div>
</RowOptionsMenuWrapper>
);
@@ -559,6 +572,7 @@ const ExpensesTable = () => {
<div className='flex flex-col gap-2 mb-4'>
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-4'>
<div className='w-full sm:w-fit flex flex-col sm:flex-row self-start gap-2'>
<RequirePermission permissions='lti.expense.create'>
<Button
href='/expense/add'
variant='outline'
@@ -568,9 +582,11 @@ const ExpensesTable = () => {
<Icon icon='ic:round-plus' width={24} height={24} />
Tambah
</Button>
</RequirePermission>
{selectedRowIds.length > 0 && (
<>
<RequirePermission permissions='lti.expense.approve.manager'>
<Button
variant='outline'
color='info'
@@ -581,7 +597,9 @@ const ExpensesTable = () => {
<Icon icon='lucide-lab:farm' width={24} height={24} />
Approve Manager
</Button>
</RequirePermission>
<RequirePermission permissions='lti.expense.approve.finance'>
<Button
variant='outline'
color='success'
@@ -592,7 +610,14 @@ const ExpensesTable = () => {
<Icon icon='tdesign:money' width={24} height={24} />
Approve Finance
</Button>
</RequirePermission>
<RequirePermission
permissions={[
'lti.expense.approve.manager',
'lti.expense.approve.finance',
]}
>
<Button
variant='outline'
color='error'
@@ -610,6 +635,7 @@ const ExpensesTable = () => {
/>
Reject
</Button>
</RequirePermission>
</>
)}
</div>
@@ -16,6 +16,7 @@ import DateInput from '@/components/input/DateInput';
import DropFileInput from '@/components/input/DropFileInput';
import ExpenseKandangsTable from '@/components/pages/expense/form/ExpenseKandangsTable';
import ExpenseRealizationKandangDetailExpense from '@/components/pages/expense/form/ExpenseRealizationKandangDetailExpense';
import RequirePermission from '@/components/helper/RequirePermission';
import {
CreateExpenseRealizationPayload,
@@ -290,6 +291,7 @@ const ExpenseRealizationForm = ({
className={{ wrapper: 'col-span-12' }}
/>
<RequirePermission permissions='lti.expense.document.realization'>
<DropFileInput
label='Dokumen Realisasi'
name='documents'
@@ -305,6 +307,7 @@ const ExpenseRealizationForm = ({
inputWrapper: 'h-12 flex items-center',
}}
/>
</RequirePermission>
{formik.values.existing_documents &&
formik.values.existing_documents.length > 0 && (
@@ -357,6 +360,7 @@ const ExpenseRealizationForm = ({
{type !== 'add' && (
<div className='flex flex-row justify-start gap-2'>
{type !== 'edit' && (
<RequirePermission permissions='lti.expense.update'>
<Button
type='button'
color='warning'
@@ -371,6 +375,7 @@ const ExpenseRealizationForm = ({
/>
Edit
</Button>
</RequirePermission>
)}
</div>
)}
@@ -18,6 +18,7 @@ import DateInput from '@/components/input/DateInput';
import ExpenseKandangsTable from '@/components/pages/expense/form/ExpenseKandangsTable';
import DropFileInput from '@/components/input/DropFileInput';
import ExpenseRequestKandangDetailExpense from '@/components/pages/expense/form/ExpenseRequestKandangDetailExpense';
import RequirePermission from '@/components/helper/RequirePermission';
import {
ExpenseRequestFormSchema,
@@ -385,6 +386,7 @@ const ExpenseRequestForm = ({
className={{ wrapper: 'col-span-12' }}
/>
<RequirePermission permissions='lti.expense.document'>
<DropFileInput
label='Dokumen Pengajuan'
name='documents'
@@ -400,6 +402,7 @@ const ExpenseRequestForm = ({
inputWrapper: 'h-12 flex items-center',
}}
/>
</RequirePermission>
{formik.values.existing_documents &&
formik.values.existing_documents.length > 0 && (
@@ -461,6 +464,7 @@ const ExpenseRequestForm = ({
<div className='flex flex-row justify-between gap-2 flex-wrap'>
{type !== 'add' && (
<div className='flex flex-row justify-start gap-2'>
<RequirePermission permissions='lti.expense.delete'>
<Button
type='button'
color='error'
@@ -475,8 +479,10 @@ const ExpenseRequestForm = ({
/>
Delete
</Button>
</RequirePermission>
{type !== 'edit' && (
<RequirePermission permissions='lti.expense.update'>
<Button
type='button'
color='warning'
@@ -491,6 +497,7 @@ const ExpenseRequestForm = ({
/>
Edit
</Button>
</RequirePermission>
)}
</div>
)}