From ef13117042d4d511cfceaa4ff5e9ebd71012abef Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Mon, 28 Sep 2026 10:58:02 +0200 Subject: [PATCH] Added Role column to user directory --- .../src/components/ui/StatusBadge.tsx | 24 +++++- .../src/lib/formatters.ts | 78 +++++++++++++++++++ .../src/pages/SettingsPage.tsx | 51 +++++++++++- PostFundManagement.ConsoleUI/src/types.ts | 8 +- 4 files changed, 153 insertions(+), 8 deletions(-) diff --git a/PostFundManagement.ConsoleUI/src/components/ui/StatusBadge.tsx b/PostFundManagement.ConsoleUI/src/components/ui/StatusBadge.tsx index ea872a3..9c845c8 100644 --- a/PostFundManagement.ConsoleUI/src/components/ui/StatusBadge.tsx +++ b/PostFundManagement.ConsoleUI/src/components/ui/StatusBadge.tsx @@ -1,6 +1,11 @@ import React from 'react'; -import { ApprovalStatus, Priority, ActivityStatus } from '../../types'; -import { getApprovalStatusBadge, getPriorityBadge, getActivityStatusBadge } from '../../lib/formatters'; +import { ApprovalStatus, Priority, ActivityStatus, UserRole } from '../../types'; +import { + getApprovalStatusBadge, + getPriorityBadge, + getActivityStatusBadge, + getUserRoleBadge, +} from '../../lib/formatters'; export const ApprovalStatusBadge: React.FC<{ status: ApprovalStatus | number | null | undefined }> = ({ status }) => { const badge = getApprovalStatusBadge(status ?? ApprovalStatus.Pending); @@ -29,3 +34,18 @@ export const ActivityStatusBadge: React.FC<{ status: ActivityStatus | number | s ); }; + +export const UserRoleBadge: React.FC<{ role: string | number | UserRole | null | undefined; className?: string }> = ({ + role, + className = '', +}) => { + const badge = getUserRoleBadge(role); + return ( + + {badge.dot && } + {badge.label} + + ); +}; diff --git a/PostFundManagement.ConsoleUI/src/lib/formatters.ts b/PostFundManagement.ConsoleUI/src/lib/formatters.ts index 349551e..c32f66d 100644 --- a/PostFundManagement.ConsoleUI/src/lib/formatters.ts +++ b/PostFundManagement.ConsoleUI/src/lib/formatters.ts @@ -13,6 +13,7 @@ import { NotificationPlatform, NotificationStatus, EmailStatuses, + UserRole, } from '../types'; export const formatZAR = (amount: number): string => { @@ -570,3 +571,80 @@ export const getUserEmail = (userIdOrEmail?: string | number | null): string => return str; }; + +// ========================================== +// User Role Badges & Formatter +// ========================================== + +export const USER_ROLE_OPTIONS = [ + { value: 'Admin', label: 'Admin' }, + { value: 'Executive', label: 'Executive' }, + { value: 'Supervisor', label: 'Supervisor' }, + { value: 'Manager', label: 'Manager' }, + { value: 'Contributor', label: 'Contributor' }, +]; + +export const getUserRoleBadge = (role?: string | number | UserRole | null) => { + if (role === null || role === undefined || role === '') { + return { + label: 'Contributor', + className: 'bg-emerald-500/15 text-emerald-300 border-emerald-500/30', + dot: 'bg-emerald-400', + }; + } + + const roleStr = String(role).trim().toLowerCase(); + + // 1. Admin - High-privilege Crimson/Rose + if (roleStr === 'admin' || roleStr === 'superadmin' || roleStr === '1') { + return { + label: 'Admin', + className: 'bg-rose-500/15 text-rose-300 border-rose-500/30 shadow-xs shadow-rose-500/10 font-semibold', + dot: 'bg-rose-400', + }; + } + + // 2. Executive - Strategic Amber/Gold + if (roleStr === 'executive' || roleStr === '2') { + return { + label: 'Executive', + className: 'bg-amber-500/15 text-amber-300 border-amber-500/30 shadow-xs shadow-amber-500/10 font-semibold', + dot: 'bg-amber-400', + }; + } + + // 3. Supervisor - Operational Governance Purple/Violet + if (roleStr === 'supervisor' || roleStr === '3') { + return { + label: 'Supervisor', + className: 'bg-purple-500/15 text-purple-300 border-purple-500/30 shadow-xs shadow-purple-500/10 font-semibold', + dot: 'bg-purple-400', + }; + } + + // 4. Manager - Programme & Project Management Blue/Sky + if (roleStr === 'manager' || roleStr === 'programmemanager' || roleStr === 'projectmanager' || roleStr === '4') { + return { + label: 'Manager', + className: 'bg-blue-500/15 text-blue-300 border-blue-500/30 shadow-xs shadow-blue-500/10 font-semibold', + dot: 'bg-blue-400', + }; + } + + // 5. Contributor - Execution & Outreach Emerald/Mint + if (roleStr === 'contributor' || roleStr === 'viewer' || roleStr === 'recipientviewer' || roleStr === '5') { + return { + label: 'Contributor', + className: 'bg-emerald-500/15 text-emerald-300 border-emerald-500/30 shadow-xs shadow-emerald-500/10 font-semibold', + dot: 'bg-emerald-400', + }; + } + + // Fallback for custom formatted role strings + const formatted = roleStr.charAt(0).toUpperCase() + roleStr.slice(1); + return { + label: formatted, + className: 'bg-cyan-500/15 text-cyan-300 border-cyan-500/30', + dot: 'bg-cyan-400', + }; +}; diff --git a/PostFundManagement.ConsoleUI/src/pages/SettingsPage.tsx b/PostFundManagement.ConsoleUI/src/pages/SettingsPage.tsx index 5a01acd..b3cde36 100644 --- a/PostFundManagement.ConsoleUI/src/pages/SettingsPage.tsx +++ b/PostFundManagement.ConsoleUI/src/pages/SettingsPage.tsx @@ -61,6 +61,7 @@ import { useToast } from '../components/ui/Toast'; import { ExportButton } from '../components/ui/ExportButton'; import { ExportColumn } from '../utils/exportUtils'; import { useTableSort, SortableTh } from '../utils/tableSort'; +import { UserRoleBadge } from '../components/ui/StatusBadge'; import { useAuth } from '../auth/AuthProvider'; import { EmailStatuses, @@ -84,6 +85,8 @@ import { getUserEmail, registerUserEmails, getActivityStatusBadge, + getUserRoleBadge, + USER_ROLE_OPTIONS, EMAIL_STATUSES_OPTIONS, NOTIFICATION_STATUS_OPTIONS, NOTIFICATION_PLATFORM_OPTIONS, @@ -101,6 +104,7 @@ export const SettingsPage: React.FC = () => { // ========================================== const [userEmailSearch, setUserEmailSearch] = useState(''); const [userStatusFilter, setUserStatusFilter] = useState('all'); + const [userRoleFilter, setUserRoleFilter] = useState('all'); const [selectedUserForDetail, setSelectedUserForDetail] = useState(null); const [isUserDetailModalOpen, setIsUserDetailModalOpen] = useState(false); @@ -125,6 +129,16 @@ export const SettingsPage: React.FC = () => { queryFn: () => getUsers(1, 100, userFilter), }); + const filteredUsers = useMemo(() => { + return usersList.filter((u) => { + const matchRole = + userRoleFilter === 'all' || + (u.role && String(u.role).toLowerCase() === userRoleFilter.toLowerCase()) || + (!u.role && userRoleFilter.toLowerCase() === 'contributor'); + return matchRole; + }); + }, [usersList, userRoleFilter]); + React.useEffect(() => { if (usersDirectory && usersDirectory.length > 0) { registerUserEmails(usersDirectory); @@ -462,6 +476,7 @@ export const SettingsPage: React.FC = () => { const userExportColumns: ExportColumn[] = [ { header: 'User Email', key: 'email', format: (val, u) => val || getUserEmail(u.id) || '—' }, { header: 'User Identifier', key: 'id' }, + { header: 'Role', key: 'role', format: (val) => getUserRoleBadge(val).label }, { header: 'Status', key: 'status', format: (val) => getActivityStatusBadge(val).label }, { header: 'Last Login', key: 'lastLoginAt', format: (val) => (val ? formatDate(val) : 'Never') }, ]; @@ -483,7 +498,9 @@ export const SettingsPage: React.FC = () => { sortColumn: userSortCol, sortDirection: userSortDir, handleSort: handleUserSort, - } = useTableSort(usersList, 'email', 'asc'); + } = useTableSort(filteredUsers, 'email', 'asc', { + role: (u: User) => getUserRoleBadge(u.role).label, + }); const { sortedData: sortedInvites, @@ -682,6 +699,21 @@ export const SettingsPage: React.FC = () => { /> +
+ +
+
- {(userEmailSearch.trim() !== '' || userStatusFilter !== 'all') && ( + {(userEmailSearch.trim() !== '' || userStatusFilter !== 'all' || userRoleFilter !== 'all') && (