feat(kpi): Nyckeltal as Instrumentbrädan — instrument panes, monthly bars, cost list (#1148)

* feat(kpi): Nyckeltal as Berattelsen (serif month hero + metric rail + quiet cost rows)

The founder-picked concept variant: the month's result as a serif hero
with a +/- delta sentence against the previous month, a single sage net
area chart (income/expenses ride in the hover tooltip), and a hairline
metric rail on the right still driven by the user's KPI preferences
(Anpassa, formula tooltips, all seven definitions supported). The cost
story renders as quiet bar rows: expense classes 4xxx-7xxx and top five
suppliers. Replaces the four-tile + three-Recharts-card layout;
KPIHeroCards, KPITrendChart, KPIExpenseMixChart and KPITopSuppliersChart
are deleted. FyPicker replaces FiscalYearSelector; help behind ?.
No API changes: everything derives from the existing KPIReport.

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

* feat(kpi): switch Nyckeltal to Instrumentbradan (founder pick v2)

Berattelsen replaced by the instrument-pane grid on founder review:
monthly result bars as plain SVG (muted months, latest in sage or
terracotta when negative, compact endpoint label, per-bar tooltips)
plus one bordered pane per visible preference KPI, with the
receivables pane carrying a two-segment not-due/overdue strip. The
cost story rows below are unchanged. Recharts leaves this page
entirely (KPIResultChart deleted). Anpassa, formula tooltips and all
seven KPI definitions still supported.

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

* feat(kpi): concept-true cost list and cash runway note

Founder review against the concept: the report now carries
topExpenseAccounts (top five BAS 4-7 accounts for the period, computed
from the trial-balance rows the route already holds) and the page
renders them as the full-width Storsta kostnaderna rows with account
numbers, exactly like the concept. The Kassa pane derives its 'Tacker
cirka N dagars utgifter' note from the period's daily burn so far.
Class-composition and supplier columns leave the UI (data stays on the
API). Route test extended for the new field.

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-24 16:43:42 +02:00
committed by GitHub
co-authored by Claude Fable 5
parent 6911f657e9
commit d0fb72dc63
11 changed files with 439 additions and 521 deletions
@@ -179,6 +179,9 @@ describe('GET /api/reports/kpi', () => {
],
period: { start: '2026-01-01', end: '2026-03-31' },
expenseComposition: { class4: 0, class5: 3000, class6: 0, class7: 0 },
topExpenseAccounts: [
{ account_number: '5010', account_name: 'Lokalhyra', total: 3000 },
],
topSuppliers: [
{ supplier_id: 'sup-1', supplier_name: 'Leverantören AB', total: 500 },
{ supplier_id: 'sup-2', supplier_name: 'Andra AB', total: 200 },
+15
View File
@@ -238,6 +238,20 @@ export const GET = withRouteContext('report.kpi', async (request, { supabase, co
{ class4: 0, class5: 0, class6: 0, class7: 0 }
)
// Top expense accounts (classes 4-7) for the period: the concept's
// "Största kostnaderna" list. Same debit-normal reading as the class
// composition above.
const topExpenseAccounts = (filteredTrialBalance ?? trialBalanceResult).rows
.filter((r) => r.account_class >= 4 && r.account_class <= 7)
.map((r) => ({
account_number: r.account_number,
account_name: r.account_name,
total: Math.round((r.closing_debit - r.closing_credit) * 100) / 100,
}))
.filter((r) => r.total > 0)
.sort((a, b) => b.total - a.total)
.slice(0, 5)
// Top suppliers by spend within the fiscal period. Sum total_sek to avoid
// mixing currencies. Drop FX invoices without a SEK conversion (total_sek
// null): they would otherwise inflate a supplier's total with raw
@@ -293,6 +307,7 @@ export const GET = withRouteContext('report.kpi', async (request, { supabase, co
class6: Math.round(expenseComposition.class6 * 100) / 100,
class7: Math.round(expenseComposition.class7 * 100) / 100,
},
topExpenseAccounts,
topSuppliers,
}