Simpl-Resultat/src/components/import/DuplicateCheckPanel.tsx
Le-King-Fu f7fb6910b6 feat: add dark mode with famille-website-inspired warm gray palette
Toggle via Moon/Sun button in sidebar. Persists to localStorage with
prefers-color-scheme fallback. Fixes hardcoded colors (error banners,
badges, chart tooltips, Settings text) to use CSS variables.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 12:28:11 +00:00

198 lines
7.9 KiB
TypeScript

import { useTranslation } from "react-i18next";
import { AlertTriangle, CheckCircle, FileWarning } from "lucide-react";
import type { DuplicateCheckResult } from "../../shared/types";
interface DuplicateCheckPanelProps {
result: DuplicateCheckResult;
excludedIndices: Set<number>;
onToggleRow: (index: number) => void;
onSkipAll: () => void;
onIncludeAll: () => void;
}
export default function DuplicateCheckPanel({
result,
excludedIndices,
onToggleRow,
onSkipAll,
onIncludeAll,
}: DuplicateCheckPanelProps) {
const { t } = useTranslation();
const allExcluded = result.duplicateRows.length > 0 &&
result.duplicateRows.every((d) => excludedIndices.has(d.rowIndex));
const noneExcluded = result.duplicateRows.every((d) => !excludedIndices.has(d.rowIndex));
return (
<div className="space-y-6">
<h2 className="text-lg font-semibold">
{t("import.duplicates.title")}
</h2>
{/* File-level duplicate */}
{result.fileAlreadyImported && (
<div className="flex items-start gap-3 p-4 rounded-xl bg-[var(--card)] border-2 border-[var(--accent)]">
<FileWarning size={20} className="text-[var(--accent)] shrink-0 mt-0.5" />
<div>
<p className="text-sm font-medium text-[var(--foreground)]">
{t("import.duplicates.fileAlreadyImported")}
</p>
<p className="text-xs text-[var(--muted-foreground)] mt-1">
{t("import.duplicates.fileAlreadyImportedDesc")}
</p>
</div>
</div>
)}
{/* Row-level duplicates */}
{result.duplicateRows.length > 0 ? (
<div>
<div className="flex items-start gap-3 p-4 rounded-xl bg-[var(--card)] border-2 border-[var(--accent)] mb-4">
<AlertTriangle
size={20}
className="text-[var(--accent)] shrink-0 mt-0.5"
/>
<div>
<p className="text-sm font-medium text-[var(--foreground)]">
{t("import.duplicates.rowsFound", {
count: result.duplicateRows.length,
})}
</p>
<p className="text-xs text-[var(--muted-foreground)] mt-1">
{t("import.duplicates.rowsFoundDesc")}
</p>
</div>
</div>
{/* Bulk actions */}
<div className="flex gap-4 mb-4">
<button
type="button"
onClick={onSkipAll}
className={`px-3 py-1.5 text-sm rounded-lg border transition-colors ${
allExcluded
? "bg-[var(--primary)] text-white border-[var(--primary)]"
: "bg-[var(--card)] border-[var(--border)] hover:bg-[var(--muted)]"
}`}
>
{t("import.duplicates.skip")}
</button>
<button
type="button"
onClick={onIncludeAll}
className={`px-3 py-1.5 text-sm rounded-lg border transition-colors ${
noneExcluded
? "bg-[var(--primary)] text-white border-[var(--primary)]"
: "bg-[var(--card)] border-[var(--border)] hover:bg-[var(--muted)]"
}`}
>
{t("import.duplicates.includeAll")}
</button>
</div>
{/* Duplicate table */}
<div className="overflow-x-auto rounded-xl border border-[var(--border)] max-h-64 overflow-y-auto">
<table className="w-full text-sm">
<thead className="sticky top-0">
<tr className="bg-[var(--muted)]">
<th className="px-3 py-2 text-left text-xs font-medium text-[var(--muted-foreground)] w-10">
<input
type="checkbox"
checked={noneExcluded}
onChange={() => noneExcluded ? onSkipAll() : onIncludeAll()}
className="accent-[var(--primary)]"
/>
</th>
<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.source")}
</th>
</tr>
</thead>
<tbody className="divide-y divide-[var(--border)]">
{result.duplicateRows.map((row) => {
const included = !excludedIndices.has(row.rowIndex);
const isBatch = row.existingTransactionId === -1;
return (
<tr
key={row.rowIndex}
className={included ? "bg-[var(--card)]" : "bg-[var(--muted)] opacity-60"}
>
<td className="px-3 py-2">
<input
type="checkbox"
checked={included}
onChange={() => onToggleRow(row.rowIndex)}
className="accent-[var(--primary)]"
/>
</td>
<td className="px-3 py-2 text-[var(--muted-foreground)]">
{row.rowIndex + 1}
</td>
<td className="px-3 py-2">{row.date}</td>
<td className="px-3 py-2 max-w-xs truncate">
{row.description}
</td>
<td className="px-3 py-2 text-right font-mono">
{row.amount.toFixed(2)}
</td>
<td className="px-3 py-2">
<span
className={`inline-block px-2 py-0.5 text-xs rounded-full ${
isBatch
? "bg-[var(--accent)]/15 text-[var(--accent)]"
: "bg-[var(--primary)]/15 text-[var(--primary)]"
}`}
>
{isBatch
? t("import.duplicates.sourceBatch")
: t("import.duplicates.sourceDb")}
</span>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
) : (
!result.fileAlreadyImported && (
<div className="flex items-center gap-3 p-4 rounded-xl bg-[var(--card)] border-2 border-[var(--positive)]">
<CheckCircle size={20} className="text-[var(--positive)]" />
<p className="text-sm font-medium text-[var(--foreground)]">
{t("import.duplicates.noneFound")}
</p>
</div>
)
)}
{/* Summary */}
<div className="p-4 rounded-xl bg-[var(--muted)]">
<p className="text-sm text-[var(--foreground)]">
{t("import.duplicates.summary", {
total: result.newRows.length + result.duplicateRows.length,
new: result.newRows.length,
duplicates: result.duplicateRows.length,
})}
{excludedIndices.size > 0 && (
<span className="text-[var(--muted-foreground)]">
{" "} {excludedIndices.size} {t("import.duplicates.skip").toLowerCase()}
</span>
)}
</p>
</div>
</div>
);
}