From a74785f64d5b06e1d2af7444466da86fd1f9e9ae Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Tue, 21 Apr 2026 10:07:38 +0200 Subject: [PATCH] refactor(alerts/ui): rewrite All alerts as DataTable + SegmentedTabs filter Replaces 4-Button filter row with DS SegmentedTabs and custom row rendering with DataTable. Shares expandedContent renderer and severity-driven rowAccent with Inbox. Co-Authored-By: Claude Opus 4.7 (1M context) --- ui/src/pages/Alerts/AllAlertsPage.tsx | 109 +++++++++++++++++++------- 1 file changed, 82 insertions(+), 27 deletions(-) diff --git a/ui/src/pages/Alerts/AllAlertsPage.tsx b/ui/src/pages/Alerts/AllAlertsPage.tsx index db462ba4..4212e7b7 100644 --- a/ui/src/pages/Alerts/AllAlertsPage.tsx +++ b/ui/src/pages/Alerts/AllAlertsPage.tsx @@ -1,50 +1,105 @@ import { useState } from 'react'; -import { SectionHeader, Button } from '@cameleer/design-system'; +import { Link } from 'react-router'; +import { Bell } from 'lucide-react'; +import { + SectionHeader, DataTable, EmptyState, SegmentedTabs, +} from '@cameleer/design-system'; +import type { Column } from '@cameleer/design-system'; import { PageLoader } from '../../components/PageLoader'; -import { useAlerts, type AlertDto } from '../../api/queries/alerts'; -import { AlertRow } from './AlertRow'; +import { SeverityBadge } from '../../components/SeverityBadge'; +import { AlertStateChip } from '../../components/AlertStateChip'; +import { + useAlerts, useMarkAlertRead, + type AlertDto, +} from '../../api/queries/alerts'; +import { severityToAccent } from './severity-utils'; +import { formatRelativeTime } from './time-utils'; +import { renderAlertExpanded } from './alert-expanded'; import css from './alerts-page.module.css'; +import tableStyles from '../../styles/table-section.module.css'; type AlertState = NonNullable; -const STATE_FILTERS: Array<{ label: string; values: AlertState[] }> = [ - { label: 'Open', values: ['PENDING', 'FIRING', 'ACKNOWLEDGED'] }, - { label: 'Firing', values: ['FIRING'] }, - { label: 'Acked', values: ['ACKNOWLEDGED'] }, - { label: 'All', values: ['PENDING', 'FIRING', 'ACKNOWLEDGED', 'RESOLVED'] }, -]; +const STATE_FILTERS: Record = { + open: { label: 'Open', values: ['PENDING', 'FIRING', 'ACKNOWLEDGED'] }, + firing: { label: 'Firing', values: ['FIRING'] }, + acked: { label: 'Acked', values: ['ACKNOWLEDGED'] }, + all: { label: 'All', values: ['PENDING', 'FIRING', 'ACKNOWLEDGED', 'RESOLVED'] }, +}; export default function AllAlertsPage() { - const [filterIdx, setFilterIdx] = useState(0); - const filter = STATE_FILTERS[filterIdx]; + const [filterKey, setFilterKey] = useState('open'); + const filter = STATE_FILTERS[filterKey]; const { data, isLoading, error } = useAlerts({ state: filter.values, limit: 200 }); + const markRead = useMarkAlertRead(); + + const rows = data ?? []; + + const columns: Column[] = [ + { + key: 'severity', header: 'Severity', width: '110px', + render: (_, row) => row.severity ? : null, + }, + { + key: 'state', header: 'State', width: '140px', + render: (_, row) => row.state ? : null, + }, + { + key: 'title', header: 'Title', + render: (_, row) => ( +
+ row.id && markRead.mutate(row.id)}> + {row.title ?? '(untitled)'} + + {row.message && {row.message}} +
+ ), + }, + { + key: 'firedAt', header: 'Fired at', width: '140px', sortable: true, + render: (_, row) => + row.firedAt ? ( + + {formatRelativeTime(row.firedAt)} + + ) : '—', + }, + ]; if (isLoading) return ; if (error) return
Failed to load alerts: {String(error)}
; - const rows = data ?? []; - return (
All alerts -
- {STATE_FILTERS.map((f, i) => ( - - ))} -
+ +
+ ({ value, label: f.label }))} + active={filterKey} + onChange={setFilterKey} + /> +
+ {rows.length === 0 ? ( -
No alerts match this filter.
+ } + title="No alerts match this filter" + description="Try switching to a different state or widening your criteria." + /> ) : ( - rows.map((a) => ) +
+ + columns={columns as Column[]} + data={rows as Array} + sortable + flush + rowAccent={(row) => row.severity ? severityToAccent(row.severity) : undefined} + expandedContent={renderAlertExpanded} + /> +
)}
);