110 lines
3.4 KiB
TypeScript
110 lines
3.4 KiB
TypeScript
|
|
"use client";
|
||
|
|
|
||
|
|
import { Checkbox } from "@/components/ui/checkbox";
|
||
|
|
import { Label } from "@/components/ui/label";
|
||
|
|
import { statement } from "@/lib/permissions";
|
||
|
|
|
||
|
|
interface PermissionPickerProps {
|
||
|
|
value: Record<string, string[]>;
|
||
|
|
onChange: (permissions: Record<string, string[]>) => void;
|
||
|
|
disabled?: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
const resourceLabels: Record<string, string> = {
|
||
|
|
tickets: "Tickets",
|
||
|
|
configItems: "Configuration Items",
|
||
|
|
admin: "Admin Panel",
|
||
|
|
users: "User Management",
|
||
|
|
roles: "Role Management",
|
||
|
|
auditLog: "Audit Log",
|
||
|
|
settings: "Settings",
|
||
|
|
};
|
||
|
|
|
||
|
|
const actionLabels: Record<string, string> = {
|
||
|
|
create: "Create",
|
||
|
|
read: "Read",
|
||
|
|
update: "Update",
|
||
|
|
delete: "Delete",
|
||
|
|
access: "Access",
|
||
|
|
invite: "Invite",
|
||
|
|
ban: "Ban",
|
||
|
|
};
|
||
|
|
|
||
|
|
export function PermissionPicker({ value, onChange, disabled }: PermissionPickerProps) {
|
||
|
|
const handleToggle = (resource: string, action: string, checked: boolean) => {
|
||
|
|
const currentActions = value[resource] || [];
|
||
|
|
let newActions: string[];
|
||
|
|
|
||
|
|
if (checked) {
|
||
|
|
newActions = [...currentActions, action];
|
||
|
|
} else {
|
||
|
|
newActions = currentActions.filter((a) => a !== action);
|
||
|
|
}
|
||
|
|
|
||
|
|
onChange({
|
||
|
|
...value,
|
||
|
|
[resource]: newActions,
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleToggleAll = (resource: string, checked: boolean) => {
|
||
|
|
const allActions = statement[resource as keyof typeof statement] as readonly string[];
|
||
|
|
onChange({
|
||
|
|
...value,
|
||
|
|
[resource]: checked ? [...allActions] : [],
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="space-y-6">
|
||
|
|
{Object.entries(statement).map(([resource, actions]) => {
|
||
|
|
const currentActions = value[resource] || [];
|
||
|
|
const allChecked = actions.every((a) => currentActions.includes(a));
|
||
|
|
const someChecked = actions.some((a) => currentActions.includes(a));
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div key={resource} className="space-y-3">
|
||
|
|
<div className="flex items-center gap-2">
|
||
|
|
<Checkbox
|
||
|
|
id={`${resource}-all`}
|
||
|
|
checked={allChecked}
|
||
|
|
onCheckedChange={(checked) =>
|
||
|
|
handleToggleAll(resource, checked as boolean)
|
||
|
|
}
|
||
|
|
disabled={disabled}
|
||
|
|
className={someChecked && !allChecked ? "data-[state=checked]:bg-muted" : ""}
|
||
|
|
/>
|
||
|
|
<Label
|
||
|
|
htmlFor={`${resource}-all`}
|
||
|
|
className="font-semibold cursor-pointer"
|
||
|
|
>
|
||
|
|
{resourceLabels[resource] || resource}
|
||
|
|
</Label>
|
||
|
|
</div>
|
||
|
|
<div className="ml-6 grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3">
|
||
|
|
{(actions as readonly string[]).map((action) => (
|
||
|
|
<div key={action} className="flex items-center gap-2">
|
||
|
|
<Checkbox
|
||
|
|
id={`${resource}-${action}`}
|
||
|
|
checked={currentActions.includes(action)}
|
||
|
|
onCheckedChange={(checked) =>
|
||
|
|
handleToggle(resource, action, checked as boolean)
|
||
|
|
}
|
||
|
|
disabled={disabled}
|
||
|
|
/>
|
||
|
|
<Label
|
||
|
|
htmlFor={`${resource}-${action}`}
|
||
|
|
className="text-sm cursor-pointer"
|
||
|
|
>
|
||
|
|
{actionLabels[action] || action}
|
||
|
|
</Label>
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
})}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|