feat(FE-331): implement permission guard in project flock

This commit is contained in:
ValdiANS
2025-12-26 21:08:00 +07:00
parent 4be719b9d8
commit 9e0d3e2bbf
5 changed files with 242 additions and 173 deletions
+30 -2
View File
@@ -5,6 +5,8 @@ import Tooltip from '@/components/Tooltip';
import { cn } from '@/lib/helper';
import { Icon } from '@iconify/react';
import { useAuth } from '@/services/hooks/useAuth';
type FloatingActionsButtonProps = {
actions: {
action: 'DETAIL' | 'EDIT' | 'DELETE';
@@ -13,6 +15,7 @@ type FloatingActionsButtonProps = {
onClick?: () => void;
hidden?: boolean;
disabled?: boolean;
permissions?: string | string[];
}[];
approvals: {
action: 'APPROVED' | 'REJECTED';
@@ -20,6 +23,7 @@ type FloatingActionsButtonProps = {
label?: string;
onClick?: () => void;
disabled?: boolean;
permissions?: string | string[];
}[];
selectedRowIds: number[];
onClose: () => void;
@@ -31,6 +35,7 @@ const FloatingActionsButton = ({
selectedRowIds,
onClose,
}: FloatingActionsButtonProps) => {
const { permissionCheck } = useAuth();
// Jika tidak ada baris yang dipilih, jangan tampilkan FAB
const positionStyles =
selectedRowIds.length > 0
@@ -71,7 +76,18 @@ const FloatingActionsButton = ({
<div className='flex gap-4 items-center'>
{/* Render Aksi dari props.actions */}
{actions
.filter((action) => !action.hidden)
.filter((action) => {
if (action.hidden) return false;
if (action.permissions) {
if (typeof action.permissions === 'string') {
return permissionCheck(action.permissions);
}
return action.permissions.some((permission) =>
permissionCheck(permission)
);
}
return true;
})
.map((action, index) => {
return (
<Button
@@ -111,7 +127,19 @@ const FloatingActionsButton = ({
{/* === BARIS BAWAH: Approval Buttons (Approve/Reject) === */}
<div className={`grid grid-cols-${approvals.length} gap-3`}>
{approvals.map((approval, index) => (
{approvals
.filter((approval) => {
if (approval.permissions) {
if (typeof approval.permissions === 'string') {
return permissionCheck(approval.permissions);
}
return approval.permissions.some((permission) =>
permissionCheck(permission)
);
}
return true;
})
.map((approval, index) => (
<Button
key={index}
onClick={approval.onClick}
@@ -25,6 +25,8 @@ import { ChangeEventHandler, useEffect, useMemo, useState } from 'react';
import toast from 'react-hot-toast';
import useSWR from 'swr';
import RequirePermission from '@/components/helper/RequirePermission';
const RowOptionsMenu = ({
type = 'dropdown',
props,
@@ -46,6 +48,7 @@ const RowOptionsMenu = ({
)}
>
<div className='flex flex-col gap-1'>
<RequirePermission permissions='lti.production.project_flocks.detail'>
<Button
href={`/production/project-flock/detail?projectFlockId=${props.row.original.id}`}
variant='ghost'
@@ -55,7 +58,9 @@ const RowOptionsMenu = ({
<Icon icon='mdi:eye-outline' width={16} height={16} />
Detail
</Button>
</RequirePermission>
{props.row.original.approval.step_name === 'Aktif' && (
<RequirePermission permissions='lti.production.chickins.create'>
<Button
href={`/production/project-flock/chickin/add?projectFlockId=${props.row.original.id}`}
variant='ghost'
@@ -65,8 +70,10 @@ const RowOptionsMenu = ({
<Icon icon='mdi:home-import-outline' width={16} height={16} />
Chickin
</Button>
</RequirePermission>
)}
{props.row.original.approval.step_name === 'Pengajuan' && (
<RequirePermission permissions='lti.production.project_flocks.update'>
<Button
href={`/production/project-flock/detail/edit?projectFlockId=${props.row.original.id}`}
variant='ghost'
@@ -76,7 +83,9 @@ const RowOptionsMenu = ({
<Icon icon='mdi:pencil-outline' width={16} height={16} />
Edit
</Button>
</RequirePermission>
)}
<RequirePermission permissions='lti.production.project_flocks.delete'>
<Button
onClick={deleteClickHandler}
variant='ghost'
@@ -90,6 +99,7 @@ const RowOptionsMenu = ({
/>
Delete
</Button>
</RequirePermission>
</div>
</div>
);
@@ -287,6 +297,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
<div className='flex flex-col gap-2 mb-4'>
<div className='w-full flex flex-col justify-between items-end gap-2'>
<div className='flex flex-col sm:flex-row gap-3 w-full'>
<RequirePermission permissions='lti.production.project_flocks.create'>
<Button
color='primary'
className='w-full sm:w-fit'
@@ -295,6 +306,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
<Icon icon='ic:round-plus' width={24} height={24} />
Tambah
</Button>
</RequirePermission>
{/* <Button
variant='outline'
color='success'
@@ -630,6 +642,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
);
setRowSelection({});
},
permissions: 'lti.production.project_flocks.detail',
},
{
action: 'DELETE',
@@ -639,6 +652,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
onClick: () => {
deleteModal.openModal();
},
permissions: 'lti.production.project_flocks.delete',
},
]}
approvals={[
@@ -651,6 +665,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
confirmModal.openModal();
},
disabled: !canApprove,
permissions: 'lti.production.project_flocks.approve',
},
{
icon: 'mdi:times',
@@ -660,6 +675,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
setApprovalAction('REJECTED');
confirmModal.openModal();
},
permissions: 'lti.production.project_flocks.approve',
},
]}
selectedRowIds={selectedRowIds}
@@ -22,6 +22,7 @@ import { useEffect, useState } from 'react';
import useSWR from 'swr';
import { FormHeader } from '@/components/helper/form/FormHeader';
import Link from 'next/link';
import RequirePermission from '@/components/helper/RequirePermission';
const ProjectFlockChickinDetail = ({
projectFlockId,
@@ -484,6 +485,7 @@ const ProjectFlockChickinDetail = ({
{kandang.kandang.name}
</span>
</div>
<RequirePermission permissions='lti.production.chickins.create'>
<Button
variant='outline'
className='py-1 text-sm'
@@ -499,6 +501,7 @@ const ProjectFlockChickinDetail = ({
height={11}
/>
</Button>
</RequirePermission>
</div>
))}
</div>
@@ -29,6 +29,7 @@ import {
} from '@/config/approval-line';
import useSWR from 'swr';
import { ProjectFlockKandangApi } from '@/services/api/production';
import RequirePermission from '@/components/helper/RequirePermission';
const ProjectFlockDetail = ({
projectFlock,
@@ -110,6 +111,7 @@ const ProjectFlockDetail = ({
leftIconHref='/production/project-flock'
subtitle={`Created On ${formatDate(projectFlock.created_at, 'MMM DD, YYYY')}`}
>
<RequirePermission permissions='lti.production.project_flocks.update'>
<Link
href={`/production/project-flock/detail/edit?projectFlockId=${projectFlock.id}`}
className='p-0'
@@ -120,6 +122,8 @@ const ProjectFlockDetail = ({
</Button>
</Tooltip>
</Link>
</RequirePermission>
<RequirePermission permissions='lti.production.project_flocks.delete'>
<Button
variant='link'
className='p-0 text-error'
@@ -131,6 +135,7 @@ const ProjectFlockDetail = ({
<Icon icon='mdi:trash-can-outline' width={20} height={20} />
</Tooltip>
</Button>
</RequirePermission>
</DrawerHeader>
{/* Informasi Umum */}
@@ -418,6 +423,7 @@ const ProjectFlockDetail = ({
</RadioGroup>
</Card>
<div className='grid grid-cols-4 gap-3'>
<RequirePermission permissions='lti.production.chickins.create'>
<Link
href={`/production/project-flock/chickin/add/kandang?projectFlockKandangId=${selectedKandang?.project_flock_kandang_id}&projectFlockId=${projectFlock.id}`}
className='m-0 p-0'
@@ -434,6 +440,8 @@ const ProjectFlockDetail = ({
Chickin <Icon icon='mdi:checkbox-marked-outline' />
</Button>
</Link>
</RequirePermission>
<RequirePermission permissions='lti.production.project_flock_kandangs.closing'>
<Link
href={`/production/project-flock/closing?projectFlockId=${projectFlock.id}&projectFlockKandangId=${selectedKandang?.project_flock_kandang_id}`}
className='m-0 p-0'
@@ -450,6 +458,7 @@ const ProjectFlockDetail = ({
Close <Icon icon='mdi:checkbox-marked-circle-outline' />
</Button>
</Link>
</RequirePermission>
</div>
</div>
</div>
@@ -47,6 +47,7 @@ import Card from '@/components/Card';
import ProjectFlockKandangTable from '@/components/pages/production/project-flock/form/ProjectFlockKandangTable';
import { Nonstock } from '@/types/api/master-data/nonstock';
import { useUiStore } from '@/stores/ui/ui.store';
import RequirePermission from '@/components/helper/RequirePermission';
import DrawerHeader from '@/components/helper/drawer/DrawerHeader';
interface ProjectFlockFormProps {
@@ -734,6 +735,7 @@ const ProjectFlockForm = ({
)}
{formType == 'detail' && (
<div className='w-full flex flex-col sm:flex-row gap-2 py-4'>
<RequirePermission permissions='lti.production.project_flocks.approve'>
<Button
variant='outline'
color='success'
@@ -749,6 +751,8 @@ const ProjectFlockForm = ({
<Icon icon='material-symbols:check' width={24} height={24} />
Approve
</Button>
</RequirePermission>
<RequirePermission permissions='lti.production.project_flocks.approve'>
<Button
variant='outline'
color='error'
@@ -764,6 +768,7 @@ const ProjectFlockForm = ({
<Icon icon='mdi:times' width={24} height={24} />
Reject
</Button>
</RequirePermission>
</div>
)}
<form
@@ -1127,6 +1132,13 @@ const ProjectFlockForm = ({
</div>
</div> */}
{formType !== 'detail' && (
<RequirePermission
permissions={
formType == 'add'
? 'lti.production.project_flocks.create'
: 'lti.production.project_flocks.update'
}
>
<Button
type='submit'
color='primary'
@@ -1137,6 +1149,7 @@ const ProjectFlockForm = ({
<Icon icon='mdi:plus' width={24} height={24} />
{formType == 'add' ? 'Add Flock' : 'Update Flock'}
</Button>
</RequirePermission>
)}
</div>
</form>