@burdenoff/microfe-channel 2026.526.1 → 2026.527.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/channel/ChannelRoutes.js +0 -1
- package/dist/channel/ChannelRoutes.js.map +1 -1
- package/dist/channel/components/AdapterCredentialsForm.js +14 -2
- package/dist/channel/components/AdapterCredentialsForm.js.map +1 -1
- package/dist/channel/components/FeatureNotEnabled.js +1 -1
- package/dist/channel/components/FeatureNotEnabled.js.map +1 -1
- package/dist/channel/components/PermissionGuard.js +1 -1
- package/dist/channel/components/PermissionGuard.js.map +1 -1
- package/dist/channel/config/adapterCredentials.js +19 -11
- package/dist/channel/config/adapterCredentials.js.map +1 -1
- package/dist/channel/hooks/useChannelSubscription.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelDashboardPage.js +18 -0
- package/dist/channel/modules/channels/pages/ChannelDashboardPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelDetailsPage.js +411 -386
- package/dist/channel/modules/channels/pages/ChannelDetailsPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelSettingsPage.js +30 -15
- package/dist/channel/modules/channels/pages/ChannelSettingsPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelUsersPage.js +7 -1
- package/dist/channel/modules/channels/pages/ChannelUsersPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelsListPage.js +26 -2
- package/dist/channel/modules/channels/pages/ChannelsListPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js +19 -11
- package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ConnectionsListPage.js +39 -22
- package/dist/channel/modules/channels/pages/ConnectionsListPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/CreateChannelPage.js +7 -0
- package/dist/channel/modules/channels/pages/CreateChannelPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/CreateConnectionPage.js +16 -5
- package/dist/channel/modules/channels/pages/CreateConnectionPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js +10 -3
- package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/EditChannelPage.js +17 -8
- package/dist/channel/modules/channels/pages/EditChannelPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/EditConnectionPage.js +11 -6
- package/dist/channel/modules/channels/pages/EditConnectionPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/SendMessagePage.js +21 -9
- package/dist/channel/modules/channels/pages/SendMessagePage.js.map +1 -1
- package/dist/channel/modules/channels/pages/WebhookEventsPage.js +7 -0
- package/dist/channel/modules/channels/pages/WebhookEventsPage.js.map +1 -1
- package/dist/channel/pages/ChannelLandingPage.js +0 -1
- package/dist/channel/pages/ChannelLandingPage.js.map +1 -1
- package/dist/channel/providers/ChannelProvider.js +1 -1
- package/dist/channel/providers/ChannelProvider.js.map +1 -1
- package/package.json +1 -1
- package/dist/channel/modules/channels/pages/index.js +0 -14
|
@@ -255,10 +255,12 @@ function M() {
|
|
|
255
255
|
}), /* @__PURE__ */ D("div", {
|
|
256
256
|
className: "flex items-center gap-2",
|
|
257
257
|
children: [/* @__PURE__ */ D("button", {
|
|
258
|
+
type: "button",
|
|
258
259
|
onClick: () => Y(),
|
|
259
260
|
className: "inline-flex items-center gap-1.5 px-3 py-2 text-sm text-muted-foreground hover:text-foreground border border-border rounded-lg hover:hover:bg-action-primary-bg/10 transition-colors",
|
|
260
261
|
children: [/* @__PURE__ */ E(w, { size: 14 }), v("channel.refresh", "Refresh")]
|
|
261
262
|
}), /* @__PURE__ */ D("button", {
|
|
263
|
+
type: "button",
|
|
262
264
|
onClick: () => h("/channels/create"),
|
|
263
265
|
className: "inline-flex items-center gap-1.5 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bg-hover rounded-lg transition-colors",
|
|
264
266
|
children: [/* @__PURE__ */ E(x, { size: 14 }), v("channel.createChannel", "Create Channel")]
|
|
@@ -316,6 +318,7 @@ function M() {
|
|
|
316
318
|
]
|
|
317
319
|
}),
|
|
318
320
|
/* @__PURE__ */ E("button", {
|
|
321
|
+
type: "button",
|
|
319
322
|
onClick: () => W({ status: "ERROR" }),
|
|
320
323
|
className: "ml-auto text-xs font-medium text-status-error-text hover:text-status-error-text/80 transition-colors",
|
|
321
324
|
children: v("channel.showErrors", "Show errors")
|
|
@@ -332,6 +335,7 @@ function M() {
|
|
|
332
335
|
className: "absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground"
|
|
333
336
|
}), /* @__PURE__ */ E("input", {
|
|
334
337
|
type: "text",
|
|
338
|
+
"aria-label": v("channel.searchChannels", "Search channels..."),
|
|
335
339
|
placeholder: v("channel.searchChannels", "Search channels..."),
|
|
336
340
|
value: U.search || "",
|
|
337
341
|
onChange: (e) => W({ search: e.target.value }),
|
|
@@ -382,6 +386,7 @@ function M() {
|
|
|
382
386
|
})]
|
|
383
387
|
}),
|
|
384
388
|
/* @__PURE__ */ E("button", {
|
|
389
|
+
type: "button",
|
|
385
390
|
onClick: () => Y(),
|
|
386
391
|
className: "shrink-0 px-3 py-1 text-sm text-status-error-text hover:bg-status-error-bg-subtle rounded transition-colors",
|
|
387
392
|
children: v("channel.retry", "Retry")
|
|
@@ -414,6 +419,7 @@ function M() {
|
|
|
414
419
|
children: U.search || U.platform || U.status ? v("channel.adjustFilters", "Try adjusting your search or filters.") : v("channel.createFirstChannel", "Create your first communication channel to start sending messages across Email, SMS, Push, Slack and more.")
|
|
415
420
|
}),
|
|
416
421
|
!(U.search || U.platform || U.status) && /* @__PURE__ */ D("button", {
|
|
422
|
+
type: "button",
|
|
417
423
|
onClick: () => h("/channels/create"),
|
|
418
424
|
className: "inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bg-hover rounded-lg transition-colors",
|
|
419
425
|
children: [/* @__PURE__ */ E(x, { size: 14 }), v("channel.createChannel", "Create Channel")]
|
|
@@ -472,6 +478,7 @@ function M() {
|
|
|
472
478
|
/* @__PURE__ */ E("td", {
|
|
473
479
|
className: "px-4 py-3",
|
|
474
480
|
children: /* @__PURE__ */ D("button", {
|
|
481
|
+
type: "button",
|
|
475
482
|
onClick: () => h(`/channels/${e.id}`),
|
|
476
483
|
className: "text-left group/name",
|
|
477
484
|
children: [/* @__PURE__ */ E("p", {
|
|
@@ -540,6 +547,7 @@ function M() {
|
|
|
540
547
|
className: "flex items-center justify-end gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity",
|
|
541
548
|
children: [
|
|
542
549
|
/* @__PURE__ */ E("button", {
|
|
550
|
+
type: "button",
|
|
543
551
|
onClick: () => h(`/channels/${e.id}`),
|
|
544
552
|
className: "p-1.5 text-muted-foreground hover:text-accent-primary hover:bg-accent-primary/10 rounded transition-colors",
|
|
545
553
|
title: v("channel.view", "View"),
|
|
@@ -548,6 +556,7 @@ function M() {
|
|
|
548
556
|
/* @__PURE__ */ E(s, {
|
|
549
557
|
permission: c.CHANNEL_EXECUTE,
|
|
550
558
|
children: /* @__PURE__ */ E("button", {
|
|
559
|
+
type: "button",
|
|
551
560
|
onClick: () => _e(e.id),
|
|
552
561
|
disabled: t,
|
|
553
562
|
className: "p-1.5 text-muted-foreground hover:text-accent-blue hover:bg-accent-blue-subtle rounded transition-colors disabled:opacity-40",
|
|
@@ -561,6 +570,7 @@ function M() {
|
|
|
561
570
|
/* @__PURE__ */ E(s, {
|
|
562
571
|
permission: c.CHANNEL_UPDATE,
|
|
563
572
|
children: /* @__PURE__ */ E("button", {
|
|
573
|
+
type: "button",
|
|
564
574
|
onClick: () => ge(e),
|
|
565
575
|
disabled: t,
|
|
566
576
|
className: `p-1.5 rounded transition-colors disabled:opacity-40 ${e.status === "ACTIVE" ? "text-muted-foreground hover:text-status-warning-text hover:bg-status-warning-bg-subtle" : "text-muted-foreground hover:text-status-success-text hover:bg-status-success-bg-subtle"}`,
|
|
@@ -571,11 +581,13 @@ function M() {
|
|
|
571
581
|
T === e.id ? /* @__PURE__ */ D("span", {
|
|
572
582
|
className: "inline-flex items-center gap-1 ml-1",
|
|
573
583
|
children: [/* @__PURE__ */ E("button", {
|
|
584
|
+
type: "button",
|
|
574
585
|
onClick: () => he(e.id),
|
|
575
586
|
disabled: t,
|
|
576
587
|
className: "px-1.5 py-0.5 text-[11px] font-medium text-status-error-text hover:bg-status-error-bg-subtle rounded transition-colors",
|
|
577
588
|
children: t ? "..." : v("channel.yes", "Yes")
|
|
578
589
|
}), /* @__PURE__ */ E("button", {
|
|
590
|
+
type: "button",
|
|
579
591
|
onClick: () => M(null),
|
|
580
592
|
className: "px-1.5 py-0.5 text-[11px] text-muted-foreground hover:hover:bg-action-primary-bg/10 rounded transition-colors",
|
|
581
593
|
children: v("channel.no", "No")
|
|
@@ -583,6 +595,7 @@ function M() {
|
|
|
583
595
|
}) : /* @__PURE__ */ E(s, {
|
|
584
596
|
permission: c.CHANNEL_DELETE,
|
|
585
597
|
children: /* @__PURE__ */ E("button", {
|
|
598
|
+
type: "button",
|
|
586
599
|
onClick: () => M(e.id),
|
|
587
600
|
className: "p-1.5 text-muted-foreground hover:text-status-error-text hover:bg-status-error-bg-subtle rounded transition-colors",
|
|
588
601
|
title: v("channel.delete", "Delete"),
|
|
@@ -626,6 +639,7 @@ function M() {
|
|
|
626
639
|
className: "flex items-center gap-2",
|
|
627
640
|
children: [
|
|
628
641
|
/* @__PURE__ */ E("button", {
|
|
642
|
+
type: "button",
|
|
629
643
|
onClick: () => F((e) => Math.max(0, e - 1)),
|
|
630
644
|
disabled: P === 0,
|
|
631
645
|
className: "px-2.5 py-1 text-xs border border-border rounded hover:bg-muted transition-colors disabled:opacity-40 disabled:cursor-not-allowed",
|
|
@@ -640,6 +654,7 @@ function M() {
|
|
|
640
654
|
]
|
|
641
655
|
}),
|
|
642
656
|
/* @__PURE__ */ E("button", {
|
|
657
|
+
type: "button",
|
|
643
658
|
onClick: () => F((e) => Math.min(X - 1, e + 1)),
|
|
644
659
|
disabled: P >= X - 1,
|
|
645
660
|
className: "px-2.5 py-1 text-xs border border-border rounded hover:bg-muted transition-colors disabled:opacity-40 disabled:cursor-not-allowed",
|
|
@@ -653,6 +668,7 @@ function M() {
|
|
|
653
668
|
className: "grid gap-3 sm:grid-cols-3",
|
|
654
669
|
children: [
|
|
655
670
|
/* @__PURE__ */ D("button", {
|
|
671
|
+
type: "button",
|
|
656
672
|
onClick: () => h("/channels/?tab=all"),
|
|
657
673
|
className: "flex items-center gap-3 p-3 bg-surface border border-border rounded-lg hover:hover:bg-action-primary-bg/10 transition-colors text-left",
|
|
658
674
|
children: [/* @__PURE__ */ E(C, {
|
|
@@ -670,6 +686,7 @@ function M() {
|
|
|
670
686
|
})]
|
|
671
687
|
}),
|
|
672
688
|
/* @__PURE__ */ D("button", {
|
|
689
|
+
type: "button",
|
|
673
690
|
onClick: () => h("/channels/?tab=connections"),
|
|
674
691
|
className: "flex items-center gap-3 p-3 bg-surface border border-border rounded-lg hover:hover:bg-action-primary-bg/10 transition-colors text-left",
|
|
675
692
|
children: [/* @__PURE__ */ E(g, {
|
|
@@ -687,6 +704,7 @@ function M() {
|
|
|
687
704
|
})]
|
|
688
705
|
}),
|
|
689
706
|
/* @__PURE__ */ D("button", {
|
|
707
|
+
type: "button",
|
|
690
708
|
onClick: () => h("/channels/?tab=send"),
|
|
691
709
|
className: "flex items-center gap-3 p-3 bg-surface border border-border rounded-lg hover:hover:bg-action-primary-bg/10 transition-colors text-left",
|
|
692
710
|
children: [/* @__PURE__ */ E(p, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChannelDashboardPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/ChannelDashboardPage.tsx"],"sourcesContent":["/**\n * Channel Dashboard Page\n *\n * Main overview page for the Channel microfrontend.\n * Fetches real data from global-channel-svc via the useChannels hook.\n *\n * Sections:\n * 1. Header with Create Channel action\n * 2. KPI summary cards (total, active, healthy, connected)\n * 3. Search + platform/status filters\n * 4. Channel table with inline actions\n */\n\nimport { useState, useMemo, useCallback, useEffect, useRef } from 'react';\nimport { nativeImpact, nativeNotify } from '../../../../utils/nativeBridge';\nimport { subscribeShellEvent } from '../../../utils/shellBridge';\nimport { useChannel as useChannelContext } from '../../../providers/ChannelProvider';\nimport {\n CHANNEL_HEALTH_CHANGED_SUBSCRIPTION,\n useChannelSubscription,\n} from '../../../hooks/useChannelSubscription';\nimport { useNavigate } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n Radio,\n Plus,\n Search,\n Loader2,\n AlertCircle,\n RefreshCw,\n Power,\n PowerOff,\n Eye,\n Trash2,\n CheckCircle2,\n XCircle,\n AlertTriangle,\n HelpCircle,\n Cable,\n MessageSquare,\n ShieldCheck,\n Activity,\n LayoutDashboard,\n} from 'lucide-react';\nimport {\n useChannels,\n type Channel,\n type ChannelPlatform,\n type ChannelStatus,\n type ConnectionHealth,\n} from '../../../hooks/useChannels';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\nimport { HEALTH_I18N, PLATFORM_I18N, STATUS_I18N, translateLabel } from '../i18n';\n\n// ============================================================================\n// DISPLAY CONFIG\n// ============================================================================\n\nconst PLATFORM_CONFIG: Record<ChannelPlatform, { label: string; color: string }> = {\n EMAIL: { label: 'Email', color: 'bg-accent-blue-subtle text-accent-blue' },\n SMS: { label: 'SMS', color: 'bg-status-success-bg-subtle text-status-success-text' },\n PUSH: { label: 'Push', color: 'bg-accent-purple-subtle text-accent-purple' },\n INAPP: { label: 'In-App', color: 'bg-accent-indigo-subtle text-accent-indigo' },\n WEBHOOK: { label: 'Webhook', color: 'bg-accent-orange-subtle text-accent-orange' },\n SLACK: { label: 'Slack', color: 'bg-accent-pink-subtle text-accent-pink' },\n TEAMS: { label: 'Teams', color: 'bg-accent-cyan-subtle text-accent-cyan' },\n DISCORD: { label: 'Discord', color: 'bg-accent-violet-subtle text-accent-violet' },\n WHATSAPP: { label: 'WhatsApp', color: 'bg-accent-emerald-subtle text-accent-emerald' },\n WEBCHAT: { label: 'Webchat', color: 'bg-accent-teal-subtle text-accent-teal' },\n};\n\nconst STATUS_CONFIG: Record<\n ChannelStatus,\n { label: string; dotColor: string; textColor: string; icon: typeof CheckCircle2 }\n> = {\n ACTIVE: {\n label: 'Active',\n dotColor: 'bg-status-success-bg',\n textColor: 'text-status-success-text',\n icon: CheckCircle2,\n },\n INACTIVE: {\n label: 'Inactive',\n dotColor: 'bg-text-tertiary',\n textColor: 'text-muted-foreground',\n icon: PowerOff,\n },\n ERROR: {\n label: 'Error',\n dotColor: 'bg-status-error-bg-subtle',\n textColor: 'text-status-error-text',\n icon: XCircle,\n },\n SUSPENDED: {\n label: 'Suspended',\n dotColor: 'bg-status-warning-bg',\n textColor: 'text-status-warning-text',\n icon: AlertTriangle,\n },\n ARCHIVED: {\n label: 'Archived',\n dotColor: 'bg-text-tertiary',\n textColor: 'text-muted-foreground',\n icon: PowerOff,\n },\n};\n\nconst HEALTH_CONFIG: Record<\n ConnectionHealth,\n { label: string; color: string; icon: typeof CheckCircle2 }\n> = {\n HEALTHY: { label: 'Healthy', color: 'text-status-success-text', icon: CheckCircle2 },\n DEGRADED: { label: 'Degraded', color: 'text-status-warning-text', icon: AlertTriangle },\n UNHEALTHY: { label: 'Unhealthy', color: 'text-status-error-text', icon: XCircle },\n UNKNOWN: { label: 'Unknown', color: 'text-muted-foreground', icon: HelpCircle },\n};\n\n// ============================================================================\n// HELPERS\n// ============================================================================\n\nfunction formatRelativeTime(dateStr: string): string {\n const diffMs = Date.now() - new Date(dateStr).getTime();\n if (Number.isNaN(diffMs) || diffMs < 0) return 'just now';\n const sec = Math.floor(diffMs / 1000);\n if (sec < 60) return 'just now';\n const min = Math.floor(sec / 60);\n if (min < 60) return `${min}m ago`;\n const hr = Math.floor(min / 60);\n if (hr < 24) return `${hr}h ago`;\n const d = Math.floor(hr / 24);\n if (d < 7) return `${d}d ago`;\n if (d < 30) return `${Math.floor(d / 7)}w ago`;\n return `${Math.floor(d / 30)}mo ago`;\n}\n\n// ============================================================================\n// SUB-COMPONENTS\n// ============================================================================\n\nfunction KpiCard({\n icon: Icon,\n iconClass,\n value,\n label,\n sub,\n}: {\n icon: typeof Radio;\n iconClass: string;\n value: string | number;\n label: string;\n sub?: string;\n}) {\n return (\n <div className=\"p-4 bg-surface border border-border rounded-lg\">\n <div className=\"flex items-center gap-3\">\n <div className=\"p-2 rounded-lg hover:bg-action-primary-bg/10\">\n <Icon size={18} className={iconClass} />\n </div>\n <div>\n <p className=\"text-2xl font-bold text-foreground leading-tight\">{value}</p>\n <p className=\"text-xs text-muted-foreground\">{label}</p>\n </div>\n </div>\n {sub && <p className=\"text-[11px] text-muted-foreground mt-2 pl-11\">{sub}</p>}\n </div>\n );\n}\n\n// ============================================================================\n// MAIN COMPONENT\n// ============================================================================\n\nexport function ChannelDashboardPage() {\n const navigate = useNavigate();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const [deleteConfirm, setDeleteConfirm] = useState<string | null>(null);\n const [actionLoading, setActionLoading] = useState<string | null>(null);\n const pageSize = 10;\n const [currentPage, setCurrentPage] = useState(0);\n const getPlatformConfig = (platform: ChannelPlatform) => {\n const config = PLATFORM_CONFIG[platform] ?? {\n label: platform,\n color: 'bg-bg-sunken text-text-tertiary',\n };\n return {\n ...config,\n label: translateLabel(platform, PLATFORM_I18N, tr),\n };\n };\n const getStatusConfig = (status: ChannelStatus) => {\n const config = STATUS_CONFIG[status] ?? STATUS_CONFIG.INACTIVE;\n return {\n ...config,\n label: translateLabel(status, STATUS_I18N, tr),\n };\n };\n const getHealthConfig = (health: ConnectionHealth) => {\n const config = HEALTH_CONFIG[health] ?? HEALTH_CONFIG.UNKNOWN;\n return {\n ...config,\n label: translateLabel(health, HEALTH_I18N, tr),\n };\n };\n\n const {\n channels,\n isLoading,\n error,\n total,\n filter,\n updateFilter,\n deleteChannel,\n activateChannel,\n deactivateChannel,\n runHealthCheck,\n refetch,\n } = useChannels();\n\n const totalPages = Math.max(1, Math.ceil(total / pageSize));\n\n const refetchRef = useRef(refetch);\n useEffect(() => {\n refetchRef.current = refetch;\n });\n\n useEffect(() => {\n void refetchRef.current({ offset: currentPage * pageSize, limit: pageSize });\n }, [currentPage, pageSize]);\n\n useEffect(() => {\n void Promise.resolve().then(() => setCurrentPage(0));\n }, [filter.search, filter.platform, filter.status]);\n\n const { organizationId } = useChannelContext();\n\n // Real-time channel health stream. Subscribes when the gateway exposes\n // `channelHealthChanged`; otherwise falls back to polling every 60s.\n // Conservative fallback interval keeps gateway/svc cost low when subs\n // are unavailable; live updates flow through the subscription path.\n useChannelSubscription({\n query: CHANNEL_HEALTH_CHANGED_SUBSCRIPTION,\n variables: { organizationId },\n operationName: 'ChannelHealthChanged',\n enabled: Boolean(organizationId),\n onData: () => void refetch(),\n pollFn: refetch,\n pollIntervalMs: 60_000,\n });\n\n // ── Cross-MFE refresh: subscribe to channel/connection bus events so the\n // dashboard auto-updates when another tab or another MFE mutates state. ─\n useEffect(() => {\n const events = [\n 'channel.created',\n 'channel.updated',\n 'channel.deleted',\n 'connection.created',\n 'connection.updated',\n 'connection.deleted',\n 'delivery.completed',\n 'webhook.received',\n ];\n const unsubs = events.map((name) => subscribeShellEvent(name, () => void refetch()));\n return () => unsubs.forEach((u) => u());\n }, [refetch]);\n\n // ── Derived KPIs ──────────────────────────────────────────────────────\n const kpis = useMemo(() => {\n let active = 0;\n let healthy = 0;\n let connected = 0;\n let messages24h = 0;\n let errored = 0;\n let unhealthy = 0;\n\n for (const ch of channels) {\n if (ch.status === 'ACTIVE') active++;\n if (ch.status === 'ERROR') errored++;\n if (ch.connectionHealth === 'HEALTHY') healthy++;\n if (ch.connectionHealth === 'UNHEALTHY') unhealthy++;\n if (ch.isConnected) connected++;\n messages24h += ch.messageCount24h ?? 0;\n }\n\n return { active, healthy, connected, messages24h, errored, unhealthy };\n }, [channels]);\n\n // ── Action handlers ───────────────────────────────────────────────────\n const handleDelete = useCallback(\n async (id: string) => {\n void nativeImpact('medium');\n setActionLoading(id);\n try {\n await deleteChannel(id);\n void nativeNotify('success');\n setDeleteConfirm(null);\n } catch (err) {\n void nativeNotify('error');\n throw err;\n } finally {\n setActionLoading(null);\n }\n },\n [deleteChannel]\n );\n\n const handleToggle = useCallback(\n async (ch: Channel) => {\n setActionLoading(ch.id);\n try {\n if (ch.status === 'ACTIVE') await deactivateChannel(ch.id);\n else await activateChannel(ch.id);\n } finally {\n setActionLoading(null);\n }\n },\n [activateChannel, deactivateChannel]\n );\n\n const handleHealthCheck = useCallback(\n async (id: string) => {\n setActionLoading(id);\n try {\n await runHealthCheck(id);\n } finally {\n setActionLoading(null);\n }\n },\n [runHealthCheck]\n );\n\n // ── Render ────────────────────────────────────────────────────────────\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_READ}>\n <div className=\"space-y-5 p-3 sm:p-6\">\n {/* ── Header ───────────────────────────────────────────────────── */}\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-center gap-3\">\n <div className=\"p-2 rounded-lg bg-accent-primary/10\">\n <LayoutDashboard size={20} className=\"text-accent-primary\" />\n </div>\n <div>\n <h1 className=\"text-xl font-bold text-foreground\">\n {tr('channel.channelDashboard', 'Channel Dashboard')}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {tr('channel.dashboardSubtitle', 'Manage and monitor your communication channels')}\n </p>\n </div>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <button\n onClick={() => refetch()}\n className=\"inline-flex items-center gap-1.5 px-3 py-2 text-sm text-muted-foreground hover:text-foreground border border-border rounded-lg hover:hover:bg-action-primary-bg/10 transition-colors\"\n >\n <RefreshCw size={14} />\n {tr('channel.refresh', 'Refresh')}\n </button>\n <button\n onClick={() => navigate('/channels/create')}\n className=\"inline-flex items-center gap-1.5 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bg-hover rounded-lg transition-colors\"\n >\n <Plus size={14} />\n {tr('channel.createChannel', 'Create Channel')}\n </button>\n </div>\n </div>\n\n {/* ── KPI Cards ────────────────────────────────────────────────── */}\n <div className=\"grid gap-3 grid-cols-2 md:grid-cols-3 lg:grid-cols-4\">\n <KpiCard\n icon={Radio}\n iconClass=\"text-accent-primary\"\n value={total}\n label={tr('channel.totalChannels', 'Total Channels')}\n sub={\n kpis.active > 0\n ? `${kpis.active} ${tr('channel.status.active', 'Active').toLowerCase()}, ${total - kpis.active} ${tr('channel.status.inactive', 'Inactive').toLowerCase()}`\n : undefined\n }\n />\n <KpiCard\n icon={ShieldCheck}\n iconClass=\"text-status-success-text\"\n value={kpis.healthy}\n label={tr('channel.healthy', 'Healthy')}\n sub={\n kpis.unhealthy > 0\n ? `${kpis.unhealthy} ${tr('channel.health.unhealthy', 'Unhealthy').toLowerCase()}`\n : total > 0\n ? tr('channel.allSystemsNominal', 'All systems nominal')\n : undefined\n }\n />\n <KpiCard\n icon={Cable}\n iconClass=\"text-accent-blue\"\n value={kpis.connected}\n label={tr('channel.connected', 'Connected')}\n sub={\n total > 0\n ? `${total - kpis.connected} ${tr('channel.disconnected', 'Disconnected').toLowerCase()}`\n : undefined\n }\n />\n <KpiCard\n icon={MessageSquare}\n iconClass=\"text-accent-violet\"\n value={kpis.messages24h.toLocaleString()}\n label={tr('channel.messages24h', 'Messages (24h)')}\n />\n </div>\n\n {/* ── Alerts ───────────────────────────────────────────────────── */}\n {kpis.errored > 0 && (\n <div className=\"flex items-center gap-3 p-3 border border-status-error-border/30 bg-status-error-bg-subtle rounded-lg\">\n <AlertCircle size={16} className=\"text-status-error-text shrink-0\" />\n <p className=\"text-sm text-status-error-text\">\n <span className=\"font-semibold\">{kpis.errored}</span>{' '}\n {tr(\n kpis.errored === 1\n ? 'channel.errorStateSummarySingle'\n : 'channel.errorStateSummaryMultiple',\n kpis.errored === 1 ? 'channel in error state' : 'channels in error state'\n )}\n </p>\n <button\n onClick={() => updateFilter({ status: 'ERROR' as ChannelStatus })}\n className=\"ml-auto text-xs font-medium text-status-error-text hover:text-status-error-text/80 transition-colors\"\n >\n {tr('channel.showErrors', 'Show errors')}\n </button>\n </div>\n )}\n\n {/* ── Search & Filters ─────────────────────────────────────────── */}\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center\">\n <div className=\"relative flex-1\">\n <Search\n size={14}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground\"\n />\n <input\n type=\"text\"\n placeholder={tr('channel.searchChannels', 'Search channels...')}\n value={filter.search || ''}\n onChange={(e) => updateFilter({ search: e.target.value })}\n className=\"w-full pl-9 pr-4 py-2 text-sm bg-surface border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500/50 placeholder:text-muted-foreground\"\n />\n </div>\n\n <select\n value={filter.platform || ''}\n onChange={(e) =>\n updateFilter({ platform: (e.target.value as ChannelPlatform) || undefined })\n }\n className=\"px-3 py-2 text-sm bg-surface border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500/50 text-foreground\"\n >\n <option value=\"\">{tr('channel.allPlatforms', 'All Platforms')}</option>\n {Object.entries(PLATFORM_CONFIG).map(([key, cfg]) => (\n <option key={key} value={key}>\n {cfg.label}\n </option>\n ))}\n </select>\n\n <select\n value={filter.status || ''}\n onChange={(e) =>\n updateFilter({ status: (e.target.value as ChannelStatus) || undefined })\n }\n className=\"px-3 py-2 text-sm bg-surface border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500/50 text-foreground\"\n >\n <option value=\"\">{tr('channel.allStatus', 'All Status')}</option>\n {Object.entries(STATUS_CONFIG).map(([key, cfg]) => (\n <option key={key} value={key}>\n {cfg.label}\n </option>\n ))}\n </select>\n </div>\n\n {/* ── Error State ──────────────────────────────────────────────── */}\n {error && (\n <div className=\"flex items-center gap-3 p-4 bg-status-error-bg-subtle border border-status-error-border/30 rounded-lg\">\n <AlertCircle size={18} className=\"text-status-error-text shrink-0\" />\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium text-status-error-text\">\n {tr('channel.loadFailed', 'Failed to load channels')}\n </p>\n <p className=\"text-xs text-status-error-text/70 truncate\">{error}</p>\n </div>\n <button\n onClick={() => refetch()}\n className=\"shrink-0 px-3 py-1 text-sm text-status-error-text hover:bg-status-error-bg-subtle rounded transition-colors\"\n >\n {tr('channel.retry', 'Retry')}\n </button>\n </div>\n )}\n\n {/* ── Loading ──────────────────────────────────────────────────── */}\n {isLoading && channels.length === 0 && (\n <div className=\"flex items-center justify-center py-16\">\n <Loader2 size={28} className=\"animate-spin text-accent-primary\" />\n </div>\n )}\n\n {/* ── Empty State ──────────────────────────────────────────────── */}\n {!isLoading && !error && channels.length === 0 && (\n <div className=\"flex flex-col items-center justify-center py-16 text-center\">\n <div className=\"p-3 rounded-full bg-accent-primary/10 mb-4\">\n <Radio size={36} className=\"text-accent-primary\" />\n </div>\n <h3 className=\"text-lg font-semibold text-foreground mb-1\">\n {filter.search || filter.platform || filter.status\n ? tr('channel.noChannelsMatchFilter', 'No channels match your filters')\n : tr('channel.noChannels', 'No channels yet')}\n </h3>\n <p className=\"text-sm text-muted-foreground max-w-sm mb-5\">\n {filter.search || filter.platform || filter.status\n ? tr('channel.adjustFilters', 'Try adjusting your search or filters.')\n : tr(\n 'channel.createFirstChannel',\n 'Create your first communication channel to start sending messages across Email, SMS, Push, Slack and more.'\n )}\n </p>\n {!(filter.search || filter.platform || filter.status) && (\n <button\n onClick={() => navigate('/channels/create')}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bg-hover rounded-lg transition-colors\"\n >\n <Plus size={14} />\n {tr('channel.createChannel', 'Create Channel')}\n </button>\n )}\n </div>\n )}\n\n {/* ── Channel Table ────────────────────────────────────────────── */}\n {channels.length > 0 && (\n <div className=\"border border-border rounded-lg overflow-hidden\">\n <div className=\"overflow-x-auto min-w-full\">\n <table className=\"w-full text-sm min-w-[800px]\">\n <thead>\n <tr className=\"hover:bg-action-primary-bg/10/50 border-b border-border\">\n <th className=\"text-left px-4 py-3 text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.channel', 'Channel')}\n </th>\n <th className=\"text-left px-4 py-3 text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.platform', 'Platform')}\n </th>\n <th className=\"text-left px-4 py-3 text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.status', 'Status')}\n </th>\n <th className=\"text-left px-4 py-3 text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.health', 'Health')}\n </th>\n <th className=\"text-left px-4 py-3 text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.connection', 'Connection')}\n </th>\n <th className=\"text-right px-4 py-3 text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.msgs24h', 'Msgs 24h')}\n </th>\n <th className=\"text-right px-4 py-3 text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.updated', 'Updated')}\n </th>\n <th className=\"text-right px-4 py-3 text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.actions', 'Actions')}\n </th>\n </tr>\n </thead>\n\n <tbody className=\"divide-y divide-border\">\n {channels.map((ch) => {\n const isAL = actionLoading === ch.id;\n\n return (\n <tr\n key={ch.id}\n className=\"bg-surface hover:hover:bg-action-primary-bg/10/60 transition-colors group\"\n >\n {/* Name */}\n <td className=\"px-4 py-3\">\n <button\n onClick={() => navigate(`/channels/${ch.id}`)}\n className=\"text-left group/name\"\n >\n <p className=\"font-medium text-foreground group-hover/name:text-accent-primary transition-colors truncate max-w-[200px]\">\n {ch.name}\n </p>\n {ch.description && (\n <p className=\"text-xs text-muted-foreground truncate max-w-[200px]\">\n {ch.description}\n </p>\n )}\n </button>\n </td>\n\n {/* Platform */}\n <td className=\"px-4 py-3\">\n <span\n className={`inline-flex items-center px-2 py-0.5 text-[11px] font-semibold rounded-full ${getPlatformConfig(ch.platform).color}`}\n >\n {getPlatformConfig(ch.platform).label}\n </span>\n {ch.adapterType && (\n <p className=\"text-[10px] text-muted-foreground mt-0.5 font-mono\">\n {ch.adapterType.replace(/_/g, ' ')}\n </p>\n )}\n </td>\n\n {/* Status */}\n <td className=\"px-4 py-3\">\n <span className=\"inline-flex items-center gap-1.5\">\n <span\n className={`w-1.5 h-1.5 rounded-full ${getStatusConfig(ch.status).dotColor}`}\n />\n <span className={`text-xs ${getStatusConfig(ch.status).textColor}`}>\n {getStatusConfig(ch.status).label}\n </span>\n </span>\n </td>\n\n {/* Health */}\n <td className=\"px-4 py-3\">\n <span\n className={`inline-flex items-center gap-1 text-xs ${getHealthConfig(ch.connectionHealth).color}`}\n >\n {(() => {\n const Icon = getHealthConfig(ch.connectionHealth).icon;\n return <Icon size={12} />;\n })()}\n {getHealthConfig(ch.connectionHealth).label}\n </span>\n </td>\n\n {/* Connection */}\n <td className=\"px-4 py-3\">\n <span\n className={`text-xs ${ch.isConnected ? 'text-status-success-text' : 'text-muted-foreground'}`}\n >\n {ch.isConnected\n ? tr('channel.connected', 'Connected')\n : tr('channel.disconnected', 'Disconnected')}\n </span>\n </td>\n\n {/* Messages 24h */}\n <td className=\"px-4 py-3 text-right\">\n <span className=\"text-xs text-foreground tabular-nums\">\n {(ch.messageCount24h ?? 0).toLocaleString()}\n </span>\n </td>\n\n {/* Updated */}\n <td className=\"px-4 py-3 text-right\">\n <span className=\"text-xs text-muted-foreground whitespace-nowrap\">\n {formatRelativeTime(ch.updatedAt)}\n </span>\n </td>\n\n {/* Actions */}\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center justify-end gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity\">\n <button\n onClick={() => navigate(`/channels/${ch.id}`)}\n className=\"p-1.5 text-muted-foreground hover:text-accent-primary hover:bg-accent-primary/10 rounded transition-colors\"\n title={tr('channel.view', 'View')}\n >\n <Eye size={14} />\n </button>\n\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_EXECUTE}>\n <button\n onClick={() => handleHealthCheck(ch.id)}\n disabled={isAL}\n className=\"p-1.5 text-muted-foreground hover:text-accent-blue hover:bg-accent-blue-subtle rounded transition-colors disabled:opacity-40\"\n title={tr('channel.healthCheck', 'Health Check')}\n >\n {isAL ? (\n <Loader2 size={14} className=\"animate-spin\" />\n ) : (\n <RefreshCw size={14} />\n )}\n </button>\n </PermissionGuard>\n\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_UPDATE}>\n <button\n onClick={() => handleToggle(ch)}\n disabled={isAL}\n className={`p-1.5 rounded transition-colors disabled:opacity-40 ${\n ch.status === 'ACTIVE'\n ? 'text-muted-foreground hover:text-status-warning-text hover:bg-status-warning-bg-subtle'\n : 'text-muted-foreground hover:text-status-success-text hover:bg-status-success-bg-subtle'\n }`}\n title={\n ch.status === 'ACTIVE'\n ? tr('channel.deactivate', 'Deactivate')\n : tr('channel.activate', 'Activate')\n }\n >\n {ch.status === 'ACTIVE' ? (\n <PowerOff size={14} />\n ) : (\n <Power size={14} />\n )}\n </button>\n </PermissionGuard>\n\n {deleteConfirm === ch.id ? (\n <span className=\"inline-flex items-center gap-1 ml-1\">\n <button\n onClick={() => handleDelete(ch.id)}\n disabled={isAL}\n className=\"px-1.5 py-0.5 text-[11px] font-medium text-status-error-text hover:bg-status-error-bg-subtle rounded transition-colors\"\n >\n {isAL ? '...' : tr('channel.yes', 'Yes')}\n </button>\n <button\n onClick={() => setDeleteConfirm(null)}\n className=\"px-1.5 py-0.5 text-[11px] text-muted-foreground hover:hover:bg-action-primary-bg/10 rounded transition-colors\"\n >\n {tr('channel.no', 'No')}\n </button>\n </span>\n ) : (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_DELETE}>\n <button\n onClick={() => setDeleteConfirm(ch.id)}\n className=\"p-1.5 text-muted-foreground hover:text-status-error-text hover:bg-status-error-bg-subtle rounded transition-colors\"\n title={tr('channel.delete', 'Delete')}\n >\n <Trash2 size={14} />\n </button>\n </PermissionGuard>\n )}\n </div>\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n\n {/* Table footer — pagination */}\n <div className=\"px-4 py-2.5 border-t border-border flex items-center justify-between\">\n <p className=\"text-xs text-muted-foreground\">\n {tr('channel.showing', 'Showing')}{' '}\n <span className=\"font-medium text-foreground\">\n {total === 0 ? 0 : currentPage * pageSize + 1}–\n {Math.min((currentPage + 1) * pageSize, total)}\n </span>{' '}\n {tr('channel.of', 'of')}{' '}\n <span className=\"font-medium text-foreground\">{total}</span>{' '}\n {tr('channel.channelsLower', 'channels')}\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n onClick={() => setCurrentPage((p) => Math.max(0, p - 1))}\n disabled={currentPage === 0}\n className=\"px-2.5 py-1 text-xs border border-border rounded hover:bg-muted transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n {tr('channel.previous', 'Prev')}\n </button>\n <span className=\"text-xs text-muted-foreground\">\n {currentPage + 1} / {totalPages}\n </span>\n <button\n onClick={() => setCurrentPage((p) => Math.min(totalPages - 1, p + 1))}\n disabled={currentPage >= totalPages - 1}\n className=\"px-2.5 py-1 text-xs border border-border rounded hover:bg-muted transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n {tr('channel.next', 'Next')}\n </button>\n </div>\n </div>\n </div>\n )}\n\n {/* ── Quick Links ──────────────────────────────────────────────── */}\n {channels.length > 0 && (\n <div className=\"grid gap-3 sm:grid-cols-3\">\n <button\n onClick={() => navigate('/channels/?tab=all')}\n className=\"flex items-center gap-3 p-3 bg-surface border border-border rounded-lg hover:hover:bg-action-primary-bg/10 transition-colors text-left\"\n >\n <Radio size={16} className=\"text-accent-primary shrink-0\" />\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-foreground\">\n {tr('channel.allChannels', 'All Channels')}\n </p>\n <p className=\"text-xs text-muted-foreground truncate\">\n {tr('channel.browseChannels', 'Browse and manage channel cards')}\n </p>\n </div>\n </button>\n <button\n onClick={() => navigate('/channels/?tab=connections')}\n className=\"flex items-center gap-3 p-3 bg-surface border border-border rounded-lg hover:hover:bg-action-primary-bg/10 transition-colors text-left\"\n >\n <Cable size={16} className=\"text-accent-blue shrink-0\" />\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-foreground\">\n {tr('channel.connections', 'Connections')}\n </p>\n <p className=\"text-xs text-muted-foreground truncate\">\n {tr('channel.connectionsSubtitle', 'OAuth, API keys & credentials')}\n </p>\n </div>\n </button>\n <button\n onClick={() => navigate('/channels/?tab=send')}\n className=\"flex items-center gap-3 p-3 bg-surface border border-border rounded-lg hover:hover:bg-action-primary-bg/10 transition-colors text-left\"\n >\n <Activity size={16} className=\"text-accent-violet shrink-0\" />\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-foreground\">\n {tr('channel.sendMessage', 'Send Message')}\n </p>\n <p className=\"text-xs text-muted-foreground truncate\">\n {tr('channel.sendMessageSubtitle', 'Send a test or one-off notification')}\n </p>\n </div>\n </button>\n </div>\n )}\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ChannelDashboardPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/ChannelDashboardPage.tsx"],"sourcesContent":["/**\n * Channel Dashboard Page\n *\n * Main overview page for the Channel microfrontend.\n * Fetches real data from global-channel-svc via the useChannels hook.\n *\n * Sections:\n * 1. Header with Create Channel action\n * 2. KPI summary cards (total, active, healthy, connected)\n * 3. Search + platform/status filters\n * 4. Channel table with inline actions\n */\n\nimport { useState, useMemo, useCallback, useEffect, useRef } from 'react';\nimport { nativeImpact, nativeNotify } from '../../../../utils/nativeBridge';\nimport { subscribeShellEvent } from '../../../utils/shellBridge';\nimport { useChannel as useChannelContext } from '../../../providers/ChannelProvider';\nimport {\n CHANNEL_HEALTH_CHANGED_SUBSCRIPTION,\n useChannelSubscription,\n} from '../../../hooks/useChannelSubscription';\nimport { useNavigate } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n Radio,\n Plus,\n Search,\n Loader2,\n AlertCircle,\n RefreshCw,\n Power,\n PowerOff,\n Eye,\n Trash2,\n CheckCircle2,\n XCircle,\n AlertTriangle,\n HelpCircle,\n Cable,\n MessageSquare,\n ShieldCheck,\n Activity,\n LayoutDashboard,\n} from 'lucide-react';\nimport {\n useChannels,\n type Channel,\n type ChannelPlatform,\n type ChannelStatus,\n type ConnectionHealth,\n} from '../../../hooks/useChannels';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\nimport { HEALTH_I18N, PLATFORM_I18N, STATUS_I18N, translateLabel } from '../i18n';\n\n// ============================================================================\n// DISPLAY CONFIG\n// ============================================================================\n\nconst PLATFORM_CONFIG: Record<ChannelPlatform, { label: string; color: string }> = {\n EMAIL: { label: 'Email', color: 'bg-accent-blue-subtle text-accent-blue' },\n SMS: { label: 'SMS', color: 'bg-status-success-bg-subtle text-status-success-text' },\n PUSH: { label: 'Push', color: 'bg-accent-purple-subtle text-accent-purple' },\n INAPP: { label: 'In-App', color: 'bg-accent-indigo-subtle text-accent-indigo' },\n WEBHOOK: { label: 'Webhook', color: 'bg-accent-orange-subtle text-accent-orange' },\n SLACK: { label: 'Slack', color: 'bg-accent-pink-subtle text-accent-pink' },\n TEAMS: { label: 'Teams', color: 'bg-accent-cyan-subtle text-accent-cyan' },\n DISCORD: { label: 'Discord', color: 'bg-accent-violet-subtle text-accent-violet' },\n WHATSAPP: { label: 'WhatsApp', color: 'bg-accent-emerald-subtle text-accent-emerald' },\n WEBCHAT: { label: 'Webchat', color: 'bg-accent-teal-subtle text-accent-teal' },\n};\n\nconst STATUS_CONFIG: Record<\n ChannelStatus,\n { label: string; dotColor: string; textColor: string; icon: typeof CheckCircle2 }\n> = {\n ACTIVE: {\n label: 'Active',\n dotColor: 'bg-status-success-bg',\n textColor: 'text-status-success-text',\n icon: CheckCircle2,\n },\n INACTIVE: {\n label: 'Inactive',\n dotColor: 'bg-text-tertiary',\n textColor: 'text-muted-foreground',\n icon: PowerOff,\n },\n ERROR: {\n label: 'Error',\n dotColor: 'bg-status-error-bg-subtle',\n textColor: 'text-status-error-text',\n icon: XCircle,\n },\n SUSPENDED: {\n label: 'Suspended',\n dotColor: 'bg-status-warning-bg',\n textColor: 'text-status-warning-text',\n icon: AlertTriangle,\n },\n ARCHIVED: {\n label: 'Archived',\n dotColor: 'bg-text-tertiary',\n textColor: 'text-muted-foreground',\n icon: PowerOff,\n },\n};\n\nconst HEALTH_CONFIG: Record<\n ConnectionHealth,\n { label: string; color: string; icon: typeof CheckCircle2 }\n> = {\n HEALTHY: { label: 'Healthy', color: 'text-status-success-text', icon: CheckCircle2 },\n DEGRADED: { label: 'Degraded', color: 'text-status-warning-text', icon: AlertTriangle },\n UNHEALTHY: { label: 'Unhealthy', color: 'text-status-error-text', icon: XCircle },\n UNKNOWN: { label: 'Unknown', color: 'text-muted-foreground', icon: HelpCircle },\n};\n\n// ============================================================================\n// HELPERS\n// ============================================================================\n\nfunction formatRelativeTime(dateStr: string): string {\n const diffMs = Date.now() - new Date(dateStr).getTime();\n if (Number.isNaN(diffMs) || diffMs < 0) return 'just now';\n const sec = Math.floor(diffMs / 1000);\n if (sec < 60) return 'just now';\n const min = Math.floor(sec / 60);\n if (min < 60) return `${min}m ago`;\n const hr = Math.floor(min / 60);\n if (hr < 24) return `${hr}h ago`;\n const d = Math.floor(hr / 24);\n if (d < 7) return `${d}d ago`;\n if (d < 30) return `${Math.floor(d / 7)}w ago`;\n return `${Math.floor(d / 30)}mo ago`;\n}\n\n// ============================================================================\n// SUB-COMPONENTS\n// ============================================================================\n\nfunction KpiCard({\n icon: Icon,\n iconClass,\n value,\n label,\n sub,\n}: {\n icon: typeof Radio;\n iconClass: string;\n value: string | number;\n label: string;\n sub?: string;\n}) {\n return (\n <div className=\"p-4 bg-surface border border-border rounded-lg\">\n <div className=\"flex items-center gap-3\">\n <div className=\"p-2 rounded-lg hover:bg-action-primary-bg/10\">\n <Icon size={18} className={iconClass} />\n </div>\n <div>\n <p className=\"text-2xl font-bold text-foreground leading-tight\">{value}</p>\n <p className=\"text-xs text-muted-foreground\">{label}</p>\n </div>\n </div>\n {sub && <p className=\"text-[11px] text-muted-foreground mt-2 pl-11\">{sub}</p>}\n </div>\n );\n}\n\n// ============================================================================\n// MAIN COMPONENT\n// ============================================================================\n\nexport function ChannelDashboardPage() {\n const navigate = useNavigate();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const [deleteConfirm, setDeleteConfirm] = useState<string | null>(null);\n const [actionLoading, setActionLoading] = useState<string | null>(null);\n const pageSize = 10;\n const [currentPage, setCurrentPage] = useState(0);\n const getPlatformConfig = (platform: ChannelPlatform) => {\n const config = PLATFORM_CONFIG[platform] ?? {\n label: platform,\n color: 'bg-bg-sunken text-text-tertiary',\n };\n return {\n ...config,\n label: translateLabel(platform, PLATFORM_I18N, tr),\n };\n };\n const getStatusConfig = (status: ChannelStatus) => {\n const config = STATUS_CONFIG[status] ?? STATUS_CONFIG.INACTIVE;\n return {\n ...config,\n label: translateLabel(status, STATUS_I18N, tr),\n };\n };\n const getHealthConfig = (health: ConnectionHealth) => {\n const config = HEALTH_CONFIG[health] ?? HEALTH_CONFIG.UNKNOWN;\n return {\n ...config,\n label: translateLabel(health, HEALTH_I18N, tr),\n };\n };\n\n const {\n channels,\n isLoading,\n error,\n total,\n filter,\n updateFilter,\n deleteChannel,\n activateChannel,\n deactivateChannel,\n runHealthCheck,\n refetch,\n } = useChannels();\n\n const totalPages = Math.max(1, Math.ceil(total / pageSize));\n\n const refetchRef = useRef(refetch);\n useEffect(() => {\n refetchRef.current = refetch;\n });\n\n useEffect(() => {\n void refetchRef.current({ offset: currentPage * pageSize, limit: pageSize });\n }, [currentPage, pageSize]);\n\n useEffect(() => {\n void Promise.resolve().then(() => setCurrentPage(0));\n }, [filter.search, filter.platform, filter.status]);\n\n const { organizationId } = useChannelContext();\n\n // Real-time channel health stream. Subscribes when the gateway exposes\n // `channelHealthChanged`; otherwise falls back to polling every 60s.\n // Conservative fallback interval keeps gateway/svc cost low when subs\n // are unavailable; live updates flow through the subscription path.\n useChannelSubscription({\n query: CHANNEL_HEALTH_CHANGED_SUBSCRIPTION,\n variables: { organizationId },\n operationName: 'ChannelHealthChanged',\n enabled: Boolean(organizationId),\n onData: () => void refetch(),\n pollFn: refetch,\n pollIntervalMs: 60_000,\n });\n\n // ── Cross-MFE refresh: subscribe to channel/connection bus events so the\n // dashboard auto-updates when another tab or another MFE mutates state. ─\n useEffect(() => {\n const events = [\n 'channel.created',\n 'channel.updated',\n 'channel.deleted',\n 'connection.created',\n 'connection.updated',\n 'connection.deleted',\n 'delivery.completed',\n 'webhook.received',\n ];\n const unsubs = events.map((name) => subscribeShellEvent(name, () => void refetch()));\n return () => unsubs.forEach((u) => u());\n }, [refetch]);\n\n // ── Derived KPIs ──────────────────────────────────────────────────────\n const kpis = useMemo(() => {\n let active = 0;\n let healthy = 0;\n let connected = 0;\n let messages24h = 0;\n let errored = 0;\n let unhealthy = 0;\n\n for (const ch of channels) {\n if (ch.status === 'ACTIVE') active++;\n if (ch.status === 'ERROR') errored++;\n if (ch.connectionHealth === 'HEALTHY') healthy++;\n if (ch.connectionHealth === 'UNHEALTHY') unhealthy++;\n if (ch.isConnected) connected++;\n messages24h += ch.messageCount24h ?? 0;\n }\n\n return { active, healthy, connected, messages24h, errored, unhealthy };\n }, [channels]);\n\n // ── Action handlers ───────────────────────────────────────────────────\n const handleDelete = useCallback(\n async (id: string) => {\n void nativeImpact('medium');\n setActionLoading(id);\n try {\n await deleteChannel(id);\n void nativeNotify('success');\n setDeleteConfirm(null);\n } catch (err) {\n void nativeNotify('error');\n throw err;\n } finally {\n setActionLoading(null);\n }\n },\n [deleteChannel]\n );\n\n const handleToggle = useCallback(\n async (ch: Channel) => {\n setActionLoading(ch.id);\n try {\n if (ch.status === 'ACTIVE') await deactivateChannel(ch.id);\n else await activateChannel(ch.id);\n } finally {\n setActionLoading(null);\n }\n },\n [activateChannel, deactivateChannel]\n );\n\n const handleHealthCheck = useCallback(\n async (id: string) => {\n setActionLoading(id);\n try {\n await runHealthCheck(id);\n } finally {\n setActionLoading(null);\n }\n },\n [runHealthCheck]\n );\n\n // ── Render ────────────────────────────────────────────────────────────\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_READ}>\n <div className=\"space-y-5 p-3 sm:p-6\">\n {/* ── Header ───────────────────────────────────────────────────── */}\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-center gap-3\">\n <div className=\"p-2 rounded-lg bg-accent-primary/10\">\n <LayoutDashboard size={20} className=\"text-accent-primary\" />\n </div>\n <div>\n <h1 className=\"text-xl font-bold text-foreground\">\n {tr('channel.channelDashboard', 'Channel Dashboard')}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {tr('channel.dashboardSubtitle', 'Manage and monitor your communication channels')}\n </p>\n </div>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"inline-flex items-center gap-1.5 px-3 py-2 text-sm text-muted-foreground hover:text-foreground border border-border rounded-lg hover:hover:bg-action-primary-bg/10 transition-colors\"\n >\n <RefreshCw size={14} />\n {tr('channel.refresh', 'Refresh')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigate('/channels/create')}\n className=\"inline-flex items-center gap-1.5 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bg-hover rounded-lg transition-colors\"\n >\n <Plus size={14} />\n {tr('channel.createChannel', 'Create Channel')}\n </button>\n </div>\n </div>\n\n {/* ── KPI Cards ────────────────────────────────────────────────── */}\n <div className=\"grid gap-3 grid-cols-2 md:grid-cols-3 lg:grid-cols-4\">\n <KpiCard\n icon={Radio}\n iconClass=\"text-accent-primary\"\n value={total}\n label={tr('channel.totalChannels', 'Total Channels')}\n sub={\n kpis.active > 0\n ? `${kpis.active} ${tr('channel.status.active', 'Active').toLowerCase()}, ${total - kpis.active} ${tr('channel.status.inactive', 'Inactive').toLowerCase()}`\n : undefined\n }\n />\n <KpiCard\n icon={ShieldCheck}\n iconClass=\"text-status-success-text\"\n value={kpis.healthy}\n label={tr('channel.healthy', 'Healthy')}\n sub={\n kpis.unhealthy > 0\n ? `${kpis.unhealthy} ${tr('channel.health.unhealthy', 'Unhealthy').toLowerCase()}`\n : total > 0\n ? tr('channel.allSystemsNominal', 'All systems nominal')\n : undefined\n }\n />\n <KpiCard\n icon={Cable}\n iconClass=\"text-accent-blue\"\n value={kpis.connected}\n label={tr('channel.connected', 'Connected')}\n sub={\n total > 0\n ? `${total - kpis.connected} ${tr('channel.disconnected', 'Disconnected').toLowerCase()}`\n : undefined\n }\n />\n <KpiCard\n icon={MessageSquare}\n iconClass=\"text-accent-violet\"\n value={kpis.messages24h.toLocaleString()}\n label={tr('channel.messages24h', 'Messages (24h)')}\n />\n </div>\n\n {/* ── Alerts ───────────────────────────────────────────────────── */}\n {kpis.errored > 0 && (\n <div className=\"flex items-center gap-3 p-3 border border-status-error-border/30 bg-status-error-bg-subtle rounded-lg\">\n <AlertCircle size={16} className=\"text-status-error-text shrink-0\" />\n <p className=\"text-sm text-status-error-text\">\n <span className=\"font-semibold\">{kpis.errored}</span>{' '}\n {tr(\n kpis.errored === 1\n ? 'channel.errorStateSummarySingle'\n : 'channel.errorStateSummaryMultiple',\n kpis.errored === 1 ? 'channel in error state' : 'channels in error state'\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => updateFilter({ status: 'ERROR' as ChannelStatus })}\n className=\"ml-auto text-xs font-medium text-status-error-text hover:text-status-error-text/80 transition-colors\"\n >\n {tr('channel.showErrors', 'Show errors')}\n </button>\n </div>\n )}\n\n {/* ── Search & Filters ─────────────────────────────────────────── */}\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center\">\n <div className=\"relative flex-1\">\n <Search\n size={14}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground\"\n />\n <input\n type=\"text\"\n aria-label={tr('channel.searchChannels', 'Search channels...')}\n placeholder={tr('channel.searchChannels', 'Search channels...')}\n value={filter.search || ''}\n onChange={(e) => updateFilter({ search: e.target.value })}\n className=\"w-full pl-9 pr-4 py-2 text-sm bg-surface border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500/50 placeholder:text-muted-foreground\"\n />\n </div>\n\n <select\n value={filter.platform || ''}\n onChange={(e) =>\n updateFilter({ platform: (e.target.value as ChannelPlatform) || undefined })\n }\n className=\"px-3 py-2 text-sm bg-surface border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500/50 text-foreground\"\n >\n <option value=\"\">{tr('channel.allPlatforms', 'All Platforms')}</option>\n {Object.entries(PLATFORM_CONFIG).map(([key, cfg]) => (\n <option key={key} value={key}>\n {cfg.label}\n </option>\n ))}\n </select>\n\n <select\n value={filter.status || ''}\n onChange={(e) =>\n updateFilter({ status: (e.target.value as ChannelStatus) || undefined })\n }\n className=\"px-3 py-2 text-sm bg-surface border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500/50 text-foreground\"\n >\n <option value=\"\">{tr('channel.allStatus', 'All Status')}</option>\n {Object.entries(STATUS_CONFIG).map(([key, cfg]) => (\n <option key={key} value={key}>\n {cfg.label}\n </option>\n ))}\n </select>\n </div>\n\n {/* ── Error State ──────────────────────────────────────────────── */}\n {error && (\n <div className=\"flex items-center gap-3 p-4 bg-status-error-bg-subtle border border-status-error-border/30 rounded-lg\">\n <AlertCircle size={18} className=\"text-status-error-text shrink-0\" />\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium text-status-error-text\">\n {tr('channel.loadFailed', 'Failed to load channels')}\n </p>\n <p className=\"text-xs text-status-error-text/70 truncate\">{error}</p>\n </div>\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"shrink-0 px-3 py-1 text-sm text-status-error-text hover:bg-status-error-bg-subtle rounded transition-colors\"\n >\n {tr('channel.retry', 'Retry')}\n </button>\n </div>\n )}\n\n {/* ── Loading ──────────────────────────────────────────────────── */}\n {isLoading && channels.length === 0 && (\n <div className=\"flex items-center justify-center py-16\">\n <Loader2 size={28} className=\"animate-spin text-accent-primary\" />\n </div>\n )}\n\n {/* ── Empty State ──────────────────────────────────────────────── */}\n {!isLoading && !error && channels.length === 0 && (\n <div className=\"flex flex-col items-center justify-center py-16 text-center\">\n <div className=\"p-3 rounded-full bg-accent-primary/10 mb-4\">\n <Radio size={36} className=\"text-accent-primary\" />\n </div>\n <h3 className=\"text-lg font-semibold text-foreground mb-1\">\n {filter.search || filter.platform || filter.status\n ? tr('channel.noChannelsMatchFilter', 'No channels match your filters')\n : tr('channel.noChannels', 'No channels yet')}\n </h3>\n <p className=\"text-sm text-muted-foreground max-w-sm mb-5\">\n {filter.search || filter.platform || filter.status\n ? tr('channel.adjustFilters', 'Try adjusting your search or filters.')\n : tr(\n 'channel.createFirstChannel',\n 'Create your first communication channel to start sending messages across Email, SMS, Push, Slack and more.'\n )}\n </p>\n {!(filter.search || filter.platform || filter.status) && (\n <button\n type=\"button\"\n onClick={() => navigate('/channels/create')}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bg-hover rounded-lg transition-colors\"\n >\n <Plus size={14} />\n {tr('channel.createChannel', 'Create Channel')}\n </button>\n )}\n </div>\n )}\n\n {/* ── Channel Table ────────────────────────────────────────────── */}\n {channels.length > 0 && (\n <div className=\"border border-border rounded-lg overflow-hidden\">\n <div className=\"overflow-x-auto min-w-full\">\n <table className=\"w-full text-sm min-w-[800px]\">\n <thead>\n <tr className=\"hover:bg-action-primary-bg/10/50 border-b border-border\">\n <th className=\"text-left px-4 py-3 text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.channel', 'Channel')}\n </th>\n <th className=\"text-left px-4 py-3 text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.platform', 'Platform')}\n </th>\n <th className=\"text-left px-4 py-3 text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.status', 'Status')}\n </th>\n <th className=\"text-left px-4 py-3 text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.health', 'Health')}\n </th>\n <th className=\"text-left px-4 py-3 text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.connection', 'Connection')}\n </th>\n <th className=\"text-right px-4 py-3 text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.msgs24h', 'Msgs 24h')}\n </th>\n <th className=\"text-right px-4 py-3 text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.updated', 'Updated')}\n </th>\n <th className=\"text-right px-4 py-3 text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.actions', 'Actions')}\n </th>\n </tr>\n </thead>\n\n <tbody className=\"divide-y divide-border\">\n {channels.map((ch) => {\n const isAL = actionLoading === ch.id;\n\n return (\n <tr\n key={ch.id}\n className=\"bg-surface hover:hover:bg-action-primary-bg/10/60 transition-colors group\"\n >\n {/* Name */}\n <td className=\"px-4 py-3\">\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${ch.id}`)}\n className=\"text-left group/name\"\n >\n <p className=\"font-medium text-foreground group-hover/name:text-accent-primary transition-colors truncate max-w-[200px]\">\n {ch.name}\n </p>\n {ch.description && (\n <p className=\"text-xs text-muted-foreground truncate max-w-[200px]\">\n {ch.description}\n </p>\n )}\n </button>\n </td>\n\n {/* Platform */}\n <td className=\"px-4 py-3\">\n <span\n className={`inline-flex items-center px-2 py-0.5 text-[11px] font-semibold rounded-full ${getPlatformConfig(ch.platform).color}`}\n >\n {getPlatformConfig(ch.platform).label}\n </span>\n {ch.adapterType && (\n <p className=\"text-[10px] text-muted-foreground mt-0.5 font-mono\">\n {ch.adapterType.replace(/_/g, ' ')}\n </p>\n )}\n </td>\n\n {/* Status */}\n <td className=\"px-4 py-3\">\n <span className=\"inline-flex items-center gap-1.5\">\n <span\n className={`w-1.5 h-1.5 rounded-full ${getStatusConfig(ch.status).dotColor}`}\n />\n <span className={`text-xs ${getStatusConfig(ch.status).textColor}`}>\n {getStatusConfig(ch.status).label}\n </span>\n </span>\n </td>\n\n {/* Health */}\n <td className=\"px-4 py-3\">\n <span\n className={`inline-flex items-center gap-1 text-xs ${getHealthConfig(ch.connectionHealth).color}`}\n >\n {(() => {\n const Icon = getHealthConfig(ch.connectionHealth).icon;\n return <Icon size={12} />;\n })()}\n {getHealthConfig(ch.connectionHealth).label}\n </span>\n </td>\n\n {/* Connection */}\n <td className=\"px-4 py-3\">\n <span\n className={`text-xs ${ch.isConnected ? 'text-status-success-text' : 'text-muted-foreground'}`}\n >\n {ch.isConnected\n ? tr('channel.connected', 'Connected')\n : tr('channel.disconnected', 'Disconnected')}\n </span>\n </td>\n\n {/* Messages 24h */}\n <td className=\"px-4 py-3 text-right\">\n <span className=\"text-xs text-foreground tabular-nums\">\n {(ch.messageCount24h ?? 0).toLocaleString()}\n </span>\n </td>\n\n {/* Updated */}\n <td className=\"px-4 py-3 text-right\">\n <span className=\"text-xs text-muted-foreground whitespace-nowrap\">\n {formatRelativeTime(ch.updatedAt)}\n </span>\n </td>\n\n {/* Actions */}\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center justify-end gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity\">\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${ch.id}`)}\n className=\"p-1.5 text-muted-foreground hover:text-accent-primary hover:bg-accent-primary/10 rounded transition-colors\"\n title={tr('channel.view', 'View')}\n >\n <Eye size={14} />\n </button>\n\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_EXECUTE}>\n <button\n type=\"button\"\n onClick={() => handleHealthCheck(ch.id)}\n disabled={isAL}\n className=\"p-1.5 text-muted-foreground hover:text-accent-blue hover:bg-accent-blue-subtle rounded transition-colors disabled:opacity-40\"\n title={tr('channel.healthCheck', 'Health Check')}\n >\n {isAL ? (\n <Loader2 size={14} className=\"animate-spin\" />\n ) : (\n <RefreshCw size={14} />\n )}\n </button>\n </PermissionGuard>\n\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_UPDATE}>\n <button\n type=\"button\"\n onClick={() => handleToggle(ch)}\n disabled={isAL}\n className={`p-1.5 rounded transition-colors disabled:opacity-40 ${\n ch.status === 'ACTIVE'\n ? 'text-muted-foreground hover:text-status-warning-text hover:bg-status-warning-bg-subtle'\n : 'text-muted-foreground hover:text-status-success-text hover:bg-status-success-bg-subtle'\n }`}\n title={\n ch.status === 'ACTIVE'\n ? tr('channel.deactivate', 'Deactivate')\n : tr('channel.activate', 'Activate')\n }\n >\n {ch.status === 'ACTIVE' ? (\n <PowerOff size={14} />\n ) : (\n <Power size={14} />\n )}\n </button>\n </PermissionGuard>\n\n {deleteConfirm === ch.id ? (\n <span className=\"inline-flex items-center gap-1 ml-1\">\n <button\n type=\"button\"\n onClick={() => handleDelete(ch.id)}\n disabled={isAL}\n className=\"px-1.5 py-0.5 text-[11px] font-medium text-status-error-text hover:bg-status-error-bg-subtle rounded transition-colors\"\n >\n {isAL ? '...' : tr('channel.yes', 'Yes')}\n </button>\n <button\n type=\"button\"\n onClick={() => setDeleteConfirm(null)}\n className=\"px-1.5 py-0.5 text-[11px] text-muted-foreground hover:hover:bg-action-primary-bg/10 rounded transition-colors\"\n >\n {tr('channel.no', 'No')}\n </button>\n </span>\n ) : (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_DELETE}>\n <button\n type=\"button\"\n onClick={() => setDeleteConfirm(ch.id)}\n className=\"p-1.5 text-muted-foreground hover:text-status-error-text hover:bg-status-error-bg-subtle rounded transition-colors\"\n title={tr('channel.delete', 'Delete')}\n >\n <Trash2 size={14} />\n </button>\n </PermissionGuard>\n )}\n </div>\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n\n {/* Table footer — pagination */}\n <div className=\"px-4 py-2.5 border-t border-border flex items-center justify-between\">\n <p className=\"text-xs text-muted-foreground\">\n {tr('channel.showing', 'Showing')}{' '}\n <span className=\"font-medium text-foreground\">\n {total === 0 ? 0 : currentPage * pageSize + 1}–\n {Math.min((currentPage + 1) * pageSize, total)}\n </span>{' '}\n {tr('channel.of', 'of')}{' '}\n <span className=\"font-medium text-foreground\">{total}</span>{' '}\n {tr('channel.channelsLower', 'channels')}\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setCurrentPage((p) => Math.max(0, p - 1))}\n disabled={currentPage === 0}\n className=\"px-2.5 py-1 text-xs border border-border rounded hover:bg-muted transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n {tr('channel.previous', 'Prev')}\n </button>\n <span className=\"text-xs text-muted-foreground\">\n {currentPage + 1} / {totalPages}\n </span>\n <button\n type=\"button\"\n onClick={() => setCurrentPage((p) => Math.min(totalPages - 1, p + 1))}\n disabled={currentPage >= totalPages - 1}\n className=\"px-2.5 py-1 text-xs border border-border rounded hover:bg-muted transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n {tr('channel.next', 'Next')}\n </button>\n </div>\n </div>\n </div>\n )}\n\n {/* ── Quick Links ──────────────────────────────────────────────── */}\n {channels.length > 0 && (\n <div className=\"grid gap-3 sm:grid-cols-3\">\n <button\n type=\"button\"\n onClick={() => navigate('/channels/?tab=all')}\n className=\"flex items-center gap-3 p-3 bg-surface border border-border rounded-lg hover:hover:bg-action-primary-bg/10 transition-colors text-left\"\n >\n <Radio size={16} className=\"text-accent-primary shrink-0\" />\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-foreground\">\n {tr('channel.allChannels', 'All Channels')}\n </p>\n <p className=\"text-xs text-muted-foreground truncate\">\n {tr('channel.browseChannels', 'Browse and manage channel cards')}\n </p>\n </div>\n </button>\n <button\n type=\"button\"\n onClick={() => navigate('/channels/?tab=connections')}\n className=\"flex items-center gap-3 p-3 bg-surface border border-border rounded-lg hover:hover:bg-action-primary-bg/10 transition-colors text-left\"\n >\n <Cable size={16} className=\"text-accent-blue shrink-0\" />\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-foreground\">\n {tr('channel.connections', 'Connections')}\n </p>\n <p className=\"text-xs text-muted-foreground truncate\">\n {tr('channel.connectionsSubtitle', 'OAuth, API keys & credentials')}\n </p>\n </div>\n </button>\n <button\n type=\"button\"\n onClick={() => navigate('/channels/?tab=send')}\n className=\"flex items-center gap-3 p-3 bg-surface border border-border rounded-lg hover:hover:bg-action-primary-bg/10 transition-colors text-left\"\n >\n <Activity size={16} className=\"text-accent-violet shrink-0\" />\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-foreground\">\n {tr('channel.sendMessage', 'Send Message')}\n </p>\n <p className=\"text-xs text-muted-foreground truncate\">\n {tr('channel.sendMessageSubtitle', 'Send a test or one-off notification')}\n </p>\n </div>\n </button>\n </div>\n )}\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|