Skip to content
9 changes: 8 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,14 @@ start; relaties staan bij dit type uit omdat een taak onder meerdere banden kan
relatielijnen en legendaregel, de balken volgen `barColorSelection` via `criticalFill`); optie "blad
per resource" = `PrintOptions.pageBreakBeforeGroups`
→ `RenderReportResult.forcedBreakOffsets` → `forcedBreakOffsetsPx` in `tileLayout`, waar een gedwongen
positie zonder vulgraaddrempel wint; `isGanttReportType()` bundelt beide Gantt-achtige typen; de voet
positie zonder vulgraaddrempel wint — een band direct onder een band (de optionele typelaag
`groupByType`: eerst een band per resourcetype in de vaste volgorde `RESOURCE_TYPE_BAND_ORDER`) krijgt
geen eigen gedwongen positie; optie *Rapportageperiode* = de gedeelde `ReportingPeriod` als
`PrintOptions.timeWindow` (tijdas exact op het venster, geometrie geklemd op het chartgebied want
`Draw2D` kent geen clip; de rijenbron filtert op overlap en telt `counts.outsidePeriod`); optie
*Eenheden/dag en curve tonen* = `PrintOptions.assignmentColumns` + `rowAssignments` (per `rowKey`
uit `assignmentByRowKey`: eenheden opgeteld, curve alleen bij eensluidende records) als twee
tabelkolommen achter de naam; `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; let op: de preview
rendert per pagina één volledige `renderReport`-pass extra voor die strook, net als voor de kop), het
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 23 additions & 2 deletions public/docs/en/gids-rapporten-printen.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,28 @@ with the tasks assigned to it underneath, in order of start. That is the "who do
overview for the site meeting, or — with the option **Each resource on a new page** — a separate
sheet per person to hand out. A task with two resources appears under both bands; summary tasks are
left out — an assignment on a summary task (which an import can produce) is not shown here. **Include tasks without a resource** adds a *(none)* band
at the bottom, so the meeting can see at a glance what nobody owns yet.
at the bottom, so the meeting can see at a glance what nobody owns yet. **Group by resource type**
adds a layer above: first a band per type — labour, crew, subcontractor, equipment, material, in
that fixed order — with the resources inside it; useful when a meeting discusses the people first
and the equipment after, or when you only want to print the equipment block; the number after a type
band counts the task rows below it, so a task under two resources of the same type counts twice.
With *Each resource on a new page* a type band starts a new sheet together with its first resource. **Reporting period** —
the same control as on the table reports, see further down — limits the diagram to a time window:
only tasks that touch the window take part, the time axis runs exactly from the start to the end
date, and a bar that continues beyond it is cut off at the edge. That lets a meeting put the
look-ahead report and the resource diagram side by side over exactly the same weeks. *Project
duration* (the default) is the usual behaviour; with a window the summary block also counts how
many tasks fall outside it. After the task name two columns **Units/d** and **Curve** are shown by
default: how many units per day the band's resource is assigned to the task, and with which
distribution curve — "you are on Foundations half days, front loaded" says more than "you are on
Foundations". A task under two bands shows each band's own load; two assignments of the same
resource on one task are added up, and a dash in the curve column means those assignments carry
different curves. If the assignment has its own hour distribution the column says *Contour*; if
the curve was imported from MS Project or P6 and is none of the eight built-in shapes it says
*Imported curve* — the same as in the properties panel. The two columns make the table a good 130 px
wider and the time axis narrower; with long task names and *Truncate task names* off the time axis
can all but disappear. Turn **Show units/day and curve** off for the narrow table of the Gantt
print.

The settings of the Gantt print apply here too — critical path, float, bar colours, status line,
paper, header repeat — with three exceptions: *Follow view* (this report doesn't take its rows from
Expand Down Expand Up @@ -160,7 +181,7 @@ a few conventions:

Four reports work on a time window: look-ahead, progress, resource loading and resource
assignments. They share one *Reporting period* control in the report options, with its own
remembered setting per report:
remembered setting per report (the resource diagram above offers the same control):

- **Next / last week, 2, 4, 6, 8 or 12 weeks** and **next / last month** — counted from the
project's status date (or today if none is set). A preset is inclusive on both ends: *next 4
Expand Down
24 changes: 22 additions & 2 deletions public/docs/nl/gids-rapporten-printen.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,27 @@ een nieuwe pagina** — een los vel per persoon om uit te delen. Een taak met tw
onder beide banden; verzameltaken doen niet mee — een toewijzing op een verzameltaak (die uit een
import kan komen) wordt hier niet getoond. Met **Taken zonder
resource meenemen** komt er onderaan een band *(geen)* bij, zodat je in het overleg meteen ziet wat
nog niemand heeft.
nog niemand heeft. Met **Groeperen op resourcetype** komt er een laag boven: eerst een band per type
— arbeid, ploeg, onderaannemer, materieel, materiaal, in die vaste volgorde — en daarbinnen de
resources; handig als je in het overleg eerst de mensen en dan het materieel bespreekt, of alleen het
materieelblok wilt afdrukken; het getal achter een typeband telt de taakrijen eronder, dus een taak
die onder twee resources van hetzelfde type staat telt twee keer. Bij *Elke resource op een nieuwe
pagina* begint een typeband op een nieuw vel samen met zijn eerste resource. Met **Rapportageperiode** — dezelfde keuze als bij de
tabelrapporten, zie verderop — beperk je het diagram tot een tijdvenster: alleen taken die het
venster raken doen mee, de tijdas loopt precies van de begin- tot de einddatum, en een balk die
verder doorloopt wordt aan de rand afgekapt. Zo leg je in dezelfde vergadering het look-ahead-rapport
en het resourcediagram over exact dezelfde weken naast elkaar. *Hele project* (standaard) is het
gewone gedrag; bij een venster telt het overzichtsblok ook hoeveel taken erbuiten vallen.
Achter de taaknaam staan standaard twee kolommen **Eenh./d** en **Curve**: hoeveel eenheden per
dag de resource van die band op de taak staat en met welke verdeelcurve — "je staat halve dagen
op Fundering, vooraan belast" zegt meer dan "je staat op Fundering". Een taak onder twee banden
toont per band de eigen inzet; twee toewijzingen van dezelfde resource op één taak worden
opgeteld, en een streepje bij de curve betekent dat die toewijzingen verschillende curves hebben.
Heeft de toewijzing een eigen urenverdeling, dan staat er *Contour*; is de curve uit MS Project of
P6 geïmporteerd en is het geen van de acht eigen vormen, dan staat er *Geïmporteerde curve* —
hetzelfde als in het eigenschappenpaneel. De twee kolommen maken de tabel ruim 130 px breder en de
tijdas dus smaller; bij lange taaknamen met *Taaknamen afkappen* uit kan de tijdas daardoor bijna
verdwijnen. Zet **Eenheden/dag en curve tonen** uit voor de smalle tabel van de Gantt-afdruk.

De instellingen van de Gantt-afdruk gelden hier ook — kritiek pad, speling, balkkleuren, statuslijn,
papier, kopherhaling — op drie na: *Volg weergave* (de rijen komen bij dit rapport niet van het
Expand Down Expand Up @@ -162,7 +182,7 @@ delen een paar afspraken:

Vier rapporten werken op een tijdvenster: look-ahead, voortgang, resourcebelasting en
resourcetoewijzingen. Ze delen één *Rapportageperiode*-keuze in de rapportopties, met een eigen
onthouden instelling per rapport:
onthouden instelling per rapport (het resourcediagram hierboven kent dezelfde keuze):

- **Volgende / afgelopen week, 2, 4, 6, 8 of 12 weken** en **volgende / afgelopen maand** — gerekend
vanaf de statusdatum van het project (of vandaag als die ontbreekt). Een preset is inclusief aan
Expand Down
84 changes: 80 additions & 4 deletions src/components/panels/ReportPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
import { computeResourceGanttRows } from '@/engine/reports';
import type { ViewRow } from '@/engine/view/visibleRows';
import { TableReportView } from './reports/TableReportView';
import { ReportingPeriodField, useResolvedPeriod } from './reports/ReportingPeriodField';
import { TableReportOptionsBlock } from './reports/TableReportOptionsBlock';
import { useTableReportSpec } from './reports/useTableReportSpec';
import { toPdfSpec } from './reports/tableReportSpec';
Expand Down Expand Up @@ -447,12 +448,36 @@ export function ReportPanel() {
const isGanttLike = isGanttReportType(reportType);
const noneLabel = tTask('structure.none');
// De bandvolgorde volgt de app-taal (nooit de OS-taal van de afdrukker: zelfde vel, zelfde nummering).
// Typelabels voor de optionele typelaag (punt 2): dezelfde sleutels als het resourcepaneel.
const resourceTypeLabels = useMemo(() => ({
LABOR: tCommon('resource.type.labor'), CREW: tCommon('resource.type.crew'),
SUBCONTRACTOR: tCommon('resource.type.subcontractor'), EQUIPMENT: tCommon('resource.type.equipment'),
MATERIAL: tCommon('resource.type.material'),
}), [tCommon]);
// Vertaalde curvenamen voor de toewijzingskolommen (punt 1): dezelfde sleutels als het taakraster.
const curveLabels = useMemo(() => ({
UNIFORM: tCommon('resource.curve.uniform'), FRONT_LOADED: tCommon('resource.curve.frontLoaded'),
BACK_LOADED: tCommon('resource.curve.backLoaded'), BELL: tCommon('resource.curve.bell'),
EARLY_PEAK: tCommon('resource.curve.earlyPeak'), LATE_PEAK: tCommon('resource.curve.latePeak'),
DOUBLE_PEAK: tCommon('resource.curve.doublePeak'), TURTLE: tCommon('resource.curve.turtle'),
// Dezelfde twee toestanden als het eigenschappenpaneel: contour op de taak, geïmporteerde curve.
contoured: tTask('properties.assignments.contoured'), imported: tTask('properties.assignments.importedCurve'),
}), [tCommon, tTask]);
// Rapportageperiode als tijdvenster (punt 3): dezelfde oplossing als het control toont; bij
// *Hele project* geen venster, zodat het rapport byte-identiek blijft aan vóór deze optie.
const resourceGanttPeriod = useResolvedPeriod(resourceGanttOptions.period);
const resourceGanttWindow = reportType === 'resourceGantt' && resourceGanttOptions.period.preset !== 'project'
? resourceGanttPeriod
: undefined;
const resourceGantt = useMemo(() => (reportType === 'resourceGantt'
? computeResourceGanttRows({ tasks, resources, assignments }, {
includeUnassigned: resourceGanttOptions.includeUnassigned, noneLabel, locale: i18n.language,
groupByType: resourceGanttOptions.groupByType, typeLabels: resourceTypeLabels,
window: resourceGanttWindow,
})
: null),
[reportType, tasks, resources, assignments, noneLabel, resourceGanttOptions.includeUnassigned, i18n.language]);
[reportType, tasks, resources, assignments, noneLabel, resourceGanttOptions.includeUnassigned,
resourceGanttOptions.groupByType, resourceTypeLabels, resourceGanttWindow, i18n.language]);
// Rijenbron van de Gantt-render: resourcediagram ⇒ de resourcebanden; Gantt-afdruk ⇒ de schermrijen
// bij Volg weergave (#54), anders `undefined` = de volledige takenboom (oud gedrag, geen verrassingen).
const reportRows = resourceGantt ? resourceGantt.rows : followView ? viewRows : undefined;
Expand Down Expand Up @@ -536,7 +561,13 @@ export function ReportPanel() {
labels: {
// Resourcediagram zonder één toewijzing: zeg wat er ontbreekt, niet "geen taken" — tenzij er
// écht geen taken zijn, dan is "wijs resources toe" het verkeerde advies.
noTasks: reportType === 'resourceGantt' && tasks.length > 0 ? t('resourceGantt.empty') : t('noTasks'),
// Leeg door het venster (géén bladtaak meer in de periode) ⇒ wijs naar de periode; leeg terwijl
// er wél taken in de periode staan ⇒ die zijn niet toegewezen, en een andere periode helpt niet.
noTasks: reportType === 'resourceGantt' && tasks.length > 0
? (resourceGantt && resourceGantt.counts.inPeriod === 0 && resourceGantt.counts.outsidePeriod > 0
? t('resourceGantt.emptyPeriod')
: t('resourceGantt.empty'))
: t('noTasks'),
printed: t('printed'),
legend: {
criticalPath: t('legend.criticalPath'),
Expand All @@ -552,6 +583,8 @@ export function ReportPanel() {
tableHeaders: {
wbs: t('tableHeaders.wbs'),
taskName: t('tableHeaders.taskName'),
unitsPerDay: t('tableHeaders.unitsPerDay'),
curve: t('tableHeaders.curve'),
start: t('tableHeaders.start'),
end: t('tableHeaders.end'),
duration: t('tableHeaders.duration'),
Expand Down Expand Up @@ -594,6 +627,12 @@ export function ReportPanel() {
rows: reportRows,
// Issue #113 "een blad per persoon": gedwongen paginaovergang vóór elke resourceband.
pageBreakBeforeGroups: reportType === 'resourceGantt' && resourceGanttOptions.pageBreakPerResource,
// Punt 3: de tijdas op de rapportageperiode (alleen resourcediagram, alleen buiten *Hele project*).
timeWindow: resourceGanttWindow,
// Punt 1: eenheden/dag en curve van de band op de taak als tabelkolommen (alleen resourcediagram).
assignmentColumns: reportType === 'resourceGantt' && resourceGanttOptions.showAssignmentColumns,
rowAssignments: resourceGantt?.assignmentByRowKey,
curveLabels,
barColorsLegendLabels: {
criticalOutline: t('legend.criticalOutline', { defaultValue: 'Kritiek pad (rand)' }),
categoriesMore: (n: number) => t('legend.categoriesMore', { count: n }),
Expand All @@ -603,7 +642,8 @@ export function ReportPanel() {
project.endDate, project.author, dateNotation, weekStartDay, reportCompressNonWorkdays, timelineColumns, reportFontScale,
cpmResult, barColorSelection, fieldCtx.activityCodeTypes, fieldCtx.customFieldDefs,
reportTaskTypeLabels, tTask, statusLine, statusDate, resources,
assignments, baselineOverlay, reportRows, reportType, resourceGanttOptions.pageBreakPerResource, tasks.length]);
assignments, baselineOverlay, reportRows, reportType, resourceGanttOptions.pageBreakPerResource, tasks.length,
resourceGantt, resourceGanttWindow, resourceGanttOptions.showAssignmentColumns, curveLabels]);
// `options` bevat afgeleide catalogus-/vertaalobjecten die bij een lokale preview-state-update
// opnieuw kunnen worden aangemaakt zonder dat hun inhoud wijzigde. De rastertaak gebruikt deze
// inhoudssignatuur als effectgrens: anders start `setPreviewPages` zelf opnieuw pagina 0 en 1.
Expand All @@ -615,7 +655,10 @@ export function ReportPanel() {
? (value as ViewRow[]).map(r => (r.kind === 'group'
? `g:${r.key}:${r.label}:${r.count}:${r.depth}`
: `t:${r.rowKey}:${r.depth}:${r.dimmed ? 1 : 0}`))
: value
// Een Map serialiseert als `{}`; de toewijzingskolommen (punt 1) moeten wél een herrender geven.
: key === 'rowAssignments' && value instanceof Map
? [...(value as Map<string, unknown>).entries()]
: value
)), [options]);

// Eén generatie beheert één layout + één begrensde renderqueue. Een optiewijziging annuleert het
Expand Down Expand Up @@ -1187,6 +1230,12 @@ export function ReportPanel() {
<span data-ops-resource-gantt-count="assignments">{resourceGantt.counts.assignments}</span>
<span className="text-text-secondary">{t('resourceGantt.unassigned')}</span>
<span data-ops-resource-gantt-count="unassigned">{resourceGantt.counts.unassignedTasks}</span>
{resourceGanttWindow && (
<>
<span className="text-text-secondary">{t('resourceGantt.outsidePeriod')}</span>
<span data-ops-resource-gantt-count="outsidePeriod">{resourceGantt.counts.outsidePeriod}</span>
</>
)}
</>
) : reportType === 'gantt' ? (
<>
Expand Down Expand Up @@ -1400,6 +1449,33 @@ export function ReportPanel() {
/>
<span className="min-w-0">{t('resourceGantt.includeUnassigned')}</span>
</label>
<label className="flex items-center gap-2 min-w-0">
<input
type="checkbox"
checked={resourceGanttOptions.groupByType}
onChange={e => patchResourceGanttOptions({ groupByType: e.target.checked })}
className="accent-accent flex-shrink-0"
data-ops-report-option="groupByType"
/>
<span className="min-w-0">{t('resourceGantt.groupByType')}</span>
</label>
<label className="flex items-center gap-2 min-w-0">
<input
type="checkbox"
checked={resourceGanttOptions.showAssignmentColumns}
onChange={e => patchResourceGanttOptions({ showAssignmentColumns: e.target.checked })}
className="accent-accent flex-shrink-0"
data-ops-report-option="showAssignmentColumns"
/>
<span className="min-w-0">{t('resourceGantt.showAssignmentColumns')}</span>
</label>
{/* Punt 3: de gedeelde rapportageperiode (issue #120) als tijdvenster van dit rapport. */}
<ReportingPeriodField
id="report-opt-resourceGanttPeriod"
value={resourceGanttOptions.period}
onChange={next => patchResourceGanttOptions({ period: next })}
dataKey="resourceGanttPeriod"
/>
</>
)}

Expand Down
6 changes: 4 additions & 2 deletions src/components/panels/reports/ReportingPeriodField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ interface Props {
dataKey: string;
}

/** De referentiedag en projectspanne uit de live store — dezelfde bron als `useReportContext`. */
function useResolvedPeriod(period: ReportingPeriod): ResolvedPeriod {
/** De referentiedag en projectspanne uit de live store — dezelfde bron als `useReportContext`.
* Ook gebruikt door `ReportPanel` voor het tijdvenster van het resourcediagram, zodat de rijen en
* de tijdas exact het venster nemen dat dit control toont. */
export function useResolvedPeriod(period: ReportingPeriod): ResolvedPeriod {
const tasks = useAppStore(s => s.tasks);
const statusDate = useAppStore(s => s.project.statusDate);
const today = formatDate(new Date());
Expand Down
Loading