siakad-itm/resources/js/pages/admin/developer/roles/index.tsx

187 lines
7.7 KiB
TypeScript

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>
</>
);
}