feat(invoices): concept customer-invoice list (UI migration PR 7) (#1128)

* feat(invoices): concept scene 15 for the customer invoice list

Kundfakturor becomes the concept dry-table: Nr, Kund, Forfaller, Belopp
and ONE derivable status chip per row (Utkast/Ej skickad/Skickad/Forfallen
N dgr/Betald datum/Delbetald/Kredit/Makulerad). The eight tabs collapse to
the seg [Alla/Obetalda/Forfallna (n)/Utkast] with the low-frequency views
(Proforma, Foljesedlar, Kreditfakturor, Makulerade) behind a quiet
'Fler ...' menu, plus search and the FyPicker far right. Create paths
collapse into the Ny faktura split button (Ny/Aterkommande/Sjalvfaktura,
last-used mode persisted); sjalvfaktura preselects the editor's tab via
?new=1&self=1. The summary line under the header is dropped per the plan.

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

* feat(invoices): status views behind one chip-picker

Founder direction: the Alla/Obetalda/Forfallna/Utkast seg and the
'Fler ...' menu collapse into a single status ContextPicker chip with the
overdue count on the trigger and as a row annotation. All eight views,
same filtering semantics.

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

* fix(invoices): audit round: Betalda view, viewer states, deep links

Regression-audit fixes: the Betalda view returns as a status-picker row
(it existed on main and was dropped in the seg redesign); the create
options render disabled with the viewer tooltip for read-only members
instead of silently no-opping; and the page answers ?status=/?tab= deep
links again (the Att gora worklist links /invoices?status=unpaid).

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-07-23 21:59:46 +02:00
committed by GitHub
co-authored by Claude Fable 5
parent 008b4710d3
commit 323abc57fe
5 changed files with 333 additions and 276 deletions
+9 -2
View File
@@ -81,7 +81,12 @@ export type InvoiceEditorProps = (
| { mode?: 'create' }
| { mode: 'edit'; initial: InvoiceForEdit }
| { mode: 'copy'; initial: InvoiceCopyInitial }
) & { bare?: boolean }
) & {
bare?: boolean
/** Open with the självfaktura tab preselected (the "Självfaktura" entry in
* the invoice list's split button). Create mode only. */
initialSelfBilled?: boolean
}
// Subset of Article fields the line picker needs to pre-fill a row.
type ArticleOption = Pick<
@@ -127,7 +132,9 @@ export default function InvoiceEditor(props: InvoiceEditorProps = { mode: 'creat
// Toggle between a normal customer invoice (default) and registering a
// self-billing invoice we received (mottagen självfaktura, ML 17 kap 15§).
// Self-billing is never available when editing an existing draft.
const [mode, setMode] = useState<'invoice' | 'self_billed'>('invoice')
const [mode, setMode] = useState<'invoice' | 'self_billed'>(
props.initialSelfBilled && !isEditMode ? 'self_billed' : 'invoice',
)
// Company-wide opt-in from the invoice settings page: the whole payment
// link section (manual field + Stripe auto toggle) stays hidden until the
// company enables it. The send routes enforce the same setting server-side
+4 -2
View File
@@ -34,6 +34,8 @@ interface Props {
open: boolean
onOpenChange: (open: boolean) => void
copyFromId?: string | null
/** Preselect the självfaktura tab (split-button entry on /invoices). */
selfBilled?: boolean
}
/**
@@ -47,7 +49,7 @@ interface Props {
* shows the invoice-number preview: a static DialogTitle would duplicate or
* contradict it.
*/
export default function NewInvoiceDialog({ open, onOpenChange, copyFromId = null }: Props) {
export default function NewInvoiceDialog({ open, onOpenChange, copyFromId = null, selfBilled = false }: Props) {
const t = useTranslations('invoice_editor')
const { company } = useCompany()
const supabase = useMemo(() => createClient(), [])
@@ -146,7 +148,7 @@ export default function NewInvoiceDialog({ open, onOpenChange, copyFromId = null
</div>
)
) : (
<InvoiceEditor mode="create" bare />
<InvoiceEditor mode="create" bare initialSelfBilled={selfBilled} />
)}
</DialogContent>
</Dialog>