cursor'
import { useState, useRef, useEffect, type JSX } from "react";
import { type ColDef } from "ag-grid-community";
import { Button } from "../../../Shared/Components/Button/Button";
import { Input } from "../../../Shared/Components/Input/Input";
import { Card, CardContent } from "../../../Shared/Components/Card/Card";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "../../../Shared/Components/Dialog/Dialog";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "../../../Shared/Components/Collapsible/Collapsible";
import { toast } from "sonner";
import { Switch } from "../../../Shared/Components/Switch/Switch";
import { Textarea } from "../../../Shared/Components/Textarea/Textarea";
import { ChevronDown, ChevronRight } from "lucide-react";
import AGGrid, { type AGGridRef } from "../../../Shared/Components/Ag-grid/Ag-grid";
import ActionsRenderer from "../../../Shared/Components/CellRenderer/CellRenderer";
const ROLES_STORAGE_KEY = 'user_management_roles';
const PERMISSIONS_STORAGE_KEY = 'user_management_permissions';
interface Permission {
label: string;
value: string;
field: string;
}
interface PermissionArea {
label: string;
key: string;
children: Permission[];
}
interface UserRole {
id: string;
role_id: string;
role_name: string;
description: string;
last_updated_time?: string;
last_updated_by?: string;
active?: boolean;
permissions?: Record<string, Record<string, boolean>>;
}
// Initialize permissions structure
const initializePermissions = () => {
if (!localStorage.getItem(PERMISSIONS_STORAGE_KEY)) {
const defaultPermissions: PermissionArea[] = [
{
label: "User Management",
key: "user_management",
children: [
{ label: "Create", value: "create", field: "user_management_create" },
{ label: "Read", value: "read", field: "user_management_read" },
{ label: "Update", value: "update", field: "user_management_update" },
{ label: "Delete", value: "delete", field: "user_management_delete" },
]
},
{
label: "Reports",
key: "reports",
children: [
{ label: "View", value: "view", field: "reports_view" },
{ label: "Export", value: "export", field: "reports_export" },
]
},
{
label: "Settings",
key: "settings",
children: [
{ label: "Configure", value: "configure", field: "settings_configure" },
{ label: "Manage", value: "manage", field: "settings_manage" },
]
}
];
localStorage.setItem(PERMISSIONS_STORAGE_KEY, JSON.stringify(defaultPermissions));
}
if (!localStorage.getItem(ROLES_STORAGE_KEY)) {
const mockRoles: UserRole[] = [
{
id: "role_1",
role_id: "role_1",
role_name: "Admin",
description: "Full system access",
permissions: {
user_management: { create: true, read: true, update: true, delete: true },
reports: { view: true, export: true },
settings: { configure: true, manage: true }
}
},
{
id: "role_2",
role_id: "role_2",
role_name: "QA-ADMIN",
description: "Quality assurance administrator",
permissions: {
user_management: { create: false, read: true, update: true, delete: false },
reports: { view: true, export: true },
settings: { configure: false, manage: false }
}
},
{
id: "role_3",
role_id: "role_3",
role_name: "Test new role",
description: "Test role for new features",
permissions: {
user_management: { create: false, read: true, update: false, delete: false },
reports: { view: true, export: false },
settings: { configure: false, manage: false }
}
},
{
id: "role_4",
role_id: "role_4",
role_name: "User",
description: "Standard user access",
permissions: {
user_management: { create: false, read: true, update: false, delete: false },
reports: { view: true, export: false },
settings: { configure: false, manage: false }
}
}
];
localStorage.setItem(ROLES_STORAGE_KEY, JSON.stringify(mockRoles));
}
};
function PermissionToggle({
label,
description,
checked,
onCheckedChange,
}: {
label: string;
description: string;
checked: boolean;
onCheckedChange: (checked: boolean) => void;
}) {
return (
<div className="flex items-center justify-between py-2">
<div className="flex-1">
<div className="flex items-center gap-3">
<Switch checked={checked} onCheckedChange={onCheckedChange} />
<div>
<p className="text-sm font-medium">{label}</p>
<p className="text-xs text-gray-500">{description}</p>
</div>
</div>
</div>
</div>
);
}
function PermissionSection({
title,
permissions,
permissionKeys,
onPermissionChange,
}: {
title: string;
permissions: Record<string, boolean>;
permissionKeys: string[];
onPermissionChange: (permission: string, value: boolean) => void;
}) {
const [isOpen, setIsOpen] = useState(true);
const permissionDescriptions = (key: string) => {
return `Can ${key.toLowerCase()} ${title.toLowerCase()}`;
};
return (
<Collapsible open={isOpen} onOpenChange={setIsOpen}>
<CollapsibleTrigger className="flex items-center justify-between w-full p-3 bg-gray-50 hover:bg-gray-100 rounded-lg transition-colors">
<h4 className="font-medium">{title}</h4>
{isOpen ? (
<ChevronDown className="w-4 h-4" />
) : (
<ChevronRight className="w-4 h-4" />
)}
</CollapsibleTrigger>
<CollapsibleContent className="px-3 pb-3">
<div className="space-y-2 pt-2">
{permissionKeys.map((key) => (
<PermissionToggle
key={key}
label={key.charAt(0).toUpperCase() + key.slice(1)}
description={permissionDescriptions(key)}
checked={permissions[key] || false}
onCheckedChange={(checked) => onPermissionChange(key, checked)}
/>
))}
</div>
</CollapsibleContent>
</Collapsible>
);
}
type UserRolesGridProps = {
onEdit: (role: UserRole) => void;
onDelete: (role: UserRole) => void;
refreshTrigger: number;
};
function UserRolesGrid({ onEdit, onDelete, refreshTrigger }: UserRolesGridProps) {
const gridRef = useRef<AGGridRef>(null);
const [columnDefs, setColumnDefs] = useState<ColDef[]>([]);
const [userRoleRowData, setUserRoleRowData] = useState<UserRole[]>([]);
const [loading, setLoading] = useState(true);
const handleActionClick = (actionType: string, row: UserRole) => {
switch (actionType) {
case "edit":
onEdit(row);
break;
case "delete":
onDelete(row);
break;
default:
console.warn("Unhandled action:", actionType);
}
};
const fetchData = () => {
try {
setLoading(true);
initializePermissions();
const storedRoles = localStorage.getItem(ROLES_STORAGE_KEY);
const roles: UserRole[] = storedRoles ? JSON.parse(storedRoles) : [];
const columns: ColDef[] = [
{
headerName: "Role Name",
field: "role_name",
sortable: true,
resizable: true,
},
{
headerName: "Description",
field: "description",
sortable: true,
resizable: true,
},
{
headerName: "Actions",
field: "actions",
width: 100,
cellRendererFramework: ActionsRenderer,
cellRendererParams: {
actions: [
{ action: "edit", tooltip: "Edit", type: "edit", class: "fa fa-pencil" },
{ action: "delete", tooltip: "Delete", type: "delete", class: "fa fa-trash" }
],
onActionClick: handleActionClick,
},
suppressMenu: true,
sortable: false,
filter: false,
resizable: false,
}
];
setColumnDefs(columns);
setUserRoleRowData(roles);
} catch (error) {
console.error("Failed to load roles from localStorage:", error);
toast.error("Failed to load roles");
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, [refreshTrigger]);
return (
<div className="space-y-4">
<div className="hidden md:block">
<div className="ag-theme-alpine" style={{ height: 400, width: "100%" }}>
{loading ? (
<div className="flex justify-center items-center h-full">
<div className="text-gray-500">Loading...</div>
</div>
) : userRoleRowData.length > 0 ? (
<AGGrid
ref={gridRef}
columnDefs={columnDefs}
cacheBlockSize={100}
maxBlocksInCache={5}
rowData={userRoleRowData}
height={400}
enableSorting={false}
enableFiltering={false}
enableResizing={true}
animateRows={true}
headerHeight={40}
rowHeight={35}
domLayout="normal"
/>
) : (
<div className="flex justify-center items-center h-full text-gray-500">
No user roles found.
</div>
)}
</div>
</div>
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between p-4 bg-white border border-[#b8bcbf]">
<div className="flex items-center space-x-6 text-sm font-['Roboto',sans-serif]">
<div className="flex items-center gap-1">
<span className="font-normal">Total Roles</span>
<span>:</span>
<span className="font-medium">{userRoleRowData.length}</span>
</div>
</div>
</div>
</div>
);
}
export default function UserRoles() {
const [showCreateForm, setShowCreateForm] = useState(false);
const [editingRole, setEditingRole] = useState<UserRole | null>(null);
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
const [deletingRole, setDeletingRole] = useState<UserRole | null>(null);
const [permissionAreas, setPermissionAreas] = useState<PermissionArea[]>([]);
const [userRoleFormData, setUserRoleFormData] = useState<Omit<UserRole, "id">>({
role_id: "",
description: "",
active: true,
role_name: "",
permissions: {}
});
const [refreshTrigger, setRefreshTrigger] = useState(0);
const handleEdit = (role: UserRole) => {
try {
initializePermissions();
const storedPermissions = localStorage.getItem(PERMISSIONS_STORAGE_KEY);
const permissionAreas: PermissionArea[] = storedPermissions ? JSON.parse(storedPermissions) : [];
setPermissionAreas(permissionAreas);
const initializedPermissions = permissionAreas.reduce(
(acc: any, area: { key: string | number; children: any[] }) => ({
...acc,
[area.key]: area.children.reduce(
(permAcc, perm) => ({
...permAcc,
[perm.value]: role.permissions?.[area.key]?.[perm.value] || false,
}),
{} as Record<string, boolean>
),
}),
{} as Record<string, Record<string, boolean>>
);
setUserRoleFormData({
role_name: role.role_name,
description: role.description,
permissions: initializedPermissions,
role_id: role.role_id
});
setEditingRole(role);
setShowCreateForm(true);
} catch (error) {
console.error("Failed to edit user role:", error);
toast.error("Failed to load role details. Please try again.");
}
};
const handleCreate = () => {
try {
initializePermissions();
const storedPermissions = localStorage.getItem(PERMISSIONS_STORAGE_KEY);
const permissionAreas: PermissionArea[] = storedPermissions ? JSON.parse(storedPermissions) : [];
setPermissionAreas(permissionAreas);
const initializedPermissions = permissionAreas.reduce(
(acc: any, area: { key: any; children: any[] }) => ({
...acc,
[area.key]: area.children.reduce(
(permAcc: any, perm: { value: any }) => ({
...permAcc,
[perm.value]: false,
}),
{} as Record<string, boolean>
),
}),
{} as Record<string, Record<string, boolean>>
);
setUserRoleFormData({
role_name: "",
description: "",
permissions: initializedPermissions,
role_id: "",
});
setShowCreateForm(true);
} catch (error) {
console.error("Failed to initialize form:", error);
toast.error("Failed to load permissions. Please try again.");
}
};
const handlePermissionChange = (section: string, permission: string, value: boolean) => {
setUserRoleFormData((prev) => ({
...prev,
permissions: {
...(prev.permissions ?? {}),
[section]: {
...(prev.permissions?.[section] ?? {}),
[permission]: value,
},
},
}));
};
const handleDelete = (role: UserRole) => {
setDeletingRole(role);
setShowDeleteDialog(true);
};
const confirmDeleteRole = () => {
if (!deletingRole) return;
try {
const storedRoles = localStorage.getItem(ROLES_STORAGE_KEY);
const roles: UserRole[] = storedRoles ? JSON.parse(storedRoles) : [];
const updatedRoles = roles.filter((role) => role.id !== deletingRole.id);
localStorage.setItem(ROLES_STORAGE_KEY, JSON.stringify(updatedRoles));
toast.success(`Role "${deletingRole.role_name}" has been deleted.`);
setShowDeleteDialog(false);
setDeletingRole(null);
setRefreshTrigger(prev => prev + 1);
} catch (error) {
console.error("Delete failed:", error);
toast.error("Failed to delete role. Please try again.");
}
};
const handleSave = () => {
if (!userRoleFormData.role_name.trim()) {
toast.error("Role name is required");
return;
}
try {
const storedRoles = localStorage.getItem(ROLES_STORAGE_KEY);
const roles: UserRole[] = storedRoles ? JSON.parse(storedRoles) : [];
if (editingRole) {
// Update existing role
const updatedRoles = roles.map((role) =>
role.id === editingRole.id
? {
...role,
role_name: userRoleFormData.role_name,
description: userRoleFormData.description,
permissions: userRoleFormData.permissions,
}
: role
);
localStorage.setItem(ROLES_STORAGE_KEY, JSON.stringify(updatedRoles));
toast.success("User role updated successfully");
} else {
// Create new role
const newRole: UserRole = {
...userRoleFormData,
id: `role_${Date.now()}`,
role_id: `role_${Date.now()}`,
};
roles.push(newRole);
localStorage.setItem(ROLES_STORAGE_KEY, JSON.stringify(roles));
toast.success("User role created successfully");
}
handleCancel();
setRefreshTrigger(prev => prev + 1);
} catch (error) {
console.error("Error saving user role:", error);
toast.error("Failed to save user role. Please try again.");
}
};
const handleCancel = () => {
setUserRoleFormData({
role_name: "",
description: "",
permissions: {},
role_id: "",
});
setShowCreateForm(false);
setEditingRole(null);
setPermissionAreas([]);
};
if (showCreateForm) {
return (
<div className="space-y-4">
<div className="flex justify-between items-center">
<div>
<h3 className="text-lg font-medium">
{editingRole ? "Edit User Role" : "Create New User Role"}
</h3>
<p className="text-sm text-gray-600 mt-1">
{editingRole
? "Update the role information and permissions below."
: "Fill in the role information and configure permissions below."}
</p>
</div>
<Button variant="outline" onClick={handleCancel}>
Back to Roles
</Button>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div className="space-y-6">
<Card>
<CardContent className="p-6">
<h4 className="font-medium mb-4">Role Information</h4>
<div className="space-y-4">
<div>
<label className="text-sm font-medium text-black mb-2 block">
Role Name *
</label>
<Input
value={userRoleFormData.role_name}
onChange={(e) =>
setUserRoleFormData({
...userRoleFormData,
role_name: e.target.value,
})
}
placeholder="Enter role name"
className="w-full"
required
/>
</div>
<div>
<label className="text-sm font-medium text-black mb-2 block">
Description
</label>
<Textarea
value={userRoleFormData.description}
onChange={(e) =>
setUserRoleFormData({
...userRoleFormData,
description: e.target.value,
})
}
placeholder="Enter role description"
className="w-full min-h-[80px]"
rows={3}
/>
</div>
</div>
</CardContent>
</Card>
</div>
<div className="space-y-6">
<Card>
<CardContent className="p-6">
<h4 className="font-medium mb-4">Access Permissions</h4>
<div className="space-y-3">
{permissionAreas.map((area) => (
<PermissionSection
key={area.key}
title={area.label}
permissions={userRoleFormData.permissions ? userRoleFormData.permissions[area.key] || {} : {}}
permissionKeys={area.children.map((perm) => perm.value)}
onPermissionChange={(permission, value) =>
handlePermissionChange(area.key, permission, value)
}
/>
))}
{!permissionAreas.length && (
<p className="text-sm text-gray-500">
No permission areas available. Please try again.
</p>
)}
</div>
</CardContent>
</Card>
<div className="flex justify-end gap-3">
<Button variant="outline" onClick={handleCancel}>
Cancel
</Button>
<Button
onClick={handleSave}
className="bg-[#000000] text-white hover:bg-gray-800"
>
{editingRole ? "Update Role" : "Create Role"}
</Button>
</div>
</div>
</div>
</div>
);
}
return (
<div className="space-y-4">
<div className="flex justify-end">
<Button
onClick={handleCreate}
className="bg-[#000000] text-white hover:bg-gray-800 h-[34px] px-4 rounded-[5px]"
>
Add Role +
</Button>
</div>
<UserRolesGrid
onEdit={handleEdit}
onDelete={handleDelete}
refreshTrigger={refreshTrigger}
/>
<Dialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle>Delete Role</DialogTitle>
<DialogDescription>
Are you sure you want to delete the role "{deletingRole?.role_name}"? This action cannot be undone.
</DialogDescription>
</DialogHeader>
<div className="py-4">
<p className="text-sm text-gray-600">
This will permanently remove the role from the system. Users assigned to this role may lose their permissions.
</p>
</div>
<div className="flex justify-end gap-3">
<Button variant="outline" onClick={() => setShowDeleteDialog(false)}>
Cancel
</Button>
<Button
onClick={confirmDeleteRole}
className="bg-red-600 hover:bg-red-700 text-white"
>
Delete Role
</Button>
</div>
</DialogContent>
</Dialog>
</div>
);
}
Comments
Post a Comment