'use client' import { useState, useEffect, useCallback } from 'react' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { useToast } from '@/components/ui/use-toast' import { Loader2, Trash2, Users, ChevronDown } from 'lucide-react' import { formatAccountWithName } from '@/lib/bookkeeping/client-account-names' import { formatCounterpartyName } from '@/lib/bookkeeping/counterparty-templates' import type { CategorizationTemplate } from '@/types' const SOURCE_LABELS: Record = { sie_import: 'SIE-import', user_approved: 'Godkänd', auto_learned: 'Automatisk', sni_default: 'Standard', } const VAT_LABELS: Record = { standard_25: '25%', reduced_12: '12%', reduced_6: '6%', reverse_charge: 'Omvänd', export: 'Export', exempt: 'Momsfri', } function formatDate(iso: string | null) { if (!iso) return '—' return new Date(iso).toLocaleDateString('sv-SE', { year: 'numeric', month: 'short', day: 'numeric', }) } function confidenceColor(c: number): string { if (c >= 0.8) return 'text-emerald-600 dark:text-emerald-400' if (c >= 0.5) return 'text-amber-600 dark:text-amber-400' return 'text-muted-foreground' } export function CounterpartyTemplatesPanel() { const { toast } = useToast() const [templates, setTemplates] = useState([]) const [isLoading, setIsLoading] = useState(true) const [deletingId, setDeletingId] = useState(null) const [expandedId, setExpandedId] = useState(null) const fetchTemplates = useCallback(async () => { try { const res = await fetch('/api/settings/counterparty-templates') const json = await res.json() if (json.data) { setTemplates(json.data) } } catch { toast({ title: 'Fel', description: 'Kunde inte hämta mallar', variant: 'destructive' }) } finally { setIsLoading(false) } }, [toast]) useEffect(() => { fetchTemplates() }, [fetchTemplates]) async function handleDelete(id: string) { setDeletingId(id) try { const res = await fetch('/api/settings/counterparty-templates', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id }), }) if (!res.ok) { toast({ title: 'Fel', description: 'Kunde inte ta bort mall', variant: 'destructive' }) return } setTemplates((prev) => prev.filter((t) => t.id !== id)) if (expandedId === id) setExpandedId(null) toast({ title: 'Mall borttagen' }) } catch { toast({ title: 'Fel', description: 'Kunde inte ta bort mall', variant: 'destructive' }) } finally { setDeletingId(null) } } return (
Bokföringsmallar Inlärda mönster som automatiskt föreslår kontering baserat på motpart. Mallar skapas från SIE-import och när du godkänner kategoriseringar. {isLoading ? (
) : templates.length === 0 ? (

Inga mallar ännu.

Mallar skapas automatiskt när du kategoriserar transaktioner eller importerar en SIE-fil.

) : (
{templates.map((t) => { const isExpanded = expandedId === t.id const isMultiLine = t.line_pattern && t.line_pattern.length > 0 return (
{/* Clickable summary row */} {/* Expanded detail */} {isExpanded && (
{/* Account lines */}

Kontering

{isMultiLine ? (
{t.line_pattern!.map((lp, i) => (
{lp.side === 'debit' ? 'Debet' : 'Kredit'} {formatAccountWithName(lp.account)} {lp.type === 'vat' && lp.vat_rate && ( ({Math.round(lp.vat_rate * 100)}% moms) )} {lp.ratio !== undefined && ( ({Math.round(lp.ratio * 100)}%) )}
))}
) : (
Debet {formatAccountWithName(t.debit_account)}
Kredit {formatAccountWithName(t.credit_account)}
)}
{/* Metadata */}
{t.vat_treatment && (
Moms {VAT_LABELS[t.vat_treatment] || t.vat_treatment}
)}
Antal bokföringar {t.occurrence_count}
Säkerhet {Math.round(Number(t.confidence) * 100)}%
Senast använd {formatDate(t.last_seen_date)}
{t.counterparty_aliases && t.counterparty_aliases.length > 1 && (
Alias {t.counterparty_aliases.slice(0, 3).join(', ')}{t.counterparty_aliases.length > 3 ? ` +${t.counterparty_aliases.length - 3}` : ''}
)}
{/* Delete */}
)}
) })}
)}
) }