feat: AR/AP navigation separation and bookkeeping improvements (#109)

* feat: separate AR/AP/accounting into distinct nav groups (#92)

Split the flat "Finans" sidebar group into three visually distinct
sections — Försäljning (AR), Inköp (AP), and Redovisning — so users
coming from Fortnox immediately find customer invoicing and supplier
invoices as top-level concepts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: journal entry detail view, correction chain, and account name display

- Add journal entry detail page at /bookkeeping/[id] with full entry view
- Add correction chain API and component showing storno relationships
- Add JournalEntryStatusBadge component for entry status display
- Show debit/credit account names in template picker and review dialogs
- Expand client-side BAS account name mapping with additional accounts
- Show account codes on transaction inbox suggestion buttons

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address review feedback — N+1 query, duplicate name, nav dedup

- Batch reverse-lookup into single query per BFS iteration (was N+1)
- Differentiate account 2393 from 2893 in display names
- Extract shared loop for desktop/mobile nav group rendering

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-03-23 20:01:07 +01:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 7ab789d95c
commit 4d4a9a40c9
15 changed files with 942 additions and 114 deletions
@@ -0,0 +1,95 @@
'use client'
import Link from 'next/link'
import { Badge } from '@/components/ui/badge'
import { Info } from 'lucide-react'
import JournalEntryStatusBadge from '@/components/bookkeeping/JournalEntryStatusBadge'
import type { JournalEntry, JournalEntryLine } from '@/types'
interface Props {
currentEntryId: string
chain: JournalEntry[]
}
function getRole(entry: JournalEntry): { label: string; color: string } {
if (entry.source_type === 'storno') {
return { label: 'Storno', color: 'bg-destructive' }
}
if (entry.source_type === 'correction') {
return { label: 'Rättelse', color: 'bg-primary' }
}
return { label: 'Original', color: 'bg-muted-foreground' }
}
function getTotal(entry: JournalEntry): number {
const lines = (entry.lines || []) as JournalEntryLine[]
return lines.reduce((sum, l) => sum + (Number(l.debit_amount) || 0), 0)
}
export default function CorrectionChain({ currentEntryId, chain }: Props) {
if (chain.length === 0) return null
// Combine current entry isn't in chain — chain is "other" entries
// Sort chronologically
const sorted = [...chain].sort(
(a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
)
return (
<div className="space-y-3">
<h3 className="text-sm font-medium">Ändringskedja</h3>
<div className="rounded-lg bg-muted/50 border p-3 flex gap-2 text-sm text-muted-foreground">
<Info className="h-4 w-4 shrink-0 mt-0.5" />
<p>
Bokförda verifikationer kan inte ändras direkt. Istället skapas en stornoverifikation
som nollställer den ursprungliga, och en ny rättelsepost med de korrekta uppgifterna.
</p>
</div>
<div className="relative space-y-0">
{/* Vertical line connecting nodes */}
<div className="absolute left-[7px] top-3 bottom-3 w-px bg-border" />
{sorted.map((entry) => {
const role = getRole(entry)
const total = getTotal(entry)
const isCurrent = entry.id === currentEntryId
return (
<Link
key={entry.id}
href={`/bookkeeping/${entry.id}`}
className="block"
>
<div className={`relative pl-7 py-2 rounded-md transition-colors hover:bg-muted/50 ${isCurrent ? 'bg-muted/30' : ''}`}>
{/* Timeline dot */}
<div className={`absolute left-0.5 top-[18px] h-3 w-3 rounded-full border-2 border-background ${role.color}`} />
<div className="flex items-center gap-2 flex-wrap">
<span className="text-xs font-medium text-muted-foreground">{role.label}</span>
<span className="font-mono text-sm">
{entry.voucher_series}{entry.voucher_number}
</span>
<span className="text-sm text-muted-foreground">{entry.entry_date}</span>
<JournalEntryStatusBadge entry={entry} showStatus={false} />
{isCurrent && (
<Badge variant="outline" className="text-[10px] px-1.5 py-0">
Aktuell
</Badge>
)}
<span className="ml-auto text-sm tabular-nums text-muted-foreground">
{total.toLocaleString('sv-SE', { minimumFractionDigits: 2 })} kr
</span>
</div>
{entry.description && (
<p className="text-xs text-muted-foreground truncate mt-0.5">{entry.description}</p>
)}
</div>
</Link>
)
})}
</div>
</div>
)
}
@@ -1,6 +1,7 @@
'use client'
import { useState, useEffect } from 'react'
import { useRouter } from 'next/navigation'
import {
Dialog,
DialogContent,
@@ -33,6 +34,7 @@ interface Props {
export default function CorrectionEntryDialog({ entry, open, onOpenChange, onCorrected }: Props) {
const { toast } = useToast()
const router = useRouter()
const [accounts, setAccounts] = useState<BASAccount[]>([])
const [lines, setLines] = useState<CorrectionLine[]>([])
const [isSubmitting, setIsSubmitting] = useState(false)
@@ -104,12 +106,23 @@ export default function CorrectionEntryDialog({ entry, open, onOpenChange, onCor
body: JSON.stringify({ lines: apiLines }),
})
const result = await res.json()
if (!res.ok) {
const { error } = await res.json()
throw new Error(error || 'Failed to create correction')
throw new Error(result.error || 'Failed to create correction')
}
toast({ title: 'Ändringsverifikation skapad', description: 'Storno och rättelse har bokförts.' })
const correctedId = result.data?.corrected?.id
toast({
title: 'Ändringsverifikation skapad',
description: 'Storno och rättelse har bokförts.',
action: correctedId ? (
<Button variant="outline" size="sm" onClick={() => router.push(`/bookkeeping/${correctedId}`)}>
Visa rättelsen
</Button>
) : undefined,
})
onOpenChange(false)
onCorrected()
} catch (err) {
@@ -130,6 +143,16 @@ export default function CorrectionEntryDialog({ entry, open, onOpenChange, onCor
<DialogTitle>Skapa ändringsverifikation</DialogTitle>
</DialogHeader>
{/* Storno explanation */}
<div className="rounded-lg bg-muted/50 border p-3 text-sm text-muted-foreground">
<p className="font-medium text-foreground mb-1">Hur fungerar en ändringsverifikation?</p>
<p>En bokförd verifikation kan inte ändras direkt. Istället skapas automatiskt:</p>
<ol className="list-decimal list-inside mt-1 space-y-0.5">
<li>En <strong>stornoverifikation</strong> som nollställer den ursprungliga</li>
<li>En ny verifikation med dina rättade uppgifter</li>
</ol>
</div>
{/* Original entry (read-only) */}
<div className="space-y-2">
<div className="flex items-center gap-2 text-sm text-muted-foreground">
+24 -8
View File
@@ -1,6 +1,7 @@
'use client'
import { useState, useEffect, useCallback } from 'react'
import Link from 'next/link'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
@@ -11,6 +12,7 @@ import { Input } from '@/components/ui/input'
import { AccountNumber } from '@/components/ui/account-number'
import JournalEntryAttachments from '@/components/bookkeeping/JournalEntryAttachments'
import CorrectionEntryDialog from '@/components/bookkeeping/CorrectionEntryDialog'
import JournalEntryStatusBadge from '@/components/bookkeeping/JournalEntryStatusBadge'
import type { JournalEntry, JournalEntryLine } from '@/types'
const NEEDS_ATTACHMENT = new Set([
@@ -229,12 +231,19 @@ export default function JournalEntryList({ periodId }: Props) {
) : (
<ChevronRight className="h-4 w-4 shrink-0" />
)}
<span className="font-mono text-sm text-muted-foreground w-16">
<Link
href={`/bookkeeping/${entry.id}`}
className="font-mono text-sm text-primary hover:underline w-16"
onClick={(e) => e.stopPropagation()}
>
{entry.voucher_series}{entry.voucher_number}
</span>
</Link>
<span className="text-sm text-muted-foreground w-24">
{entry.entry_date}
</span>
{(entry.status === 'reversed' || entry.source_type === 'storno' || entry.source_type === 'correction') && (
<JournalEntryStatusBadge entry={entry} showStatus={entry.status === 'reversed'} />
)}
<span className="flex-1 truncate">{entry.description}</span>
{attachmentCounts[entry.id] ? (
<span className="flex items-center gap-0.5 text-muted-foreground mr-1" title={`${attachmentCounts[entry.id]} underlag`}>
@@ -257,9 +266,13 @@ export default function JournalEntryList({ periodId }: Props) {
) : (
<ChevronRight className="h-4 w-4 shrink-0" />
)}
<span className="font-mono text-sm text-muted-foreground">
<Link
href={`/bookkeeping/${entry.id}`}
className="font-mono text-sm text-primary hover:underline"
onClick={(e) => e.stopPropagation()}
>
{entry.voucher_series}{entry.voucher_number}
</span>
</Link>
<span className="text-sm text-muted-foreground">
{entry.entry_date}
</span>
@@ -383,8 +396,11 @@ export default function JournalEntryList({ periodId }: Props) {
onCountChange={(c) => handleAttachmentCountChange(entry.id, c)}
/>
{entry.status === 'posted' && entry.source_type !== 'storno' && entry.source_type !== 'correction' && (
<div className="mt-4 pt-3 border-t flex gap-2">
<div className="mt-4 pt-3 border-t flex gap-2">
<Button variant="outline" size="sm" asChild>
<Link href={`/bookkeeping/${entry.id}`}>Visa detaljer</Link>
</Button>
{entry.status === 'posted' && entry.source_type !== 'storno' && entry.source_type !== 'correction' && (
<Button
variant="outline"
size="sm"
@@ -392,8 +408,8 @@ export default function JournalEntryList({ periodId }: Props) {
>
Skapa ändringsverifikation
</Button>
</div>
)}
)}
</div>
</CardContent>
)}
</Card>
@@ -0,0 +1,58 @@
import { Badge } from '@/components/ui/badge'
import type { JournalEntry } from '@/types'
const statusConfig: Record<string, { label: string; variant: 'default' | 'secondary' | 'success' | 'warning' | 'destructive' }> = {
draft: { label: 'Utkast', variant: 'secondary' },
posted: { label: 'Bokförd', variant: 'success' },
reversed: { label: 'Omförd', variant: 'warning' },
cancelled: { label: 'Makulerad', variant: 'secondary' },
}
const sourceTypeBadges: Record<string, { label: string; variant: 'default' | 'secondary' | 'success' | 'warning' | 'destructive' }> = {
storno: { label: 'Storno', variant: 'destructive' },
correction: { label: 'Rättelse', variant: 'default' },
}
export const sourceTypeLabels: Record<string, string> = {
manual: 'Manuell',
bank_transaction: 'Banktransaktion',
invoice_created: 'Faktura skapad',
invoice_paid: 'Fakturabetalning',
credit_note: 'Kreditfaktura',
salary_payment: 'Lön',
opening_balance: 'Ingående balans',
year_end: 'Årsbokslut',
storno: 'Storno',
correction: 'Rättelse',
import: 'Import',
system: 'System',
supplier_invoice_registered: 'Leverantörsfaktura',
supplier_invoice_paid: 'Leverantörsbetalning',
supplier_invoice_cash_payment: 'Kontantbetalning',
currency_revaluation: 'Valutaomvärdering',
}
interface Props {
entry: JournalEntry
showStatus?: boolean
}
export default function JournalEntryStatusBadge({ entry, showStatus = true }: Props) {
const status = statusConfig[entry.status]
const sourceType = sourceTypeBadges[entry.source_type]
return (
<span className="inline-flex items-center gap-1">
{showStatus && status && (
<Badge variant={status.variant} className="text-[10px] px-1.5 py-0">
{status.label}
</Badge>
)}
{sourceType && (
<Badge variant={sourceType.variant} className="text-[10px] px-1.5 py-0">
{sourceType.label}
</Badge>
)}
</span>
)
}
+100 -85
View File
@@ -57,23 +57,28 @@ const navItems: NavItem[] = [
{ href: '/', label: 'Översikt', icon: LayoutDashboard, group: 'main' },
{ href: '/kpi', label: 'Nyckeltal', icon: TrendingUp, group: 'main' },
{ href: '/deadlines', label: 'Deadlines', icon: Calendar, group: 'main' },
{ href: '/invoices', label: 'Fakturor', icon: Receipt, group: 'finans' },
{ href: '/customers', label: 'Kunder', icon: Users, group: 'finans' },
{ href: '/expenses', label: 'Utgifter', icon: Wallet, group: 'finans' },
// AR — Accounts Receivable
{ href: '/invoices', label: 'Fakturor', icon: Receipt, group: 'försäljning' },
{ href: '/customers', label: 'Kunder', icon: Users, group: 'försäljning' },
// AP — Accounts Payable
{ href: '/expenses', label: 'Utgifter', icon: Wallet, group: 'inköp' },
// Temporarily hidden pending module rework (see feedback #49)
{ href: '/suppliers', label: 'Leverantörer', icon: Building2, group: 'finans', hidden: true },
{ href: '/supplier-invoices', label: 'Leverantörsfakturor', icon: FileInput, group: 'finans', hidden: true },
{ href: '/transactions', label: 'Transaktioner', icon: ArrowLeftRight, group: 'finans' },
{ href: '/bookkeeping', label: 'Bokföring', icon: BookOpen, group: 'finans' },
{ href: '/reports', label: 'Rapporter', icon: BarChart3, group: 'finans' },
{ href: '/import', label: 'Importera', icon: Upload, group: 'finans' },
{ href: '/suppliers', label: 'Leverantörer', icon: Building2, group: 'inköp', hidden: true },
{ href: '/supplier-invoices', label: 'Leverantörsfakturor', icon: FileInput, group: 'inköp', hidden: true },
// General accounting
{ href: '/transactions', label: 'Transaktioner', icon: ArrowLeftRight, group: 'redovisning' },
{ href: '/bookkeeping', label: 'Bokföring', icon: BookOpen, group: 'redovisning' },
{ href: '/reports', label: 'Rapporter', icon: BarChart3, group: 'redovisning' },
{ href: '/import', label: 'Importera', icon: Upload, group: 'redovisning' },
{ href: '/help', label: 'Hjälp', icon: HelpCircle, group: 'övrigt' },
{ href: '/settings', label: 'Inställningar', icon: Settings, group: 'övrigt' },
]
const groupLabels: Record<string, string> = {
main: 'Huvudmeny',
finans: 'Finans',
försäljning: 'Försäljning',
inköp: 'Inköp',
redovisning: 'Redovisning',
övrigt: 'Övrigt',
}
@@ -124,9 +129,15 @@ export default function DashboardNav({ companyName, entityType, uncategorizedTra
)
const mainItems = filteredItems.filter(i => i.group === 'main')
const finansItems = filteredItems.filter(i => i.group === 'finans')
const övrigtItems = filteredItems.filter(i => i.group === 'övrigt')
// Groups rendered as distinct sidebar sections (AR, AP, Accounting)
const sidebarGroups = [
{ key: 'försäljning', items: filteredItems.filter(i => i.group === 'försäljning'), spacing: 'mb-4' },
{ key: 'inköp', items: filteredItems.filter(i => i.group === 'inköp'), spacing: 'mb-4' },
{ key: 'redovisning', items: filteredItems.filter(i => i.group === 'redovisning'), spacing: 'mb-6' },
] as const
const mobileNavItems = [
{ href: '/', label: 'Översikt', icon: LayoutDashboard },
{ href: '/invoices', label: 'Fakturor', icon: Receipt },
@@ -179,44 +190,46 @@ export default function DashboardNav({ companyName, entityType, uncategorizedTra
</div>
</div>
{/* Finans group */}
<div className="mb-6">
<p className="px-3 mb-1.5 text-[10px] font-semibold text-muted-foreground uppercase tracking-[0.08em]">
{groupLabels.finans}
</p>
<div className="space-y-px">
{finansItems.map((item) => {
const Icon = item.icon
const active = isActive(item.href)
const badge = item.href === '/transactions' && uncategorizedTransactionCount > 0
? uncategorizedTransactionCount
: null
return (
<Link
key={item.href}
href={item.href}
className={cn(
'group flex items-center px-3 py-[7px] text-[13px] transition-colors duration-150 rounded-lg',
active
? 'bg-primary/12 text-foreground font-medium'
: 'text-muted-foreground hover:text-foreground hover:bg-muted/40'
)}
>
<Icon className={cn(
"mr-2.5 h-[15px] w-[15px] flex-shrink-0",
active ? "text-primary" : "text-muted-foreground group-hover:text-foreground"
)} />
<span className="flex-1">{item.label}</span>
{badge !== null && (
<span className="ml-auto min-w-[18px] h-[18px] flex items-center justify-center rounded-full bg-primary/15 text-primary text-[10px] font-semibold px-1">
{badge > 99 ? '99+' : badge}
</span>
)}
</Link>
)
})}
{/* AR / AP / Accounting groups */}
{sidebarGroups.map(({ key, items, spacing }) => (
<div key={key} className={spacing}>
<p className="px-3 mb-1.5 text-[10px] font-semibold text-muted-foreground uppercase tracking-[0.08em]">
{groupLabels[key]}
</p>
<div className="space-y-px">
{items.map((item) => {
const Icon = item.icon
const active = isActive(item.href)
const badge = item.href === '/transactions' && uncategorizedTransactionCount > 0
? uncategorizedTransactionCount
: null
return (
<Link
key={item.href}
href={item.href}
className={cn(
'group flex items-center px-3 py-[7px] text-[13px] transition-colors duration-150 rounded-lg',
active
? 'bg-primary/12 text-foreground font-medium'
: 'text-muted-foreground hover:text-foreground hover:bg-muted/40'
)}
>
<Icon className={cn(
"mr-2.5 h-[15px] w-[15px] flex-shrink-0",
active ? "text-primary" : "text-muted-foreground group-hover:text-foreground"
)} />
<span className="flex-1">{item.label}</span>
{badge !== null && (
<span className="ml-auto min-w-[18px] h-[18px] flex items-center justify-center rounded-full bg-primary/15 text-primary text-[10px] font-semibold px-1">
{badge > 99 ? '99+' : badge}
</span>
)}
</Link>
)
})}
</div>
</div>
</div>
))}
{/* Övrigt group - collapsible */}
<div className="mb-4">
@@ -416,43 +429,45 @@ export default function DashboardNav({ companyName, entityType, uncategorizedTra
})}
</div>
{/* Finans divider */}
<div className="flex items-center gap-3 my-1.5 px-3">
<span className="text-[10px] font-semibold text-muted-foreground/60 uppercase tracking-[0.08em]">Finans</span>
<div className="flex-1 h-px bg-border/30" />
</div>
{/* Finance items */}
<div className="space-y-0.5">
{finansItems.map((item) => {
const Icon = item.icon
const active = isActive(item.href)
const badge = item.href === '/transactions' && uncategorizedTransactionCount > 0
? uncategorizedTransactionCount
: null
return (
<Link
key={item.href}
href={item.href}
onClick={closeMobileMenu}
className={cn(
'flex items-center gap-3 px-3 min-h-[44px] rounded-lg transition-colors',
active
? 'bg-primary/10 text-primary font-medium'
: 'text-foreground active:bg-muted/60'
)}
>
<Icon className={cn("h-[18px] w-[18px] flex-shrink-0", active ? "text-primary" : "text-muted-foreground")} />
<span className="text-sm flex-1">{item.label}</span>
{badge !== null && (
<span className="min-w-[20px] h-[20px] flex items-center justify-center rounded-full bg-primary/15 text-primary text-[10px] font-semibold px-1.5">
{badge > 99 ? '99+' : badge}
</span>
)}
</Link>
)
})}
</div>
{/* AR / AP / Accounting groups (mobile) */}
{sidebarGroups.map(({ key, items }) => (
<div key={key}>
<div className="flex items-center gap-3 my-1.5 px-3">
<span className="text-[10px] font-semibold text-muted-foreground/60 uppercase tracking-[0.08em]">{groupLabels[key]}</span>
<div className="flex-1 h-px bg-border/30" />
</div>
<div className="space-y-0.5">
{items.map((item) => {
const Icon = item.icon
const active = isActive(item.href)
const badge = item.href === '/transactions' && uncategorizedTransactionCount > 0
? uncategorizedTransactionCount
: null
return (
<Link
key={item.href}
href={item.href}
onClick={closeMobileMenu}
className={cn(
'flex items-center gap-3 px-3 min-h-[44px] rounded-lg transition-colors',
active
? 'bg-primary/10 text-primary font-medium'
: 'text-foreground active:bg-muted/60'
)}
>
<Icon className={cn("h-[18px] w-[18px] flex-shrink-0", active ? "text-primary" : "text-muted-foreground")} />
<span className="text-sm flex-1">{item.label}</span>
{badge !== null && (
<span className="min-w-[20px] h-[20px] flex items-center justify-center rounded-full bg-primary/15 text-primary text-[10px] font-semibold px-1.5">
{badge > 99 ? '99+' : badge}
</span>
)}
</Link>
)
})}
</div>
</div>
))}
{/* Övrigt divider */}
<div className="flex items-center gap-3 my-1.5 px-3">
@@ -9,6 +9,7 @@ import { formatCurrency, formatDate } from '@/lib/utils'
import { ArrowUpRight, ArrowDownRight, Check, Paperclip, ChevronDown, ChevronUp, AlertTriangle } from 'lucide-react'
import { getDefaultAccountForCategory } from '@/lib/bookkeeping/category-mapping'
import type { BookingTemplate } from '@/lib/bookkeeping/booking-templates'
import { formatAccountWithName } from '@/lib/bookkeeping/client-account-names'
import JournalEntryPreview from './JournalEntryPreview'
import AccountCombobox from '@/components/bookkeeping/AccountCombobox'
import DocumentUploadZone from '@/components/bookkeeping/DocumentUploadZone'
@@ -194,6 +195,11 @@ export default function QuickReviewDialog({
</button>
)}
</div>
{template && (
<p className="mt-1.5 text-xs font-mono text-muted-foreground">
D: {formatAccountWithName(template.debit_account)} → K: {formatAccountWithName(template.credit_account)}
</p>
)}
</div>
{/* Template special rules */}
@@ -424,6 +424,11 @@ export default function SwipeCategorizationView({
Byt mall
</button>
</div>
{selectedTemplate && (
<p className="mt-1.5 text-xs font-mono text-muted-foreground">
D: {formatAccountWithName(selectedTemplate.debit_account)} → K: {formatAccountWithName(selectedTemplate.credit_account)}
</p>
)}
</div>
{/* Template special rules warning */}
+2 -1
View File
@@ -12,6 +12,7 @@ import {
type BookingTemplate,
type TemplateGroup,
} from '@/lib/bookkeeping/booking-templates'
import { formatAccountWithName } from '@/lib/bookkeeping/client-account-names'
import type { EntityType } from '@/types'
import type { SuggestedTemplate } from '@/lib/transactions/category-suggestions'
@@ -92,7 +93,7 @@ function TemplateCard({ template, selected, onClick, compact }: TemplateCardProp
</p>
<div className="flex items-center gap-2 mt-1 flex-wrap">
<span className="text-xs font-mono text-muted-foreground">
D: {template.debit_account} &middot; K: {template.credit_account}
D: {formatAccountWithName(template.debit_account)} &middot; K: {formatAccountWithName(template.credit_account)}
</span>
{vatLabel && (
<Badge
@@ -184,7 +184,7 @@ export default function TransactionInboxCard({
key={ts.template_id}
size="sm"
variant={idx === 0 ? 'default' : 'outline'}
className="h-9 text-xs"
className="h-auto py-1.5 text-xs"
onClick={() => {
if (onOpenTemplateReview && tmpl) {
onOpenTemplateReview(transaction, ts.template_id)
@@ -194,13 +194,17 @@ export default function TransactionInboxCard({
}}
disabled={isProcessing || isDisabled}
>
{isProcessing && idx === 0 ? (
<Loader2 className="mr-1.5 h-3 w-3 animate-spin" />
) : null}
{ts.name_sv}
<span className="ml-1 opacity-70 font-normal">
({ts.debit_account})
</span>
<div className="flex flex-col items-start">
<div className="flex items-center">
{isProcessing && idx === 0 ? (
<Loader2 className="mr-1.5 h-3 w-3 animate-spin" />
) : null}
{ts.name_sv}
</div>
<span className="opacity-70 font-normal font-mono text-[10px]">
D: {ts.debit_account} → K: {ts.credit_account}
</span>
</div>
</Button>
)
})}