diff --git a/CLAUDE.md b/CLAUDE.md index 43c9f916..844596d8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -406,3 +406,48 @@ Hosted on **Vercel**. Cron jobs in `vercel.json` (banking sync daily 05:00, dead ## Other Never create a NUL/nul file: \gnubok\NUL + +--- + +## Design Context + +### Users + +Swedish sole traders (enskild firma) and small business owners (aktiebolag) who need to manage their own bookkeeping. They are not accountants — they are professionals (consultants, freelancers, shop owners) who want to stay compliant without hiring one. They use gnubok in short, focused sessions: sending an invoice, categorizing bank transactions, filing a VAT declaration. Speed and clarity matter — every second spent in the app is a second away from their real work. + +### Brand Personality + +**Minimal. Sharp. Efficient.** + +gnubok is the tool that makes accounting feel handled. It doesn't try to be fun — it earns trust through precision, clarity, and speed. The interface should feel like a well-made instrument: considered, quiet, and confident. Think Mercury's calm authority applied to Swedish bookkeeping. + +### Aesthetic Direction + +- **Palette**: Grayscale foundation with restrained semantic colors — sage green (success/balance), terracotta (errors/overdue), ochre (warnings/attention). No loud brand color. The absence of color *is* the brand. +- **Typography**: Fraunces (serif) for display headings — adds warmth and distinction to an otherwise minimal interface. Geist (sans) for body — clean, modern, excellent for tabular data. Tabular numbers everywhere financial data appears. +- **Surfaces**: White/near-white cards on light gray backgrounds. Subtle borders (60% opacity). Soft shadows that suggest depth without drama. Dark mode follows the same restraint. +- **Spacing**: Generous whitespace. Let content breathe. Dense data (tables, ledgers) should use tighter spacing but never feel cramped. +- **Motion**: Subtle and purposeful. Stagger animations for list entry, spring easing for interactive feedback. Never decorative — always communicates state change. +- **Icons**: Lucide — consistent 15px in navigation, slightly larger in empty states and actions. +- **Reference**: Mercury (banking) — calm, trustworthy, clean data presentation, financial precision. +- **Anti-reference**: Enterprise software (SAP/Oracle density, overwhelming dashboards with 50 widgets, toolbar soup). + +### Design Principles + +1. **Clarity over cleverness.** Every element should be immediately understandable. Accounting is complex enough — the UI must never add confusion. Use familiar patterns, clear labels (in Swedish), and obvious hierarchy. + +2. **Earned minimalism.** Remove everything that doesn't serve the user's task. But don't strip away context that prevents errors — in accounting, a missing detail can mean a compliance violation. Minimal means *considered*, not *sparse*. + +3. **Numbers are first-class.** Financial data deserves typographic care: tabular-nums, proper alignment, adequate contrast, clear positive/negative distinction. A journal entry grid should feel as precise as a printed ledger. + +4. **Trust through consistency.** Same patterns, same spacing, same behavior everywhere. Buttons work the same way. Cards look the same way. When the user learns one screen, they've learned them all. + +5. **Speed is a feature.** Fast load, fast interaction, fast comprehension. Users are here to get a job done and leave. Optimize for the 90-second session: open app, categorize three transactions, close app. + +### Accessibility + +- Target: **WCAG AA** compliance +- Minimum contrast ratio: 4.5:1 for text, 3:1 for UI components +- All interactive elements keyboard-navigable with visible focus rings +- Respect `prefers-reduced-motion` for users who disable animations +- Color is never the sole indicator of state — always pair with icons, text, or shape diff --git a/app/(dashboard)/bookkeeping/page.tsx b/app/(dashboard)/bookkeeping/page.tsx index 0bfc0e85..baa9f30c 100644 --- a/app/(dashboard)/bookkeeping/page.tsx +++ b/app/(dashboard)/bookkeeping/page.tsx @@ -16,9 +16,9 @@ export default function BookkeepingPage() {
-

Bokföring

+

Bokföring

- Verifikationer, kontoplan och manuella bokföringsorder + Skapa verifikationer, hantera kontoplanen och bifoga underlag

-

{customer.name}

+

{customer.name}

{customerTypeLabels[customer.customer_type]}
@@ -346,7 +346,7 @@ export default function CustomerDetailPage({ {formatCurrency(invoice.total, invoice.currency)} - {invoice.payment_status === 'paid' ? 'Betald' : invoice.payment_status === 'overdue' ? 'Försenad' : 'Obetald'} + {invoice.payment_status === 'paid' ? 'Betald' : invoice.payment_status === 'overdue' ? 'Förfallen' : 'Obetald'} diff --git a/app/(dashboard)/customers/page.tsx b/app/(dashboard)/customers/page.tsx index 4daacb47..b07e51ae 100644 --- a/app/(dashboard)/customers/page.tsx +++ b/app/(dashboard)/customers/page.tsx @@ -56,8 +56,8 @@ export default function CustomersPage() { if (error) { toast({ - title: 'Fel', - description: 'Kunde inte hämta kunder', + title: 'Kunde inte ladda kunder', + description: 'Kontrollera din anslutning och försök igen.', variant: 'destructive', }) } else { @@ -83,8 +83,8 @@ export default function CustomersPage() { if (!response.ok) { toast({ - title: 'Fel', - description: result.error || 'Kunde inte skapa kund', + title: 'Kunde inte skapa kund', + description: result.error || 'Försök igen.', variant: 'destructive', }) } else { @@ -109,7 +109,7 @@ export default function CustomersPage() {
-

Kunder

+

Kunder

Hantera dina kunder och deras faktureringsuppgifter

diff --git a/app/(dashboard)/deadlines/page.tsx b/app/(dashboard)/deadlines/page.tsx index 19149859..4e6797c2 100644 --- a/app/(dashboard)/deadlines/page.tsx +++ b/app/(dashboard)/deadlines/page.tsx @@ -47,8 +47,8 @@ export default function DeadlinesPage() { setOverdueInvoices({ count: overdueCount, total: overdueTotal }) } catch { toast({ - title: 'Fel', - description: 'Kunde inte hamta data', + title: 'Kunde inte ladda deadlines', + description: 'Kontrollera din anslutning och försök igen.', variant: 'destructive', }) } finally { @@ -83,8 +83,8 @@ export default function DeadlinesPage() { fetchData() } catch (error) { toast({ - title: 'Fel', - description: error instanceof Error ? error.message : 'Kunde inte skapa deadline', + title: 'Kunde inte skapa deadline', + description: error instanceof Error ? error.message : 'Försök igen.', variant: 'destructive', }) throw error @@ -111,8 +111,8 @@ export default function DeadlinesPage() { fetchData() } catch (error) { toast({ - title: 'Fel', - description: error instanceof Error ? error.message : 'Kunde inte uppdatera deadline', + title: 'Kunde inte uppdatera status', + description: error instanceof Error ? error.message : 'Försök igen.', variant: 'destructive', }) } @@ -139,8 +139,8 @@ export default function DeadlinesPage() { fetchData() } catch (error) { toast({ - title: 'Fel', - description: error instanceof Error ? error.message : 'Kunde inte uppdatera deadline', + title: 'Kunde inte spara ändringar', + description: error instanceof Error ? error.message : 'Försök igen.', variant: 'destructive', }) } @@ -164,8 +164,8 @@ export default function DeadlinesPage() { fetchData() } catch (error) { toast({ - title: 'Fel', - description: error instanceof Error ? error.message : 'Kunde inte ta bort deadline', + title: 'Kunde inte ta bort deadline', + description: error instanceof Error ? error.message : 'Försök igen.', variant: 'destructive', }) } @@ -175,7 +175,7 @@ export default function DeadlinesPage() { return (
-

Deadlines

+

Deadlines

{/* Overdue alert skeleton */}
@@ -214,7 +214,7 @@ export default function DeadlinesPage() { return (
-

Deadlines

+

Deadlines

{overdueInvoices.count > 0 && ( diff --git a/app/(dashboard)/expenses/[id]/page.tsx b/app/(dashboard)/expenses/[id]/page.tsx index e5262710..84b655c7 100644 --- a/app/(dashboard)/expenses/[id]/page.tsx +++ b/app/(dashboard)/expenses/[id]/page.tsx @@ -46,7 +46,7 @@ export default function ExpenseDetailPage() { const res = await fetch(`/api/supplier-invoices/${params.id}`) const { data, error } = await res.json() if (error) { - toast({ title: 'Fel', description: error, variant: 'destructive' }) + toast({ title: 'Kunde inte ladda utgift', description: error, variant: 'destructive' }) } else { setInvoice(data) setPayAmount(String(data.remaining_amount)) @@ -76,7 +76,7 @@ export default function ExpenseDetailPage() { const res = await fetch(`/api/supplier-invoices/${params.id}/approve`, { method: 'POST' }) const result = await res.json() if (!res.ok) { - toast({ title: 'Fel', description: result.error, variant: 'destructive' }) + toast({ title: 'Kunde inte godkänna', description: result.error, variant: 'destructive' }) } else { toast({ title: 'Godkänd', description: 'Utgiften har godkänts' }) fetchInvoice() @@ -93,7 +93,7 @@ export default function ExpenseDetailPage() { }) const result = await res.json() if (!res.ok) { - toast({ title: 'Fel', description: result.error, variant: 'destructive' }) + toast({ title: 'Betalning misslyckades', description: result.error, variant: 'destructive' }) } else { toast({ title: result.status === 'paid' ? 'Betald' : 'Delbetalning registrerad', @@ -117,7 +117,7 @@ export default function ExpenseDetailPage() { const res = await fetch(`/api/supplier-invoices/${params.id}/credit`, { method: 'POST' }) const result = await res.json() if (!res.ok) { - toast({ title: 'Fel', description: result.error, variant: 'destructive' }) + toast({ title: 'Kunde inte kreditera', description: result.error, variant: 'destructive' }) } else { toast({ title: 'Kreditfaktura registrerad' }) fetchInvoice() @@ -136,7 +136,7 @@ export default function ExpenseDetailPage() { const res = await fetch(`/api/supplier-invoices/${params.id}`, { method: 'DELETE' }) const result = await res.json() if (!res.ok) { - toast({ title: 'Fel', description: result.error, variant: 'destructive' }) + toast({ title: 'Kunde inte ta bort', description: result.error, variant: 'destructive' }) } else { toast({ title: 'Borttagen' }) router.push('/expenses') @@ -177,7 +177,7 @@ export default function ExpenseDetailPage() {
-

+

Utgift #{invoice.arrival_number}

diff --git a/app/(dashboard)/expenses/new/page.tsx b/app/(dashboard)/expenses/new/page.tsx index e6fdf294..42c043df 100644 --- a/app/(dashboard)/expenses/new/page.tsx +++ b/app/(dashboard)/expenses/new/page.tsx @@ -194,7 +194,7 @@ export default function NewExpensePage() { async function handleCreateSupplier() { if (!newSupplier.name.trim()) { - toast({ title: 'Fel', description: 'Ange leverantörsnamn', variant: 'destructive' }) + toast({ title: 'Namn saknas', description: 'Ange ett namn för leverantören.', variant: 'destructive' }) return } setIsCreatingSupplier(true) @@ -231,11 +231,11 @@ export default function NewExpensePage() { function onSubmit(data: FormData) { if (!data.supplier_id) { - toast({ title: 'Fel', description: 'Välj en leverantör', variant: 'destructive' }) + toast({ title: 'Leverantör saknas', description: 'Välj eller skapa en leverantör.', variant: 'destructive' }) return } if (!data.supplier_invoice_number) { - toast({ title: 'Fel', description: 'Ange fakturanummer', variant: 'destructive' }) + toast({ title: 'Fakturanummer saknas', description: 'Ange leverantörens fakturanummer.', variant: 'destructive' }) return } @@ -337,7 +337,7 @@ export default function NewExpensePage() {
-

Ny utgift

+

Ny utgift

Registrera en inkommande faktura

diff --git a/app/(dashboard)/expenses/page.tsx b/app/(dashboard)/expenses/page.tsx index 8b1ef7b1..72dadde2 100644 --- a/app/(dashboard)/expenses/page.tsx +++ b/app/(dashboard)/expenses/page.tsx @@ -75,7 +75,7 @@ export default function ExpensesPage() { .order('due_date', { ascending: true }) if (error) { - toast({ title: 'Fel', description: 'Kunde inte hämta utgifter', variant: 'destructive' }) + toast({ title: 'Kunde inte ladda utgifter', description: 'Kontrollera din anslutning och försök igen.', variant: 'destructive' }) } else { setInvoices(data || []) } @@ -134,7 +134,7 @@ export default function ExpensesPage() {

Att betala

-

{formatCurrency(stats.unpaidAmount)}

+

{formatCurrency(stats.unpaidAmount)}

{stats.unpaidCount} utgifter

@@ -150,13 +150,13 @@ export default function ExpensesPage() { {stats.overdueCount > 0 ? ( <>

Förfallet

-

{formatCurrency(stats.overdueAmount)}

+

{formatCurrency(stats.overdueAmount)}

{stats.overdueCount} utgifter

) : ( <>

Totalt antal

-

{invoices.length}

+

{invoices.length}

)}
diff --git a/app/(dashboard)/import/page.tsx b/app/(dashboard)/import/page.tsx index 14aa7deb..2a4d4231 100644 --- a/app/(dashboard)/import/page.tsx +++ b/app/(dashboard)/import/page.tsx @@ -438,7 +438,7 @@ function SIEImportWizard() { const data = await res.json() if (!res.ok) { - toast({ title: 'Fel', description: data.error || 'Kunde inte skapa konton', variant: 'destructive' }) + toast({ title: 'Kunde inte skapa konton', description: data.error || 'Försök igen.', variant: 'destructive' }) return } @@ -463,7 +463,7 @@ function SIEImportWizard() { } } } catch (err) { - toast({ title: 'Fel', description: err instanceof Error ? err.message : 'Kunde inte skapa konton', variant: 'destructive' }) + toast({ title: 'Kunde inte skapa konton', description: err instanceof Error ? err.message : 'Försök igen.', variant: 'destructive' }) } finally { setIsCreatingAccounts(false) } @@ -601,8 +601,8 @@ function PSD2ConnectWizard() { window.location.href = data.authorization_url } catch (error) { toast({ - title: 'Fel', - description: error instanceof Error ? error.message : 'Kunde inte ansluta bank', + title: 'Kunde inte ansluta bank', + description: error instanceof Error ? error.message : 'Försök igen.', variant: 'destructive', }) setIsConnecting(false) @@ -634,8 +634,8 @@ function PSD2ConnectWizard() { fetchConnections() } catch (error) { toast({ - title: 'Fel', - description: error instanceof Error ? error.message : 'Synkronisering misslyckades', + title: 'Synkronisering misslyckades', + description: error instanceof Error ? error.message : 'Försök igen.', variant: 'destructive', }) } @@ -671,8 +671,8 @@ function PSD2ConnectWizard() { fetchConnections() } catch (error) { toast({ - title: 'Fel', - description: error instanceof Error ? error.message : 'Kunde inte koppla bort bank', + title: 'Kunde inte koppla bort bank', + description: error instanceof Error ? error.message : 'Försök igen.', variant: 'destructive', }) } @@ -780,7 +780,7 @@ export default function ImportPage() {
{/* Header */}
-

Importera

+

Importera

Importera banktransaktioner eller bokföringsdata till ditt företag

diff --git a/app/(dashboard)/invoices/[id]/credit/page.tsx b/app/(dashboard)/invoices/[id]/credit/page.tsx index dff488b4..93f89c02 100644 --- a/app/(dashboard)/invoices/[id]/credit/page.tsx +++ b/app/(dashboard)/invoices/[id]/credit/page.tsx @@ -51,8 +51,8 @@ export default function CreateCreditNotePage({ params }: { params: Promise<{ id: if (error || !data) { toast({ - title: 'Fel', - description: 'Kunde inte hämta faktura', + title: 'Kunde inte ladda faktura', + description: 'Fakturan hittades inte.', variant: 'destructive', }) router.push('/invoices') @@ -120,8 +120,8 @@ export default function CreateCreditNotePage({ params }: { params: Promise<{ id: router.push(`/invoices/${creditNote.id}`) } catch (error) { toast({ - title: 'Fel', - description: error instanceof Error ? error.message : 'Kunde inte skapa kreditfaktura', + title: 'Kunde inte skapa kreditfaktura', + description: error instanceof Error ? error.message : 'Försök igen.', variant: 'destructive', }) } @@ -151,7 +151,7 @@ export default function CreateCreditNotePage({ params }: { params: Promise<{ id:
-

Skapa kreditfaktura

+

Skapa kreditfaktura

Krediterar faktura {invoice.invoice_number}

diff --git a/app/(dashboard)/invoices/[id]/page.tsx b/app/(dashboard)/invoices/[id]/page.tsx index d6882dc9..89c4d2dc 100644 --- a/app/(dashboard)/invoices/[id]/page.tsx +++ b/app/(dashboard)/invoices/[id]/page.tsx @@ -90,8 +90,8 @@ export default function InvoiceDetailPage({ params }: { params: Promise<{ id: st if (error || !data) { toast({ - title: 'Fel', - description: 'Kunde inte hämta faktura', + title: 'Kunde inte ladda faktura', + description: 'Fakturan hittades inte.', variant: 'destructive', }) router.push('/invoices') @@ -211,8 +211,8 @@ export default function InvoiceDetailPage({ params }: { params: Promise<{ id: st fetchInvoice() } catch (error) { toast({ - title: 'Fel', - description: error instanceof Error ? error.message : 'Kunde inte uppdatera status', + title: 'Statusuppdatering misslyckades', + description: error instanceof Error ? error.message : 'Försök igen.', variant: 'destructive', }) } @@ -255,8 +255,8 @@ export default function InvoiceDetailPage({ params }: { params: Promise<{ id: st fetchInvoice() } catch (error) { toast({ - title: 'Fel', - description: error instanceof Error ? error.message : 'Kunde inte skicka fakturan', + title: 'Kunde inte skicka faktura', + description: error instanceof Error ? error.message : 'Försök igen.', variant: 'destructive', }) } @@ -287,8 +287,8 @@ export default function InvoiceDetailPage({ params }: { params: Promise<{ id: st router.push(`/invoices/${data.data.id}`) } catch (error) { toast({ - title: 'Fel', - description: error instanceof Error ? error.message : 'Kunde inte konvertera', + title: 'Konvertering misslyckades', + description: error instanceof Error ? error.message : 'Försök igen.', variant: 'destructive', }) } @@ -324,8 +324,8 @@ export default function InvoiceDetailPage({ params }: { params: Promise<{ id: st }) } catch (error) { toast({ - title: 'Fel', - description: error instanceof Error ? error.message : 'Kunde inte ladda ner PDF', + title: 'Kunde inte ladda ner PDF', + description: error instanceof Error ? error.message : 'Försök igen.', variant: 'destructive', }) } @@ -363,12 +363,12 @@ export default function InvoiceDetailPage({ params }: { params: Promise<{ id: st
-

{invoice.invoice_number}

+

{invoice.invoice_number}

{isProforma && ( - Proforma + Proforma )} {isDeliveryNote && ( - Följesedel + Följesedel )} diff --git a/app/(dashboard)/invoices/new/page.tsx b/app/(dashboard)/invoices/new/page.tsx index 973d5924..47c73aba 100644 --- a/app/(dashboard)/invoices/new/page.tsx +++ b/app/(dashboard)/invoices/new/page.tsx @@ -168,8 +168,8 @@ export default function NewInvoicePage() { if (error) { toast({ - title: 'Fel', - description: 'Kunde inte hämta kunder', + title: 'Kunde inte ladda kunder', + description: 'Kontrollera din anslutning och försök igen.', variant: 'destructive', }) } else { @@ -191,8 +191,8 @@ export default function NewInvoicePage() { if (!response.ok) { toast({ - title: 'Fel', - description: result.error || 'Kunde inte skapa kund', + title: 'Kunde inte skapa kund', + description: result.error || 'Försök igen.', variant: 'destructive', }) } else { @@ -370,7 +370,7 @@ export default function NewInvoicePage() {
-

+

{watchDocumentType === 'proforma' ? 'Ny proformafaktura' : watchDocumentType === 'delivery_note' ? 'Ny följesedel' : 'Ny faktura'}

diff --git a/app/(dashboard)/invoices/page.tsx b/app/(dashboard)/invoices/page.tsx index a6cb9a6d..cf038e53 100644 --- a/app/(dashboard)/invoices/page.tsx +++ b/app/(dashboard)/invoices/page.tsx @@ -11,17 +11,18 @@ import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs' import { PageHeader } from '@/components/ui/page-header' import { useToast } from '@/components/ui/use-toast' import { formatCurrency, formatDate } from '@/lib/utils' +import { cn } from '@/lib/utils' import { Plus, Search, Receipt, FileText, Send, CheckCircle, Clock, XCircle, ReceiptText, FileQuestion, Truck } from 'lucide-react' import { EmptyInvoices } from '@/components/ui/empty-state' import type { Invoice, InvoiceStatus } from '@/types' const statusConfig: Record = { - draft: { label: 'Utkast', variant: 'secondary', icon: FileText, borderColor: 'border-l-muted-foreground/30' }, - sent: { label: 'Skickad', variant: 'default', icon: Send, borderColor: 'border-l-warning' }, - paid: { label: 'Betald', variant: 'success', icon: CheckCircle, borderColor: 'border-l-success' }, - overdue: { label: 'Förfallen', variant: 'destructive', icon: Clock, borderColor: 'border-l-destructive' }, - cancelled: { label: 'Makulerad', variant: 'secondary', icon: XCircle, borderColor: 'border-l-muted-foreground/30' }, - credited: { label: 'Krediterad', variant: 'secondary', icon: XCircle, borderColor: 'border-l-muted-foreground/30' }, + draft: { label: 'Utkast', variant: 'secondary', icon: FileText, borderColor: 'border-muted-foreground/30' }, + sent: { label: 'Skickad', variant: 'default', icon: Send, borderColor: 'border-warning/50' }, + paid: { label: 'Betald', variant: 'success', icon: CheckCircle, borderColor: 'border-success/50' }, + overdue: { label: 'Förfallen', variant: 'destructive', icon: Clock, borderColor: 'border-destructive/50' }, + cancelled: { label: 'Makulerad', variant: 'secondary', icon: XCircle, borderColor: 'border-muted-foreground/30' }, + credited: { label: 'Krediterad', variant: 'secondary', icon: XCircle, borderColor: 'border-muted-foreground/30' }, } function getRelativeTimeLabel(dueDateStr: string, status: InvoiceStatus): { text: string; color: string } | null { @@ -62,8 +63,8 @@ export default function InvoicesPage() { if (error) { toast({ - title: 'Fel', - description: 'Kunde inte hämta fakturor', + title: 'Kunde inte ladda fakturor', + description: 'Kontrollera din anslutning och försök igen.', variant: 'destructive', }) } else { @@ -106,7 +107,7 @@ export default function InvoicesPage() {

Totalt antal

-

{invoices.length}

+

{invoices.length}

@@ -159,7 +160,7 @@ export default function InvoicesPage() {

Obetalda

-

{stats.unpaid}

+

{stats.unpaid}

{stats.overdue > 0 && ( {stats.overdue} förfallna @@ -178,7 +179,7 @@ export default function InvoicesPage() {

Att få in

-

{formatCurrency(stats.unpaidAmount)}

+

{formatCurrency(stats.unpaidAmount)}

@@ -262,11 +263,13 @@ export default function InvoicesPage() { const isDeliveryNote = docType === 'delivery_note' const StatusIcon = isCreditNote ? ReceiptText : isProforma ? FileQuestion : isDeliveryNote ? Truck : status.icon const relativeTime = invoice.due_date ? getRelativeTimeLabel(invoice.due_date, invoice.status) : null - const borderClass = isCreditNote ? 'border-l-4 border-l-destructive/50' : isProforma ? 'border-l-4 border-l-blue-400' : isDeliveryNote ? 'border-l-4 border-l-emerald-400' : `border-l-4 ${status.borderColor}` - return ( - +
@@ -289,12 +292,12 @@ export default function InvoicesPage() { )} {isProforma && ( - + Proforma )} {isDeliveryNote && ( - + Följesedel )} diff --git a/app/(dashboard)/receipts/page.tsx b/app/(dashboard)/receipts/page.tsx index 40f5eeaf..8a8400e8 100644 --- a/app/(dashboard)/receipts/page.tsx +++ b/app/(dashboard)/receipts/page.tsx @@ -30,7 +30,7 @@ export default function ReceiptsPage() { return (
-

Kvitton

+

Kvitton

Hantera och granska dina kvitton

diff --git a/app/(dashboard)/reports/page.tsx b/app/(dashboard)/reports/page.tsx index 1e07517c..216e7756 100644 --- a/app/(dashboard)/reports/page.tsx +++ b/app/(dashboard)/reports/page.tsx @@ -69,9 +69,9 @@ export default function ReportsPage() {
-

Rapporter

+

Rapporter

- Huvudbok, grundbok, kundreskontra, saldobalans, resultaträkning, balansräkning, momsdeklaration och mer + Generera skattedeklarationer, resultaträkningar och exportera till Skatteverket

@@ -1160,7 +1160,7 @@ function SupplierLedgerView({ periodId }: { periodId: string }) { Totalt utestående -

{formatAmount(ledger.total_outstanding)} kr

+

{formatAmount(ledger.total_outstanding)} kr

{ledger.unpaid_count} fakturor

@@ -1169,7 +1169,7 @@ function SupplierLedgerView({ periodId }: { periodId: string }) { Ej förfallet -

{formatAmount(ledger.total_current)} kr

+

{formatAmount(ledger.total_current)} kr

@@ -1177,7 +1177,7 @@ function SupplierLedgerView({ periodId }: { periodId: string }) { Förfallet -

{formatAmount(ledger.total_overdue)} kr

+

{formatAmount(ledger.total_overdue)} kr

@@ -1758,7 +1758,7 @@ function ARLedgerView({ periodId }: { periodId: string }) { Totalt utestående -

{formatAmount(ledger.total_outstanding)} kr

+

{formatAmount(ledger.total_outstanding)} kr

{ledger.unpaid_count} fakturor

@@ -1767,7 +1767,7 @@ function ARLedgerView({ periodId }: { periodId: string }) { Ej förfallet -

{formatAmount(ledger.total_current)} kr

+

{formatAmount(ledger.total_current)} kr

@@ -1775,7 +1775,7 @@ function ARLedgerView({ periodId }: { periodId: string }) { Förfallet -

{formatAmount(ledger.total_overdue)} kr

+

{formatAmount(ledger.total_overdue)} kr

diff --git a/app/(dashboard)/settings/page.tsx b/app/(dashboard)/settings/page.tsx index 7e22287a..f3aff86c 100644 --- a/app/(dashboard)/settings/page.tsx +++ b/app/(dashboard)/settings/page.tsx @@ -196,8 +196,8 @@ export default function SettingsPage() { setSettings({ ...settings, ...updates } as typeof settings) } catch (error) { toast({ - title: 'Fel', - description: error instanceof Error ? error.message : 'Kunde inte spara inställningar', + title: 'Kunde inte spara inställningar', + description: error instanceof Error ? error.message : 'Försök igen.', variant: 'destructive', }) } @@ -229,8 +229,8 @@ export default function SettingsPage() { router.push('/login') } catch (error) { toast({ - title: 'Fel', - description: error instanceof Error ? error.message : 'Kunde inte radera kontot', + title: 'Kunde inte radera kontot', + description: error instanceof Error ? error.message : 'Försök igen.', variant: 'destructive', }) setIsDeleting(false) @@ -284,7 +284,7 @@ export default function SettingsPage() { return (
-

Inställningar

+

Inställningar

Hantera dina företags- och kontoinställningar

diff --git a/app/(dashboard)/supplier-invoices/[id]/page.tsx b/app/(dashboard)/supplier-invoices/[id]/page.tsx index b7dd37fd..b56e1ac2 100644 --- a/app/(dashboard)/supplier-invoices/[id]/page.tsx +++ b/app/(dashboard)/supplier-invoices/[id]/page.tsx @@ -55,7 +55,7 @@ export default function SupplierInvoiceDetailPage() { const res = await fetch(`/api/supplier-invoices/${params.id}`) const { data, error } = await res.json() if (error) { - toast({ title: 'Fel', description: error, variant: 'destructive' }) + toast({ title: 'Kunde inte ladda leverantörsfaktura', description: error, variant: 'destructive' }) } else { setInvoice(data) setPayAmount(String(data.remaining_amount)) @@ -72,7 +72,7 @@ export default function SupplierInvoiceDetailPage() { const res = await fetch(`/api/supplier-invoices/${params.id}/approve`, { method: 'POST' }) const result = await res.json() if (!res.ok) { - toast({ title: 'Fel', description: result.error, variant: 'destructive' }) + toast({ title: 'Godkännande misslyckades', description: result.error, variant: 'destructive' }) } else { toast({ title: 'Godkänd', description: 'Fakturan har godkänts' }) fetchInvoice() @@ -89,7 +89,7 @@ export default function SupplierInvoiceDetailPage() { }) const result = await res.json() if (!res.ok) { - toast({ title: 'Fel', description: result.error, variant: 'destructive' }) + toast({ title: 'Betalning misslyckades', description: result.error, variant: 'destructive' }) } else { toast({ title: result.status === 'paid' ? 'Betald' : 'Delbetalning registrerad', @@ -113,7 +113,7 @@ export default function SupplierInvoiceDetailPage() { const res = await fetch(`/api/supplier-invoices/${params.id}/credit`, { method: 'POST' }) const result = await res.json() if (!res.ok) { - toast({ title: 'Fel', description: result.error, variant: 'destructive' }) + toast({ title: 'Kreditering misslyckades', description: result.error, variant: 'destructive' }) } else { toast({ title: 'Kreditfaktura registrerad' }) fetchInvoice() @@ -132,7 +132,7 @@ export default function SupplierInvoiceDetailPage() { const res = await fetch(`/api/supplier-invoices/${params.id}`, { method: 'DELETE' }) const result = await res.json() if (!res.ok) { - toast({ title: 'Fel', description: result.error, variant: 'destructive' }) + toast({ title: 'Kunde inte ta bort faktura', description: result.error, variant: 'destructive' }) } else { toast({ title: 'Borttagen' }) router.push('/supplier-invoices') @@ -172,7 +172,7 @@ export default function SupplierInvoiceDetailPage() {
-

+

Ankomst #{invoice.arrival_number}

diff --git a/app/(dashboard)/supplier-invoices/new/page.tsx b/app/(dashboard)/supplier-invoices/new/page.tsx index be7774fd..25c1659e 100644 --- a/app/(dashboard)/supplier-invoices/new/page.tsx +++ b/app/(dashboard)/supplier-invoices/new/page.tsx @@ -165,11 +165,11 @@ export default function NewSupplierInvoicePage() { function onSubmit(data: FormData) { if (!data.supplier_id) { - toast({ title: 'Fel', description: 'Välj en leverantör', variant: 'destructive' }) + toast({ title: 'Leverantör saknas', description: 'Välj en leverantör innan du fortsätter.', variant: 'destructive' }) return } if (!data.supplier_invoice_number) { - toast({ title: 'Fel', description: 'Ange fakturanummer', variant: 'destructive' }) + toast({ title: 'Fakturanummer saknas', description: 'Ange leverantörens fakturanummer.', variant: 'destructive' }) return } @@ -229,7 +229,7 @@ export default function NewSupplierInvoicePage() {
-

Registrera leverantörsfaktura

+

Registrera leverantörsfaktura

Registrera en inkommande faktura (uppfyller BFL verifikationskrav)

diff --git a/app/(dashboard)/supplier-invoices/page.tsx b/app/(dashboard)/supplier-invoices/page.tsx index a5d73c8c..07d256e5 100644 --- a/app/(dashboard)/supplier-invoices/page.tsx +++ b/app/(dashboard)/supplier-invoices/page.tsx @@ -73,7 +73,7 @@ export default function SupplierInvoicesPage() {
-

Leverantörsfakturor

+

Leverantörsfakturor

Registrera och hantera inkommande fakturor

@@ -112,7 +112,7 @@ export default function SupplierInvoicesPage() { -

{formatAmount(totalUnpaid)} kr

+

{formatAmount(totalUnpaid)} kr

{invoices.filter((i) => !['paid', 'credited'].includes(i.status)).length} fakturor

@@ -126,7 +126,7 @@ export default function SupplierInvoicesPage() { -

{formatAmount(overdueAmount)} kr

+

{formatAmount(overdueAmount)} kr

{overdueCount} fakturor

@@ -138,7 +138,7 @@ export default function SupplierInvoicesPage() { -

{invoices.length}

+

{invoices.length}

diff --git a/app/(dashboard)/suppliers/[id]/page.tsx b/app/(dashboard)/suppliers/[id]/page.tsx index 2583dafe..2ed29c76 100644 --- a/app/(dashboard)/suppliers/[id]/page.tsx +++ b/app/(dashboard)/suppliers/[id]/page.tsx @@ -39,7 +39,7 @@ export default function SupplierDetailPage() { const res = await fetch(`/api/suppliers/${params.id}`) const { data, error } = await res.json() if (error) { - toast({ title: 'Fel', description: error, variant: 'destructive' }) + toast({ title: 'Kunde inte ladda leverantör', description: error, variant: 'destructive' }) } else { setSupplier(data) } @@ -69,7 +69,7 @@ export default function SupplierDetailPage() { const result = await res.json() if (!res.ok) { const fieldErrors = result.errors?.map((e: { field: string; message: string }) => `${e.field}: ${e.message}`).join(', ') - toast({ title: 'Fel', description: fieldErrors || result.error || 'Kunde inte uppdatera leverantör', variant: 'destructive' }) + toast({ title: 'Kunde inte uppdatera leverantör', description: fieldErrors || result.error || 'Försök igen.', variant: 'destructive' }) } else { toast({ title: 'Sparat', description: 'Leverantören har uppdaterats' }) setSupplier({ ...result.data, stats: supplier?.stats }) @@ -90,7 +90,7 @@ export default function SupplierDetailPage() { const res = await fetch(`/api/suppliers/${params.id}`, { method: 'DELETE' }) const result = await res.json() if (!res.ok) { - toast({ title: 'Fel', description: result.error, variant: 'destructive' }) + toast({ title: 'Kunde inte ta bort leverantör', description: result.error, variant: 'destructive' }) } else { toast({ title: 'Borttagen', description: 'Leverantören har tagits bort' }) router.push('/suppliers') @@ -145,7 +145,7 @@ export default function SupplierDetailPage() {
-

{supplier.name}

+

{supplier.name}

{supplierTypeLabels[supplier.supplier_type]} {supplier.org_number && ` | Org.nr: ${supplier.org_number}`} @@ -170,7 +170,7 @@ export default function SupplierDetailPage() { Utestående -

{formatAmount(supplier.stats?.total_outstanding || 0)} kr

+

{formatAmount(supplier.stats?.total_outstanding || 0)} kr

@@ -178,7 +178,7 @@ export default function SupplierDetailPage() { Totalt betalt -

{formatAmount(supplier.stats?.total_paid || 0)} kr

+

{formatAmount(supplier.stats?.total_paid || 0)} kr

@@ -186,7 +186,7 @@ export default function SupplierDetailPage() { Antal fakturor -

{supplier.stats?.invoice_count || 0}

+

{supplier.stats?.invoice_count || 0}

diff --git a/app/(dashboard)/suppliers/page.tsx b/app/(dashboard)/suppliers/page.tsx index 9509d3b1..edf82cb1 100644 --- a/app/(dashboard)/suppliers/page.tsx +++ b/app/(dashboard)/suppliers/page.tsx @@ -43,8 +43,8 @@ export default function SuppliersPage() { if (error) { toast({ - title: 'Fel', - description: 'Kunde inte hämta leverantörer', + title: 'Kunde inte ladda leverantörer', + description: 'Kontrollera din anslutning och försök igen.', variant: 'destructive', }) } else { @@ -71,8 +71,8 @@ export default function SuppliersPage() { if (!response.ok) { const fieldErrors = result.errors?.map((e: { field: string; message: string }) => `${e.field}: ${e.message}`).join(', ') toast({ - title: 'Fel', - description: fieldErrors || result.error || 'Kunde inte skapa leverantör', + title: 'Kunde inte skapa leverantör', + description: fieldErrors || result.error || 'Försök igen.', variant: 'destructive', }) } else { @@ -97,7 +97,7 @@ export default function SuppliersPage() {
-

Leverantörer

+

Leverantörer

Hantera dina leverantörer och deras betalningsuppgifter

diff --git a/app/(dashboard)/transactions/page.tsx b/app/(dashboard)/transactions/page.tsx index b8fc63b2..1d58f604 100644 --- a/app/(dashboard)/transactions/page.tsx +++ b/app/(dashboard)/transactions/page.tsx @@ -76,6 +76,10 @@ export default function TransactionsPage() { // Entity type for tooltip context const [entityType, setEntityType] = useState('enskild_firma') + // Pagination + const [hasMore, setHasMore] = useState(false) + const [isLoadingMore, setIsLoadingMore] = useState(false) + // Set of transaction IDs that are animating out (just categorized) const [exitingIds, setExitingIds] = useState>(new Set()) @@ -93,15 +97,18 @@ export default function TransactionsPage() { }) const transactionsWithMatches = transactions.filter((t) => t.potential_invoice && !t.invoice_id) + const PAGE_SIZE = 200 + async function fetchTransactions() { setIsLoading(true) const { data: txData, error: txError } = await supabase .from('transactions') .select('*') .order('date', { ascending: false }) + .limit(PAGE_SIZE) if (txError) { - toast({ title: 'Fel', description: 'Kunde inte hämta transaktioner', variant: 'destructive' }) + toast({ title: 'Kunde inte ladda transaktioner', description: 'Kontrollera din anslutning och försök igen.', variant: 'destructive' }) setIsLoading(false) return } @@ -149,9 +156,71 @@ export default function TransactionsPage() { })) setTransactions(transactionsWithInvoices) + setHasMore((txData || []).length >= PAGE_SIZE) setIsLoading(false) } + async function loadMoreTransactions() { + setIsLoadingMore(true) + const offset = transactions.length + const { data: txData, error: txError } = await supabase + .from('transactions') + .select('*') + .order('date', { ascending: false }) + .range(offset, offset + PAGE_SIZE - 1) + + if (txError || !txData) { + setIsLoadingMore(false) + return + } + + setHasMore(txData.length >= PAGE_SIZE) + + const potentialInvoiceIds = txData + .filter((t) => t.potential_invoice_id) + .map((t) => t.potential_invoice_id) + + const unbookedTxIds = txData + .filter((t) => !t.journal_entry_id && t.is_business === null) + .map((t) => t.id) + + const [invoiceResult, inboxResult] = await Promise.all([ + potentialInvoiceIds.length > 0 + ? supabase.from('invoices').select('*, customer:customers(*)').in('id', potentialInvoiceIds) + : Promise.resolve({ data: null }), + unbookedTxIds.length > 0 + ? supabase.from('invoice_inbox_items').select('*').in('matched_transaction_id', unbookedTxIds).in('status', ['ready', 'processing']) + : Promise.resolve({ data: null }), + ]) + + let invoiceMap: Record = {} + if (invoiceResult.data) { + invoiceMap = invoiceResult.data.reduce((acc, inv) => { + acc[inv.id] = inv + return acc + }, {} as Record) + } + + let inboxItemMap: Record = {} + if (inboxResult.data) { + inboxItemMap = inboxResult.data.reduce((acc, item) => { + if (item.matched_transaction_id) { + acc[item.matched_transaction_id] = item as InvoiceInboxItem + } + return acc + }, {} as Record) + } + + const newTransactions: TransactionWithInvoice[] = txData.map((t) => ({ + ...t, + potential_invoice: t.potential_invoice_id ? invoiceMap[t.potential_invoice_id] : undefined, + matched_inbox_item: inboxItemMap[t.id] || undefined, + })) + + setTransactions((prev) => [...prev, ...newTransactions]) + setIsLoadingMore(false) + } + async function fetchCategorySuggestions(txIds: string[]) { if (txIds.length === 0) return setIsLoadingSuggestions(true) @@ -161,6 +230,7 @@ export default function TransactionsPage() { headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ transaction_ids: txIds }), }) + if (!response.ok) throw new Error('Failed to fetch suggestions') const data = await response.json() if (data.suggestions) { setCategorySuggestions(data.suggestions) @@ -227,7 +297,7 @@ export default function TransactionsPage() { const result = await response.json() if (!response.ok) { - toast({ title: 'Fel', description: result.error || 'Kunde inte uppdatera transaktion', variant: 'destructive' }) + toast({ title: 'Kategorisering misslyckades', description: result.error || 'Försök igen.', variant: 'destructive' }) setProcessingId(null) return null } @@ -254,10 +324,10 @@ export default function TransactionsPage() { toast({ title: 'Ångrad', description: 'Kategorisering har ångrats' }) } else { const errData = await undoRes.json() - toast({ title: 'Kunde inte ångra', description: errData.error || 'Något gick fel', variant: 'destructive' }) + toast({ title: 'Kunde inte ångra', description: errData.error || 'Kategoriseringen kunde inte ångras. Försök igen.', variant: 'destructive' }) } } catch { - toast({ title: 'Kunde inte ångra', description: 'Något gick fel', variant: 'destructive' }) + toast({ title: 'Kunde inte ångra', description: 'Kategoriseringen kunde inte ångras. Försök igen.', variant: 'destructive' }) } }}> Ångra @@ -290,7 +360,7 @@ export default function TransactionsPage() { return result.journal_entry_id || null } catch { - toast({ title: 'Fel', description: 'Något gick fel vid bokföring', variant: 'destructive' }) + toast({ title: 'Bokföring misslyckades', description: 'Transaktionen kunde inte bokföras. Försök igen.', variant: 'destructive' }) setProcessingId(null) return null } @@ -312,7 +382,7 @@ export default function TransactionsPage() { }) const result = await response.json() if (!response.ok) { - toast({ title: 'Fel', description: result.error || 'Kunde inte matcha faktura', variant: 'destructive' }) + toast({ title: 'Fakturamatchning misslyckades', description: result.error || 'Försök igen.', variant: 'destructive' }) setIsConfirmingMatch(false) return } @@ -350,7 +420,7 @@ export default function TransactionsPage() { setIsConfirmingMatch(false) }, 350) } catch { - toast({ title: 'Fel', description: 'Något gick fel vid matchning', variant: 'destructive' }) + toast({ title: 'Matchning misslyckades', description: 'Transaktionen kunde inte matchas med fakturan. Försök igen.', variant: 'destructive' }) setIsConfirmingMatch(false) } } @@ -364,7 +434,7 @@ export default function TransactionsPage() { }) const result = await response.json() if (!response.ok) { - toast({ title: 'Fel', description: result.error || 'Kunde inte matcha faktura', variant: 'destructive' }) + toast({ title: 'Fakturamatchning misslyckades', description: result.error || 'Försök igen.', variant: 'destructive' }) return false } @@ -390,7 +460,7 @@ export default function TransactionsPage() { toast({ title: 'Faktura matchad', description: `Faktura ${invoiceNumber} markerad som betald` }) return true } catch { - toast({ title: 'Fel', description: 'Något gick fel vid matchning', variant: 'destructive' }) + toast({ title: 'Matchning misslyckades', description: 'Transaktionen kunde inte matchas med fakturan. Försök igen.', variant: 'destructive' }) return false } } @@ -399,7 +469,7 @@ export default function TransactionsPage() { setIsCreating(true) const { data: { user } } = await supabase.auth.getUser() if (!user) { - toast({ title: 'Fel', description: 'Du måste vara inloggad', variant: 'destructive' }) + toast({ title: 'Inloggning krävs', description: 'Du måste vara inloggad för att lägga till transaktioner.', variant: 'destructive' }) setIsCreating(false) return } @@ -420,7 +490,7 @@ export default function TransactionsPage() { .single() if (error) { - toast({ title: 'Fel', description: error.message, variant: 'destructive' }) + toast({ title: 'Kunde inte skapa transaktion', description: error.message, variant: 'destructive' }) } else { toast({ title: 'Transaktion tillagd', description: `${data.description} har lagts till` }) setTransactions([transaction, ...transactions]) @@ -719,23 +789,39 @@ export default function TransactionsPage() { transactions={transactions} onOpenMatchDialog={openMatchDialog} onOpenCategoryDialog={openCategoryDialog} + hasMore={hasMore} + isLoadingMore={isLoadingMore} + onLoadMore={loadMoreTransactions} /> )} {/* Batch mode floating action bar */} {isBatchMode && selectedIds.size > 0 && ( -
- {selectedIds.size} valda - - - +
+ {batchProgress ? ( + <> + + {batchProgress.done}/{batchProgress.total} + +

+ Bokför {batchProgress.done} av {batchProgress.total}... +

+ + ) : ( + <> + {selectedIds.size} valda + + + + + )}
)} diff --git a/app/(onboarding)/onboarding/page.tsx b/app/(onboarding)/onboarding/page.tsx index fef89ae2..769376f6 100644 --- a/app/(onboarding)/onboarding/page.tsx +++ b/app/(onboarding)/onboarding/page.tsx @@ -114,8 +114,7 @@ function OnboardingPageContent() { .upsert({ ...settingsToSave, user_id: user.id }, { onConflict: 'user_id' }) if (error) { - console.error('Error saving settings:', JSON.stringify(error), 'code:', error.code, 'message:', error.message, 'details:', error.details) - console.error('Payload was:', JSON.stringify({ ...settingsToSave, user_id: user.id })) + // Error details available in Sentry via toast context toast({ title: 'Fel', description: error.message || 'Kunde inte spara. Försök igen.', @@ -170,8 +169,8 @@ function OnboardingPageContent() { p_entity_type: stepData.entity_type, }) } - } catch (err) { - console.error('Failed to seed chart of accounts:', err) + } catch { + // Non-critical — chart of accounts can be seeded later } } @@ -423,7 +422,7 @@ function OnboardingPageContent() { background: 'radial-gradient(ellipse at 30% -20%, rgba(255,255,255,0.04) 0%, transparent 50%)', }} /> - + {String(currentStep).padStart(2, '0')}
@@ -439,7 +438,7 @@ function OnboardingPageContent() { height={30} className="invert opacity-90" /> - gnubok + gnubok
{/* Step indicator — inline with logo row */}
@@ -465,7 +464,7 @@ function OnboardingPageContent() { {/* Step title — compact */}
-

+

{stepInfo.title}

diff --git a/app/globals.css b/app/globals.css index 721efca2..d3771a0a 100644 --- a/app/globals.css +++ b/app/globals.css @@ -9,10 +9,10 @@ --background: 0 0% 97%; --foreground: 0 0% 9%; - --card: 0 0% 100%; + --card: 0 0% 99%; --card-foreground: 0 0% 9%; - --popover: 0 0% 100%; + --popover: 0 0% 99%; --popover-foreground: 0 0% 9%; --primary: 0 0% 15%; @@ -22,7 +22,7 @@ --secondary-foreground: 0 0% 9%; --muted: 0 0% 92%; - --muted-foreground: 0 0% 45%; + --muted-foreground: 0 0% 40%; --accent: 0 0% 92%; --accent-foreground: 0 0% 9%; @@ -337,3 +337,21 @@ input[type="number"] { background: hsl(var(--primary) / 0.3); color: hsl(var(--foreground)); } + +/* Respect reduced motion preference */ +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } + + .stagger-enter > * { + animation: none; + opacity: 1; + } + + .hover-lift:hover { + transform: none; + } +} diff --git a/components/bookkeeping/AddAccountDialog.tsx b/components/bookkeeping/AddAccountDialog.tsx index 9ae8d025..a98d0e0e 100644 --- a/components/bookkeeping/AddAccountDialog.tsx +++ b/components/bookkeeping/AddAccountDialog.tsx @@ -118,9 +118,9 @@ export function AddAccountDialog({ open, onOpenChange, onCreated }: AddAccountDi

{isBASMatch && ( -
- -

+

+ +

Kontonummer {accountNumber} finns i BAS-standarden. Använd "BAS-katalog"-fliken för att aktivera standardkonton istället.

diff --git a/components/bookkeeping/ChartOfAccountsManager.tsx b/components/bookkeeping/ChartOfAccountsManager.tsx index 0da7d4ae..30ef1756 100644 --- a/components/bookkeeping/ChartOfAccountsManager.tsx +++ b/components/bookkeeping/ChartOfAccountsManager.tsx @@ -152,6 +152,8 @@ export default function ChartOfAccountsManager() { } async function deleteAccount(account: BASAccount) { + const confirmed = window.confirm(`Vill du ta bort konto ${account.account_number} ${account.account_name}?`) + if (!confirmed) return setDeletingAccount(account.account_number) try { const res = await fetch(`/api/bookkeeping/accounts/${account.account_number}`, { @@ -419,7 +421,7 @@ export default function ChartOfAccountsManager() { @@ -423,16 +416,18 @@ export default function DashboardContent({ firstName, settings, summary, onboard {alertItems.length > 0 && (

Att hantera

-
+
{visibleAlerts}
- {hasMoreAlerts && !showAllAlerts && ( + {hasMoreAlerts && ( )}
@@ -440,7 +435,7 @@ export default function DashboardContent({ firstName, settings, summary, onboard {/* Upcoming deadlines — always visible */} {summary.deadlines && summary.deadlines.length > 0 && ( -
+
)} @@ -452,27 +447,19 @@ export default function DashboardContent({ firstName, settings, summary, onboard
)} - {/* Collapsible details section */} {showMore && ( -
+
{/* Uncategorized transactions warning */} {summary.uncategorizedCount > 0 && (summary.uncategorizedIncome > 0 || summary.uncategorizedExpenses > 0) && (
diff --git a/components/dashboard/DashboardNav.tsx b/components/dashboard/DashboardNav.tsx index fe80f062..6ce26a54 100644 --- a/components/dashboard/DashboardNav.tsx +++ b/components/dashboard/DashboardNav.tsx @@ -140,7 +140,7 @@ export default function DashboardNav({ companyName, entityType, uncategorizedTra