'use client' import { useEffect, useMemo, useState } from 'react' import { useTranslations } from 'next-intl' import { Button } from '@/components/ui/button' import { Checkbox } from '@/components/ui/checkbox' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog' import { Input } from '@/components/ui/input' import type { Register, RegisterRow } from '@/lib/parties/register' import { formatOrgNumber } from '@/lib/utils' export interface MergeCandidate { id: string displayName: string orgNumber: string | null status: string } /** * Merge with a visible survivor that can be swapped. Everything merged keeps * its rows; the survivor gains the aliases. Undo lives on the toast. */ export function MergeDialog({ open, onOpenChange, subject, suggested, busy, onMerge, }: { open: boolean onOpenChange: (open: boolean) => void subject: MergeCandidate suggested: MergeCandidate[] busy: boolean onMerge: (survivorId: string, mergedIds: string[]) => Promise }) { const t = useTranslations('parties') const tCommon = useTranslations('common') const [picked, setPicked] = useState>(new Set(suggested.map((s) => s.id))) const [survivor, setSurvivor] = useState(subject.id) const [query, setQuery] = useState('') const [found, setFound] = useState([]) useEffect(() => { const q = query.trim() if (q.length < 2) return const ctrl = new AbortController() const timer = setTimeout(async () => { try { const res = await fetch(`/api/parties?view=all&q=${encodeURIComponent(q)}`, { signal: ctrl.signal }) if (!res.ok) return const json = (await res.json()) as { data: Register } setFound( json.data.rows .filter((r: RegisterRow) => r.id !== subject.id) .slice(0, 8) .map((r: RegisterRow) => ({ id: r.id, displayName: r.displayName, orgNumber: r.orgNumber, status: r.status })), ) } catch { // aborted or offline: the list simply does not update } }, 250) return () => { clearTimeout(timer) ctrl.abort() } }, [query, subject.id]) const candidates = useMemo(() => { const seen = new Set() const out: MergeCandidate[] = [] const searched = query.trim().length >= 2 ? found : [] for (const c of [...suggested, ...searched]) { if (seen.has(c.id) || c.id === subject.id) continue seen.add(c.id) out.push(c) } return out }, [suggested, found, query, subject.id]) const members = [subject, ...candidates.filter((c) => picked.has(c.id))] const mergedIds = members.filter((m) => m.id !== survivor).map((m) => m.id) function toggle(id: string) { setPicked((prev) => { const next = new Set(prev) if (next.has(id)) { next.delete(id) if (survivor === id) setSurvivor(subject.id) } else next.add(id) return next }) } return ( {t('merge_dialog_title')} {t('merge_body')}
    {[subject, ...candidates].map((c) => { const isSubject = c.id === subject.id const included = isSubject || picked.has(c.id) return (
  • toggle(c.id)} aria-label={c.displayName} />
    {c.displayName}
    {c.orgNumber ? formatOrgNumber(c.orgNumber) : ''} {c.status === 'suggested' ? (c.orgNumber ? ' ยท ' : '') + t('view_suggested') : ''}
  • ) })}
setQuery(e.target.value)} placeholder={t('merge_search_placeholder')} aria-label={t('merge_search_placeholder')} /> {candidates.length === 0 && query.trim().length >= 2 ? (

{t('merge_none')}

) : null}
) }