Merge ticket/009-all-dashboards-print-friendly-view
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o
This commit is contained in:
@@ -1,9 +0,0 @@
|
||||
# Add "All dashboards" option with print-friendly stacked layout
|
||||
|
||||
The dashboards part should keep the click-to-select dashboards, however it should have an "All dashboards" option. In that situation, the dashboards are displayed one after another with named separators. This is important so that it is possible to print the page or print the dashboards section. Printing itself is a browser capability we don't need to implement, but the page should be laid out in a way that's compatible with it.
|
||||
|
||||
## Acceptance criteria
|
||||
- Existing click-to-select single-dashboard view is preserved.
|
||||
- New "All dashboards" option stacks all dashboards vertically, one after another, each preceded by a named separator (dashboard name/title).
|
||||
- The stacked layout uses normal document flow (no fixed-height scroll containers that would clip content) so the browser's native print function can render the full stacked content across pages.
|
||||
- Verified visually in browser, including a print-preview check.
|
||||
@@ -26,6 +26,9 @@ import { HelpIcon } from './HelpIcon'
|
||||
|
||||
const CHART_TYPES: ChartType[] = ['line', 'bar', 'pie']
|
||||
|
||||
/// Virtual selection id for the "All dashboards" stacked/print view; distinct from any real dashboard id.
|
||||
const ALL_DASHBOARDS_ID = '__all__'
|
||||
|
||||
/// Long enough that rapid typing doesn't spam the save endpoint, short enough that a pause reads as "done" and commits promptly.
|
||||
const AUTOSAVE_DEBOUNCE_MS = 600
|
||||
|
||||
@@ -96,6 +99,36 @@ function AutosaveStatus({ status, error }: { status: SaveStatus; error: string |
|
||||
return null
|
||||
}
|
||||
|
||||
/// Read-only, print-friendly view of every dashboard stacked one after another, each preceded by a heading naming it.
|
||||
/// Deliberately uses normal document flow (no scroll containers, no fixed heights) so the browser's native print
|
||||
/// function can lay the full stack out across as many pages as it needs.
|
||||
function AllDashboardsView({ dashboards, range }: { dashboards: Dashboard[]; range: TimeRange | null }) {
|
||||
if (dashboards.length === 0) {
|
||||
return <div className="text-xs text-text-faint">No dashboards saved yet.</div>
|
||||
}
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{dashboards.map((d) => (
|
||||
<section key={d.id} className="break-inside-avoid-page">
|
||||
<h3 className="mb-2 border-b border-border pb-1 text-sm font-medium text-text">{d.name}</h3>
|
||||
{d.panels.length === 0 ? (
|
||||
<div className="text-xs text-text-faint">No panels in this dashboard.</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 gap-3 lg:grid-cols-2 print:grid-cols-1">
|
||||
{d.panels.map((panel) => (
|
||||
<div key={panel.id} className="break-inside-avoid-page rounded border border-border p-2">
|
||||
{panel.title && <div className="mb-1 text-xs text-text-subtle">{panel.title}</div>}
|
||||
<PanelPreview panel={panel} range={range} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function Dashboards({ range }: { range: TimeRange | null }) {
|
||||
const [activeProfile, setActiveProfile] = useState<string | null>(null)
|
||||
const [dashboards, setDashboards] = useState<Dashboard[]>([])
|
||||
@@ -281,6 +314,19 @@ export function Dashboards({ range }: { range: TimeRange | null }) {
|
||||
</button>
|
||||
</form>
|
||||
<div className="space-y-1">
|
||||
{dashboards.length > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
className={`flex w-full items-center rounded border px-2 py-1 text-left text-xs ${
|
||||
selectedId === ALL_DASHBOARDS_ID
|
||||
? 'border-accent bg-accent/10 text-accent-text'
|
||||
: 'border-border-soft text-text-secondary hover:bg-surface-2'
|
||||
}`}
|
||||
onClick={() => setSelectedId(ALL_DASHBOARDS_ID)}
|
||||
>
|
||||
All dashboards
|
||||
</button>
|
||||
)}
|
||||
{dashboards.length === 0 && <div className="text-xs text-text-faint">No dashboards saved yet.</div>}
|
||||
{dashboards.map((d) => (
|
||||
<div
|
||||
@@ -308,7 +354,9 @@ export function Dashboards({ range }: { range: TimeRange | null }) {
|
||||
</div>
|
||||
|
||||
<div className="min-w-0 flex-1">
|
||||
{!draft ? (
|
||||
{selectedId === ALL_DASHBOARDS_ID ? (
|
||||
<AllDashboardsView dashboards={dashboards} range={range} />
|
||||
) : !draft ? (
|
||||
<div className="text-xs text-text-faint">Select or create a dashboard to edit its panels.</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
|
||||
Reference in New Issue
Block a user