Add a dayTintProvider plugin hook

Lets a plugin colour-code days in the Plan sidebar. trip-segments can
already announce "Kanazawa begins" in the day plan, but it can't make
day 12 look like it belongs to Kanazawa.

dayScheduleProvider can't cover this: it renders a fixed row, and it's
capped at 60 items per provider, so one item per day would tint the
first 60 days of a six-month trip and silently stop. This hook is
bounded by day count instead.

GET /api/day-tints/:tripId returns per-day tones for three regions of a
day card - the number badge (also the mobile day chip), the header row,
and the expanded activity list. `tone` is a shorthand filling every
region a contribution doesn't name; unnamed regions render as they do
today. Tones only, never hex, so the host can pick the alpha per theme
and per region.

One contribution per day, resolved whole: first entry wins within a
provider, first granted provider wins across plugins.
This commit is contained in:
Renzo Beux
2026-07-29 12:05:31 -03:00
committed by Maurice
parent 28ffc814c0
commit c89d2bf3a1
46 changed files with 664 additions and 17 deletions
+19
View File
@@ -687,6 +687,21 @@ export interface PluginDayScheduleItem {
tone: 'default' | 'success' | 'warn' | 'danger';
}
/** The colours a dayTintProvider plugin puts into one day card, so leg membership is
* visible while scrolling the itinerary. The card has three separately tintable
* regions; an absent one is not tinted and renders exactly as it does with no plugin.
* Server-normalized: dayIds checked against the trip, one contribution per day (first
* granted provider wins), the `tone` shorthand already resolved into the regions,
* labels sanitized + capped. Tone only — the client picks the alpha per theme. */
export type PluginDayTintTone = 'default' | 'success' | 'warn' | 'danger'
export interface PluginDayTint {
pluginId: string; dayId: number;
badgeTone?: PluginDayTintTone;
headerTone?: PluginDayTintTone;
activityTone?: PluginDayTintTone;
label?: string;
}
/** A route computed by a routeProvider plugin (server-normalized: coordinates
* range-checked, legs forced to waypoints-1, vias capped). null = provider failed
* or refused — the caller falls back to straight lines like on an OSRM outage. */
@@ -754,6 +769,10 @@ export const pluginsApi = {
// hook (charging stops, security buffers). Host-normalized; fail-safe.
daySchedule: (tripId: number | string) =>
apiClient.get(`/day-schedule/${tripId}`).then(r => r.data as { items: PluginDayScheduleItem[] }),
// Per-day colours plugins put behind the day cards via the dayTintProvider hook
// (which leg of the trip a day belongs to). Host-normalized; fail-safe.
dayTints: (tripId: number | string) =>
apiClient.get(`/day-tints/${tripId}`).then(r => r.data as { tints: PluginDayTint[] }),
// Text-only sections plugins append to the trip PDF export via the
// pdfSectionProvider hook. Host-normalized (counts + lengths capped); fail-safe.
pdfSections: (tripId: number | string) =>