feat: implement role and permission management with CRUD functionality
This commit is contained in:
parent
acda85b4e5
commit
23961969f4
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Admin\Developer;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\Admin\Developer\UpdateRolePermissionsRequest;
|
||||||
|
use App\Services\Admin\Developer\RolePermissionService;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Inertia\Inertia;
|
||||||
|
use Inertia\Response;
|
||||||
|
use Spatie\Permission\Models\Role;
|
||||||
|
|
||||||
|
class RolePermissionController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly RolePermissionService $service,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function index(): Response
|
||||||
|
{
|
||||||
|
return Inertia::render('admin/developer/roles/index', [
|
||||||
|
'roles' => $this->service->roles(),
|
||||||
|
'permissionGroups' => $this->service->groupedPermissions(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(UpdateRolePermissionsRequest $request, Role $role): RedirectResponse
|
||||||
|
{
|
||||||
|
$this->service->updatePermissions($role, $request->validated('permissions'));
|
||||||
|
|
||||||
|
Inertia::flash('toast', ['type' => 'success', 'message' => 'Permission role berhasil diperbarui.']);
|
||||||
|
|
||||||
|
return to_route('admin.developer.roles.index');
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Admin\Developer;
|
||||||
|
|
||||||
|
use App\Support\PermissionCatalog;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class UpdateRolePermissionsRequest extends FormRequest
|
||||||
|
{
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'permissions' => ['present', 'array'],
|
||||||
|
'permissions.*' => ['string', Rule::in(PermissionCatalog::all())],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
52
app/Services/Admin/Developer/RolePermissionService.php
Normal file
52
app/Services/Admin/Developer/RolePermissionService.php
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services\Admin\Developer;
|
||||||
|
|
||||||
|
use App\Support\PermissionCatalog;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Spatie\Permission\Models\Role;
|
||||||
|
use Spatie\Permission\PermissionRegistrar;
|
||||||
|
|
||||||
|
class RolePermissionService
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return array<string, array<int, string>>
|
||||||
|
*/
|
||||||
|
public function groupedPermissions(): array
|
||||||
|
{
|
||||||
|
return PermissionCatalog::groups();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<int, array{id: int, name: string, permissions: array<int, string>}>
|
||||||
|
*/
|
||||||
|
public function roles(): Collection
|
||||||
|
{
|
||||||
|
return Role::with('permissions:id,name')
|
||||||
|
->orderBy('name')
|
||||||
|
->get(['id', 'name'])
|
||||||
|
->map(fn (Role $role) => [
|
||||||
|
'id' => $role->id,
|
||||||
|
'name' => $role->name,
|
||||||
|
'permissions' => $role->permissions->pluck('name')->all(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int, string> $permissionNames
|
||||||
|
*/
|
||||||
|
public function updatePermissions(Role $role, array $permissionNames): Role
|
||||||
|
{
|
||||||
|
$valid = array_values(array_intersect($permissionNames, PermissionCatalog::all()));
|
||||||
|
|
||||||
|
if ($role->name === 'developer') {
|
||||||
|
$valid = array_values(array_unique([...$valid, 'view-roles', 'update-roles']));
|
||||||
|
}
|
||||||
|
|
||||||
|
$role->syncPermissions($valid);
|
||||||
|
|
||||||
|
app(PermissionRegistrar::class)->forgetCachedPermissions();
|
||||||
|
|
||||||
|
return $role->load('permissions:id,name');
|
||||||
|
}
|
||||||
|
}
|
||||||
75
app/Support/PermissionCatalog.php
Normal file
75
app/Support/PermissionCatalog.php
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Support;
|
||||||
|
|
||||||
|
class PermissionCatalog
|
||||||
|
{
|
||||||
|
public const DASHBOARD = ['view-dashboard'];
|
||||||
|
|
||||||
|
public const 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',
|
||||||
|
];
|
||||||
|
|
||||||
|
public const ACADEMIC_CLASSES = [
|
||||||
|
'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',
|
||||||
|
];
|
||||||
|
|
||||||
|
public const 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',
|
||||||
|
];
|
||||||
|
|
||||||
|
public const 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',
|
||||||
|
];
|
||||||
|
|
||||||
|
public const 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',
|
||||||
|
];
|
||||||
|
|
||||||
|
public const 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',
|
||||||
|
];
|
||||||
|
|
||||||
|
public const DEVELOPER = [
|
||||||
|
'view-logs',
|
||||||
|
'view-roles', 'update-roles',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, array<int, string>>
|
||||||
|
*/
|
||||||
|
public static function groups(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'Dashboard' => self::DASHBOARD,
|
||||||
|
'Master' => self::MASTER,
|
||||||
|
'Kelas' => self::ACADEMIC_CLASSES,
|
||||||
|
'Kelola' => self::MANAGE,
|
||||||
|
'Keuangan' => self::FINANCES,
|
||||||
|
'Layanan' => self::SERVICES,
|
||||||
|
'Pengguna' => self::USERS,
|
||||||
|
'Pengembang' => self::DEVELOPER,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<int, string>
|
||||||
|
*/
|
||||||
|
public static function all(): array
|
||||||
|
{
|
||||||
|
return array_merge(...array_values(self::groups()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace Database\Seeders;
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use App\Support\PermissionCatalog;
|
||||||
use Illuminate\Database\Seeder;
|
use Illuminate\Database\Seeder;
|
||||||
use Spatie\Permission\Models\Permission;
|
use Spatie\Permission\Models\Permission;
|
||||||
use Spatie\Permission\Models\Role;
|
use Spatie\Permission\Models\Role;
|
||||||
@ -13,57 +14,14 @@ public function run(): void
|
|||||||
{
|
{
|
||||||
app()[PermissionRegistrar::class]->forgetCachedPermissions();
|
app()[PermissionRegistrar::class]->forgetCachedPermissions();
|
||||||
|
|
||||||
$master = [
|
$master = PermissionCatalog::MASTER;
|
||||||
'view-academic-terms', 'create-academic-terms', 'update-academic-terms', 'delete-academic-terms', 'update-academic-terms-status',
|
$academicClasses = PermissionCatalog::ACADEMIC_CLASSES;
|
||||||
'view-departments', 'create-departments', 'update-departments', 'delete-departments',
|
$manage = PermissionCatalog::MANAGE;
|
||||||
'view-courses', 'create-courses', 'update-courses', 'delete-courses',
|
$finances = PermissionCatalog::FINANCES;
|
||||||
];
|
$services = PermissionCatalog::SERVICES;
|
||||||
|
$users = PermissionCatalog::USERS;
|
||||||
|
|
||||||
$academicClasses = [
|
$permissionNames = PermissionCatalog::all();
|
||||||
'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',
|
|
||||||
];
|
|
||||||
|
|
||||||
$developer = [
|
|
||||||
'view-logs',
|
|
||||||
];
|
|
||||||
|
|
||||||
$permissionNames = [
|
|
||||||
'view-dashboard',
|
|
||||||
...$master,
|
|
||||||
...$academicClasses,
|
|
||||||
...$manage,
|
|
||||||
...$finances,
|
|
||||||
...$services,
|
|
||||||
...$users,
|
|
||||||
...$developer,
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($permissionNames as $name) {
|
foreach ($permissionNames as $name) {
|
||||||
Permission::firstOrCreate(['name' => $name, 'guard_name' => 'web']);
|
Permission::firstOrCreate(['name' => $name, 'guard_name' => 'web']);
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import {
|
|||||||
Receipt,
|
Receipt,
|
||||||
School,
|
School,
|
||||||
ScrollText,
|
ScrollText,
|
||||||
|
ShieldCheck,
|
||||||
User,
|
User,
|
||||||
Users,
|
Users,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
@ -39,6 +40,7 @@ import { index as attendancesRoute } from '@/routes/admin/academic-classes/atten
|
|||||||
import { index as materialsRoute } from '@/routes/admin/academic-classes/materials';
|
import { index as materialsRoute } from '@/routes/admin/academic-classes/materials';
|
||||||
import { index as schedulesRoute } from '@/routes/admin/academic-classes/schedules';
|
import { index as schedulesRoute } from '@/routes/admin/academic-classes/schedules';
|
||||||
import { index as logsRoute } from '@/routes/admin/developer/logs';
|
import { index as logsRoute } from '@/routes/admin/developer/logs';
|
||||||
|
import { index as rolePermissionsRoute } from '@/routes/admin/developer/roles';
|
||||||
import { index as feedbackRoute } from '@/routes/admin/feedback';
|
import { index as feedbackRoute } from '@/routes/admin/feedback';
|
||||||
import { index as tuitionInvoicesRoute } from '@/routes/admin/finances/tuition-invoices';
|
import { index as tuitionInvoicesRoute } from '@/routes/admin/finances/tuition-invoices';
|
||||||
import { index as announcementsRoute } from '@/routes/admin/manage/announcements';
|
import { index as announcementsRoute } from '@/routes/admin/manage/announcements';
|
||||||
@ -63,10 +65,12 @@ function buildNavMain({
|
|||||||
isMahasiswa,
|
isMahasiswa,
|
||||||
isDosen,
|
isDosen,
|
||||||
canViewLogs,
|
canViewLogs,
|
||||||
|
canViewRoles,
|
||||||
}: {
|
}: {
|
||||||
isMahasiswa: boolean;
|
isMahasiswa: boolean;
|
||||||
isDosen: boolean;
|
isDosen: boolean;
|
||||||
canViewLogs: boolean;
|
canViewLogs: boolean;
|
||||||
|
canViewRoles: boolean;
|
||||||
}): (NavGroup | NavItem)[] {
|
}): (NavGroup | NavItem)[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -190,16 +194,29 @@ function buildNavMain({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
...(canViewLogs
|
...(canViewLogs || canViewRoles
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
label: 'Pengembang',
|
label: 'Pengembang',
|
||||||
items: [
|
items: [
|
||||||
{
|
...(canViewLogs
|
||||||
name: 'Logs',
|
? [
|
||||||
url: logsRoute.url(),
|
{
|
||||||
icon: ScrollText,
|
name: 'Logs',
|
||||||
},
|
url: logsRoute.url(),
|
||||||
|
icon: ScrollText,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
...(canViewRoles
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
name: 'Role & Permission',
|
||||||
|
url: rolePermissionsRoute.url(),
|
||||||
|
icon: ShieldCheck,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@ -227,7 +244,13 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
|||||||
const isMahasiswa = !isStaff && roleNames.includes('mahasiswa');
|
const isMahasiswa = !isStaff && roleNames.includes('mahasiswa');
|
||||||
const isDosen = !isStaff && roleNames.includes('dosen');
|
const isDosen = !isStaff && roleNames.includes('dosen');
|
||||||
const canViewLogs = (auth?.permissions ?? []).includes('view-logs');
|
const canViewLogs = (auth?.permissions ?? []).includes('view-logs');
|
||||||
const navMain = buildNavMain({ isMahasiswa, isDosen, canViewLogs });
|
const canViewRoles = (auth?.permissions ?? []).includes('view-roles');
|
||||||
|
const navMain = buildNavMain({
|
||||||
|
isMahasiswa,
|
||||||
|
isDosen,
|
||||||
|
canViewLogs,
|
||||||
|
canViewRoles,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sidebar collapsible="icon" {...props}>
|
<Sidebar collapsible="icon" {...props}>
|
||||||
|
|||||||
186
resources/js/pages/admin/developer/roles/index.tsx
Normal file
186
resources/js/pages/admin/developer/roles/index.tsx
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
import { Head, router } from '@inertiajs/react';
|
||||||
|
import { Save } from 'lucide-react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { PageHeader } from '@/components/page-header';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select';
|
||||||
|
import { update } from '@/routes/admin/developer/roles';
|
||||||
|
import type {
|
||||||
|
PermissionGroups,
|
||||||
|
RoleWithPermissions,
|
||||||
|
} from '@/types/role-permission';
|
||||||
|
import { formatRoleLabel } from '@/types/role-permission';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
roles: RoleWithPermissions[];
|
||||||
|
permissionGroups: PermissionGroups;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RolePermissionIndex({
|
||||||
|
roles,
|
||||||
|
permissionGroups,
|
||||||
|
}: Props) {
|
||||||
|
const [selectedRoleId, setSelectedRoleId] = useState<number | undefined>(
|
||||||
|
roles[0]?.id,
|
||||||
|
);
|
||||||
|
const selectedRole = roles.find((role) => role.id === selectedRoleId);
|
||||||
|
const [selected, setSelected] = useState<string[]>(
|
||||||
|
selectedRole?.permissions ?? [],
|
||||||
|
);
|
||||||
|
const [processing, setProcessing] = useState(false);
|
||||||
|
|
||||||
|
function handleSelectRole(id: string) {
|
||||||
|
const role = roles.find((r) => r.id === Number(id));
|
||||||
|
setSelectedRoleId(role?.id);
|
||||||
|
setSelected(role?.permissions ?? []);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggle(name: string, checked: boolean) {
|
||||||
|
setSelected((prev) =>
|
||||||
|
checked ? [...prev, name] : prev.filter((p) => p !== name),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleGroup(names: string[], checked: boolean) {
|
||||||
|
setSelected((prev) => {
|
||||||
|
const withoutGroup = prev.filter((p) => !names.includes(p));
|
||||||
|
|
||||||
|
return checked ? [...withoutGroup, ...names] : withoutGroup;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSave() {
|
||||||
|
if (!selectedRole) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setProcessing(true);
|
||||||
|
router.put(
|
||||||
|
update.url(selectedRole.id),
|
||||||
|
{ permissions: selected },
|
||||||
|
{
|
||||||
|
preserveScroll: true,
|
||||||
|
onFinish: () => setProcessing(false),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head title="Role & Permission" />
|
||||||
|
|
||||||
|
<div className="flex h-full flex-1 flex-col gap-6 overflow-x-auto p-4 md:p-6">
|
||||||
|
<PageHeader
|
||||||
|
title="Role & Permission"
|
||||||
|
actions={
|
||||||
|
selectedRole && (
|
||||||
|
<Button onClick={handleSave} disabled={processing}>
|
||||||
|
<Save className="h-4 w-4" />
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Select
|
||||||
|
value={
|
||||||
|
selectedRoleId ? String(selectedRoleId) : undefined
|
||||||
|
}
|
||||||
|
onValueChange={handleSelectRole}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-full sm:w-72">
|
||||||
|
<SelectValue placeholder="Pilih role" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{roles.map((role) => (
|
||||||
|
<SelectItem
|
||||||
|
key={role.id}
|
||||||
|
value={String(role.id)}
|
||||||
|
>
|
||||||
|
{formatRoleLabel(role.name)}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
{selectedRole && (
|
||||||
|
<Badge variant="secondary">
|
||||||
|
{selected.length} permission
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{selectedRole && (
|
||||||
|
<div className="grid gap-4">
|
||||||
|
{Object.entries(permissionGroups).map(
|
||||||
|
([group, names]) => {
|
||||||
|
const allChecked = names.every((name) =>
|
||||||
|
selected.includes(name),
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card key={group}>
|
||||||
|
<CardHeader className="flex flex-row items-center justify-between gap-4">
|
||||||
|
<CardTitle className="text-base">
|
||||||
|
{group}
|
||||||
|
</CardTitle>
|
||||||
|
<label className="flex items-center gap-2 text-sm font-normal text-muted-foreground">
|
||||||
|
<Checkbox
|
||||||
|
checked={allChecked}
|
||||||
|
onCheckedChange={(
|
||||||
|
checked,
|
||||||
|
) =>
|
||||||
|
toggleGroup(
|
||||||
|
names,
|
||||||
|
checked === true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
Pilih Semua
|
||||||
|
</label>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
|
{names.map((name) => (
|
||||||
|
<label
|
||||||
|
key={name}
|
||||||
|
className="flex items-center gap-2 rounded-md border p-2 text-sm hover:bg-muted"
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={selected.includes(
|
||||||
|
name,
|
||||||
|
)}
|
||||||
|
onCheckedChange={(
|
||||||
|
checked,
|
||||||
|
) =>
|
||||||
|
toggle(
|
||||||
|
name,
|
||||||
|
checked ===
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<span className="font-mono text-xs">
|
||||||
|
{name}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
20
resources/js/types/role-permission.ts
Normal file
20
resources/js/types/role-permission.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
export type RoleWithPermissions = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
permissions: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PermissionGroups = Record<string, string[]>;
|
||||||
|
|
||||||
|
export const RoleLabels: Record<string, string> = {
|
||||||
|
mahasiswa: 'Mahasiswa',
|
||||||
|
dosen: 'Dosen',
|
||||||
|
'staff-admin': 'Staff Admin',
|
||||||
|
'staff-keuangan': 'Staff Keuangan',
|
||||||
|
kaprodi: 'Kaprodi',
|
||||||
|
developer: 'Developer',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function formatRoleLabel(name: string): string {
|
||||||
|
return RoleLabels[name] ?? name;
|
||||||
|
}
|
||||||
@ -6,6 +6,7 @@
|
|||||||
use App\Http\Controllers\Admin\AcademicClasses\ScheduleController;
|
use App\Http\Controllers\Admin\AcademicClasses\ScheduleController;
|
||||||
use App\Http\Controllers\Admin\AcademicClasses\SubmissionController;
|
use App\Http\Controllers\Admin\AcademicClasses\SubmissionController;
|
||||||
use App\Http\Controllers\Admin\Developer\LogController;
|
use App\Http\Controllers\Admin\Developer\LogController;
|
||||||
|
use App\Http\Controllers\Admin\Developer\RolePermissionController;
|
||||||
use App\Http\Controllers\Admin\FeedbackController;
|
use App\Http\Controllers\Admin\FeedbackController;
|
||||||
use App\Http\Controllers\Admin\Finances\TuitionInvoiceController;
|
use App\Http\Controllers\Admin\Finances\TuitionInvoiceController;
|
||||||
use App\Http\Controllers\Admin\Finances\TuitionPaymentController;
|
use App\Http\Controllers\Admin\Finances\TuitionPaymentController;
|
||||||
@ -238,8 +239,11 @@
|
|||||||
Route::patch('administrators/{user}/user-status', [AdministratorController::class, 'updateUserStatus'])->name('administrators.update_user_status')->middleware('permission:update-administrators-status');
|
Route::patch('administrators/{user}/user-status', [AdministratorController::class, 'updateUserStatus'])->name('administrators.update_user_status')->middleware('permission:update-administrators-status');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::prefix('admin/developer')->name('admin.developer.')->middleware('permission:view-logs')->group(function () {
|
Route::prefix('admin/developer')->name('admin.developer.')->group(function () {
|
||||||
Route::get('logs', [LogController::class, 'index'])->name('logs.index');
|
Route::get('logs', [LogController::class, 'index'])->name('logs.index')->middleware('permission:view-logs');
|
||||||
Route::get('logs/download', [LogController::class, 'download'])->name('logs.download');
|
Route::get('logs/download', [LogController::class, 'download'])->name('logs.download')->middleware('permission:view-logs');
|
||||||
|
|
||||||
|
Route::get('roles', [RolePermissionController::class, 'index'])->name('roles.index')->middleware('permission:view-roles');
|
||||||
|
Route::put('roles/{role}', [RolePermissionController::class, 'update'])->name('roles.update')->middleware('permission:update-roles');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user