Files
Checkmate/client/src/Pages/Dashboard/layout.ts
T
gorkem-bwl f86ead0806 feat(client): add the card picker and the remaining catalogue cards
Completes the catalogue at 17 cards and adds the picker that chooses
between them.

New cards: Slowest monitors, Lowest uptime, Monitors by group, Busiest
servers, Incident stats, Notification channels, Maintenance, Status
pages, Checks on schedule, Recent errors, Team.

Data caveats honoured from the specs:

- Rankings exclude paused monitors. A paused ping monitor reads 0% uptime
  and would otherwise rank beside a genuinely down one; neither is a live
  problem. Rankings also hide below two entries — a ranking of one is not
  a ranking.
- Busiest servers reads cpu/memory/disk from the newest recentChecks
  entry (snake_case, 0-1 fractions) and renders a dash for a down server
  rather than 0%.
- Maintenance compares start/end client-side: `active` is a user on/off
  flag, not 'running now', and repeat > 0 means end may sit in the past
  for a live window.
- Notification channels renders only the type. The response also carries
  accessToken and accountSid; those must never reach the DOM.
- Recent errors is worded 'recent' because /logs is a 1000-entry
  in-memory ring buffer that rotates fast and is wiped on restart.

Also fixes a layout gap the six default cards never exposed: a 6-wide
card followed by an 8-wide one left a lone card stretched to 8 in a
middle row, with 4 empty columns beside it. Mid-page rows now always fill
to 12; only the final row may end short, and the cap applies only there,
so a lone trailing 4-wide card does not become a banner.

Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
2026-08-29 09:37:34 -04:00

58 lines
1.8 KiB
TypeScript

import { GRID_COLUMNS, STRETCH_MAX_WIDTH, type CardDefinition } from "./cards";
export interface PlacedCard {
card: CardDefinition;
/** Columns actually rendered — the declared width, or more if stretched. */
renderedWidth: number;
}
/**
* Packs cards into rows of GRID_COLUMNS. Declared width is a minimum; when a
* row ends short, its last card absorbs the remainder.
*
* A row in the middle of the page always fills completely — leaving a hole
* there reads as a rendering fault. Only the final row may end short, and only
* when a single card sits in it: stretching a lone trailing card to full width
* makes a 4-column card look like a banner. There, STRETCH_MAX_WIDTH caps the
* growth instead.
*
* `grid-auto-flow: dense` was tried instead and does not help: no later card is
* small enough to backfill the gaps that occur in practice.
*/
export const layoutCards = (cards: CardDefinition[]): PlacedCard[] => {
const placed: PlacedCard[] = [];
let row: PlacedCard[] = [];
let used = 0;
const flush = (isFinalRow: boolean) => {
if (row.length === 0) {
return;
}
const remainder = GRID_COLUMNS - used;
if (remainder > 0) {
const last = row[row.length - 1];
// A lone card ending the page grows only to the cap; every other short
// row is filled completely so no gap is left mid-page.
const ceiling = isFinalRow && row.length === 1 ? STRETCH_MAX_WIDTH : GRID_COLUMNS;
last.renderedWidth = Math.min(last.renderedWidth + remainder, ceiling);
}
placed.push(...row);
row = [];
used = 0;
};
for (const card of cards) {
if (used + card.width > GRID_COLUMNS) {
flush(false);
}
row.push({ card, renderedWidth: card.width });
used += card.width;
if (used === GRID_COLUMNS) {
flush(false);
}
}
flush(true);
return placed;
};