Files
accounted/lib/invoices/invoice-list-row-tone.ts
T
0016732232 feat(invoices): let the invoice list read by row tone without the status column (#2353)
* feat(invoices): let the invoice list read by row tone without the status column

A user asked for green/yellow/red row tints in the fakturalista (#2215) so
the list can be read without the last column. The need is real; the tint
is not the answer the design system already gives: status colours are
data, not chrome (convention 12), and DECISIONS 2026-08-03 already ruled
out row tints for this list.

Settled rows (paid, cancelled, credited) now recede to muted text as a
whole row. Open rows keep the foreground and the overdue chip stays the
one marker on its row, so three row classes read at a glance with zero
new colour: grey is done, dark is waiting for money, dark with an ochre
chip is late. Works in dark mode and the brand themes for free and never
stacks with the hover or selection tints.

The tone lives in a pure lib helper (invoiceRowTone, tested) and one
Record in the page maps tone to class; a row tint or a left-edge bar is
a three-string swap there if the founder prefers colour.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvMaHcTnwAfxzgYD1fGYX1

* docs(decisions): record the first-principles choices of the 2026-09-06 evening issue batch

Ten lines for #2215 #2351 #2218 #2220 #2312 #2184 #2293 #2239 #2331 #2332
(PRs #2353 to #2362), carried by this PR so the other nine branches do not
all touch DECISIONS.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 21:24:28 +02:00

35 lines
1.6 KiB
TypeScript

import type { Invoice } from '@/types'
/**
* Row tone for the customer invoice list (#2215): the list must read without
* the status column. Four tones, three of which the eye separates at a glance:
*
* - settled: the row is done (paid, cancelled, credited) and recedes
* - open: a customer invoice waiting for money (sent, partially paid)
* - overdue: an open customer invoice past its due date
* - none: everything else (drafts, quotes, proformas, delivery notes, unpaid
* credit notes); the status column already marks those with a chip
*
* The guards mirror matchesListTab in the list page: only a real customer
* invoice (document_type 'invoice', not a credit note) counts as open or
* overdue, so a sent proforma or quote never reads as a receivable.
*/
export type InvoiceRowTone = 'settled' | 'open' | 'overdue' | 'none'
export type InvoiceRowToneInput = Pick<Invoice, 'status' | 'credited_invoice_id'> & {
/** Older list rows can lack the column; the list treats a missing value as 'invoice'. */
document_type?: Invoice['document_type'] | null
}
const SETTLED_STATUSES: ReadonlySet<Invoice['status']> = new Set(['paid', 'cancelled', 'credited'])
export function invoiceRowTone(invoice: InvoiceRowToneInput): InvoiceRowTone {
if (SETTLED_STATUSES.has(invoice.status)) return 'settled'
const isCustomerInvoice =
(invoice.document_type || 'invoice') === 'invoice' && !invoice.credited_invoice_id
if (!isCustomerInvoice) return 'none'
if (invoice.status === 'overdue') return 'overdue'
if (invoice.status === 'sent' || invoice.status === 'partially_paid') return 'open'
return 'none'
}