feat: layout hamburger, media browser filters+previews, billing fixes
- Layout: mobile hamburger menu + overlay backdrop + close button; content area always full-width - Media browser: filter chips (default still+turntable); advanced toggle for GLB/STL; thumbnail_url previews for non-image types; video hover-play for turntable - Backend: asset_types multi-filter, thumbnail_url in MediaAssetOut, download proxy endpoint for MinIO/local files - Admin: "Import Existing Media" button → POST /api/admin/import-media-assets - Billing: fix invoice create 500 (MissingGreenlet — use selectinload after commit); PDF download uses axios blob instead of bare <a href> (auth header missing); fix storage.upload() accepting str|Path - SSE task logs: task_logs.py core + router, LiveRenderLog component - CadPreview: fix infinite loop when no gltf_geometry assets; loading screen before ThreeDViewer render - render-worker: add trimesh layer to Dockerfile Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -60,6 +60,12 @@ export async function deleteInvoice(id: string): Promise<void> {
|
||||
await api.delete(`/billing/invoices/${id}`)
|
||||
}
|
||||
|
||||
export function getInvoicePdfUrl(id: string): string {
|
||||
return `/api/billing/invoices/${id}/pdf`
|
||||
export async function downloadInvoicePdf(id: string): Promise<void> {
|
||||
const res = await api.get(`/billing/invoices/${id}/pdf`, { responseType: 'blob' })
|
||||
const url = URL.createObjectURL(res.data)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `invoice-${id}.pdf`
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
@@ -28,13 +28,14 @@ export interface MediaAsset {
|
||||
is_archived: boolean
|
||||
created_at: string
|
||||
download_url: string | null
|
||||
thumbnail_url: string | null
|
||||
}
|
||||
|
||||
export interface MediaFilter {
|
||||
product_id?: string
|
||||
order_line_id?: string
|
||||
cad_file_id?: string
|
||||
asset_type?: MediaAssetType
|
||||
asset_types?: MediaAssetType[]
|
||||
skip?: number
|
||||
limit?: number
|
||||
}
|
||||
@@ -44,10 +45,10 @@ export const getMediaAssets = (filters: MediaFilter = {}): Promise<MediaAsset[]>
|
||||
if (filters.product_id) params.set('product_id', filters.product_id)
|
||||
if (filters.order_line_id) params.set('order_line_id', filters.order_line_id)
|
||||
if (filters.cad_file_id) params.set('cad_file_id', filters.cad_file_id)
|
||||
if (filters.asset_type) params.set('asset_type', filters.asset_type)
|
||||
if (filters.asset_types?.length) filters.asset_types.forEach(t => params.append('asset_types', t))
|
||||
if (filters.skip !== undefined) params.set('skip', String(filters.skip))
|
||||
if (filters.limit !== undefined) params.set('limit', String(filters.limit))
|
||||
return api.get(`/media?${params}`).then(r => r.data)
|
||||
return api.get(`/media/?${params}`).then(r => r.data)
|
||||
}
|
||||
|
||||
export const getMediaAsset = (id: string): Promise<MediaAsset> =>
|
||||
|
||||
Reference in New Issue
Block a user