SPB Git
1.6 KB · 10 lines tsx
1"use client";2import { useState } from "react";3import Link from "next/link";4import { api } from "./api";5export function DuplicateList({ items }: { items: any[] }) {6  const [list, setList] = useState(items); const [msg, setMsg] = useState("");7  const merge = async (from: string, into: string, label: string) => { if (!confirm(`Merge ${label}? This cannot be undone from the UI.`)) return; try { await api(`/api/admin/tools/${from}/action`, { method: "POST", body: JSON.stringify({ action: "merge", into }) }); setList((l) => l.filter((x) => x.aId !== from && x.bId !== from)); setMsg("Merged."); } catch (e) { setMsg((e as Error).message); } };8  return <div className="mt-4">{msg && <p className="mb-2 text-sm text-fg-2">{msg}</p>}<ul className="space-y-2">{list.map((d) => <li key={d.aId + d.bId} className="card flex flex-wrap items-center gap-3 p-3 text-sm"><Link href={`/admin/tools/${d.aId}`} className="font-medium hover:text-accent">{d.aName}</Link><span className="text-fg-3">↔</span><Link href={`/admin/tools/${d.bId}`} className="font-medium hover:text-accent">{d.bName}</Link><span className="chip">{d.reason} · {Number(d.score).toFixed(2)}</span><span className="ml-auto flex gap-1"><button onClick={() => merge(d.bId, d.aId, `${d.bName} into ${d.aName}`)} className="btn-ghost h-8 px-2 text-xs">Merge → {d.aName}</button><button onClick={() => merge(d.aId, d.bId, `${d.aName} into ${d.bName}`)} className="btn-ghost h-8 px-2 text-xs">Merge → {d.bName}</button></span></li>)}{!list.length && <li className="text-fg-3">No duplicate candidates.</li>}</ul></div>;9}10
Files · laptop-initial-20261102
Menu

Theme