stralos-feature-rates. The relevant components are DayRateInheritance.tsx (dialogs) and DayRatesPanel.tsx (schedule list and orchestration).
Day rate schedule list
DayRatesPanel lists active day rate schedules at the organisation level by default. Show archived schedules switches the list to archived schedules, which are read-only until restored. Each row in the table shows:
- Schedule — the schedule name.
- Client — derived from the displayed revision’s
clientNameSnapshot; falls back to"Internal"when no client is set and"Client unavailable"whenclientIdSnapshotis set but the snapshot is null. - Derivation — computed by
selectedDerivation()from the current or displayed revision. Resolves the base revision name and adjustment percentage from the in-memory revision index. Falls back to"Inherited ± N% · pinned revision"when the base revision is not in the list;"Client-defined lines"whenlineAuthoritySnapshot === "client_defined";"Independent · from <date>"otherwise. - Status — current and draft revision buttons, plus one orange Review required · Rev N button per non-superseded revision with
needsBaseReview: true. - Actions — a Duplicate for client button, shown only when
canEditis true and at least one published revision exists.
canEdit={false} for them. An admin must restore a schedule before acknowledging or applying an inheritance review.
Review chips
reviewRevisions() filters to revisions where status !== "superseded" && needsBaseReview. Each result produces an orange Review required · Rev N button that opens the review dialog for that specific revision. The chip disappears from the list after a successful acknowledgeDayRateBase call clears needsBaseReview on that revision. Other revisions on the same schedule keep their own chips if they still require review.
Schedule-level needsBaseReview
withUpdatedRevision() recomputes schedule.needsBaseReview on any local update as revisions.some((r) => r.status !== "superseded" && r.needsBaseReview). The panel does not re-fetch the full list after a review action; it applies the updated revision in place.
Rebase review dialog
RebaseReviewDialog opens when an admin clicks Review required · Rev N in the schedule list, then Review base changes in the expanded revision detail. Both buttons refer to the same revision.
Preview loading
On mount, the dialog callspreviewDayRateRebase(scheduleId) only when the displayed revision matches schedule.currentRevision (canPreviewDisplayed). A displayed revision that is not the current one cannot generate a fresh preview and shows a read-only message; it can still be acknowledged with Keep pinned.
Preview loading errors display an inline alert and a Retry review button. The dialog does not suppress dismiss during a load — only during a submit.
Rebase buckets
DayRateRebasePreview.buckets is keyed by DayRateRebaseBucket. The order is defined by DAY_RATE_REBASE_BUCKETS (imported from the domain package). RebaseBucketTable renders each bucket in that order.
removed is the only bucket with checkboxes. The checkbox state is held in a Set<string> of lineKey values. The set is reset to empty whenever the preview refreshes.
Pricing-rule buckets
DayRateRebasePreview.ruleBuckets separates pricing-rule outcomes from line outcomes. DAY_RATE_RULE_REBASE_BUCKETS supplies their display order.
Each row identifies whether the rule applies to the whole schedule or a line, its slot and condition, and the old base, new base, current, and draft-result forms. Rule removals are outcomes rather than selectable choices; the checkboxes apply only to removed lines.
Rate formatting
formatRate(value, rateKind, present) renders currency as NZD with two decimal places using Intl.NumberFormat("en-NZ"). When rateKind === "cost_plus" it returns "Cost plus" regardless of value. When present is false (the value is absent for this bucket, e.g. the old base rate on an added line) it returns an em dash (—).
Keep pinned
Keep pinned callsacknowledgeDayRateBase(scheduleId, displayedRevisionId, acknowledgementTargetId).
acknowledgementTargetId resolves as follows:
- When the displayed revision is the current revision and a preview loaded successfully, it is
preview.targetBaseRevisionId. - Otherwise it is
reviewingRevision.baseRevisionIdresolved through the revision index to the base schedule’s current revision id (revisionIndex.get(baseRevisionId)?.schedule.currentRevision?.id ?? null).
acknowledgementTargetId is null — for example because the base schedule is not in the loaded list — the Keep pinned button is disabled and an explanatory role="status" message is shown.
Create draft revision
Create draft revision callsapplyDayRateRebase(scheduleId, preview.targetBaseRevisionId, [...removals].sort()). Removal keys are sorted before sending so the payload is deterministic.
The button is disabled with a descriptive reason when:
- A draft revision already exists on this schedule.
- The displayed revision is not the current revision.
- The user is not an organisation admin (
!canEdit).
role="status" message above the bucket tables.
Stale 409 recovery
WhenapplyDayRateRebase returns a 409, the dialog attempts recovery:
- Calls
onRefreshSchedule()to reload the schedule detail. - Calls
previewDayRateRebaseagain to fetch a fresh preview. - Resets
removalsto an empty set. - Displays an inline error telling the admin the review was refreshed and all outcomes must be checked again.
safeApiMessage(), which maps:
Duplicate for client dialog
DuplicateDayRateScheduleDialog opens when an admin clicks Duplicate for client on a schedule row. The source schedule’s published revisions are available as source options.
Fields
Client loading
listAllClients() is called on mount. Despite the compatibility name, it pages the Companies register through /api/global/parties?role=client and projects each party to the ClientSummary shape required by the picker. While loading, the Create duplicate button is disabled. A load error shows an inline alert with a Retry clients button. The client picker is disabled during load or when a load error exists.
Submission
duplicateDayRateSchedule(sourceScheduleId, input) is called on valid submit. On success, onCreated(schedule) is called with the new schedule. DayRatesPanel prepends the new schedule to the list, expands it, and selects its draft revision.
The form disables double-submit via a busyRef. API errors are surfaced via safeApiMessage().
A dirty-close guard prompts the admin before closing the dialog when any field has changed from its default.

