From b32f7c32c8b4a60ed562969bf80a45745b360c25 Mon Sep 17 00:00:00 2001 From: Thomas Joise Date: Tue, 21 Jul 2026 00:33:38 +0530 Subject: [PATCH] fix(client-detail): make the linked payment/document reference legible The status-line reference under a done step was `white-space: nowrap` + `overflow: hidden` inside a 104px node, so the linked payment's amount -- the only reason the chip exists -- was ellipsised away, and the `title` that could have revealed it held static text instead. - the reference now wraps (up to two short lines) rather than clipping; it grows the node downwards only, so the track keeps its 112px columns and its horizontal scroll on narrow screens is unchanged - `title` + `aria-label` now carry the real detail: payment date, amount and reference / document type, number, date and payable - the document chip (quotation_sent), which clipped the same way, is resolved against the ledger too and shows the document number instead of the static "view document ->" Co-Authored-By: Claude Opus 4.8 (1M context) --- apps/hq-web/src/app.css | 10 ++++-- apps/hq-web/src/pages/ClientDetail.tsx | 42 +++++++++++++++++++------- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/apps/hq-web/src/app.css b/apps/hq-web/src/app.css index a1f0995..477e20e 100644 --- a/apps/hq-web/src/app.css +++ b/apps/hq-web/src/app.css @@ -516,11 +516,15 @@ table.wf tbody tr[role='button']:focus-visible td:first-child { box-shadow: inse .cdr-node.done .nm { color: var(--text-dim); } .cdr-node.now .nm { font-weight: 600; } .cdr-node .dt { font-size: 10px; color: var(--text-dim); font-family: var(--mono); margin-top: 3px; } -/* linked payment/document reference under a done step's date */ +/* Linked payment/document reference under a done step's date. It WRAPS (up to two short + lines) instead of being ellipsised to one 104px line: the reference exists to show the + payment's amount / the document's number, and a single mono line at this width cut + exactly that off. Wrapping grows the node downwards only, so the track keeps its 112px + columns and its horizontal scroll on narrow screens. */ .cdr-node .ref { font-size: 10px; font-family: var(--mono); color: var(--accent); background: none; border: none; - padding: 0; margin-top: 3px; cursor: pointer; max-width: 104px; overflow: hidden; - text-overflow: ellipsis; white-space: nowrap; + padding: 0; margin-top: 3px; cursor: pointer; max-width: 104px; + white-space: normal; overflow-wrap: anywhere; line-height: 1.3; text-align: center; } .cdr-node .ref:hover { text-decoration: underline; } /* outstanding panel */ diff --git a/apps/hq-web/src/pages/ClientDetail.tsx b/apps/hq-web/src/pages/ClientDetail.tsx index 1400c96..2bf7e0a 100644 --- a/apps/hq-web/src/pages/ClientDetail.tsx +++ b/apps/hq-web/src/pages/ClientDetail.tsx @@ -345,6 +345,7 @@ export function ClientDetail() { name={moduleName(cm.moduleId)} refreshToken={milestonesVersion} payments={ledger.data?.payments ?? []} + docs={ledger.data?.documents ?? []} onTickPayment={(key, label) => { setLinkError(undefined); setPaymentPicker({ clientModuleId: cm.id, key, label }) }} onTickCreds={(key, label) => { setCredsError(undefined); setCredsDialog({ cm, key, label }) }} onTickDocument={(key, label) => { setDocLinkError(undefined); setDocPicker({ clientModuleId: cm.id, key, label }) }} @@ -1427,7 +1428,10 @@ function SecretField(props: { cm: ClientModule; field: FieldDef; managerial: boo * Un-ticking any of them toggles directly (the server clears the link). * A done step carrying `paymentId`/`documentId` also renders a small reference under its * date (`onOpenDocument` / `onOpenPayments`) — otherwise a linked step looks identical to a - * bare-toggled one. + * bare-toggled one. That reference is resolved against the client's ledger (`payments` / + * `docs`) so it reads as the real date + amount / document number, wraps to a second line + * inside the node rather than being ellipsised, and carries the full detail in its + * tooltip + accessible name. * "+ Add step" appends a project-specific milestone. Every change is one audited POST * that returns the fresh list; `refreshToken` lets the parent force a refetch after a * link lands from outside this component. `onChanged` additionally reloads the parent's @@ -1437,7 +1441,10 @@ function SecretField(props: { cm: ClientModule; field: FieldDef; managerial: boo */ function StatusLine(props: { clientModuleId: string; name: string; refreshToken: number + /** The client's ledger — used to render the linked payment / document by its real + * date + amount (and to fill the reference's tooltip), not a generic "linked" label. */ payments: Payment[] + docs: Doc[] onTickPayment: (key: string, label: string) => void onTickCreds: (key: string, label: string) => void onTickDocument: (key: string, label: string) => void @@ -1547,20 +1554,33 @@ function StatusLine(props: { {m.label} {m.done ? (m.doneOn ?? '—') : m.key === currentKey ? 'now' : '—'} {/* The linked payment/document itself — otherwise a done payment/quotation - step shows only a date, with no evidence the link actually landed. */} - {m.done && m.documentId !== null && ( - - )} + step shows only a date, with no evidence the link actually landed. The + reference wraps inside the node (it used to be ellipsised to one 104px + line, which cut the amount — the one thing it exists to show), and the + tooltip/accessible name carries the full detail rather than static text. */} + {m.done && m.documentId !== null && (() => { + const d = props.docs.find((x) => x.id === m.documentId) + const detail = d === undefined + ? 'Linked document — open it' + : `${docLabel(d)} ${d.docNo ?? '(draft)'} · ${d.docDate} · ${inr(d.payablePaise)} — open it` + return ( + + ) + })()} {m.done && m.paymentId !== null && (() => { const p = props.payments.find((x) => x.id === m.paymentId) + const detail = p === undefined + ? 'Linked payment — open Payments & plans' + : `Payment ${p.receivedOn} · ${inr(p.amountPaise)}` + + `${p.reference !== '' ? ` · ${p.reference}` : ''} — open Payments & plans` return (