From 5113f6eca3693c533e56706ecd91f022503d2f57 Mon Sep 17 00:00:00 2001 From: Mattsson <111893710+mattssonn@users.noreply.github.com> Date: Mon, 23 Mar 2026 20:57:43 +0100 Subject: [PATCH] feat: add drill-down navigation between reports and general ledger (#112) Enable clicking account rows in Trial Balance, Income Statement, and Balance Sheet to navigate to the Huvudbok filtered to that account. Voucher numbers in the Huvudbok now link to the journal entry detail page. Breadcrumb trail supports tracing back through the navigation chain. Co-authored-by: Claude Opus 4.6 (1M context) --- app/(dashboard)/reports/page.tsx | 347 +++++++++++++++++++++++-------- lib/reports/general-ledger.ts | 5 +- 2 files changed, 268 insertions(+), 84 deletions(-) diff --git a/app/(dashboard)/reports/page.tsx b/app/(dashboard)/reports/page.tsx index e14196d2..903da628 100644 --- a/app/(dashboard)/reports/page.tsx +++ b/app/(dashboard)/reports/page.tsx @@ -1,12 +1,13 @@ 'use client' -import React, { useState, useEffect } from 'react' +import React, { useState, useEffect, useCallback } from 'react' +import Link from 'next/link' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Label } from '@/components/ui/label' import { Badge } from '@/components/ui/badge' import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs' -import { Download, AlertCircle, ChevronDown, ChevronRight } from 'lucide-react' +import { Download, AlertCircle, ChevronDown, ChevronRight, ArrowRight } from 'lucide-react' import { AccountNumber } from '@/components/ui/account-number' import { NEDeclarationView } from '@/components/reports/NEDeclarationView' import { INK2DeclarationView } from '@/components/reports/INK2DeclarationView' @@ -28,6 +29,20 @@ function formatAmount(amount: number): string { return amount.toLocaleString('sv-SE', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) } +// Breadcrumb trail for drill-down navigation +interface DrillDownStep { + tab: string + label: string + accountNumber?: string +} + +const TAB_LABELS: Record = { + 'trial-balance': 'Saldobalans', + 'income-statement': 'Resultaträkning', + 'balance-sheet': 'Balansräkning', + 'huvudbok': 'Huvudbok', +} + export default function ReportsPage() { const [periods, setPeriods] = useState([]) const [selectedPeriod, setSelectedPeriod] = useState('') @@ -35,6 +50,33 @@ export default function ReportsPage() { const [entityType, setEntityType] = useState(null) const [isLoadingInit, setIsLoadingInit] = useState(true) + // Drill-down state: when navigating from a report to the GL for a specific account + const [glAccountFilter, setGlAccountFilter] = useState(null) + const [drillDownTrail, setDrillDownTrail] = useState([]) + + const navigateToAccount = useCallback((accountNumber: string) => { + setDrillDownTrail((prev) => [ + ...prev, + { tab: activeTab, label: TAB_LABELS[activeTab] || activeTab }, + ]) + setGlAccountFilter(accountNumber) + setActiveTab('huvudbok') + }, [activeTab]) + + const handleTabChange = useCallback((tab: string) => { + // Manual tab change clears drill-down state + setActiveTab(tab) + setGlAccountFilter(null) + setDrillDownTrail([]) + }, []) + + const navigateBack = useCallback((stepIndex: number) => { + const step = drillDownTrail[stepIndex] + setActiveTab(step.tab) + setGlAccountFilter(null) + setDrillDownTrail(drillDownTrail.slice(0, stepIndex)) + }, [drillDownTrail]) + async function fetchPeriods() { const res = await fetch('/api/bookkeeping/fiscal-periods') const { data } = await res.json() @@ -134,12 +176,33 @@ export default function ReportsPage() { {selectedPeriod ? ( - + <> + {/* Drill-down breadcrumb */} + {drillDownTrail.length > 0 && ( + + )} + + {/* Mobile: compact select dropdown */}