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) <noreply@anthropic.com>
main
Thomas Joise 1 day ago
parent b1e1420b4c
commit b32f7c32c8

@ -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.done .nm { color: var(--text-dim); }
.cdr-node.now .nm { font-weight: 600; } .cdr-node.now .nm { font-weight: 600; }
.cdr-node .dt { font-size: 10px; color: var(--text-dim); font-family: var(--mono); margin-top: 3px; } .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 { .cdr-node .ref {
font-size: 10px; font-family: var(--mono); color: var(--accent); background: none; border: none; 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; padding: 0; margin-top: 3px; cursor: pointer; max-width: 104px;
text-overflow: ellipsis; white-space: nowrap; white-space: normal; overflow-wrap: anywhere; line-height: 1.3; text-align: center;
} }
.cdr-node .ref:hover { text-decoration: underline; } .cdr-node .ref:hover { text-decoration: underline; }
/* outstanding panel */ /* outstanding panel */

@ -345,6 +345,7 @@ export function ClientDetail() {
name={moduleName(cm.moduleId)} name={moduleName(cm.moduleId)}
refreshToken={milestonesVersion} refreshToken={milestonesVersion}
payments={ledger.data?.payments ?? []} payments={ledger.data?.payments ?? []}
docs={ledger.data?.documents ?? []}
onTickPayment={(key, label) => { setLinkError(undefined); setPaymentPicker({ clientModuleId: cm.id, key, label }) }} onTickPayment={(key, label) => { setLinkError(undefined); setPaymentPicker({ clientModuleId: cm.id, key, label }) }}
onTickCreds={(key, label) => { setCredsError(undefined); setCredsDialog({ cm, key, label }) }} onTickCreds={(key, label) => { setCredsError(undefined); setCredsDialog({ cm, key, label }) }}
onTickDocument={(key, label) => { setDocLinkError(undefined); setDocPicker({ clientModuleId: cm.id, 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). * 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 * A done step carrying `paymentId`/`documentId` also renders a small reference under its
* date (`onOpenDocument` / `onOpenPayments`) otherwise a linked step looks identical to a * 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 * "+ 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 * 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 * 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: { function StatusLine(props: {
clientModuleId: string; name: string; refreshToken: number 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[] payments: Payment[]
docs: Doc[]
onTickPayment: (key: string, label: string) => void onTickPayment: (key: string, label: string) => void
onTickCreds: (key: string, label: string) => void onTickCreds: (key: string, label: string) => void
onTickDocument: (key: string, label: string) => void onTickDocument: (key: string, label: string) => void
@ -1547,20 +1554,33 @@ function StatusLine(props: {
<span className="nm">{m.label}</span> <span className="nm">{m.label}</span>
<span className="dt">{m.done ? (m.doneOn ?? '—') : m.key === currentKey ? 'now' : '—'}</span> <span className="dt">{m.done ? (m.doneOn ?? '—') : m.key === currentKey ? 'now' : '—'}</span>
{/* The linked payment/document itself otherwise a done payment/quotation {/* The linked payment/document itself otherwise a done payment/quotation
step shows only a date, with no evidence the link actually landed. */} step shows only a date, with no evidence the link actually landed. The
{m.done && m.documentId !== null && ( 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 (
<button <button
type="button" className="ref" title="Open the linked document" type="button" className="ref" title={detail} aria-label={detail}
onClick={() => props.onOpenDocument(m.documentId!)} onClick={() => props.onOpenDocument(m.documentId!)}
> >
view document {d === undefined ? 'view document' : d.docNo ?? `${docLabel(d)} (draft)`}
</button> </button>
)} )
})()}
{m.done && m.paymentId !== null && (() => { {m.done && m.paymentId !== null && (() => {
const p = props.payments.find((x) => x.id === m.paymentId) 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 ( return (
<button <button
type="button" className="ref" title="The linked payment — open Payments & plans" type="button" className="ref" title={detail} aria-label={detail}
onClick={() => props.onOpenPayments()} onClick={() => props.onOpenPayments()}
> >
{p !== undefined ? `${p.receivedOn} · ${inr(p.amountPaise)}` : 'payment linked'} {p !== undefined ? `${p.receivedOn} · ${inr(p.amountPaise)}` : 'payment linked'}

Loading…
Cancel
Save