Spaces:
Running
Running
| import * as React from "react"; | |
| import { Card, CardContent } from "@/components/ui/card"; | |
| import { Input } from "@/components/ui/input"; | |
| import { | |
| Select, | |
| SelectContent, | |
| SelectItem, | |
| SelectTrigger, | |
| SelectValue, | |
| } from "@/components/ui/select"; | |
| import { | |
| Table, | |
| TableBody, | |
| TableCell, | |
| TableHead, | |
| TableHeader, | |
| TableRow, | |
| } from "@/components/ui/table"; | |
| import type { HardwareDatabaseRow, SelectOption } from "@/types/local-frontier"; | |
| export type HardwareDatabaseProps = { | |
| rows: readonly HardwareDatabaseRow[]; | |
| producers: readonly SelectOption[]; | |
| priceBands: readonly SelectOption[]; | |
| }; | |
| function matchesRow( | |
| row: HardwareDatabaseRow, | |
| search: string, | |
| producer: string, | |
| priceBand: string, | |
| ): boolean { | |
| const query = search.trim().toLowerCase(); | |
| return ( | |
| (!query || row.searchText.includes(query)) && | |
| (producer === "all" || row.acceleratorVendorKey === producer) && | |
| (priceBand === "all" || row.priceBand === priceBand) | |
| ); | |
| } | |
| function FilterSelect({ | |
| label, | |
| value, | |
| options, | |
| onValueChange, | |
| }: { | |
| label: string; | |
| value: string; | |
| options: readonly SelectOption[]; | |
| onValueChange: (value: string) => void; | |
| }): React.JSX.Element { | |
| return ( | |
| <label className="grid gap-1.5"> | |
| <span className="text-xs font-semibold text-muted-foreground"> | |
| {label} | |
| </span> | |
| <Select value={value} onValueChange={onValueChange}> | |
| <SelectTrigger> | |
| <SelectValue /> | |
| </SelectTrigger> | |
| <SelectContent> | |
| {options.map((option) => ( | |
| <SelectItem key={option.value} value={option.value}> | |
| {option.label} | |
| </SelectItem> | |
| ))} | |
| </SelectContent> | |
| </Select> | |
| </label> | |
| ); | |
| } | |
| function HardwareRow({ row }: { row: HardwareDatabaseRow }): React.JSX.Element { | |
| return ( | |
| <TableRow> | |
| <TableCell> | |
| <a | |
| className="font-semibold text-foreground underline-offset-2 hover:underline" | |
| href={row.deviceHref} | |
| > | |
| {row.name} | |
| </a> | |
| <span className="mt-1 inline-flex border border-border px-2 py-0.5 text-xs"> | |
| {row.kind} | |
| </span> | |
| </TableCell> | |
| <TableCell> | |
| <a | |
| className="text-foreground underline-offset-2 hover:underline" | |
| href={row.systemVendorHref} | |
| > | |
| {row.systemVendor} | |
| </a> | |
| </TableCell> | |
| <TableCell> | |
| <a | |
| className="text-foreground underline-offset-2 hover:underline" | |
| href={row.acceleratorVendorHref} | |
| > | |
| {row.acceleratorVendor} | |
| </a> | |
| </TableCell> | |
| <TableCell> | |
| <span>{row.memory}</span> | |
| <span className="ml-1 inline-flex border border-border px-1.5 py-0.5 text-xs"> | |
| {row.memorySymbol} | |
| </span> | |
| <span className="block">{row.memoryType}</span> | |
| </TableCell> | |
| <TableCell>{row.bandwidth}</TableCell> | |
| <TableCell> | |
| {row.latestPrice} | |
| {row.estimated ? ( | |
| <span title="Estimated or reconstructed price"> ≈</span> | |
| ) : null} | |
| </TableCell> | |
| <TableCell>{row.priceBandLabel}</TableCell> | |
| <TableCell> | |
| <span>{row.evidence}</span> | |
| <span className="mt-1 block text-xs">{row.confidence}</span> | |
| </TableCell> | |
| </TableRow> | |
| ); | |
| } | |
| function HardwareTable({ | |
| rows, | |
| }: { | |
| rows: readonly HardwareDatabaseRow[]; | |
| }): React.JSX.Element { | |
| if (!rows.length) { | |
| return ( | |
| <div className="p-8 text-muted-foreground"> | |
| No devices match the current filters. | |
| </div> | |
| ); | |
| } | |
| return ( | |
| <Table className="min-w-[1120px]"> | |
| <TableHeader> | |
| <TableRow> | |
| <TableHead>Device</TableHead> | |
| <TableHead>System vendor</TableHead> | |
| <TableHead>Accelerator vendor</TableHead> | |
| <TableHead>Memory</TableHead> | |
| <TableHead>Bandwidth</TableHead> | |
| <TableHead>Latest price</TableHead> | |
| <TableHead>Price band</TableHead> | |
| <TableHead>Evidence</TableHead> | |
| </TableRow> | |
| </TableHeader> | |
| <TableBody> | |
| {rows.map((row) => ( | |
| <HardwareRow key={row.id} row={row} /> | |
| ))} | |
| </TableBody> | |
| </Table> | |
| ); | |
| } | |
| export function HardwareDatabase({ | |
| rows, | |
| producers, | |
| priceBands, | |
| }: HardwareDatabaseProps): React.JSX.Element { | |
| const [search, setSearch] = React.useState(""); | |
| const [producer, setProducer] = React.useState("all"); | |
| const [priceBand, setPriceBand] = React.useState("all"); | |
| const filteredRows = rows.filter((row) => | |
| matchesRow(row, search, producer, priceBand), | |
| ); | |
| return ( | |
| <Card> | |
| <CardContent className="grid gap-4 p-0"> | |
| <div className="grid grid-cols-[minmax(0,1fr)_minmax(12rem,16rem)_minmax(12rem,16rem)] gap-3 p-4 max-[980px]:grid-cols-1"> | |
| <label className="grid gap-1.5"> | |
| <span className="text-xs font-semibold text-muted-foreground"> | |
| Search | |
| </span> | |
| <Input | |
| placeholder="Mac Studio, DGX, W7900, 128GB" | |
| type="search" | |
| value={search} | |
| onChange={(event) => { | |
| setSearch(event.currentTarget.value); | |
| }} | |
| /> | |
| </label> | |
| <FilterSelect | |
| label="Accelerator vendor" | |
| options={producers} | |
| value={producer} | |
| onValueChange={setProducer} | |
| /> | |
| <FilterSelect | |
| label="Price band" | |
| options={priceBands} | |
| value={priceBand} | |
| onValueChange={setPriceBand} | |
| /> | |
| </div> | |
| <HardwareTable rows={filteredRows} /> | |
| </CardContent> | |
| </Card> | |
| ); | |
| } | |