Files
accounted/components/transactions/InvoiceMatchDialog.tsx
T
Jakob Wennberg 3e7fa45ed6 feat: transaction categorization UX improvements and description matching
Add journal entry preview, human-readable account names, auto-apply VAT,
fallback template suggestions, example prompts, invoice match comparison,
and batch result feedback. Also includes user-description-match extension,
describe/batch-describe API routes, improved AI categorization with multi-
suggestion support, and template embedding search.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 16:02:06 +01:00

132 lines
5.0 KiB
TypeScript

'use client'
import { Button } from '@/components/ui/button'
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog'
import { formatCurrency, formatDate } from '@/lib/utils'
import { CheckCircle2, AlertTriangle } from 'lucide-react'
import type { TransactionWithInvoice } from './transaction-types'
interface InvoiceMatchDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
transaction: TransactionWithInvoice | null
isConfirming: boolean
onConfirm: () => void
}
export default function InvoiceMatchDialog({
open,
onOpenChange,
transaction,
isConfirming,
onConfirm,
}: InvoiceMatchDialogProps) {
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>Bekräfta fakturamatchning</DialogTitle>
<DialogDescription>
Vill du koppla denna transaktion till fakturan? Fakturan kommer att markeras som betald.
</DialogDescription>
</DialogHeader>
{transaction?.potential_invoice && (
<div className="space-y-4">
{/* Transaction details */}
<div className="rounded-lg border p-4 space-y-2">
<p className="text-sm font-medium text-muted-foreground">Transaktion</p>
<p className="font-medium">{transaction.description}</p>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">{formatDate(transaction.date)}</span>
<span className="font-medium text-success">
+{formatCurrency(transaction.amount, transaction.currency)}
</span>
</div>
</div>
{/* Invoice details */}
<div className="rounded-lg border p-4 space-y-2">
<p className="text-sm font-medium text-muted-foreground">Faktura</p>
<p className="font-medium">
Faktura {transaction.potential_invoice.invoice_number}
</p>
<p className="text-sm text-muted-foreground">
{transaction.potential_invoice.customer?.name || 'Okänd kund'}
</p>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">
Förfaller: {formatDate(transaction.potential_invoice.due_date)}
</span>
<span className="font-medium">
{formatCurrency(
transaction.potential_invoice.total,
transaction.potential_invoice.currency
)}
</span>
</div>
</div>
{/* Amount comparison */}
{(() => {
const txAmount = transaction.amount
const invAmount = transaction.potential_invoice!.total
const sameCurrency = transaction.currency === transaction.potential_invoice!.currency
const amountsMatch = sameCurrency && Math.abs(txAmount - invAmount) < 0.01
if (amountsMatch) {
return (
<div className="flex items-center gap-2 p-3 rounded-lg bg-success/10 text-success">
<CheckCircle2 className="h-4 w-4 flex-shrink-0" />
<p className="text-sm font-medium">Beloppen stammer</p>
</div>
)
}
const diff = Math.abs(txAmount - invAmount)
return (
<div className="flex items-start gap-2 p-3 rounded-lg bg-amber-500/10 text-amber-700 dark:text-amber-400">
<AlertTriangle className="h-4 w-4 flex-shrink-0 mt-0.5" />
<div className="text-sm">
<p className="font-medium">Beloppen skiljer sig</p>
<p>
Differens: {formatCurrency(diff, transaction.currency)}
{!sameCurrency && ' (olika valutor)'}
</p>
</div>
</div>
)
})()}
{/* What will happen */}
<div className="rounded-lg bg-muted/50 p-4 space-y-2">
<p className="text-sm font-medium">Vid bekräftelse:</p>
<ul className="text-sm text-muted-foreground space-y-1">
<li> Transaktionen kopplas till fakturan</li>
<li> Fakturan markeras som betald</li>
<li> Bokföringsverifikation skapas automatiskt</li>
</ul>
</div>
</div>
)}
<DialogFooter>
<Button
variant="outline"
onClick={() => onOpenChange(false)}
disabled={isConfirming}
>
Avbryt
</Button>
<Button
onClick={onConfirm}
disabled={isConfirming}
>
{isConfirming ? 'Bekräftar...' : 'Bekräfta matchning'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}