{item.spool_id}
@@ -92,8 +91,8 @@ function ActivityItem({ item }: { item: RecentActivityItem }) {
<>
{item.run_number}
diff --git a/frontend/src/components/dashboard/LowStockAlerts.tsx b/frontend/src/components/dashboard/LowStockAlerts.tsx
index 457b673d..3f528092 100644
--- a/frontend/src/components/dashboard/LowStockAlerts.tsx
+++ b/frontend/src/components/dashboard/LowStockAlerts.tsx
@@ -29,8 +29,7 @@ function SpoolAlertCard({ spool }: { spool: LowStockSpool }) {
{spool.spool_id}
diff --git a/frontend/src/components/designers/DesignerList.tsx b/frontend/src/components/designers/DesignerList.tsx
index 013f0bb2..f76c6cfd 100644
--- a/frontend/src/components/designers/DesignerList.tsx
+++ b/frontend/src/components/designers/DesignerList.tsx
@@ -482,7 +482,7 @@ export function DesignerList() {
-
-
Purge/Waste (g)
+
Purge/Waste (g)
setFormData({
...formData,
- estimated_total_purge_grams: e.target.value
+ estimated_flushed_grams: e.target.value
? parseFloat(e.target.value)
: undefined,
})
diff --git a/frontend/src/components/products/ProductComponentsEditor.tsx b/frontend/src/components/products/ProductComponentsEditor.tsx
index 6aa31916..ed6e9ff8 100644
--- a/frontend/src/components/products/ProductComponentsEditor.tsx
+++ b/frontend/src/components/products/ProductComponentsEditor.tsx
@@ -70,7 +70,7 @@ import {
const componentFormSchema = z.object({
child_product_id: z.string().uuid('Must select a product'),
- quantity: z.coerce.number().int().positive('Quantity must be positive'),
+ quantity: z.coerce.number
().int().positive('Quantity must be positive'),
})
type ComponentFormValues = z.infer
@@ -91,7 +91,7 @@ export function ProductComponentsEditor({ productId, components }: ProductCompon
queryFn: () => listProducts({ limit: 100, is_active: true }),
})
- const form = useForm({
+ const form = useForm, unknown, ComponentFormValues>({
resolver: zodResolver(componentFormSchema),
defaultValues: {
child_product_id: '',
diff --git a/frontend/src/components/products/ProductForm.tsx b/frontend/src/components/products/ProductForm.tsx
index 49ca8011..061b3646 100644
--- a/frontend/src/components/products/ProductForm.tsx
+++ b/frontend/src/components/products/ProductForm.tsx
@@ -125,7 +125,7 @@ export function ProductForm({ product, mode }: ProductFormProps) {
queryFn: () => listDesigners({ limit: 100 }),
})
- const form = useForm({
+ const form = useForm, unknown, ProductFormValues>({
resolver: zodResolver(productFormSchema),
defaultValues: {
sku: product?.sku || '',
diff --git a/frontend/src/components/products/ProductModelsEditor.tsx b/frontend/src/components/products/ProductModelsEditor.tsx
index 726c7cb6..ea0844b3 100644
--- a/frontend/src/components/products/ProductModelsEditor.tsx
+++ b/frontend/src/components/products/ProductModelsEditor.tsx
@@ -70,7 +70,7 @@ import {
const modelFormSchema = z.object({
model_id: z.string().uuid('Must select a model'),
- quantity: z.coerce.number().int().positive('Quantity must be positive'),
+ quantity: z.coerce.number().int().positive('Quantity must be positive'),
})
type ModelFormValues = z.infer
@@ -92,7 +92,7 @@ export function ProductModelsEditor({ productId, models }: ProductModelsEditorPr
queryFn: () => listModels({ limit: 100 }),
})
- const form = useForm({
+ const form = useForm, unknown, ModelFormValues>({
resolver: zodResolver(modelFormSchema),
defaultValues: {
model_id: '',
diff --git a/frontend/src/components/products/ProductPricingEditor.tsx b/frontend/src/components/products/ProductPricingEditor.tsx
index 1f5d1e16..fc788584 100644
--- a/frontend/src/components/products/ProductPricingEditor.tsx
+++ b/frontend/src/components/products/ProductPricingEditor.tsx
@@ -95,7 +95,7 @@ export function ProductPricingEditor({ productId, pricing, makeCost }: ProductPr
queryFn: () => listSalesChannels({ limit: 100 }),
})
- const form = useForm({
+ const form = useForm, unknown, PricingFormValues>({
resolver: zodResolver(pricingFormSchema),
defaultValues: {
sales_channel_id: '',
diff --git a/frontend/src/components/sales-channels/SalesChannelDetail.tsx b/frontend/src/components/sales-channels/SalesChannelDetail.tsx
index 79134306..17ea71b8 100644
--- a/frontend/src/components/sales-channels/SalesChannelDetail.tsx
+++ b/frontend/src/components/sales-channels/SalesChannelDetail.tsx
@@ -17,7 +17,7 @@ import {
} from '@/lib/api/sales-channels'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
-import { Card, CardDescription, CardHeader } from '@/components/ui/card'
+import { CardContent, CardTitle, Card, CardDescription, CardHeader } from '@/components/ui/card'
import {
AlertDialog,
AlertDialogCancel,
diff --git a/frontend/src/components/sales-channels/SalesChannelForm.tsx b/frontend/src/components/sales-channels/SalesChannelForm.tsx
index 094690ff..c438d49e 100644
--- a/frontend/src/components/sales-channels/SalesChannelForm.tsx
+++ b/frontend/src/components/sales-channels/SalesChannelForm.tsx
@@ -83,7 +83,7 @@ export function SalesChannelForm({ channel, mode }: SalesChannelFormProps) {
const navigate = useNavigate()
const queryClient = useQueryClient()
- const form = useForm({
+ const form = useForm, unknown, SalesChannelFormValues>({
resolver: zodResolver(salesChannelFormSchema),
defaultValues: {
name: channel?.name || '',
diff --git a/frontend/src/components/settings/EtsySettings.tsx b/frontend/src/components/settings/EtsySettings.tsx
index 9ebd61c5..5490d107 100644
--- a/frontend/src/components/settings/EtsySettings.tsx
+++ b/frontend/src/components/settings/EtsySettings.tsx
@@ -65,7 +65,7 @@ export function EtsySettings() {
queryFn: getEtsySettings,
})
- const form = useForm({
+ const form = useForm, unknown, EtsySettingsFormValues>({
resolver: zodResolver(etsySettingsSchema),
defaultValues: {
enabled: settings?.enabled ?? false,
diff --git a/frontend/src/components/settings/GeneralSettings.tsx b/frontend/src/components/settings/GeneralSettings.tsx
index 69565ef8..ff10bc48 100644
--- a/frontend/src/components/settings/GeneralSettings.tsx
+++ b/frontend/src/components/settings/GeneralSettings.tsx
@@ -48,7 +48,7 @@ export function GeneralSettings() {
queryFn: getTenant,
})
- const form = useForm({
+ const form = useForm, unknown, TenantFormValues>({
resolver: zodResolver(tenantFormSchema),
defaultValues: {
name: tenant?.name ?? '',
diff --git a/frontend/src/components/settings/SquareSettings.tsx b/frontend/src/components/settings/SquareSettings.tsx
index 4a483007..2190f6e6 100644
--- a/frontend/src/components/settings/SquareSettings.tsx
+++ b/frontend/src/components/settings/SquareSettings.tsx
@@ -72,7 +72,7 @@ export function SquareSettings() {
queryFn: getSquareSettings,
})
- const form = useForm({
+ const form = useForm, unknown, SquareSettingsFormValues>({
resolver: zodResolver(squareSettingsSchema),
defaultValues: {
enabled: settings?.enabled ?? false,
diff --git a/frontend/src/components/settings/TeamSettings.tsx b/frontend/src/components/settings/TeamSettings.tsx
index 13653e9f..0f751eac 100644
--- a/frontend/src/components/settings/TeamSettings.tsx
+++ b/frontend/src/components/settings/TeamSettings.tsx
@@ -128,7 +128,7 @@ export function TeamSettings() {
queryFn: listTenantMembers,
})
- const form = useForm({
+ const form = useForm, unknown, InviteFormValues>({
resolver: zodResolver(inviteFormSchema),
defaultValues: {
email: '',
diff --git a/frontend/src/hooks/useModules.test.ts b/frontend/src/hooks/useModules.test.ts
index 10736b52..1e8cd521 100644
--- a/frontend/src/hooks/useModules.test.ts
+++ b/frontend/src/hooks/useModules.test.ts
@@ -21,7 +21,7 @@ import { getModules } from '@/lib/api/modules'
const mockGetModules = vi.mocked(getModules)
const mockModulesResponse = {
- tenant_type: 'three_d_print',
+ tenant_type: 'three_d_print' as const,
modules: [
{
name: 'inventory',
@@ -62,7 +62,7 @@ const mockModulesResponse = {
const mockKnittingResponse = {
...mockModulesResponse,
- tenant_type: 'hand_knitting',
+ tenant_type: 'hand_knitting' as const,
}
describe('useModules', () => {
diff --git a/frontend/src/hooks/useOfflineSpools.test.ts b/frontend/src/hooks/useOfflineSpools.test.ts
index baf5a635..05c146c1 100644
--- a/frontend/src/hooks/useOfflineSpools.test.ts
+++ b/frontend/src/hooks/useOfflineSpools.test.ts
@@ -78,6 +78,17 @@ const mockSpool = {
spool_id: 'SPOOL-001',
material_type_id: 'mat-1',
material_type_name: 'PLA',
+ material_type_code: 'PLA',
+ tenant_id: 'tenant-1',
+ diameter: 1.75,
+ translucent: false,
+ glow: false,
+ initial_weight: 1000,
+ current_weight: 800,
+ remaining_weight: 800,
+ remaining_percentage: 80,
+ purchased_quantity: 1,
+ spools_remaining: 1,
brand: 'TestBrand',
color: 'Red',
is_active: true,
@@ -105,6 +116,8 @@ const mockMaterialType = {
print_temp_max: 220,
bed_temp_min: 50,
bed_temp_max: 60,
+ created_at: '2024-01-01T00:00:00Z',
+ updated_at: '2024-01-01T00:00:00Z',
}
// Helper to create wrapper with QueryClient
diff --git a/frontend/src/hooks/useSKU.test.ts b/frontend/src/hooks/useSKU.test.ts
index c3f1aa63..fbce6fdb 100644
--- a/frontend/src/hooks/useSKU.test.ts
+++ b/frontend/src/hooks/useSKU.test.ts
@@ -114,7 +114,7 @@ describe('useSKUAvailability', () => {
mockSkuApi.checkAvailability.mockResolvedValue({
sku: 'PROD-001',
available: true,
- entity_type: 'PROD',
+
})
const { result } = renderHook(() => useSKUAvailability('PROD'), {
@@ -132,8 +132,7 @@ describe('useSKUAvailability', () => {
mockSkuApi.checkAvailability.mockResolvedValue({
sku: 'PROD-001',
available: false,
- entity_type: 'PROD',
- existing_entity_id: 'uuid-123',
+
})
const { result } = renderHook(() => useSKUAvailability('PROD'), {
@@ -143,7 +142,7 @@ describe('useSKUAvailability', () => {
const checkResult = await result.current.checkSKU('PROD-001')
expect(checkResult.available).toBe(false)
- expect(checkResult.existing_entity_id).toBe('uuid-123')
+
})
it('handles check errors', async () => {
@@ -160,7 +159,7 @@ describe('useSKUAvailability', () => {
mockSkuApi.checkAvailability.mockResolvedValue({
sku: 'PROD-001',
available: true,
- entity_type: 'PROD',
+
})
const { result } = renderHook(() => useSKUAvailability('PROD'), {
@@ -189,15 +188,16 @@ describe('useSKUWithValidation', () => {
it('combines next SKU and availability checking', async () => {
mockSkuApi.getNextSKU.mockResolvedValue({
+ entity_type: 'MOD',
next_sku: 'MOD-010',
highest_existing: 9,
- entity_type: 'MOD',
+
})
mockSkuApi.checkAvailability.mockResolvedValue({
sku: 'MOD-005',
available: true,
- entity_type: 'MOD',
+
})
const { result } = renderHook(() => useSKUWithValidation('MOD'), {
diff --git a/frontend/src/hooks/useTracedMutation.ts b/frontend/src/hooks/useTracedMutation.ts
index d6b94f26..247e1da4 100644
--- a/frontend/src/hooks/useTracedMutation.ts
+++ b/frontend/src/hooks/useTracedMutation.ts
@@ -8,6 +8,7 @@
import {
useMutation,
type UseMutationOptions,
+ type MutationFunctionContext,
type UseMutationResult,
} from '@tanstack/react-query'
import { withSpan, recordError, getTracer } from '@/lib/telemetry'
@@ -53,7 +54,7 @@ export function useTracedMutation<
const { spanName, getSpanAttributes, mutationFn, onSuccess, onError, ...rest } =
options
- const tracedMutationFn = async (variables: TVariables): Promise => {
+ const tracedMutationFn = async (variables: TVariables, mutationContext: MutationFunctionContext): Promise => {
const attributes = getSpanAttributes ? getSpanAttributes(variables) : {}
return withSpan(
@@ -63,7 +64,7 @@ export function useTracedMutation<
span.setAttribute('mutation.name', spanName)
// Execute the actual mutation
- const result = await mutationFn!(variables)
+ const result = await mutationFn!(variables, mutationContext)
// Add result attributes if available
if (result && typeof result === 'object' && 'id' in result) {
@@ -79,23 +80,23 @@ export function useTracedMutation<
return useMutation({
...rest,
mutationFn: mutationFn ? tracedMutationFn : undefined,
- onSuccess: (data, variables, context) => {
+ onSuccess: (data, variables, context, mutationContext) => {
// Record success metric
const tracer = getTracer()
const span = tracer.startSpan(`mutation.${spanName}.success`)
span.setStatus({ code: SpanStatusCode.OK })
span.end()
- onSuccess?.(data, variables, context)
+ onSuccess?.(data, variables, context, mutationContext)
},
- onError: (error, variables, context) => {
+ onError: (error, variables, context, mutationContext) => {
// Record error in a span
const tracer = getTracer()
const span = tracer.startSpan(`mutation.${spanName}.error`)
recordError(span, error)
span.end()
- onError?.(error, variables, context)
+ onError?.(error, variables, context, mutationContext)
},
})
}
diff --git a/frontend/src/lib/api.test.ts b/frontend/src/lib/api.test.ts
index abad26e0..b0d4ada4 100644
--- a/frontend/src/lib/api.test.ts
+++ b/frontend/src/lib/api.test.ts
@@ -37,7 +37,7 @@ describe('apiClient', () => {
describe('get', () => {
it('makes a GET request and returns data', async () => {
const mockData = { id: 1, name: 'Test' }
- // @ts-expect-error - Mocking axios response
+
mockAxios.get.mockResolvedValue({ data: mockData })
const result = await apiClient.get('/test')
@@ -49,7 +49,7 @@ describe('apiClient', () => {
it('passes config to GET request', async () => {
const mockData = { id: 1 }
const config = { params: { page: 1 } }
- // @ts-expect-error - Mocking axios response
+
mockAxios.get.mockResolvedValue({ data: mockData })
await apiClient.get('/test', config)
@@ -69,7 +69,7 @@ describe('apiClient', () => {
it('makes a POST request with data', async () => {
const mockResponse = { id: 1, created: true }
const postData = { name: 'New Item' }
- // @ts-expect-error - Mocking axios response
+
mockAxios.post.mockResolvedValue({ data: mockResponse })
const result = await apiClient.post('/items', postData)
@@ -80,7 +80,7 @@ describe('apiClient', () => {
it('handles POST without data', async () => {
const mockResponse = { success: true }
- // @ts-expect-error - Mocking axios response
+
mockAxios.post.mockResolvedValue({ data: mockResponse })
await apiClient.post('/trigger')
@@ -93,7 +93,7 @@ describe('apiClient', () => {
it('makes a PUT request', async () => {
const mockResponse = { id: 1, updated: true }
const updateData = { name: 'Updated' }
- // @ts-expect-error - Mocking axios response
+
mockAxios.put.mockResolvedValue({ data: mockResponse })
const result = await apiClient.put('/items/1', updateData)
@@ -107,7 +107,7 @@ describe('apiClient', () => {
it('makes a PATCH request', async () => {
const mockResponse = { id: 1, patched: true }
const patchData = { status: 'active' }
- // @ts-expect-error - Mocking axios response
+
mockAxios.patch.mockResolvedValue({ data: mockResponse })
const result = await apiClient.patch('/items/1', patchData)
@@ -120,7 +120,7 @@ describe('apiClient', () => {
describe('delete', () => {
it('makes a DELETE request', async () => {
const mockResponse = { deleted: true }
- // @ts-expect-error - Mocking axios response
+
mockAxios.delete.mockResolvedValue({ data: mockResponse })
const result = await apiClient.delete('/items/1')
@@ -134,7 +134,7 @@ describe('apiClient', () => {
describe('checkHealth', () => {
it('calls the health endpoint', async () => {
const mockHealth = { status: 'healthy', environment: 'production' }
- // @ts-expect-error - Mocking axios response
+
mockAxios.get.mockResolvedValue({ data: mockHealth })
const result = await checkHealth()
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts
index 5a38e71c..9eaf9218 100644
--- a/frontend/src/lib/api.ts
+++ b/frontend/src/lib/api.ts
@@ -59,6 +59,7 @@ api.interceptors.request.use(
const { access_token, refresh_token, expires_in } = response.data;
const newTokens = {
accessToken: access_token,
+ tokenType: response.data.token_type || tokens.tokenType || "bearer",
refreshToken: refresh_token || tokens.refreshToken,
expiresAt: Date.now() + expires_in * 1000,
};
diff --git a/frontend/src/lib/api/categories.ts b/frontend/src/lib/api/categories.ts
index 2d459e25..91e0561a 100644
--- a/frontend/src/lib/api/categories.ts
+++ b/frontend/src/lib/api/categories.ts
@@ -14,7 +14,7 @@ export interface Category {
tenant_id: string
name: string
slug: string
- description?: string
+ description?: string | null
image_url?: string
parent_id?: string
display_order: number
@@ -32,7 +32,7 @@ export interface CategoryListResponse {
export interface CategoryCreateRequest {
name: string
slug?: string
- description?: string
+ description?: string | null
image_url?: string
parent_id?: string
display_order?: number
@@ -42,7 +42,7 @@ export interface CategoryCreateRequest {
export interface CategoryUpdateRequest {
name?: string
slug?: string
- description?: string
+ description?: string | null
image_url?: string | null
parent_id?: string | null
display_order?: number
diff --git a/frontend/src/lib/api/designers.ts b/frontend/src/lib/api/designers.ts
index a23c0452..88f4ba11 100644
--- a/frontend/src/lib/api/designers.ts
+++ b/frontend/src/lib/api/designers.ts
@@ -13,7 +13,7 @@ export interface Designer {
id: string
name: string
slug: string
- description?: string
+ description?: string | null
logo_url?: string
website_url?: string
social_links?: Record
@@ -35,7 +35,7 @@ export interface DesignerListResponse {
export interface DesignerCreateRequest {
name: string
slug?: string
- description?: string
+ description?: string | null
logo_url?: string
website_url?: string
social_links?: Record
@@ -49,7 +49,7 @@ export interface DesignerCreateRequest {
export interface DesignerUpdateRequest {
name?: string
slug?: string
- description?: string
+ description?: string | null
logo_url?: string | null
website_url?: string | null
social_links?: Record | null
diff --git a/frontend/src/lib/api/production-runs.ts b/frontend/src/lib/api/production-runs.ts
index b7f815c2..48590466 100644
--- a/frontend/src/lib/api/production-runs.ts
+++ b/frontend/src/lib/api/production-runs.ts
@@ -270,7 +270,7 @@ export function calculateEstimatedTotalWeight(
materials: ProductionRunMaterialCreate[]
): number {
return materials.reduce((total, material) => {
- return total + material.estimated_weight_grams + material.estimated_purge_grams;
+ return total + material.estimated_model_weight_grams + material.estimated_flushed_grams;
}, 0);
}
@@ -281,7 +281,7 @@ export function calculateEstimatedMaterialCost(
materials: ProductionRunMaterialCreate[]
): number {
return materials.reduce((total, material) => {
- const weight = material.estimated_weight_grams + material.estimated_purge_grams;
+ const weight = material.estimated_model_weight_grams + material.estimated_flushed_grams;
return total + weight * material.cost_per_gram;
}, 0);
}
diff --git a/frontend/src/lib/api/products.ts b/frontend/src/lib/api/products.ts
index 4150aced..7a5714b1 100644
--- a/frontend/src/lib/api/products.ts
+++ b/frontend/src/lib/api/products.ts
@@ -83,7 +83,7 @@ export interface Product {
name: string
description?: string
packaging_cost: string
- packaging_consumable_id?: string
+ packaging_consumable_id?: string | null
packaging_quantity: number
assembly_minutes: number
units_in_stock: number
@@ -188,7 +188,7 @@ export interface ProductCreateRequest {
name: string
description?: string
packaging_cost?: string
- packaging_consumable_id?: string
+ packaging_consumable_id?: string | null
packaging_quantity?: number
assembly_minutes?: number
units_in_stock?: number
@@ -196,9 +196,9 @@ export interface ProductCreateRequest {
models?: ProductModelCreateRequest[]
child_products?: ProductComponentCreateRequest[]
// Product specifications
- weight_grams?: number
- size_cm?: string
- print_time_hours?: string
+ weight_grams?: number | null
+ size_cm?: string | null
+ print_time_hours?: string | null
}
/**
diff --git a/frontend/src/lib/api/sku.test.ts b/frontend/src/lib/api/sku.test.ts
index b9f04603..07e9e56b 100644
--- a/frontend/src/lib/api/sku.test.ts
+++ b/frontend/src/lib/api/sku.test.ts
@@ -85,7 +85,7 @@ describe('skuApi', () => {
const mockResponse = {
sku: 'PROD-001',
available: true,
- entity_type: 'PROD',
+
}
mockApiClient.get.mockResolvedValue(mockResponse)
@@ -99,22 +99,21 @@ describe('skuApi', () => {
const mockResponse = {
sku: 'PROD-001',
available: false,
- entity_type: 'PROD',
- existing_entity_id: 'uuid-123',
+
}
mockApiClient.get.mockResolvedValue(mockResponse)
const result = await skuApi.checkAvailability('PROD', 'PROD-001')
expect(result.available).toBe(false)
- expect(result.existing_entity_id).toBe('uuid-123')
+
})
it('encodes SKU with special characters', async () => {
const mockResponse = {
sku: 'PROD-001/A',
available: true,
- entity_type: 'PROD',
+
}
mockApiClient.get.mockResolvedValue(mockResponse)
@@ -127,7 +126,7 @@ describe('skuApi', () => {
const mockResponse = {
sku: 'PROD 001',
available: true,
- entity_type: 'PROD',
+
}
mockApiClient.get.mockResolvedValue(mockResponse)
diff --git a/frontend/src/lib/sentry.ts b/frontend/src/lib/sentry.ts
index d3c2dd85..8f0c79c7 100644
--- a/frontend/src/lib/sentry.ts
+++ b/frontend/src/lib/sentry.ts
@@ -59,17 +59,12 @@ export function initSentry(): boolean {
replaysSessionSampleRate,
replaysOnErrorSampleRate,
+ tracePropagationTargets: ["localhost", /^https:\/\/.*\.batchivo\.com/, /^\/api\//],
+
// Integrations
integrations: [
// Browser tracing for performance
- Sentry.browserTracingIntegration({
- // Trace navigation and API calls
- tracePropagationTargets: [
- 'localhost',
- /^https:\/\/.*\.batchivo\.com/,
- /^\/api\//,
- ],
- }),
+ Sentry.browserTracingIntegration(),
// Session replay for debugging
Sentry.replayIntegration({
// Mask all text and block all media for privacy in production
diff --git a/frontend/src/lib/telemetry.ts b/frontend/src/lib/telemetry.ts
index c674e04e..8d0583b6 100644
--- a/frontend/src/lib/telemetry.ts
+++ b/frontend/src/lib/telemetry.ts
@@ -55,20 +55,15 @@ export function initTelemetry(): void {
headers: {},
})
- // Create tracer provider
tracerProvider = new WebTracerProvider({
resource,
- })
-
- // Add batch span processor for efficient export
- tracerProvider.addSpanProcessor(
- new BatchSpanProcessor(exporter, {
+ spanProcessors: [new BatchSpanProcessor(exporter, {
maxQueueSize: 100,
maxExportBatchSize: 10,
scheduledDelayMillis: 500,
exportTimeoutMillis: 30000,
- })
- )
+ })],
+ })
// Register the provider globally
tracerProvider.register({
@@ -92,7 +87,7 @@ export function initTelemetry(): void {
if (request instanceof Request) {
span.setAttribute('http.request.url', request.url)
}
- if (response) {
+ if (typeof response?.status === 'number') {
span.setAttribute('http.response.status_code', response.status)
}
},
diff --git a/frontend/src/pages/ProductionRuns.tsx b/frontend/src/pages/ProductionRuns.tsx
index b8265633..1144e96e 100644
--- a/frontend/src/pages/ProductionRuns.tsx
+++ b/frontend/src/pages/ProductionRuns.tsx
@@ -4,6 +4,7 @@
* Main page for production run management with list and analytics views
*/
+import type { ProductionRunDetail } from '@/types/production-run'
import { useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import { Link } from '@tanstack/react-router'
@@ -13,15 +14,25 @@ import { VarianceDashboard } from '@/components/charts'
import { AppLayout } from '@/components/layout/AppLayout'
import { Button } from '@/components/ui/button'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
-import { listProductionRuns } from '@/lib/api/production-runs'
+import { listProductionRuns, getProductionRun } from '@/lib/api/production-runs'
export function ProductionRuns() {
const [activeTab, setActiveTab] = useState('list')
// Fetch all runs for analytics (separate from paginated list)
- const { data: analyticsData, isLoading: analyticsLoading, refetch } = useQuery({
+ const { data: analyticsData, isLoading: analyticsLoading, error: analyticsError, refetch } = useQuery({
queryKey: ['production-runs-analytics'],
- queryFn: () => listProductionRuns({ limit: 500 }),
+ queryFn: async () => {
+ const page = await listProductionRuns({ limit: 500 })
+ const runs: ProductionRunDetail[] = []
+ // Bound requests so opening analytics cannot flood the backend.
+ for (let offset = 0; offset < page.runs.length; offset += 5) {
+ runs.push(...await Promise.all(
+ page.runs.slice(offset, offset + 5).map(run => getProductionRun(run.id))
+ ))
+ }
+ return { ...page, runs }
+ },
enabled: activeTab === 'analytics',
})
@@ -62,6 +73,7 @@ export function ProductionRuns() {
+ {analyticsError && Unable to load production analytics. Please try refreshing.
}
{
pending: 0,
processing: 0,
shipped: 0,
- completed: 0,
+ delivered: 0,
+ refunded: 0,
+ total: 0,
cancelled: 0,
})
})
diff --git a/frontend/src/pages/help/HelpGuide.tsx b/frontend/src/pages/help/HelpGuide.tsx
index f44d7df5..dd6f50e3 100644
--- a/frontend/src/pages/help/HelpGuide.tsx
+++ b/frontend/src/pages/help/HelpGuide.tsx
@@ -59,7 +59,7 @@ function Sidebar({ currentSlug }: { currentSlug: string }) {
return (
{prevGuide ? (
-
+
View
diff --git a/frontend/src/pages/platform/TenantDetailPage.tsx b/frontend/src/pages/platform/TenantDetailPage.tsx
index f5947036..d2114674 100644
--- a/frontend/src/pages/platform/TenantDetailPage.tsx
+++ b/frontend/src/pages/platform/TenantDetailPage.tsx
@@ -156,7 +156,7 @@ export function TenantDetailPage() {
-
+
Manage Modules
@@ -292,13 +292,13 @@ export function TenantDetailPage() {
{shopSettings.enabled ? 'Yes' : 'No'}
- {shopSettings.shop_name && (
+ {Boolean(shopSettings.shop_name) && (
)}
- {shopSettings.custom_domain && (
+ {Boolean(shopSettings.custom_domain) && (
)}
- {shopSettings.order_prefix && (
+ {Boolean(shopSettings.order_prefix) && (