import { useState } from "react"; import { Link } from "react-router-dom"; import type { Match } from "../types"; import ConfidenceBar from "./ConfidenceBar"; type Photo = { id: number; url: string | null; thumb_url: string | null }; // Top-3 estimated breeds as a comma-joined label (owners don't enter breed — we estimate it). export function estimatedBreeds(breeds?: string[] | null): string { return breeds && breeds.length ? breeds.slice(0, 3).join(", ") : "Breed not estimated"; } interface Props { match: Match; onConfirm?: (m: Match) => void; onReject?: (m: Match) => void; // For the "Not a match" list: move a rejected candidate back to pending for another look. onReconsider?: (m: Match) => void; // When provided, the photo modal compares these (the case's own dog) side-by-side with the match. queryPhotos?: Photo[]; queryLabel?: string; // When set, a confirmed match links here (reunion next-steps page). reclaimTo?: string; } export default function MatchCard({ match, onConfirm, onReject, onReconsider, queryPhotos, queryLabel, reclaimTo, }: Props) { const [busy, setBusy] = useState(false); const [gallery, setGallery] = useState(false); const c = match.candidate; const photos = c?.pictures ?? []; const compare = (queryPhotos?.length ?? 0) > 0; const pct = (Math.round(match.similarity_score * 1000) / 10).toFixed(1); // one decimal const primary = photos.find((p) => p.is_primary) ?? photos[0]; const title = c?.name || c?.description || (c?.type === "known" ? "Lost dog" : "Found dog"); const zip = c?.current_zip || c?.last_known_zip; async function act(fn?: (m: Match) => void) { if (!fn) return; setBusy(true); try { await fn(match); } finally { setBusy(false); } } return (

#{match.rank} · {title}

{match.status !== "pending" && ( {match.status} )}

{estimatedBreeds(c?.estimated_breeds)} {zip ? ` · near ${zip}` : ""}

{c?.current_location_detail && (

Being held at {c.current_location_detail}

)} {c?.estimated_breeds && c.estimated_breeds.length > 0 && (
{c.estimated_breeds.slice(0, 3).map((b) => ( {b} ))}
)} {photos.length > 0 && ( )} {(onConfirm || onReject) && match.status === "pending" && (
)} {onReconsider && match.status === "rejected" && ( )} {reclaimTo && match.status === "confirmed" && ( Reunion next steps → )}
{gallery && (
setGallery(false)} >
e.stopPropagation()} >

{compare ? `Compare · ${pct}% match` : title}

{estimatedBreeds(c?.estimated_breeds)} {zip ? ` · near ${zip}` : ""}

{compare ? ( // Each side scrolls on its own so you can line up one photo against the other.
) : (
{photos.map((p) => ( {title} ))}
)}
)}
); } function PhotoColumn({ label, photos }: { label: string; photos: Photo[] }) { return (

{label} ({photos.length})

{photos.map((p) => ( {label} ))} {photos.length === 0 &&

No photos.

}
); }