'use client' import { useState } from 'react' import { useTranslations } from 'next-intl' import { MoreHorizontal, Loader2 } from 'lucide-react' import { Button } from '@/components/ui/button' import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' import { EmptyState } from '@/components/ui/empty-state' import { TH_CLASS, TD_CLASS } from '@/components/ui/dry-table' import { formatCurrency, formatDate, cn } from '@/lib/utils' import type { TransactionWithInvoice } from './transaction-types' /** * "Granska migrerad historik": the review surface for journal-entry match * suggestions the reconciliation sweep persisted (the 0.75-0.89 band). Each * row shows the bank transaction beside its suggested verifikat; confirming * goes through the server-side revalidating bulk endpoint, so a stale pair * degrades to a reported skip, never a wrong link. Per-row fallbacks: open the * match dialog to pick another verifikat, or reject the suggestion (the row * returns to the ordinary "Att bokföra" flow as backstop). */ interface SuggestionReviewListProps { items: TransactionWithInvoice[] /** Bulk/single confirm: resolves when the API call and list refresh are done. */ onConfirm: (transactionIds: string[]) => Promise onReject: (transactionIds: string[]) => Promise onOpenMatchVoucher: (tx: TransactionWithInvoice) => void onRerunMatching: () => Promise rerunning: boolean } export function SuggestionReviewList({ items, onConfirm, onReject, onOpenMatchVoucher, onRerunMatching, rerunning, }: SuggestionReviewListProps) { const t = useTranslations('tx_review') const [busyIds, setBusyIds] = useState>(new Set()) const [bulkBusy, setBulkBusy] = useState(false) const runRows = async (ids: string[], action: (ids: string[]) => Promise) => { setBusyIds((prev) => new Set([...prev, ...ids])) try { await action(ids) } finally { setBusyIds((prev) => { const next = new Set(prev) for (const id of ids) next.delete(id) return next }) } } const confirmAll = async () => { setBulkBusy(true) try { await runRows(items.map((i) => i.id), onConfirm) } finally { setBulkBusy(false) } } return (

{t('intro', { count: items.length })}

{items.length === 0 ? ( ) : (
{items.map((tx) => { const busy = busyIds.has(tx.id) const voucher = tx.potential_voucher return ( ) })}
{t('th_date')} {t('th_description')} {t('th_amount')} {t('th_suggestion')}
{formatDate(tx.date)} {tx.description} {formatCurrency(tx.amount, tx.currency)} {voucher ? ( {voucher.voucher_series}-{voucher.voucher_number} {' · '} {formatDate(voucher.entry_date)} {typeof tx.potential_match_confidence === 'number' && ( <> {' · '}{Math.round(tx.potential_match_confidence * 100)} % )} ) : ( {t('suggestion_gone')} )}
onOpenMatchVoucher(tx)}> {t('open_match_dialog')} void runRows([tx.id], onReject)}> {t('reject')}
)}
) }