refactor(role): 重構角色權限選擇介面並新增快速搜尋功能
1. 新增 PermissionSelector 組件,採用 Accordion 折疊式設計 2. 實作全選/取消全選、展開/收合全部功能 3. 新增權限搜尋過濾器,支援自動展開與中文關鍵字搜尋 4. 優化 UI細節:修正邊框顯示、調整全選框位置與邏輯
This commit is contained in:
@@ -4,19 +4,8 @@ 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[];
|
||||
}
|
||||
import PermissionSelector, { GroupedPermission } from './PermissionSelector';
|
||||
|
||||
interface Role {
|
||||
id: number;
|
||||
@@ -42,71 +31,6 @@ export default function RoleEdit({ role, groupedPermissions, currentPermissions
|
||||
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[parts.length - 1];
|
||||
|
||||
const map: Record<string, string> = {
|
||||
'view': '檢視',
|
||||
'create': '新增',
|
||||
'edit': '編輯',
|
||||
'delete': '刪除',
|
||||
'publish': '發佈',
|
||||
'adjust': '調整',
|
||||
'transfer': '調撥',
|
||||
'count': '盤點',
|
||||
// 'inventory_count': '盤點', // Hide prefix
|
||||
// 'inventory_adjust': '盤調', // Hide prefix
|
||||
// 'inventory_transfer': '調撥', // Hide prefix
|
||||
'safety_stock': '安全庫存設定',
|
||||
'export': '匯出',
|
||||
'complete': '完成',
|
||||
'view_cost': '檢視成本',
|
||||
'view_logs': '檢視日誌',
|
||||
'activate': '啟用/停用',
|
||||
};
|
||||
|
||||
const actionText = map[action] || action;
|
||||
|
||||
// 處理多段式權限 (例如 inventory_count.view)
|
||||
if (parts.length >= 2) {
|
||||
const middleKey = parts[parts.length - 2];
|
||||
|
||||
// 如果中間那段有翻譯且不等於動作本身,則顯示為 "標籤: 動作"
|
||||
if (map[middleKey] && middleKey !== action) {
|
||||
return `${map[middleKey]}: ${actionText}`;
|
||||
}
|
||||
}
|
||||
|
||||
return actionText;
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthenticatedLayout
|
||||
breadcrumbs={[
|
||||
@@ -201,52 +125,11 @@ export default function RoleEdit({ role, groupedPermissions, currentPermissions
|
||||
{/* 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>
|
||||
<PermissionSelector
|
||||
groupedPermissions={groupedPermissions}
|
||||
selectedPermissions={data.permissions}
|
||||
onChange={(permissions) => setData('permissions', permissions)}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user