- web/src/lib/api.ts: typed fetch wrappers for GET /api/inventory and GET /api/inventory/:id - web/src/hooks/useInventory.ts: useInventory + useInventoryItem TanStack Query hooks - web/src/components/inventory/StatusBadge.tsx: catalog_status → Badge variant mapping - web/src/components/inventory/ItemCard.tsx: grid card with photo, HW ID, name, status, NetBox link - web/src/components/inventory/ItemRow.tsx: list-mode row with status color indicator - web/src/components/layout/TopBar.tsx: sticky nav with HWLab volt brand, Add Item + Scan buttons - web/src/components/layout/AppShell.tsx: TopBar + main content wrapper
69 lines
2.3 KiB
TypeScript
69 lines
2.3 KiB
TypeScript
import { ExternalLink, Package } from 'lucide-react'
|
|
import { Link } from '@tanstack/react-router'
|
|
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
|
|
import { Button } from '@/components/ui/button'
|
|
import { StatusBadge } from './StatusBadge'
|
|
import type { InventoryItem } from '@/lib/api'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
interface ItemCardProps {
|
|
item: InventoryItem
|
|
className?: string
|
|
}
|
|
|
|
export function ItemCard({ item, className }: ItemCardProps) {
|
|
const netboxUrl = `http://netbox.local/dcim/devices/${item.id}/`
|
|
|
|
return (
|
|
<Link to="/item/$id" params={{ id: String(item.id) }} className="block">
|
|
<Card
|
|
className={cn(
|
|
'hover:border-volt/60 transition-colors cursor-pointer h-full flex flex-col',
|
|
className,
|
|
)}
|
|
>
|
|
{/* Photo */}
|
|
<div className="aspect-video bg-near-black rounded-t-card overflow-hidden flex items-center justify-center border-b border-charcoal/80">
|
|
{item.photo_urls.length > 0 ? (
|
|
<img
|
|
src={item.photo_urls[0]}
|
|
alt={item.name}
|
|
className="w-full h-full object-cover"
|
|
loading="lazy"
|
|
/>
|
|
) : (
|
|
<Package className="w-12 h-12 text-charcoal" />
|
|
)}
|
|
</div>
|
|
|
|
<CardHeader className="pb-2">
|
|
{/* HW ID */}
|
|
<p className="font-code text-volt text-xs label-upper">{item.hw_id || item.asset_tag}</p>
|
|
<CardTitle className="text-white text-sm leading-tight line-clamp-2">{item.name}</CardTitle>
|
|
</CardHeader>
|
|
|
|
<CardContent className="pb-2 flex-1">
|
|
<StatusBadge status={item.catalog_status} />
|
|
{item.ai_notes && (
|
|
<p className="mt-2 text-xs text-[#a0a0a0] line-clamp-2">{item.ai_notes}</p>
|
|
)}
|
|
</CardContent>
|
|
|
|
<CardFooter className="pt-2">
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="text-xs text-[#a0a0a0] hover:text-volt p-0 h-auto"
|
|
asChild
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
<a href={netboxUrl} target="_blank" rel="noopener noreferrer">
|
|
<ExternalLink className="w-3 h-3 mr-1" />
|
|
NetBox
|
|
</a>
|
|
</Button>
|
|
</CardFooter>
|
|
</Card>
|
|
</Link>
|
|
)
|
|
}
|