/** * DedupMerge page — Main deduplication & merge UI. * Search for duplicate contacts, review pairs, merge with field-level control. */ import React, { useState, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { Search, CopyCheck, Loader2, AlertCircle } from 'lucide-react'; import { Button } from '@/components/ui/Button'; import { Input } from '@/components/ui/Input'; import { EmptyState } from '@/components/ui/EmptyState'; import { DuplicatePairCard } from '@/components/dedup/DuplicatePairCard'; import { MergeDialog } from '@/components/dedup/MergeDialog'; import { MergeHistory } from '@/components/dedup/MergeHistory'; import { useFindDuplicates, type DuplicatePair } from '@/api/dedup'; export function DedupMergePage() { const { t } = useTranslation(); const [threshold, setThreshold] = useState(0.7); const [limit, setLimit] = useState(50); const [hasSearched, setHasSearched] = useState(false); const [mergePair, setMergePair] = useState(null); const [mergeDialogOpen, setMergeDialogOpen] = useState(false); const findDuplicates = useFindDuplicates(); const duplicates = findDuplicates.data ?? []; const handleSearch = useCallback(() => { setHasSearched(true); findDuplicates.mutate({ threshold, limit }); }, [findDuplicates, threshold, limit]); const handleMergeClick = useCallback((pair: DuplicatePair) => { setMergePair(pair); setMergeDialogOpen(true); }, []); const handleMergeDialogClose = useCallback(() => { setMergeDialogOpen(false); setMergePair(null); }, []); const handleLimitChange = useCallback( (e: React.ChangeEvent) => { const val = parseInt(e.target.value, 10); if (isNaN(val) || val < 1) { setLimit(1); } else if (val > 500) { setLimit(500); } else { setLimit(val); } }, [], ); return (
{/* Header section */}

{t('dedup.title', 'Duplikate & Zusammenführen')}

{t( 'dedup.description', 'Suchen Sie nach dubletten Kontakten und führen Sie diese zusammen.', )}

{/* Threshold slider */}
{Math.round(threshold * 100)}%
setThreshold(parseFloat(e.target.value))} className="w-full h-2 rounded-lg appearance-none cursor-pointer bg-secondary-200 accent-primary-600" />
50% 100%
{/* Limit input + search button */}
{/* Results section */}
{/* Loading state */} {findDuplicates.isPending && (
)} {/* Error state */} {findDuplicates.isError && (
)} {/* Empty state: no search yet */} {!findDuplicates.isPending && !findDuplicates.isError && !hasSearched && ( } title={t('dedup.emptyTitle', 'Keine Suche durchgeführt')} description={t( 'dedup.emptyDescription', 'Stellen Sie die Schwellwerte ein und klicken Sie auf „Duplikate suchen“, um mögliche Dubletten zu finden.', )} /> )} {/* Empty results: search done, no duplicates found */} {!findDuplicates.isPending && !findDuplicates.isError && hasSearched && duplicates.length === 0 && ( } title={t('dedup.noDuplicates', 'Keine Duplikate gefunden')} description={t( 'dedup.noDuplicatesDescription', 'Mit den aktuellen Einstellungen wurden keine Duplikate gefunden. Versuchen Sie eine niedrigere Schwellwert.', )} /> )} {/* Results list */} {!findDuplicates.isPending && !findDuplicates.isError && duplicates.length > 0 && ( <>

{t('dedup.results', 'Gefundene Duplikate')}

{duplicates.length}
{duplicates.map((pair, idx) => ( ))}
)}
{/* Merge History section */}
{/* Merge Dialog */}
); }