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

This commit is contained in:
ValdiANS
2025-12-26 16:21:22 +07:00
parent 1152cd2bef
commit 4be719b9d8
3 changed files with 128 additions and 88 deletions
+39 -32
View File
@@ -15,6 +15,7 @@ import SelectInput, { OptionType } from '@/components/input/SelectInput';
import RowDropdownOptions from '@/components/table/RowDropdownOptions'; import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions'; import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper'; import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import RequirePermission from '@/components/helper/RequirePermission';
import { cn, formatDate } from '@/lib/helper'; import { cn, formatDate } from '@/lib/helper';
import { isResponseSuccess } from '@/lib/api-helper'; import { isResponseSuccess } from '@/lib/api-helper';
@@ -38,15 +39,17 @@ const RowOptionsMenu = ({
}: RowOptionsMenuProps) => { }: RowOptionsMenuProps) => {
return ( return (
<RowOptionsMenuWrapper type={type}> <RowOptionsMenuWrapper type={type}>
<Button <RequirePermission permissions='lti.purchase.detail'>
href={`/purchase/detail/?purchaseId=${props.row.original.id}`} <Button
variant='ghost' href={`/purchase/detail/?purchaseId=${props.row.original.id}`}
color='primary' variant='ghost'
className='justify-start text-sm' color='primary'
> className='justify-start text-sm'
<Icon icon='mdi:eye-outline' width={16} height={16} /> >
Detail <Icon icon='mdi:eye-outline' width={16} height={16} />
</Button> Detail
</Button>
</RequirePermission>
{/*<Button*/} {/*<Button*/}
{/* href={`/purchase/detail/edit/?purchaseId=${props.row.original.id}`}*/} {/* href={`/purchase/detail/edit/?purchaseId=${props.row.original.id}`}*/}
@@ -58,20 +61,22 @@ const RowOptionsMenu = ({
{/* Edit*/} {/* Edit*/}
{/*</Button>*/} {/*</Button>*/}
<Button <RequirePermission permissions='lti.purchase.delete'>
onClick={deleteClickHandler} <Button
variant='ghost' onClick={deleteClickHandler}
color='error' variant='ghost'
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content' color='error'
> className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
<Icon >
icon='material-symbols:delete-outline-rounded' <Icon
width={16} icon='material-symbols:delete-outline-rounded'
height={16} width={16}
className='justify-start text-sm' height={16}
/> className='justify-start text-sm'
Delete />
</Button> Delete
</Button>
</RequirePermission>
</RowOptionsMenuWrapper> </RowOptionsMenuWrapper>
); );
}; };
@@ -227,15 +232,17 @@ const PurchaseTable = () => {
<div className='flex flex-col gap-2 mb-4'> <div className='flex flex-col gap-2 mb-4'>
<div className='w-full flex flex-col xl:flex-row justify-between items-end xl:items-center gap-2'> <div className='w-full flex flex-col xl:flex-row justify-between items-end xl:items-center gap-2'>
<div className='w-full flex flex-row gap-2'> <div className='w-full flex flex-row gap-2'>
<Button <RequirePermission permissions='lti.purchase.create'>
href='/purchase/add' <Button
variant='outline' href='/purchase/add'
color='primary' variant='outline'
className='w-full sm:w-fit' color='primary'
> className='w-full sm:w-fit'
<Icon icon='ic:round-plus' width={24} height={24} /> >
Tambah <Icon icon='ic:round-plus' width={24} height={24} />
</Button> Tambah
</Button>
</RequirePermission>
</div> </div>
<DebouncedTextInput <DebouncedTextInput
@@ -32,6 +32,7 @@ import {
} from '@/types/api/purchase/purchase'; } from '@/types/api/purchase/purchase';
import { BaseApproval, BaseGroupedApproval } from '@/types/api/api-general'; import { BaseApproval, BaseGroupedApproval } from '@/types/api/api-general';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import RequirePermission from '@/components/helper/RequirePermission';
interface PurchaseOrderStaffApprovalFormProps { interface PurchaseOrderStaffApprovalFormProps {
type?: 'add' | 'edit'; type?: 'add' | 'edit';
@@ -897,20 +898,25 @@ const PurchaseOrderStaffApprovalForm = ({
<div className='flex justify-center'> <div className='flex justify-center'>
{canUpdatePurchaseItems && {canUpdatePurchaseItems &&
canShowDeleteAddButtons && ( canShowDeleteAddButtons && (
<Button <RequirePermission permissions='lti.purchase.delete.item'>
type='button' <Button
color='error' type='button'
onClick={() => color='error'
removePurchaseItem(formItemIndex) className='text-sm w-fit'
} onClick={() =>
title='Hapus item' removePurchaseItem(
> formItemIndex
<Icon )
icon='mdi:trash-can' }
width={16} title='Hapus item'
height={16} >
/> <Icon
</Button> icon='mdi:trash-can'
width={16}
height={16}
/>
</Button>
</RequirePermission>
)} )}
</div> </div>
</td> </td>
@@ -39,19 +39,22 @@ import { toast } from 'react-hot-toast';
import { useSearchParams } from 'next/navigation'; import { useSearchParams } from 'next/navigation';
import { formatCurrency, formatNumber, formatDate } from '@/lib/helper'; import { formatCurrency, formatNumber, formatDate } from '@/lib/helper';
import { PURCHASE_ORDER_APPROVAL_LINE } from '@/config/approval-line'; import { PURCHASE_ORDER_APPROVAL_LINE } from '@/config/approval-line';
import RequirePermission from '@/components/helper/RequirePermission';
const ItemPembelianDropdown = ({ onEdit }: { onEdit: () => void }) => { const ItemPembelianDropdown = ({ onEdit }: { onEdit: () => void }) => {
return ( return (
<RowOptionsMenuWrapper type='dropdown'> <RowOptionsMenuWrapper type='dropdown'>
<Button <RequirePermission permissions='lti.purchase.update'>
onClick={onEdit} <Button
variant='ghost' onClick={onEdit}
color='warning' variant='ghost'
className='justify-start text-sm' color='warning'
> className='justify-start text-sm'
<Icon icon='material-symbols:edit-outline' width={16} height={16} /> >
Edit <Icon icon='material-symbols:edit-outline' width={16} height={16} />
</Button> Edit
</Button>
</RequirePermission>
</RowOptionsMenuWrapper> </RowOptionsMenuWrapper>
); );
}; };
@@ -59,15 +62,17 @@ const ItemPembelianDropdown = ({ onEdit }: { onEdit: () => void }) => {
const PenerimaanBarangDropdown = ({ onEdit }: { onEdit: () => void }) => { const PenerimaanBarangDropdown = ({ onEdit }: { onEdit: () => void }) => {
return ( return (
<RowOptionsMenuWrapper type='dropdown'> <RowOptionsMenuWrapper type='dropdown'>
<Button <RequirePermission permissions='lti.purchase.receive'>
onClick={onEdit} <Button
variant='ghost' onClick={onEdit}
color='warning' variant='ghost'
className='justify-start text-sm' color='warning'
> className='justify-start text-sm'
<Icon icon='material-symbols:edit-outline' width={16} height={16} /> >
Edit <Icon icon='material-symbols:edit-outline' width={16} height={16} />
</Button> Edit
</Button>
</RequirePermission>
</RowOptionsMenuWrapper> </RowOptionsMenuWrapper>
); );
}; };
@@ -496,14 +501,16 @@ const PurchaseOrderDetail = ({
}; };
return ( return (
<Button <RequirePermission permissions='lti.purchase.delete.item'>
type='button' <Button
color='error' type='button'
className='text-sm' color='error'
onClick={deleteClickHandler} className='text-sm'
> onClick={deleteClickHandler}
<Icon icon='mdi:trash-can' width={16} height={16} /> >
</Button> <Icon icon='mdi:trash-can' width={16} height={16} />
</Button>
</RequirePermission>
); );
}, },
}, },
@@ -632,25 +639,45 @@ const PurchaseOrderDetail = ({
{showApprovalButton && ( {showApprovalButton && (
<div className='flex flex-row gap-2'> <div className='flex flex-row gap-2'>
<Button <RequirePermission
onClick={handleApprovalClick} permissions={
variant='outline' approvalStep === 1
color='success' ? 'lti.purchase.approve.staff'
className='w-full sm:w-fit' : approvalStep === 2
? 'lti.purchase.approve.manager'
: 'lti.purchase.receive'
}
> >
<Icon icon='material-symbols:check' width={24} height={24} /> <Button
Approve onClick={handleApprovalClick}
</Button> variant='outline'
color='success'
className='w-full sm:w-fit'
>
<Icon icon='material-symbols:check' width={24} height={24} />
Approve
</Button>
</RequirePermission>
<Button <RequirePermission
variant='outline' permissions={
color='error' approvalStep === 1
className='w-full sm:w-fit' ? 'lti.purchase.approve.staff'
onClick={handleRejectionClick} : approvalStep === 2
? 'lti.purchase.approve.manager'
: 'lti.purchase.receive'
}
> >
<Icon icon='material-symbols:close' width={24} height={24} /> <Button
Reject variant='outline'
</Button> color='error'
className='w-full sm:w-fit'
onClick={handleRejectionClick}
>
<Icon icon='material-symbols:close' width={24} height={24} />
Reject
</Button>
</RequirePermission>
</div> </div>
)} )}
</div> </div>