"use client" import { useCallback, useEffect, useState } from "react" import { ExternalLink, ShoppingCart, Loader2, AlertCircle } from "lucide-react" import { Button } from "@/components/ui/button" import type { EbayCacheState } from "@/lib/ebay-parts-match" import { getTopPartsForManual, type PartForPage } from "@/lib/parts-lookup" interface PartsPanelProps { manualFilename: string className?: string } export function PartsPanel({ manualFilename, className = "", }: PartsPanelProps) { const [parts, setParts] = useState([]) const [isLoading, setIsLoading] = useState(true) const [error, setError] = useState(null) const [cache, setCache] = useState(null) const formatFreshness = (value: number | null) => { if (!value) { return "not refreshed yet" } const minutes = Math.max(0, Math.floor(value / 60000)) if (minutes < 60) { return `${minutes}m ago` } const hours = Math.floor(minutes / 60) if (hours < 24) { return `${hours}h ago` } const days = Math.floor(hours / 24) return `${days}d ago` } const loadParts = useCallback(async () => { setIsLoading(true) setError(null) try { const result = await getTopPartsForManual(manualFilename, 5) setParts(result.parts) setError(result.error ?? null) setCache(result.cache ?? null) } catch (err) { console.error("Error loading parts:", err) setParts([]) setCache(null) setError("Could not load parts") } finally { setIsLoading(false) } }, [manualFilename]) useEffect(() => { if (manualFilename) { void loadParts() } }, [loadParts, manualFilename]) const hasListings = parts.some((part) => part.ebayListings.length > 0) const cacheFreshnessText = formatFreshness(cache?.freshnessMs ?? null) const renderStatusCard = (title: string, message: string) => (
Parts

{title}

{message}

) if (isLoading) { return (
Parts
{cache && (
{cache.lastSuccessfulAt ? `Cache updated ${cacheFreshnessText}` : "Cache warming up"} {cache.isStale ? " • stale" : ""}
)}
Loading parts...
) } if (error && !hasListings) { const loweredError = error.toLowerCase() const statusMessage = loweredError.includes("next_public_convex_url") || loweredError.includes("cached ebay backend is disabled") ? "Set NEXT_PUBLIC_CONVEX_URL in the app environment so cached eBay listings can load." : loweredError.includes("ebay_app_id") ? "Set EBAY_APP_ID in the app environment so the background cache refresh can run." : loweredError.includes("rate limit") || loweredError.includes("exceeded") ? "eBay is temporarily rate-limited. Existing cached listings will be reused until refresh resumes." : error return renderStatusCard("eBay unavailable", statusMessage) } if (parts.length === 0) { return (
Parts
{cache && (
{cache.lastSuccessfulAt ? `Cache updated ${cacheFreshnessText}` : "Cache warming up"} {cache.isStale ? " • stale" : ""}
)}
No parts data extracted for this manual yet
) } if (!hasListings) { return (
Parts
{cache && (
{cache.lastSuccessfulAt ? `Cache updated ${cacheFreshnessText}` : "Cache warming up"} {cache.isStale ? " • stale" : ""}
)}
No cached eBay matches found for these parts yet
) } return (
Parts ({parts.length})
{cache && (
{cache.lastSuccessfulAt ? `Cache updated ${cacheFreshnessText}` : "Cache warming up"} {cache.isStale ? " • stale" : ""}
)}
{error && (

Cached eBay listings are unavailable right now.

{error}

)} {parts.map((part, index) => (
{/* Part Header */}
{part.partNumber}
{part.description && (
{part.description}
)}
{/* eBay Listings */} {part.ebayListings.length > 0 && ( )} {/* View All Listings Button */} {part.ebayListings.length > 2 && (
)}
))} {/* View All Parts Button */} {parts.length > 3 && (
)}
) }