// invoice app — sidebar shell, invoice list, invoice detail const { Icon, Button, StatusBadge, Avatar, kr, useState } = window; function Sidebar() { const nav = [ { icon: 'file', label: 'Invoices', active: true }, { icon: 'users', label: 'Clients' }, { icon: 'chart', label: 'Reports' }, { icon: 'settings', label: 'Settings' }, ]; const apps = [['inv', 'invoice', true], ['ts', 'timesheet', false], ['re', 'reimburse', false]]; return ( ); } function InvoiceList({ onOpen }) { const { invoices } = window.INVOICE_DATA; const [q, setQ] = useState(''); const list = invoices.filter(i => (i.id + i.client).toLowerCase().includes(q.toLowerCase())); const outstanding = invoices.filter(i => i.status === 'sent' || i.status === 'overdue').reduce((s, i) => s + i.amount, 0); const paid = invoices.filter(i => i.status === 'paid').reduce((s, i) => s + i.amount, 0); return (

Invoices

Outstandingkr {kr(outstanding)}
Paid this periodkr {kr(paid)}
Open invoices{invoices.filter(i => i.status !== 'paid' && i.status !== 'draft').length}
setQ(e.target.value)} placeholder="Search invoices or clients…" />
{list.map(inv => ( onOpen(inv)}> ))}
InvoiceClientStatusDueAmount
{inv.id} {inv.client} {inv.due} kr {kr(inv.amount)}
); } function InvoiceDetail({ inv, onBack }) { const { from } = window.INVOICE_DATA; const subtotal = inv.items.reduce((s, [, qty, rate]) => s + qty * rate, 0); const vat = Math.round(subtotal * 0.25); const total = subtotal + vat; return (
{inv.status !== 'paid' && } {inv.status === 'draft' ? : }
{inv.id}
{from.org} {from.name} {from.email} {from.orgnr}
Billed to
{inv.client}
Issued
{inv.issued}
Due
{inv.due}
{inv.items.map(([desc, qty, rate], i) => ( ))}
DescriptionQtyRateAmount
{desc}{qty}kr {kr(rate)}kr {kr(qty * rate)}
Subtotalkr {kr(subtotal)}
VAT 25%kr {kr(vat)}
Totalkr {kr(total)}
); } function App() { const [inv, setInv] = useState(null); return (
{inv ? setInv(null)} /> : }
); } ReactDOM.createRoot(document.getElementById('root')).render();