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 (
{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 && ({estimatedBreeds(c?.estimated_breeds)} {zip ? ` · near ${zip}` : ""}
{label} ({photos.length})
No photos.
}