KQ-1655 · PAGER/ROTA

Pager Rota

An on-call week drawn as a dial — Monday at twelve o'clock, the week running clockwise, one ring per layer cut into wedges in each engineer's colour — with a now-hand, who is on call now, next and after that, and the selected shift's Page, Override and Swap. Adding an override opens a notch in the ring and the new wedge, in the covering engineer's colour, slides into it from 18px out along its bisector on springs.snap; when now moves the hand ticks there on springs.snap, unwrapped so it never runs backwards round the week, and time nobody covers is hatched and flagged. The dial is a grid with one tab stop: Left and Right walk the week, Up and Down change layer, Home and End jump, O overrides, S swaps and P pages, and narrow containers get a linear schedule with the same keys.

KQ-1655 · PAGER/ROTA

Gaugeworks platform on call

Week of Mon 12 · 3 layers

Wed 10:00

Left and Right move through the week, Up and Down change layer. O adds an override, S swaps with the next shift, P pages.

IM
TA
ML
NH
TA
ML
IM
DK
JF
Primary on call
Ravi OkonjoPrimary · Wed 09:00–18:00 · 9 hNow

Wed 10:00 · Ravi Okonjo · until Wed 18:00 · 1 gap

Install

One command — the source lands in your repo. Or copy it from the Code tab.

$ pnpm dlx shadcn@latest add @kinetiq/pager-rota

Props

dialStyle
"rings" | "solid" | "line"
default "rings"
How the week is drawn: separate rings with gaps between shifts, rings that touch, or thin arcs with a dot at each handoff.
layerCount
number
default 3
How many layers are shown, outermost first (1–3).
accent
string
default "var(--accent-bright)"
The now-hand, the selection and Override, any CSS colour.
rota / defaultRota
Rota
default — / defaultRota
The rota, controlled or initial: { people: { id, name, color }[], layers: { id, label, shifts }[], overrides }.
onRotaChange
(rota: Rota) => void
default —
Fires from the override or swap that changed the rota, with the whole rota.
onOverride
(override: RotaOverride) => void
default —
An override was added: { id, layer, person, start, end }.
onSwap
(a: RotaShift, b: RotaShift) => void
default —
Two shifts traded people; a and b as they are after the swap.
onPage
(person: RotaPerson, segment: RotaSegment) => void
default —
Page was pressed (or P) for the person on the selected segment.
now
number
default defaultRotaNow
The current time in ms; the hand points at it. Never read from the clock.
weekStart
number
default defaultRotaWeekStart
Monday 00:00 of the week drawn, in ms.
selected / defaultSelected
string | null
default — / the primary segment at now
The selected segment id, controlled or initial.
onSelectedChange
(id: string) => void
default —
Fires from the key or press that moved the selection.
status
"ready" | "loading" | "error"
default "ready"
Whether the rota has loaded. Loading draws an empty dial; error adds a Retry banner.
onRetry
() => void
default —
Retry was pressed after the rota failed to load.
team
string
default "Gaugeworks platform"
The team, in the header and the grid's name.
height
number
default 540
Height of the rota in px; its body scrolls inside it.
disabled
boolean
default false
Nothing can be overridden, swapped or paged.
className
string
default —
Classes for the outer surface.