mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 05:22:02 +00:00
feat(FE-331): implement permission guard in project flock
This commit is contained in:
@@ -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,29 +127,41 @@ const FloatingActionsButton = ({
|
||||
|
||||
{/* === BARIS BAWAH: Approval Buttons (Approve/Reject) === */}
|
||||
<div className={`grid grid-cols-${approvals.length} gap-3`}>
|
||||
{approvals.map((approval, index) => (
|
||||
<Button
|
||||
key={index}
|
||||
onClick={approval.onClick}
|
||||
className={cn(
|
||||
'btn btn-lg w-full',
|
||||
'bg-white/20 border-white/30',
|
||||
'text-white/50 font-semibold flex items-center gap-2 rounded-lg transition-all duration-200',
|
||||
approval.disabled
|
||||
? 'cursor-not-allowed'
|
||||
: 'hover:text-white/100 hover:bg-white/40 hover:border-white/50'
|
||||
)}
|
||||
disabled={approval.disabled}
|
||||
>
|
||||
<Icon
|
||||
icon={approval.icon}
|
||||
width={20}
|
||||
height={20}
|
||||
className={`text-${getApprovalColor(approval.action)}`}
|
||||
/>
|
||||
{approval.label || approval.action}
|
||||
</Button>
|
||||
))}
|
||||
{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}
|
||||
className={cn(
|
||||
'btn btn-lg w-full',
|
||||
'bg-white/20 border-white/30',
|
||||
'text-white/50 font-semibold flex items-center gap-2 rounded-lg transition-all duration-200',
|
||||
approval.disabled
|
||||
? 'cursor-not-allowed'
|
||||
: 'hover:text-white/100 hover:bg-white/40 hover:border-white/50'
|
||||
)}
|
||||
disabled={approval.disabled}
|
||||
>
|
||||
<Icon
|
||||
icon={approval.icon}
|
||||
width={20}
|
||||
height={20}
|
||||
className={`text-${getApprovalColor(approval.action)}`}
|
||||
/>
|
||||
{approval.label || approval.action}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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,50 +48,58 @@ const RowOptionsMenu = ({
|
||||
)}
|
||||
>
|
||||
<div className='flex flex-col gap-1'>
|
||||
<Button
|
||||
href={`/production/project-flock/detail?projectFlockId=${props.row.original.id}`}
|
||||
variant='ghost'
|
||||
color='primary'
|
||||
className='justify-start text-sm'
|
||||
>
|
||||
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
||||
Detail
|
||||
</Button>
|
||||
{props.row.original.approval.step_name === 'Aktif' && (
|
||||
<RequirePermission permissions='lti.production.project_flocks.detail'>
|
||||
<Button
|
||||
href={`/production/project-flock/chickin/add?projectFlockId=${props.row.original.id}`}
|
||||
href={`/production/project-flock/detail?projectFlockId=${props.row.original.id}`}
|
||||
variant='ghost'
|
||||
color='success'
|
||||
color='primary'
|
||||
className='justify-start text-sm'
|
||||
>
|
||||
<Icon icon='mdi:home-import-outline' width={16} height={16} />
|
||||
Chickin
|
||||
<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'
|
||||
color='success'
|
||||
className='justify-start text-sm'
|
||||
>
|
||||
<Icon icon='mdi:home-import-outline' width={16} height={16} />
|
||||
Chickin
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
)}
|
||||
{props.row.original.approval.step_name === 'Pengajuan' && (
|
||||
<Button
|
||||
href={`/production/project-flock/detail/edit?projectFlockId=${props.row.original.id}`}
|
||||
variant='ghost'
|
||||
color='warning'
|
||||
className='justify-start text-sm'
|
||||
>
|
||||
<Icon icon='mdi:pencil-outline' width={16} height={16} />
|
||||
Edit
|
||||
</Button>
|
||||
<RequirePermission permissions='lti.production.project_flocks.update'>
|
||||
<Button
|
||||
href={`/production/project-flock/detail/edit?projectFlockId=${props.row.original.id}`}
|
||||
variant='ghost'
|
||||
color='warning'
|
||||
className='justify-start text-sm'
|
||||
>
|
||||
<Icon icon='mdi:pencil-outline' width={16} height={16} />
|
||||
Edit
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
)}
|
||||
<Button
|
||||
onClick={deleteClickHandler}
|
||||
variant='ghost'
|
||||
color='error'
|
||||
className='text-error hover:text-inherit justify-start text-sm'
|
||||
>
|
||||
<Icon
|
||||
icon='material-symbols:delete-outline-rounded'
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Delete
|
||||
</Button>
|
||||
<RequirePermission permissions='lti.production.project_flocks.delete'>
|
||||
<Button
|
||||
onClick={deleteClickHandler}
|
||||
variant='ghost'
|
||||
color='error'
|
||||
className='text-error hover:text-inherit justify-start text-sm'
|
||||
>
|
||||
<Icon
|
||||
icon='material-symbols:delete-outline-rounded'
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Delete
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -287,14 +297,16 @@ 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'>
|
||||
<Button
|
||||
color='primary'
|
||||
className='w-full sm:w-fit'
|
||||
href='/production/project-flock/add'
|
||||
>
|
||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||
Tambah
|
||||
</Button>
|
||||
<RequirePermission permissions='lti.production.project_flocks.create'>
|
||||
<Button
|
||||
color='primary'
|
||||
className='w-full sm:w-fit'
|
||||
href='/production/project-flock/add'
|
||||
>
|
||||
<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}
|
||||
|
||||
+18
-15
@@ -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,21 +485,23 @@ const ProjectFlockChickinDetail = ({
|
||||
{kandang.kandang.name}
|
||||
</span>
|
||||
</div>
|
||||
<Button
|
||||
variant='outline'
|
||||
className='py-1 text-sm'
|
||||
onClick={() => {
|
||||
handleChickinClick(kandang);
|
||||
}}
|
||||
disabled={projectFlock?.approval?.step_number === 1}
|
||||
>
|
||||
Chick In{' '}
|
||||
<Icon
|
||||
icon='mdi:arrow-top-right-thin'
|
||||
width={11}
|
||||
height={11}
|
||||
/>
|
||||
</Button>
|
||||
<RequirePermission permissions='lti.production.chickins.create'>
|
||||
<Button
|
||||
variant='outline'
|
||||
className='py-1 text-sm'
|
||||
onClick={() => {
|
||||
handleChickinClick(kandang);
|
||||
}}
|
||||
disabled={projectFlock?.approval?.step_number === 1}
|
||||
>
|
||||
Chick In{' '}
|
||||
<Icon
|
||||
icon='mdi:arrow-top-right-thin'
|
||||
width={11}
|
||||
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,27 +111,31 @@ const ProjectFlockDetail = ({
|
||||
leftIconHref='/production/project-flock'
|
||||
subtitle={`Created On ${formatDate(projectFlock.created_at, 'MMM DD, YYYY')}`}
|
||||
>
|
||||
<Link
|
||||
href={`/production/project-flock/detail/edit?projectFlockId=${projectFlock.id}`}
|
||||
className='p-0'
|
||||
>
|
||||
<Tooltip content='Edit' position='bottom'>
|
||||
<Button variant='link' className='p-0 text-neutral'>
|
||||
<Icon icon='mdi:square-edit-outline' width={20} height={20} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Link>
|
||||
<Button
|
||||
variant='link'
|
||||
className='p-0 text-error'
|
||||
onClick={() => {
|
||||
deleteModal.openModal();
|
||||
}}
|
||||
>
|
||||
<Tooltip content='Hapus' position='bottom'>
|
||||
<Icon icon='mdi:trash-can-outline' width={20} height={20} />
|
||||
</Tooltip>
|
||||
</Button>
|
||||
<RequirePermission permissions='lti.production.project_flocks.update'>
|
||||
<Link
|
||||
href={`/production/project-flock/detail/edit?projectFlockId=${projectFlock.id}`}
|
||||
className='p-0'
|
||||
>
|
||||
<Tooltip content='Edit' position='bottom'>
|
||||
<Button variant='link' className='p-0 text-neutral'>
|
||||
<Icon icon='mdi:square-edit-outline' width={20} height={20} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Link>
|
||||
</RequirePermission>
|
||||
<RequirePermission permissions='lti.production.project_flocks.delete'>
|
||||
<Button
|
||||
variant='link'
|
||||
className='p-0 text-error'
|
||||
onClick={() => {
|
||||
deleteModal.openModal();
|
||||
}}
|
||||
>
|
||||
<Tooltip content='Hapus' position='bottom'>
|
||||
<Icon icon='mdi:trash-can-outline' width={20} height={20} />
|
||||
</Tooltip>
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
</DrawerHeader>
|
||||
|
||||
{/* Informasi Umum */}
|
||||
@@ -418,38 +423,42 @@ const ProjectFlockDetail = ({
|
||||
</RadioGroup>
|
||||
</Card>
|
||||
<div className='grid grid-cols-4 gap-3'>
|
||||
<Link
|
||||
href={`/production/project-flock/chickin/add/kandang?projectFlockKandangId=${selectedKandang?.project_flock_kandang_id}&projectFlockId=${projectFlock.id}`}
|
||||
className='m-0 p-0'
|
||||
>
|
||||
<Button
|
||||
className='w-full px-2 py-1 text-sm'
|
||||
variant='outline'
|
||||
color='success'
|
||||
disabled={
|
||||
!selectedKandangId ||
|
||||
projectFlock?.approval?.step_number == 1
|
||||
}
|
||||
<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'
|
||||
>
|
||||
Chickin <Icon icon='mdi:checkbox-marked-outline' />
|
||||
</Button>
|
||||
</Link>
|
||||
<Link
|
||||
href={`/production/project-flock/closing?projectFlockId=${projectFlock.id}&projectFlockKandangId=${selectedKandang?.project_flock_kandang_id}`}
|
||||
className='m-0 p-0'
|
||||
>
|
||||
<Button
|
||||
className='w-full px-2 py-1 text-sm'
|
||||
variant='outline'
|
||||
color='error'
|
||||
disabled={
|
||||
!selectedKandangId ||
|
||||
projectFlock?.approval?.step_number == 1
|
||||
}
|
||||
<Button
|
||||
className='w-full px-2 py-1 text-sm'
|
||||
variant='outline'
|
||||
color='success'
|
||||
disabled={
|
||||
!selectedKandangId ||
|
||||
projectFlock?.approval?.step_number == 1
|
||||
}
|
||||
>
|
||||
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'
|
||||
>
|
||||
Close <Icon icon='mdi:checkbox-marked-circle-outline' />
|
||||
</Button>
|
||||
</Link>
|
||||
<Button
|
||||
className='w-full px-2 py-1 text-sm'
|
||||
variant='outline'
|
||||
color='error'
|
||||
disabled={
|
||||
!selectedKandangId ||
|
||||
projectFlock?.approval?.step_number == 1
|
||||
}
|
||||
>
|
||||
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,36 +735,40 @@ const ProjectFlockForm = ({
|
||||
)}
|
||||
{formType == 'detail' && (
|
||||
<div className='w-full flex flex-col sm:flex-row gap-2 py-4'>
|
||||
<Button
|
||||
variant='outline'
|
||||
color='success'
|
||||
onClick={() => {
|
||||
if (initialValues?.id) {
|
||||
setApprovalAction('APPROVED');
|
||||
confirmModal.openModal();
|
||||
}
|
||||
}}
|
||||
disabled={!initialValues?.id || isApprovedDisabled}
|
||||
className='w-full sm:w-fit'
|
||||
>
|
||||
<Icon icon='material-symbols:check' width={24} height={24} />
|
||||
Approve
|
||||
</Button>
|
||||
<Button
|
||||
variant='outline'
|
||||
color='error'
|
||||
onClick={() => {
|
||||
if (initialValues?.id) {
|
||||
setApprovalAction('REJECTED');
|
||||
confirmModal.openModal();
|
||||
}
|
||||
}}
|
||||
disabled={!initialValues?.id || isRejectedDisabled}
|
||||
className='w-full sm:w-fit'
|
||||
>
|
||||
<Icon icon='mdi:times' width={24} height={24} />
|
||||
Reject
|
||||
</Button>
|
||||
<RequirePermission permissions='lti.production.project_flocks.approve'>
|
||||
<Button
|
||||
variant='outline'
|
||||
color='success'
|
||||
onClick={() => {
|
||||
if (initialValues?.id) {
|
||||
setApprovalAction('APPROVED');
|
||||
confirmModal.openModal();
|
||||
}
|
||||
}}
|
||||
disabled={!initialValues?.id || isApprovedDisabled}
|
||||
className='w-full sm:w-fit'
|
||||
>
|
||||
<Icon icon='material-symbols:check' width={24} height={24} />
|
||||
Approve
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
<RequirePermission permissions='lti.production.project_flocks.approve'>
|
||||
<Button
|
||||
variant='outline'
|
||||
color='error'
|
||||
onClick={() => {
|
||||
if (initialValues?.id) {
|
||||
setApprovalAction('REJECTED');
|
||||
confirmModal.openModal();
|
||||
}
|
||||
}}
|
||||
disabled={!initialValues?.id || isRejectedDisabled}
|
||||
className='w-full sm:w-fit'
|
||||
>
|
||||
<Icon icon='mdi:times' width={24} height={24} />
|
||||
Reject
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
</div>
|
||||
)}
|
||||
<form
|
||||
@@ -1127,16 +1132,24 @@ const ProjectFlockForm = ({
|
||||
</div>
|
||||
</div> */}
|
||||
{formType !== 'detail' && (
|
||||
<Button
|
||||
type='submit'
|
||||
color='primary'
|
||||
isLoading={formik.isSubmitting}
|
||||
disabled={!formik.isValid || formik.isSubmitting}
|
||||
className='px-4 w-full'
|
||||
<RequirePermission
|
||||
permissions={
|
||||
formType == 'add'
|
||||
? 'lti.production.project_flocks.create'
|
||||
: 'lti.production.project_flocks.update'
|
||||
}
|
||||
>
|
||||
<Icon icon='mdi:plus' width={24} height={24} />
|
||||
{formType == 'add' ? 'Add Flock' : 'Update Flock'}
|
||||
</Button>
|
||||
<Button
|
||||
type='submit'
|
||||
color='primary'
|
||||
isLoading={formik.isSubmitting}
|
||||
disabled={!formik.isValid || formik.isSubmitting}
|
||||
className='px-4 w-full'
|
||||
>
|
||||
<Icon icon='mdi:plus' width={24} height={24} />
|
||||
{formType == 'add' ? 'Add Flock' : 'Update Flock'}
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
Reference in New Issue
Block a user