93 lines
4.1 KiB
TypeScript
93 lines
4.1 KiB
TypeScript
'use client'
|
|
import { motion, AnimatePresence } from 'framer-motion'
|
|
import ImageGallery from './ImageGallery'
|
|
import type { Listing } from '@/lib/types'
|
|
|
|
interface Props { listing: Listing | null; onClose: () => void }
|
|
|
|
export default function ListingDetailPanel({ listing, onClose }: Props) {
|
|
return (
|
|
<AnimatePresence>
|
|
{listing && (
|
|
<>
|
|
<motion.div
|
|
initial={{ opacity: 1 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}
|
|
onClick={onClose}
|
|
className="fixed inset-0 bg-black/60 backdrop-blur-md z-40"
|
|
/>
|
|
<motion.div
|
|
initial={{ x: '100%', opacity: 0 }}
|
|
animate={{ x: 0, opacity: 1 }}
|
|
exit={{ x: '100%', opacity: 0 }}
|
|
transition={{ type: 'spring', damping: 30, stiffness: 260 }}
|
|
className="fixed right-0 top-0 h-full w-[32rem] glass-strong z-50 overflow-y-auto shadow-2xl shadow-black/60"
|
|
>
|
|
{/* Gradient accent at top */}
|
|
<div className="h-px bg-gradient-to-r from-g-green/50 via-g-cyan/40 to-transparent" />
|
|
|
|
<div className="sticky top-0 flex items-center justify-between px-6 py-4 border-b border-g-border/30 glass-strong z-10">
|
|
<span className="text-[10px] font-bold text-g-faint uppercase tracking-[0.15em]">Lot Detail</span>
|
|
<button onClick={onClose} className="g-btn h-7 px-3 text-xs">✕ Close</button>
|
|
</div>
|
|
|
|
<div className="p-6 space-y-6">
|
|
<div>
|
|
<h2 className="text-g-text font-bold text-base leading-snug">{listing.title}</h2>
|
|
<div className="flex items-center gap-2 mt-3 flex-wrap">
|
|
<span className="g-badge g-badge-neutral">{listing.site_name}</span>
|
|
<span className="g-badge g-badge-blue">{listing.keyword}</span>
|
|
{listing.ai_match === 1 && <span className="g-badge g-badge-green">AI Match</span>}
|
|
{listing.ai_match === 0 && <span className="g-badge g-badge-red">AI Rejected</span>}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div className="g-card-glow p-4">
|
|
<p className="text-[10px] uppercase tracking-[0.12em] text-g-faint mb-2">Price</p>
|
|
<p className="font-mono text-xl font-extrabold text-g-amber">{listing.price_raw || '—'}</p>
|
|
</div>
|
|
<div className="g-card-glow p-4">
|
|
<p className="text-[10px] uppercase tracking-[0.12em] text-g-faint mb-2">Score</p>
|
|
<p className="font-mono text-xl font-extrabold text-g-text">{listing.score}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="g-card divide-y divide-g-border/30">
|
|
<MetaRow label="Location" value={listing.location || '—'} />
|
|
<MetaRow label="Captured" value={new Date(listing.timestamp).toLocaleString()} />
|
|
{listing.ai_reason && (
|
|
<MetaRow label="AI reason" value={listing.ai_reason}
|
|
valueClass={listing.ai_match === 1 ? 'text-g-green' : 'text-g-red'} />
|
|
)}
|
|
</div>
|
|
|
|
{listing.images?.length > 0 && (
|
|
<div>
|
|
<p className="text-[10px] uppercase tracking-[0.12em] text-g-faint mb-3">Images</p>
|
|
<ImageGallery images={listing.images} />
|
|
</div>
|
|
)}
|
|
|
|
<a href={listing.link} target="_blank" rel="noopener noreferrer"
|
|
className="g-btn-primary w-full justify-center text-sm py-3 !rounded-xl font-semibold">
|
|
Open lot →
|
|
</a>
|
|
</div>
|
|
</motion.div>
|
|
</>
|
|
)}
|
|
</AnimatePresence>
|
|
)
|
|
}
|
|
|
|
function MetaRow({ label, value, valueClass = 'text-g-muted' }: {
|
|
label: string; value: string; valueClass?: string
|
|
}) {
|
|
return (
|
|
<div className="flex gap-4 px-4 py-3">
|
|
<span className="text-xs text-g-faint/60 w-20 flex-shrink-0">{label}</span>
|
|
<span className={`text-xs leading-relaxed ${valueClass}`}>{value}</span>
|
|
</div>
|
|
)
|
|
}
|