Added, beneficiaries, site visits and change management
This commit is contained in:
@@ -59,6 +59,7 @@ 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,
|
||||
@@ -139,7 +140,7 @@ export const SettingsPage: React.FC = () => {
|
||||
const syncMutation = useMutation({
|
||||
mutationFn: () => syncUser(undefined, true),
|
||||
onSuccess: () => {
|
||||
success('User Sync Complete', 'Authenticated user enabled and directory synced. Browser cache updated to prevent endpoint hotspot.');
|
||||
success('User Sync Complete', 'Authenticated user enabled and directory synced.');
|
||||
queryClient.invalidateQueries({ queryKey: ['users'] });
|
||||
},
|
||||
onError: (err: any) => {
|
||||
@@ -320,6 +321,13 @@ export const SettingsPage: React.FC = () => {
|
||||
);
|
||||
});
|
||||
|
||||
const {
|
||||
sortedData: sortedNotifications,
|
||||
sortColumn: notifSortCol,
|
||||
sortDirection: notifSortDir,
|
||||
handleSort: handleNotifSort,
|
||||
} = useTableSort(filteredNotifications, 'createdAt', 'desc');
|
||||
|
||||
// ==========================================
|
||||
// 4. Audit Log Ledger State
|
||||
// ==========================================
|
||||
@@ -417,6 +425,27 @@ export const SettingsPage: React.FC = () => {
|
||||
);
|
||||
});
|
||||
|
||||
const {
|
||||
sortedData: sortedAuditLogs,
|
||||
sortColumn: auditSortCol,
|
||||
sortDirection: auditSortDir,
|
||||
handleSort: handleAuditSort,
|
||||
} = useTableSort(filteredAuditLogs, 'timestamp', 'desc');
|
||||
|
||||
const {
|
||||
sortedData: sortedUsers,
|
||||
sortColumn: userSortCol,
|
||||
sortDirection: userSortDir,
|
||||
handleSort: handleUserSort,
|
||||
} = useTableSort(usersList, 'email', 'asc');
|
||||
|
||||
const {
|
||||
sortedData: sortedInvites,
|
||||
sortColumn: inviteSortCol,
|
||||
sortDirection: inviteSortDir,
|
||||
handleSort: handleInviteSort,
|
||||
} = useTableSort(invitesList, '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')) {
|
||||
@@ -488,51 +517,16 @@ export const SettingsPage: React.FC = () => {
|
||||
)}
|
||||
|
||||
{activeTab === 'notifications' && (
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
refetchNotifications();
|
||||
success('Notifications Refreshed', 'Latest communication transmissions loaded from endpoint.');
|
||||
}}
|
||||
disabled={isFetchingNotifications}
|
||||
className="inline-flex items-center gap-1.5 px-4 py-2 text-xs font-semibold text-white bg-slate-800 hover:bg-slate-700 border border-slate-700/80 rounded-xl shadow-md transition cursor-pointer disabled:opacity-50"
|
||||
title="Force refresh notifications from endpoint"
|
||||
>
|
||||
<RefreshCw className={`w-3.5 h-3.5 text-blue-400 ${isFetchingNotifications ? 'animate-spin' : ''}`} />
|
||||
<span>Refresh Notifications</span>
|
||||
</button>
|
||||
<ExportButton
|
||||
title="Communications & Notifications Ledger"
|
||||
subtitle="Transmission telemetry report via /v1/api/communications/norifications"
|
||||
filename={`pfm-notifications-report-${new Date().toISOString().split('T')[0]}`}
|
||||
columns={notificationExportColumns}
|
||||
data={filteredNotifications}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'audit' && (
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
refetchAuditLogs();
|
||||
success('Audit Ledger Refreshed', 'Latest audit log entries loaded from backend.');
|
||||
}}
|
||||
disabled={isFetchingAuditLogs}
|
||||
className="inline-flex items-center gap-1.5 px-4 py-2 text-xs font-semibold text-white bg-slate-800 hover:bg-slate-700 border border-slate-700/80 rounded-xl shadow-md transition cursor-pointer disabled:opacity-50"
|
||||
title="Force refresh audit logs from API"
|
||||
>
|
||||
<RefreshCw className={`w-3.5 h-3.5 text-blue-400 ${isFetchingAuditLogs ? 'animate-spin' : ''}`} />
|
||||
<span>Refresh Audit Logs</span>
|
||||
</button>
|
||||
<ExportButton
|
||||
title="System Audit & Security Log"
|
||||
subtitle="Complete audit trail of user access, statutory approvals, and financial transactions"
|
||||
filename={`pfm-audit-trail-${new Date().toISOString().split('T')[0]}`}
|
||||
columns={auditExportColumns}
|
||||
data={filteredAuditLogs}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
setInviteOrgId(effectiveNotificationOrgId);
|
||||
setIsSendInviteModalOpen(true);
|
||||
}}
|
||||
className="inline-flex items-center gap-1.5 px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-lg shadow-blue-500/20 transition cursor-pointer"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
Add Notification
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -659,10 +653,18 @@ export const SettingsPage: React.FC = () => {
|
||||
<table className="w-full text-left text-xs border-collapse">
|
||||
<thead>
|
||||
<tr className="bg-slate-900/60 border-b border-slate-800/80 text-slate-400 uppercase tracking-wider font-semibold">
|
||||
<th className="py-3.5 px-5">User Email</th>
|
||||
<th className="py-3.5 px-4">User Identifier</th>
|
||||
<th className="py-3.5 px-4">Status</th>
|
||||
<th className="py-3.5 px-4">Last Login</th>
|
||||
<SortableTh column="email" currentColumn={userSortCol} direction={userSortDir} onSort={handleUserSort} className="py-3.5 px-5">
|
||||
User Email
|
||||
</SortableTh>
|
||||
<SortableTh column="id" currentColumn={userSortCol} direction={userSortDir} onSort={handleUserSort} className="py-3.5 px-4">
|
||||
User Identifier
|
||||
</SortableTh>
|
||||
<SortableTh column="status" currentColumn={userSortCol} direction={userSortDir} onSort={handleUserSort} className="py-3.5 px-4">
|
||||
Status
|
||||
</SortableTh>
|
||||
<SortableTh column="lastLoginAt" currentColumn={userSortCol} direction={userSortDir} onSort={handleUserSort} className="py-3.5 px-4">
|
||||
Last Login
|
||||
</SortableTh>
|
||||
<th className="py-3.5 px-5 text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -673,14 +675,14 @@ export const SettingsPage: React.FC = () => {
|
||||
Loading user directory...
|
||||
</td>
|
||||
</tr>
|
||||
) : usersList.length === 0 ? (
|
||||
) : sortedUsers.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={5} className="py-8 text-center text-slate-500">
|
||||
No users found matching current filters.
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
usersList.map((u) => {
|
||||
sortedUsers.map((u) => {
|
||||
const badge = getActivityStatusBadge(u.status);
|
||||
return (
|
||||
<tr key={u.id} className="hover:bg-slate-800/40 transition-colors">
|
||||
@@ -831,11 +833,21 @@ export const SettingsPage: React.FC = () => {
|
||||
<table className="w-full text-left text-xs border-collapse">
|
||||
<thead>
|
||||
<tr className="bg-slate-900/60 border-b border-slate-800/80 text-slate-400 uppercase tracking-wider font-semibold">
|
||||
<th className="py-3.5 px-5">Recipient (UUID)</th>
|
||||
<th className="py-3.5 px-4">Channel</th>
|
||||
<th className="py-3.5 px-4">Message</th>
|
||||
<th className="py-3.5 px-4">Target Org / Grant</th>
|
||||
<th className="py-3.5 px-5 text-right">Created Date</th>
|
||||
<SortableTh column="recipient" currentColumn={inviteSortCol} direction={inviteSortDir} onSort={handleInviteSort} className="py-3.5 px-5">
|
||||
Recipient (UUID)
|
||||
</SortableTh>
|
||||
<SortableTh column="platform" currentColumn={inviteSortCol} direction={inviteSortDir} onSort={handleInviteSort} className="py-3.5 px-4">
|
||||
Channel
|
||||
</SortableTh>
|
||||
<SortableTh column="message" currentColumn={inviteSortCol} direction={inviteSortDir} onSort={handleInviteSort} className="py-3.5 px-4">
|
||||
Message
|
||||
</SortableTh>
|
||||
<SortableTh column="invitedOrganisationId" currentColumn={inviteSortCol} direction={inviteSortDir} onSort={handleInviteSort} className="py-3.5 px-4">
|
||||
Target Org / Grant
|
||||
</SortableTh>
|
||||
<SortableTh column="createdAt" currentColumn={inviteSortCol} direction={inviteSortDir} onSort={handleInviteSort} align="right" className="py-3.5 px-5 text-right">
|
||||
Created Date
|
||||
</SortableTh>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-800/50">
|
||||
@@ -845,14 +857,14 @@ export const SettingsPage: React.FC = () => {
|
||||
Loading invitations ledger...
|
||||
</td>
|
||||
</tr>
|
||||
) : invitesList.length === 0 ? (
|
||||
) : sortedInvites.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={5} className="py-8 text-center text-slate-500">
|
||||
No invitations recorded yet. Use “Dispatch Invitation” to send one.
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
invitesList.map((inv, idx) => (
|
||||
sortedInvites.map((inv, idx) => (
|
||||
<tr key={idx} className="hover:bg-slate-800/40 transition-colors">
|
||||
<td className="py-3.5 px-5 font-mono text-slate-300 text-[11px]">
|
||||
{inv.recipient}
|
||||
@@ -919,7 +931,7 @@ export const SettingsPage: React.FC = () => {
|
||||
Total Notifications
|
||||
</span>
|
||||
<div className="text-xl font-bold font-mono text-white">{fetchedNotifications.length}</div>
|
||||
<span className="text-[10px] text-slate-500">Returned by endpoint</span>
|
||||
<span className="text-[10px] text-slate-500">System records</span>
|
||||
</div>
|
||||
|
||||
<div className="bg-[#0f1523]/80 backdrop-blur-md p-4 rounded-2xl border border-slate-800/80 shadow-xl space-y-1">
|
||||
@@ -966,19 +978,19 @@ export const SettingsPage: React.FC = () => {
|
||||
</div>
|
||||
|
||||
{/* Filter & Search Toolbar */}
|
||||
<div className="bg-[#0f1523]/80 backdrop-blur-md p-4 rounded-2xl border border-slate-800/80 shadow-xl flex flex-col lg:flex-row items-stretch lg:items-center justify-between gap-3">
|
||||
<div className="relative flex-1 max-w-md">
|
||||
<Search className="w-4 h-4 text-slate-500 absolute left-3 top-3" />
|
||||
<input
|
||||
type="text"
|
||||
value={notificationSearchTerm}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
</div>
|
||||
<div className="bg-[#0d121f]/90 p-4 rounded-2xl border border-slate-800/80 backdrop-blur-sm shadow-xl flex flex-col lg:flex-row lg:items-center justify-between gap-3 relative z-20">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center gap-3 flex-1 flex-wrap">
|
||||
<div className="relative flex-1 min-w-[240px] max-w-md">
|
||||
<Search className="w-4 h-4 text-slate-500 absolute left-3 top-3" />
|
||||
<input
|
||||
type="text"
|
||||
value={notificationSearchTerm}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-xs font-semibold text-slate-400 whitespace-nowrap">Target Org:</span>
|
||||
<Building2 className="w-3.5 h-3.5 text-blue-400" />
|
||||
@@ -1045,7 +1057,9 @@ export const SettingsPage: React.FC = () => {
|
||||
Reset
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2.5 self-end sm:self-center shrink-0 flex-wrap">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
@@ -1080,13 +1094,13 @@ export const SettingsPage: React.FC = () => {
|
||||
</h3>
|
||||
<HelpTooltip
|
||||
title="Communications Telemetry"
|
||||
content="Real-time notification reports returned via /v1/api/communications/norifications/{organisationId}. Displays live delivery status across Email, SMS, WhatsApp, Webhooks, and In-App channels."
|
||||
content="Real-time notification reports. Displays live delivery status across Email, SMS, WhatsApp, Webhooks, and In-App channels."
|
||||
size="sm"
|
||||
placement="right"
|
||||
/>
|
||||
</div>
|
||||
<span className="text-[11px] text-slate-400 font-mono">
|
||||
GET /v1/api/communications/norifications/{effectiveNotificationOrgId}
|
||||
<span className="text-xs text-slate-400 font-mono">
|
||||
Org #{effectiveNotificationOrgId}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -1094,14 +1108,28 @@ export const SettingsPage: React.FC = () => {
|
||||
<table className="w-full text-left border-collapse text-xs">
|
||||
<thead>
|
||||
<tr className="border-b border-slate-800 bg-slate-900/40 text-slate-400 font-medium">
|
||||
<th className="py-3.5 px-4">Notification ID</th>
|
||||
<th className="py-3.5 px-4">Channel / Platform</th>
|
||||
<th className="py-3.5 px-4">Status</th>
|
||||
<th className="py-3.5 px-4">Subject & Message Payload</th>
|
||||
<th className="py-3.5 px-4">Recipient & Destination</th>
|
||||
<th className="py-3.5 px-4">Target Org</th>
|
||||
<th className="py-3.5 px-4">Timestamp</th>
|
||||
<th className="py-3.5 px-4 text-right">Actions</th>
|
||||
<SortableTh column="id" currentColumn={notifSortCol} direction={notifSortDir} onSort={handleNotifSort} className="py-3.5 px-4">
|
||||
Notification ID
|
||||
</SortableTh>
|
||||
<SortableTh column="platform" currentColumn={notifSortCol} direction={notifSortDir} onSort={handleNotifSort} className="py-3.5 px-4">
|
||||
Channel / Platform
|
||||
</SortableTh>
|
||||
<SortableTh column="status" currentColumn={notifSortCol} direction={notifSortDir} onSort={handleNotifSort} className="py-3.5 px-4">
|
||||
Status
|
||||
</SortableTh>
|
||||
<SortableTh column="subject" currentColumn={notifSortCol} direction={notifSortDir} onSort={handleNotifSort} className="py-3.5 px-4">
|
||||
Subject & Message Payload
|
||||
</SortableTh>
|
||||
<SortableTh column="recipient" currentColumn={notifSortCol} direction={notifSortDir} onSort={handleNotifSort} className="py-3.5 px-4">
|
||||
Recipient & Destination
|
||||
</SortableTh>
|
||||
<SortableTh column="organisationId" currentColumn={notifSortCol} direction={notifSortDir} onSort={handleNotifSort} className="py-3.5 px-4">
|
||||
Target Org
|
||||
</SortableTh>
|
||||
<SortableTh column="createdAt" currentColumn={notifSortCol} direction={notifSortDir} onSort={handleNotifSort} className="py-3.5 px-4">
|
||||
Timestamp
|
||||
</SortableTh>
|
||||
<th className="py-3.5 px-4 text-right font-medium">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-800/50">
|
||||
@@ -1109,10 +1137,10 @@ export const SettingsPage: React.FC = () => {
|
||||
<tr>
|
||||
<td colSpan={8} className="py-12 text-center text-slate-500">
|
||||
<RefreshCw className="w-6 h-6 animate-spin mx-auto mb-2 text-blue-400" />
|
||||
Querying communication microservice endpoints for Organisation #{effectiveNotificationOrgId}...
|
||||
Loading communication records for Organisation #{effectiveNotificationOrgId}...
|
||||
</td>
|
||||
</tr>
|
||||
) : filteredNotifications.length === 0 ? (
|
||||
) : sortedNotifications.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={8} className="py-12 text-center text-slate-500">
|
||||
<Bell className="w-8 h-8 text-slate-600 mx-auto mb-2" />
|
||||
@@ -1120,7 +1148,7 @@ export const SettingsPage: React.FC = () => {
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
filteredNotifications.map((item) => {
|
||||
sortedNotifications.map((item) => {
|
||||
const statusBadge = getNotificationStatusBadge(item.status);
|
||||
return (
|
||||
<tr key={item.id} className="hover:bg-slate-800/40 transition-colors">
|
||||
@@ -1332,13 +1360,13 @@ export const SettingsPage: React.FC = () => {
|
||||
</h3>
|
||||
<HelpTooltip
|
||||
title="Audit Trail"
|
||||
content="Authoritative record of state changes retrieved via POST /v1/api/governance/auditlog."
|
||||
content="Authoritative record of state changes and system activities."
|
||||
size="sm"
|
||||
placement="right"
|
||||
/>
|
||||
</div>
|
||||
<span className="text-[11px] text-slate-400 font-mono">
|
||||
POST /v1/api/governance/auditlog
|
||||
<span className="text-xs text-slate-400 font-mono">
|
||||
{filteredAuditLogs.length} entries
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -1347,13 +1375,25 @@ export const SettingsPage: React.FC = () => {
|
||||
<thead>
|
||||
<tr className="bg-slate-900/60 border-b border-slate-800/80 text-slate-400 uppercase tracking-wider font-semibold">
|
||||
<th className="py-3.5 px-3 w-10 text-center"></th>
|
||||
<th className="py-3.5 px-4">Timestamp</th>
|
||||
<th className="py-3.5 px-4">Event ID</th>
|
||||
<th className="py-3.5 px-4">Action</th>
|
||||
<th className="py-3.5 px-4">Entity</th>
|
||||
<th className="py-3.5 px-4">Performed By</th>
|
||||
<th className="py-3.5 px-4">Changes</th>
|
||||
<th className="py-3.5 px-5 text-right">Inspect</th>
|
||||
<SortableTh column="timestamp" currentColumn={auditSortCol} direction={auditSortDir} onSort={handleAuditSort} className="py-3.5 px-4">
|
||||
Timestamp
|
||||
</SortableTh>
|
||||
<SortableTh column="id" currentColumn={auditSortCol} direction={auditSortDir} onSort={handleAuditSort} className="py-3.5 px-4">
|
||||
Event ID
|
||||
</SortableTh>
|
||||
<SortableTh column="action" currentColumn={auditSortCol} direction={auditSortDir} onSort={handleAuditSort} className="py-3.5 px-4">
|
||||
Action
|
||||
</SortableTh>
|
||||
<SortableTh column="entityName" currentColumn={auditSortCol} direction={auditSortDir} onSort={handleAuditSort} className="py-3.5 px-4">
|
||||
Entity
|
||||
</SortableTh>
|
||||
<SortableTh column="performedBy" currentColumn={auditSortCol} direction={auditSortDir} onSort={handleAuditSort} className="py-3.5 px-4">
|
||||
Performed By
|
||||
</SortableTh>
|
||||
<SortableTh column="changes" currentColumn={auditSortCol} direction={auditSortDir} onSort={handleAuditSort} className="py-3.5 px-4">
|
||||
Changes
|
||||
</SortableTh>
|
||||
<th className="py-3.5 px-5 text-right font-medium">Inspect</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-800/50">
|
||||
@@ -1361,20 +1401,20 @@ export const SettingsPage: React.FC = () => {
|
||||
<tr>
|
||||
<td colSpan={8} className="py-12 text-center text-slate-500">
|
||||
<RefreshCw className="w-6 h-6 animate-spin mx-auto mb-2 text-blue-400" />
|
||||
Querying governance audit log endpoints...
|
||||
Loading governance audit records...
|
||||
</td>
|
||||
</tr>
|
||||
) : filteredAuditLogs.length === 0 ? (
|
||||
) : sortedAuditLogs.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={8} className="py-12 text-center text-slate-500">
|
||||
<History className="w-8 h-8 text-slate-600 mx-auto mb-2" />
|
||||
{auditSearchTerm || auditEntityFilter !== 'all' || auditActionFilter !== 'all'
|
||||
? 'No audit log entries match your filter criteria.'
|
||||
: 'No audit records returned from endpoint.'}
|
||||
: 'No audit records found.'}
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
filteredAuditLogs.map((log) => {
|
||||
sortedAuditLogs.map((log) => {
|
||||
const badge = getActionBadge(log.action);
|
||||
const isExpanded = !!expandedAuditLogIds[log.id];
|
||||
const changesCount = Array.isArray(log.changes) ? log.changes.length : 0;
|
||||
@@ -1574,7 +1614,7 @@ export const SettingsPage: React.FC = () => {
|
||||
setCopiedAuditPayload(false);
|
||||
}}
|
||||
title={`Audit Event: #${selectedAuditLog?.id || ''}`}
|
||||
subtitle="Authoritative event payload from /v1/api/governance/auditlog"
|
||||
subtitle="Authoritative event payload and activity ledger"
|
||||
>
|
||||
{selectedAuditLog && (
|
||||
<div className="space-y-4 text-xs">
|
||||
@@ -2111,7 +2151,7 @@ export const SettingsPage: React.FC = () => {
|
||||
{/* Read-only Engine Notice */}
|
||||
<div className="p-2.5 bg-blue-500/5 border border-blue-500/20 rounded-xl text-[11px] text-slate-400 leading-relaxed">
|
||||
<span className="font-semibold text-blue-400">Communication System Report: </span>
|
||||
Data returned via <span className="font-mono text-slate-300">GET /v1/api/communications/norifications/{selectedNotificationForDetail.organisationId}</span>. Notification status transitions and retry schedules are managed autonomously by gateway microservices.
|
||||
Notification status transitions and retry schedules are managed autonomously by communication gateway services.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user