'use client' import { useTranslations } from 'next-intl' 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' 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 t = useTranslations('settings_counterparty_templates') const { toast } = useToast() const SOURCE_LABELS: Record = { sie_import: t('source_sie_import'), user_approved: t('source_user_approved'), auto_learned: t('source_auto_learned'), sni_default: t('source_sni_default'), } const VAT_LABELS: Record = { standard_25: '25%', reduced_12: '12%', reduced_6: '6%', reverse_charge: t('vat_reverse_charge'), export: t('vat_export'), exempt: t('vat_exempt'), } 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: t('toast_fetch_failed'), variant: 'destructive' }) } finally { setIsLoading(false) } }, [toast, t]) 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: t('toast_delete_failed'), variant: 'destructive' }) return } setTemplates((prev) => prev.filter((tt) => tt.id !== id)) if (expandedId === id) setExpandedId(null) toast({ title: t('toast_deleted') }) } catch { toast({ title: t('toast_delete_failed'), variant: 'destructive' }) } finally { setDeletingId(null) } } return (
{t('title')} {t('description')} {isLoading ? (
) : templates.length === 0 ? (

{t('empty_title')}

{t('empty_help')}

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

{t('booking_label')}

{isMultiLine ? (
{tt.line_pattern!.map((lp, i) => (
{lp.side === 'debit' ? t('debit_label') : t('credit_label')} {formatAccountWithName(lp.account)} {lp.type === 'vat' && lp.vat_rate && ( {t('vat_paren', { rate: Math.round(lp.vat_rate * 100) })} )} {lp.ratio !== undefined && ( ({Math.round(lp.ratio * 100)}%) )}
))}
) : (
{t('debit_label')} {formatAccountWithName(tt.debit_account)}
{t('credit_label')} {formatAccountWithName(tt.credit_account)}
)}
{/* Metadata */}
{tt.vat_treatment && (
{t('vat_label')} {VAT_LABELS[tt.vat_treatment] || tt.vat_treatment}
)}
{t('occurrence_count_label')} {tt.occurrence_count}
{t('confidence_label')} {Math.round(Number(tt.confidence) * 100)}%
{t('last_seen_label')} {formatDate(tt.last_seen_date)}
{tt.counterparty_aliases && tt.counterparty_aliases.length > 1 && (
{t('aliases_label')} {tt.counterparty_aliases.slice(0, 3).join(', ')}{tt.counterparty_aliases.length > 3 ? ` +${tt.counterparty_aliases.length - 3}` : ''}
)}
{/* Delete */}
)}
) })}
)}
) }