Simpl-Resultat/src/components/import/FilePreviewTable.tsx
Le-King-Fu 49e0bd2c94 feat: implement CSV import wizard with folder-based source detection
Full import pipeline: Rust backend (6 Tauri commands for folder scanning,
file reading, encoding detection, hashing, folder picker), TypeScript
services (DB, import sources, transactions, auto-categorization, user
preferences), utility parsers (French amounts, multi-format dates),
12 React components forming a 7-step wizard (source list, config,
column mapping, preview, duplicate detection, import, report), and
i18n support (FR/EN, ~60 keys each).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 03:38:46 +00:00

102 lines
3.5 KiB
TypeScript

import { useTranslation } from "react-i18next";
import { AlertCircle } from "lucide-react";
import type { ParsedRow } from "../../shared/types";
interface FilePreviewTableProps {
rows: ParsedRow[];
}
export default function FilePreviewTable({
rows,
}: FilePreviewTableProps) {
const { t } = useTranslation();
if (rows.length === 0) {
return (
<div className="text-center py-8 text-[var(--muted-foreground)]">
{t("import.preview.noData")}
</div>
);
}
const errorCount = rows.filter((r) => r.error).length;
return (
<div>
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-semibold">
{t("import.preview.title")}
</h2>
<div className="flex items-center gap-4 text-sm">
<span className="text-[var(--muted-foreground)]">
{t("import.preview.rowCount", { count: rows.length })}
</span>
{errorCount > 0 && (
<span className="flex items-center gap-1 text-red-500">
<AlertCircle size={14} />
{t("import.preview.errorCount", { count: errorCount })}
</span>
)}
</div>
</div>
<div className="overflow-x-auto rounded-xl border border-[var(--border)]">
<table className="w-full text-sm">
<thead>
<tr className="bg-[var(--muted)]">
<th className="px-3 py-2 text-left text-xs font-medium text-[var(--muted-foreground)]">
#
</th>
<th className="px-3 py-2 text-left text-xs font-medium text-[var(--muted-foreground)]">
{t("import.preview.date")}
</th>
<th className="px-3 py-2 text-left text-xs font-medium text-[var(--muted-foreground)]">
{t("import.preview.description")}
</th>
<th className="px-3 py-2 text-right text-xs font-medium text-[var(--muted-foreground)]">
{t("import.preview.amount")}
</th>
<th className="px-3 py-2 text-left text-xs font-medium text-[var(--muted-foreground)]">
{t("import.preview.raw")}
</th>
</tr>
</thead>
<tbody className="divide-y divide-[var(--border)]">
{rows.map((row) => (
<tr
key={row.rowIndex}
className={
row.error
? "bg-red-50 dark:bg-red-950/20"
: "hover:bg-[var(--muted)]"
}
>
<td className="px-3 py-2 text-[var(--muted-foreground)]">
{row.rowIndex + 1}
</td>
<td className="px-3 py-2">
{row.parsed?.date || (
<span className="text-red-500 text-xs">
{row.error || "—"}
</span>
)}
</td>
<td className="px-3 py-2 max-w-xs truncate">
{row.parsed?.description || "—"}
</td>
<td className="px-3 py-2 text-right font-mono">
{row.parsed?.amount != null
? row.parsed.amount.toFixed(2)
: "—"}
</td>
<td className="px-3 py-2 text-xs text-[var(--muted-foreground)] max-w-xs truncate">
{row.raw.join(" | ")}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
}