The smart-timeline-chart block in global.css declared @page { size: A4
landscape } inside @media print. @page rules are global even when nested
in selectors, so this leaked landscape onto every printed surface in
paliad — not just the chart.
Switch to named-page strategy:
- Default @page { size: A4 portrait; margin: 1.5cm 1.2cm }
- @page paliad-landscape { size: A4 landscape; margin: 1.5cm }
- @media print: body.<surface> { page: paliad-landscape } opts surfaces
that need width into landscape via per-page body classes
Landscape opt-ins:
- body.page-kostenrechner — wide fee-tier tables
- body.page-projects-chart — horizontal Smart Timeline chart
- body.events-view-calendar — /events Kalender tab (month grid)
- body.views-shape-active-calendar / -timeline — Custom Views shapes
- body.verfahrensablauf-view-timeline — horizontal procedure timeline
Body classes:
- kostenrechner.tsx, projects-chart.tsx, verfahrensablauf.tsx now set
page-<slug> on body
- verfahrensablauf.ts toggles verfahrensablauf-view-(timeline|columns)
in initViewToggle
- views.ts toggles views-shape-active-<shape> in setActiveShape (mirrors
the existing events.ts events-view-* pattern)
General print polish in the universal block (the catch-all at the bottom
of global.css):
- Hide .fab / .fab-button / .edit-mode-handle / .paliadin-widget /
[data-print-hide] in print
- thead { display: table-header-group } so headers repeat across pages
- tr/th/td page-break-inside: avoid so rows don't split mid-cell
- h1-h6 page-break-after: avoid, orphans/widows: 3 for p/h*/li
- print-color-adjust: exact on brand-coloured headers + status pills
- a[href^="http"]::after content: " (" attr(href) ")" prints external
URLs after their link text (opt-out via data-print-url="hide")
- body font-size: 11pt for print readability
Verified via Playwright on static dist build that:
- Default surfaces (dashboard, projects, fristenrechner, agenda, admin)
match no page: rule → portrait
- kostenrechner, projects-chart match the landscape rule
- verfahrensablauf-view-columns → portrait, -view-timeline → landscape
- views-shape-active-list/-cards → portrait, -calendar/-timeline →
landscape
- /events default (events-view-cards) → portrait, calendar toggle →
landscape
go build ./... + go test ./internal/... + bun test (99 pass) + bun
run build all clean.
173 lines
8.8 KiB
TypeScript
173 lines
8.8 KiB
TypeScript
import { h } from "./jsx";
|
|
import { Sidebar } from "./components/Sidebar";
|
|
import { PaliadinWidget } from "./components/PaliadinWidget";
|
|
import { BottomNav } from "./components/BottomNav";
|
|
import { Footer } from "./components/Footer";
|
|
import { PWAHead } from "./components/PWAHead";
|
|
|
|
// t-paliad-177 Slice 1 — Project Timeline / Chart standalone page.
|
|
//
|
|
// Pure shell: header / controls scaffold (inert chips for the
|
|
// vertical-toggle, density and palette pickers, which Slice 3 wires
|
|
// live) + a chart host that client/projects-chart.ts mounts the SVG
|
|
// renderer into. Project metadata is loaded client-side so the same
|
|
// dist/projects-chart.html serves every {id}.
|
|
//
|
|
// Design ref: docs/design-project-chart-2026-05-09.md §8.2 + §12.
|
|
export function renderProjectsChart(): string {
|
|
return "<!DOCTYPE html>" + (
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
<meta name="theme-color" content="#BFF355" />
|
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
|
<PWAHead />
|
|
<title data-i18n="projects.chart.title">Projekt-Chart — Paliad</title>
|
|
<link rel="stylesheet" href="/assets/global.css" />
|
|
</head>
|
|
<body className="has-sidebar page-projects-chart">
|
|
<Sidebar currentPath="/projects" />
|
|
<BottomNav currentPath="/projects" />
|
|
|
|
<main>
|
|
<section className="tool-page smart-timeline-chart-page">
|
|
<div className="container">
|
|
<a
|
|
id="projects-chart-back-link"
|
|
href="/projects"
|
|
className="back-link"
|
|
data-i18n="projects.chart.back"
|
|
>
|
|
← Zurück zum Verlauf
|
|
</a>
|
|
|
|
<div id="projects-chart-loading" className="entity-loading">
|
|
<p data-i18n="projects.chart.loading">Lädt…</p>
|
|
</div>
|
|
|
|
<div id="projects-chart-notfound" className="entity-empty" style="display:none">
|
|
<p data-i18n="projects.chart.notfound">Projekt nicht gefunden oder keine Berechtigung.</p>
|
|
</div>
|
|
|
|
<div id="projects-chart-body" style="display:none">
|
|
<header className="smart-timeline-chart-header">
|
|
<h1 id="projects-chart-title" />
|
|
<span id="projects-chart-meta" className="smart-timeline-chart-meta" />
|
|
</header>
|
|
|
|
<div className="smart-timeline-chart-controls" id="projects-chart-controls">
|
|
{/* Slice 1: chips render inert. Slice 3 wires them to
|
|
density / palette / zoom state. The presence keeps
|
|
the surface visually stable when controls light up. */}
|
|
<span className="chip-inert" data-i18n="projects.chart.control.layout.horizontal" title="Slice 3">
|
|
Layout: Horizontal
|
|
</span>
|
|
<span className="smart-timeline-chart-picker">
|
|
<label htmlFor="projects-chart-range" data-i18n="projects.chart.control.range.label">
|
|
Zeitraum:
|
|
</label>
|
|
<select id="projects-chart-range">
|
|
<option value="1y" data-i18n="projects.chart.range.1y">1 Jahr</option>
|
|
<option value="2y" data-i18n="projects.chart.range.2y">2 Jahre</option>
|
|
<option value="all" data-i18n="projects.chart.range.all">Alles anzeigen</option>
|
|
<option value="custom" data-i18n="projects.chart.range.custom">Eigener Bereich…</option>
|
|
</select>
|
|
</span>
|
|
<span className="smart-timeline-chart-picker smart-timeline-chart-range-custom" id="projects-chart-range-custom" style="display:none">
|
|
<label htmlFor="projects-chart-range-from" data-i18n="projects.chart.range.from">Von:</label>
|
|
<input type="date" id="projects-chart-range-from" />
|
|
<label htmlFor="projects-chart-range-to" data-i18n="projects.chart.range.to">Bis:</label>
|
|
<input type="date" id="projects-chart-range-to" />
|
|
</span>
|
|
<span className="smart-timeline-chart-picker">
|
|
<label htmlFor="projects-chart-density" data-i18n="projects.chart.control.density.label">
|
|
Dichte:
|
|
</label>
|
|
<select id="projects-chart-density">
|
|
<option value="compact" data-i18n="projects.chart.density.compact">Kompakt</option>
|
|
<option value="standard" data-i18n="projects.chart.density.standard">Standard</option>
|
|
<option value="spacious" data-i18n="projects.chart.density.spacious">Großzügig</option>
|
|
</select>
|
|
</span>
|
|
<span className="smart-timeline-chart-picker">
|
|
<label htmlFor="projects-chart-palette" data-i18n="projects.chart.control.palette.label">
|
|
Palette:
|
|
</label>
|
|
<select id="projects-chart-palette">
|
|
<option value="default" data-i18n="projects.chart.palette.default">Standard</option>
|
|
<option value="kind-coded" data-i18n="projects.chart.palette.kind_coded">Nach Ereignistyp</option>
|
|
<option value="track-coded" data-i18n="projects.chart.palette.track_coded">Nach Spur</option>
|
|
<option value="high-contrast" data-i18n="projects.chart.palette.high_contrast">Hoher Kontrast</option>
|
|
<option value="print" data-i18n="projects.chart.palette.print">Druck (S/W)</option>
|
|
</select>
|
|
</span>
|
|
<button
|
|
type="button"
|
|
id="projects-chart-copylink"
|
|
className="smart-timeline-chart-copylink"
|
|
data-i18n="projects.chart.permalink.copy"
|
|
data-i18n-title="projects.chart.permalink.title"
|
|
title="URL mit allen Filtern in die Zwischenablage kopieren"
|
|
>
|
|
🔗 Link kopieren
|
|
</button>
|
|
<details className="smart-timeline-chart-export">
|
|
<summary data-i18n="projects.chart.export.menu">
|
|
⇓ Export
|
|
</summary>
|
|
<menu className="smart-timeline-chart-export-menu">
|
|
<li>
|
|
<button type="button" id="projects-chart-export-svg" data-i18n="projects.chart.export.svg">
|
|
SVG (Vektorgrafik)
|
|
</button>
|
|
</li>
|
|
<li>
|
|
<button type="button" id="projects-chart-export-png" data-i18n="projects.chart.export.png">
|
|
PNG (Bild, 2× HiDPI)
|
|
</button>
|
|
</li>
|
|
<li>
|
|
<button type="button" id="projects-chart-export-print" data-i18n="projects.chart.export.print">
|
|
PDF (Drucken)
|
|
</button>
|
|
</li>
|
|
<li className="smart-timeline-chart-export-divider" />
|
|
<li>
|
|
<button type="button" id="projects-chart-export-csv" data-i18n="projects.chart.export.csv">
|
|
CSV (Excel-Tabelle)
|
|
</button>
|
|
</li>
|
|
<li>
|
|
<button type="button" id="projects-chart-export-json" data-i18n="projects.chart.export.json">
|
|
JSON (Rohdaten)
|
|
</button>
|
|
</li>
|
|
<li>
|
|
<button type="button" id="projects-chart-export-ics" data-i18n="projects.chart.export.ics">
|
|
iCal (.ics — Outlook / Apple)
|
|
</button>
|
|
</li>
|
|
</menu>
|
|
</details>
|
|
</div>
|
|
|
|
<div id="projects-chart-lanes-filter" className="smart-timeline-chart-lanes-filter" style="display:none" />
|
|
|
|
<div id="projects-chart-host" className="smart-timeline-chart-host" />
|
|
|
|
<p id="projects-chart-undated" className="smart-timeline-chart-undated-hint" style="display:none" />
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<Footer />
|
|
<PaliadinWidget />
|
|
<script src="/assets/projects-chart.js"></script>
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|