Draw the rota as shift bars, and the escalation chain as a ladder
The rota put an initial on every day, so one person covering a week was seven identical chips and a legend to decode them. A shift is now one bar across its days with the person's name on it; a flat end with a chevron means it carries on across the row break. Days with nobody on call are a hatched amber bar while they can still be fixed and a quiet grey one once they are history, and the footer is a green banner when the month is covered or an amber one when it is not. Tapping a day still opens it: the bars ignore the pointer, so the tap reaches the cell underneath. The month title was monospace because its button borrowed .label, which belongs to label chips; it has its own class now, and a Today button sits beside the arrows. The escalation card was a five-column table. It is now the ladder it describes: a numbered step per level with its status and who it pages, the wait before the next level on its own line, and what happens after the last one at the bottom. With no fallback topic that last step is an amber callout, with an Add fallback button for owners that opens the editor on the field. That gap is what incident #36 hit. Web UI only: no endpoint or JSON shape changed, so nothing to mirror in terdut-tui.
This commit is contained in:
+94
-19
@@ -718,7 +718,7 @@ details[open] > summary { margin-bottom: 8px; }
|
||||
.you-badge { margin-left: 6px; }
|
||||
|
||||
.week-nav { display: flex; align-items: center; gap: 4px; }
|
||||
.week-nav .label { font-size: 14px; font-weight: 650; min-width: 9em; text-align: center; }
|
||||
.week-nav .month-label { font-size: 15px; font-weight: 650; min-width: 8em; text-align: center; }
|
||||
/* The week-nav button that shows the date range, "28 Sep – 4 Oct", with the
|
||||
ISO week number as secondary text inside it — a separate class from
|
||||
.label above (team.js's month-nav uses that one) so its <small> isn't
|
||||
@@ -999,23 +999,32 @@ kbd {
|
||||
too alike down a column to read, so a day carries an initial in that
|
||||
person's colour and the legend underneath says whose. A shift is then a run
|
||||
of one colour, which is the shape the question actually has. */
|
||||
.rota-grid { display: grid; grid-template-columns: 2.4em repeat(7, 1fr); gap: 2px; padding: 10px; }
|
||||
.rota-grid { padding: 10px 12px 4px; }
|
||||
.rota-heads, .rota-row { display: grid; grid-template-columns: 2.4em repeat(7, minmax(0, 1fr)); column-gap: 2px; }
|
||||
.rota-wd {
|
||||
padding-bottom: 4px; text-align: center;
|
||||
padding-bottom: 6px; text-align: center;
|
||||
color: var(--muted); font-size: 11px; font-weight: 700;
|
||||
text-transform: uppercase; letter-spacing: 0.04em;
|
||||
}
|
||||
.rota-row { grid-template-rows: 26px 34px; padding: 4px 0 6px; }
|
||||
.rota-row + .rota-row { border-top: 1px solid var(--border); }
|
||||
/* A day is a cell that spans both of its row's lines: the number on the first,
|
||||
the shift bar drawn over the second. The bar ignores the pointer, so a tap
|
||||
anywhere in the cell reaches the button. */
|
||||
.rota-day {
|
||||
display: flex; flex-direction: column; align-items: center; gap: 4px;
|
||||
min-height: 52px; padding: 6px 0 8px;
|
||||
grid-row: 1 / span 2;
|
||||
display: flex; justify-content: center; align-items: flex-start;
|
||||
min-width: 0; padding-top: 2px;
|
||||
border: 0; border-radius: var(--radius-sm); background: none;
|
||||
font: inherit; color: inherit;
|
||||
}
|
||||
button.rota-day { cursor: pointer; }
|
||||
button.rota-day:hover { background: var(--surface-2); }
|
||||
button.rota-day:focus-visible, button.rota-week:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
|
||||
/* The week number starts each row. Quiet by default, because it is a label
|
||||
first; an owner's tap on it is the second thing it does. */
|
||||
.rota-week {
|
||||
grid-column: 1; grid-row: 1 / span 2;
|
||||
display: grid; place-items: center;
|
||||
border: 0; border-radius: var(--radius-sm); background: none;
|
||||
font: inherit; font-size: 12px; font-variant-numeric: tabular-nums;
|
||||
@@ -1023,18 +1032,43 @@ button.rota-day:hover { background: var(--surface-2); }
|
||||
}
|
||||
button.rota-week { cursor: pointer; }
|
||||
button.rota-week:hover { background: var(--surface-2); color: var(--text); }
|
||||
.rota-week.current { color: var(--accent); font-weight: 700; }
|
||||
.rota-week.current { color: var(--text); font-weight: 700; }
|
||||
/* The sheet's row of who holds each day of the week. */
|
||||
.week-holders { display: flex; justify-content: space-between; gap: 4px; margin: 4px 0 12px; }
|
||||
.week-holder { display: flex; flex-direction: column; align-items: center; gap: 4px; flex: 1; }
|
||||
.week-holder.past { opacity: 0.55; }
|
||||
.rota-num { color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; }
|
||||
.rota-day.today { background: var(--accent-soft); }
|
||||
.rota-day.today .rota-num { color: var(--accent); font-weight: 700; }
|
||||
.rota-day.past { opacity: 0.55; }
|
||||
.rota-num {
|
||||
min-width: 22px; height: 22px; padding: 0 3px; border-radius: 11px;
|
||||
text-align: center; line-height: 22px;
|
||||
color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums;
|
||||
}
|
||||
/* Today is a filled number, a marker and not the accent, which means "you can
|
||||
act on this". */
|
||||
.rota-day.today .rota-num { background: var(--text); color: var(--bg); font-weight: 700; }
|
||||
/* The days either side of the month are real days and are drawn, but they
|
||||
belong to the month you are not looking at. */
|
||||
.rota-day.outside { opacity: 0.35; }
|
||||
.rota-day.outside .rota-num { opacity: 0.4; }
|
||||
|
||||
/* A shift: a run of consecutive days held by one person, in that person's
|
||||
colour (.rc1-.rc6 below). Rounded ends are where it really starts or stops;
|
||||
a flat end with a chevron carries on across the row break. */
|
||||
.rota-bar {
|
||||
grid-row: 2; align-self: center; z-index: 1; pointer-events: none;
|
||||
display: flex; align-items: center; min-width: 0; height: 28px; padding: 0 8px;
|
||||
border-radius: 6px;
|
||||
font-size: 13px; font-weight: 650; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||
}
|
||||
.rota-bar.past { opacity: 0.55; }
|
||||
.rota-bar.outside { opacity: 0.4; }
|
||||
.rota-bar.cont-l { margin-left: -2px; padding-left: 10px; border-top-left-radius: 0; border-bottom-left-radius: 0; }
|
||||
.rota-bar.cont-r { margin-right: -2px; border-top-right-radius: 0; border-bottom-right-radius: 0; }
|
||||
.rota-bar.cont-l::before { content: "‹"; margin-right: 4px; opacity: 0.7; }
|
||||
/* Nobody on call. Loud while it can still be fixed, quiet once it is history. */
|
||||
.rota-bar.gap {
|
||||
background: repeating-linear-gradient(135deg, transparent 0 5px, var(--warn-soft) 5px 10px);
|
||||
border: 1px dashed var(--warn); color: var(--warn);
|
||||
}
|
||||
.rota-bar.gap.past { background: none; border-color: var(--border-strong); color: var(--faint); }
|
||||
|
||||
.rota-chip {
|
||||
display: grid; place-items: center;
|
||||
@@ -1054,14 +1088,18 @@ button.rota-week:hover { background: var(--surface-2); color: var(--text); }
|
||||
.rc5 { background: var(--teal-soft); color: var(--teal); }
|
||||
.rc6 { background: var(--pink-soft); color: var(--pink); }
|
||||
|
||||
.rota-foot { padding: 12px 14px; border-top: 1px solid var(--border); }
|
||||
.rota-legend { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 14px; font-size: 14px; }
|
||||
.rota-key { display: inline-flex; align-items: center; gap: 6px; }
|
||||
.rota-key .rota-chip { width: 22px; height: 22px; font-size: 11px; }
|
||||
.rota-note { margin: 10px 0 0; color: var(--muted); font-size: 13px; }
|
||||
.rota-note:first-child { margin-top: 0; }
|
||||
.rota-bulk { padding: 12px 14px; border-top: 1px solid var(--border); }
|
||||
.rota-bulk .stacked-form { margin-top: 4px; }
|
||||
.rota-banner {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
padding: 12px 14px; border-top: 1px solid var(--border); font-size: 14px;
|
||||
}
|
||||
.rota-banner .icon { width: 20px; height: 20px; }
|
||||
.rota-banner.ok { color: var(--ok); }
|
||||
.rota-banner.warn { color: var(--warn); background: var(--warn-soft); }
|
||||
.rota-foot {
|
||||
display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap;
|
||||
padding: 12px 14px; border-top: 1px solid var(--border);
|
||||
}
|
||||
.rota-bulk-form { padding: 0 14px 14px; }
|
||||
.sheet-pick { display: flex; align-items: center; gap: 8px; font-size: 14px; }
|
||||
|
||||
.ladder-level {
|
||||
@@ -1074,6 +1112,43 @@ button.rota-week:hover { background: var(--surface-2); color: var(--text); }
|
||||
.ladder-editor { display: flex; flex-direction: column; gap: 10px; align-items: flex-start; margin-top: 12px; }
|
||||
/* A target that would not wake anybody says why, in place: it is the reason a
|
||||
level is red, and the thing to go and fix. */
|
||||
/* The escalation ladder: a numbered step per level, the wait between levels as
|
||||
its own line, and what happens after the last one at the bottom. */
|
||||
.ladder { list-style: none; margin: 16px 0 0; padding: 0; }
|
||||
.step { display: grid; grid-template-columns: 32px minmax(0, 1fr); column-gap: 14px; }
|
||||
.step-rail { display: flex; flex-direction: column; align-items: center; }
|
||||
.step-node {
|
||||
display: grid; place-items: center; flex: none;
|
||||
width: 32px; height: 32px; border-radius: 50%;
|
||||
border: 2px solid var(--border-strong); background: var(--surface);
|
||||
font-size: 14px; font-weight: 750;
|
||||
}
|
||||
.step-node.st-ready { border-color: var(--ok); color: var(--ok); }
|
||||
.step-node.st-escalating { border-color: var(--warn); color: var(--warn); background: var(--warn-soft); }
|
||||
.step-node.st-unreachable { border-color: var(--crit); color: var(--crit); background: var(--crit-soft); }
|
||||
.step-node.end { border-style: dashed; color: var(--muted); }
|
||||
.step-node.end.warn { border-color: var(--warn); color: var(--warn); background: var(--warn-soft); }
|
||||
.step-node .icon { width: 16px; height: 16px; }
|
||||
.step-line { flex: 1; width: 2px; min-height: 8px; background: var(--border-strong); }
|
||||
.step-body { min-width: 0; padding-bottom: 4px; }
|
||||
.step-top { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; min-height: 32px; }
|
||||
.step-pages { display: flex; flex-direction: column; gap: 2px; margin-top: 2px; font-size: 14px; }
|
||||
.step-waiting { margin-top: 6px; font-size: 13px; color: var(--muted); }
|
||||
.step-waiting a { font-weight: 650; }
|
||||
.step-gap { display: flex; align-items: center; gap: 8px; padding: 6px 0 10px; color: var(--muted); font-size: 13px; font-weight: 600; }
|
||||
.step-gap .icon { width: 14px; height: 14px; }
|
||||
.callout {
|
||||
display: flex; align-items: flex-start; gap: 12px; margin-top: 4px;
|
||||
padding: 12px 14px; border: 1px solid var(--warn); border-radius: var(--radius);
|
||||
background: var(--warn-soft);
|
||||
}
|
||||
.callout > .icon { flex: none; width: 20px; height: 20px; margin-top: 1px; color: var(--warn); }
|
||||
.callout strong { color: var(--warn); }
|
||||
.callout p { margin: 2px 0 10px; font-size: 14px; }
|
||||
.ladder-facts { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--border); }
|
||||
.ladder-facts p { margin: 0; }
|
||||
.ladder-facts p + p { margin-top: 4px; }
|
||||
|
||||
.target-line { display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap; }
|
||||
.target-problem { color: var(--crit); font-size: 12px; font-weight: 600; }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user