| |
| |
| |
| import type { DogPhoto, PhotoSearchMatch } from "../types"; |
| import { estimatedBreeds } from "./MatchCard"; |
|
|
| function pct(score: number): string { |
| return `${(score * 100).toFixed(1)}%`; |
| } |
|
|
| |
| export function photosFromUrls(urls: string[]): DogPhoto[] { |
| return urls.map((src, i) => ({ id: i, url: src, thumb_url: src, is_primary: i === 0 })); |
| } |
|
|
| export default function ComparePhotosModal({ |
| match, |
| queryPhotos, |
| queryLabel, |
| isCorrect = false, |
| onClose, |
| }: { |
| match: PhotoSearchMatch; |
| queryPhotos: DogPhoto[]; |
| queryLabel: string; |
| /** Sample dogs know their planted twin, so a confirmed match can be badged. */ |
| isCorrect?: boolean; |
| onClose: () => void; |
| }) { |
| return ( |
| <div |
| className="fixed inset-0 bg-black/50 flex items-center justify-center p-4 z-40" |
| role="dialog" |
| onClick={onClose} |
| > |
| <div |
| className="card max-w-4xl w-full max-h-[88vh] flex flex-col" |
| onClick={(e) => e.stopPropagation()} |
| > |
| <div className="flex items-start justify-between mb-3 shrink-0"> |
| <div> |
| <h3 className="font-bold text-lg flex items-center gap-2"> |
| Compare · <span className="text-brand-600">{pct(match.score)} match</span> |
| {isCorrect && <span className="badge bg-leaf-600 text-white">✓ Correct match</span>} |
| </h3> |
| <p className="text-xs text-gray-500"> |
| {estimatedBreeds(match.dog.predicted_breeds)} |
| {match.dog.zip ? ` · ZIP ${match.dog.zip}` : ""} |
| {match.distance_miles != null ? ` · ${match.distance_miles} mi away` : ""} |
| </p> |
| </div> |
| <button className="btn-secondary" onClick={onClose}> |
| Close |
| </button> |
| </div> |
| <div className="grid grid-cols-2 gap-4 flex-1 min-h-0"> |
| <PhotoColumn label={queryLabel} photos={queryPhotos} /> |
| <PhotoColumn label={`${match.dog.name} (match)`} photos={match.photos} /> |
| </div> |
| </div> |
| </div> |
| ); |
| } |
|
|
| function PhotoColumn({ label, photos }: { label: string; photos: DogPhoto[] }) { |
| return ( |
| <div className="h-full min-h-0 overflow-y-auto pr-1"> |
| <p className="text-sm font-medium text-gray-700 mb-2 sticky top-0 bg-white py-1 truncate z-10"> |
| {label} <span className="text-gray-400 font-normal">({photos.length})</span> |
| </p> |
| <div className="space-y-2"> |
| {photos.map((p) => ( |
| <img |
| key={p.id} |
| src={p.url || p.thumb_url || "/paw-placeholder.svg"} |
| alt={label} |
| className="w-full h-52 object-contain rounded-md bg-gray-100" |
| /> |
| ))} |
| {photos.length === 0 && <p className="text-gray-500 text-sm">No images.</p>} |
| </div> |
| </div> |
| ); |
| } |
|
|