Files
star-erp/resources/js/Pages/Admin/Role/Edit.tsx

211 lines
9.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import { Head, Link, useForm } from '@inertiajs/react';
import { Shield, ArrowLeft, Check, AlertCircle } from 'lucide-react';
import { Button } from '@/Components/ui/button';
import { Input } from '@/Components/ui/input';
import { Label } from '@/Components/ui/label';
import { Checkbox } from '@/Components/ui/checkbox';
import { FormEvent } from 'react';
interface Permission {
id: number;
name: string;
}
interface GroupedPermission {
key: string;
name: string;
permissions: Permission[];
}
interface Role {
id: number;
name: string;
}
interface Props {
role: Role;
groupedPermissions: GroupedPermission[];
currentPermissions: string[];
}
export default function RoleEdit({ role, groupedPermissions, currentPermissions }: Props) {
const { data, setData, put, processing, errors } = useForm({
name: role.name,
permissions: currentPermissions,
});
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
put(route('roles.update', role.id));
};
const togglePermission = (name: string) => {
if (data.permissions.includes(name)) {
setData('permissions', data.permissions.filter(p => p !== name));
} else {
setData('permissions', [...data.permissions, name]);
}
};
const toggleGroup = (groupPermissions: Permission[]) => {
const groupNames = groupPermissions.map(p => p.name);
const allSelected = groupNames.every(name => data.permissions.includes(name));
if (allSelected) {
// Unselect all
setData('permissions', data.permissions.filter(p => !groupNames.includes(p)));
} else {
// Select all
const newPermissions = [...data.permissions];
groupNames.forEach(name => {
if (!newPermissions.includes(name)) newPermissions.push(name);
});
setData('permissions', newPermissions);
}
};
const translateAction = (permissionName: string) => {
const parts = permissionName.split('.');
if (parts.length < 2) return permissionName;
const action = parts[1];
const map: Record<string, string> = {
'view': '檢視',
'create': '新增',
'edit': '編輯',
'delete': '刪除',
'publish': '發布',
'adjust': '調整',
'transfer': '調撥',
};
return map[action] || action;
};
return (
<AuthenticatedLayout
breadcrumbs={[
{ label: '系統管理', href: '#' },
{ label: '角色與權限', href: route('roles.index') },
{ label: '編輯角色', href: route('roles.edit', role.id), isPage: true },
]}
>
<Head title={`編輯角色 - ${role.name}`} />
<div className="p-8 max-w-7xl mx-auto">
<form onSubmit={handleSubmit} className="space-y-8">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-grey-0 flex items-center gap-2">
<Shield className="h-6 w-6 text-[#01ab83]" />
</h1>
<p className="text-gray-500 mt-1">
</p>
</div>
<div className="flex items-center gap-3">
<Link href={route('roles.index')}>
<Button variant="outline" type="button">
<ArrowLeft className="h-4 w-4 mr-2" />
</Button>
</Link>
<Button
type="submit"
className="bg-[#01ab83] hover:bg-[#019a76]"
disabled={processing}
>
<Check className="h-4 w-4 mr-2" />
</Button>
</div>
</div>
{/* Role Name */}
<div className="bg-white p-6 rounded-xl border border-gray-200 shadow-sm">
<div className="max-w-md space-y-4">
<div className="space-y-2">
<Label htmlFor="name"> ()</Label>
<Input
id="name"
value={data.name}
onChange={e => setData('name', e.target.value)}
className="font-mono bg-gray-50"
disabled={role.name === 'super-admin'} // Should be handled by controller redirect, but extra safety
/>
{errors.name && (
<p className="text-sm text-red-500">{errors.name}</p>
)}
{role.name === 'super-admin' ? (
<div className="flex items-center gap-2 text-amber-600 text-sm mt-2">
<AlertCircle className="h-4 w-4" />
<span></span>
</div>
) : (
<p className="text-xs text-gray-500">
使: <code>warehouse-staff</code>
</p>
)}
</div>
</div>
</div>
{/* Permissions Matrix */}
<div className="space-y-4">
<h2 className="text-lg font-bold text-grey-0"></h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{groupedPermissions.map((group) => {
const allGroupSelected = group.permissions.every(p => data.permissions.includes(p.name));
return (
<div key={group.key} className="bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden flex flex-col">
<div className="bg-gray-50 px-4 py-3 border-b border-gray-200 flex items-center justify-between">
<span className="font-medium text-gray-700">{group.name}</span>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => toggleGroup(group.permissions)}
className="text-xs h-7 text-[#01ab83] hover:text-[#01ab83] hover:bg-[#01ab83]/10"
>
{allGroupSelected ? '取消全選' : '全選'}
</Button>
</div>
<div className="p-4 flex-1">
<div className="space-y-3">
{group.permissions.map((permission) => (
<div key={permission.id} className="flex items-start space-x-3">
<Checkbox
id={permission.name}
checked={data.permissions.includes(permission.name)}
onCheckedChange={() => togglePermission(permission.name)}
/>
<div className="grid gap-1.5 leading-none">
<label
htmlFor={permission.name}
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
>
{translateAction(permission.name)}
</label>
<p className="text-[10px] text-gray-400 font-mono">
{permission.name}
</p>
</div>
</div>
))}
</div>
</div>
</div>
);
})}
</div>
</div>
</form>
</div>
</AuthenticatedLayout>
);
}