import type { BreakdownItem } from "./breakdown"; import { type ColorName } from "./color_tokens"; /** DEADLINES — the countdown vocabulary: how many days are left, what to CALL * that number, and how loud it reads. All three are contracts, not layout. */ /** What a deadline's proximity means. Directly usable as a `Text` `color`; * `deadlineColor` is the badge/dot side, which keeps hue nuance. */ export type DeadlineTone = "danger" | "warning" | "muted"; /** Where "act now" and "act soon" start, in whole days. The defaults (1 / 3) * come from freight, where a missed cut-off costs a sailing and demurrage * accrues inside three days. */ export interface DeadlineThresholds { /** ≤ this many days out reads `danger`. Default 1 (today and tomorrow). */ dangerWithinDays?: number; /** ≤ this many days out reads `warning`. Default 3. */ warningWithinDays?: number; } /** The four things a countdown can say. Resolved from `LoticsLocale.deadline` * (English defaults, Vietnamese pack shipped) or passed per instance. */ export interface DeadlineLabels { /** Past due, given the count as a POSITIVE number of days. */ overdue: (days: number) => string; /** Due today — the count is 0, and "0 days left" is not what a reader wants. */ today: string; /** Due tomorrow. Named for the same reason as `today`. */ tomorrow: string; /** Any distance further out, in whole days. */ inDays: (days: number) => string; } /** A milestone resolved against a clock: what it is, when, how far off. */ export interface Deadline { label: string; date: Date; /** Calendar days from now — NEGATIVE when the date has passed. */ days: number; } /** Whole CALENDAR days until `due`, negative once it has passed. Both ends * collapse to midnight first: diffing the raw instants and rounding lands on the * wrong day whenever `now` and the deadline sit on opposite sides of noon, and * carriers really do set cut-offs at 02:00. */ export declare function daysUntil(due: Date, now: Date): number; /** How loud a day-count reads. Overdue folds into `danger` — a missed deadline * and one due today both mean ACT NOW, and the LABEL is what distinguishes * them. */ export declare function deadlineTone(days: number, thresholds?: DeadlineThresholds): DeadlineTone; /** The same decision as a `ColorName`, for a `Status` / `Status variant="dot"` * where hue nuance survives. `muted` becomes `zinc`: a deadline comfortably out * is a standing fact. */ export declare function deadlineColor(days: number, thresholds?: DeadlineThresholds): ColorName; /** A day-count as words. 0 and 1 get their own words because a countdown that * renders them arithmetically ("0 days left") reads as broken. */ export declare function countdownLabel(days: number, labels: DeadlineLabels): string; /** * The countdown as a FIELD ANNOTATION rather than a trailing badge — spread it * onto the `DetailRow` holding the date. The three annotation slots carry the * three urgency levels: past due or due today is `error`, the warning window is * `warning`, and anything further out is the standing `description`. * * **Spread it BEFORE the row's own `description`, never after.** Outside the * warning window this returns `{ description }`, so spreading it last silently * overwrites a `description=` the caller wrote — and only on the NON-urgent rows. */ export declare function deadlineAnnotation(days: number, labels: DeadlineLabels, thresholds?: DeadlineThresholds): { description?: string; warning?: string; error?: string; }; /** The nearest of several milestones. Undated candidates drop out and the rest * sort by date, so an OVERDUE milestone comes back FIRST — which means the * CALLER passes only the milestones still OPEN: done-ness is not knowable from a * date, and one left in after it was satisfied dominates this answer forever. */ export declare function nearestDeadline(candidates: ReadonlyArray<{ label: string; date: Date | null | undefined; }>, now: Date): Deadline | null; /** WHAT AN AGEING BAND IS CALLED. Resolved from `LoticsLocale.ageing`, or passed * per instance — the same contract as {@link DeadlineLabels}. */ export interface AgeingLabels { /** Nothing is owed yet — the amount whose date has not passed. */ current: string; /** A closed band, in whole days past due: `(1, 30)` → "1–30 days". */ band: (from: number, to: number) => string; /** The open tail past the last edge: `(90)` → "90+ days". */ beyond: (from: number) => string; } /** WHERE THE BANDS FALL, in whole days past due. Thirty / sixty / ninety is what * a receivables ledger is aged by; a faster trade passes its own edges. */ export declare const AGEING_EDGES: readonly number[]; /** * AN AMOUNT AGED BY HOW LATE IT IS — the bands a `Breakdown` draws above a * receivables register, each already named and already coloured. The reading is * severity, so the ramp runs one way and in ONE hue (`ramp`, strongest at the * oldest band), and what is not yet due is the neutral. * * EVERY BAND IS RETURNED, including the empty ones — "nothing in 61–90" is an * answer. And WHAT IS NOT YET DUE IS A BAND, not an exclusion, which is what * makes the BAR a reading: overdue bands alone are full at every input. * * ITEMS ARE DATED BY CONSTRUCTION: an amount whose date nobody recorded has no * age, and both ways to fold it in are a lie, so the caller decides. */ export declare function ageingBands(items: ReadonlyArray<{ amount: number; due: Date; }>, now: Date, labels: AgeingLabels, edges?: readonly number[]): BreakdownItem[];