Files
fast-inventory/client/src/api.ts
jonas 88e151f792 Initial commit: Fast Inventory app
Monorepo with Express + SQLite backend and Vite + React frontend.
Features: item CRUD, file uploads with thumbnails, soft delete,
item duplication with file copying, autocomplete inputs, stats dashboard.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 07:06:52 +01:00

97 lines
3.2 KiB
TypeScript

import type { Item, CreateItemBody, UpdateItemBody, StatsResponse, ItemFile } from './types';
const BASE = '/api';
async function request<T>(path: string, init?: RequestInit): Promise<T> {
const res = await fetch(`${BASE}${path}`, {
headers: { 'Content-Type': 'application/json' },
...init,
});
if (!res.ok) {
const body = await res.json().catch(() => ({}));
throw new Error(body.error || `Request failed: ${res.status}`);
}
if (res.status === 204) return undefined as T;
return res.json();
}
export const api = {
// Items
listItems(params?: Record<string, string>): Promise<Item[]> {
const qs = params ? '?' + new URLSearchParams(params).toString() : '';
return request(`/items${qs}`);
},
getItem(id: string): Promise<Item & { files: ItemFile[] }> {
return request(`/items/${id}`);
},
createItem(body: CreateItemBody): Promise<Item> {
return request('/items', { method: 'POST', body: JSON.stringify(body) });
},
updateItem(id: string, body: UpdateItemBody): Promise<Item> {
return request(`/items/${id}`, { method: 'PUT', body: JSON.stringify(body) });
},
deleteItem(id: string): Promise<void> {
return request(`/items/${id}`, { method: 'DELETE' });
},
restoreItem(id: string): Promise<Item> {
return request(`/items/${id}/restore`, { method: 'POST' });
},
permanentDeleteItem(id: string): Promise<void> {
return request(`/items/${id}/permanent`, { method: 'DELETE' });
},
duplicateItem(id: string): Promise<Item> {
return request(`/items/${id}/duplicate`, { method: 'POST' });
},
bulkUpdateItems(ids: string[], updates: { category?: string; location?: string; status?: string; flagged?: number }): Promise<{ updated: number }> {
return request('/items/bulk', { method: 'PATCH', body: JSON.stringify({ ids, updates }) });
},
// Suggestions
getCategories(): Promise<string[]> {
return request('/suggestions/categories');
},
getLocations(): Promise<string[]> {
return request('/suggestions/locations');
},
// Files
async uploadFiles(itemId: string, files: File[], type: 'photo' | 'receipt'): Promise<ItemFile[]> {
const form = new FormData();
form.append('type', type);
for (const f of files) form.append('files', f);
const res = await fetch(`${BASE}/items/${itemId}/files`, { method: 'POST', body: form });
if (!res.ok) throw new Error('Upload failed');
return res.json();
},
deleteFile(fileId: string): Promise<void> {
return request(`/files/${fileId}`, { method: 'DELETE' });
},
setFeaturedPhoto(itemId: string, fileId: string): Promise<void> {
return request(`/items/${itemId}/files/${fileId}/featured`, { method: 'PUT' });
},
// Stats
getStats(): Promise<StatsResponse> {
return request('/stats');
},
};
// Image variant URL helpers
function variantUrl(storedName: string, suffix: string): string {
const dot = storedName.lastIndexOf('.');
return `/api/uploads/${storedName.slice(0, dot)}${suffix}${storedName.slice(dot)}`;
}
export function thumbUrl(storedName: string): string {
return variantUrl(storedName, '_thumb');
}
export function largeUrl(storedName: string): string {
return variantUrl(storedName, '_large');
}
export function uploadUrl(storedName: string): string {
return `/api/uploads/${storedName}`;
}