2026-04-26 18:32:52 +06:00
'use client' ;
import { useState } from 'react' ;
2026-05-06 02:44:45 +06:00
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' ;
2026-04-26 18:32:52 +06:00
interface Permission {
2026-05-06 02:27:10 +06:00
key : string ;
label : string ;
enabled : boolean ;
2026-05-06 02:44:45 +06:00
divider? : boolean ;
pair? : boolean ;
}
interface PermissionPair {
label : string ;
view : Permission ;
edit : Permission ;
2026-05-06 02:27:10 +06:00
}
interface PermissionGroup {
id : string ;
title : string ;
description : string ;
icon : React.ComponentType < { className? : string } > ;
permissions : Permission [ ] ;
2026-05-06 02:44:45 +06:00
permissionPairs? : PermissionPair [ ] ;
2026-04-26 18:32:52 +06:00
}
interface Role {
id : string ;
name : string ;
description : string ;
isDefault : boolean ;
2026-05-06 02:27:10 +06:00
permissionGroups : PermissionGroup [ ] ;
2026-04-26 18:32:52 +06:00
}
2026-05-06 02:27:10 +06:00
const buildDefaultGroups = ( ) : PermissionGroup [ ] = > [
{
id : 'kyc' ,
title : 'KYC Requests & Verification' ,
description : 'The Biker user will request from the app. Investor, Shop, Merchant will request from the website. Front desk officers (hub/head office) can request for Biker, Investor, Shop, Merchant and can upload remaining documents. Admin officers (head office) will approve or reject documents with notes and "make a Biker | Investor | Shop | Merchant".' ,
icon : FileSearch ,
permissions : [
{ key : 'kyc.request' , label : 'KYC Request' , enabled : false } ,
{ key : 'kyc.view' , label : 'View KYC Requests' , enabled : false } ,
{ key : 'kyc.doc_upload' , label : 'Document Upload' , enabled : false } ,
{ key : 'kyc.doc_approve' , label : 'Document Approve' , enabled : false } ,
{ key : 'kyc.doc_reject' , label : 'Document Reject' , enabled : false } ,
2026-05-06 02:44:45 +06:00
{ key : 'kyc.make_valid_user' , label : 'Make a Biker | Make an Investor | Make a Shop | Make a Merchant' , enabled : false } ,
2026-05-06 02:27:10 +06:00
]
} ,
{
id : 'settings' ,
title : 'Settings' ,
2026-05-06 02:44:45 +06:00
description : 'Manage KYC documents, plans, company policies, and system configuration' ,
2026-05-06 02:27:10 +06:00
icon : Settings ,
2026-05-06 02:44:45 +06:00
permissionPairs : [
{
label : 'KYC Documents' ,
view : { key : 'settings.kyc_documents.view' , label : 'View' , enabled : false } ,
edit : { key : 'settings.kyc_documents.edit' , label : 'Edit' , 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 : [ ] ,
2026-05-06 02:27:10 +06:00
} ,
{
id : 'dashboard' ,
title : 'Dashboard' ,
description : 'Access to main dashboard' ,
icon : BarChart3 ,
permissions : [
{ key : 'dashboard.view' , label : 'View Dashboard' , enabled : false } ,
]
} ,
{
id : 'rentals' ,
title : 'Rentals' ,
description : 'Manage rental operations' ,
icon : Bike ,
permissions : [
{ key : 'rentals.view' , label : 'View' , enabled : false } ,
{ key : 'rentals.create' , label : 'Create' , enabled : false } ,
{ key : 'rentals.edit' , label : 'Edit' , enabled : false } ,
{ key : 'rentals.delete' , label : 'Delete' , enabled : false } ,
]
} ,
{
id : 'bikers' ,
title : 'Bikers' ,
description : 'Manage bikers' ,
icon : Users ,
permissions : [
{ key : 'bikers.view' , label : 'View' , enabled : false } ,
{ key : 'bikers.create' , label : 'Create' , enabled : false } ,
{ key : 'bikers.edit' , label : 'Edit' , enabled : false } ,
{ key : 'bikers.delete' , label : 'Delete' , enabled : false } ,
]
} ,
{
id : 'investors' ,
title : 'Investors' ,
description : 'Manage investors' ,
icon : Briefcase ,
permissions : [
{ key : 'investors.view' , label : 'View' , enabled : false } ,
{ key : 'investors.create' , label : 'Create' , enabled : false } ,
{ key : 'investors.edit' , label : 'Edit' , enabled : false } ,
{ key : 'investors.delete' , label : 'Delete' , enabled : false } ,
]
} ,
{
id : 'fleet' ,
title : 'Fleet' ,
description : 'Manage fleet vehicles' ,
icon : Truck ,
permissions : [
{ key : 'fleet.view' , label : 'View' , enabled : false } ,
{ key : 'fleet.create' , label : 'Create' , enabled : false } ,
{ key : 'fleet.edit' , label : 'Edit' , enabled : false } ,
{ key : 'fleet.delete' , label : 'Delete' , enabled : false } ,
]
} ,
{
id : 'merchants' ,
title : 'Merchants' ,
description : 'Manage merchants' ,
icon : Store ,
permissions : [
{ key : 'merchants.view' , label : 'View' , enabled : false } ,
{ key : 'merchants.create' , label : 'Create' , enabled : false } ,
{ key : 'merchants.edit' , label : 'Edit' , enabled : false } ,
{ key : 'merchants.delete' , label : 'Delete' , enabled : false } ,
]
} ,
{
id : 'swap_stations' ,
title : 'Swap Stations' ,
description : 'Manage swap stations' ,
icon : BatteryCharging ,
permissions : [
{ key : 'swap_stations.view' , label : 'View' , enabled : false } ,
{ key : 'swap_stations.create' , label : 'Create' , enabled : false } ,
{ key : 'swap_stations.edit' , label : 'Edit' , enabled : false } ,
{ key : 'swap_stations.delete' , label : 'Delete' , enabled : false } ,
]
} ,
{
id : 'hubs' ,
title : 'Hubs' ,
description : 'Manage hubs' ,
icon : Building2 ,
permissions : [
{ key : 'hubs.view' , label : 'View' , enabled : false } ,
{ key : 'hubs.create' , label : 'Create' , enabled : false } ,
{ key : 'hubs.edit' , label : 'Edit' , enabled : false } ,
{ key : 'hubs.delete' , label : 'Delete' , enabled : false } ,
]
} ,
{
id : 'maintenance' ,
title : 'Maintenance' ,
description : 'Manage maintenance requests' ,
icon : Wrench ,
permissions : [
{ key : 'maintenance.view' , label : 'View' , enabled : false } ,
{ key : 'maintenance.create' , label : 'Create' , enabled : false } ,
{ key : 'maintenance.edit' , label : 'Edit' , enabled : false } ,
{ key : 'maintenance.delete' , label : 'Delete' , enabled : false } ,
]
} ,
{
id : 'accounting' ,
title : 'Accounting' ,
description : 'Manage financial records' ,
icon : DollarSign ,
permissions : [
{ key : 'accounting.view' , label : 'View' , enabled : false } ,
{ key : 'accounting.create' , label : 'Create' , enabled : false } ,
{ key : 'accounting.edit' , label : 'Edit' , enabled : false } ,
{ key : 'accounting.delete' , label : 'Delete' , enabled : false } ,
]
} ,
{
id : 'reports' ,
title : 'Reports' ,
description : 'View and generate reports' ,
icon : TrendingUp ,
permissions : [
{ key : 'reports.view' , label : 'View' , enabled : false } ,
{ key : 'reports.export' , label : 'Export' , enabled : false } ,
]
} ,
{
id : 'users' ,
title : 'Users' ,
description : 'Manage system users' ,
icon : UserCog ,
permissions : [
{ key : 'users.view' , label : 'View' , enabled : false } ,
{ key : 'users.create' , label : 'Create' , enabled : false } ,
{ key : 'users.edit' , label : 'Edit' , enabled : false } ,
{ key : 'users.delete' , label : 'Delete' , enabled : false } ,
]
} ,
2026-04-26 18:32:52 +06:00
] ;
const mockRoles : Role [ ] = [
{
id : 'ROLE-001' ,
name : 'Admin' ,
description : 'Full system access with all permissions' ,
isDefault : false ,
2026-05-06 02:27:10 +06:00
permissionGroups : buildDefaultGroups ( ) . map ( g = > ( {
. . . g ,
permissions : g.permissions.map ( p = > ( { . . . p , enabled : true } ) )
} ) )
2026-04-26 18:32:52 +06:00
} ,
{
id : 'ROLE-002' ,
name : 'Manager' ,
description : 'Management access with limited delete permissions' ,
isDefault : false ,
2026-05-06 02:27:10 +06:00
permissionGroups : buildDefaultGroups ( ) . map ( g = > ( {
. . . g ,
permissions : g.permissions.map ( p = > ( {
. . . p ,
enabled : ! p . key . includes ( 'delete' )
} ) )
2026-04-26 18:32:52 +06:00
} ) )
} ,
{
id : 'ROLE-003' ,
2026-05-06 02:27:10 +06:00
name : 'Front Desk Officer' ,
description : 'Hub/head office officer - can request KYC and upload documents' ,
2026-04-26 18:32:52 +06:00
isDefault : false ,
2026-05-06 02:27:10 +06:00
permissionGroups : buildDefaultGroups ( ) . map ( g = > {
if ( g . id === 'kyc' ) {
return {
. . . g ,
permissions : g.permissions.map ( p = > ( {
. . . p ,
enabled : [ 'kyc.request' , 'kyc.view' , 'kyc.doc_upload' ] . includes ( p . key )
} ) )
} ;
}
if ( g . id === 'dashboard' ) {
return { . . . g , permissions : g.permissions.map ( p = > ( { . . . p , enabled : true } ) ) } ;
}
return g ;
} )
2026-04-26 18:32:52 +06:00
} ,
{
id : 'ROLE-004' ,
2026-05-06 02:27:10 +06:00
name : 'Admin Officer' ,
description : 'Head office officer - can approve/reject KYC documents and make valid users' ,
2026-04-26 18:32:52 +06:00
isDefault : false ,
2026-05-06 02:27:10 +06:00
permissionGroups : buildDefaultGroups ( ) . map ( g = > {
if ( g . id === 'kyc' ) {
return {
. . . g ,
permissions : g.permissions.map ( p = > ( {
. . . p ,
enabled : [ 'kyc.view' , 'kyc.doc_approve' , 'kyc.doc_reject' , 'kyc.make_valid_user' ] . includes ( p . key )
} ) )
} ;
}
if ( g . id === 'dashboard' ) {
return { . . . g , permissions : g.permissions.map ( p = > ( { . . . p , enabled : true } ) ) } ;
}
return g ;
} )
2026-04-26 18:32:52 +06:00
} ,
{
id : 'ROLE-005' ,
2026-05-06 02:27:10 +06:00
name : 'Biker' ,
description : 'Limited access for bike riders - can view dashboard, request KYC, manage rentals' ,
2026-04-26 18:32:52 +06:00
isDefault : false ,
2026-05-06 02:27:10 +06:00
permissionGroups : buildDefaultGroups ( ) . map ( g = > {
if ( g . id === 'dashboard' ) {
return { . . . g , permissions : g.permissions.map ( p = > ( { . . . p , enabled : true } ) ) } ;
}
if ( g . id === 'kyc' ) {
return {
. . . g ,
permissions : g.permissions.map ( p = > ( {
. . . p ,
enabled : [ 'kyc.request' , 'kyc.view' ] . includes ( p . key )
} ) )
} ;
}
if ( g . id === 'rentals' ) {
return {
. . . g ,
permissions : g.permissions.map ( p = > ( {
. . . p ,
enabled : [ 'rentals.view' , 'rentals.create' ] . includes ( p . key )
} ) )
} ;
}
return g ;
} )
2026-04-26 18:32:52 +06:00
} ,
] ;
2026-05-06 02:27:10 +06:00
function Toggle ( { checked , onChange , color = 'blue' } : { checked : boolean ; onChange : ( ) = > void ; color? : string } ) {
const colors : Record < string , string > = {
blue : checked ? 'bg-blue-600' : 'bg-slate-200' ,
green : checked ? 'bg-green-600' : 'bg-slate-200' ,
amber : checked ? 'bg-amber-600' : 'bg-slate-200' ,
red : checked ? 'bg-red-600' : 'bg-slate-200' ,
} ;
return (
< button
onClick = { onChange }
className = { ` relative inline-flex h-5 w-9 items-center rounded-full transition-colors ${ colors [ color ] || colors . blue } ` }
>
< span
className = { ` inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${ checked ? 'translate-x-4' : 'translate-x-1'
} ` }
/ >
< / button >
) ;
}
2026-04-26 18:32:52 +06:00
export default function RolesPage() {
const [ roles , setRoles ] = useState < Role [ ] > ( mockRoles ) ;
const [ search , setSearch ] = useState ( '' ) ;
const [ showCreateModal , setShowCreateModal ] = useState ( false ) ;
const [ editingRole , setEditingRole ] = useState < Role | null > ( null ) ;
const [ selectedRole , setSelectedRole ] = useState < Role | null > ( null ) ;
2026-05-06 02:27:10 +06:00
const [ expandedGroups , setExpandedGroups ] = useState < Record < string , boolean > > ( { } ) ;
const [ formData , setFormData ] = useState ( { name : '' , description : '' } ) ;
2026-04-26 18:32:52 +06:00
2026-05-06 02:27:10 +06:00
const filteredRoles = roles . filter ( r = >
2026-04-26 18:32:52 +06:00
r . name . toLowerCase ( ) . includes ( search . toLowerCase ( ) ) ||
r . description . toLowerCase ( ) . includes ( search . toLowerCase ( ) )
) ;
const handleSave = ( ) = > {
if ( ! formData . name ) return ;
if ( editingRole ) {
setRoles ( roles . map ( r = > r . id === editingRole . id ? { . . . r , . . . formData , isDefault : false } : r ) ) ;
} else {
const newRole : Role = {
id : ` ROLE- ${ String ( roles . length + 1 ) . padStart ( 3 , '0' ) } ` ,
. . . formData ,
isDefault : false ,
2026-05-06 02:27:10 +06:00
permissionGroups : buildDefaultGroups ( )
2026-04-26 18:32:52 +06:00
} ;
setRoles ( [ . . . roles , newRole ] ) ;
}
setShowCreateModal ( false ) ;
setEditingRole ( null ) ;
setFormData ( { name : '' , description : '' } ) ;
} ;
const handleDelete = ( id : string ) = > {
if ( confirm ( 'Are you sure you want to delete this role?' ) ) {
setRoles ( roles . filter ( r = > r . id !== id ) ) ;
setSelectedRole ( null ) ;
}
} ;
const handleDuplicate = ( role : Role ) = > {
const newRole : Role = {
id : ` ROLE- ${ String ( roles . length + 1 ) . padStart ( 3 , '0' ) } ` ,
name : ` ${ role . name } (Copy) ` ,
description : role.description ,
isDefault : false ,
2026-05-06 02:27:10 +06:00
permissionGroups : role.permissionGroups.map ( g = > ( {
. . . g ,
permissions : g.permissions.map ( p = > ( { . . . p } ) )
} ) )
2026-04-26 18:32:52 +06:00
} ;
setRoles ( [ . . . roles , newRole ] ) ;
2026-05-06 02:27:10 +06:00
setSelectedRole ( newRole ) ;
2026-04-26 18:32:52 +06:00
} ;
const openEdit = ( role : Role ) = > {
setEditingRole ( role ) ;
2026-05-06 02:27:10 +06:00
setFormData ( { name : role.name , description : role.description } ) ;
2026-04-26 18:32:52 +06:00
setShowCreateModal ( true ) ;
} ;
2026-05-06 02:27:10 +06:00
const togglePermission = ( groupIndex : number , permIndex : number ) = > {
2026-04-26 18:32:52 +06:00
if ( ! selectedRole ) return ;
2026-05-06 02:27:10 +06:00
const updated = [ . . . selectedRole . permissionGroups ] ;
const perm = updated [ groupIndex ] . permissions [ permIndex ] ;
perm . enabled = ! perm . enabled ;
setSelectedRole ( { . . . selectedRole , permissionGroups : updated } ) ;
setRoles ( roles . map ( r = > r . id === selectedRole . id ? { . . . selectedRole , permissionGroups : updated } : r ) ) ;
2026-04-26 18:32:52 +06:00
} ;
2026-05-06 02:44:45 +06:00
const togglePermissionPair = ( groupIndex : number , pairIndex : number , type : 'view' | 'edit' ) = > {
if ( ! selectedRole ) return ;
const updated = [ . . . selectedRole . permissionGroups ] ;
const pair = updated [ groupIndex ] . permissionPairs ! [ pairIndex ] ;
pair [ type ] . enabled = ! pair [ type ] . enabled ;
setSelectedRole ( { . . . selectedRole , permissionGroups : updated } ) ;
setRoles ( roles . map ( r = > r . id === selectedRole . id ? { . . . selectedRole , permissionGroups : updated } : r ) ) ;
} ;
2026-05-06 02:27:10 +06:00
const toggleGroupAll = ( groupIndex : number , enabled : boolean ) = > {
if ( ! selectedRole ) return ;
const updated = [ . . . selectedRole . permissionGroups ] ;
2026-05-06 02:44:45 +06:00
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 } ) ) ;
}
2026-05-06 02:27:10 +06:00
setSelectedRole ( { . . . selectedRole , permissionGroups : updated } ) ;
setRoles ( roles . map ( r = > r . id === selectedRole . id ? { . . . selectedRole , permissionGroups : updated } : r ) ) ;
} ;
2026-05-06 02:44:45 +06:00
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 ) ;
2026-05-06 02:27:10 +06:00
const toggleGroup = ( groupId : string ) = > {
setExpandedGroups ( prev = > ( { . . . prev , [ groupId ] : ! prev [ groupId ] } ) ) ;
2026-04-26 18:32:52 +06:00
} ;
return (
< div className = "p-4 lg:p-6" >
< div className = "flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4 mb-6" >
< div >
< h1 className = "text-2xl lg:text-3xl font-extrabold text-slate-800" > Roles & Permissions < / h1 >
< p className = "text-sm text-slate-500 mt-1" > Manage roles and access permissions < / p >
< / div >
2026-05-06 02:27:10 +06:00
< button
2026-04-26 18:32:52 +06:00
onClick = { ( ) = > {
setEditingRole ( null ) ;
setFormData ( { name : '' , description : '' } ) ;
setShowCreateModal ( true ) ;
} }
className = "py-2.5 px-4 bg-accent text-white rounded-lg font-semibold text-sm hover:bg-accent-dark transition-colors flex items-center gap-2"
>
< Plus className = "w-4 h-4" / > Create Role
< / button >
< / div >
< div className = "grid grid-cols-2 lg:grid-cols-4 gap-4 mb-6" >
< div className = "bg-white rounded-xl p-5 shadow-sm border border-slate-100" >
< p className = "text-2xl font-extrabold text-slate-800" > { roles . length } < / p >
< p className = "text-sm text-slate-500" > Total Roles < / p >
< / div >
< div className = "bg-white rounded-xl p-5 shadow-sm border border-slate-100" >
< p className = "text-2xl font-extrabold text-green-600" > { roles . filter ( r = > r . isDefault ) . length } < / p >
< p className = "text-sm text-slate-500" > Default Roles < / p >
< / div >
< div className = "bg-white rounded-xl p-5 shadow-sm border border-slate-100" >
2026-05-06 02:27:10 +06:00
< p className = "text-2xl font-extrabold text-blue-600" > { roles . reduce ( ( a , r ) = > a + getTotalEnabled ( r ) , 0 ) } < / p >
< p className = "text-sm text-slate-500" > Total Permissions < / p >
2026-04-26 18:32:52 +06:00
< / div >
< div className = "bg-white rounded-xl p-5 shadow-sm border border-slate-100" >
2026-05-06 02:27:10 +06:00
< p className = "text-2xl font-extrabold text-purple-600" > { selectedRole ? getTotalEnabled ( selectedRole ) : '-' } < / p >
< p className = "text-sm text-slate-500" > Selected Role Perms < / p >
2026-04-26 18:32:52 +06:00
< / div >
< / div >
< div className = "grid lg:grid-cols-3 gap-6" >
< div className = "lg:col-span-1 bg-white rounded-xl shadow-sm border border-slate-100" >
< div className = "p-4 border-b border-slate-100" >
< div className = "relative" >
< Search className = "absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" / >
2026-05-06 02:27:10 +06:00
< input
type = "text"
placeholder = "Search roles..."
2026-04-26 18:32:52 +06:00
value = { search }
onChange = { ( e ) = > setSearch ( e . target . value ) }
className = "w-full pl-10 pr-4 py-2 border border-slate-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
/ >
< / div >
< / div >
< div className = "divide-y divide-slate-50 max-h-[600px] overflow-y-auto" >
{ filteredRoles . map ( role = > (
2026-05-06 02:27:10 +06:00
< div
key = { role . id }
2026-04-26 18:32:52 +06:00
className = { ` p-4 hover:bg-slate-50 cursor-pointer transition-colors ${ selectedRole ? . id === role . id ? 'bg-accent-light' : '' } ` }
onClick = { ( ) = > setSelectedRole ( role ) }
>
< div className = "flex items-center justify-between" >
< div >
< p className = "text-sm font-medium text-slate-700" > { role . name } < / p >
< p className = "text-xs text-slate-400 truncate" > { role . description } < / p >
< / div >
{ role . isDefault && (
< span className = "text-xs px-2 py-0.5 bg-accent-light text-accent rounded-full" > Default < / span >
) }
< / div >
< div className = "flex items-center gap-2 mt-2 text-xs text-slate-400" >
2026-05-06 02:27:10 +06:00
< span > { getTotalEnabled ( role ) } permissions < / span >
2026-04-26 18:32:52 +06:00
< / div >
< / div >
) ) }
< / div >
< / div >
< div className = "lg:col-span-2 bg-white rounded-xl shadow-sm border border-slate-100" >
{ selectedRole ? (
< >
< div className = "p-4 border-b border-slate-100 flex items-center justify-between" >
< div >
< h3 className = "text-lg font-semibold text-slate-800" > { selectedRole . name } < / h3 >
< p className = "text-sm text-slate-500" > { selectedRole . description } < / p >
< / div >
< div className = "flex gap-2" >
< button onClick = { ( ) = > handleDuplicate ( selectedRole ) } className = "p-2 hover:bg-slate-100 rounded-lg text-slate-600" >
< Copy className = "w-4 h-4" / >
< / button >
< button onClick = { ( ) = > openEdit ( selectedRole ) } className = "p-2 hover:bg-slate-100 rounded-lg text-slate-600" >
< Edit className = "w-4 h-4" / >
< / button >
{ ! selectedRole . isDefault && (
< button onClick = { ( ) = > handleDelete ( selectedRole . id ) } className = "p-2 hover:bg-red-50 rounded-lg text-red-500" >
< Trash2 className = "w-4 h-4" / >
< / button >
) }
< / div >
< / div >
2026-05-06 02:27:10 +06:00
< div className = "p-4 space-y-3 max-h-[600px] overflow-y-auto" >
{ selectedRole . permissionGroups . map ( ( group , gi ) = > (
< div key = { group . id } className = "border border-slate-200 rounded-lg" >
< div
className = "flex items-center justify-between p-3 cursor-pointer hover:bg-slate-50"
onClick = { ( ) = > toggleGroup ( group . id ) }
>
< div className = "flex items-center gap-3" >
< group.icon className = "w-5 h-5 text-slate-600" / >
< div >
< p className = "text-sm font-medium text-slate-700" > { group . title } < / p >
2026-05-06 02:44:45 +06:00
< p className = "text-xs text-slate-400" > { getEnabledCount ( group ) } / { getTotalCount ( group ) } enabled < / p >
2026-05-06 02:27:10 +06:00
< / div >
< / div >
< div className = "flex items-center gap-2" >
< button
onClick = { ( e ) = > {
e . stopPropagation ( ) ;
toggleGroupAll ( gi , ! isGroupAllEnabled ( group ) ) ;
} }
className = "text-xs px-2 py-1 bg-slate-100 hover:bg-slate-200 rounded text-slate-600"
>
{ isGroupAllEnabled ( group ) ? 'Uncheck All' : 'Check All' }
< / button >
{ expandedGroups [ group . id ] ? (
< ChevronDown className = "w-4 h-4 text-slate-400" / >
) : (
< ChevronRight className = "w-4 h-4 text-slate-400" / >
) }
< / div >
< / div >
{ expandedGroups [ group . id ] && (
< div className = "border-t border-slate-200" >
< div className = "px-3 py-2 bg-slate-50" >
< p className = "text-xs text-slate-500" > { group . description } < / p >
< / div >
2026-05-06 02:44:45 +06:00
{ group . permissionPairs ? (
< div className = "divide-y divide-slate-200" >
{ group . permissionPairs . map ( ( pair , pairIdx ) = > (
< div key = { pair . label } >
< div className = "flex items-center justify-between px-3 py-2.5" >
< span className = "text-sm font-medium text-slate-700" > { pair . label } < / span >
< div className = "flex items-center gap-4" >
< div className = "flex items-center gap-2" >
{ pair . view . 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" > 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 >
{ / * { p a i r I d x < ( g r o u p . p e r m i s s i o n P a i r s ? . l e n g t h | | 0 ) - 1 & & (
< hr className = "my-2 border-slate-200" / >
) } * / }
2026-05-06 02:27:10 +06:00
< / div >
2026-05-06 02:44:45 +06:00
) ) }
< / div >
) : (
< div className = "divide-y divide-slate-100" >
{ group . permissions . map ( ( perm , pi ) = > (
< div key = { perm . key } className = "flex items-center justify-between px-3 py-2.5" >
< 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 >
) }
2026-05-06 02:27:10 +06:00
< / div >
) }
2026-04-26 18:32:52 +06:00
< / div >
2026-05-06 02:27:10 +06:00
) ) }
2026-04-26 18:32:52 +06:00
< / div >
< / >
) : (
< div className = "p-12 text-center text-slate-500" >
< Shield className = "w-12 h-12 mx-auto mb-4 text-slate-300" / >
< p className = "text-sm" > Select a role to view and edit permissions < / p >
< / div >
) }
< / div >
< / div >
{ showCreateModal && (
< div className = "fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4" >
< div className = "bg-white rounded-xl shadow-xl w-full max-w-md" >
< div className = "p-4 border-b border-slate-100 flex justify-between items-center" >
< h3 className = "font-semibold text-slate-800" > { editingRole ? 'Edit Role' : 'Create New Role' } < / h3 >
< button onClick = { ( ) = > setShowCreateModal ( false ) } className = "text-slate-400 hover:text-slate-600" >
< X className = "w-5 h-5" / >
< / button >
< / div >
< div className = "p-4 space-y-4" >
< div >
< label className = "text-sm text-slate-600" > Role Name * < / label >
< input
type = "text"
value = { formData . name }
onChange = { ( e ) = > setFormData ( { . . . formData , name : e.target.value } ) }
className = "w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
placeholder = "e.g., Super Manager"
/ >
< / div >
< div >
< label className = "text-sm text-slate-600" > Description < / label >
< textarea
value = { formData . description }
onChange = { ( e ) = > setFormData ( { . . . formData , description : e.target.value } ) }
className = "w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
rows = { 2 }
placeholder = "Describe what this role can do"
/ >
< / div >
< / div >
< div className = "p-4 border-t border-slate-100 flex justify-end gap-2" >
2026-05-06 02:27:10 +06:00
< button
onClick = { ( ) = > setShowCreateModal ( false ) }
2026-04-26 18:32:52 +06:00
className = "px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm"
>
Cancel
< / button >
2026-05-06 02:27:10 +06:00
< button
2026-04-26 18:32:52 +06:00
onClick = { handleSave }
disabled = { ! formData . name }
className = "px-4 py-2 bg-accent text-white rounded-lg text-sm disabled:opacity-50"
>
{ editingRole ? 'Update' : 'Create' }
< / button >
< / div >
< / div >
< / div >
) }
< / div >
) ;
}