From 57cea0c47de3b961d624e4cadfb1319a2c5e46e8 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 14 Sep 2026 09:42:24 +0000 Subject: [PATCH 1/4] Rapport: voet met legenda op elke pagina (issue #113, nabespreking) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit De Gantt-afdruk tekent één voetstrook (projectnaam, afdrukdatum, legenda, merk) onder de laatste rij; de pagineerder herhaalde alleen de kop, dus bij een meerpagina-afdruk stond de legenda uitsluitend op de laatste pagina. Met "een blad per persoon" (#132) is dat geen randgeval meer: een uitgedeeld vel zonder legenda is onleesbaar (hyperkritische review op #132, bevinding 5). De voet is nu, net als de kop, een herhaalbaar blok: - `RenderReportResult.footerHeight` (de render meldt zijn voethoogte, schaalt mee met de rapport-lettergrootte; tabelrenders en de lege staat hebben er geen); - `tileLayout.computeTileLayout` kent `repeatFooterHeightPx`: de body loopt van onder de kop tot boven de voet (`repeatFooterSrcY`), elke pagina houdt onderaan het printgebied `footerTopPt` vrij; zonder de optie byte-identiek, met dezelfde degeneratievangnetten als de kop; - raster-pagineerder, vector-pagineerder én preview tekenen de strook per pagina uit hetzelfde bronvenster (in de vector-tak komt de voettekst daardoor per pagina mee, zoals de koptekst); - instelling `repeatFooter` (standaard aan, zoals de kop) in ops-reportSettings, checkbox naast "Kop op elke pagina herhalen", 14 locales, gids nl+en. Het vaste "Pagina 1 van 1" in de voet is weg: de render kent het paginatotaal niet en het zou nu op elke pagina zo staan; het echte "n / totaal" drukken de pagineerders al in de ondermarge. Regressie: check-print-report.ts (voethoogte, tegeling met/zonder voet byte-identiek, laatste body-tegel eindigt boven de voet, rijgrenzen blijven, degeneratie, zonder kop en in 'actual', samen met blad-per-resource, geen nep-paginanummer), nieuw check-report-footer-setting.ts, browserspec report-footer.spec.ts (telt per PDF-pagina de XObject-tekeningen: 3 mét voet, 2 zonder; preview rastert opnieuw). report-preview-performance.spec.ts: fixture 260 → 240 rijen, want het paginatotaal stond precies op de previewcachegrens en de voet kost per pagina één strook body. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_014zosxbSXvi3YSGmeN1MsQq --- CLAUDE.md | 4 +- .../03-voet-op-elke-pagina-pagina-2.png | Bin 0 -> 24391 bytes public/docs/en/gids-rapporten-printen.md | 7 +- public/docs/nl/gids-rapporten-printen.md | 7 +- src/components/panels/ReportPanel.tsx | 28 +++++-- src/i18n/locales/ar/report.json | 1 + src/i18n/locales/de/report.json | 1 + src/i18n/locales/en/report.json | 1 + src/i18n/locales/es/report.json | 1 + src/i18n/locales/fa/report.json | 1 + src/i18n/locales/fr/report.json | 1 + src/i18n/locales/it/report.json | 1 + src/i18n/locales/ja/report.json | 1 + src/i18n/locales/ko/report.json | 1 + src/i18n/locales/nl/report.json | 1 + src/i18n/locales/pl/report.json | 1 + src/i18n/locales/pt/report.json | 1 + src/i18n/locales/tr/report.json | 1 + src/i18n/locales/zh/report.json | 1 + src/services/print/paginate.ts | 10 +++ src/services/print/paginateVector.ts | 18 +++- src/services/print/printPreview.ts | 31 +++++-- src/services/print/tileLayout.ts | 48 +++++++++-- src/utils/reportSettings.ts | 5 ++ tests/browser/report-footer.spec.ts | 77 ++++++++++++++++++ .../report-preview-performance.spec.ts | 6 +- tests/planning/check-print-report.ts | 66 +++++++++++++++ tests/planning/check-report-footer-setting.ts | 36 ++++++++ tests/planning/run.sh | 4 + 29 files changed, 332 insertions(+), 29 deletions(-) create mode 100644 artifacts/resourcediagram/03-voet-op-elke-pagina-pagina-2.png create mode 100644 tests/browser/report-footer.spec.ts create mode 100644 tests/planning/check-report-footer-setting.ts diff --git a/CLAUDE.md b/CLAUDE.md index 95ad9642e..1a57911fa 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -153,7 +153,9 @@ schermgroepering: gelijknamigen krijgen `#n`, naamlozen een surrogaat), daaronde start; relaties staan bij dit type uit omdat een taak onder meerdere banden kan staan; optie "blad per resource" = `PrintOptions.pageBreakBeforeGroups` → `RenderReportResult.forcedBreakOffsets` → `forcedBreakOffsetsPx` in `tileLayout`, waar een gedwongen -positie zonder vulgraaddrempel wint; `isGanttReportType()` bundelt beide Gantt-achtige typen), het +positie zonder vulgraaddrempel wint; `isGanttReportType()` bundelt beide Gantt-achtige typen; de voet +met legenda is sinds #113 net als de kop een herhaalbaar blok — `RenderReportResult.footerHeight` → +`repeatFooterHeightPx`/`repeatFooter`, instelling `repeatFooter` standaard aan), het mijlpalen- en variance-rapport (eigen DOM-component + `build*Columns` voor de PDF) en zeven **tabelrapporten** uit discussie #31 — look-ahead, kritiek/near-critical, voortgang, planningsgezondheid, resourcebelasting per week, resourcetoewijzingen en WBS-samenvatting. Die zeven hebben een pure diff --git a/artifacts/resourcediagram/03-voet-op-elke-pagina-pagina-2.png b/artifacts/resourcediagram/03-voet-op-elke-pagina-pagina-2.png new file mode 100644 index 0000000000000000000000000000000000000000..127cda26b2c91e57277e6b8a03f48eebc3111361 GIT binary patch literal 24391 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 literal 0 HcmV?d00001 diff --git a/public/docs/en/gids-rapporten-printen.md b/public/docs/en/gids-rapporten-printen.md index f5a91d2d7..2e2240081 100644 --- a/public/docs/en/gids-rapporten-printen.md +++ b/public/docs/en/gids-rapporten-printen.md @@ -40,6 +40,9 @@ A full, formatted printout of the Gantt bars — this is the only report type wi independently of the zoom level above. - **Repeat header on each page** — on by default; keeps the report header visible on every printed page instead of only the first. +- **Repeat footer on each page** — on by default; puts the footer with project name, print date and + legend at the bottom of every page instead of only the last, so a sheet handed out on its own + carries its own legend. Off = slightly more rows per page. - **Timeline over** — spreads the Gantt timeline across 1 to 8 pages side by side; only available with auto-fit on. Choose more pages when you want a less compressed timeline without reducing the table text. @@ -102,8 +105,8 @@ resource yourself first. Bands are per resource, not per name: two resources tha name each get their own band (*Jan #1*, *Jan #2*), and a resource without a name gets a sequence number. The summary block counts the resources, the assignments and the tasks without a resource — that last count includes milestones and hammocks, because they are drawn here (the *Resource -assignments* table report counts real activities only). The footer with the legend and print date -sits, as in every multi-page print, on the last sheet. If there are no assignments yet, the preview +assignments* table report counts real activities only). With *Repeat footer on each page* (on by +default) every sheet you hand out carries its own legend. If there are no assignments yet, the preview says so instead of showing an empty page; assigning happens on the **Resources** tab (see [Resources & histogram](docs://gids-resources-histogram)). diff --git a/public/docs/nl/gids-rapporten-printen.md b/public/docs/nl/gids-rapporten-printen.md index c0ca5fb32..1fe4f981b 100644 --- a/public/docs/nl/gids-rapporten-printen.md +++ b/public/docs/nl/gids-rapporten-printen.md @@ -41,6 +41,9 @@ instellingenblok: van het zoomniveau hierboven. - **Kop op elke pagina herhalen** — standaard aan; houdt de rapportkop zichtbaar op elke afgedrukte pagina in plaats van alleen de eerste. +- **Voet op elke pagina herhalen** — standaard aan; zet de voet met projectnaam, afdrukdatum en + legenda onderaan elke pagina in plaats van alleen de laatste, zodat een los uitgedeeld vel zijn + eigen legenda heeft. Uit = iets meer rijen per pagina. - **Tijdlijn over** — verdeelt de Gantt-tijdlijn over 1 tot 8 pagina's naast elkaar; alleen beschikbaar met auto-fit aan. Kies meer pagina's wanneer je de tijdas minder wilt comprimeren zonder de tabeltekst kleiner te maken. @@ -104,8 +107,8 @@ niet eerst zelf op resource te groeperen. Banden zijn per resource, niet per naa toevallig hetzelfde heten krijgen elk een eigen band (*Jan #1*, *Jan #2*), en een resource zonder naam een volgnummer. Het overzichtsblok telt de resources, de toewijzingen en de taken zonder resource — die laatste telling omvat ook mijlpalen en hammocks, want die worden hier getekend (het -tabelrapport *Resourcetoewijzingen* telt alleen echte activiteiten). De voet met legenda en -afdrukdatum staat, zoals bij elke meerpagina-afdruk, op het laatste vel. Zijn er nog geen +tabelrapport *Resourcetoewijzingen* telt alleen echte activiteiten). Met *Voet op elke pagina +herhalen* (standaard aan) heeft elk uitgedeeld vel zijn eigen legenda. Zijn er nog geen toewijzingen, dan zegt het voorbeeld dat in plaats van een lege pagina te tonen; toewijzen gebeurt op het tabblad **Resources** (zie [Resources & histogram](docs://gids-resources-histogram)). diff --git a/src/components/panels/ReportPanel.tsx b/src/components/panels/ReportPanel.tsx index 1bdb9129d..59f918021 100644 --- a/src/components/panels/ReportPanel.tsx +++ b/src/components/panels/ReportPanel.tsx @@ -305,6 +305,9 @@ export function ReportPanel() { // Bewust géén veld in `PrintOptions`: de kopherhaling is puur een pagineerder-zaak (raster: // hoogte in px; vector: boolean), niet iets dat de render-zoom raakt. const [repeatHeader, setRepeatHeader] = useState(DEFAULT_REPORT_SETTINGS.repeatHeader); + // Voet (projectnaam, afdrukdatum, legenda) op elke pagina — issue #113: een blad per persoon + // zonder legenda is onleesbaar. Zelfde pagineerder-zaak als de kop, dus óók geen PrintOptions-veld. + const [repeatFooter, setRepeatFooter] = useState(DEFAULT_REPORT_SETTINGS.repeatFooter); // Issue #25 punt 5 — smeert de tijdlijn uit over N paginabreedtes (1 = oud gedrag, geen // verrassing voor bestaande gebruikers). Alleen zinvol in fit-width-modus; daarom `disabled` // wanneer `autoFit` uit staat (dan tegelt de export in 'actual'-modus toch al horizontaal). @@ -387,6 +390,7 @@ export function ReportPanel() { setPaperSize(s.paperSize); setOrientation(s.orientation); setRepeatHeader(s.repeatHeader); + setRepeatFooter(s.repeatFooter); setTimelineColumns(s.timelineColumns); setReportFontScale(s.reportFontScale); setStatusLine(s.statusLine); @@ -427,14 +431,14 @@ export function ReportPanel() { void saveReportSettings({ reportType, showCritical, showFloat, showDeps, showWeekends, compressNonWorkdays: reportCompressNonWorkdays, showLegend, showTaskNames, showCompletion, truncateTaskNames, taskNameColumnWidth, showBaselineOverlay, autoFit, customZoom, - paperSize, orientation, repeatHeader, timelineColumns, reportFontScale, statusLine, followView, previewQuality, + paperSize, orientation, repeatHeader, repeatFooter, timelineColumns, reportFontScale, statusLine, followView, previewQuality, tableReports: tableOptions, resourceGantt: resourceGanttOptions, }).catch(() => {}); }, [reportType, showCritical, showFloat, showDeps, showWeekends, reportCompressNonWorkdays, showLegend, showTaskNames, showCompletion, truncateTaskNames, taskNameColumnWidth, showBaselineOverlay, autoFit, customZoom, paperSize, - orientation, repeatHeader, timelineColumns, reportFontScale, statusLine, followView, previewQuality, tableOptions, - resourceGanttOptions]); + orientation, repeatHeader, repeatFooter, timelineColumns, reportFontScale, statusLine, followView, previewQuality, + tableOptions, resourceGanttOptions]); // Resourcediagram (issue #113): dezelfde Gantt-render, maar de rijen komen uit de pure rekenmodule // (per resource-identiteit een band, daaronder zijn taken) en niet van het scherm. @@ -647,7 +651,7 @@ export function ReportPanel() { const renderPreview = () => { if (cancelled) return; const { - width: logicalWidth, height: logicalHeight, tableWidth, headerHeight, breakOffsets, forcedBreakOffsets, + width: logicalWidth, height: logicalHeight, tableWidth, headerHeight, footerHeight, breakOffsets, forcedBreakOffsets, } = measurePrintReport(tasks, sequences, calendar, projectName, options); const lowerPaper = options.paperSize.toLowerCase() as 'a4' | 'a3' | 'a2' | 'a1'; const cssPageWidth = previewCssWidth; @@ -665,6 +669,7 @@ export function ReportPanel() { // Kop herhalen per pagina (issue #25 punt 1): de hoogte komt uit de render zelf; 0 = niet // herhalen (oud gedrag). De raster-tak wil px, de vector-tak een boolean. repeatHeaderHeightPx: repeatHeader ? headerHeight : 0, + repeatFooterHeightPx: repeatFooter ? (footerHeight ?? 0) : 0, timelineColumns: options.timelineColumns, // Rij-bewuste paginering (issue #110): preview en export delen dezelfde breekposities; // het resourcediagram (issue #113) ook zijn gedwongen overgangen per resource. @@ -828,7 +833,7 @@ export function ReportPanel() { // toevoegen zou iedere preview-state-update opnieuw laten rasteren. // eslint-disable-next-line react-hooks/exhaustive-deps }, [reportSettingsHydrated, isGanttLike, tasks, sequences, calendar, projectName, previewOptionsSignature, - repeatHeader, previewCssWidth, previewQuality, replacePreviewPages]); + repeatHeader, repeatFooter, previewCssWidth, previewQuality, replacePreviewPages]); // Eén stabiele observer per layout. Een nieuwe afbeelding verandert zijn dependencies niet en kan // dus geen observer-rebuild/ping-pong veroorzaken. De queue dedupliceert callbacks. @@ -919,7 +924,7 @@ export function ReportPanel() { const exportRaster = (): Uint8Array => { const exportCanvas = document.createElement('canvas'); const { - width: logicalWidth, height: logicalHeight, tableWidth, headerHeight, breakOffsets, forcedBreakOffsets, + width: logicalWidth, height: logicalHeight, tableWidth, headerHeight, footerHeight, breakOffsets, forcedBreakOffsets, } = renderPrintCanvas(exportCanvas, tasks, sequences, calendar, projectName, options, 1); const exportScale = computeHighResScale(logicalWidth, logicalHeight); renderPrintCanvas(exportCanvas, tasks, sequences, calendar, projectName, options, exportScale); @@ -929,6 +934,7 @@ export function ReportPanel() { // Zelfde kopherhaling (px) en tijdlijn-spreiding als de preview en de vector-tak, zodat de // raster-terugval WYSIWYG gelijk is aan beide (issue #25 punt 1 + 5). repeatHeaderHeightPx: repeatHeader ? headerHeight : 0, + repeatFooterHeightPx: repeatFooter ? (footerHeight ?? 0) : 0, timelineColumns, breakOffsetsPx: breakOffsets, forcedBreakOffsetsPx: forcedBreakOffsets, @@ -954,8 +960,10 @@ export function ReportPanel() { orientation, mode, baseDir: exportBaseDir, - // Kop per pagina herhalen (issue #25 punt 1) + tijdlijn over N pagina's (punt 5). + // Kop per pagina herhalen (issue #25 punt 1) + tijdlijn over N pagina's (punt 5); voet + // per pagina (issue #113). repeatHeader, + repeatFooter, timelineColumns, }, { regular, bold }, @@ -1061,7 +1069,7 @@ export function ReportPanel() { await writePdf(tablePdfBytes, `${fileBase}-${suffix}.pdf`); }, [reportType, isGanttLike, projectName, fileBase, tasks, sequences, calendar, options, paperSize, orientation, - autoFit, repeatHeader, timelineColumns, writePdf, t, dd, milestoneRows, varianceResult, tableSpec]); + autoFit, repeatHeader, repeatFooter, timelineColumns, writePdf, t, dd, milestoneRows, varianceResult, tableSpec]); // K7-guard: een stale planning eerst doorrekenen. NIET meteen daarna exporteren — `runExport` // leest `tasks`/`options`/`tableSpec` uit de closure van de HUIDIGE render, en die kent de @@ -1433,6 +1441,10 @@ export function ReportPanel() { setRepeatHeader(e.target.checked)} className="accent-accent flex-shrink-0" /> {t('repeatHeader')} +