Adopt shared WebUI structural primitives
This commit is contained in:
@@ -2,15 +2,20 @@ import { useEffect, useMemo, useState } from "react";
|
|||||||
import { Bell, Check, ExternalLink, RefreshCw, Send, XCircle } from "lucide-react";
|
import { Bell, Check, ExternalLink, RefreshCw, Send, XCircle } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
ActionBlockerHint,
|
ActionBlockerHint,
|
||||||
|
ActionToolbar,
|
||||||
AdminIconButton,
|
AdminIconButton,
|
||||||
Button,
|
Button,
|
||||||
ConfirmDialog,
|
ConfirmDialog,
|
||||||
|
CountBadge,
|
||||||
DismissibleAlert,
|
DismissibleAlert,
|
||||||
DocumentationHelpLink,
|
DocumentationHelpLink,
|
||||||
SegmentedControl,
|
SegmentedControl,
|
||||||
SelectionList,
|
SelectionList,
|
||||||
SelectionListItem,
|
SelectionListItem,
|
||||||
|
StatePanel,
|
||||||
StatusBadge,
|
StatusBadge,
|
||||||
|
WorkspaceFrame,
|
||||||
|
WorkspaceLayout,
|
||||||
hasScope,
|
hasScope,
|
||||||
i18nMessage,
|
i18nMessage,
|
||||||
type ApiSettings,
|
type ApiSettings,
|
||||||
@@ -152,7 +157,7 @@ export default function NotificationCenterPage({ settings, auth }: { settings: A
|
|||||||
|
|
||||||
if (!canRead) {
|
if (!canRead) {
|
||||||
return (
|
return (
|
||||||
<main className="notifications-page">
|
<WorkspaceFrame as="main" height="viewport" surface="plain" className="notifications-page" label="Notification center">
|
||||||
<div className="notifications-permission-state">
|
<div className="notifications-permission-state">
|
||||||
<ActionBlockerHint
|
<ActionBlockerHint
|
||||||
tone="warning"
|
tone="warning"
|
||||||
@@ -167,19 +172,31 @@ export default function NotificationCenterPage({ settings, auth }: { settings: A
|
|||||||
documentation={NOTIFICATIONS_DOCUMENTATION}
|
documentation={NOTIFICATIONS_DOCUMENTATION}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</WorkspaceFrame>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="notifications-page">
|
<WorkspaceFrame as="main" height="viewport" surface="plain" className="notifications-page" label="Notification center">
|
||||||
<div className="notifications-shell">
|
<WorkspaceLayout
|
||||||
<aside className="notifications-sidebar">
|
variant="split"
|
||||||
<div className="notifications-sidebar-bar">
|
primarySize="default"
|
||||||
|
primaryScrollable={false}
|
||||||
|
contentScrollable={false}
|
||||||
|
surface="contained"
|
||||||
|
primaryClassName="notifications-sidebar"
|
||||||
|
contentClassName="notifications-workspace"
|
||||||
|
primaryLabel="i18n:govoplan-notifications.notifications"
|
||||||
|
contentLabel="i18n:govoplan-notifications.surface.center"
|
||||||
|
interfaceId="notifications.center.workspace"
|
||||||
|
helpContextId="notifications.page.center"
|
||||||
|
helpModuleId="notifications"
|
||||||
|
primary={<>
|
||||||
|
<ActionToolbar surface="panel-header" className="notifications-sidebar-bar">
|
||||||
<div className="notifications-title">
|
<div className="notifications-title">
|
||||||
<Bell size={17} />
|
<Bell size={17} />
|
||||||
<strong>i18n:govoplan-notifications.notifications</strong>
|
<strong>i18n:govoplan-notifications.notifications</strong>
|
||||||
{unreadCount > 0 ? <span className="notifications-count">{unreadCount}</span> : null}
|
{unreadCount > 0 ? <CountBadge>{unreadCount}</CountBadge> : null}
|
||||||
</div>
|
</div>
|
||||||
<AdminIconButton
|
<AdminIconButton
|
||||||
label="i18n:govoplan-notifications.refresh"
|
label="i18n:govoplan-notifications.refresh"
|
||||||
@@ -188,7 +205,7 @@ export default function NotificationCenterPage({ settings, auth }: { settings: A
|
|||||||
disabled={loading || busy}
|
disabled={loading || busy}
|
||||||
disabledReason={loading ? NOTIFICATIONS_I18N.loading : busy ? NOTIFICATIONS_I18N.actionActive : undefined}
|
disabledReason={loading ? NOTIFICATIONS_I18N.loading : busy ? NOTIFICATIONS_I18N.actionActive : undefined}
|
||||||
/>
|
/>
|
||||||
</div>
|
</ActionToolbar>
|
||||||
<SegmentedControl
|
<SegmentedControl
|
||||||
className="notifications-status-filter"
|
className="notifications-status-filter"
|
||||||
options={statusFilters.map((status) => ({ id: status, label: statusLabel(status) }))}
|
options={statusFilters.map((status) => ({ id: status, label: statusLabel(status) }))}
|
||||||
@@ -201,7 +218,7 @@ export default function NotificationCenterPage({ settings, auth }: { settings: A
|
|||||||
{loading ? <div className="notifications-note">i18n:govoplan-notifications.loading_notifications</div> : null}
|
{loading ? <div className="notifications-note">i18n:govoplan-notifications.loading_notifications</div> : null}
|
||||||
{!loading && notifications.length === 0 ? <div className="notifications-note">i18n:govoplan-notifications.no_notifications</div> : null}
|
{!loading && notifications.length === 0 ? <div className="notifications-note">i18n:govoplan-notifications.no_notifications</div> : null}
|
||||||
{notifications.length > 0 ? (
|
{notifications.length > 0 ? (
|
||||||
<SelectionList label="i18n:govoplan-notifications.notifications" className="notifications-selection-list">
|
<SelectionList variant="navigation" label="i18n:govoplan-notifications.notifications" className="notifications-selection-list">
|
||||||
{notifications.map((notification) => (
|
{notifications.map((notification) => (
|
||||||
<SelectionListItem
|
<SelectionListItem
|
||||||
key={notification.id}
|
key={notification.id}
|
||||||
@@ -222,9 +239,9 @@ export default function NotificationCenterPage({ settings, auth }: { settings: A
|
|||||||
</SelectionList>
|
</SelectionList>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</>}
|
||||||
<section className="notifications-workspace">
|
>
|
||||||
<div className="notifications-topbar">
|
<ActionToolbar surface="panel-header" className="notifications-topbar">
|
||||||
<div className="notifications-title-line">
|
<div className="notifications-title-line">
|
||||||
<Bell size={18} />
|
<Bell size={18} />
|
||||||
<strong>{selected?.subject || selected?.event_kind || "i18n:govoplan-notifications.surface.center"}</strong>
|
<strong>{selected?.subject || selected?.event_kind || "i18n:govoplan-notifications.surface.center"}</strong>
|
||||||
@@ -244,7 +261,7 @@ export default function NotificationCenterPage({ settings, auth }: { settings: A
|
|||||||
<Send size={16} /> i18n:govoplan-notifications.dispatch_pending
|
<Send size={16} /> i18n:govoplan-notifications.dispatch_pending
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ActionToolbar>
|
||||||
|
|
||||||
{error ? <DismissibleAlert tone="danger" compact resetKey={error}>{error}</DismissibleAlert> : null}
|
{error ? <DismissibleAlert tone="danger" compact resetKey={error}>{error}</DismissibleAlert> : null}
|
||||||
|
|
||||||
@@ -264,14 +281,9 @@ export default function NotificationCenterPage({ settings, auth }: { settings: A
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{selected ? <NotificationDetails notification={selected} /> : (
|
{selected ? <NotificationDetails notification={selected} /> : (
|
||||||
<div className="notifications-empty-state">
|
<StatePanel size="fill" icon={<Bell size={22} />} title="i18n:govoplan-notifications.notifications" description="i18n:govoplan-notifications.select_notification_help" />
|
||||||
<Bell size={22} />
|
|
||||||
<h1>i18n:govoplan-notifications.notifications</h1>
|
|
||||||
<p>i18n:govoplan-notifications.select_notification_help</p>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</section>
|
</WorkspaceLayout>
|
||||||
</div>
|
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
open={confirmingAction === "cancel"}
|
open={confirmingAction === "cancel"}
|
||||||
title="i18n:govoplan-notifications.cancel_delivery_title"
|
title="i18n:govoplan-notifications.cancel_delivery_title"
|
||||||
@@ -291,7 +303,7 @@ export default function NotificationCenterPage({ settings, auth }: { settings: A
|
|||||||
onCancel={() => setConfirmingAction(null)}
|
onCancel={() => setConfirmingAction(null)}
|
||||||
onConfirm={() => void confirmAction()}
|
onConfirm={() => void confirmAction()}
|
||||||
/>
|
/>
|
||||||
</main>
|
</WorkspaceFrame>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,10 +342,10 @@ function NotificationDetails({ notification }: { notification: NotificationMessa
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="notifications-attempts">
|
<section className="notifications-attempts">
|
||||||
<div className="notifications-section-heading">
|
<ActionToolbar surface="section-header" className="notifications-section-heading">
|
||||||
<h2>i18n:govoplan-notifications.delivery_attempts</h2>
|
<h2>i18n:govoplan-notifications.delivery_attempts</h2>
|
||||||
<DocumentationHelpLink reference={NOTIFICATIONS_DELIVERY_DOCUMENTATION} />
|
<DocumentationHelpLink reference={NOTIFICATIONS_DELIVERY_DOCUMENTATION} />
|
||||||
</div>
|
</ActionToolbar>
|
||||||
{notification.attempts.length === 0 ? <p className="muted">i18n:govoplan-notifications.no_delivery_attempt</p> : null}
|
{notification.attempts.length === 0 ? <p className="muted">i18n:govoplan-notifications.no_delivery_attempt</p> : null}
|
||||||
{notification.attempts.map((attempt) => (
|
{notification.attempts.map((attempt) => (
|
||||||
<div className="notifications-attempt" key={attempt.id}>
|
<div className="notifications-attempt" key={attempt.id}>
|
||||||
|
|||||||
@@ -1,18 +1,3 @@
|
|||||||
.notifications-page {
|
|
||||||
box-sizing: border-box;
|
|
||||||
height: calc(100vh - 115px);
|
|
||||||
min-height: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
color: var(--text);
|
|
||||||
background: var(--bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.notifications-page *,
|
|
||||||
.notifications-page *::before,
|
|
||||||
.notifications-page *::after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notifications-widget-actions {
|
.notifications-widget-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -21,28 +6,14 @@
|
|||||||
margin-top: 14px;
|
margin-top: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notifications-shell {
|
|
||||||
height: 100%;
|
|
||||||
min-height: 0;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: minmax(270px, 340px) minmax(0, 1fr);
|
|
||||||
border: var(--border-line);
|
|
||||||
background: var(--panel);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notifications-sidebar {
|
.notifications-sidebar {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
border-right: var(--border-line);
|
|
||||||
background: var(--panel-soft);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.notifications-sidebar-bar,
|
|
||||||
.notifications-title,
|
.notifications-title,
|
||||||
.notifications-topbar,
|
|
||||||
.notifications-title-line,
|
.notifications-title-line,
|
||||||
.notifications-actions,
|
.notifications-actions,
|
||||||
.notifications-message-meta,
|
.notifications-message-meta,
|
||||||
@@ -52,27 +23,6 @@
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notifications-sidebar-bar,
|
|
||||||
.notifications-topbar {
|
|
||||||
min-height: 54px;
|
|
||||||
justify-content: space-between;
|
|
||||||
border-bottom: var(--border-line);
|
|
||||||
background: var(--panel-header);
|
|
||||||
padding: 9px 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notifications-count {
|
|
||||||
min-width: 21px;
|
|
||||||
height: 21px;
|
|
||||||
display: inline-grid;
|
|
||||||
place-items: center;
|
|
||||||
border-radius: 999px;
|
|
||||||
background: var(--accent);
|
|
||||||
color: var(--on-accent);
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 800;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notifications-status-filter {
|
.notifications-status-filter {
|
||||||
width: calc(100% - 16px);
|
width: calc(100% - 16px);
|
||||||
margin: 8px;
|
margin: 8px;
|
||||||
@@ -244,17 +194,6 @@
|
|||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notifications-section-heading {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notifications-section-heading h2 {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notifications-properties dl {
|
.notifications-properties dl {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, minmax(180px, 1fr));
|
grid-template-columns: repeat(2, minmax(180px, 1fr));
|
||||||
@@ -307,44 +246,13 @@
|
|||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.notifications-empty-state {
|
|
||||||
min-height: 100%;
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
align-content: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 32px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notifications-permission-state {
|
.notifications-permission-state {
|
||||||
max-width: 860px;
|
max-width: 860px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 32px 20px;
|
padding: 32px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notifications-empty-state h1 {
|
|
||||||
margin: 0;
|
|
||||||
color: var(--text-strong);
|
|
||||||
}
|
|
||||||
|
|
||||||
.notifications-empty-state p {
|
|
||||||
max-width: 520px;
|
|
||||||
margin: 0;
|
|
||||||
color: var(--muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.notifications-shell {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notifications-sidebar {
|
|
||||||
min-height: 220px;
|
|
||||||
border-right: 0;
|
|
||||||
border-bottom: var(--border-line);
|
|
||||||
}
|
|
||||||
|
|
||||||
.notifications-topbar {
|
.notifications-topbar {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
Reference in New Issue
Block a user