Files
star-erp/resources/js/Pages/Admin/Role/Edit.tsx
sky121113 4c959efc8b
All checks were successful
Koori-ERP-Deploy-System / deploy-demo (push) Successful in 50s
Koori-ERP-Deploy-System / deploy-production (push) Has been skipped
feat: 補齊生產管理與進貨單權限、功能實作及 UI 優化
2026-01-27 17:40:56 +08:00

239 lines
11 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;
display_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,
display_name: role.display_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': '調撥',
'safety_stock': '安全庫存設定',
'export': '匯出',
'complete': '完成',
'view_cost': '檢視成本',
'view_logs': '檢視日誌',
};
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="container mx-auto p-6 max-w-7xl">
<form onSubmit={handleSubmit} className="space-y-6">
{/* Header Area */}
<div>
<Link href={route('roles.index')}>
<Button
variant="outline"
type="button"
className="gap-2 button-outlined-primary mb-2"
>
<ArrowLeft className="h-4 w-4" />
</Button>
</Link>
<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-primary-main" />
</h1>
<p className="text-gray-500 mt-1">
</p>
</div>
<Button
type="submit"
className="button-filled-primary"
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="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
<Label htmlFor="display_name"> (/)</Label>
<Input
id="display_name"
placeholder="例如:行政管理員"
value={data.display_name}
onChange={e => setData('display_name', e.target.value)}
/>
{errors.display_name && (
<p className="text-sm text-red-500">{errors.display_name}</p>
)}
<p className="text-xs text-gray-500">
</p>
</div>
<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'}
/>
{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-primary-main hover:text-primary-main hover:bg-primary-main/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>
);
}