feat: UI polish, lint fixes, onboarding redesign, help page expansion, and test improvements

Broad update across dashboard pages, components, extensions, and lib code. Includes ESLint config additions, onboarding flow redesign, settings page refactor, help page content expansion, dead code removal, and test mock fixes. Adds dev docs and public assets.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-03-05 23:05:49 +01:00
co-authored by Claude Opus 4.6
parent 02ebe50f52
commit 091d043c85
106 changed files with 1177 additions and 326 deletions
+1 -1
View File
@@ -451,7 +451,7 @@ function validateVatBreakdown(data: any): VatBreakdownItem[] {
*/
export function validateExtractionConsistency(
extraction: ReceiptExtractionResult | InvoiceExtractionResult,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_type: 'receipt' | 'invoice'
): ConsistencyResult {
const issues: string[] = []
-3
View File
@@ -13,9 +13,6 @@ const isoDate = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Expected YYYY-MM-DD dat
/** BAS account number — always a string of 4 digits */
const accountNumber = z.string().regex(/^\d{4}$/, 'Account number must be exactly 4 digits')
/** Positive monetary amount (> 0) */
const positiveAmount = z.number().positive()
/** Non-negative monetary amount (>= 0) */
const nonNegativeAmount = z.number().nonnegative()
+2 -2
View File
@@ -1,3 +1,5 @@
import { getBASReference, ACCOUNT_CLASS_LABELS } from './bas-reference'
export type AccountType = 'asset' | 'liability' | 'equity' | 'revenue' | 'expense' | 'untaxed_reserves'
export interface AccountDescription {
@@ -332,8 +334,6 @@ export function getAccountDescription(accountNumber: string): AccountDescription
// Fall back to BAS reference data for accounts not in the hardcoded list
try {
// Dynamic import avoided — use lazy require pattern
const { getBASReference, ACCOUNT_CLASS_LABELS } = require('./bas-reference')
const ref = getBASReference(accountNumber)
if (ref) {
const classLabel = ACCOUNT_CLASS_LABELS[ref.account_class] || ''
-2
View File
@@ -173,10 +173,8 @@ export async function previewCurrencyRevaluation(
let credit1510 = 0 // Receivable loss (revalue down)
let debit2440 = 0 // Payable gain (liability shrank)
let credit2440 = 0 // Payable loss (liability grew)
let debit3960 = 0 // Placeholder — we won't debit 3960
let credit3960 = 0 // Gains
let debit7960 = 0 // Losses
let credit7960 = 0 // Placeholder — we won't credit 7960
for (const item of items) {
if (item.type === 'receivable') {
+1 -1
View File
@@ -1,6 +1,6 @@
import { createJournalEntry, findFiscalPeriod } from './engine'
import { resolveSekAmount, buildCurrencyMetadata } from './currency-utils'
import { generateSalesVatLines, generateReverseChargeLines } from './vat-entries'
import { generateSalesVatLines } from './vat-entries'
import { getVatTreatmentForRate } from '@/lib/invoices/vat-rules'
import { createLogger } from '@/lib/logger'
import type { SupabaseClient } from '@supabase/supabase-js'
-3
View File
@@ -2,14 +2,11 @@ import type { SupabaseClient } from '@supabase/supabase-js'
import {
generateInputVatLine,
generateReverseChargeLines,
extractNetAmount,
extractVatAmount,
} from './vat-entries'
import { findMatchingTemplates, buildMappingResultFromTemplate } from './booking-templates'
import type {
MappingRule,
MappingResult,
RiskLevel,
Transaction,
EntityType,
VatJournalLine,
+1 -1
View File
@@ -1,6 +1,6 @@
import { createJournalEntry, findFiscalPeriod } from './engine'
import { resolveSekAmount, buildCurrencyMetadata } from './currency-utils'
import { generateInputVatLine, generateReverseChargeLines, extractNetAmount, extractVatAmount } from './vat-entries'
import { extractNetAmount, extractVatAmount } from './vat-entries'
import { createLogger } from '@/lib/logger'
import type { SupabaseClient } from '@supabase/supabase-js'
import type {
+1 -1
View File
@@ -1,7 +1,7 @@
import type { SupabaseClient } from '@supabase/supabase-js'
import { createServiceClient } from '@/lib/supabase/server'
import { eventBus } from '@/lib/events'
import type { DocumentAttachment, CreateDocumentAttachmentInput, DocumentUploadSource } from '@/types'
import type { DocumentAttachment, DocumentUploadSource } from '@/types'
/**
* Document Service - WORM-style document archive
@@ -1,7 +1,7 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { matchDocumentToTransactions } from '../document-matcher'
import { makeInvoiceInboxItem, makeTransaction } from '@/tests/helpers'
import type { InvoiceExtractionResult, ReceiptExtractionResult, Transaction } from '@/types'
import type { InvoiceExtractionResult, ReceiptExtractionResult } from '@/types'
describe('matchDocumentToTransactions', () => {
const mockSupabase = {} as never // Not used when candidateTransactions is provided
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { createMockSupabase } from '@/tests/helpers'
import {
+2 -1
View File
@@ -1,4 +1,4 @@
import { resolve, join, dirname } from 'path'
import { resolve, join } from 'path'
import { readdirSync, readFileSync } from 'fs'
/**
@@ -74,6 +74,7 @@ describe('sectors registry', () => {
})
it('getSector returns undefined for unknown slug', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const sector = getSector('invalid' as any)
expect(sector).toBeUndefined()
})
+1 -1
View File
@@ -355,7 +355,7 @@ async function importVouchers(
p_series: voucherSeries,
})
let currentVoucherNumber = (startNumber as number) || 1
const currentVoucherNumber = (startNumber as number) || 1
// Batch insert journal entries (in chunks of 100)
const BATCH_SIZE = 100
+1
View File
@@ -20,6 +20,7 @@ function makeBuilder() {
function makeClient() {
return {
from: vi.fn().mockImplementation(() => makeBuilder()),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any
}
@@ -20,6 +20,7 @@ function makeBuilder() {
function makeClient() {
return {
from: vi.fn().mockImplementation(() => makeBuilder()),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any
}
@@ -10,6 +10,7 @@ import type { TrialBalanceRow } from '@/types'
const mockTrialBalance = vi.mocked(generateTrialBalance)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const supabase = {} as any
beforeEach(() => {
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { describe, it, expect, vi, beforeEach } from 'vitest'
import JSZip from 'jszip'
import { generateFullArchive } from '../full-archive-export'
@@ -21,6 +21,7 @@ function makeClient() {
return {
from: vi.fn().mockImplementation(() => makeBuilder()),
rpc: vi.fn().mockImplementation(async () => results[resultIdx++] ?? { data: null, error: null }),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any
}
@@ -10,6 +10,7 @@ import type { TrialBalanceRow } from '@/types'
const mockTrialBalance = vi.mocked(generateTrialBalance)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const supabase = {} as any
beforeEach(() => {
@@ -20,6 +20,7 @@ function makeBuilder() {
function makeClient() {
return {
from: vi.fn().mockImplementation(() => makeBuilder()),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any
}
+1
View File
@@ -20,6 +20,7 @@ function makeBuilder() {
function makeClient() {
return {
from: vi.fn().mockImplementation(() => makeBuilder()),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any
}
@@ -20,6 +20,7 @@ function makeBuilder() {
function makeClient() {
return {
from: vi.fn().mockImplementation(() => makeBuilder()),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any
}
@@ -20,6 +20,7 @@ function makeBuilder() {
function makeClient() {
return {
from: vi.fn().mockImplementation(() => makeBuilder()),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any
}
@@ -21,6 +21,7 @@ function makeClient() {
return {
from: vi.fn().mockImplementation(() => makeBuilder()),
rpc: vi.fn().mockImplementation(async () => results[resultIdx++] ?? { data: null, error: null }),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any
}
@@ -20,6 +20,7 @@ function makeBuilder() {
function makeClient() {
return {
from: vi.fn().mockImplementation(() => makeBuilder()),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any
}
@@ -1,6 +1,6 @@
import { describe, it, expect } from 'vitest'
import { INK2_ACCOUNT_MAPPINGS, isAccountInMapping } from '../ink2-engine'
import type { INK2AccountMapping, INK2SRUCode } from '../types'
import type { INK2SRUCode } from '../types'
/**
* Helper to find which SRU code an account maps to
@@ -1,6 +1,5 @@
import { describe, it, expect } from 'vitest'
import { NE_ACCOUNT_MAPPINGS } from '../ne-engine'
import type { NEAccountMapping } from '@/lib/reports/ne-bilaga/types'
/**
* Helper to check if an account falls into a specific ruta
-2
View File
@@ -137,8 +137,6 @@ export function calculateABTax(
// Arbetsgivaravgifter on salaries (already included in salary costs usually)
// This is just for reference
const arbetsgivaravgifter = salaryCostsYTD * TAX_RATES.arbetsgivaravgifter
const totalTax = bolagsskatt + momsFromUnpaidInvoices
return {
+2 -5
View File
@@ -214,7 +214,6 @@ export const TAX_DEADLINE_CONFIGS: TaxDeadlineConfig[] = [
const dl = getDeadline(fyEndYear)
if (dl.year === year) {
// Compute the FY start year
const fyStartYear = fyEndMonth === 12 ? fyEndYear : fyEndYear - (fyEndMonth < settings.fiscal_year_start_month ? 0 : 0)
const fyStart = fyEndMonth === 12 ? fyEndYear : fyEndYear
const periodLabel = fyEndMonth === 12
? `${fyEndYear}`
@@ -249,14 +248,12 @@ export const TAX_DEADLINE_CONFIGS: TaxDeadlineConfig[] = [
// 7 months after FY end per ÅRL 8:3
// Deadline month (0-indexed): ((fyEndMonth - 1) + 7) % 12
const deadlineMonth0 = ((fyEndMonth - 1) + 7) % 12
// Last day of the deadline month
// Determine which year the deadline falls in
const wrapsYear = fyEndMonth > 5 // Jun+ wraps into next year
const _wrapsYear = fyEndMonth > 5 // Jun+ wraps into next year
// For calendar year (Dec end): deadline Jul 31 same year+1
// The FY ending in `year` produces a deadline:
const fyEndYear = year - 1 // By default we show deadline for the FY that ended in year-1
const deadlineYear = wrapsYear ? fyEndYear + 1 + 1 : fyEndYear + 1
const _fyEndYear = year - 1 // By default we show deadline for the FY that ended in year-1
// Simpler: compute from a concrete FY end date
// FY ends: fyEndMonth (1-indexed), last day, in some year.
// We want the deadline that falls in `year`.
+2 -3
View File
@@ -4,11 +4,10 @@
import { SupabaseClient } from '@supabase/supabase-js'
import { createLogger } from '@/lib/logger'
import type { TaxDeadlineType, DeadlineStatus, CreateDeadlineInput } from '@/types'
import type { TaxDeadlineType, DeadlineStatus } from '@/types'
const log = createLogger('deadline-generator')
import {
TAX_DEADLINE_CONFIGS,
getApplicableDeadlineConfigs,
type CompanySettingsForDeadlines,
type DeadlineInstance,
@@ -180,7 +179,7 @@ export async function generateTaxDeadlinesForUser(
*/
function createLinkedReportPeriod(
instance: DeadlineInstance,
type: TaxDeadlineType
_type: TaxDeadlineType
): Record<string, unknown> | null {
const period = instance.period