chore: repo-wide bloat sweep, remove dead code and fold duplicate helpers (#2150)

* chore: repo-wide bloat sweep, remove dead code and fold duplicate helpers

Remove 33 dead files, ~270 unreferenced exports/types, 13 dead i18n
namespaces and 4 unused dependencies; fold byte-identical helper copies
into one canonical home each (lib/utils chunk/sleep/utcDateStamp,
lib/dates/iso, lib/invariants/uuid, lib/xml/escape, lib/reports/sru/format,
lib/pdf/number-text, lib/browser/panel-request, lib/api/v1/body +
v1ValidationError rolled out to ~55 v1 routes, booking-template schemas).

No behaviour change: v1 bodies and status codes, MCP tool schemas, DB
writes and money math are untouched. Naive ore rounding was deliberately
not swapped for roundOre; see DECISIONS.md 2026-09-02 for the full list
of things left alone on purpose.

tsc, lint, 19588 unit tests and check:guards green; antipattern baseline
ratcheted (naive-ore-round 622 -> 620, hand-rolled-invariant 115 -> 113).

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

* test(transactions): import RawTransaction from @/types after the ingest re-export removal

CI's type ratchet (check:types, full tsconfig) caught the one test file
that still imported the type through lib/transactions/ingest.

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>
This commit is contained in:
Jakob Wennberg
2026-09-02 11:51:16 +02:00
committed by GitHub
co-authored by Claude Fable 5.1 Jakob Wennberg
parent 18cbc4c30a
commit f266c386f3
359 changed files with 1796 additions and 11199 deletions
-137
View File
@@ -1,137 +0,0 @@
'use client'
import { useEffect, useRef } from 'react'
import { useCashAccounts } from '@/lib/reference-data/hooks'
import { Label } from '@/components/ui/label'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { useCompany } from '@/contexts/CompanyContext'
const STORAGE_KEY_PREFIX = 'Accounted:cash-account:'
interface Props {
/**
* Current selection: a BAS ledger account number ('1930', '1932', …).
* `null` would only be meaningful if "all accounts" were an option, which
* isn't currently supported (reconciliation is always single-account).
*/
value: string
onChange: (accountNumber: string) => void
/**
* Optional label above the select. Pass null to render without a label.
*/
label?: string | null
/**
* Called once after the initial fetch completes so callers can suppress a
* skeleton until the selector is ready.
*/
onReady?: () => void
className?: string
}
/**
* Cash account selector for reconciliation, drift, and any UI that scopes a
* read to a particular settlement account (1930 SEK, 1932 EUR, …).
*
* Loads /api/cash-accounts for the active company, persists the last selection
* per company in sessionStorage, and renders the same Select primitive as the
* fiscal-year picker so the UX stays consistent.
*
* sessionStorage (not localStorage) so the selection clears when the tab/
* session ends. The data is a UI preference, not a credential; persisting
* which BAS account a company uses across sessions in browser storage would
* couple company id + financial account reference for the lifetime of the
* browser profile (GDPR Art. 25(2) data minimisation, ISO 27001 A.8.12).
*/
export function CashAccountSelector({
value,
onChange,
label = 'Konto',
onReady,
className,
}: Props) {
const { company } = useCompany()
// Session-cached and seeded by the dashboard layout (lib/reference-data):
// on a normal visit the list is here on the first render, so the restore
// below runs in the first effect tick and onReady fires without a round
// trip. Restore once per company load, not on every background refresh.
const { cashAccounts: accounts, isLoading } = useCashAccounts()
const loaded = !isLoading
const restoredForRef = useRef<string | null>(null)
useEffect(() => {
if (!company?.id) {
onReady?.()
return
}
if (!loaded || restoredForRef.current === company.id) return
restoredForRef.current = company.id
// Restore last selection or pick the primary as default.
if (typeof window !== 'undefined') {
const stored = window.sessionStorage.getItem(STORAGE_KEY_PREFIX + company.id)
const inFetched = (ledger: string) =>
accounts.some(a => a.ledger_account === ledger)
if (stored && inFetched(stored)) {
if (stored !== value) onChange(stored)
} else {
const primary = accounts.find(a => a.is_primary)
const fallback = primary ?? accounts[0]
if (fallback && fallback.ledger_account !== value) {
onChange(fallback.ledger_account)
}
}
}
onReady?.()
// onReady/onChange are lifecycle callbacks: fire once per load, not on
// parent re-renders that re-create them. `value` is read once at restore.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [company?.id, loaded, accounts])
const handleChange = (next: string) => {
if (company?.id && typeof window !== 'undefined') {
window.sessionStorage.setItem(STORAGE_KEY_PREFIX + company.id, next)
}
onChange(next)
}
// Fallback when the table is empty (fresh company, no PSD2 connections yet):
// show a single hardcoded '1930' option so the rest of the UI still works.
const options = accounts.length > 0
? accounts.map(a => ({
value: a.ledger_account,
label: `${a.ledger_account} ${a.name ?? a.iban ?? a.currency}`,
}))
: [{ value: '1930', label: '1930 Bankkonto' }]
return (
<div className={className}>
{label && <Label>{label}</Label>}
<div className={`flex items-center gap-2 ${label ? 'mt-1' : ''}`}>
<Select
value={value}
onValueChange={handleChange}
disabled={!loaded && accounts.length === 0}
>
<SelectTrigger className="w-full sm:w-[280px]">
<SelectValue placeholder={loaded ? 'Välj konto' : 'Laddar…'} />
</SelectTrigger>
<SelectContent>
{options.map(o => (
<SelectItem key={o.value} value={o.value}>
{o.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
)
}
-97
View File
@@ -1,97 +0,0 @@
'use client'
import * as React from 'react'
import { AlertCircle } from 'lucide-react'
import { useTranslations } from 'next-intl'
import { Button } from '@/components/ui/button'
import { Skeleton } from '@/components/ui/skeleton'
import { cn } from '@/lib/utils'
interface DataStateProps {
loading: boolean
error: string | null
/** When true (and not loading/error), render `empty` instead of `children`. */
isEmpty?: boolean
/** Retry handler: typically `useFetch().refetch`. Shows a retry button. */
onRetry?: () => void
/** Loading placeholder. Defaults to three skeleton rows. */
skeleton?: React.ReactNode
/** Shown when `isEmpty`. Pass an `EmptyState` / preset (e.g. `<EmptyInvoices />`). */
empty?: React.ReactNode
children: React.ReactNode
className?: string
}
/**
* Renders the loading / error / empty / ready states for a data-driven section,
* so callers stop hand-rolling that branch every time.
*
* Pairs with `useFetch`:
*
* @example
* const { data, loading, error, refetch } = useFetch<Account[]>(url, { select: b => b.data })
* return (
* <DataState
* loading={loading}
* error={error}
* onRetry={refetch}
* isEmpty={!data?.length}
* empty={<EmptyState title={t('none_title')} description={t('none_desc')} />}
* >
* <AccountsTable accounts={data!} />
* </DataState>
* )
*
* Loading uses the `Skeleton` primitive; empty expects an `EmptyState`; the
* error branch uses the only chrome-permitted semantic colour (`destructive`).
*/
export function DataState({
loading,
error,
isEmpty = false,
onRetry,
skeleton,
empty,
children,
className,
}: DataStateProps) {
const t = useTranslations('common')
if (loading) {
return (
<div className={className}>
{skeleton ?? (
<div className="space-y-3" aria-busy="true" aria-live="polite">
<Skeleton className="h-8 w-full" />
<Skeleton className="h-8 w-full" />
<Skeleton className="h-8 w-3/4" />
</div>
)}
</div>
)
}
if (error) {
return (
<div
role="alert"
className={cn('flex flex-col items-center justify-center py-12 px-4 text-center', className)}
>
<AlertCircle className="h-8 w-8 text-destructive mb-3" aria-hidden="true" />
<p className="text-sm font-medium mb-1">{t('load_error')}</p>
<p className="text-sm text-muted-foreground max-w-sm mb-6 text-balance">{error}</p>
{onRetry && (
<Button variant="outline" onClick={onRetry}>
{t('retry')}
</Button>
)}
</div>
)
}
if (isEmpty) {
return <div className={className}>{empty}</div>
}
return <>{children}</>
}