diff --git a/PostFundManagement.ConsoleUI/src/pages/SettingsPage.tsx b/PostFundManagement.ConsoleUI/src/pages/SettingsPage.tsx index 8ba8e97..1341c5a 100644 --- a/PostFundManagement.ConsoleUI/src/pages/SettingsPage.tsx +++ b/PostFundManagement.ConsoleUI/src/pages/SettingsPage.tsx @@ -73,6 +73,8 @@ import { useToast } from '../components/ui/Toast'; import { ExportButton } from '../components/ui/ExportButton'; import { ExportColumn } from '../utils/exportUtils'; import { useTableSort, SortableTh } from '../utils/tableSort'; +import { MarkdownViewer } from '../components/notifications/MarkdownViewer'; +import { isHtmlContent, stripMarkdown } from '../lib/markdownUtils'; import { UserRoleBadge } from '../components/ui/StatusBadge'; import { useAuth } from '../auth/AuthProvider'; import { @@ -439,6 +441,9 @@ export const SettingsPage: React.FC = () => { const [selectedNotificationForDetail, setSelectedNotificationForDetail] = useState(null); const [isNotificationDetailModalOpen, setIsNotificationDetailModalOpen] = useState(false); const [copiedNotificationPayload, setCopiedNotificationPayload] = useState(false); + const [isNotificationRenderModalOpen, setIsNotificationRenderModalOpen] = useState(false); + const [renderedViewTab, setRenderedViewTab] = useState<'rendered' | 'source'>('rendered'); + const [copiedRenderedContent, setCopiedRenderedContent] = useState(false); const effectiveNotificationOrgId = notificationOrgFilter || (organisations[0]?.id ? String(organisations[0].id) : '1'); @@ -455,7 +460,7 @@ export const SettingsPage: React.FC = () => { }); const notificationExportColumns: ExportColumn[] = [ - { header: 'Notification ID', key: 'id' }, + { header: '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) }, @@ -463,7 +468,14 @@ export const SettingsPage: React.FC = () => { { 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: 'Target Organisation', + key: 'organisationId', + format: (val) => { + const org = organisations.find((o) => String(o.id) === String(val)); + return org?.name || `Org #${val}`; + }, + }, { header: 'Message Content', key: 'message', format: (val) => val || '—' }, { header: 'Error Diagnostics', key: 'error', format: (val) => val || 'None' }, ]; @@ -483,13 +495,16 @@ export const SettingsPage: React.FC = () => { const recip = (item.recipient || '').toLowerCase(); const dest = (item.destination || '').toLowerCase(); const err = (item.error || '').toLowerCase(); + const org = organisations.find((o) => String(o.id) === String(item.organisationId)); + const orgName = (org?.name || '').toLowerCase(); return ( id.includes(q) || subj.includes(q) || msg.includes(q) || recip.includes(q) || dest.includes(q) || - err.includes(q) + err.includes(q) || + orgName.includes(q) ); }); @@ -498,7 +513,13 @@ export const SettingsPage: React.FC = () => { sortColumn: notifSortCol, sortDirection: notifSortDir, handleSort: handleNotifSort, - } = useTableSort(filteredNotifications, 'createdAt', 'desc'); + } = useTableSort(filteredNotifications, 'createdAt', 'desc', { + organisationId: (item: Notification) => { + const org = organisations.find((o) => String(o.id) === String(item.organisationId)); + return org?.name || `Organisation #${item.organisationId}`; + }, + destination: (item: Notification) => item.destination || '', + }); const resolveInvitePlatform = (inv: Invite): NotificationPlatform => { if (inv.platform !== undefined && inv.platform !== null) { @@ -1664,11 +1685,11 @@ export const SettingsPage: React.FC = () => {
- +
- - Notification ID + + ID Channel / Platform @@ -1676,11 +1697,8 @@ export const SettingsPage: React.FC = () => { Status - - Subject & Message Payload - - - Recipient & Destination + + Destination Target Org @@ -1694,14 +1712,14 @@ export const SettingsPage: React.FC = () => { {loadingNotifications ? ( - ) : sortedNotifications.length === 0 ? ( - @@ -1709,9 +1727,11 @@ export const SettingsPage: React.FC = () => { ) : ( sortedNotifications.map((item) => { const statusBadge = getNotificationStatusBadge(item.status); + const org = organisations.find((o) => String(o.id) === String(item.organisationId)); + const orgName = org?.name || (item.organisationId ? `Organisation #${item.organisationId}` : '—'); return ( - - -
+ Loading communication records for Organisation #{effectiveNotificationOrgId}...
+ No notification records returned for Organisation #{effectiveNotificationOrgId}.
+ #{item.id} @@ -1726,22 +1746,13 @@ export const SettingsPage: React.FC = () => { {statusBadge.label} -
{item.subject || 'Statutory Notice'}
-
{item.message || '—'}
-
-
- {item.recipient} -
- {item.destination && ( -
- {item.destination} -
- )} + + {item.destination || '—'} +
- Org #{item.organisationId} + + {orgName} {formatDate(item.createdAt)} @@ -3001,8 +3012,19 @@ export const SettingsPage: React.FC = () => { {/* Message */}
- Message Content: -
+
+ Message Content: + +
+
{selectedNotificationForDetail.message || '—'}
@@ -3026,8 +3048,10 @@ export const SettingsPage: React.FC = () => { {/* Organisation & Tenant Grid */}
- Organisation ID: - Org #{selectedNotificationForDetail.organisationId} + Target Organisation: + + {organisations.find((o) => String(o.id) === String(selectedNotificationForDetail.organisationId))?.name || `Org #${selectedNotificationForDetail.organisationId}`} +
Tenant Scope: @@ -3109,13 +3133,22 @@ export const SettingsPage: React.FC = () => {
-
+
+
)} + + {/* ============================================================== */} + {/* Modal: Rendered Notification Contents Preview */} + {/* ============================================================== */} + setIsNotificationRenderModalOpen(false)} + title={`Rendered Notification: #${selectedNotificationForDetail?.id || ''}`} + subtitle={ + selectedNotificationForDetail + ? `${getNotificationPlatformLabel(selectedNotificationForDetail.platform)} Delivery View • ${ + selectedNotificationForDetail.platform === NotificationPlatform.InApp ? 'Markdown Format' : 'HTML Format' + }` + : 'Rendered message output' + } + maxWidth="4xl" + zIndex="z-[10000]" + > + {selectedNotificationForDetail && (() => { + const isEmail = selectedNotificationForDetail.platform === NotificationPlatform.Email; + const isHtml = isEmail || isHtmlContent(selectedNotificationForDetail.message || ''); + const targetOrg = organisations.find( + (o) => String(o.id) === String(selectedNotificationForDetail.organisationId) + ); + const orgName = targetOrg?.name || `Organisation #${selectedNotificationForDetail.organisationId}`; + + return ( +
+ {/* Header Info Bar */} +
+
+ + {getNotificationPlatformLabel(selectedNotificationForDetail.platform)} + + + {isHtml ? 'HTML Format' : 'Markdown Format'} + + + Target Org: {orgName} + + {selectedNotificationForDetail.destination && ( + + Destination: {selectedNotificationForDetail.destination} + + )} +
+ +
+
+ + +
+ + +
+
+ + {/* Subject */} + {selectedNotificationForDetail.subject && ( +
+ Subject +
+ {selectedNotificationForDetail.subject} +
+
+ )} + + {/* Body Content */} + {renderedViewTab === 'source' ? ( +
+
+                    {selectedNotificationForDetail.message || '—'}
+                  
+
+ ) : isHtml ? ( +
+
+ + + Rendered Email HTML + + Sandboxed HTML Canvas +
+
+