Adopt shared WebUI layout primitives

This commit is contained in:
2026-08-18 10:42:53 +02:00
parent c2c400a43a
commit 79fa45d14a
3 changed files with 14 additions and 23 deletions
@@ -1,6 +1,6 @@
import { Download, FileJson, Play, ShieldCheck } from "lucide-react"; import { Download, FileJson, Play, ShieldCheck } from "lucide-react";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { import { ContentGrid, ActionToolbar,
Button, Button,
Card, Card,
DismissibleAlert, DismissibleAlert,
@@ -174,7 +174,7 @@ export function ProviderReportWorkspace({ settings, auth, report }: {
</div> </div>
{execution ? {execution ?
<> <>
<div className="reporting-output-toolbar"> <ActionToolbar className="reporting-output-toolbar">
<span>Generated {formatDateTime(execution.generated_at)}</span> <span>Generated {formatDateTime(execution.generated_at)}</span>
{report.export_formats.includes("csv") && {report.export_formats.includes("csv") &&
<IconButton label="Download CSV" icon={<Download size={17} />} variant="ghost" onClick={() => void download("csv")} /> <IconButton label="Download CSV" icon={<Download size={17} />} variant="ghost" onClick={() => void download("csv")} />
@@ -182,7 +182,7 @@ export function ProviderReportWorkspace({ settings, auth, report }: {
{report.export_formats.includes("json") && {report.export_formats.includes("json") &&
<IconButton label="Download JSON" icon={<FileJson size={17} />} variant="ghost" onClick={() => void download("json")} /> <IconButton label="Download JSON" icon={<FileJson size={17} />} variant="ghost" onClick={() => void download("json")} />
} }
</div> </ActionToolbar>
<div className="reporting-provider-output"> <div className="reporting-provider-output">
<ProviderResult execution={execution} /> <ProviderResult execution={execution} />
</div> </div>
@@ -227,11 +227,11 @@ function ProviderResult({ execution }: { execution: ProviderReportExecution }) {
return ( return (
<Card title={group} key={group}> <Card title={group} key={group}>
{metrics.length > 0 && {metrics.length > 0 &&
<div className="dashboard-grid reporting-provider-metrics"> <ContentGrid columns={2} collapseAt="workspace" className="reporting-provider-metrics">
{metrics.map((field) => {metrics.map((field) =>
<MetricCard key={field.path} label={field.label} value={displayValue(pathValue(execution.result, field.path))} /> <MetricCard key={field.path} label={field.label} value={displayValue(pathValue(execution.result, field.path))} />
)} )}
</div> </ContentGrid>
} }
{details.length > 0 && {details.length > 0 &&
<dl className="detail-list"> <dl className="detail-list">
@@ -19,7 +19,7 @@ import {
useState, useState,
type FormEvent type FormEvent
} from "react"; } from "react";
import { import { FormGrid, ActionToolbar,
Button, Button,
DataGrid, DataGrid,
Dialog, Dialog,
@@ -250,7 +250,7 @@ export default function ReportingPage({ settings, auth }: PlatformRouteContext)
return ( return (
<main className="reporting-page"> <main className="reporting-page">
<div className="reporting-shell"> <div className="reporting-shell">
<div className="reporting-toolbar"> <ActionToolbar className="reporting-toolbar">
<form className="reporting-search" onSubmit={submitSearch}> <form className="reporting-search" onSubmit={submitSearch}>
<Search size={17} aria-hidden="true" /> <Search size={17} aria-hidden="true" />
<input <input
@@ -271,7 +271,7 @@ export default function ReportingPage({ settings, auth }: PlatformRouteContext)
variant="ghost" variant="ghost"
onClick={() => void reload()} onClick={() => void reload()}
/> />
</div> </ActionToolbar>
{error && {error &&
<DismissibleAlert tone="danger" resetKey={error}> <DismissibleAlert tone="danger" resetKey={error}>
{error} {error}
@@ -345,7 +345,7 @@ export default function ReportingPage({ settings, auth }: PlatformRouteContext)
onQueryChange={setQuery} onQueryChange={setQuery}
onParametersChange={setParameters} onParametersChange={setParameters}
/> />
<div className="reporting-output-toolbar"> <ActionToolbar className="reporting-output-toolbar">
<SegmentedControl <SegmentedControl
ariaLabel="Report output" ariaLabel="Report output"
value={outputMode} value={outputMode}
@@ -365,7 +365,7 @@ export default function ReportingPage({ settings, auth }: PlatformRouteContext)
} }
</> </>
} }
</div> </ActionToolbar>
<div className="reporting-output"> <div className="reporting-output">
{!execution && <div className="reporting-empty">Run the report or select a previous execution.</div>} {!execution && <div className="reporting-empty">Run the report or select a previous execution.</div>}
{execution && outputMode === "visual" && <ReportVisual execution={execution} onDrill={execution.query.mode === "detail" ? undefined : drill} />} {execution && outputMode === "visual" && <ReportVisual execution={execution} onDrill={execution.query.mode === "detail" ? undefined : drill} />}
@@ -757,7 +757,7 @@ function PublishDialog({ open, targets, onClose, onPublish }: {
</Button> </Button>
</>}> </>}>
{dialogError && <DismissibleAlert tone="danger" resetKey={dialogError}>{dialogError}</DismissibleAlert>} {dialogError && <DismissibleAlert tone="danger" resetKey={dialogError}>{dialogError}</DismissibleAlert>}
<div className="reporting-dialog-grid"> <FormGrid columns={2} gap="small" collapseAt="narrow">
<label className="reporting-dialog-field"> <label className="reporting-dialog-field">
<span>Target</span> <span>Target</span>
<select value={targetCapability} onChange={(event) => { <select value={targetCapability} onChange={(event) => {
@@ -784,7 +784,7 @@ function PublishDialog({ open, targets, onClose, onPublish }: {
<label className="reporting-dialog-field"><span>Sender address</span><input type="email" value={fromAddress} onChange={(event) => setFromAddress(event.target.value)} /></label> <label className="reporting-dialog-field"><span>Sender address</span><input type="email" value={fromAddress} onChange={(event) => setFromAddress(event.target.value)} /></label>
<label className="reporting-dialog-field reporting-dialog-span"><span>Subject</span><input value={subject} onChange={(event) => setSubject(event.target.value)} placeholder="Generated from report name" /></label> <label className="reporting-dialog-field reporting-dialog-span"><span>Subject</span><input value={subject} onChange={(event) => setSubject(event.target.value)} placeholder="Generated from report name" /></label>
</>} </>}
</div> </FormGrid>
{target?.reason && <DismissibleAlert tone="warning" dismissible={false}>{target.reason}</DismissibleAlert>} {target?.reason && <DismissibleAlert tone="warning" dismissible={false}>{target.reason}</DismissibleAlert>}
</Dialog> </Dialog>
); );
@@ -936,10 +936,10 @@ function ScheduleDialog({ open, onClose, onSave }: { open: boolean; onClose: ()
<Button onClick={onClose}>Cancel</Button> <Button onClick={onClose}>Cancel</Button>
<Button variant="primary" disabled={!name.trim() || saving} onClick={() => { setSaving(true); void onSave(name.trim(), Number(interval)).finally(() => setSaving(false)); }}>Schedule</Button> <Button variant="primary" disabled={!name.trim() || saving} onClick={() => { setSaving(true); void onSave(name.trim(), Number(interval)).finally(() => setSaving(false)); }}>Schedule</Button>
</>}> </>}>
<div className="reporting-dialog-grid"> <FormGrid columns={2} gap="small" collapseAt="narrow">
<label className="reporting-dialog-field"><span>Name</span><input value={name} onChange={(event) => setName(event.target.value)} autoFocus /></label> <label className="reporting-dialog-field"><span>Name</span><input value={name} onChange={(event) => setName(event.target.value)} autoFocus /></label>
<label className="reporting-dialog-field"><span>Interval</span><select value={interval} onChange={(event) => setIntervalValue(event.target.value)}><option value="3600">Hourly</option><option value="86400">Daily</option><option value="604800">Weekly</option><option value="2592000">Every 30 days</option></select></label> <label className="reporting-dialog-field"><span>Interval</span><select value={interval} onChange={(event) => setIntervalValue(event.target.value)}><option value="3600">Hourly</option><option value="86400">Daily</option><option value="604800">Weekly</option><option value="2592000">Every 30 days</option></select></label>
</div> </FormGrid>
</Dialog> </Dialog>
); );
} }
-9
View File
@@ -611,12 +611,6 @@
overflow: auto; overflow: auto;
} }
.reporting-dialog-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
@media (max-width: 1100px) { @media (max-width: 1100px) {
.reporting-workspace { .reporting-workspace {
grid-template-columns: minmax(220px, 28%) minmax(0, 1fr); grid-template-columns: minmax(220px, 28%) minmax(0, 1fr);
@@ -647,7 +641,4 @@
width: 100%; width: 100%;
} }
.reporting-dialog-grid {
grid-template-columns: 1fr;
}
} }