import { useTranslation } from "react-i18next"; import { AlertCircle, ArrowDownLeft, ArrowUpRight, RefreshCw } from "lucide-react"; import type { ParsedRow } from "../../shared/types"; import { isRowErrorKey, summarizeParsedRows } from "../../utils/importFormat"; import RepairPathNotice from "./RepairPathNotice"; /** Rows rendered in the table. The recap above it always covers the whole file. */ const DISPLAYED_ROWS = 20; interface FilePreviewTableProps { /** ALL parsed rows — the recap is meaningless on a truncated sample. */ rows: ParsedRow[]; onFlipSigns?: () => void; isFlipping?: boolean; } export default function FilePreviewTable({ rows, onFlipSigns, isFlipping = false, }: FilePreviewTableProps) { const { t, i18n } = useTranslation(); if (rows.length === 0) { return (
{t("import.preview.noData")}
); } const totals = summarizeParsedRows(rows); const displayedRows = rows.slice(0, DISPLAYED_ROWS); const currency = new Intl.NumberFormat( i18n.language === "fr" ? "fr-CA" : "en-CA", { style: "currency", currency: "CAD" } ); // Row errors are i18n keys (#325); anything else reaches us verbatim. const errorText = (error: string) => isRowErrorKey(error) ? t(error) : error; return (

{t("import.preview.title")}

{t("import.preview.rowCount", { count: rows.length })} {totals.errorCount > 0 && ( {t("import.preview.errorCount", { count: totals.errorCount })} )}
{/* The signed recap (#329) — the last control before the database, and the only one that looks at what the amounts MEAN. A statement showing zero inflows next to a payroll line is wrong on its face, whatever produced it. Computed over every row, never over the twenty displayed below. */}

{t("import.preview.outflowCount", { count: totals.outflowCount })}

{currency.format(totals.outflowTotal)}

{t("import.preview.inflowCount", { count: totals.inflowCount })}

{currency.format(totals.inflowTotal)}

{t("import.preview.errorRows")}

0 ? "text-[var(--negative)]" : "text-[var(--muted-foreground)]" }`} > {totals.errorCount}

{onFlipSigns && (

{t("import.preview.flipSignsHint")}

)}
{/* The repair path (#330), stated where the correction is offered. A user who flips the signs here has just learned that earlier imports of the same source read backwards; re-importing those files is the natural next move and it double-books every row instead of fixing them. */}
{displayedRows.map((row) => ( ))}
# {t("import.preview.date")} {t("import.preview.description")} {t("import.preview.amount")} {t("import.preview.raw")}
{row.rowIndex + 1} {row.parsed?.date || ( {row.error ? errorText(row.error) : "—"} )} {row.parsed?.description || "—"} {row.parsed?.amount != null ? row.parsed.amount.toFixed(2) : "—"} {row.raw.map((cell, i) => ( {i > 0 && {'·'}} {cell} ))}
{rows.length > displayedRows.length && (

{t("import.preview.moreRows", { count: rows.length - displayedRows.length, })}

)}
); }