abbashkyt-creator 7d8ce0e322 V0.1
2026-03-14 04:02:22 +03:00

106 lines
4.0 KiB
TypeScript

'use client'
import Image from 'next/image'
import { motion } from 'framer-motion'
import { useCountdown } from '@/hooks/useCountdown'
import { cn } from '@/lib/utils'
import type { Listing } from '@/lib/types'
function formatMins(mins: number | null): string {
if (mins === null) return '—'
if (mins < 1) return '<1m'
const d = Math.floor(mins / 1440)
const h = Math.floor((mins % 1440) / 60)
const m = Math.floor(mins % 60)
return [d && `${d}d`, h && `${h}h`, `${m}m`].filter(Boolean).join(' ')
}
const AiBadge = ({ match }: { match: 1 | 0 | null }) => {
if (match === 1) return <span title="AI match" className="g-badge g-badge-green"><span className="w-1 h-1 rounded-full bg-g-green shadow-[0_0_4px_rgba(0,232,123,0.6)]" />Match</span>
if (match === 0) return <span title="AI rejected" className="g-badge g-badge-red"><span className="w-1 h-1 rounded-full bg-g-red" />Skip</span>
return <span className="text-g-faint text-xs"></span>
}
interface Props { listing: Listing; onSelect: (l: Listing) => void }
export default function ListingRow({ listing, onSelect }: Props) {
const getTime = useCountdown()
const mins = getTime(listing.id) ?? listing.time_left_mins
const isUrgent = mins !== null && mins < 60
return (
<motion.tr
initial={{ opacity: 1, y: -6 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
className={cn(
'transition-all duration-200',
listing.ai_match === 0 && 'opacity-40'
)}
>
<td className="w-16">
{listing.images[0] ? (
<div className="relative w-11 h-11 rounded-xl overflow-hidden bg-g-raised ring-1 ring-g-border/50 group/img">
<Image
src={listing.images[0]} alt={listing.title}
width={44} height={44}
className="object-cover w-full h-full transition-transform duration-300 group-hover/img:scale-110"
onError={(e) => { e.currentTarget.style.display = 'none' }}
/>
</div>
) : (
<div className="w-11 h-11 bg-gradient-to-br from-g-raised to-g-panel rounded-xl ring-1 ring-g-border/30 flex items-center justify-center">
<span className="text-g-faint/30 text-xs"></span>
</div>
)}
</td>
<td>
<button
onClick={() => onSelect(listing)}
className="text-left text-g-text hover:text-g-green transition-all duration-200 block text-sm font-medium leading-snug group/title"
>
<span className="group-hover/title:underline decoration-g-green/30 underline-offset-2">
{listing.title.length > 64 ? listing.title.slice(0, 64) + '…' : listing.title}
</span>
</button>
<div className="flex items-center gap-1.5 mt-1">
{listing.location && <span className="text-[11px] text-g-faint/60">{listing.location}</span>}
{listing.location && <span className="text-g-faint/20 text-[8px]">·</span>}
<span className="text-[11px] text-g-faint/60">{listing.site_name}</span>
</div>
</td>
<td>
<span className="font-mono text-sm font-semibold tabular-nums text-g-amber">
{listing.price_raw || '—'}
</span>
</td>
<td>
<div className="flex items-center gap-1.5">
<span className={cn(
'font-mono text-sm tabular-nums',
isUrgent ? 'text-g-red font-bold' : 'text-g-muted'
)}>
{formatMins(mins)}
</span>
{isUrgent && <span className="g-badge g-badge-red text-[10px] py-0 animate-pulse">Live</span>}
</div>
</td>
<td className="text-center">
<span className={cn(
'font-mono text-sm font-bold tabular-nums',
listing.score >= 20 ? 'text-g-green' :
listing.score >= 10 ? 'text-g-amber' : 'text-g-muted'
)}>
{listing.score}
</span>
</td>
<td><span className="g-badge g-badge-blue text-[11px]">{listing.keyword}</span></td>
<td className="text-center"><AiBadge match={listing.ai_match} /></td>
</motion.tr>
)
}