"use client"; import { useEffect, useMemo, useState } from "react"; import { Cpu, Library, Loader2, FileText, Zap, ExternalLink, } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { Skeleton } from "@/components/ui/skeleton"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { fetchLibrary, fetchLibraryDatasheetUrl, type LibraryCatalog, } from "@/lib/api"; export default function LibraryPage() { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [filter, setFilter] = useState(""); const [opening, setOpening] = useState(null); useEffect(() => { fetchLibrary() .then(setData) .catch((e) => setError(e instanceof Error ? e.message : "Failed to load library")) .finally(() => setLoading(false)); }, []); const lf = filter.trim().toLowerCase(); const ics = useMemo( () => (data?.ics ?? []).filter( (c) => !lf || c.mpn.toLowerCase().includes(lf) || c.subtype.toLowerCase().includes(lf), ), [data, lf], ); const passives = useMemo( () => (data?.passives ?? []).filter( (c) => !lf || c.mpn.toLowerCase().includes(lf) || c.subtype.toLowerCase().includes(lf) || c.description.toLowerCase().includes(lf), ), [data, lf], ); const simple = useMemo( () => (data?.simple ?? []).filter( (c) => !lf || c.mpn.toLowerCase().includes(lf) || c.subtype.toLowerCase().includes(lf) || c.specs_type.toLowerCase().includes(lf), ), [data, lf], ); const datasheets = useMemo( () => (data?.datasheets ?? []).filter( (c) => !lf || c.mpn.toLowerCase().includes(lf), ), [data, lf], ); async function openDatasheet(mpn: string) { setOpening(mpn); try { const url = await fetchLibraryDatasheetUrl(mpn); if (url) window.open(url, "_blank", "noopener,noreferrer"); } finally { setOpening(null); } } if (loading) { return (
{Array.from({ length: 5 }).map((_, i) => ( ))}
); } if (error) { return (

{error}

); } const empty = !data || (data.ics.length === 0 && data.passives.length === 0 && data.simple.length === 0 && data.datasheets.length === 0); return (

Component library

Datasheets, pin tables, and passive specs are stored once and reused on every later project. A second board with the same CH340E will not re-download the PDF or re-extract the pin table.

{empty ? (

Library is empty

Create a project and run a review. Fetched datasheets land here immediately; pin tables and passive patterns arrive after the first extraction.

) : ( <>
{data.ics.length} ICs {data.passives.length} passive series {data.simple.length} discrete {data.datasheets.length} datasheets
setFilter(e.target.value)} className="sm:ml-auto sm:w-64" />
ICs ({ics.length}) Passives ({passives.length}) Discrete ({simple.length}) Datasheets ({datasheets.length}) {ics.length === 0 ? ( ) : (
{ics.map((ic) => ( ))}
MPN Type Pins Cached
{ic.mpn} {ic.subtype ? ( {ic.subtype} ) : ( )} {ic.pin_count}
pin table {ic.has_datasheet && ( )}
)}
{passives.length === 0 ? ( ) : (
{passives.map((p) => ( ))}
Series Type Description
{p.mpn} {p.subtype ? ( {p.subtype} ) : ( )} {p.description || "—"}
)}
{simple.length === 0 ? ( ) : (
{simple.map((s) => ( ))}
MPN Kind Params PDF
{s.mpn} {s.subtype || s.specs_type || "discrete"} {s.param_count} {s.has_datasheet ? ( ) : ( )}
)}
{datasheets.length === 0 ? ( ) : (
{datasheets.map((d) => ( ))}
MPN Status PDF
{d.mpn} {d.has_extraction ? ( pin table ready ) : d.has_model ? ( specs ready ) : ( PDF saved — extract on next review )}
)}
)}
); } function EmptyFilter({ label }: { label: string }) { return (

No {label} match this filter.

); } function DatasheetLink({ mpn, opening, onOpen, }: { mpn: string; opening: string | null; onOpen: (mpn: string) => void; }) { return ( ); }