diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php index fb40980..02b9079 100644 --- a/app/Http/Middleware/HandleInertiaRequests.php +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -42,6 +42,7 @@ public function share(Request $request): array 'name' => config('app.name'), 'auth' => [ 'user' => $request->user()?->load('roles:id,name'), + 'permissions' => $request->user()?->getAllPermissions()->pluck('name')->values() ?? [], ], 'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true', 'unreadNotificationsCount' => fn () => $request->user() diff --git a/bootstrap/app.php b/bootstrap/app.php index bc4b18f..95a5faf 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -7,6 +7,7 @@ use Illuminate\Foundation\Configuration\Middleware; use Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets; use Illuminate\Http\Request; +use Spatie\Permission\Middleware\PermissionMiddleware; use Spatie\Permission\Middleware\RoleMiddleware; return Application::configure(basePath: dirname(__DIR__)) @@ -26,6 +27,7 @@ $middleware->alias([ 'role' => RoleMiddleware::class, + 'permission' => PermissionMiddleware::class, ]); }) ->withExceptions(function (Exceptions $exceptions): void { diff --git a/database/seeders/RolePermissionSeeder.php b/database/seeders/RolePermissionSeeder.php index d4571df..79f3ad9 100644 --- a/database/seeders/RolePermissionSeeder.php +++ b/database/seeders/RolePermissionSeeder.php @@ -13,17 +13,55 @@ public function run(): void { app()[PermissionRegistrar::class]->forgetCachedPermissions(); - $permissionNames = [ - 'view-dashboard', - 'view-academic-terms', - 'create-academic-terms', - 'update-academic-terms', - 'delete-academic-terms', + $master = [ + 'view-academic-terms', 'create-academic-terms', 'update-academic-terms', 'delete-academic-terms', 'update-academic-terms-status', + 'view-departments', 'create-departments', 'update-departments', 'delete-departments', + 'view-courses', 'create-courses', 'update-courses', 'delete-courses', + ]; + + $academicClasses = [ + 'view-materials', 'create-materials', 'update-materials', 'delete-materials', + 'view-assignments', 'create-assignments', 'update-assignments', 'delete-assignments', + 'view-assignment-submissions', 'create-assignment-submissions', 'update-assignment-submissions', 'delete-assignment-submissions', + 'view-schedules', 'create-schedules', 'update-schedules', 'delete-schedules', + 'view-attendances', 'create-attendances', 'delete-attendances', + ]; + + $manage = [ + 'view-course-classes', 'create-course-classes', 'update-course-classes', 'delete-course-classes', + 'view-course-class-enrollments', 'create-course-class-enrollments', 'delete-course-class-enrollments', + 'view-course-registrations', 'create-course-registrations', 'approve-course-registrations', 'reject-course-registrations', + 'view-announcements', 'create-announcements', 'update-announcements', 'delete-announcements', + ]; + + $finances = [ + 'view-tuition-invoices', 'create-tuition-invoices', 'update-tuition-invoices', 'delete-tuition-invoices', + 'view-tuition-payments', 'create-tuition-payments', 'update-tuition-payments', 'delete-tuition-payments', + ]; + + $services = [ + 'view-letter-requests', 'create-letter-requests', 'update-letter-requests', 'delete-letter-requests', + 'view-academic-advising-logs', 'create-academic-advising-logs', 'update-academic-advising-logs', 'delete-academic-advising-logs', + ]; + + $users = [ + 'view-lecturers', 'create-lecturers', 'update-lecturers', 'delete-lecturers', 'reset-lecturers-password', 'update-lecturers-status', 'export-lecturers', + 'view-students', 'create-students', 'update-students', 'delete-students', 'reset-students-password', 'update-students-academic-status', 'update-students-account-status', 'export-students', + 'view-administrators', 'create-administrators', 'update-administrators', 'delete-administrators', 'reset-administrators-password', 'update-administrators-status', + ]; + + $permissionNames = [ + 'view-dashboard', + ...$master, + ...$academicClasses, + ...$manage, + ...$finances, + ...$services, + ...$users, ]; - $permissions = []; foreach ($permissionNames as $name) { - $permissions[] = Permission::firstOrCreate(['name' => $name, 'guard_name' => 'web']); + Permission::firstOrCreate(['name' => $name, 'guard_name' => 'web']); } app()[PermissionRegistrar::class]->forgetCachedPermissions(); @@ -33,19 +71,30 @@ public function run(): void 'dosen' => ['view-dashboard'], 'staff-admin' => [ 'view-dashboard', - 'view-academic-terms', - 'create-academic-terms', - 'update-academic-terms', - 'delete-academic-terms', + ...$master, + ...$academicClasses, + ...$manage, + ...$services, + ...$users, + ], + 'staff-keuangan' => [ + 'view-dashboard', + ...$finances, + ], + 'kaprodi' => [ + 'view-dashboard', + 'view-academic-terms', + 'view-courses', + 'view-course-classes', + 'view-course-registrations', + 'view-students', ], - 'staff-keuangan' => ['view-dashboard'], - 'kaprodi' => ['view-dashboard', 'view-academic-terms'], 'developer' => $permissionNames, ]; foreach ($roles as $roleName => $rolePermissions) { $role = Role::firstOrCreate(['name' => $roleName, 'guard_name' => 'web']); - $role->givePermissionTo($rolePermissions); + $role->syncPermissions($rolePermissions); } } } diff --git a/resources/js/components/active-status-switch.tsx b/resources/js/components/active-status-switch.tsx index ddaf9ed..c0c1b8a 100644 --- a/resources/js/components/active-status-switch.tsx +++ b/resources/js/components/active-status-switch.tsx @@ -3,15 +3,21 @@ import { Switch } from '@/components/ui/switch'; type ActiveStatusSwitchProps = { isActive: boolean; onChange: (isActive: boolean) => void; + disabled?: boolean; }; export function ActiveStatusSwitch({ isActive, onChange, + disabled, }: ActiveStatusSwitchProps) { return (
- + {isActive ? 'Aktif' : 'Nonaktif'} diff --git a/resources/js/components/student-status-badge.tsx b/resources/js/components/student-status-badge.tsx index 8694883..89b47b2 100644 --- a/resources/js/components/student-status-badge.tsx +++ b/resources/js/components/student-status-badge.tsx @@ -13,6 +13,7 @@ type StudentStatusBadgeProps = { status: string | null | undefined; statuses: StatusOption[]; onChange: (status: string) => void; + disabled?: boolean; }; const StudentStatusVariants: Record< @@ -29,6 +30,7 @@ export function StudentStatusBadge({ status, statuses, onChange, + disabled, }: StudentStatusBadgeProps) { const label = statuses.find((option) => option.value === status)?.label ?? @@ -38,10 +40,11 @@ export function StudentStatusBadge({ return ( - + diff --git a/resources/js/hooks/use-permissions.ts b/resources/js/hooks/use-permissions.ts new file mode 100644 index 0000000..0fab45d --- /dev/null +++ b/resources/js/hooks/use-permissions.ts @@ -0,0 +1,17 @@ +import { usePage } from '@inertiajs/react'; +import type { Auth } from '@/types/auth'; + +export function usePermissions() { + const { auth } = usePage<{ auth: Auth }>().props; + const permissions = auth?.permissions ?? []; + + function hasPermission(name: string): boolean { + return permissions.includes(name); + } + + function hasAnyPermission(names: string[]): boolean { + return names.some((name) => permissions.includes(name)); + } + + return { permissions, hasPermission, hasAnyPermission }; +} diff --git a/resources/js/pages/admin/academic-classes/assignments/columns.tsx b/resources/js/pages/admin/academic-classes/assignments/columns.tsx index fda93c4..91f638b 100644 --- a/resources/js/pages/admin/academic-classes/assignments/columns.tsx +++ b/resources/js/pages/admin/academic-classes/assignments/columns.tsx @@ -10,12 +10,21 @@ export type { Assignment } from '@/types/assignment'; type CreateColumnsParams = { handleEdit: (assignment: Assignment) => void; handleDeleteClick: (assignment: Assignment) => void; + canUpdate: boolean; + canDelete: boolean; + canViewSubmissions: boolean; }; export function createAssignmentColumns( params: CreateColumnsParams, ): ColumnDef[] { - const { handleEdit, handleDeleteClick } = params; + const { + handleEdit, + handleDeleteClick, + canUpdate, + canDelete, + canViewSubmissions, + } = params; return [ { @@ -103,11 +112,13 @@ export function createAssignmentColumns( { label: 'Pengumpulan', icon: , + show: canViewSubmissions, href: submissionsIndex.url(assignment.id), }, { label: 'Edit', icon: , + show: canUpdate, onClick: () => handleEdit(assignment), }, { @@ -115,6 +126,7 @@ export function createAssignmentColumns( icon: ( ), + show: canDelete, onClick: () => handleDeleteClick(assignment), }, ]} diff --git a/resources/js/pages/admin/academic-classes/assignments/index.tsx b/resources/js/pages/admin/academic-classes/assignments/index.tsx index ce145a5..5924b90 100644 --- a/resources/js/pages/admin/academic-classes/assignments/index.tsx +++ b/resources/js/pages/admin/academic-classes/assignments/index.tsx @@ -22,6 +22,7 @@ import { SelectValue, } from '@/components/ui/select'; import { Textarea } from '@/components/ui/textarea'; +import { usePermissions } from '@/hooks/use-permissions'; import { useServerTable } from '@/hooks/use-server-table'; import { index as assignmentIndex, @@ -65,6 +66,11 @@ export default function AssignmentIndex({ const [createOpen, setCreateOpen] = useState(false); const [editing, setEditing] = useState(null); const [deleting, setDeleting] = useState(null); + const { hasPermission } = usePermissions(); + const canCreate = hasPermission('create-assignments'); + const canUpdate = hasPermission('update-assignments'); + const canDelete = hasPermission('delete-assignments'); + const canViewSubmissions = hasPermission('view-assignment-submissions'); const filterFields: FilterField[] = [ { @@ -109,6 +115,9 @@ export default function AssignmentIndex({ const columns = createAssignmentColumns({ handleEdit: (assignment) => setEditing(assignment), handleDeleteClick: (assignment) => setDeleting(assignment), + canUpdate, + canDelete, + canViewSubmissions, }); return ( @@ -141,15 +150,17 @@ export default function AssignmentIndex({ ) } actions={ - - + canCreate && ( + + + ) } /> diff --git a/resources/js/pages/admin/academic-classes/assignments/submissions.tsx b/resources/js/pages/admin/academic-classes/assignments/submissions.tsx index 5c3ea93..efd341a 100644 --- a/resources/js/pages/admin/academic-classes/assignments/submissions.tsx +++ b/resources/js/pages/admin/academic-classes/assignments/submissions.tsx @@ -24,6 +24,7 @@ import { SelectValue, } from '@/components/ui/select'; import { Textarea } from '@/components/ui/textarea'; +import { usePermissions } from '@/hooks/use-permissions'; import { index as assignmentIndex } from '@/routes/admin/academic-classes/assignments'; import { destroy, @@ -48,6 +49,10 @@ export default function SubmissionIndex({ const [createOpen, setCreateOpen] = useState(false); const [editing, setEditing] = useState(null); const [deleting, setDeleting] = useState(null); + const { hasPermission } = usePermissions(); + const canCreate = hasPermission('create-assignment-submissions'); + const canUpdate = hasPermission('update-assignment-submissions'); + const canDelete = hasPermission('delete-assignment-submissions'); function handleDelete() { if (!deleting) { @@ -137,6 +142,7 @@ export default function SubmissionIndex({ { label: 'Edit', icon: , + show: canUpdate, onClick: () => setEditing(row.original), }, { @@ -144,6 +150,7 @@ export default function SubmissionIndex({ icon: ( ), + show: canDelete, onClick: () => setDeleting(row.original), }, ]} @@ -193,13 +200,15 @@ export default function SubmissionIndex({

Daftar Pengumpulan

- + {canCreate && ( + + )}
diff --git a/resources/js/pages/admin/academic-classes/attendances/index.tsx b/resources/js/pages/admin/academic-classes/attendances/index.tsx index 525556d..fcbd6e6 100644 --- a/resources/js/pages/admin/academic-classes/attendances/index.tsx +++ b/resources/js/pages/admin/academic-classes/attendances/index.tsx @@ -23,6 +23,7 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select'; +import { usePermissions } from '@/hooks/use-permissions'; import { destroy, session } from '@/routes/admin/academic-classes/attendances'; import type { AttendanceCourseClass, @@ -41,6 +42,9 @@ function courseClassLabel(courseClass: AttendanceCourseClass): string { export default function AttendanceIndex({ sessions, courseClasses }: Props) { const [newSessionOpen, setNewSessionOpen] = useState(false); const [deleting, setDeleting] = useState(null); + const { hasPermission } = usePermissions(); + const canCreate = hasPermission('create-attendances'); + const canDelete = hasPermission('delete-attendances'); function handleDelete() { if (!deleting || deleting.meeting_number === null) { @@ -61,10 +65,12 @@ export default function AttendanceIndex({ sessions, courseClasses }: Props) { setNewSessionOpen(true)}> - - Ambil Kehadiran - + canCreate && ( + + ) } /> @@ -129,16 +135,18 @@ export default function AttendanceIndex({ sessions, courseClasses }: Props) { )} - + {canDelete && ( + + )} diff --git a/resources/js/pages/admin/academic-classes/attendances/session.tsx b/resources/js/pages/admin/academic-classes/attendances/session.tsx index 3bd62a1..2fa3dc6 100644 --- a/resources/js/pages/admin/academic-classes/attendances/session.tsx +++ b/resources/js/pages/admin/academic-classes/attendances/session.tsx @@ -7,6 +7,7 @@ import { PageHeader } from '@/components/page-header'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Label } from '@/components/ui/label'; +import { usePermissions } from '@/hooks/use-permissions'; import { index as attendanceIndex, store, @@ -45,6 +46,8 @@ export default function AttendanceSession({ ), ); const [saving, setSaving] = useState(false); + const { hasPermission } = usePermissions(); + const canSave = hasPermission('create-attendances'); function setAll(status: AttendanceStatus) { setStatuses( @@ -198,7 +201,7 @@ export default function AttendanceSession({ )} - {roster.length > 0 && ( + {roster.length > 0 && canSave && (
- + canCreate && ( + + + ) } /> diff --git a/resources/js/pages/admin/academic-classes/schedules/index.tsx b/resources/js/pages/admin/academic-classes/schedules/index.tsx index e637333..e04c140 100644 --- a/resources/js/pages/admin/academic-classes/schedules/index.tsx +++ b/resources/js/pages/admin/academic-classes/schedules/index.tsx @@ -18,6 +18,7 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select'; +import { usePermissions } from '@/hooks/use-permissions'; import { cn } from '@/lib/utils'; import { destroy, @@ -58,6 +59,10 @@ export default function ScheduleIndex({ const [createOpen, setCreateOpen] = useState(false); const [editing, setEditing] = useState(null); const [deleting, setDeleting] = useState(null); + const { hasPermission } = usePermissions(); + const canCreate = hasPermission('create-schedules'); + const canUpdate = hasPermission('update-schedules'); + const canDelete = hasPermission('delete-schedules'); function handleDelete() { if (!deleting) { @@ -89,15 +94,17 @@ export default function ScheduleIndex({ - - + canCreate && ( + + + ) } /> @@ -177,6 +184,7 @@ export default function ScheduleIndex({ icon: ( ), + show: canUpdate, onClick: () => setEditing( @@ -188,6 +196,7 @@ export default function ScheduleIndex({ icon: ( ), + show: canDelete, onClick: () => setDeleting( diff --git a/resources/js/pages/admin/finances/tuition-invoices/columns.tsx b/resources/js/pages/admin/finances/tuition-invoices/columns.tsx index 29abff7..93d839f 100644 --- a/resources/js/pages/admin/finances/tuition-invoices/columns.tsx +++ b/resources/js/pages/admin/finances/tuition-invoices/columns.tsx @@ -14,12 +14,21 @@ export type { TuitionInvoice } from '@/types/tuition-invoice'; type CreateColumnsParams = { handleEdit: (invoice: TuitionInvoice) => void; handleDeleteClick: (invoice: TuitionInvoice) => void; + canUpdate: boolean; + canDelete: boolean; + canViewPayments: boolean; }; export function createTuitionInvoiceColumns( params: CreateColumnsParams, ): ColumnDef[] { - const { handleEdit, handleDeleteClick } = params; + const { + handleEdit, + handleDeleteClick, + canUpdate, + canDelete, + canViewPayments, + } = params; return [ { @@ -133,11 +142,13 @@ export function createTuitionInvoiceColumns( { label: 'Pembayaran', icon: , + show: canViewPayments, href: paymentsIndex.url(invoice.id), }, { label: 'Edit', icon: , + show: canUpdate, onClick: () => handleEdit(invoice), }, { @@ -145,6 +156,7 @@ export function createTuitionInvoiceColumns( icon: ( ), + show: canDelete, onClick: () => handleDeleteClick(invoice), }, ]} diff --git a/resources/js/pages/admin/finances/tuition-invoices/index.tsx b/resources/js/pages/admin/finances/tuition-invoices/index.tsx index 34a51e0..4ae99fd 100644 --- a/resources/js/pages/admin/finances/tuition-invoices/index.tsx +++ b/resources/js/pages/admin/finances/tuition-invoices/index.tsx @@ -47,6 +47,7 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select'; +import { usePermissions } from '@/hooks/use-permissions'; import { useServerTable } from '@/hooks/use-server-table'; import { formatRupiah } from '@/lib/currency'; import { @@ -115,6 +116,11 @@ export default function TuitionInvoiceIndex({ const [createOpen, setCreateOpen] = useState(false); const [editing, setEditing] = useState(null); const [deleting, setDeleting] = useState(null); + const { hasPermission } = usePermissions(); + const canCreate = hasPermission('create-tuition-invoices'); + const canUpdate = hasPermission('update-tuition-invoices'); + const canDelete = hasPermission('delete-tuition-invoices'); + const canViewPayments = hasPermission('view-tuition-payments'); const filterFields: FilterField[] = [ { @@ -175,6 +181,9 @@ export default function TuitionInvoiceIndex({ const columns = createTuitionInvoiceColumns({ handleEdit: (invoice) => setEditing(invoice), handleDeleteClick: (invoice) => setDeleting(invoice), + canUpdate, + canDelete, + canViewPayments, }); return ( @@ -192,15 +201,17 @@ export default function TuitionInvoiceIndex({ ) } actions={ - - + canCreate && ( + + + ) } /> diff --git a/resources/js/pages/admin/finances/tuition-invoices/payments.tsx b/resources/js/pages/admin/finances/tuition-invoices/payments.tsx index f318091..476efbf 100644 --- a/resources/js/pages/admin/finances/tuition-invoices/payments.tsx +++ b/resources/js/pages/admin/finances/tuition-invoices/payments.tsx @@ -18,6 +18,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Label } from '@/components/ui/label'; import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; import { Textarea } from '@/components/ui/textarea'; +import { usePermissions } from '@/hooks/use-permissions'; import { formatRupiah } from '@/lib/currency'; import { index as tuitionInvoiceIndex } from '@/routes/admin/finances/tuition-invoices'; import { @@ -43,6 +44,10 @@ export default function TuitionPaymentIndex({ invoice, payments }: Props) { const [createOpen, setCreateOpen] = useState(false); const [editing, setEditing] = useState(null); const [deleting, setDeleting] = useState(null); + const { hasPermission } = usePermissions(); + const canCreate = hasPermission('create-tuition-payments'); + const canUpdate = hasPermission('update-tuition-payments'); + const canDelete = hasPermission('delete-tuition-payments'); function handleDelete() { if (!deleting) { @@ -156,6 +161,7 @@ export default function TuitionPaymentIndex({ invoice, payments }: Props) { { label: 'Edit', icon: , + show: canUpdate, onClick: () => setEditing(row.original), }, { @@ -163,6 +169,7 @@ export default function TuitionPaymentIndex({ invoice, payments }: Props) { icon: ( ), + show: canDelete, onClick: () => setDeleting(row.original), }, ]} @@ -231,7 +238,7 @@ export default function TuitionPaymentIndex({ invoice, payments }: Props) {

Riwayat Pembayaran

- {!isFullyPaid && ( + {!isFullyPaid && canCreate && ( - + canCreate && ( + + + ) } /> diff --git a/resources/js/pages/admin/manage/course-classes/columns.tsx b/resources/js/pages/admin/manage/course-classes/columns.tsx index ea96d9c..fba81e6 100644 --- a/resources/js/pages/admin/manage/course-classes/columns.tsx +++ b/resources/js/pages/admin/manage/course-classes/columns.tsx @@ -12,12 +12,21 @@ export type { CourseClass } from '@/types/course-class'; type CreateColumnsParams = { handleEdit: (courseClass: CourseClass) => void; handleDeleteClick: (courseClass: CourseClass) => void; + canUpdate: boolean; + canDelete: boolean; + canViewEnrollments: boolean; }; export function createCourseClassColumns( params: CreateColumnsParams, ): ColumnDef[] { - const { handleEdit, handleDeleteClick } = params; + const { + handleEdit, + handleDeleteClick, + canUpdate, + canDelete, + canViewEnrollments, + } = params; return [ { @@ -97,11 +106,13 @@ export function createCourseClassColumns( { label: 'Peserta', icon: , + show: canViewEnrollments, href: enrollmentsIndex.url(courseClass.id), }, { label: 'Edit', icon: , + show: canUpdate, onClick: () => handleEdit(courseClass), }, { @@ -109,6 +120,7 @@ export function createCourseClassColumns( icon: ( ), + show: canDelete, onClick: () => handleDeleteClick(courseClass), }, ]} diff --git a/resources/js/pages/admin/manage/course-classes/enrollments.tsx b/resources/js/pages/admin/manage/course-classes/enrollments.tsx index 6a45aa8..12d4640 100644 --- a/resources/js/pages/admin/manage/course-classes/enrollments.tsx +++ b/resources/js/pages/admin/manage/course-classes/enrollments.tsx @@ -13,6 +13,7 @@ import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Checkbox } from '@/components/ui/checkbox'; import { Label } from '@/components/ui/label'; +import { usePermissions } from '@/hooks/use-permissions'; import { index as courseClassIndex } from '@/routes/admin/manage/course-classes'; import { destroy, @@ -39,6 +40,9 @@ export default function ClassEnrollmentIndex({ }: Props) { const [createOpen, setCreateOpen] = useState(false); const [deleting, setDeleting] = useState(null); + const { hasPermission } = usePermissions(); + const canCreate = hasPermission('create-course-class-enrollments'); + const canDelete = hasPermission('delete-course-class-enrollments'); function handleDelete() { if (!deleting) { @@ -93,6 +97,7 @@ export default function ClassEnrollmentIndex({ icon: ( ), + show: canDelete, onClick: () => setDeleting(row.original), }, ]} @@ -152,10 +157,12 @@ export default function ClassEnrollmentIndex({

Daftar Mahasiswa

- + {canCreate && ( + + )}
diff --git a/resources/js/pages/admin/manage/course-classes/index.tsx b/resources/js/pages/admin/manage/course-classes/index.tsx index 564c896..76fae96 100644 --- a/resources/js/pages/admin/manage/course-classes/index.tsx +++ b/resources/js/pages/admin/manage/course-classes/index.tsx @@ -26,6 +26,7 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select'; +import { usePermissions } from '@/hooks/use-permissions'; import { useServerTable } from '@/hooks/use-server-table'; import { index as courseClassIndex, @@ -76,6 +77,11 @@ export default function CourseClassIndex({ const [createOpen, setCreateOpen] = useState(false); const [editing, setEditing] = useState(null); const [deleting, setDeleting] = useState(null); + const { hasPermission } = usePermissions(); + const canCreate = hasPermission('create-course-classes'); + const canUpdate = hasPermission('update-course-classes'); + const canDelete = hasPermission('delete-course-classes'); + const canViewEnrollments = hasPermission('view-course-class-enrollments'); const filterFields: FilterField[] = [ { @@ -128,6 +134,9 @@ export default function CourseClassIndex({ const columns = createCourseClassColumns({ handleEdit: (courseClass) => setEditing(courseClass), handleDeleteClick: (courseClass) => setDeleting(courseClass), + canUpdate, + canDelete, + canViewEnrollments, }); return ( @@ -160,15 +169,17 @@ export default function CourseClassIndex({ ) } actions={ - - + canCreate && ( + + + ) } /> diff --git a/resources/js/pages/admin/manage/course-registrations/columns.tsx b/resources/js/pages/admin/manage/course-registrations/columns.tsx index db7b39d..b4627d3 100644 --- a/resources/js/pages/admin/manage/course-registrations/columns.tsx +++ b/resources/js/pages/admin/manage/course-registrations/columns.tsx @@ -11,12 +11,14 @@ export type { CourseRegistrationSubmission } from '@/types/course-registration'; type CreateColumnsParams = { handleApprove: (submission: CourseRegistrationSubmission) => void; handleRejectClick: (submission: CourseRegistrationSubmission) => void; + canApprove: boolean; + canReject: boolean; }; export function createCourseRegistrationColumns( params: CreateColumnsParams, ): ColumnDef[] { - const { handleApprove, handleRejectClick } = params; + const { handleApprove, handleRejectClick, canApprove, canReject } = params; return [ { @@ -105,14 +107,18 @@ export function createCourseRegistrationColumns( label: 'Setujui', icon: , iconClassName: 'text-primary', - show: submission.status === 'submitted', + show: + submission.status === 'submitted' && + canApprove, onClick: () => handleApprove(submission), }, { label: 'Tolak', icon: , iconClassName: 'text-destructive', - show: submission.status === 'submitted', + show: + submission.status === 'submitted' && + canReject, onClick: () => handleRejectClick(submission), }, ]} diff --git a/resources/js/pages/admin/manage/course-registrations/index.tsx b/resources/js/pages/admin/manage/course-registrations/index.tsx index 00e62fd..afa1405 100644 --- a/resources/js/pages/admin/manage/course-registrations/index.tsx +++ b/resources/js/pages/admin/manage/course-registrations/index.tsx @@ -27,6 +27,7 @@ import { SelectValue, } from '@/components/ui/select'; import { Textarea } from '@/components/ui/textarea'; +import { usePermissions } from '@/hooks/use-permissions'; import { useServerTable } from '@/hooks/use-server-table'; import { index as courseRegistrationIndex, @@ -92,6 +93,10 @@ export default function CourseRegistrationIndex({ const [createOpen, setCreateOpen] = useState(false); const [rejecting, setRejecting] = useState(null); + const { hasPermission } = usePermissions(); + const canCreate = hasPermission('create-course-registrations'); + const canApprove = hasPermission('approve-course-registrations'); + const canReject = hasPermission('reject-course-registrations'); const filterFields: FilterField[] = [ { @@ -138,6 +143,8 @@ export default function CourseRegistrationIndex({ const columns = createCourseRegistrationColumns({ handleApprove, handleRejectClick: (submission) => setRejecting(submission), + canApprove, + canReject, }); return ( @@ -155,15 +162,17 @@ export default function CourseRegistrationIndex({ ) } actions={ - - + canCreate && ( + + + ) } /> diff --git a/resources/js/pages/admin/master/academic-terms/columns.tsx b/resources/js/pages/admin/master/academic-terms/columns.tsx index 8697413..7ef8cdf 100644 --- a/resources/js/pages/admin/master/academic-terms/columns.tsx +++ b/resources/js/pages/admin/master/academic-terms/columns.tsx @@ -12,12 +12,22 @@ type CreateColumnsParams = { handleEdit: (academicTerm: AcademicTerm) => void; handleDeleteClick: (academicTerm: AcademicTerm) => void; handleStatusChange: (academicTerm: AcademicTerm, isActive: boolean) => void; + canUpdate: boolean; + canDelete: boolean; + canUpdateStatus: boolean; }; export function createAcademicTermColumns( params: CreateColumnsParams, ): ColumnDef[] { - const { handleEdit, handleDeleteClick, handleStatusChange } = params; + const { + handleEdit, + handleDeleteClick, + handleStatusChange, + canUpdate, + canDelete, + canUpdateStatus, + } = params; return [ { @@ -73,6 +83,7 @@ export function createAcademicTermColumns( onChange={(isActive) => handleStatusChange(academicTerm, isActive) } + disabled={!canUpdateStatus} />
); @@ -94,6 +105,7 @@ export function createAcademicTermColumns( { label: 'Edit', icon: , + show: canUpdate, onClick: () => handleEdit(academicTerm), }, { @@ -101,6 +113,7 @@ export function createAcademicTermColumns( icon: ( ), + show: canDelete, onClick: () => handleDeleteClick(academicTerm), }, ]} diff --git a/resources/js/pages/admin/master/academic-terms/index.tsx b/resources/js/pages/admin/master/academic-terms/index.tsx index 2e1ae8c..2697870 100644 --- a/resources/js/pages/admin/master/academic-terms/index.tsx +++ b/resources/js/pages/admin/master/academic-terms/index.tsx @@ -15,6 +15,7 @@ import { Checkbox } from '@/components/ui/checkbox'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; +import { usePermissions } from '@/hooks/use-permissions'; import { useServerTable } from '@/hooks/use-server-table'; import { index as academicTermIndex, @@ -69,6 +70,11 @@ export default function AcademicTermIndex({ const [createOpen, setCreateOpen] = useState(false); const [editing, setEditing] = useState(null); const [deleting, setDeleting] = useState(null); + const { hasPermission } = usePermissions(); + const canCreate = hasPermission('create-academic-terms'); + const canUpdate = hasPermission('update-academic-terms'); + const canDelete = hasPermission('delete-academic-terms'); + const canUpdateStatus = hasPermission('update-academic-terms-status'); const pagination: PaginationState = { current_page: academicTerms.current_page, @@ -111,6 +117,9 @@ export default function AcademicTermIndex({ handleEdit: (academicTerm) => setEditing(academicTerm), handleDeleteClick: (academicTerm) => setDeleting(academicTerm), handleStatusChange, + canUpdate, + canDelete, + canUpdateStatus, }); return ( @@ -143,15 +152,17 @@ export default function AcademicTermIndex({ ) } actions={ - - + canCreate && ( + + + ) } /> diff --git a/resources/js/pages/admin/master/courses/columns.tsx b/resources/js/pages/admin/master/courses/columns.tsx index 4cedae4..77ffc44 100644 --- a/resources/js/pages/admin/master/courses/columns.tsx +++ b/resources/js/pages/admin/master/courses/columns.tsx @@ -9,12 +9,14 @@ export type { Course } from '@/types/course'; type CreateColumnsParams = { handleEdit: (course: Course) => void; handleDeleteClick: (course: Course) => void; + canUpdate: boolean; + canDelete: boolean; }; export function createCourseColumns( params: CreateColumnsParams, ): ColumnDef[] { - const { handleEdit, handleDeleteClick } = params; + const { handleEdit, handleDeleteClick, canUpdate, canDelete } = params; return [ { @@ -87,6 +89,7 @@ export function createCourseColumns( { label: 'Edit', icon: , + show: canUpdate, onClick: () => handleEdit(course), }, { @@ -94,6 +97,7 @@ export function createCourseColumns( icon: ( ), + show: canDelete, onClick: () => handleDeleteClick(course), }, ]} diff --git a/resources/js/pages/admin/master/courses/index.tsx b/resources/js/pages/admin/master/courses/index.tsx index c5b07c0..df764a9 100644 --- a/resources/js/pages/admin/master/courses/index.tsx +++ b/resources/js/pages/admin/master/courses/index.tsx @@ -19,6 +19,7 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select'; +import { usePermissions } from '@/hooks/use-permissions'; import { useServerTable } from '@/hooks/use-server-table'; import { index as courseIndex, @@ -59,6 +60,10 @@ export default function CourseIndex({ const [createOpen, setCreateOpen] = useState(false); const [editing, setEditing] = useState(null); const [deleting, setDeleting] = useState(null); + const { hasPermission } = usePermissions(); + const canCreate = hasPermission('create-courses'); + const canUpdate = hasPermission('update-courses'); + const canDelete = hasPermission('delete-courses'); const filterFields: FilterField[] = [ { @@ -111,6 +116,8 @@ export default function CourseIndex({ const columns = createCourseColumns({ handleEdit: (course) => setEditing(course), handleDeleteClick: (course) => setDeleting(course), + canUpdate, + canDelete, }); return ( @@ -143,15 +150,17 @@ export default function CourseIndex({ ) } actions={ - - + canCreate && ( + + + ) } /> diff --git a/resources/js/pages/admin/master/departments/columns.tsx b/resources/js/pages/admin/master/departments/columns.tsx index 2b72cae..ce1babf 100644 --- a/resources/js/pages/admin/master/departments/columns.tsx +++ b/resources/js/pages/admin/master/departments/columns.tsx @@ -9,12 +9,14 @@ export type { Department } from '@/types/department'; type CreateColumnsParams = { handleEdit: (department: Department) => void; handleDeleteClick: (department: Department) => void; + canUpdate: boolean; + canDelete: boolean; }; export function createDepartmentColumns( params: CreateColumnsParams, ): ColumnDef[] { - const { handleEdit, handleDeleteClick } = params; + const { handleEdit, handleDeleteClick, canUpdate, canDelete } = params; return [ { @@ -69,6 +71,7 @@ export function createDepartmentColumns( { label: 'Edit', icon: , + show: canUpdate, onClick: () => handleEdit(department), }, { @@ -76,6 +79,7 @@ export function createDepartmentColumns( icon: ( ), + show: canDelete, onClick: () => handleDeleteClick(department), }, ]} diff --git a/resources/js/pages/admin/master/departments/index.tsx b/resources/js/pages/admin/master/departments/index.tsx index e43e44b..362532b 100644 --- a/resources/js/pages/admin/master/departments/index.tsx +++ b/resources/js/pages/admin/master/departments/index.tsx @@ -17,6 +17,7 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select'; +import { usePermissions } from '@/hooks/use-permissions'; import { useServerTable } from '@/hooks/use-server-table'; import { index as departmentIndex, @@ -43,6 +44,10 @@ export default function DepartmentIndex({ departments, highlight }: Props) { const [createOpen, setCreateOpen] = useState(false); const [editing, setEditing] = useState(null); const [deleting, setDeleting] = useState(null); + const { hasPermission } = usePermissions(); + const canCreate = hasPermission('create-departments'); + const canUpdate = hasPermission('update-departments'); + const canDelete = hasPermission('delete-departments'); const pagination: PaginationState = { current_page: departments.current_page, @@ -74,6 +79,8 @@ export default function DepartmentIndex({ departments, highlight }: Props) { const columns = createDepartmentColumns({ handleEdit: (department) => setEditing(department), handleDeleteClick: (department) => setDeleting(department), + canUpdate, + canDelete, }); return ( @@ -106,15 +113,17 @@ export default function DepartmentIndex({ departments, highlight }: Props) { ) } actions={ - - + canCreate && ( + + + ) } /> diff --git a/resources/js/pages/admin/services/academic-advising-logs/columns.tsx b/resources/js/pages/admin/services/academic-advising-logs/columns.tsx index 36c0edf..db1de0e 100644 --- a/resources/js/pages/admin/services/academic-advising-logs/columns.tsx +++ b/resources/js/pages/admin/services/academic-advising-logs/columns.tsx @@ -9,12 +9,14 @@ export type { AcademicAdvisingLog } from '@/types/academic-advising-log'; type CreateColumnsParams = { handleEdit: (log: AcademicAdvisingLog) => void; handleDeleteClick: (log: AcademicAdvisingLog) => void; + canUpdate: boolean; + canDelete: boolean; }; export function createAcademicAdvisingLogColumns( params: CreateColumnsParams, ): ColumnDef[] { - const { handleEdit, handleDeleteClick } = params; + const { handleEdit, handleDeleteClick, canUpdate, canDelete } = params; return [ { @@ -83,6 +85,7 @@ export function createAcademicAdvisingLogColumns( { label: 'Edit', icon: , + show: canUpdate, onClick: () => handleEdit(row.original), }, { @@ -90,6 +93,7 @@ export function createAcademicAdvisingLogColumns( icon: ( ), + show: canDelete, onClick: () => handleDeleteClick(row.original), }, ]} diff --git a/resources/js/pages/admin/services/academic-advising-logs/index.tsx b/resources/js/pages/admin/services/academic-advising-logs/index.tsx index 88d479d..427ef61 100644 --- a/resources/js/pages/admin/services/academic-advising-logs/index.tsx +++ b/resources/js/pages/admin/services/academic-advising-logs/index.tsx @@ -21,6 +21,7 @@ import { SelectValue, } from '@/components/ui/select'; import { Textarea } from '@/components/ui/textarea'; +import { usePermissions } from '@/hooks/use-permissions'; import { useServerTable } from '@/hooks/use-server-table'; import { index as academicAdvisingLogIndex, @@ -69,6 +70,10 @@ export default function AcademicAdvisingLogIndex({ const [createOpen, setCreateOpen] = useState(false); const [editing, setEditing] = useState(null); const [deleting, setDeleting] = useState(null); + const { hasPermission } = usePermissions(); + const canCreate = hasPermission('create-academic-advising-logs'); + const canUpdate = hasPermission('update-academic-advising-logs'); + const canDelete = hasPermission('delete-academic-advising-logs'); const filterFields: FilterField[] = [ { @@ -113,6 +118,8 @@ export default function AcademicAdvisingLogIndex({ const columns = createAcademicAdvisingLogColumns({ handleEdit: (log) => setEditing(log), handleDeleteClick: (log) => setDeleting(log), + canUpdate, + canDelete, }); return ( @@ -130,15 +137,17 @@ export default function AcademicAdvisingLogIndex({ ) } actions={ - - + canCreate && ( + + + ) } /> diff --git a/resources/js/pages/admin/services/letter-requests/columns.tsx b/resources/js/pages/admin/services/letter-requests/columns.tsx index 9b63224..d0745f0 100644 --- a/resources/js/pages/admin/services/letter-requests/columns.tsx +++ b/resources/js/pages/admin/services/letter-requests/columns.tsx @@ -11,12 +11,14 @@ export type { LetterRequest } from '@/types/letter-request'; type CreateColumnsParams = { handleEdit: (letterRequest: LetterRequest) => void; handleDeleteClick: (letterRequest: LetterRequest) => void; + canUpdate: boolean; + canDelete: boolean; }; export function createLetterRequestColumns( params: CreateColumnsParams, ): ColumnDef[] { - const { handleEdit, handleDeleteClick } = params; + const { handleEdit, handleDeleteClick, canUpdate, canDelete } = params; return [ { @@ -143,6 +145,7 @@ export function createLetterRequestColumns( { label: 'Edit', icon: , + show: canUpdate, onClick: () => handleEdit(row.original), }, { @@ -150,6 +153,7 @@ export function createLetterRequestColumns( icon: ( ), + show: canDelete, onClick: () => handleDeleteClick(row.original), }, ]} diff --git a/resources/js/pages/admin/services/letter-requests/index.tsx b/resources/js/pages/admin/services/letter-requests/index.tsx index 6c56f90..0e925a1 100644 --- a/resources/js/pages/admin/services/letter-requests/index.tsx +++ b/resources/js/pages/admin/services/letter-requests/index.tsx @@ -21,6 +21,7 @@ import { SelectValue, } from '@/components/ui/select'; import { Textarea } from '@/components/ui/textarea'; +import { usePermissions } from '@/hooks/use-permissions'; import { useServerTable } from '@/hooks/use-server-table'; import { index as letterRequestIndex, @@ -63,6 +64,10 @@ export default function LetterRequestIndex({ const [createOpen, setCreateOpen] = useState(false); const [editing, setEditing] = useState(null); const [deleting, setDeleting] = useState(null); + const { hasPermission } = usePermissions(); + const canCreate = hasPermission('create-letter-requests'); + const canUpdate = hasPermission('update-letter-requests'); + const canDelete = hasPermission('delete-letter-requests'); const filterFields: FilterField[] = [ { @@ -107,6 +112,8 @@ export default function LetterRequestIndex({ const columns = createLetterRequestColumns({ handleEdit: (letterRequest) => setEditing(letterRequest), handleDeleteClick: (letterRequest) => setDeleting(letterRequest), + canUpdate, + canDelete, }); return ( @@ -124,15 +131,17 @@ export default function LetterRequestIndex({ ) } actions={ - - + canCreate && ( + + + ) } /> diff --git a/resources/js/pages/admin/users/administrators/columns.tsx b/resources/js/pages/admin/users/administrators/columns.tsx index 54da5e5..881f3ba 100644 --- a/resources/js/pages/admin/users/administrators/columns.tsx +++ b/resources/js/pages/admin/users/administrators/columns.tsx @@ -18,6 +18,10 @@ type CreateColumnsParams = { handleDeleteClick: (admin: Administrator) => void; handleResetPassword: (admin: Administrator) => void; handleUserStatusChange: (admin: Administrator, isActive: boolean) => void; + canUpdate: boolean; + canResetPassword: boolean; + canDelete: boolean; + canUpdateStatus: boolean; }; export function createAdministratorColumns( @@ -28,6 +32,10 @@ export function createAdministratorColumns( handleDeleteClick, handleResetPassword, handleUserStatusChange, + canUpdate, + canResetPassword, + canDelete, + canUpdateStatus, } = params; return [ @@ -74,6 +82,7 @@ export function createAdministratorColumns( onChange={(isActive) => handleUserStatusChange(row.original, isActive) } + disabled={!canUpdateStatus} /> ), }, @@ -93,11 +102,13 @@ export function createAdministratorColumns( { label: 'Edit', icon: , + show: canUpdate, onClick: () => handleEdit(admin), }, { label: 'Reset Kata Sandi', icon: , + show: canResetPassword, onClick: () => handleResetPassword(admin), }, { @@ -105,6 +116,7 @@ export function createAdministratorColumns( icon: ( ), + show: canDelete, onClick: () => handleDeleteClick(admin), }, ]} diff --git a/resources/js/pages/admin/users/administrators/index.tsx b/resources/js/pages/admin/users/administrators/index.tsx index d8ec5f1..ad23fe7 100644 --- a/resources/js/pages/admin/users/administrators/index.tsx +++ b/resources/js/pages/admin/users/administrators/index.tsx @@ -9,6 +9,7 @@ import type { FilterField } from '@/components/filter-dialog'; import { FilterDialog } from '@/components/filter-dialog'; import { PageHeader } from '@/components/page-header'; import { Button } from '@/components/ui/button'; +import { usePermissions } from '@/hooks/use-permissions'; import { useServerTable } from '@/hooks/use-server-table'; import { index as administratorsIndex, @@ -48,6 +49,8 @@ const filterFields: FilterField[] = [ export default function AdministratorIndex({ administrators, filters }: Props) { const [deleting, setDeleting] = useState(null); const [resetting, setResetting] = useState(null); + const { hasPermission } = usePermissions(); + const canCreate = hasPermission('create-administrators'); const pagination: PaginationState = { current_page: administrators.current_page, @@ -107,6 +110,10 @@ export default function AdministratorIndex({ administrators, filters }: Props) { handleDeleteClick: (admin) => setDeleting(admin), handleResetPassword: (admin) => setResetting(admin), handleUserStatusChange, + canUpdate: hasPermission('update-administrators'), + canResetPassword: hasPermission('reset-administrators-password'), + canDelete: hasPermission('delete-administrators'), + canUpdateStatus: hasPermission('update-administrators-status'), }); return ( @@ -117,12 +124,14 @@ export default function AdministratorIndex({ administrators, filters }: Props) { - - - Tambah - - + canCreate && ( + + ) } /> diff --git a/resources/js/pages/admin/users/lecturers/columns.tsx b/resources/js/pages/admin/users/lecturers/columns.tsx index b82f6b2..6446291 100644 --- a/resources/js/pages/admin/users/lecturers/columns.tsx +++ b/resources/js/pages/admin/users/lecturers/columns.tsx @@ -21,6 +21,10 @@ type CreateColumnsParams = { handleDeleteClick: (lecturer: Lecturer) => void; handleResetPassword: (lecturer: Lecturer) => void; handleUserStatusChange: (lecturer: Lecturer, isActive: boolean) => void; + canUpdate: boolean; + canDelete: boolean; + canResetPassword: boolean; + canUpdateStatus: boolean; }; export function createLecturerColumns( @@ -31,6 +35,10 @@ export function createLecturerColumns( handleDeleteClick, handleResetPassword, handleUserStatusChange, + canUpdate, + canDelete, + canResetPassword, + canUpdateStatus, } = params; return [ @@ -92,6 +100,7 @@ export function createLecturerColumns( onChange={(isActive) => handleUserStatusChange(row.original, isActive) } + disabled={!canUpdateStatus} /> ), }, @@ -111,11 +120,13 @@ export function createLecturerColumns( { label: 'Edit', icon: , + show: canUpdate, onClick: () => handleEdit(lecturer), }, { label: 'Reset Kata Sandi', icon: , + show: canResetPassword, onClick: () => handleResetPassword(lecturer), }, { @@ -123,6 +134,7 @@ export function createLecturerColumns( icon: ( ), + show: canDelete, onClick: () => handleDeleteClick(lecturer), }, ]} diff --git a/resources/js/pages/admin/users/lecturers/index.tsx b/resources/js/pages/admin/users/lecturers/index.tsx index eebdda2..ad3008a 100644 --- a/resources/js/pages/admin/users/lecturers/index.tsx +++ b/resources/js/pages/admin/users/lecturers/index.tsx @@ -9,6 +9,7 @@ import type { FilterField } from '@/components/filter-dialog'; import { FilterDialog } from '@/components/filter-dialog'; import { PageHeader } from '@/components/page-header'; import { Button } from '@/components/ui/button'; +import { usePermissions } from '@/hooks/use-permissions'; import { useServerTable } from '@/hooks/use-server-table'; import { index as lecturersIndex, @@ -47,6 +48,13 @@ export default function LecturerIndex({ }: Props) { const [deleting, setDeleting] = useState(null); const [resetting, setResetting] = useState(null); + const { hasPermission } = usePermissions(); + const canCreate = hasPermission('create-lecturers'); + const canUpdate = hasPermission('update-lecturers'); + const canDelete = hasPermission('delete-lecturers'); + const canResetPassword = hasPermission('reset-lecturers-password'); + const canUpdateStatus = hasPermission('update-lecturers-status'); + const canExport = hasPermission('export-lecturers'); const filterFields: FilterField[] = [ { @@ -125,6 +133,10 @@ export default function LecturerIndex({ handleDeleteClick: (lecturer) => setDeleting(lecturer), handleResetPassword: (lecturer) => setResetting(lecturer), handleUserStatusChange, + canUpdate, + canDelete, + canResetPassword, + canUpdateStatus, }); return ( @@ -136,22 +148,26 @@ export default function LecturerIndex({ title="Dosen" actions={
- - + {canExport && ( + + )} + {canCreate && ( + + )}
} /> diff --git a/resources/js/pages/admin/users/students/columns.tsx b/resources/js/pages/admin/users/students/columns.tsx index 631c7e4..467581e 100644 --- a/resources/js/pages/admin/users/students/columns.tsx +++ b/resources/js/pages/admin/users/students/columns.tsx @@ -29,6 +29,11 @@ type CreateColumnsParams = { handleStatusChange: (student: Student, status: string) => void; handleUserStatusChange: (student: Student, isActive: boolean) => void; statuses: StatusOption[]; + canUpdate: boolean; + canDelete: boolean; + canResetPassword: boolean; + canUpdateAcademicStatus: boolean; + canUpdateAccountStatus: boolean; }; export function createStudentColumns( @@ -41,6 +46,11 @@ export function createStudentColumns( handleStatusChange, handleUserStatusChange, statuses, + canUpdate, + canDelete, + canResetPassword, + canUpdateAcademicStatus, + canUpdateAccountStatus, } = params; return [ @@ -93,6 +103,7 @@ export function createStudentColumns( onChange={(status) => handleStatusChange(row.original, status) } + disabled={!canUpdateAcademicStatus} /> ), }, @@ -117,6 +128,7 @@ export function createStudentColumns( onChange={(isActive) => handleUserStatusChange(row.original, isActive) } + disabled={!canUpdateAccountStatus} /> ), }, @@ -136,11 +148,13 @@ export function createStudentColumns( { label: 'Edit', icon: , + show: canUpdate, onClick: () => handleEdit(student), }, { label: 'Reset Kata Sandi', icon: , + show: canResetPassword, onClick: () => handleResetPassword(student), }, { @@ -148,6 +162,7 @@ export function createStudentColumns( icon: ( ), + show: canDelete, onClick: () => handleDeleteClick(student), }, ]} diff --git a/resources/js/pages/admin/users/students/index.tsx b/resources/js/pages/admin/users/students/index.tsx index 252471b..e77d6a1 100644 --- a/resources/js/pages/admin/users/students/index.tsx +++ b/resources/js/pages/admin/users/students/index.tsx @@ -10,6 +10,7 @@ import { FilterDialog } from '@/components/filter-dialog'; import { PageHeader } from '@/components/page-header'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Button } from '@/components/ui/button'; +import { usePermissions } from '@/hooks/use-permissions'; import { useServerTable } from '@/hooks/use-server-table'; import { create, @@ -56,6 +57,18 @@ export default function StudentIndex({ }: Props) { const [deleting, setDeleting] = useState(null); const [resetting, setResetting] = useState(null); + const { hasPermission } = usePermissions(); + const canCreate = hasPermission('create-students'); + const canUpdate = hasPermission('update-students'); + const canDelete = hasPermission('delete-students'); + const canResetPassword = hasPermission('reset-students-password'); + const canUpdateAcademicStatus = hasPermission( + 'update-students-academic-status', + ); + const canUpdateAccountStatus = hasPermission( + 'update-students-account-status', + ); + const canExport = hasPermission('export-students'); const filterFields: FilterField[] = [ { @@ -157,6 +170,11 @@ export default function StudentIndex({ handleStatusChange, handleUserStatusChange, statuses, + canUpdate, + canDelete, + canResetPassword, + canUpdateAcademicStatus, + canUpdateAccountStatus, }); return ( @@ -168,22 +186,26 @@ export default function StudentIndex({ title="Mahasiswa" actions={
- - + {canExport && ( + + )} + {canCreate && ( + + )}
} /> diff --git a/resources/js/types/auth.ts b/resources/js/types/auth.ts index 716f0cc..2f78729 100644 --- a/resources/js/types/auth.ts +++ b/resources/js/types/auth.ts @@ -14,4 +14,5 @@ export type User = { export type Auth = { user: User; + permissions?: string[]; }; diff --git a/routes/admin.php b/routes/admin.php index fd292e5..76107e0 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -26,62 +26,105 @@ Route::middleware(['auth', 'verified'])->group(function () { Route::prefix('admin/master')->name('admin.master.')->group(function () { - Route::resource('academic-terms', AcademicTermController::class)->except(['create', 'edit', 'show']); - Route::patch('academic-terms/{academic_term}/status', [AcademicTermController::class, 'updateStatus'])->name('academic-terms.update_status'); - Route::resource('departments', DepartmentController::class)->except(['create', 'edit', 'show']); - Route::resource('courses', CourseController::class)->except(['create', 'edit', 'show']); + Route::resource('academic-terms', AcademicTermController::class) + ->except(['create', 'edit', 'show']) + ->middlewareFor(['index'], 'permission:view-academic-terms') + ->middlewareFor(['store'], 'permission:create-academic-terms') + ->middlewareFor(['update'], 'permission:update-academic-terms') + ->middlewareFor(['destroy'], 'permission:delete-academic-terms'); + Route::patch('academic-terms/{academic_term}/status', [AcademicTermController::class, 'updateStatus'])->name('academic-terms.update_status')->middleware('permission:update-academic-terms-status'); + + Route::resource('departments', DepartmentController::class) + ->except(['create', 'edit', 'show']) + ->middlewareFor(['index'], 'permission:view-departments') + ->middlewareFor(['store'], 'permission:create-departments') + ->middlewareFor(['update'], 'permission:update-departments') + ->middlewareFor(['destroy'], 'permission:delete-departments'); + + Route::resource('courses', CourseController::class) + ->except(['create', 'edit', 'show']) + ->middlewareFor(['index'], 'permission:view-courses') + ->middlewareFor(['store'], 'permission:create-courses') + ->middlewareFor(['update'], 'permission:update-courses') + ->middlewareFor(['destroy'], 'permission:delete-courses'); }); Route::prefix('admin/academic-classes')->name('admin.academic-classes.')->group(function () { - Route::resource('materials', MaterialController::class)->except(['create', 'edit', 'show']); + Route::resource('materials', MaterialController::class) + ->except(['create', 'edit', 'show']) + ->middlewareFor(['index'], 'permission:view-materials') + ->middlewareFor(['store'], 'permission:create-materials') + ->middlewareFor(['update'], 'permission:update-materials') + ->middlewareFor(['destroy'], 'permission:delete-materials'); - Route::resource('assignments', AssignmentController::class)->except(['create', 'edit', 'show']); + Route::resource('assignments', AssignmentController::class) + ->except(['create', 'edit', 'show']) + ->middlewareFor(['index'], 'permission:view-assignments') + ->middlewareFor(['store'], 'permission:create-assignments') + ->middlewareFor(['update'], 'permission:update-assignments') + ->middlewareFor(['destroy'], 'permission:delete-assignments'); Route::prefix('assignments/{assignment}/submissions')->name('assignments.submissions.')->group(function () { - Route::get('/', [SubmissionController::class, 'index'])->name('index'); - Route::post('/', [SubmissionController::class, 'store'])->name('store'); - Route::put('{submission}', [SubmissionController::class, 'update'])->name('update'); - Route::delete('{submission}', [SubmissionController::class, 'destroy'])->name('destroy'); + Route::get('/', [SubmissionController::class, 'index'])->name('index')->middleware('permission:view-assignment-submissions'); + Route::post('/', [SubmissionController::class, 'store'])->name('store')->middleware('permission:create-assignment-submissions'); + Route::put('{submission}', [SubmissionController::class, 'update'])->name('update')->middleware('permission:update-assignment-submissions'); + Route::delete('{submission}', [SubmissionController::class, 'destroy'])->name('destroy')->middleware('permission:delete-assignment-submissions'); }); - Route::resource('schedules', ScheduleController::class)->except(['create', 'edit', 'show']); + Route::resource('schedules', ScheduleController::class) + ->except(['create', 'edit', 'show']) + ->middlewareFor(['index'], 'permission:view-schedules') + ->middlewareFor(['store'], 'permission:create-schedules') + ->middlewareFor(['update'], 'permission:update-schedules') + ->middlewareFor(['destroy'], 'permission:delete-schedules'); Route::prefix('attendances')->name('attendances.')->group(function () { - Route::get('/', [AttendanceController::class, 'index'])->name('index'); + Route::get('/', [AttendanceController::class, 'index'])->name('index')->middleware('permission:view-attendances'); Route::get('{course_class}/{meeting_number}', [AttendanceController::class, 'session']) ->whereNumber('meeting_number') - ->name('session'); + ->name('session') + ->middleware('permission:view-attendances'); Route::post('{course_class}/{meeting_number}', [AttendanceController::class, 'store']) ->whereNumber('meeting_number') - ->name('store'); + ->name('store') + ->middleware('permission:create-attendances'); Route::delete('{course_class}/{meeting_number}', [AttendanceController::class, 'destroy']) ->whereNumber('meeting_number') - ->name('destroy'); + ->name('destroy') + ->middleware('permission:delete-attendances'); }); }); Route::prefix('admin/manage')->name('admin.manage.')->group(function () { - Route::resource('course-registrations', CourseRegistrationController::class)->only(['index', 'store']); + Route::resource('course-registrations', CourseRegistrationController::class) + ->only(['index', 'store']) + ->middlewareFor(['index'], 'permission:view-course-registrations') + ->middlewareFor(['store'], 'permission:create-course-registrations'); Route::prefix('course-registrations/{submission}')->name('course-registrations.')->group(function () { - Route::get('/', [CourseRegistrationController::class, 'show'])->name('show'); - Route::patch('approve', [CourseRegistrationController::class, 'approve'])->name('approve'); - Route::patch('reject', [CourseRegistrationController::class, 'reject'])->name('reject'); + Route::get('/', [CourseRegistrationController::class, 'show'])->name('show')->middleware('permission:view-course-registrations'); + Route::patch('approve', [CourseRegistrationController::class, 'approve'])->name('approve')->middleware('permission:approve-course-registrations'); + Route::patch('reject', [CourseRegistrationController::class, 'reject'])->name('reject')->middleware('permission:reject-course-registrations'); }); - Route::resource('course-classes', CourseClassController::class)->except(['create', 'edit', 'show']); + Route::resource('course-classes', CourseClassController::class) + ->except(['create', 'edit', 'show']) + ->middlewareFor(['index'], 'permission:view-course-classes') + ->middlewareFor(['store'], 'permission:create-course-classes') + ->middlewareFor(['update'], 'permission:update-course-classes') + ->middlewareFor(['destroy'], 'permission:delete-course-classes'); Route::prefix('course-classes/{course_class}/enrollments')->name('course-classes.enrollments.')->group(function () { - Route::get('/', [ClassEnrollmentController::class, 'index'])->name('index'); - Route::post('/', [ClassEnrollmentController::class, 'store'])->name('store'); - Route::delete('{enrollment}', [ClassEnrollmentController::class, 'destroy'])->name('destroy'); + Route::get('/', [ClassEnrollmentController::class, 'index'])->name('index')->middleware('permission:view-course-class-enrollments'); + Route::post('/', [ClassEnrollmentController::class, 'store'])->name('store')->middleware('permission:create-course-class-enrollments'); + Route::delete('{enrollment}', [ClassEnrollmentController::class, 'destroy'])->name('destroy')->middleware('permission:delete-course-class-enrollments'); }); Route::prefix('announcements')->name('announcements.')->group(function () { - Route::get('/', [AnnouncementController::class, 'index'])->name('index'); - Route::post('/', [AnnouncementController::class, 'store'])->name('store'); - Route::put('{announcement}', [AnnouncementController::class, 'update'])->name('update'); - Route::delete('{announcement}', [AnnouncementController::class, 'destroy'])->name('destroy'); + Route::get('/', [AnnouncementController::class, 'index'])->name('index')->middleware('permission:view-announcements'); + Route::post('/', [AnnouncementController::class, 'store'])->name('store')->middleware('permission:create-announcements'); + Route::put('{announcement}', [AnnouncementController::class, 'update'])->name('update')->middleware('permission:update-announcements'); + Route::delete('{announcement}', [AnnouncementController::class, 'destroy'])->name('destroy')->middleware('permission:delete-announcements'); }); }); @@ -107,20 +150,35 @@ }); Route::prefix('admin/finances')->name('admin.finances.')->group(function () { - Route::resource('tuition-invoices', TuitionInvoiceController::class)->except(['create', 'edit', 'show']); + Route::resource('tuition-invoices', TuitionInvoiceController::class) + ->except(['create', 'edit', 'show']) + ->middlewareFor(['index'], 'permission:view-tuition-invoices') + ->middlewareFor(['store'], 'permission:create-tuition-invoices') + ->middlewareFor(['update'], 'permission:update-tuition-invoices') + ->middlewareFor(['destroy'], 'permission:delete-tuition-invoices'); Route::prefix('tuition-invoices/{tuition_invoice}/payments')->name('tuition-invoices.payments.')->group(function () { - Route::get('/', [TuitionPaymentController::class, 'index'])->name('index'); - Route::post('/', [TuitionPaymentController::class, 'store'])->name('store'); - Route::put('{payment}', [TuitionPaymentController::class, 'update'])->name('update'); - Route::delete('{payment}', [TuitionPaymentController::class, 'destroy'])->name('destroy'); + Route::get('/', [TuitionPaymentController::class, 'index'])->name('index')->middleware('permission:view-tuition-payments'); + Route::post('/', [TuitionPaymentController::class, 'store'])->name('store')->middleware('permission:create-tuition-payments'); + Route::put('{payment}', [TuitionPaymentController::class, 'update'])->name('update')->middleware('permission:update-tuition-payments'); + Route::delete('{payment}', [TuitionPaymentController::class, 'destroy'])->name('destroy')->middleware('permission:delete-tuition-payments'); }); }); Route::prefix('admin/services')->name('admin.services.')->group(function () { - Route::resource('letter-requests', LetterRequestController::class)->except(['create', 'edit', 'show']); + Route::resource('letter-requests', LetterRequestController::class) + ->except(['create', 'edit', 'show']) + ->middlewareFor(['index'], 'permission:view-letter-requests') + ->middlewareFor(['store'], 'permission:create-letter-requests') + ->middlewareFor(['update'], 'permission:update-letter-requests') + ->middlewareFor(['destroy'], 'permission:delete-letter-requests'); - Route::resource('academic-advising-logs', AcademicAdvisingLogController::class)->except(['create', 'edit', 'show']); + Route::resource('academic-advising-logs', AcademicAdvisingLogController::class) + ->except(['create', 'edit', 'show']) + ->middlewareFor(['index'], 'permission:view-academic-advising-logs') + ->middlewareFor(['store'], 'permission:create-academic-advising-logs') + ->middlewareFor(['update'], 'permission:update-academic-advising-logs') + ->middlewareFor(['destroy'], 'permission:delete-academic-advising-logs'); }); Route::prefix('admin/settings')->name('admin.settings.')->group(function () { @@ -145,19 +203,37 @@ ->names('admin.feedback'); Route::prefix('admin/users')->name('admin.users.')->group(function () { - Route::resource('lecturers', LecturerController::class)->except(['show'])->parameters(['lecturers' => 'user']); - Route::patch('lecturers/{user}/reset-password', [LecturerController::class, 'resetPassword'])->name('lecturers.reset_password'); - Route::patch('lecturers/{user}/user-status', [LecturerController::class, 'updateUserStatus'])->name('lecturers.update_user_status'); - Route::get('lecturers/export', [LecturerController::class, 'export'])->name('lecturers.export'); + Route::resource('lecturers', LecturerController::class) + ->except(['show']) + ->parameters(['lecturers' => 'user']) + ->middlewareFor(['index'], 'permission:view-lecturers') + ->middlewareFor(['create', 'store'], 'permission:create-lecturers') + ->middlewareFor(['edit', 'update'], 'permission:update-lecturers') + ->middlewareFor(['destroy'], 'permission:delete-lecturers'); + Route::patch('lecturers/{user}/reset-password', [LecturerController::class, 'resetPassword'])->name('lecturers.reset_password')->middleware('permission:reset-lecturers-password'); + Route::patch('lecturers/{user}/user-status', [LecturerController::class, 'updateUserStatus'])->name('lecturers.update_user_status')->middleware('permission:update-lecturers-status'); + Route::get('lecturers/export', [LecturerController::class, 'export'])->name('lecturers.export')->middleware('permission:export-lecturers'); - Route::resource('students', StudentController::class)->except(['show'])->parameters(['students' => 'user']); - Route::patch('students/{user}/reset-password', [StudentController::class, 'resetPassword'])->name('students.reset_password'); - Route::patch('students/{user}/status', [StudentController::class, 'updateStatus'])->name('students.update_status'); - Route::patch('students/{user}/user-status', [StudentController::class, 'updateUserStatus'])->name('students.update_user_status'); - Route::get('students/export', [StudentController::class, 'export'])->name('students.export'); + Route::resource('students', StudentController::class) + ->except(['show']) + ->parameters(['students' => 'user']) + ->middlewareFor(['index'], 'permission:view-students') + ->middlewareFor(['create', 'store'], 'permission:create-students') + ->middlewareFor(['edit', 'update'], 'permission:update-students') + ->middlewareFor(['destroy'], 'permission:delete-students'); + Route::patch('students/{user}/reset-password', [StudentController::class, 'resetPassword'])->name('students.reset_password')->middleware('permission:reset-students-password'); + Route::patch('students/{user}/status', [StudentController::class, 'updateStatus'])->name('students.update_status')->middleware('permission:update-students-academic-status'); + Route::patch('students/{user}/user-status', [StudentController::class, 'updateUserStatus'])->name('students.update_user_status')->middleware('permission:update-students-account-status'); + Route::get('students/export', [StudentController::class, 'export'])->name('students.export')->middleware('permission:export-students'); - Route::resource('administrators', AdministratorController::class)->except(['show'])->parameters(['administrators' => 'user']); - Route::patch('administrators/{user}/reset-password', [AdministratorController::class, 'resetPassword'])->name('administrators.reset_password'); - Route::patch('administrators/{user}/user-status', [AdministratorController::class, 'updateUserStatus'])->name('administrators.update_user_status'); + Route::resource('administrators', AdministratorController::class) + ->except(['show']) + ->parameters(['administrators' => 'user']) + ->middlewareFor(['index'], 'permission:view-administrators') + ->middlewareFor(['create', 'store'], 'permission:create-administrators') + ->middlewareFor(['edit', 'update'], 'permission:update-administrators') + ->middlewareFor(['destroy'], 'permission:delete-administrators'); + Route::patch('administrators/{user}/reset-password', [AdministratorController::class, 'resetPassword'])->name('administrators.reset_password')->middleware('permission:reset-administrators-password'); + Route::patch('administrators/{user}/user-status', [AdministratorController::class, 'updateUserStatus'])->name('administrators.update_user_status')->middleware('permission:update-administrators-status'); }); });