import React, { useState, useMemo } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Shield, History, CheckCircle, Bell, Send, Radio, Filter, UserCheck, Users, RefreshCw, Search, Mail, Eye, ExternalLink, Plus, Building2, Award as AwardIcon, FileText, AlertCircle, Layers, Activity, Calendar, SlidersHorizontal, Copy, Check, Smartphone, MessageSquare, Globe, Terminal, Server, Database, Hash, ChevronDown, ChevronRight, User as UserIcon, RotateCcw, } from 'lucide-react'; import { getUsers, getUser, updateUserStatus, syncIntegration, syncUser, getUserSyncRecord, getInvites, sendInvite, getApplicantInvite, getApplicantInvites, getOrganisations, getAwards, getContracts, getAuditLogs, getNotifications, } from '../services/api'; import { Modal } from '../components/ui/Modal'; import { HelpTooltip } from '../components/ui/HelpTooltip'; import { useToast } from '../components/ui/Toast'; import { ExportButton } from '../components/ui/ExportButton'; import { ExportColumn } from '../utils/exportUtils'; import { useTableSort, SortableTh } from '../utils/tableSort'; import { useAuth } from '../auth/AuthProvider'; import { EmailStatuses, NotificationStatus, NotificationPlatform, ActivityStatus, User, Invite, InviteRequest, UserFilterRequest, AuditLog, Notification, } from '../types'; import { formatDate, getEmailStatusLabel, getEmailStatusBadge, getNotificationStatusLabel, getNotificationStatusBadge, getNotificationPlatformLabel, getUserEmail, registerUserEmails, getActivityStatusBadge, EMAIL_STATUSES_OPTIONS, NOTIFICATION_STATUS_OPTIONS, NOTIFICATION_PLATFORM_OPTIONS, } from '../lib/formatters'; export const SettingsPage: React.FC = () => { const queryClient = useQueryClient(); const { user } = useAuth(); const { success, error: toastError } = useToast(); const [activeTab, setActiveTab] = useState<'users' | 'invites' | 'notifications' | 'audit'>('users'); // ========================================== // 1. User Directory State // ========================================== const [userEmailSearch, setUserEmailSearch] = useState(''); const [userStatusFilter, setUserStatusFilter] = useState('all'); const [selectedUserForDetail, setSelectedUserForDetail] = useState(null); const [isUserDetailModalOpen, setIsUserDetailModalOpen] = useState(false); const userFilter: UserFilterRequest = { email: userEmailSearch.trim() || undefined, status: userStatusFilter !== 'all' ? userStatusFilter : undefined, }; const { data: usersDirectory = [] } = useQuery({ queryKey: ['users-directory'], queryFn: () => getUsers(1, 200), staleTime: 5 * 60 * 1000, }); const { data: usersList = [], isLoading: loadingUsers, isFetching: isFetchingUsers, refetch: refetchUsers, } = useQuery({ queryKey: ['users', userStatusFilter, userEmailSearch], queryFn: () => getUsers(1, 100, userFilter), }); React.useEffect(() => { if (usersDirectory && usersDirectory.length > 0) { registerUserEmails(usersDirectory); } }, [usersDirectory]); React.useEffect(() => { if (usersList && usersList.length > 0) { registerUserEmails(usersList); } }, [usersList]); const { data: userFreshDetail, isLoading: loadingUserFreshDetail } = useQuery({ queryKey: ['user-canonical', selectedUserForDetail?.id], queryFn: () => (selectedUserForDetail?.id ? getUser(selectedUserForDetail.id) : null), enabled: !!selectedUserForDetail?.id && isUserDetailModalOpen, }); const syncMutation = useMutation({ mutationFn: () => syncUser(undefined, true), onSuccess: () => { success('User Sync Complete', 'Authenticated user enabled and directory synced.'); queryClient.invalidateQueries({ queryKey: ['users'] }); }, onError: (err: any) => { toastError('Sync Failed', err); }, }); const syncIntegrationMutation = useMutation({ mutationFn: () => syncIntegration(true), onSuccess: () => { success('Backend Integration Synced', 'System records and telemetry successfully synchronized.'); queryClient.invalidateQueries({ queryKey: ['users'] }); }, onError: (err: any) => { toastError('Integration Sync Failed', err); }, }); const updateUserStatusMutation = useMutation({ mutationFn: ({ userId, status }: { userId: string; status: ActivityStatus }) => updateUserStatus(userId, status), onSuccess: (_, vars) => { success('User Status Updated', `User status changed to ${getActivityStatusBadge(vars.status).label}.`); queryClient.invalidateQueries({ queryKey: ['users'] }); if (selectedUserForDetail && selectedUserForDetail.id === vars.userId) { setSelectedUserForDetail({ ...selectedUserForDetail, status: vars.status }); } }, onError: (err: any) => { toastError('Update Failed', err); }, }); // ========================================== // 2. Communications & Invites State // ========================================== const [isSendInviteModalOpen, setIsSendInviteModalOpen] = useState(false); const [applicantSearchId, setApplicantSearchId] = useState(''); const [applicantInvitesResults, setApplicantInvitesResults] = useState(null); const [searchingApplicant, setSearchingApplicant] = useState(false); // Invite Form State const [inviteOrgId, setInviteOrgId] = useState(''); const [invitedOrgId, setInvitedOrgId] = useState(''); const [inviteAwardId, setInviteAwardId] = useState(''); const [inviteContractId, setInviteContractId] = useState(''); const [inviteRecipientUuid, setInviteRecipientUuid] = useState(''); const [inviteMessage, setInviteMessage] = useState(''); const [invitePlatform, setInvitePlatform] = useState(NotificationPlatform.Email); // Invites filtering & search const [inviteSearchTerm, setInviteSearchTerm] = useState(''); const [invitePlatformFilter, setInvitePlatformFilter] = useState<'all' | number>('all'); const { data: invitesList = [], isLoading: loadingInvites, isFetching: isFetchingInvites, refetch: refetchInvites, } = useQuery({ queryKey: ['invites'], queryFn: () => getInvites(1, 100), }); const filteredInvites = useMemo(() => { return invitesList.filter((inv) => { const matchSearch = !inviteSearchTerm.trim() || (inv.recipient && inv.recipient.toLowerCase().includes(inviteSearchTerm.toLowerCase())) || (inv.message && inv.message.toLowerCase().includes(inviteSearchTerm.toLowerCase())); const matchPlatform = invitePlatformFilter === 'all' || Number(inv.platform) === Number(invitePlatformFilter); return matchSearch && matchPlatform; }); }, [invitesList, inviteSearchTerm, invitePlatformFilter]); const { data: organisations = [] } = useQuery({ queryKey: ['organisations'], queryFn: () => getOrganisations(1, 50), }); const { data: awards = [] } = useQuery({ queryKey: ['awards'], queryFn: () => getAwards(1, 50), }); const { data: contracts = [] } = useQuery({ queryKey: ['contracts'], queryFn: () => getContracts(1, 50), }); const sendInviteMutation = useMutation({ mutationFn: (payload: InviteRequest) => sendInvite(payload), onSuccess: () => { success('Invitation Dispatched', 'Statutory communications invitation sent successfully.'); queryClient.invalidateQueries({ queryKey: ['invites'] }); setIsSendInviteModalOpen(false); setInviteRecipientUuid(''); setInviteMessage(''); }, onError: (err: any) => { toastError('Dispatch Error', err); }, }); const handleSearchApplicantInvites = async (e?: React.FormEvent) => { if (e) e.preventDefault(); if (!applicantSearchId.trim()) return; try { setSearchingApplicant(true); const res = await getApplicantInvites(applicantSearchId.trim()); setApplicantInvitesResults(res); success('Lookup Complete', `Retrieved ${res.length} invitation records for applicant.`); } catch (err: any) { toastError('Lookup Error', err); setApplicantInvitesResults([]); } finally { setSearchingApplicant(false); } }; const handleLookupSingleApplicantInvite = async () => { if (!applicantSearchId.trim()) return; try { setSearchingApplicant(true); const res = await getApplicantInvite(applicantSearchId.trim()); setApplicantInvitesResults(res ? [res] : []); success('Primary Invite Found', `Retrieved primary invitation for applicant.`); } catch (err: any) { toastError('Lookup Error', err); setApplicantInvitesResults([]); } finally { setSearchingApplicant(false); } }; // ========================================== // 3. Email Gateway & Notifications State // ========================================== const [emailGatewayStatus, setEmailGatewayStatus] = useState(EmailStatuses.Connected); const [notificationSearchTerm, setNotificationSearchTerm] = useState(''); const [notificationOrgFilter, setNotificationOrgFilter] = useState(''); const [notificationPlatformFilter, setNotificationPlatformFilter] = useState('all'); const [notificationStatusFilter, setNotificationStatusFilter] = useState('all'); const [selectedNotificationForDetail, setSelectedNotificationForDetail] = useState(null); const [isNotificationDetailModalOpen, setIsNotificationDetailModalOpen] = useState(false); const [copiedNotificationPayload, setCopiedNotificationPayload] = useState(false); const effectiveNotificationOrgId = notificationOrgFilter || (organisations[0]?.id ? String(organisations[0].id) : '1'); const { data: fetchedNotifications = [], isLoading: loadingNotifications, refetch: refetchNotifications, isFetching: isFetchingNotifications, } = useQuery({ queryKey: ['notifications', effectiveNotificationOrgId], queryFn: () => getNotifications(effectiveNotificationOrgId), enabled: !!effectiveNotificationOrgId, }); const notificationExportColumns: ExportColumn[] = [ { header: 'Notification ID', key: 'id' }, { header: 'Created Date', key: 'createdAt', format: (val) => formatDate(val) }, { header: 'Sent Date', key: 'sentAt', format: (val) => (val ? formatDate(val) : 'Pending/Queued') }, { header: 'Platform / Channel', key: 'platform', format: (val) => getNotificationPlatformLabel(val) }, { header: 'Delivery Status', key: 'status', format: (val) => getNotificationStatusLabel(val) }, { header: 'Subject', key: 'subject', format: (val) => val || '—' }, { header: 'Recipient UUID', key: 'recipient' }, { header: 'Destination', key: 'destination', format: (val) => val || '—' }, { header: 'Target Organisation', key: 'organisationId', format: (val) => `Org #${val}` }, { header: 'Message Content', key: 'message', format: (val) => val || '—' }, { header: 'Error Diagnostics', key: 'error', format: (val) => val || 'None' }, ]; const filteredNotifications = fetchedNotifications.filter((item) => { if (notificationPlatformFilter !== 'all' && String(item.platform) !== String(notificationPlatformFilter)) { return false; } if (notificationStatusFilter !== 'all' && String(item.status) !== String(notificationStatusFilter)) { return false; } if (!notificationSearchTerm.trim()) return true; const q = notificationSearchTerm.toLowerCase(); const id = String(item.id).toLowerCase(); const subj = (item.subject || '').toLowerCase(); const msg = (item.message || '').toLowerCase(); const recip = (item.recipient || '').toLowerCase(); const dest = (item.destination || '').toLowerCase(); const err = (item.error || '').toLowerCase(); return ( id.includes(q) || subj.includes(q) || msg.includes(q) || recip.includes(q) || dest.includes(q) || err.includes(q) ); }); const { sortedData: sortedNotifications, sortColumn: notifSortCol, sortDirection: notifSortDir, handleSort: handleNotifSort, } = useTableSort(filteredNotifications, 'createdAt', 'desc'); // ========================================== // 4. Audit Log Ledger State // ========================================== const [auditSearchTerm, setAuditSearchTerm] = useState(''); const [auditEntityFilter, setAuditEntityFilter] = useState('all'); const [auditActionFilter, setAuditActionFilter] = useState('all'); const [selectedAuditLog, setSelectedAuditLog] = useState(null); const [isAuditDetailModalOpen, setIsAuditDetailModalOpen] = useState(false); const [copiedAuditPayload, setCopiedAuditPayload] = useState(false); const [expandedAuditLogIds, setExpandedAuditLogIds] = useState>({}); const [copiedUserId, setCopiedUserId] = useState(null); const toggleExpandAuditLog = (id: string | number) => { setExpandedAuditLogIds((prev) => ({ ...prev, [id]: !prev[id], })); }; const handleCopyUserId = (userId: string | number, e?: React.MouseEvent) => { if (e) e.stopPropagation(); if (!userId) return; const strId = String(userId); navigator.clipboard.writeText(strId); setCopiedUserId(userId); success('User ID Copied', `Copied ${strId} to clipboard.`); setTimeout(() => { setCopiedUserId((curr) => (curr === userId ? null : curr)); }, 2000); }; const { data: auditLogs = [], isLoading: loadingAuditLogs, refetch: refetchAuditLogs, isFetching: isFetchingAuditLogs, } = useQuery({ queryKey: ['audit-logs'], queryFn: () => getAuditLogs(1, 100), }); const auditExportColumns: ExportColumn[] = [ { header: 'Event ID', key: 'id' }, { header: 'Timestamp', key: 'timestamp', format: (val) => formatDate(val) }, { header: 'Action', key: 'action', format: (val) => val || '—' }, { header: 'Entity Name', key: 'entityName', format: (val) => val || '—' }, { header: 'Entity ID', key: 'entityId', format: (val) => val || '—' }, { header: 'Performed By (Email)', key: 'performedBy', format: (val) => { const email = getUserEmail(val); return email && email !== '—' ? `${email} (${val})` : String(val || 'System'); }, }, { header: 'Tenant ID', key: 'tenantId', format: (val) => (val ? `Tenant #${val}` : '—') }, { header: 'Changes', key: 'changes', format: (val) => (Array.isArray(val) ? val.join('; ') : val || '—'), }, ]; const uniqueAuditEntities = Array.from( new Set(auditLogs.map((l) => l.entityName).filter((e): e is string => Boolean(e))) ); const uniqueAuditActions = Array.from( new Set(auditLogs.map((l) => l.action).filter((a): a is string => Boolean(a))) ); const filteredAuditLogs = auditLogs.filter((log) => { if (auditEntityFilter !== 'all' && (log.entityName || '').toLowerCase() !== auditEntityFilter.toLowerCase()) { return false; } if (auditActionFilter !== 'all' && (log.action || '').toLowerCase() !== auditActionFilter.toLowerCase()) { return false; } if (!auditSearchTerm.trim()) return true; const q = auditSearchTerm.toLowerCase(); const id = String(log.id || '').toLowerCase(); const action = (log.action || '').toLowerCase(); const entityName = (log.entityName || '').toLowerCase(); const entityId = String(log.entityId || '').toLowerCase(); const performedBy = (log.performedBy || '').toLowerCase(); const tenant = String(log.tenantId || '').toLowerCase(); const changes = Array.isArray(log.changes) ? log.changes.join(' ').toLowerCase() : ''; return ( id.includes(q) || action.includes(q) || entityName.includes(q) || entityId.includes(q) || performedBy.includes(q) || tenant.includes(q) || changes.includes(q) ); }); const { sortedData: sortedAuditLogs, sortColumn: auditSortCol, sortDirection: auditSortDir, handleSort: handleAuditSort, } = useTableSort(filteredAuditLogs, 'timestamp', 'desc'); const userExportColumns: ExportColumn[] = [ { header: 'User Email', key: 'email', format: (val, u) => val || getUserEmail(u.id) || '—' }, { header: 'User Identifier', key: 'id' }, { header: 'Status', key: 'status', format: (val) => getActivityStatusBadge(val).label }, { header: 'Last Login', key: 'lastLoginAt', format: (val) => (val ? formatDate(val) : 'Never') }, ]; const inviteExportColumns: ExportColumn[] = [ { header: 'Recipient (UUID)', key: 'recipient' }, { header: 'Channel / Platform', key: 'platform', format: (val) => getNotificationPlatformLabel(val) }, { header: 'Invitation Message', key: 'message' }, { header: 'Target Organisation / Grant', key: 'invitedOrganisationId', format: (val, inv) => `Org #${val || inv.organisationId || '—'} (Award #${inv.awardId || '—'})`, }, { header: 'Date Dispatched', key: 'createdAt', format: (val) => (val ? formatDate(val) : '—') }, ]; const { sortedData: sortedUsers, sortColumn: userSortCol, sortDirection: userSortDir, handleSort: handleUserSort, } = useTableSort(usersList, 'email', 'asc'); const { sortedData: sortedInvites, sortColumn: inviteSortCol, sortDirection: inviteSortDir, handleSort: handleInviteSort, } = useTableSort(filteredInvites, 'createdAt', 'desc'); const getActionBadge = (action?: string | null) => { const act = (action || 'GENERAL_EVENT').toUpperCase(); if (act.includes('APPROV') || act.includes('RELEASE') || act.includes('AUTH') || act.includes('VERIF')) { return { label: action || 'APPROVED', className: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/30' }; } if (act.includes('CREATE') || act.includes('REGISTER') || act.includes('ADD') || act.includes('POST') || act.includes('INSERT')) { return { label: action || 'CREATED', className: 'bg-blue-500/10 text-blue-400 border-blue-500/30' }; } if (act.includes('UPDATE') || act.includes('SYNC') || act.includes('EDIT') || act.includes('PUT') || act.includes('PATCH')) { return { label: action || 'UPDATED', className: 'bg-indigo-500/10 text-indigo-400 border-indigo-500/30' }; } if (act.includes('DISPATCH') || act.includes('INVIT') || act.includes('SEND') || act.includes('NOTIF')) { return { label: action || 'DISPATCHED', className: 'bg-purple-500/10 text-purple-400 border-purple-500/30' }; } if (act.includes('DELETE') || act.includes('SUSPEND') || act.includes('REJECT') || act.includes('LOCK')) { return { label: action || 'REJECTED', className: 'bg-rose-500/10 text-rose-400 border-rose-500/30' }; } return { label: action || 'AUDIT_EVENT', className: 'bg-slate-800 text-slate-300 border-slate-700' }; }; return (
{/* Header Banner */}

System Administration & Audit

User access directory, statutory invitations, gateway broadcasts, and immutable system audit ledger.

{activeTab === 'users' && (
Browser Sync Saved
)} {activeTab === 'invites' && ( )} {activeTab === 'notifications' && ( )}
{/* Navigation Tabs */}
{/* ============================================================== */} {/* TAB 1: User Directory & Access */} {/* ============================================================== */} {activeTab === 'users' && (
{/* Standardized Toolbar & Filter Bar directly above Users Table */}
{/* Filters on the left */}
setUserEmailSearch(e.target.value)} placeholder="Search users by email..." className="w-full pl-8 pr-3 py-1.5 text-xs bg-slate-900 border border-slate-800 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-blue-500 shadow-xs" />
{(userEmailSearch.trim() !== '' || userStatusFilter !== 'all') && ( )}
{/* Actions on the right: Refresh, Export, Sync */}
{/* Users Table */}

Authorized User Directory ({sortedUsers.length} of {usersList.length})

User Email User Identifier Status Last Login {loadingUsers ? ( ) : sortedUsers.length === 0 ? ( ) : ( sortedUsers.map((u) => { const badge = getActivityStatusBadge(u.status); return ( ); }) )}
Actions
Loading user directory...
No users found matching current filters.
{u.email || getUserEmail(u.id) || No email recorded} {u.id} {badge.label} {u.lastLoginAt ? formatDate(u.lastLoginAt) : 'Never'} {/* Status switch actions */} {u.status === ActivityStatus.Active ? ( ) : ( )}
)} {/* ============================================================== */} {/* TAB 2: Communications & Invites */} {/* ============================================================== */} {activeTab === 'invites' && (
{/* Applicant Invites Lookup */}

Applicant Invitations Lookup

Query invitations dispatched to a specific candidate or grantee using their unique Applicant User UUID.

setApplicantSearchId(e.target.value)} placeholder="Enter Applicant User ID (UUID)..." className="flex-1 px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white placeholder-slate-500 font-mono focus:outline-none focus:ring-2 focus:ring-blue-500/50" />
{applicantInvitesResults && (
Lookup Results ({applicantInvitesResults.length} matches): {applicantInvitesResults.length === 0 ? (

No invitations found for this applicant UUID.

) : (
{applicantInvitesResults.map((inv, idx) => (
{inv.message}
Org #{inv.organisationId} • Award #{inv.awardId} • Contract #{inv.contractId}
{getNotificationPlatformLabel(inv.platform)}
))}
)}
)}
{/* Standardized Toolbar & Filter Bar directly above Sent Invitations Table */}
{/* Filters on the left */}
setInviteSearchTerm(e.target.value)} placeholder="Search recipient UUID, message..." className="w-full pl-8 pr-3 py-1.5 text-xs bg-slate-900 border border-slate-800 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-blue-500 shadow-xs font-mono" />
{(inviteSearchTerm.trim() !== '' || invitePlatformFilter !== 'all') && ( )}
{/* Actions on the right: Refresh, Export, Dispatch */}
{/* Sent Invites Ledger */}

Sent Invitations Ledger ({sortedInvites.length} of {invitesList.length})

Recipient (UUID) Channel Message Target Org / Grant Created Date {loadingInvites ? ( ) : sortedInvites.length === 0 ? ( ) : ( sortedInvites.map((inv, idx) => ( )) )}
Loading invitations ledger...
No invitations recorded yet. Use “Dispatch Invitation” to send one.
{inv.recipient} {getNotificationPlatformLabel(inv.platform)} {inv.message} Org #{inv.invitedOrganisationId || inv.organisationId} • Award #{inv.awardId} {inv.createdAt ? formatDate(inv.createdAt) : 'Recently'}
)} {/* ============================================================== */} {/* TAB 3: Notifications & Gateways */} {/* ============================================================== */} {activeTab === 'notifications' && (
{/* Summary Metric Cards */}
Email Gateway {getEmailStatusBadge(emailGatewayStatus).label}
Total Notifications
{fetchedNotifications.length}
System records
Delivered / Sent
{fetchedNotifications.filter( (n) => n.status === NotificationStatus.Delivered || n.status === NotificationStatus.Sent || n.status === NotificationStatus.Read ).length}
Successful transmissions
Queued / Pending
{fetchedNotifications.filter( (n) => n.status === NotificationStatus.Pending || n.status === NotificationStatus.Queued || n.status === NotificationStatus.Processing ).length}
Active retry pipeline
Failed / Bounced
{fetchedNotifications.filter( (n) => n.status === NotificationStatus.Failed || n.status === NotificationStatus.Bounced ).length}
Diagnostic errors
{/* Filter & Search Toolbar */}
setNotificationSearchTerm(e.target.value)} placeholder="Search notifications by subject, message, recipient, destination..." className="w-full pl-9 pr-3 py-2 text-xs bg-[#090d16] border border-slate-700/80 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:ring-1 focus:ring-blue-500" />
Target Org:
{(notificationSearchTerm || notificationPlatformFilter !== 'all' || notificationStatusFilter !== 'all') && ( )}
{/* Returned Notification Data Table */}

Returned Communications Report ({filteredNotifications.length} of {fetchedNotifications.length})

Org #{effectiveNotificationOrgId}
Notification ID Channel / Platform Status Subject & Message Payload Recipient & Destination Target Org Timestamp {loadingNotifications ? ( ) : sortedNotifications.length === 0 ? ( ) : ( sortedNotifications.map((item) => { const statusBadge = getNotificationStatusBadge(item.status); return ( ); }) )}
Actions
Loading communication records for Organisation #{effectiveNotificationOrgId}...
No notification records returned for Organisation #{effectiveNotificationOrgId}.
#{item.id} {getNotificationPlatformLabel(item.platform)} {statusBadge.label}
{item.subject || 'Statutory Notice'}
{item.message || '—'}
{item.recipient}
{item.destination && (
{item.destination}
)}
Org #{item.organisationId} {formatDate(item.createdAt)}
)} {/* ============================================================== */} {/* TAB 4: Audit & Telemetry Ledger */} {/* ============================================================== */} {activeTab === 'audit' && (
{/* Summary Metric Cards */}
Total Audit Events
{auditLogs.length}
Live API ledger records
Audited Entities
{uniqueAuditEntities.length}
Distinct system resources
Active Actors
{new Set(auditLogs.map((l) => l.performedBy).filter(Boolean)).size}
Distinct caller UUIDs
Action Categories
{uniqueAuditActions.length}
Registered action types
{/* Filter & Action Toolbar */}
setAuditSearchTerm(e.target.value)} placeholder="Search audit trail by action, entity name, ID, performed by UUID..." className="w-full pl-9 pr-3 py-2 text-xs bg-[#090d16] border border-slate-700/80 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:ring-1 focus:ring-blue-500" />
Entity:
Action:
{(auditSearchTerm || auditEntityFilter !== 'all' || auditActionFilter !== 'all') && ( )}
{/* Audit Logs Table */}

Audit Log Ledger ({filteredAuditLogs.length} of {auditLogs.length})

{filteredAuditLogs.length} entries
Timestamp Event ID Action Entity Performed By Changes {loadingAuditLogs ? ( ) : sortedAuditLogs.length === 0 ? ( ) : ( sortedAuditLogs.map((log) => { const badge = getActionBadge(log.action); const isExpanded = !!expandedAuditLogIds[log.id]; const changesCount = Array.isArray(log.changes) ? log.changes.length : 0; const performerEmail = getUserEmail(log.performedBy); const hasResolvedEmail = performerEmail && performerEmail !== '—' && !performerEmail.includes('—'); const displayEmail = hasResolvedEmail ? performerEmail : (log.performedBy || 'System'); return ( toggleExpandAuditLog(log.id)} className={`hover:bg-slate-800/40 transition-colors cursor-pointer ${ isExpanded ? 'bg-slate-800/30' : '' }`} > {/* Expanding Shelf for Changes Content */} {isExpanded && ( )} ); }) )}
Inspect
Loading governance audit records...
{auditSearchTerm || auditEntityFilter !== 'all' || auditActionFilter !== 'all' ? 'No audit log entries match your filter criteria.' : 'No audit records found.'}
{formatDate(log.timestamp)} #{log.id} {badge.label} {log.entityName || 'General'} {log.entityId && ( #{log.entityId} )} e.stopPropagation()} >
{displayEmail} {log.performedBy && ( )}

Recorded Changes Content (Event #{log.id})

{changesCount} {changesCount === 1 ? 'Entry' : 'Entries'}
Entity: {log.entityName || 'General'} {log.entityId ? `(#${log.entityId})` : ''} • Performer: {displayEmail} {log.performedBy && ( )}
{/* Changes List */} {Array.isArray(log.changes) && log.changes.length > 0 ? (
{log.changes.map((changeItem, idx) => (
#{idx + 1}
{changeItem}
))}
) : (
No itemized field changes content recorded for this event.
)}
)} {/* ============================================================== */} {/* Modal: Audit Log Entry Inspection */} {/* ============================================================== */} { setIsAuditDetailModalOpen(false); setSelectedAuditLog(null); setCopiedAuditPayload(false); }} title={`Audit Event: #${selectedAuditLog?.id || ''}`} subtitle="Authoritative event payload and activity ledger" > {selectedAuditLog && (
Action Type: {selectedAuditLog.action || 'EVENT'}
Entity Name: {selectedAuditLog.entityName || '—'}
Entity ID: {selectedAuditLog.entityId || '—'}
Performed By:
{getUserEmail(selectedAuditLog.performedBy) !== '—' ? getUserEmail(selectedAuditLog.performedBy) : (selectedAuditLog.performedBy || 'System')} {selectedAuditLog.performedBy && ( )}
{selectedAuditLog.performedBy && ( ID: {selectedAuditLog.performedBy} )}
Logged Timestamp: {formatDate(selectedAuditLog.timestamp)}
{selectedAuditLog.tenantId && (
Tenant Scope: Tenant #{selectedAuditLog.tenantId}
)}
State Changes / Delta: {Array.isArray(selectedAuditLog.changes) && selectedAuditLog.changes.length > 0 ? (
    {selectedAuditLog.changes.map((ch, idx) => (
  • {ch}
  • ))}
) : (

No granular delta changes specified in audit payload.

)}
{/* Raw JSON Data Viewer */}
Raw Audit Record (JSON):
                  {JSON.stringify(selectedAuditLog, null, 2)}
                
)}
{/* ============================================================== */} {/* Modal: Dispatch Invitation */} {/* ============================================================== */} setIsSendInviteModalOpen(false)} title="Dispatch Statutory Invitation" subtitle="Onboard enterprise entities, project PIs, or external reviewers" helpText="Generates cryptographic invitations linked to organizations, awards, or contracts to initiate formal onboarding workflows." >
{ e.preventDefault(); if (!inviteRecipientUuid || !inviteMessage) { toastError('Validation Error', 'Recipient UUID and message content are mandatory.'); return; } sendInviteMutation.mutate({ organisationId: inviteOrgId || (organisations[0]?.id ? Number(organisations[0].id) : 101), invitedOrganisationId: invitedOrgId || (organisations[1]?.id ? Number(organisations[1].id) : 102), awardId: inviteAwardId || (awards[0]?.id ? Number(awards[0].id) : 301), contractId: inviteContractId || (contracts[0]?.id ? Number(contracts[0].id) : 401), recipient: inviteRecipientUuid, message: inviteMessage, platform: invitePlatform, }); }} className="space-y-4" >
setInviteRecipientUuid(e.target.value)} placeholder="e.g. 550e8400-e29b-41d4-a716-446655440000" className="w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white placeholder-slate-500 font-mono focus:outline-none focus:ring-2 focus:ring-blue-500/50" required />