267 lines
12 KiB
TypeScript
267 lines
12 KiB
TypeScript
import { Button } from '@/components/ui/button';
|
|
import { Card, CardContent } from '@/components/ui/card';
|
|
import { Checkbox } from '@/components/ui/checkbox';
|
|
import { Field, FieldError } from '@/components/ui/field';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import roleRoutes from '@/routes/system/role';
|
|
import type { Permission, Role } from '@/types';
|
|
import { Head, Link, useForm } from '@inertiajs/react';
|
|
import { ArrowLeft, Loader, Save } from 'lucide-react';
|
|
import React, { useMemo } from 'react';
|
|
import { toast } from 'sonner';
|
|
|
|
export default function RoleEdit({
|
|
role,
|
|
permissions,
|
|
}: {
|
|
role: Role;
|
|
permissions: Permission[];
|
|
}) {
|
|
const { data, setData, put, processing, errors } = useForm<{
|
|
name: string;
|
|
guard_name: string;
|
|
permissions: number[];
|
|
}>({
|
|
name: role.name,
|
|
guard_name: role.guard_name,
|
|
permissions: role.permissions?.map((p: Permission) => p.id) || [],
|
|
});
|
|
|
|
const onSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
put(roleRoutes.update(role.id).url, {
|
|
onSuccess: (response: any) => {
|
|
const flash = response.props.flash;
|
|
if (flash?.error) {
|
|
toast.error(flash.error);
|
|
} else if (flash?.success) {
|
|
toast.success(flash.success);
|
|
}
|
|
},
|
|
});
|
|
};
|
|
|
|
const groupedPermissions = useMemo(() => {
|
|
const groups: Record<string, Permission[]> = {};
|
|
permissions.forEach((permission) => {
|
|
const parts = permission.name.split(':');
|
|
const groupName = parts.length > 1 ? parts[1] : 'Lainnya';
|
|
if (!groups[groupName]) {
|
|
groups[groupName] = [];
|
|
}
|
|
groups[groupName].push(permission);
|
|
});
|
|
return groups;
|
|
}, [permissions]);
|
|
|
|
const handleCheckboxChange = (id: number, checked: boolean) => {
|
|
if (checked) {
|
|
setData('permissions', [...data.permissions, id]);
|
|
} else {
|
|
setData(
|
|
'permissions',
|
|
data.permissions.filter((pId) => pId !== id),
|
|
);
|
|
}
|
|
};
|
|
|
|
const toggleGroup = (groupName: string) => {
|
|
const groupPermissionIds = groupedPermissions[groupName].map(
|
|
(p) => p.id,
|
|
);
|
|
const allInGroupSelected = groupPermissionIds.every((id) =>
|
|
data.permissions.includes(id),
|
|
);
|
|
|
|
if (allInGroupSelected) {
|
|
setData(
|
|
'permissions',
|
|
data.permissions.filter(
|
|
(id) => !groupPermissionIds.includes(id),
|
|
),
|
|
);
|
|
} else {
|
|
const otherPermissions = data.permissions.filter(
|
|
(id) => !groupPermissionIds.includes(id),
|
|
);
|
|
setData('permissions', [
|
|
...otherPermissions,
|
|
...groupPermissionIds,
|
|
]);
|
|
}
|
|
};
|
|
|
|
const selectAll = () => {
|
|
setData(
|
|
'permissions',
|
|
permissions.map((p) => p.id),
|
|
);
|
|
};
|
|
|
|
const deselectAll = () => {
|
|
setData('permissions', []);
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-col gap-6 p-6">
|
|
<Head title="Ubah Role" />
|
|
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-3xl font-bold tracking-tight">
|
|
Ubah Role
|
|
</h1>
|
|
</div>
|
|
<Link href={roleRoutes.index().url}>
|
|
<Button variant="outline" className="gap-2">
|
|
<ArrowLeft className="size-4" />
|
|
Kembali
|
|
</Button>
|
|
</Link>
|
|
</div>
|
|
|
|
<form onSubmit={onSubmit} className="space-y-6">
|
|
<Card className="overflow-hidden border-none bg-card/50 shadow-lg backdrop-blur-sm">
|
|
<CardContent className="p-0">
|
|
<div className="border-b bg-muted/30 p-6">
|
|
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
|
<Field>
|
|
<Label htmlFor="name" required>
|
|
Nama
|
|
</Label>
|
|
<Input
|
|
id="name"
|
|
name="name"
|
|
value={data.name}
|
|
onChange={(e) =>
|
|
setData('name', e.target.value)
|
|
}
|
|
autoComplete="off"
|
|
/>
|
|
<FieldError
|
|
error={errors.name}
|
|
label="Nama"
|
|
className="text-xs"
|
|
/>
|
|
</Field>
|
|
|
|
<Field>
|
|
<Label htmlFor="guard_name" required>
|
|
Guard
|
|
</Label>
|
|
<Input
|
|
id="guard_name"
|
|
name="guard_name"
|
|
value={data.guard_name}
|
|
onChange={(e) =>
|
|
setData(
|
|
'guard_name',
|
|
e.target.value,
|
|
)
|
|
}
|
|
autoComplete="off"
|
|
/>
|
|
<FieldError
|
|
error={errors.guard_name}
|
|
label="Guard"
|
|
className="text-xs"
|
|
/>
|
|
</Field>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-8 p-6">
|
|
{Object.entries(groupedPermissions).map(
|
|
([groupName, groupPermissions]) => {
|
|
const allInGroupSelected =
|
|
groupPermissions.every((p) =>
|
|
data.permissions.includes(p.id),
|
|
);
|
|
|
|
return (
|
|
<div
|
|
key={groupName}
|
|
className="space-y-4"
|
|
>
|
|
<div className="flex items-center justify-between border-b pb-2">
|
|
<h3 className="text-xs font-bold tracking-widest text-muted-foreground uppercase">
|
|
{groupName}
|
|
</h3>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
size="sm"
|
|
className="h-8 gap-1.5"
|
|
onClick={() =>
|
|
toggleGroup(groupName)
|
|
}
|
|
>
|
|
{allInGroupSelected
|
|
? 'Lepas Semua'
|
|
: 'Pilih Semua'}
|
|
</Button>
|
|
</div>
|
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
|
|
{groupPermissions.map(
|
|
(permission) => (
|
|
<div
|
|
key={permission.id}
|
|
className="group flex items-center space-x-3"
|
|
>
|
|
<Checkbox
|
|
id={`permission-${permission.id}`}
|
|
checked={data.permissions.includes(
|
|
permission.id,
|
|
)}
|
|
onCheckedChange={(
|
|
checked,
|
|
) =>
|
|
handleCheckboxChange(
|
|
permission.id,
|
|
checked as boolean,
|
|
)
|
|
}
|
|
className="transition-transform active:scale-90"
|
|
/>
|
|
<Label
|
|
htmlFor={`permission-${permission.id}`}
|
|
className="cursor-pointer text-sm leading-none font-medium transition-colors group-hover:text-primary"
|
|
>
|
|
{
|
|
permission.name.split(
|
|
':',
|
|
)[0]
|
|
}
|
|
</Label>
|
|
</div>
|
|
),
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
},
|
|
)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<div className="flex justify-end">
|
|
<Button type="submit" disabled={processing}>
|
|
{processing ? (
|
|
<Loader className="size-4 animate-spin" />
|
|
) : (
|
|
<Save className="size-4" />
|
|
)}
|
|
{processing ? 'Menyimpan...' : 'Simpan'}
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
RoleEdit.layout = {
|
|
breadcrumbs: [{ title: 'Sistem' }],
|
|
};
|