homelabby/web/src/lib/api.ts
Mikkel Georgsen 7db093c696 feat(07-02): frontend NL search bar wired to GET /api/search
- web/src/lib/api.ts: add fetchSearch(q) returning Promise<InventoryItem[]>
- web/src/components/inventory/FilterBar.tsx: NL search input with 400ms debounce,
  volt accent styling, Sparkles icon, Loader2 spinner during search
- web/src/pages/DashboardPage.tsx: nlQuery state + useQuery hook (enabled >2 chars),
  displays searchResults when NL active, local filter unchanged when NL empty
2026-04-10 07:56:02 +00:00

65 lines
1.9 KiB
TypeScript

// API client — typed fetch wrappers for the HWLab Go backend
export interface InventoryItem {
id: number
name: string
asset_tag: string | null
hw_id: string | null
catalog_status: string | null
product_url: string | null
firmware_version: string | null
test_date: string | null
test_data: string | null
ai_notes: string | null
photo_urls: string[]
}
const BASE = '/api'
async function fetchJSON<T>(url: string): Promise<T> {
const res = await fetch(url)
if (!res.ok) {
const body = await res.json().catch(() => ({ error: res.statusText }))
throw new Error((body as { error?: string }).error ?? `HTTP ${res.status}`)
}
return res.json() as Promise<T>
}
export const fetchInventory = (): Promise<InventoryItem[]> =>
fetchJSON<InventoryItem[]>(`${BASE}/inventory`)
export const fetchSearch = (q: string): Promise<InventoryItem[]> =>
fetchJSON<InventoryItem[]>(`${BASE}/search?q=${encodeURIComponent(q)}`)
export const fetchInventoryItem = (id: number): Promise<InventoryItem> =>
fetchJSON<InventoryItem>(`${BASE}/inventory/${id}`)
// Intake submission — matches IntakeResult in store/intake.ts
export interface IntakeResponse {
hw_id: string
model: string
manufacturer: string
category: string
specs: Record<string, string>
suggested_tags: string[]
ai_notes: string
confidence: number
catalog_status: string
netbox_id: number
queued: boolean
}
export async function submitIntake(photos: File[], quickAdd = false): Promise<IntakeResponse> {
const formData = new FormData()
photos.forEach((file) => formData.append('photos', file))
formData.append('quick_add', String(quickAdd))
const res = await fetch(`${BASE}/intake`, {
method: 'POST',
body: formData,
})
if (!res.ok) {
const body = await res.json().catch(() => ({ error: res.statusText }))
throw new Error((body as { error?: string }).error ?? `HTTP ${res.status}`)
}
return res.json() as Promise<IntakeResponse>
}