refactor: implement permission pair structure to group view and edit controls within settings
This commit is contained in:
@@ -1,12 +1,20 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Shield, Plus, Search, X, Edit, Trash2, Copy, Check, ChevronDown, ChevronRight, BookOpen, FileSearch, ClipboardList, Settings, BarChart3, Bike, Users, Briefcase, Truck, Store, BatteryCharging, Building2, Wrench, DollarSign, TrendingUp, UserCog } from 'lucide-react';
|
import { Shield, Plus, Search, X, Edit, Trash2, Copy, Check, ChevronDown, ChevronRight, BookOpen, FileSearch, Settings, BarChart3, Bike, Users, Briefcase, Truck, Store, BatteryCharging, Building2, Wrench, DollarSign, TrendingUp, UserCog } from 'lucide-react';
|
||||||
|
|
||||||
interface Permission {
|
interface Permission {
|
||||||
key: string;
|
key: string;
|
||||||
label: string;
|
label: string;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
|
divider?: boolean;
|
||||||
|
pair?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PermissionPair {
|
||||||
|
label: string;
|
||||||
|
view: Permission;
|
||||||
|
edit: Permission;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PermissionGroup {
|
interface PermissionGroup {
|
||||||
@@ -15,6 +23,7 @@ interface PermissionGroup {
|
|||||||
description: string;
|
description: string;
|
||||||
icon: React.ComponentType<{ className?: string }>;
|
icon: React.ComponentType<{ className?: string }>;
|
||||||
permissions: Permission[];
|
permissions: Permission[];
|
||||||
|
permissionPairs?: PermissionPair[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Role {
|
interface Role {
|
||||||
@@ -38,36 +47,47 @@ const buildDefaultGroups = (): PermissionGroup[] => [
|
|||||||
{ key: 'kyc.doc_upload', label: 'Document Upload', enabled: false },
|
{ key: 'kyc.doc_upload', label: 'Document Upload', enabled: false },
|
||||||
{ key: 'kyc.doc_approve', label: 'Document Approve', enabled: false },
|
{ key: 'kyc.doc_approve', label: 'Document Approve', enabled: false },
|
||||||
{ key: 'kyc.doc_reject', label: 'Document Reject', enabled: false },
|
{ key: 'kyc.doc_reject', label: 'Document Reject', enabled: false },
|
||||||
{ key: 'kyc.make_valid_user', label: 'Make a Biker | Investor | Shop | Merchant', enabled: false },
|
{ key: 'kyc.make_valid_user', label: 'Make a Biker | Make an Investor | Make a Shop | Make a Merchant', enabled: false },
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'plans',
|
|
||||||
title: 'Plan Selection + EV Condition',
|
|
||||||
description: 'Manage investment plans, swap station plans, and rider request plans',
|
|
||||||
icon: ClipboardList,
|
|
||||||
permissions: [
|
|
||||||
{ key: 'plans.investment.view', label: 'View Investment Plan', enabled: false },
|
|
||||||
{ key: 'plans.investment.edit', label: 'Edit Investment Plan', enabled: false },
|
|
||||||
{ key: 'plans.swap_station.view', label: 'View Swap Station Plan', enabled: false },
|
|
||||||
{ key: 'plans.swap_station.edit', label: 'Edit Swap Station Plan', enabled: false },
|
|
||||||
{ key: 'plans.rider_request.view', label: 'View Rider Request Plan', enabled: false },
|
|
||||||
{ key: 'plans.rider_request.edit', label: 'Edit Rider Request Plan', enabled: false },
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'settings',
|
id: 'settings',
|
||||||
title: 'Settings',
|
title: 'Settings',
|
||||||
description: 'Manage system settings including KYC documents and plans',
|
description: 'Manage KYC documents, plans, company policies, and system configuration',
|
||||||
icon: Settings,
|
icon: Settings,
|
||||||
permissions: [
|
permissionPairs: [
|
||||||
{ key: 'settings.kyc_documents.view', label: 'View KYC Documents', enabled: false },
|
{
|
||||||
{ key: 'settings.kyc_documents.edit', label: 'Edit KYC Documents', enabled: false },
|
label: 'KYC Documents',
|
||||||
{ key: 'settings.plan_selection.view', label: 'View Plan Selection with Condition', enabled: false },
|
view: { key: 'settings.kyc_documents.view', label: 'View', enabled: false },
|
||||||
{ key: 'settings.plan_selection.edit', label: 'Edit Plan Selection with Condition', enabled: false },
|
edit: { key: 'settings.kyc_documents.edit', label: 'Edit', enabled: false },
|
||||||
{ key: 'settings.company_policy.view', label: 'View Company Policy', enabled: false },
|
},
|
||||||
{ key: 'settings.company_policy.edit', label: 'Edit Company Policy', enabled: false },
|
{
|
||||||
]
|
label: 'Plan Selection with Condition',
|
||||||
|
view: { key: 'settings.plan_selection.view', label: 'View', enabled: false },
|
||||||
|
edit: { key: 'settings.plan_selection.edit', label: 'Edit', enabled: false },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Investment Plan',
|
||||||
|
view: { key: 'settings.investment_plan.view', label: 'View', enabled: false },
|
||||||
|
edit: { key: 'settings.investment_plan.edit', label: 'Edit', enabled: false },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Swap Station Plan',
|
||||||
|
view: { key: 'settings.swap_station_plan.view', label: 'View', enabled: false },
|
||||||
|
edit: { key: 'settings.swap_station_plan.edit', label: 'Edit', enabled: false },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Rider Request Plan',
|
||||||
|
view: { key: 'settings.rider_request_plan.view', label: 'View', enabled: false },
|
||||||
|
edit: { key: 'settings.rider_request_plan.edit', label: 'Edit', enabled: false },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Company Policy',
|
||||||
|
view: { key: 'settings.company_policy.view', label: 'View', enabled: false },
|
||||||
|
edit: { key: 'settings.company_policy.edit', label: 'Edit', enabled: false },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
permissions: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'dashboard',
|
id: 'dashboard',
|
||||||
@@ -397,17 +417,59 @@ export default function RolesPage() {
|
|||||||
setRoles(roles.map(r => r.id === selectedRole.id ? { ...selectedRole, permissionGroups: updated } : r));
|
setRoles(roles.map(r => r.id === selectedRole.id ? { ...selectedRole, permissionGroups: updated } : r));
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleGroupAll = (groupIndex: number, enabled: boolean) => {
|
const togglePermissionPair = (groupIndex: number, pairIndex: number, type: 'view' | 'edit') => {
|
||||||
if (!selectedRole) return;
|
if (!selectedRole) return;
|
||||||
const updated = [...selectedRole.permissionGroups];
|
const updated = [...selectedRole.permissionGroups];
|
||||||
updated[groupIndex].permissions = updated[groupIndex].permissions.map(p => ({ ...p, enabled }));
|
const pair = updated[groupIndex].permissionPairs![pairIndex];
|
||||||
|
pair[type].enabled = !pair[type].enabled;
|
||||||
setSelectedRole({ ...selectedRole, permissionGroups: updated });
|
setSelectedRole({ ...selectedRole, permissionGroups: updated });
|
||||||
setRoles(roles.map(r => r.id === selectedRole.id ? { ...selectedRole, permissionGroups: updated } : r));
|
setRoles(roles.map(r => r.id === selectedRole.id ? { ...selectedRole, permissionGroups: updated } : r));
|
||||||
};
|
};
|
||||||
|
|
||||||
const isGroupAllEnabled = (group: PermissionGroup) => group.permissions.every(p => p.enabled);
|
const toggleGroupAll = (groupIndex: number, enabled: boolean) => {
|
||||||
const getEnabledCount = (group: PermissionGroup) => group.permissions.filter(p => p.enabled).length;
|
if (!selectedRole) return;
|
||||||
const getTotalEnabled = (role: Role) => role.permissionGroups.reduce((a, g) => a + g.permissions.filter(p => p.enabled).length, 0);
|
const updated = [...selectedRole.permissionGroups];
|
||||||
|
const group = updated[groupIndex];
|
||||||
|
if (group.permissionPairs) {
|
||||||
|
group.permissionPairs = group.permissionPairs.map(p => ({
|
||||||
|
...p,
|
||||||
|
view: { ...p.view, enabled },
|
||||||
|
edit: { ...p.edit, enabled },
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
group.permissions = group.permissions.map(p => ({ ...p, enabled }));
|
||||||
|
}
|
||||||
|
setSelectedRole({ ...selectedRole, permissionGroups: updated });
|
||||||
|
setRoles(roles.map(r => r.id === selectedRole.id ? { ...selectedRole, permissionGroups: updated } : r));
|
||||||
|
};
|
||||||
|
|
||||||
|
const isGroupAllEnabled = (group: PermissionGroup) => {
|
||||||
|
if (group.permissionPairs) {
|
||||||
|
return group.permissionPairs.every(p => p.view.enabled && p.edit.enabled);
|
||||||
|
}
|
||||||
|
return group.permissions.every(p => p.enabled);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getEnabledCount = (group: PermissionGroup) => {
|
||||||
|
if (group.permissionPairs) {
|
||||||
|
return group.permissionPairs.filter(p => p.view.enabled && p.edit.enabled).length;
|
||||||
|
}
|
||||||
|
return group.permissions.filter(p => p.enabled).length;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTotalCount = (group: PermissionGroup) => {
|
||||||
|
if (group.permissionPairs) {
|
||||||
|
return group.permissionPairs.length;
|
||||||
|
}
|
||||||
|
return group.permissions.length;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTotalEnabled = (role: Role) => role.permissionGroups.reduce((a, g) => {
|
||||||
|
if (g.permissionPairs) {
|
||||||
|
return a + g.permissionPairs.filter(p => p.view.enabled && p.edit.enabled).length;
|
||||||
|
}
|
||||||
|
return a + g.permissions.filter(p => p.enabled).length;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
const toggleGroup = (groupId: string) => {
|
const toggleGroup = (groupId: string) => {
|
||||||
setExpandedGroups(prev => ({ ...prev, [groupId]: !prev[groupId] }));
|
setExpandedGroups(prev => ({ ...prev, [groupId]: !prev[groupId] }));
|
||||||
@@ -522,7 +584,7 @@ export default function RolesPage() {
|
|||||||
<group.icon className="w-5 h-5 text-slate-600" />
|
<group.icon className="w-5 h-5 text-slate-600" />
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-slate-700">{group.title}</p>
|
<p className="text-sm font-medium text-slate-700">{group.title}</p>
|
||||||
<p className="text-xs text-slate-400">{getEnabledCount(group)}/{group.permissions.length} enabled</p>
|
<p className="text-xs text-slate-400">{getEnabledCount(group)}/{getTotalCount(group)} enabled</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@@ -547,25 +609,66 @@ export default function RolesPage() {
|
|||||||
<div className="px-3 py-2 bg-slate-50">
|
<div className="px-3 py-2 bg-slate-50">
|
||||||
<p className="text-xs text-slate-500">{group.description}</p>
|
<p className="text-xs text-slate-500">{group.description}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="divide-y divide-slate-100">
|
{group.permissionPairs ? (
|
||||||
{group.permissions.map((perm, pi) => (
|
<div className="divide-y divide-slate-200">
|
||||||
<div key={perm.key} className="flex items-center justify-between px-3 py-2.5">
|
{group.permissionPairs.map((pair, pairIdx) => (
|
||||||
<div className="flex items-center gap-2">
|
<div key={pair.label}>
|
||||||
{perm.enabled ? (
|
<div className="flex items-center justify-between px-3 py-2.5">
|
||||||
<Check className="w-4 h-4 text-green-600" />
|
<span className="text-sm font-medium text-slate-700">{pair.label}</span>
|
||||||
) : (
|
<div className="flex items-center gap-4">
|
||||||
<X className="w-4 h-4 text-slate-300" />
|
<div className="flex items-center gap-2">
|
||||||
)}
|
{pair.view.enabled ? (
|
||||||
<span className="text-sm text-slate-700">{perm.label}</span>
|
<Check className="w-4 h-4 text-green-600" />
|
||||||
<span className="text-xs text-slate-400 font-mono">{perm.key}</span>
|
) : (
|
||||||
|
<X className="w-4 h-4 text-slate-300" />
|
||||||
|
)}
|
||||||
|
<span className="text-xs text-slate-600">View</span>
|
||||||
|
<Toggle
|
||||||
|
checked={pair.view.enabled}
|
||||||
|
onChange={() => togglePermissionPair(gi, pairIdx, 'view')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{pair.edit.enabled ? (
|
||||||
|
<Check className="w-4 h-4 text-green-600" />
|
||||||
|
) : (
|
||||||
|
<X className="w-4 h-4 text-slate-300" />
|
||||||
|
)}
|
||||||
|
<span className="text-xs text-slate-600">Edit</span>
|
||||||
|
<Toggle
|
||||||
|
checked={pair.edit.enabled}
|
||||||
|
onChange={() => togglePermissionPair(gi, pairIdx, 'edit')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* {pairIdx < (group.permissionPairs?.length || 0) - 1 && (
|
||||||
|
<hr className="my-2 border-slate-200" />
|
||||||
|
)} */}
|
||||||
</div>
|
</div>
|
||||||
<Toggle
|
))}
|
||||||
checked={perm.enabled}
|
</div>
|
||||||
onChange={() => togglePermission(gi, pi)}
|
) : (
|
||||||
/>
|
<div className="divide-y divide-slate-100">
|
||||||
</div>
|
{group.permissions.map((perm, pi) => (
|
||||||
))}
|
<div key={perm.key} className="flex items-center justify-between px-3 py-2.5">
|
||||||
</div>
|
<div className="flex items-center gap-2">
|
||||||
|
{perm.enabled ? (
|
||||||
|
<Check className="w-4 h-4 text-green-600" />
|
||||||
|
) : (
|
||||||
|
<X className="w-4 h-4 text-slate-300" />
|
||||||
|
)}
|
||||||
|
<span className="text-sm text-slate-700">{perm.label}</span>
|
||||||
|
<span className="text-xs text-slate-400 font-mono">{perm.key}</span>
|
||||||
|
</div>
|
||||||
|
<Toggle
|
||||||
|
checked={perm.enabled}
|
||||||
|
onChange={() => togglePermission(gi, pi)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user