Start integrating the onboarding flow with backend 7
This commit is contained in:
545
frontend/src/api/hooks/alert_processor.ts
Normal file
545
frontend/src/api/hooks/alert_processor.ts
Normal file
@@ -0,0 +1,545 @@
|
||||
/**
|
||||
* Alert Processor React Query hooks
|
||||
* Provides data fetching, caching, and state management for alert processing operations
|
||||
*/
|
||||
|
||||
import { useMutation, useQuery, useQueryClient, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
|
||||
import { alertProcessorService } from '../services/alert_processor';
|
||||
import { ApiError } from '../client/apiClient';
|
||||
import type {
|
||||
AlertResponse,
|
||||
AlertUpdateRequest,
|
||||
AlertQueryParams,
|
||||
AlertDashboardData,
|
||||
NotificationSettings,
|
||||
ChannelRoutingConfig,
|
||||
WebhookConfig,
|
||||
AlertProcessingStatus,
|
||||
ProcessingMetrics,
|
||||
PaginatedResponse,
|
||||
} from '../types/alert_processor';
|
||||
|
||||
// Query Keys Factory
|
||||
export const alertProcessorKeys = {
|
||||
all: ['alert-processor'] as const,
|
||||
alerts: {
|
||||
all: () => [...alertProcessorKeys.all, 'alerts'] as const,
|
||||
lists: () => [...alertProcessorKeys.alerts.all(), 'list'] as const,
|
||||
list: (tenantId: string, params?: AlertQueryParams) =>
|
||||
[...alertProcessorKeys.alerts.lists(), tenantId, params] as const,
|
||||
details: () => [...alertProcessorKeys.alerts.all(), 'detail'] as const,
|
||||
detail: (tenantId: string, alertId: string) =>
|
||||
[...alertProcessorKeys.alerts.details(), tenantId, alertId] as const,
|
||||
dashboard: (tenantId: string) =>
|
||||
[...alertProcessorKeys.alerts.all(), 'dashboard', tenantId] as const,
|
||||
processingStatus: (tenantId: string, alertId: string) =>
|
||||
[...alertProcessorKeys.alerts.all(), 'processing-status', tenantId, alertId] as const,
|
||||
},
|
||||
notifications: {
|
||||
all: () => [...alertProcessorKeys.all, 'notifications'] as const,
|
||||
settings: (tenantId: string) =>
|
||||
[...alertProcessorKeys.notifications.all(), 'settings', tenantId] as const,
|
||||
routing: () =>
|
||||
[...alertProcessorKeys.notifications.all(), 'routing-config'] as const,
|
||||
},
|
||||
webhooks: {
|
||||
all: () => [...alertProcessorKeys.all, 'webhooks'] as const,
|
||||
list: (tenantId: string) =>
|
||||
[...alertProcessorKeys.webhooks.all(), tenantId] as const,
|
||||
},
|
||||
metrics: {
|
||||
all: () => [...alertProcessorKeys.all, 'metrics'] as const,
|
||||
processing: (tenantId: string) =>
|
||||
[...alertProcessorKeys.metrics.all(), 'processing', tenantId] as const,
|
||||
},
|
||||
} as const;
|
||||
|
||||
// Alert Queries
|
||||
export const useAlerts = (
|
||||
tenantId: string,
|
||||
queryParams?: AlertQueryParams,
|
||||
options?: Omit<UseQueryOptions<PaginatedResponse<AlertResponse>, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<PaginatedResponse<AlertResponse>, ApiError>({
|
||||
queryKey: alertProcessorKeys.alerts.list(tenantId, queryParams),
|
||||
queryFn: () => alertProcessorService.getAlerts(tenantId, queryParams),
|
||||
enabled: !!tenantId,
|
||||
staleTime: 30 * 1000, // 30 seconds
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useAlert = (
|
||||
tenantId: string,
|
||||
alertId: string,
|
||||
options?: Omit<UseQueryOptions<AlertResponse, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<AlertResponse, ApiError>({
|
||||
queryKey: alertProcessorKeys.alerts.detail(tenantId, alertId),
|
||||
queryFn: () => alertProcessorService.getAlert(tenantId, alertId),
|
||||
enabled: !!tenantId && !!alertId,
|
||||
staleTime: 1 * 60 * 1000, // 1 minute
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useAlertDashboardData = (
|
||||
tenantId: string,
|
||||
options?: Omit<UseQueryOptions<AlertDashboardData, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<AlertDashboardData, ApiError>({
|
||||
queryKey: alertProcessorKeys.alerts.dashboard(tenantId),
|
||||
queryFn: () => alertProcessorService.getDashboardData(tenantId),
|
||||
enabled: !!tenantId,
|
||||
staleTime: 30 * 1000, // 30 seconds
|
||||
refetchInterval: 1 * 60 * 1000, // Refresh every minute
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useAlertProcessingStatus = (
|
||||
tenantId: string,
|
||||
alertId: string,
|
||||
options?: Omit<UseQueryOptions<AlertProcessingStatus, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<AlertProcessingStatus, ApiError>({
|
||||
queryKey: alertProcessorKeys.alerts.processingStatus(tenantId, alertId),
|
||||
queryFn: () => alertProcessorService.getProcessingStatus(tenantId, alertId),
|
||||
enabled: !!tenantId && !!alertId,
|
||||
staleTime: 10 * 1000, // 10 seconds
|
||||
refetchInterval: 30 * 1000, // Poll every 30 seconds
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// Notification Queries
|
||||
export const useNotificationSettings = (
|
||||
tenantId: string,
|
||||
options?: Omit<UseQueryOptions<NotificationSettings, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<NotificationSettings, ApiError>({
|
||||
queryKey: alertProcessorKeys.notifications.settings(tenantId),
|
||||
queryFn: () => alertProcessorService.getNotificationSettings(tenantId),
|
||||
enabled: !!tenantId,
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useChannelRoutingConfig = (
|
||||
options?: Omit<UseQueryOptions<ChannelRoutingConfig, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<ChannelRoutingConfig, ApiError>({
|
||||
queryKey: alertProcessorKeys.notifications.routing(),
|
||||
queryFn: () => alertProcessorService.getChannelRoutingConfig(),
|
||||
staleTime: 10 * 60 * 1000, // 10 minutes
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// Webhook Queries
|
||||
export const useWebhooks = (
|
||||
tenantId: string,
|
||||
options?: Omit<UseQueryOptions<WebhookConfig[], ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<WebhookConfig[], ApiError>({
|
||||
queryKey: alertProcessorKeys.webhooks.list(tenantId),
|
||||
queryFn: () => alertProcessorService.getWebhooks(tenantId),
|
||||
enabled: !!tenantId,
|
||||
staleTime: 2 * 60 * 1000, // 2 minutes
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// Metrics Queries
|
||||
export const useProcessingMetrics = (
|
||||
tenantId: string,
|
||||
options?: Omit<UseQueryOptions<ProcessingMetrics, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<ProcessingMetrics, ApiError>({
|
||||
queryKey: alertProcessorKeys.metrics.processing(tenantId),
|
||||
queryFn: () => alertProcessorService.getProcessingMetrics(tenantId),
|
||||
enabled: !!tenantId,
|
||||
staleTime: 1 * 60 * 1000, // 1 minute
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// Alert Mutations
|
||||
export const useUpdateAlert = (
|
||||
options?: UseMutationOptions<
|
||||
AlertResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; alertId: string; updateData: AlertUpdateRequest }
|
||||
>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
AlertResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; alertId: string; updateData: AlertUpdateRequest }
|
||||
>({
|
||||
mutationFn: ({ tenantId, alertId, updateData }) =>
|
||||
alertProcessorService.updateAlert(tenantId, alertId, updateData),
|
||||
onSuccess: (data, { tenantId, alertId }) => {
|
||||
// Update the alert in cache
|
||||
queryClient.setQueryData(
|
||||
alertProcessorKeys.alerts.detail(tenantId, alertId),
|
||||
data
|
||||
);
|
||||
|
||||
// Invalidate alerts list to reflect the change
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: alertProcessorKeys.alerts.lists()
|
||||
});
|
||||
|
||||
// Invalidate dashboard data
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: alertProcessorKeys.alerts.dashboard(tenantId)
|
||||
});
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useDismissAlert = (
|
||||
options?: UseMutationOptions<
|
||||
AlertResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; alertId: string }
|
||||
>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
AlertResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; alertId: string }
|
||||
>({
|
||||
mutationFn: ({ tenantId, alertId }) =>
|
||||
alertProcessorService.dismissAlert(tenantId, alertId),
|
||||
onSuccess: (data, { tenantId, alertId }) => {
|
||||
// Update the alert in cache
|
||||
queryClient.setQueryData(
|
||||
alertProcessorKeys.alerts.detail(tenantId, alertId),
|
||||
data
|
||||
);
|
||||
|
||||
// Invalidate related queries
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: alertProcessorKeys.alerts.lists()
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: alertProcessorKeys.alerts.dashboard(tenantId)
|
||||
});
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useAcknowledgeAlert = (
|
||||
options?: UseMutationOptions<
|
||||
AlertResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; alertId: string; notes?: string }
|
||||
>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
AlertResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; alertId: string; notes?: string }
|
||||
>({
|
||||
mutationFn: ({ tenantId, alertId, notes }) =>
|
||||
alertProcessorService.acknowledgeAlert(tenantId, alertId, notes),
|
||||
onSuccess: (data, { tenantId, alertId }) => {
|
||||
// Update the alert in cache
|
||||
queryClient.setQueryData(
|
||||
alertProcessorKeys.alerts.detail(tenantId, alertId),
|
||||
data
|
||||
);
|
||||
|
||||
// Invalidate related queries
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: alertProcessorKeys.alerts.lists()
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: alertProcessorKeys.alerts.dashboard(tenantId)
|
||||
});
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useResolveAlert = (
|
||||
options?: UseMutationOptions<
|
||||
AlertResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; alertId: string; notes?: string }
|
||||
>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
AlertResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; alertId: string; notes?: string }
|
||||
>({
|
||||
mutationFn: ({ tenantId, alertId, notes }) =>
|
||||
alertProcessorService.resolveAlert(tenantId, alertId, notes),
|
||||
onSuccess: (data, { tenantId, alertId }) => {
|
||||
// Update the alert in cache
|
||||
queryClient.setQueryData(
|
||||
alertProcessorKeys.alerts.detail(tenantId, alertId),
|
||||
data
|
||||
);
|
||||
|
||||
// Invalidate related queries
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: alertProcessorKeys.alerts.lists()
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: alertProcessorKeys.alerts.dashboard(tenantId)
|
||||
});
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// Notification Settings Mutations
|
||||
export const useUpdateNotificationSettings = (
|
||||
options?: UseMutationOptions<
|
||||
NotificationSettings,
|
||||
ApiError,
|
||||
{ tenantId: string; settings: Partial<NotificationSettings> }
|
||||
>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
NotificationSettings,
|
||||
ApiError,
|
||||
{ tenantId: string; settings: Partial<NotificationSettings> }
|
||||
>({
|
||||
mutationFn: ({ tenantId, settings }) =>
|
||||
alertProcessorService.updateNotificationSettings(tenantId, settings),
|
||||
onSuccess: (data, { tenantId }) => {
|
||||
// Update settings in cache
|
||||
queryClient.setQueryData(
|
||||
alertProcessorKeys.notifications.settings(tenantId),
|
||||
data
|
||||
);
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// Webhook Mutations
|
||||
export const useCreateWebhook = (
|
||||
options?: UseMutationOptions<
|
||||
WebhookConfig,
|
||||
ApiError,
|
||||
{ tenantId: string; webhook: Omit<WebhookConfig, 'tenant_id'> }
|
||||
>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
WebhookConfig,
|
||||
ApiError,
|
||||
{ tenantId: string; webhook: Omit<WebhookConfig, 'tenant_id'> }
|
||||
>({
|
||||
mutationFn: ({ tenantId, webhook }) =>
|
||||
alertProcessorService.createWebhook(tenantId, webhook),
|
||||
onSuccess: (data, { tenantId }) => {
|
||||
// Add the new webhook to the list
|
||||
queryClient.setQueryData(
|
||||
alertProcessorKeys.webhooks.list(tenantId),
|
||||
(oldData: WebhookConfig[] | undefined) => [...(oldData || []), data]
|
||||
);
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateWebhook = (
|
||||
options?: UseMutationOptions<
|
||||
WebhookConfig,
|
||||
ApiError,
|
||||
{ tenantId: string; webhookId: string; webhook: Partial<WebhookConfig> }
|
||||
>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
WebhookConfig,
|
||||
ApiError,
|
||||
{ tenantId: string; webhookId: string; webhook: Partial<WebhookConfig> }
|
||||
>({
|
||||
mutationFn: ({ tenantId, webhookId, webhook }) =>
|
||||
alertProcessorService.updateWebhook(tenantId, webhookId, webhook),
|
||||
onSuccess: (data, { tenantId }) => {
|
||||
// Update the webhook in the list
|
||||
queryClient.setQueryData(
|
||||
alertProcessorKeys.webhooks.list(tenantId),
|
||||
(oldData: WebhookConfig[] | undefined) =>
|
||||
oldData?.map(hook => hook.webhook_url === data.webhook_url ? data : hook) || []
|
||||
);
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteWebhook = (
|
||||
options?: UseMutationOptions<
|
||||
{ message: string },
|
||||
ApiError,
|
||||
{ tenantId: string; webhookId: string }
|
||||
>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
{ message: string },
|
||||
ApiError,
|
||||
{ tenantId: string; webhookId: string }
|
||||
>({
|
||||
mutationFn: ({ tenantId, webhookId }) =>
|
||||
alertProcessorService.deleteWebhook(tenantId, webhookId),
|
||||
onSuccess: (_, { tenantId, webhookId }) => {
|
||||
// Remove the webhook from the list
|
||||
queryClient.setQueryData(
|
||||
alertProcessorKeys.webhooks.list(tenantId),
|
||||
(oldData: WebhookConfig[] | undefined) =>
|
||||
oldData?.filter(hook => hook.webhook_url !== webhookId) || []
|
||||
);
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useTestWebhook = (
|
||||
options?: UseMutationOptions<
|
||||
{ success: boolean; message: string },
|
||||
ApiError,
|
||||
{ tenantId: string; webhookId: string }
|
||||
>
|
||||
) => {
|
||||
return useMutation<
|
||||
{ success: boolean; message: string },
|
||||
ApiError,
|
||||
{ tenantId: string; webhookId: string }
|
||||
>({
|
||||
mutationFn: ({ tenantId, webhookId }) =>
|
||||
alertProcessorService.testWebhook(tenantId, webhookId),
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// SSE Hook for Real-time Alert Updates
|
||||
export const useAlertSSE = (
|
||||
tenantId: string,
|
||||
token?: string,
|
||||
options?: {
|
||||
onAlert?: (alert: AlertResponse) => void;
|
||||
onRecommendation?: (recommendation: AlertResponse) => void;
|
||||
onAlertUpdate?: (update: AlertResponse) => void;
|
||||
onSystemStatus?: (status: any) => void;
|
||||
}
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['alert-sse', tenantId],
|
||||
queryFn: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const eventSource = alertProcessorService.createSSEConnection(tenantId, token);
|
||||
|
||||
eventSource.onopen = () => {
|
||||
console.log('Alert SSE connected');
|
||||
};
|
||||
|
||||
eventSource.onmessage = (event) => {
|
||||
try {
|
||||
const message = JSON.parse(event.data);
|
||||
|
||||
// Invalidate dashboard data on new alerts/updates
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: alertProcessorKeys.alerts.dashboard(tenantId)
|
||||
});
|
||||
|
||||
// Invalidate alerts list
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: alertProcessorKeys.alerts.lists()
|
||||
});
|
||||
|
||||
// Call appropriate callback based on message type
|
||||
switch (message.type) {
|
||||
case 'alert':
|
||||
options?.onAlert?.(message.data);
|
||||
break;
|
||||
case 'recommendation':
|
||||
options?.onRecommendation?.(message.data);
|
||||
break;
|
||||
case 'alert_update':
|
||||
options?.onAlertUpdate?.(message.data);
|
||||
// Update specific alert in cache
|
||||
if (message.data.id) {
|
||||
queryClient.setQueryData(
|
||||
alertProcessorKeys.alerts.detail(tenantId, message.data.id),
|
||||
message.data
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'system_status':
|
||||
options?.onSystemStatus?.(message.data);
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error parsing SSE message:', error);
|
||||
}
|
||||
};
|
||||
|
||||
eventSource.onerror = (error) => {
|
||||
console.error('Alert SSE error:', error);
|
||||
reject(error);
|
||||
};
|
||||
|
||||
// Return cleanup function
|
||||
return () => {
|
||||
eventSource.close();
|
||||
};
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
},
|
||||
enabled: !!tenantId,
|
||||
refetchOnWindowFocus: false,
|
||||
retry: false,
|
||||
staleTime: Infinity,
|
||||
});
|
||||
};
|
||||
|
||||
// Utility Hooks
|
||||
export const useActiveAlertsCount = (tenantId: string) => {
|
||||
const { data: dashboardData } = useAlertDashboardData(tenantId);
|
||||
return dashboardData?.active_alerts?.length || 0;
|
||||
};
|
||||
|
||||
export const useAlertsByPriority = (tenantId: string) => {
|
||||
const { data: dashboardData } = useAlertDashboardData(tenantId);
|
||||
return dashboardData?.severity_counts || {};
|
||||
};
|
||||
|
||||
export const useUnreadAlertsCount = (tenantId: string) => {
|
||||
const { data: alerts } = useAlerts(tenantId, {
|
||||
status: 'active',
|
||||
limit: 1000 // Get all active alerts to count unread ones
|
||||
});
|
||||
|
||||
return alerts?.data?.filter(alert => alert.status === 'active')?.length || 0;
|
||||
};
|
||||
212
frontend/src/api/hooks/dataImport.ts
Normal file
212
frontend/src/api/hooks/dataImport.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
/**
|
||||
* Data Import React Query hooks
|
||||
* Provides data fetching, caching, and state management for data import operations
|
||||
*/
|
||||
|
||||
import { useMutation, useQuery, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
|
||||
import { dataImportService } from '../services/dataImport';
|
||||
import { ApiError } from '../client/apiClient';
|
||||
import type {
|
||||
ImportValidationResponse,
|
||||
ImportProcessResponse,
|
||||
ImportStatusResponse,
|
||||
} from '../types/dataImport';
|
||||
|
||||
// Query Keys Factory
|
||||
export const dataImportKeys = {
|
||||
all: ['data-import'] as const,
|
||||
status: (tenantId: string, importId: string) =>
|
||||
[...dataImportKeys.all, 'status', tenantId, importId] as const,
|
||||
} as const;
|
||||
|
||||
// Status Query
|
||||
export const useImportStatus = (
|
||||
tenantId: string,
|
||||
importId: string,
|
||||
options?: Omit<UseQueryOptions<ImportStatusResponse, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<ImportStatusResponse, ApiError>({
|
||||
queryKey: dataImportKeys.status(tenantId, importId),
|
||||
queryFn: () => dataImportService.getImportStatus(tenantId, importId),
|
||||
enabled: !!tenantId && !!importId,
|
||||
refetchInterval: 5000, // Poll every 5 seconds for active imports
|
||||
staleTime: 1000, // Consider data stale after 1 second
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// Validation Mutations
|
||||
export const useValidateJsonData = (
|
||||
options?: UseMutationOptions<
|
||||
ImportValidationResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; data: any }
|
||||
>
|
||||
) => {
|
||||
return useMutation<
|
||||
ImportValidationResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; data: any }
|
||||
>({
|
||||
mutationFn: ({ tenantId, data }) => dataImportService.validateJsonData(tenantId, data),
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useValidateCsvFile = (
|
||||
options?: UseMutationOptions<
|
||||
ImportValidationResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; file: File }
|
||||
>
|
||||
) => {
|
||||
return useMutation<
|
||||
ImportValidationResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; file: File }
|
||||
>({
|
||||
mutationFn: ({ tenantId, file }) => dataImportService.validateCsvFile(tenantId, file),
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// Import Mutations
|
||||
export const useImportJsonData = (
|
||||
options?: UseMutationOptions<
|
||||
ImportProcessResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; data: any; options?: { skip_validation?: boolean; chunk_size?: number } }
|
||||
>
|
||||
) => {
|
||||
return useMutation<
|
||||
ImportProcessResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; data: any; options?: { skip_validation?: boolean; chunk_size?: number } }
|
||||
>({
|
||||
mutationFn: ({ tenantId, data, options: importOptions }) =>
|
||||
dataImportService.importJsonData(tenantId, data, importOptions),
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useImportCsvFile = (
|
||||
options?: UseMutationOptions<
|
||||
ImportProcessResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; file: File; options?: { skip_validation?: boolean; chunk_size?: number } }
|
||||
>
|
||||
) => {
|
||||
return useMutation<
|
||||
ImportProcessResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; file: File; options?: { skip_validation?: boolean; chunk_size?: number } }
|
||||
>({
|
||||
mutationFn: ({ tenantId, file, options: importOptions }) =>
|
||||
dataImportService.importCsvFile(tenantId, file, importOptions),
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// Combined validation and import hook for easier use
|
||||
export const useValidateAndImportFile = () => {
|
||||
const validateCsv = useValidateCsvFile();
|
||||
const validateJson = useValidateJsonData();
|
||||
const importCsv = useImportCsvFile();
|
||||
const importJson = useImportJsonData();
|
||||
|
||||
const processFile = async (
|
||||
tenantId: string,
|
||||
file: File,
|
||||
options?: {
|
||||
skipValidation?: boolean;
|
||||
chunkSize?: number;
|
||||
onProgress?: (stage: string, progress: number, message: string) => void;
|
||||
}
|
||||
): Promise<{
|
||||
validationResult?: ImportValidationResponse;
|
||||
importResult?: ImportProcessResponse;
|
||||
success: boolean;
|
||||
error?: string;
|
||||
}> => {
|
||||
try {
|
||||
let validationResult: ImportValidationResponse | undefined;
|
||||
|
||||
// Step 1: Validation (unless skipped)
|
||||
if (!options?.skipValidation) {
|
||||
options?.onProgress?.('validating', 20, 'Validando estructura del archivo...');
|
||||
|
||||
const fileExtension = file.name.split('.').pop()?.toLowerCase();
|
||||
if (fileExtension === 'csv') {
|
||||
validationResult = await validateCsv.mutateAsync({ tenantId, file });
|
||||
} else if (fileExtension === 'json') {
|
||||
const jsonData = await file.text().then(text => JSON.parse(text));
|
||||
validationResult = await validateJson.mutateAsync({ tenantId, data: jsonData });
|
||||
} else {
|
||||
throw new Error('Formato de archivo no soportado. Use CSV o JSON.');
|
||||
}
|
||||
|
||||
options?.onProgress?.('validating', 50, 'Verificando integridad de datos...');
|
||||
|
||||
if (!validationResult.valid) {
|
||||
throw new Error(`Archivo inválido: ${validationResult.errors?.join(', ')}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Import
|
||||
options?.onProgress?.('importing', 70, 'Importando datos...');
|
||||
|
||||
const importOptions = {
|
||||
skip_validation: options?.skipValidation || false,
|
||||
chunk_size: options?.chunkSize,
|
||||
};
|
||||
|
||||
let importResult: ImportProcessResponse;
|
||||
const fileExtension = file.name.split('.').pop()?.toLowerCase();
|
||||
|
||||
if (fileExtension === 'csv') {
|
||||
importResult = await importCsv.mutateAsync({
|
||||
tenantId,
|
||||
file,
|
||||
options: importOptions
|
||||
});
|
||||
} else if (fileExtension === 'json') {
|
||||
const jsonData = await file.text().then(text => JSON.parse(text));
|
||||
importResult = await importJson.mutateAsync({
|
||||
tenantId,
|
||||
data: jsonData,
|
||||
options: importOptions
|
||||
});
|
||||
} else {
|
||||
throw new Error('Formato de archivo no soportado. Use CSV o JSON.');
|
||||
}
|
||||
|
||||
options?.onProgress?.('completed', 100, 'Importación completada');
|
||||
|
||||
return {
|
||||
validationResult,
|
||||
importResult,
|
||||
success: true,
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Error procesando archivo';
|
||||
options?.onProgress?.('error', 0, errorMessage);
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: errorMessage,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
processFile,
|
||||
validateCsv,
|
||||
validateJson,
|
||||
importCsv,
|
||||
importJson,
|
||||
isValidating: validateCsv.isPending || validateJson.isPending,
|
||||
isImporting: importCsv.isPending || importJson.isPending,
|
||||
isLoading: validateCsv.isPending || validateJson.isPending || importCsv.isPending || importJson.isPending,
|
||||
error: validateCsv.error || validateJson.error || importCsv.error || importJson.error,
|
||||
};
|
||||
};
|
||||
650
frontend/src/api/hooks/suppliers.ts
Normal file
650
frontend/src/api/hooks/suppliers.ts
Normal file
@@ -0,0 +1,650 @@
|
||||
/**
|
||||
* Suppliers React Query hooks
|
||||
* Provides data fetching, caching, and state management for supplier operations
|
||||
*/
|
||||
|
||||
import { useMutation, useQuery, useQueryClient, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
|
||||
import { suppliersService } from '../services/suppliers';
|
||||
import { ApiError } from '../client/apiClient';
|
||||
import type {
|
||||
SupplierCreate,
|
||||
SupplierUpdate,
|
||||
SupplierResponse,
|
||||
SupplierSummary,
|
||||
SupplierApproval,
|
||||
SupplierQueryParams,
|
||||
SupplierStatistics,
|
||||
TopSuppliersResponse,
|
||||
PurchaseOrderCreate,
|
||||
PurchaseOrderUpdate,
|
||||
PurchaseOrderResponse,
|
||||
PurchaseOrderApproval,
|
||||
PurchaseOrderQueryParams,
|
||||
DeliveryCreate,
|
||||
DeliveryUpdate,
|
||||
DeliveryResponse,
|
||||
DeliveryReceiptConfirmation,
|
||||
DeliveryQueryParams,
|
||||
PerformanceCalculationRequest,
|
||||
PerformanceMetrics,
|
||||
PerformanceAlert,
|
||||
PaginatedResponse,
|
||||
} from '../types/suppliers';
|
||||
|
||||
// Query Keys Factory
|
||||
export const suppliersKeys = {
|
||||
all: ['suppliers'] as const,
|
||||
suppliers: {
|
||||
all: () => [...suppliersKeys.all, 'suppliers'] as const,
|
||||
lists: () => [...suppliersKeys.suppliers.all(), 'list'] as const,
|
||||
list: (tenantId: string, params?: SupplierQueryParams) =>
|
||||
[...suppliersKeys.suppliers.lists(), tenantId, params] as const,
|
||||
details: () => [...suppliersKeys.suppliers.all(), 'detail'] as const,
|
||||
detail: (tenantId: string, supplierId: string) =>
|
||||
[...suppliersKeys.suppliers.details(), tenantId, supplierId] as const,
|
||||
statistics: (tenantId: string) =>
|
||||
[...suppliersKeys.suppliers.all(), 'statistics', tenantId] as const,
|
||||
top: (tenantId: string) =>
|
||||
[...suppliersKeys.suppliers.all(), 'top', tenantId] as const,
|
||||
byType: (tenantId: string, supplierType: string) =>
|
||||
[...suppliersKeys.suppliers.all(), 'by-type', tenantId, supplierType] as const,
|
||||
},
|
||||
purchaseOrders: {
|
||||
all: () => [...suppliersKeys.all, 'purchase-orders'] as const,
|
||||
lists: () => [...suppliersKeys.purchaseOrders.all(), 'list'] as const,
|
||||
list: (params?: PurchaseOrderQueryParams) =>
|
||||
[...suppliersKeys.purchaseOrders.lists(), params] as const,
|
||||
details: () => [...suppliersKeys.purchaseOrders.all(), 'detail'] as const,
|
||||
detail: (orderId: string) =>
|
||||
[...suppliersKeys.purchaseOrders.details(), orderId] as const,
|
||||
},
|
||||
deliveries: {
|
||||
all: () => [...suppliersKeys.all, 'deliveries'] as const,
|
||||
lists: () => [...suppliersKeys.deliveries.all(), 'list'] as const,
|
||||
list: (params?: DeliveryQueryParams) =>
|
||||
[...suppliersKeys.deliveries.lists(), params] as const,
|
||||
details: () => [...suppliersKeys.deliveries.all(), 'detail'] as const,
|
||||
detail: (deliveryId: string) =>
|
||||
[...suppliersKeys.deliveries.details(), deliveryId] as const,
|
||||
},
|
||||
performance: {
|
||||
all: () => [...suppliersKeys.all, 'performance'] as const,
|
||||
metrics: (tenantId: string, supplierId: string) =>
|
||||
[...suppliersKeys.performance.all(), 'metrics', tenantId, supplierId] as const,
|
||||
alerts: (tenantId: string, supplierId?: string) =>
|
||||
[...suppliersKeys.performance.all(), 'alerts', tenantId, supplierId] as const,
|
||||
},
|
||||
} as const;
|
||||
|
||||
// Supplier Queries
|
||||
export const useSuppliers = (
|
||||
tenantId: string,
|
||||
queryParams?: SupplierQueryParams,
|
||||
options?: Omit<UseQueryOptions<PaginatedResponse<SupplierSummary>, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<PaginatedResponse<SupplierSummary>, ApiError>({
|
||||
queryKey: suppliersKeys.suppliers.list(tenantId, queryParams),
|
||||
queryFn: () => suppliersService.getSuppliers(tenantId, queryParams),
|
||||
enabled: !!tenantId,
|
||||
staleTime: 2 * 60 * 1000, // 2 minutes
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useSupplier = (
|
||||
tenantId: string,
|
||||
supplierId: string,
|
||||
options?: Omit<UseQueryOptions<SupplierResponse, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<SupplierResponse, ApiError>({
|
||||
queryKey: suppliersKeys.suppliers.detail(tenantId, supplierId),
|
||||
queryFn: () => suppliersService.getSupplier(tenantId, supplierId),
|
||||
enabled: !!tenantId && !!supplierId,
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useSupplierStatistics = (
|
||||
tenantId: string,
|
||||
options?: Omit<UseQueryOptions<SupplierStatistics, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<SupplierStatistics, ApiError>({
|
||||
queryKey: suppliersKeys.suppliers.statistics(tenantId),
|
||||
queryFn: () => suppliersService.getSupplierStatistics(tenantId),
|
||||
enabled: !!tenantId,
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useActiveSuppliers = (
|
||||
tenantId: string,
|
||||
queryParams?: Omit<SupplierQueryParams, 'status'>,
|
||||
options?: Omit<UseQueryOptions<PaginatedResponse<SupplierSummary>, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<PaginatedResponse<SupplierSummary>, ApiError>({
|
||||
queryKey: suppliersKeys.suppliers.list(tenantId, { ...queryParams, status: 'active' }),
|
||||
queryFn: () => suppliersService.getActiveSuppliers(tenantId, queryParams),
|
||||
enabled: !!tenantId,
|
||||
staleTime: 2 * 60 * 1000, // 2 minutes
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useTopSuppliers = (
|
||||
tenantId: string,
|
||||
options?: Omit<UseQueryOptions<TopSuppliersResponse, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<TopSuppliersResponse, ApiError>({
|
||||
queryKey: suppliersKeys.suppliers.top(tenantId),
|
||||
queryFn: () => suppliersService.getTopSuppliers(tenantId),
|
||||
enabled: !!tenantId,
|
||||
staleTime: 10 * 60 * 1000, // 10 minutes
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const usePendingApprovalSuppliers = (
|
||||
tenantId: string,
|
||||
options?: Omit<UseQueryOptions<PaginatedResponse<SupplierSummary>, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<PaginatedResponse<SupplierSummary>, ApiError>({
|
||||
queryKey: suppliersKeys.suppliers.list(tenantId, { status: 'pending_approval' }),
|
||||
queryFn: () => suppliersService.getPendingApprovalSuppliers(tenantId),
|
||||
enabled: !!tenantId,
|
||||
staleTime: 1 * 60 * 1000, // 1 minute
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useSuppliersByType = (
|
||||
tenantId: string,
|
||||
supplierType: string,
|
||||
queryParams?: Omit<SupplierQueryParams, 'supplier_type'>,
|
||||
options?: Omit<UseQueryOptions<PaginatedResponse<SupplierSummary>, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<PaginatedResponse<SupplierSummary>, ApiError>({
|
||||
queryKey: suppliersKeys.suppliers.byType(tenantId, supplierType),
|
||||
queryFn: () => suppliersService.getSuppliersByType(tenantId, supplierType, queryParams),
|
||||
enabled: !!tenantId && !!supplierType,
|
||||
staleTime: 2 * 60 * 1000, // 2 minutes
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// Purchase Order Queries
|
||||
export const usePurchaseOrders = (
|
||||
queryParams?: PurchaseOrderQueryParams,
|
||||
options?: Omit<UseQueryOptions<PaginatedResponse<PurchaseOrderResponse>, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<PaginatedResponse<PurchaseOrderResponse>, ApiError>({
|
||||
queryKey: suppliersKeys.purchaseOrders.list(queryParams),
|
||||
queryFn: () => suppliersService.getPurchaseOrders(queryParams),
|
||||
staleTime: 1 * 60 * 1000, // 1 minute
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const usePurchaseOrder = (
|
||||
orderId: string,
|
||||
options?: Omit<UseQueryOptions<PurchaseOrderResponse, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<PurchaseOrderResponse, ApiError>({
|
||||
queryKey: suppliersKeys.purchaseOrders.detail(orderId),
|
||||
queryFn: () => suppliersService.getPurchaseOrder(orderId),
|
||||
enabled: !!orderId,
|
||||
staleTime: 2 * 60 * 1000, // 2 minutes
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// Delivery Queries
|
||||
export const useDeliveries = (
|
||||
queryParams?: DeliveryQueryParams,
|
||||
options?: Omit<UseQueryOptions<PaginatedResponse<DeliveryResponse>, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<PaginatedResponse<DeliveryResponse>, ApiError>({
|
||||
queryKey: suppliersKeys.deliveries.list(queryParams),
|
||||
queryFn: () => suppliersService.getDeliveries(queryParams),
|
||||
staleTime: 1 * 60 * 1000, // 1 minute
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useDelivery = (
|
||||
deliveryId: string,
|
||||
options?: Omit<UseQueryOptions<DeliveryResponse, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<DeliveryResponse, ApiError>({
|
||||
queryKey: suppliersKeys.deliveries.detail(deliveryId),
|
||||
queryFn: () => suppliersService.getDelivery(deliveryId),
|
||||
enabled: !!deliveryId,
|
||||
staleTime: 30 * 1000, // 30 seconds
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// Performance Queries
|
||||
export const useSupplierPerformanceMetrics = (
|
||||
tenantId: string,
|
||||
supplierId: string,
|
||||
options?: Omit<UseQueryOptions<PerformanceMetrics, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<PerformanceMetrics, ApiError>({
|
||||
queryKey: suppliersKeys.performance.metrics(tenantId, supplierId),
|
||||
queryFn: () => suppliersService.getSupplierPerformanceMetrics(tenantId, supplierId),
|
||||
enabled: !!tenantId && !!supplierId,
|
||||
staleTime: 10 * 60 * 1000, // 10 minutes
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const usePerformanceAlerts = (
|
||||
tenantId: string,
|
||||
supplierId?: string,
|
||||
options?: Omit<UseQueryOptions<PerformanceAlert[], ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<PerformanceAlert[], ApiError>({
|
||||
queryKey: suppliersKeys.performance.alerts(tenantId, supplierId),
|
||||
queryFn: () => suppliersService.getPerformanceAlerts(tenantId, supplierId),
|
||||
enabled: !!tenantId,
|
||||
staleTime: 2 * 60 * 1000, // 2 minutes
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// Supplier Mutations
|
||||
export const useCreateSupplier = (
|
||||
options?: UseMutationOptions<
|
||||
SupplierResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; supplierData: SupplierCreate }
|
||||
>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
SupplierResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; supplierData: SupplierCreate }
|
||||
>({
|
||||
mutationFn: ({ tenantId, supplierData }) =>
|
||||
suppliersService.createSupplier(tenantId, supplierData),
|
||||
onSuccess: (data, { tenantId }) => {
|
||||
// Add to cache
|
||||
queryClient.setQueryData(
|
||||
suppliersKeys.suppliers.detail(tenantId, data.id),
|
||||
data
|
||||
);
|
||||
|
||||
// Invalidate lists and statistics
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: suppliersKeys.suppliers.lists()
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: suppliersKeys.suppliers.statistics(tenantId)
|
||||
});
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateSupplier = (
|
||||
options?: UseMutationOptions<
|
||||
SupplierResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; supplierId: string; updateData: SupplierUpdate }
|
||||
>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
SupplierResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; supplierId: string; updateData: SupplierUpdate }
|
||||
>({
|
||||
mutationFn: ({ tenantId, supplierId, updateData }) =>
|
||||
suppliersService.updateSupplier(tenantId, supplierId, updateData),
|
||||
onSuccess: (data, { tenantId, supplierId }) => {
|
||||
// Update cache
|
||||
queryClient.setQueryData(
|
||||
suppliersKeys.suppliers.detail(tenantId, supplierId),
|
||||
data
|
||||
);
|
||||
|
||||
// Invalidate lists
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: suppliersKeys.suppliers.lists()
|
||||
});
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteSupplier = (
|
||||
options?: UseMutationOptions<
|
||||
{ message: string },
|
||||
ApiError,
|
||||
{ tenantId: string; supplierId: string }
|
||||
>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
{ message: string },
|
||||
ApiError,
|
||||
{ tenantId: string; supplierId: string }
|
||||
>({
|
||||
mutationFn: ({ tenantId, supplierId }) =>
|
||||
suppliersService.deleteSupplier(tenantId, supplierId),
|
||||
onSuccess: (_, { tenantId, supplierId }) => {
|
||||
// Remove from cache
|
||||
queryClient.removeQueries({
|
||||
queryKey: suppliersKeys.suppliers.detail(tenantId, supplierId)
|
||||
});
|
||||
|
||||
// Invalidate lists and statistics
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: suppliersKeys.suppliers.lists()
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: suppliersKeys.suppliers.statistics(tenantId)
|
||||
});
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useApproveSupplier = (
|
||||
options?: UseMutationOptions<
|
||||
SupplierResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; supplierId: string; approval: SupplierApproval }
|
||||
>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
SupplierResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; supplierId: string; approval: SupplierApproval }
|
||||
>({
|
||||
mutationFn: ({ tenantId, supplierId, approval }) =>
|
||||
suppliersService.approveSupplier(tenantId, supplierId, approval),
|
||||
onSuccess: (data, { tenantId, supplierId }) => {
|
||||
// Update cache
|
||||
queryClient.setQueryData(
|
||||
suppliersKeys.suppliers.detail(tenantId, supplierId),
|
||||
data
|
||||
);
|
||||
|
||||
// Invalidate lists and statistics
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: suppliersKeys.suppliers.lists()
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: suppliersKeys.suppliers.statistics(tenantId)
|
||||
});
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// Purchase Order Mutations
|
||||
export const useCreatePurchaseOrder = (
|
||||
options?: UseMutationOptions<PurchaseOrderResponse, ApiError, PurchaseOrderCreate>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<PurchaseOrderResponse, ApiError, PurchaseOrderCreate>({
|
||||
mutationFn: (orderData) => suppliersService.createPurchaseOrder(orderData),
|
||||
onSuccess: (data) => {
|
||||
// Add to cache
|
||||
queryClient.setQueryData(
|
||||
suppliersKeys.purchaseOrders.detail(data.id),
|
||||
data
|
||||
);
|
||||
|
||||
// Invalidate lists
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: suppliersKeys.purchaseOrders.lists()
|
||||
});
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdatePurchaseOrder = (
|
||||
options?: UseMutationOptions<
|
||||
PurchaseOrderResponse,
|
||||
ApiError,
|
||||
{ orderId: string; updateData: PurchaseOrderUpdate }
|
||||
>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
PurchaseOrderResponse,
|
||||
ApiError,
|
||||
{ orderId: string; updateData: PurchaseOrderUpdate }
|
||||
>({
|
||||
mutationFn: ({ orderId, updateData }) =>
|
||||
suppliersService.updatePurchaseOrder(orderId, updateData),
|
||||
onSuccess: (data, { orderId }) => {
|
||||
// Update cache
|
||||
queryClient.setQueryData(
|
||||
suppliersKeys.purchaseOrders.detail(orderId),
|
||||
data
|
||||
);
|
||||
|
||||
// Invalidate lists
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: suppliersKeys.purchaseOrders.lists()
|
||||
});
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useApprovePurchaseOrder = (
|
||||
options?: UseMutationOptions<
|
||||
PurchaseOrderResponse,
|
||||
ApiError,
|
||||
{ orderId: string; approval: PurchaseOrderApproval }
|
||||
>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
PurchaseOrderResponse,
|
||||
ApiError,
|
||||
{ orderId: string; approval: PurchaseOrderApproval }
|
||||
>({
|
||||
mutationFn: ({ orderId, approval }) =>
|
||||
suppliersService.approvePurchaseOrder(orderId, approval),
|
||||
onSuccess: (data, { orderId }) => {
|
||||
// Update cache
|
||||
queryClient.setQueryData(
|
||||
suppliersKeys.purchaseOrders.detail(orderId),
|
||||
data
|
||||
);
|
||||
|
||||
// Invalidate lists
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: suppliersKeys.purchaseOrders.lists()
|
||||
});
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// Delivery Mutations
|
||||
export const useCreateDelivery = (
|
||||
options?: UseMutationOptions<DeliveryResponse, ApiError, DeliveryCreate>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<DeliveryResponse, ApiError, DeliveryCreate>({
|
||||
mutationFn: (deliveryData) => suppliersService.createDelivery(deliveryData),
|
||||
onSuccess: (data) => {
|
||||
// Add to cache
|
||||
queryClient.setQueryData(
|
||||
suppliersKeys.deliveries.detail(data.id),
|
||||
data
|
||||
);
|
||||
|
||||
// Invalidate lists
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: suppliersKeys.deliveries.lists()
|
||||
});
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateDelivery = (
|
||||
options?: UseMutationOptions<
|
||||
DeliveryResponse,
|
||||
ApiError,
|
||||
{ deliveryId: string; updateData: DeliveryUpdate }
|
||||
>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
DeliveryResponse,
|
||||
ApiError,
|
||||
{ deliveryId: string; updateData: DeliveryUpdate }
|
||||
>({
|
||||
mutationFn: ({ deliveryId, updateData }) =>
|
||||
suppliersService.updateDelivery(deliveryId, updateData),
|
||||
onSuccess: (data, { deliveryId }) => {
|
||||
// Update cache
|
||||
queryClient.setQueryData(
|
||||
suppliersKeys.deliveries.detail(deliveryId),
|
||||
data
|
||||
);
|
||||
|
||||
// Invalidate lists
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: suppliersKeys.deliveries.lists()
|
||||
});
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useConfirmDeliveryReceipt = (
|
||||
options?: UseMutationOptions<
|
||||
DeliveryResponse,
|
||||
ApiError,
|
||||
{ deliveryId: string; confirmation: DeliveryReceiptConfirmation }
|
||||
>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
DeliveryResponse,
|
||||
ApiError,
|
||||
{ deliveryId: string; confirmation: DeliveryReceiptConfirmation }
|
||||
>({
|
||||
mutationFn: ({ deliveryId, confirmation }) =>
|
||||
suppliersService.confirmDeliveryReceipt(deliveryId, confirmation),
|
||||
onSuccess: (data, { deliveryId }) => {
|
||||
// Update cache
|
||||
queryClient.setQueryData(
|
||||
suppliersKeys.deliveries.detail(deliveryId),
|
||||
data
|
||||
);
|
||||
|
||||
// Invalidate lists and performance metrics
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: suppliersKeys.deliveries.lists()
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: suppliersKeys.performance.all()
|
||||
});
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// Performance Mutations
|
||||
export const useCalculateSupplierPerformance = (
|
||||
options?: UseMutationOptions<
|
||||
{ message: string; calculation_id: string },
|
||||
ApiError,
|
||||
{ tenantId: string; supplierId: string; request?: PerformanceCalculationRequest }
|
||||
>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
{ message: string; calculation_id: string },
|
||||
ApiError,
|
||||
{ tenantId: string; supplierId: string; request?: PerformanceCalculationRequest }
|
||||
>({
|
||||
mutationFn: ({ tenantId, supplierId, request }) =>
|
||||
suppliersService.calculateSupplierPerformance(tenantId, supplierId, request),
|
||||
onSuccess: (_, { tenantId, supplierId }) => {
|
||||
// Invalidate performance metrics after calculation
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: suppliersKeys.performance.metrics(tenantId, supplierId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: suppliersKeys.performance.alerts(tenantId)
|
||||
});
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useEvaluatePerformanceAlerts = (
|
||||
options?: UseMutationOptions<
|
||||
{ alerts_generated: number; message: string },
|
||||
ApiError,
|
||||
{ tenantId: string }
|
||||
>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
{ alerts_generated: number; message: string },
|
||||
ApiError,
|
||||
{ tenantId: string }
|
||||
>({
|
||||
mutationFn: ({ tenantId }) => suppliersService.evaluatePerformanceAlerts(tenantId),
|
||||
onSuccess: (_, { tenantId }) => {
|
||||
// Invalidate performance alerts
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: suppliersKeys.performance.alerts(tenantId)
|
||||
});
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// Utility Hooks
|
||||
export const useSuppliersByStatus = (tenantId: string, status: string) => {
|
||||
return useSuppliers(tenantId, { status: status as any });
|
||||
};
|
||||
|
||||
export const useSuppliersCount = (tenantId: string) => {
|
||||
const { data: statistics } = useSupplierStatistics(tenantId);
|
||||
return statistics?.total_suppliers || 0;
|
||||
};
|
||||
|
||||
export const useActiveSuppliersCount = (tenantId: string) => {
|
||||
const { data: statistics } = useSupplierStatistics(tenantId);
|
||||
return statistics?.active_suppliers || 0;
|
||||
};
|
||||
|
||||
export const usePendingOrdersCount = (supplierId?: string) => {
|
||||
const { data: orders } = usePurchaseOrders({
|
||||
supplier_id: supplierId,
|
||||
status: 'pending_approval',
|
||||
limit: 1000
|
||||
});
|
||||
return orders?.data?.length || 0;
|
||||
};
|
||||
350
frontend/src/api/hooks/training.ts
Normal file
350
frontend/src/api/hooks/training.ts
Normal file
@@ -0,0 +1,350 @@
|
||||
/**
|
||||
* Training React Query hooks
|
||||
* Provides data fetching, caching, and state management for training operations
|
||||
*/
|
||||
|
||||
import { useMutation, useQuery, useQueryClient, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
|
||||
import { trainingService } from '../services/training';
|
||||
import { ApiError } from '../client/apiClient';
|
||||
import type {
|
||||
TrainingJobRequest,
|
||||
TrainingJobResponse,
|
||||
TrainingJobStatus,
|
||||
SingleProductTrainingRequest,
|
||||
ActiveModelResponse,
|
||||
ModelMetricsResponse,
|
||||
TrainedModelResponse,
|
||||
TenantStatistics,
|
||||
ModelPerformanceResponse,
|
||||
ModelsQueryParams,
|
||||
PaginatedResponse,
|
||||
} from '../types/training';
|
||||
|
||||
// Query Keys Factory
|
||||
export const trainingKeys = {
|
||||
all: ['training'] as const,
|
||||
jobs: {
|
||||
all: () => [...trainingKeys.all, 'jobs'] as const,
|
||||
status: (tenantId: string, jobId: string) =>
|
||||
[...trainingKeys.jobs.all(), 'status', tenantId, jobId] as const,
|
||||
},
|
||||
models: {
|
||||
all: () => [...trainingKeys.all, 'models'] as const,
|
||||
lists: () => [...trainingKeys.models.all(), 'list'] as const,
|
||||
list: (tenantId: string, params?: ModelsQueryParams) =>
|
||||
[...trainingKeys.models.lists(), tenantId, params] as const,
|
||||
details: () => [...trainingKeys.models.all(), 'detail'] as const,
|
||||
detail: (tenantId: string, modelId: string) =>
|
||||
[...trainingKeys.models.details(), tenantId, modelId] as const,
|
||||
active: (tenantId: string, inventoryProductId: string) =>
|
||||
[...trainingKeys.models.all(), 'active', tenantId, inventoryProductId] as const,
|
||||
metrics: (tenantId: string, modelId: string) =>
|
||||
[...trainingKeys.models.all(), 'metrics', tenantId, modelId] as const,
|
||||
performance: (tenantId: string, modelId: string) =>
|
||||
[...trainingKeys.models.all(), 'performance', tenantId, modelId] as const,
|
||||
},
|
||||
statistics: (tenantId: string) =>
|
||||
[...trainingKeys.all, 'statistics', tenantId] as const,
|
||||
} as const;
|
||||
|
||||
// Training Job Queries
|
||||
export const useTrainingJobStatus = (
|
||||
tenantId: string,
|
||||
jobId: string,
|
||||
options?: Omit<UseQueryOptions<TrainingJobStatus, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<TrainingJobStatus, ApiError>({
|
||||
queryKey: trainingKeys.jobs.status(tenantId, jobId),
|
||||
queryFn: () => trainingService.getTrainingJobStatus(tenantId, jobId),
|
||||
enabled: !!tenantId && !!jobId,
|
||||
refetchInterval: 5000, // Poll every 5 seconds while training
|
||||
staleTime: 1000, // Consider data stale after 1 second
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// Model Queries
|
||||
export const useActiveModel = (
|
||||
tenantId: string,
|
||||
inventoryProductId: string,
|
||||
options?: Omit<UseQueryOptions<ActiveModelResponse, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<ActiveModelResponse, ApiError>({
|
||||
queryKey: trainingKeys.models.active(tenantId, inventoryProductId),
|
||||
queryFn: () => trainingService.getActiveModel(tenantId, inventoryProductId),
|
||||
enabled: !!tenantId && !!inventoryProductId,
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useModels = (
|
||||
tenantId: string,
|
||||
queryParams?: ModelsQueryParams,
|
||||
options?: Omit<UseQueryOptions<PaginatedResponse<TrainedModelResponse>, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<PaginatedResponse<TrainedModelResponse>, ApiError>({
|
||||
queryKey: trainingKeys.models.list(tenantId, queryParams),
|
||||
queryFn: () => trainingService.getModels(tenantId, queryParams),
|
||||
enabled: !!tenantId,
|
||||
staleTime: 2 * 60 * 1000, // 2 minutes
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useModelMetrics = (
|
||||
tenantId: string,
|
||||
modelId: string,
|
||||
options?: Omit<UseQueryOptions<ModelMetricsResponse, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<ModelMetricsResponse, ApiError>({
|
||||
queryKey: trainingKeys.models.metrics(tenantId, modelId),
|
||||
queryFn: () => trainingService.getModelMetrics(tenantId, modelId),
|
||||
enabled: !!tenantId && !!modelId,
|
||||
staleTime: 10 * 60 * 1000, // 10 minutes
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useModelPerformance = (
|
||||
tenantId: string,
|
||||
modelId: string,
|
||||
options?: Omit<UseQueryOptions<ModelPerformanceResponse, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<ModelPerformanceResponse, ApiError>({
|
||||
queryKey: trainingKeys.models.performance(tenantId, modelId),
|
||||
queryFn: () => trainingService.getModelPerformance(tenantId, modelId),
|
||||
enabled: !!tenantId && !!modelId,
|
||||
staleTime: 10 * 60 * 1000, // 10 minutes
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// Statistics Queries
|
||||
export const useTenantTrainingStatistics = (
|
||||
tenantId: string,
|
||||
options?: Omit<UseQueryOptions<TenantStatistics, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<TenantStatistics, ApiError>({
|
||||
queryKey: trainingKeys.statistics(tenantId),
|
||||
queryFn: () => trainingService.getTenantStatistics(tenantId),
|
||||
enabled: !!tenantId,
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// Training Job Mutations
|
||||
export const useCreateTrainingJob = (
|
||||
options?: UseMutationOptions<
|
||||
TrainingJobResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; request: TrainingJobRequest }
|
||||
>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
TrainingJobResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; request: TrainingJobRequest }
|
||||
>({
|
||||
mutationFn: ({ tenantId, request }) => trainingService.createTrainingJob(tenantId, request),
|
||||
onSuccess: (data, { tenantId }) => {
|
||||
// Add the job status to cache
|
||||
queryClient.setQueryData(
|
||||
trainingKeys.jobs.status(tenantId, data.job_id),
|
||||
{
|
||||
job_id: data.job_id,
|
||||
status: data.status,
|
||||
progress: 0,
|
||||
message: data.message,
|
||||
}
|
||||
);
|
||||
|
||||
// Invalidate statistics to reflect the new training job
|
||||
queryClient.invalidateQueries({ queryKey: trainingKeys.statistics(tenantId) });
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useTrainSingleProduct = (
|
||||
options?: UseMutationOptions<
|
||||
TrainingJobResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; inventoryProductId: string; request: SingleProductTrainingRequest }
|
||||
>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
TrainingJobResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; inventoryProductId: string; request: SingleProductTrainingRequest }
|
||||
>({
|
||||
mutationFn: ({ tenantId, inventoryProductId, request }) =>
|
||||
trainingService.trainSingleProduct(tenantId, inventoryProductId, request),
|
||||
onSuccess: (data, { tenantId, inventoryProductId }) => {
|
||||
// Add the job status to cache
|
||||
queryClient.setQueryData(
|
||||
trainingKeys.jobs.status(tenantId, data.job_id),
|
||||
{
|
||||
job_id: data.job_id,
|
||||
status: data.status,
|
||||
progress: 0,
|
||||
message: data.message,
|
||||
}
|
||||
);
|
||||
|
||||
// Invalidate active model for this product
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: trainingKeys.models.active(tenantId, inventoryProductId)
|
||||
});
|
||||
|
||||
// Invalidate statistics
|
||||
queryClient.invalidateQueries({ queryKey: trainingKeys.statistics(tenantId) });
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// Admin Mutations
|
||||
export const useDeleteAllTenantModels = (
|
||||
options?: UseMutationOptions<{ message: string }, ApiError, { tenantId: string }>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<{ message: string }, ApiError, { tenantId: string }>({
|
||||
mutationFn: ({ tenantId }) => trainingService.deleteAllTenantModels(tenantId),
|
||||
onSuccess: (_, { tenantId }) => {
|
||||
// Invalidate all model-related queries for this tenant
|
||||
queryClient.invalidateQueries({ queryKey: trainingKeys.models.all() });
|
||||
queryClient.invalidateQueries({ queryKey: trainingKeys.statistics(tenantId) });
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// WebSocket Hook for Real-time Training Updates
|
||||
export const useTrainingWebSocket = (
|
||||
tenantId: string,
|
||||
jobId: string,
|
||||
token?: string,
|
||||
options?: {
|
||||
onProgress?: (data: any) => void;
|
||||
onCompleted?: (data: any) => void;
|
||||
onError?: (error: any) => void;
|
||||
onStarted?: (data: any) => void;
|
||||
onCancelled?: (data: any) => void;
|
||||
}
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['training-websocket', tenantId, jobId],
|
||||
queryFn: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const ws = trainingService.createWebSocketConnection(tenantId, jobId, token);
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log('Training WebSocket connected');
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
try {
|
||||
const message = JSON.parse(event.data);
|
||||
|
||||
// Update job status in cache
|
||||
queryClient.setQueryData(
|
||||
trainingKeys.jobs.status(tenantId, jobId),
|
||||
(oldData: TrainingJobStatus | undefined) => ({
|
||||
...oldData,
|
||||
job_id: jobId,
|
||||
status: message.status || oldData?.status || 'running',
|
||||
progress: message.progress?.percentage || oldData?.progress || 0,
|
||||
message: message.message || oldData?.message || '',
|
||||
current_step: message.progress?.current_step || oldData?.current_step,
|
||||
estimated_time_remaining: message.progress?.estimated_time_remaining || oldData?.estimated_time_remaining,
|
||||
})
|
||||
);
|
||||
|
||||
// Call appropriate callback based on message type
|
||||
switch (message.type) {
|
||||
case 'progress':
|
||||
options?.onProgress?.(message);
|
||||
break;
|
||||
case 'completed':
|
||||
options?.onCompleted?.(message);
|
||||
// Invalidate models and statistics
|
||||
queryClient.invalidateQueries({ queryKey: trainingKeys.models.all() });
|
||||
queryClient.invalidateQueries({ queryKey: trainingKeys.statistics(tenantId) });
|
||||
resolve(message);
|
||||
break;
|
||||
case 'error':
|
||||
options?.onError?.(message);
|
||||
reject(new Error(message.error));
|
||||
break;
|
||||
case 'started':
|
||||
options?.onStarted?.(message);
|
||||
break;
|
||||
case 'cancelled':
|
||||
options?.onCancelled?.(message);
|
||||
resolve(message);
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error parsing WebSocket message:', error);
|
||||
reject(error);
|
||||
}
|
||||
};
|
||||
|
||||
ws.onerror = (error) => {
|
||||
console.error('Training WebSocket error:', error);
|
||||
reject(error);
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
console.log('Training WebSocket disconnected');
|
||||
};
|
||||
|
||||
// Return cleanup function
|
||||
return () => {
|
||||
ws.close();
|
||||
};
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
},
|
||||
enabled: !!tenantId && !!jobId,
|
||||
refetchOnWindowFocus: false,
|
||||
retry: false,
|
||||
staleTime: Infinity,
|
||||
});
|
||||
};
|
||||
|
||||
// Utility Hooks
|
||||
export const useIsTrainingInProgress = (tenantId: string, jobId?: string) => {
|
||||
const { data: jobStatus } = useTrainingJobStatus(tenantId, jobId || '', {
|
||||
enabled: !!jobId,
|
||||
});
|
||||
|
||||
return jobStatus?.status === 'running' || jobStatus?.status === 'pending';
|
||||
};
|
||||
|
||||
export const useTrainingProgress = (tenantId: string, jobId?: string) => {
|
||||
const { data: jobStatus } = useTrainingJobStatus(tenantId, jobId || '', {
|
||||
enabled: !!jobId,
|
||||
});
|
||||
|
||||
return {
|
||||
progress: jobStatus?.progress || 0,
|
||||
currentStep: jobStatus?.current_step,
|
||||
estimatedTimeRemaining: jobStatus?.estimated_time_remaining,
|
||||
isComplete: jobStatus?.status === 'completed',
|
||||
isFailed: jobStatus?.status === 'failed',
|
||||
isRunning: jobStatus?.status === 'running',
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user