refactor(section-d): frontend API client type safety audit

All 19 api/*.ts files already used import api from './client' (X-Tenant-ID
guaranteed). Fixed missing type generics and any usage in 10 files:
- worker.ts: args: any[] → unknown[]
- imports.ts, notifications.ts: add response type generics
- renderTemplates.ts: add typed generics on 6 calls
- materials.ts, cad.ts: type previously untyped api calls
- products.ts: ProductCadUploadResponse interface, typed generics
- uploads.ts: StepUploadResponse interface
- billing.ts, orders.ts: <Blob> on download calls

Zero bare axios usage, zero as any in API layer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-08 20:36:48 +01:00
parent 69e94e25e3
commit 596360e507
10 changed files with 59 additions and 24 deletions
+1 -1
View File
@@ -61,7 +61,7 @@ export async function deleteInvoice(id: string): Promise<void> {
}
export async function downloadInvoicePdf(id: string): Promise<void> {
const res = await api.get(`/billing/invoices/${id}/pdf`, { responseType: 'blob' })
const res = await api.get<Blob>(`/billing/invoices/${id}/pdf`, { responseType: 'blob' })
const url = URL.createObjectURL(res.data)
const a = document.createElement('a')
a.href = url
+1 -1
View File
@@ -100,6 +100,6 @@ export async function generateGltfProduction(cadFileId: string): Promise<Generat
/** Force-reset a CAD file stuck in 'processing' to 'failed'. */
export async function resetStuckProcessing(cadFileId: string): Promise<{ status: string; message: string }> {
const res = await api.post(`/cad/${cadFileId}/reset-stuck`)
const res = await api.post<{ status: string; message: string }>(`/cad/${cadFileId}/reset-stuck`)
return res.data
}
+1 -1
View File
@@ -14,7 +14,7 @@ export interface ImportValidation {
}
export async function getImportValidation(id: string): Promise<ImportValidation> {
const res = await api.get(`/imports/validation/${id}`)
const res = await api.get<ImportValidation>(`/imports/validation/${id}`)
return res.data
}
+4 -1
View File
@@ -47,7 +47,10 @@ export async function saveCadPartMaterials(
itemId: string,
parts: Array<{ part_name: string; material: string }>,
) {
const res = await api.put(`/orders/${orderId}/items/${itemId}/cad-materials`, { parts })
const res = await api.put<{ parts: Array<{ part_name: string; material: string }> }>(
`/orders/${orderId}/items/${itemId}/cad-materials`,
{ parts },
)
return res.data
}
+2 -2
View File
@@ -25,12 +25,12 @@ export async function getNotifications(params?: {
unread_only?: boolean
channel?: NotificationChannel
}): Promise<NotificationListResponse> {
const { data } = await api.get('/notifications', { params })
const { data } = await api.get<NotificationListResponse>('/notifications', { params })
return data
}
export async function getUnreadCount(): Promise<number> {
const { data } = await api.get('/notifications/unread-count')
const { data } = await api.get<{ unread_count: number }>('/notifications/unread-count')
return data.unread_count
}
+16 -1
View File
@@ -46,6 +46,8 @@ export interface Order {
updated_at: string
submitted_at: string | null
completed_at: string | null
rejected_at: string | null
rejection_reason: string | null
estimated_price: number | null
item_count: number
line_count: number
@@ -239,8 +241,21 @@ export async function generateLinesFromItems(
return res.data
}
export async function rejectOrder(orderId: string, reason: string, notifyClient: boolean = true): Promise<Order> {
const res = await api.post<Order>(`/orders/${orderId}/reject`, {
reason,
notify_client: notifyClient,
})
return res.data
}
export async function resubmitOrder(orderId: string): Promise<Order> {
const res = await api.post<Order>(`/orders/${orderId}/resubmit`)
return res.data
}
export async function downloadOrderRenders(orderId: string, orderNumber: string): Promise<void> {
const res = await api.get(`/orders/${orderId}/download-renders`, { responseType: 'blob' })
const res = await api.get<Blob>(`/orders/${orderId}/download-renders`, { responseType: 'blob' })
const url = URL.createObjectURL(res.data)
const a = document.createElement('a')
a.href = url
+17 -8
View File
@@ -1,4 +1,5 @@
import api from './client'
import type { Order } from './orders'
export interface RenderPosition {
id: string
@@ -106,10 +107,18 @@ export async function deleteProduct(id: string, hard = false): Promise<void> {
await api.delete(`/products/${id}`, { params: hard ? { hard: true } : undefined })
}
export async function uploadProductCad(id: string, file: File) {
export interface ProductCadUploadResponse {
cad_file_id: string
original_name: string
file_hash: string
status: string
product_id: string
}
export async function uploadProductCad(id: string, file: File): Promise<ProductCadUploadResponse> {
const form = new FormData()
form.append('file', file)
const res = await api.post(`/products/${id}/cad`, form, {
const res = await api.post<ProductCadUploadResponse>(`/products/${id}/cad`, form, {
headers: { 'Content-Type': 'multipart/form-data' },
})
return res.data
@@ -120,13 +129,13 @@ export async function saveProductCadMaterials(id: string, parts: CadPartMaterial
return res.data
}
export async function regenerateProduct(id: string) {
const res = await api.post(`/products/${id}/regenerate`)
export async function regenerateProduct(id: string): Promise<{ status: string; task_id: string | null }> {
const res = await api.post<{ status: string; task_id: string | null }>(`/products/${id}/regenerate`)
return res.data
}
export async function reprocessProduct(id: string) {
const res = await api.post(`/products/${id}/reprocess`)
export async function reprocessProduct(id: string): Promise<{ status: string; task_id: string | null }> {
const res = await api.post<{ status: string; task_id: string | null }>(`/products/${id}/reprocess`)
return res.data
}
@@ -174,8 +183,8 @@ export async function downloadProductRenders(
URL.revokeObjectURL(url)
}
export async function getProductOrders(id: string) {
const res = await api.get(`/products/${id}/orders`)
export async function getProductOrders(id: string): Promise<Order[]> {
const res = await api.get<Order[]>(`/products/${id}/orders`)
return res.data
}
+6 -6
View File
@@ -28,12 +28,12 @@ export interface MaterialLibraryInfo {
}
export async function listRenderTemplates(): Promise<RenderTemplate[]> {
const { data } = await api.get('/render-templates');
const { data } = await api.get<RenderTemplate[]>('/render-templates');
return data;
}
export async function createRenderTemplate(formData: FormData): Promise<RenderTemplate> {
const { data } = await api.post('/render-templates', formData, {
const { data } = await api.post<RenderTemplate>('/render-templates', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
});
return data;
@@ -43,7 +43,7 @@ export async function updateRenderTemplate(
id: string,
updates: Partial<Pick<RenderTemplate, 'name' | 'category_key' | 'output_type_ids' | 'target_collection' | 'material_replace_enabled' | 'lighting_only' | 'shadow_catcher_enabled' | 'camera_orbit' | 'is_active'>>,
): Promise<RenderTemplate> {
const { data } = await api.patch(`/render-templates/${id}`, updates);
const { data } = await api.patch<RenderTemplate>(`/render-templates/${id}`, updates);
return data;
}
@@ -54,7 +54,7 @@ export async function deleteRenderTemplate(id: string): Promise<void> {
export async function reuploadBlendFile(id: string, file: File): Promise<RenderTemplate> {
const fd = new FormData();
fd.append('file', file);
const { data } = await api.post(`/render-templates/${id}/upload`, fd, {
const { data } = await api.post<RenderTemplate>(`/render-templates/${id}/upload`, fd, {
headers: { 'Content-Type': 'multipart/form-data' },
});
return data;
@@ -63,14 +63,14 @@ export async function reuploadBlendFile(id: string, file: File): Promise<RenderT
export async function uploadMaterialLibrary(file: File): Promise<MaterialLibraryInfo> {
const fd = new FormData();
fd.append('file', file);
const { data } = await api.post('/admin/settings/material-library', fd, {
const { data } = await api.post<MaterialLibraryInfo>('/admin/settings/material-library', fd, {
headers: { 'Content-Type': 'multipart/form-data' },
});
return data;
}
export async function getMaterialLibraryInfo(): Promise<MaterialLibraryInfo> {
const { data } = await api.get('/admin/settings/material-library');
const { data } = await api.get<MaterialLibraryInfo>('/admin/settings/material-library');
return data;
}
+10 -2
View File
@@ -93,10 +93,18 @@ export async function finalizeExcelImport(data: ExcelFinalizeRequest): Promise<O
return res.data
}
export async function uploadStep(file: File) {
export interface StepUploadResponse {
cad_file_id: string
original_name: string
file_hash: string
status: string
matched_items: string[]
}
export async function uploadStep(file: File): Promise<StepUploadResponse> {
const form = new FormData()
form.append('file', file)
const res = await api.post('/uploads/step', form, {
const res = await api.post<StepUploadResponse>('/uploads/step', form, {
headers: { 'Content-Type': 'multipart/form-data' },
})
return res.data
+1 -1
View File
@@ -94,7 +94,7 @@ export function renderLogStreamUrl(orderLineId: string): string {
export interface QueueTask {
task_id: string
task_name: string
args: any[]
args: unknown[]
argsrepr: string
status: 'pending' | 'active' | 'reserved'
worker?: string