Add new Frontend API folder

This commit is contained in:
Urtzi Alfaro
2025-08-03 17:48:34 +02:00
parent 935f45a283
commit 03e9dc6469
50 changed files with 4737 additions and 3321 deletions

View File

@@ -0,0 +1,30 @@
// frontend/src/api/hooks/index.ts
/**
* Main Hooks Export
*/
export { useAuth, useAuthHeaders } from './useAuth';
export { useTenant } from './useTenant';
export { useData } from './useData';
export { useTraining } from './useTraining';
export { useForecast } from './useForecast';
export { useNotification } from './useNotification';
// Combined hook for common operations
export const useApiHooks = () => {
const auth = useAuth();
const tenant = useTenant();
const data = useData();
const training = useTraining();
const forecast = useForecast();
const notification = useNotification();
return {
auth,
tenant,
data,
training,
forecast,
notification,
};
};

View File

@@ -0,0 +1,193 @@
// frontend/src/api/hooks/useAuth.ts
/**
* Authentication Hooks
* React hooks for authentication operations
*/
import { useState, useEffect, useCallback } from 'react';
import { authService } from '../services';
import type {
LoginRequest,
LoginResponse,
RegisterRequest,
UserResponse,
PasswordResetRequest,
} from '../types';
// Token management
const TOKEN_KEY = 'auth_token';
const REFRESH_TOKEN_KEY = 'refresh_token';
const USER_KEY = 'user_data';
export const useAuth = () => {
const [user, setUser] = useState<UserResponse | null>(null);
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// Initialize auth state from localStorage
useEffect(() => {
const initializeAuth = async () => {
try {
const token = localStorage.getItem(TOKEN_KEY);
const userData = localStorage.getItem(USER_KEY);
if (token && userData) {
setUser(JSON.parse(userData));
setIsAuthenticated(true);
// Verify token is still valid
try {
const currentUser = await authService.getCurrentUser();
setUser(currentUser);
} catch (error) {
// Token expired or invalid, clear auth state
logout();
}
}
} catch (error) {
console.error('Auth initialization error:', error);
logout();
} finally {
setIsLoading(false);
}
};
initializeAuth();
}, []);
const login = useCallback(async (credentials: LoginRequest): Promise<void> => {
try {
setIsLoading(true);
setError(null);
const response = await authService.login(credentials);
// Store tokens and user data
localStorage.setItem(TOKEN_KEY, response.access_token);
if (response.refresh_token) {
localStorage.setItem(REFRESH_TOKEN_KEY, response.refresh_token);
}
if (response.user) {
localStorage.setItem(USER_KEY, JSON.stringify(response.user));
setUser(response.user);
}
setIsAuthenticated(true);
} catch (error) {
const message = error instanceof Error ? error.message : 'Login failed';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
const register = useCallback(async (data: RegisterRequest): Promise<void> => {
try {
setIsLoading(true);
setError(null);
const response = await authService.register(data);
// Auto-login after successful registration
if (response.user) {
await login({ email: data.email, password: data.password });
}
} catch (error) {
const message = error instanceof Error ? error.message : 'Registration failed';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, [login]);
const logout = useCallback(async (): Promise<void> => {
try {
// Call logout endpoint if authenticated
if (isAuthenticated) {
await authService.logout();
}
} catch (error) {
console.error('Logout error:', error);
} finally {
// Clear local state regardless of API call success
localStorage.removeItem(TOKEN_KEY);
localStorage.removeItem(REFRESH_TOKEN_KEY);
localStorage.removeItem(USER_KEY);
setUser(null);
setIsAuthenticated(false);
setError(null);
}
}, [isAuthenticated]);
const updateProfile = useCallback(async (data: Partial<UserResponse>): Promise<void> => {
try {
setIsLoading(true);
setError(null);
const updatedUser = await authService.updateProfile(data);
setUser(updatedUser);
localStorage.setItem(USER_KEY, JSON.stringify(updatedUser));
} catch (error) {
const message = error instanceof Error ? error.message : 'Profile update failed';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
const requestPasswordReset = useCallback(async (data: PasswordResetRequest): Promise<void> => {
try {
setIsLoading(true);
setError(null);
await authService.requestPasswordReset(data);
} catch (error) {
const message = error instanceof Error ? error.message : 'Password reset request failed';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
const changePassword = useCallback(async (currentPassword: string, newPassword: string): Promise<void> => {
try {
setIsLoading(true);
setError(null);
await authService.changePassword(currentPassword, newPassword);
} catch (error) {
const message = error instanceof Error ? error.message : 'Password change failed';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
return {
user,
isAuthenticated,
isLoading,
error,
login,
register,
logout,
updateProfile,
requestPasswordReset,
changePassword,
clearError: () => setError(null),
};
};
// Hook for getting authentication headers
export const useAuthHeaders = () => {
const getAuthHeaders = useCallback(() => {
const token = localStorage.getItem(TOKEN_KEY);
return token ? { Authorization: `Bearer ${token}` } : {};
}, []);
return { getAuthHeaders };
};

View File

@@ -0,0 +1,172 @@
// frontend/src/api/hooks/useData.ts
/**
* Data Management Hooks
*/
import { useState, useCallback } from 'react';
import { dataService } from '../services';
import type {
SalesData,
SalesDataQuery,
SalesImportResult,
DashboardStats,
ActivityItem,
} from '../types';
export const useData = () => {
const [salesData, setSalesData] = useState<SalesData[]>([]);
const [dashboardStats, setDashboardStats] = useState<DashboardStats | null>(null);
const [recentActivity, setRecentActivity] = useState<ActivityItem[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [uploadProgress, setUploadProgress] = useState<number>(0);
const uploadSalesHistory = useCallback(async (
tenantId: string,
file: File,
additionalData?: Record<string, any>
): Promise<SalesImportResult> => {
try {
setIsLoading(true);
setError(null);
setUploadProgress(0);
const result = await dataService.uploadSalesHistory(tenantId, file, {
...additionalData,
onProgress: (progress) => {
setUploadProgress(progress.percentage);
},
});
return result;
} catch (error) {
const message = error instanceof Error ? error.message : 'Upload failed';
setError(message);
throw error;
} finally {
setIsLoading(false);
setUploadProgress(0);
}
}, []);
const validateSalesData = useCallback(async (
tenantId: string,
file: File
): Promise<SalesImportResult> => {
try {
setIsLoading(true);
setError(null);
const result = await dataService.validateSalesData(tenantId, file);
return result;
} catch (error) {
const message = error instanceof Error ? error.message : 'Validation failed';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
const getSalesData = useCallback(async (
tenantId: string,
query?: SalesDataQuery
): Promise<SalesData[]> => {
try {
setIsLoading(true);
setError(null);
const response = await dataService.getSalesData(tenantId, query);
setSalesData(response.data);
return response.data;
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to get sales data';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
const getDashboardStats = useCallback(async (tenantId: string): Promise<DashboardStats> => {
try {
setIsLoading(true);
setError(null);
const stats = await dataService.getDashboardStats(tenantId);
setDashboardStats(stats);
return stats;
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to get dashboard stats';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
const getRecentActivity = useCallback(async (tenantId: string, limit?: number): Promise<ActivityItem[]> => {
try {
setIsLoading(true);
setError(null);
const activity = await dataService.getRecentActivity(tenantId, limit);
setRecentActivity(activity);
return activity;
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to get recent activity';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
const exportSalesData = useCallback(async (
tenantId: string,
format: 'csv' | 'excel' | 'json',
query?: SalesDataQuery
): Promise<void> => {
try {
setIsLoading(true);
setError(null);
const blob = await dataService.exportSalesData(tenantId, format, query);
// Create download link
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `sales-data.${format}`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
} catch (error) {
const message = error instanceof Error ? error.message : 'Export failed';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
return {
salesData,
dashboardStats,
recentActivity,
isLoading,
error,
uploadProgress,
uploadSalesHistory,
validateSalesData,
getSalesData,
getDashboardStats,
getRecentActivity,
exportSalesData,
clearError: () => setError(null),
};
};

View File

@@ -0,0 +1,212 @@
// frontend/src/api/hooks/useForecast.ts
/**
* Forecasting Operations Hooks
*/
import { useState, useCallback } from 'react';
import { forecastingService } from '../services';
import type {
SingleForecastRequest,
BatchForecastRequest,
ForecastResponse,
BatchForecastResponse,
ForecastAlert,
QuickForecast,
} from '../types';
export const useForecast = () => {
const [forecasts, setForecasts] = useState<ForecastResponse[]>([]);
const [batchForecasts, setBatchForecasts] = useState<BatchForecastResponse[]>([]);
const [quickForecasts, setQuickForecasts] = useState<QuickForecast[]>([]);
const [alerts, setAlerts] = useState<ForecastAlert[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const createSingleForecast = useCallback(async (
tenantId: string,
request: SingleForecastRequest
): Promise<ForecastResponse[]> => {
try {
setIsLoading(true);
setError(null);
const newForecasts = await forecastingService.createSingleForecast(tenantId, request);
setForecasts(prev => [...newForecasts, ...prev]);
return newForecasts;
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to create forecast';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
const createBatchForecast = useCallback(async (
tenantId: string,
request: BatchForecastRequest
): Promise<BatchForecastResponse> => {
try {
setIsLoading(true);
setError(null);
const batchForecast = await forecastingService.createBatchForecast(tenantId, request);
setBatchForecasts(prev => [batchForecast, ...prev]);
return batchForecast;
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to create batch forecast';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
const getForecasts = useCallback(async (tenantId: string): Promise<ForecastResponse[]> => {
try {
setIsLoading(true);
setError(null);
const response = await forecastingService.getForecasts(tenantId);
setForecasts(response.data);
return response.data;
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to get forecasts';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
const getBatchForecastStatus = useCallback(async (
tenantId: string,
batchId: string
): Promise<BatchForecastResponse> => {
try {
const batchForecast = await forecastingService.getBatchForecastStatus(tenantId, batchId);
// Update batch forecast in state
setBatchForecasts(prev => prev.map(bf =>
bf.id === batchId ? batchForecast : bf
));
return batchForecast;
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to get batch forecast status';
setError(message);
throw error;
}
}, []);
const getQuickForecasts = useCallback(async (tenantId: string): Promise<QuickForecast[]> => {
try {
setIsLoading(true);
setError(null);
const quickForecastData = await forecastingService.getQuickForecasts(tenantId);
setQuickForecasts(quickForecastData);
return quickForecastData;
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to get quick forecasts';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
const getForecastAlerts = useCallback(async (tenantId: string): Promise<ForecastAlert[]> => {
try {
setIsLoading(true);
setError(null);
const response = await forecastingService.getForecastAlerts(tenantId);
setAlerts(response.data);
return response.data;
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to get forecast alerts';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
const acknowledgeForecastAlert = useCallback(async (
tenantId: string,
alertId: string
): Promise<void> => {
try {
setIsLoading(true);
setError(null);
const acknowledgedAlert = await forecastingService.acknowledgeForecastAlert(tenantId, alertId);
setAlerts(prev => prev.map(alert =>
alert.id === alertId ? acknowledgedAlert : alert
));
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to acknowledge alert';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
const exportForecasts = useCallback(async (
tenantId: string,
format: 'csv' | 'excel' | 'json',
params?: {
product_name?: string;
start_date?: string;
end_date?: string;
}
): Promise<void> => {
try {
setIsLoading(true);
setError(null);
const blob = await forecastingService.exportForecasts(tenantId, format, params);
// Create download link
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `forecasts.${format}`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
} catch (error) {
const message = error instanceof Error ? error.message : 'Export failed';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
return {
forecasts,
batchForecasts,
quickForecasts,
alerts,
isLoading,
error,
createSingleForecast,
createBatchForecast,
getForecasts,
getBatchForecastStatus,
getQuickForecasts,
getForecastAlerts,
acknowledgeForecastAlert,
exportForecasts,
clearError: () => setError(null),
};
};

View File

@@ -0,0 +1,151 @@
// frontend/src/api/hooks/useNotification.ts
/**
* Notification Operations Hooks
*/
import { useState, useCallback } from 'react';
import { notificationService } from '../services';
import type {
NotificationCreate,
NotificationResponse,
NotificationTemplate,
NotificationStats,
BulkNotificationRequest,
} from '../types';
export const useNotification = () => {
const [notifications, setNotifications] = useState<NotificationResponse[]>([]);
const [templates, setTemplates] = useState<NotificationTemplate[]>([]);
const [stats, setStats] = useState<NotificationStats | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const sendNotification = useCallback(async (
tenantId: string,
notification: NotificationCreate
): Promise<NotificationResponse> => {
try {
setIsLoading(true);
setError(null);
const sentNotification = await notificationService.sendNotification(tenantId, notification);
setNotifications(prev => [sentNotification, ...prev]);
return sentNotification;
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to send notification';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
const sendBulkNotifications = useCallback(async (
tenantId: string,
request: BulkNotificationRequest
): Promise<void> => {
try {
setIsLoading(true);
setError(null);
await notificationService.sendBulkNotifications(tenantId, request);
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to send bulk notifications';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
const getNotifications = useCallback(async (tenantId: string): Promise<NotificationResponse[]> => {
try {
setIsLoading(true);
setError(null);
const response = await notificationService.getNotifications(tenantId);
setNotifications(response.data);
return response.data;
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to get notifications';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
const getTemplates = useCallback(async (tenantId: string): Promise<NotificationTemplate[]> => {
try {
setIsLoading(true);
setError(null);
const response = await notificationService.getTemplates(tenantId);
setTemplates(response.data);
return response.data;
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to get templates';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
const createTemplate = useCallback(async (
tenantId: string,
template: Omit<NotificationTemplate, 'id' | 'tenant_id' | 'created_at' | 'updated_at'>
): Promise<NotificationTemplate> => {
try {
setIsLoading(true);
setError(null);
const newTemplate = await notificationService.createTemplate(tenantId, template);
setTemplates(prev => [newTemplate, ...prev]);
return newTemplate;
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to create template';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
const getNotificationStats = useCallback(async (tenantId: string): Promise<NotificationStats> => {
try {
setIsLoading(true);
setError(null);
const notificationStats = await notificationService.getNotificationStats(tenantId);
setStats(notificationStats);
return notificationStats;
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to get notification stats';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
return {
notifications,
templates,
stats,
isLoading,
error,
sendNotification,
sendBulkNotifications,
getNotifications,
getTemplates,
createTemplate,
getNotificationStats,
clearError: () => setError(null),
};
};

View File

@@ -1,71 +0,0 @@
// src/hooks/useSessionTimeout.ts
import { useEffect, useRef } from 'react';
import { useAuth } from '../../contexts/AuthContext';
interface SessionTimeoutOptions {
timeout: number; // milliseconds
onTimeout?: () => void;
warningTime?: number; // Show warning before timeout
onWarning?: () => void;
}
export const useSessionTimeout = ({
timeout = 30 * 60 * 1000, // 30 minutes default
onTimeout,
warningTime = 5 * 60 * 1000, // 5 minutes warning
onWarning
}: SessionTimeoutOptions) => {
const { logout } = useAuth();
const timeoutRef = useRef<NodeJS.Timeout>();
const warningRef = useRef<NodeJS.Timeout>();
const resetTimeout = () => {
// Clear existing timeouts
if (timeoutRef.current) clearTimeout(timeoutRef.current);
if (warningRef.current) clearTimeout(warningRef.current);
// Set warning timeout
if (warningTime && onWarning) {
warningRef.current = setTimeout(() => {
onWarning();
}, timeout - warningTime);
}
// Set session timeout
timeoutRef.current = setTimeout(() => {
if (onTimeout) {
onTimeout();
} else {
logout();
}
}, timeout);
};
useEffect(() => {
// Activity events to reset timeout
const events = ['mousedown', 'keypress', 'scroll', 'touchstart'];
const handleActivity = () => {
resetTimeout();
};
// Add event listeners
events.forEach(event => {
document.addEventListener(event, handleActivity);
});
// Start timeout
resetTimeout();
// Cleanup
return () => {
events.forEach(event => {
document.removeEventListener(event, handleActivity);
});
if (timeoutRef.current) clearTimeout(timeoutRef.current);
if (warningRef.current) clearTimeout(warningRef.current);
};
}, [timeout, warningTime]);
return { resetTimeout };
};

View File

@@ -0,0 +1,203 @@
// frontend/src/api/hooks/useTenant.ts
/**
* Tenant Management Hooks
*/
import { useState, useCallback } from 'react';
import { tenantService } from '../services';
import type {
TenantInfo,
TenantCreate,
TenantUpdate,
TenantMember,
InviteUser,
TenantStats,
} from '../types';
export const useTenant = () => {
const [tenants, setTenants] = useState<TenantInfo[]>([]);
const [currentTenant, setCurrentTenant] = useState<TenantInfo | null>(null);
const [members, setMembers] = useState<TenantMember[]>([]);
const [stats, setStats] = useState<TenantStats | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const createTenant = useCallback(async (data: TenantCreate): Promise<TenantInfo> => {
try {
setIsLoading(true);
setError(null);
const tenant = await tenantService.createTenant(data);
setTenants(prev => [...prev, tenant]);
setCurrentTenant(tenant);
return tenant;
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to create tenant';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
const getTenant = useCallback(async (tenantId: string): Promise<TenantInfo> => {
try {
setIsLoading(true);
setError(null);
const tenant = await tenantService.getTenant(tenantId);
setCurrentTenant(tenant);
return tenant;
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to get tenant';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
const updateTenant = useCallback(async (tenantId: string, data: TenantUpdate): Promise<TenantInfo> => {
try {
setIsLoading(true);
setError(null);
const updatedTenant = await tenantService.updateTenant(tenantId, data);
setCurrentTenant(updatedTenant);
setTenants(prev => prev.map(t => t.id === tenantId ? updatedTenant : t));
return updatedTenant;
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to update tenant';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
const getUserTenants = useCallback(async (): Promise<TenantInfo[]> => {
try {
setIsLoading(true);
setError(null);
const userTenants = await tenantService.getUserTenants();
setTenants(userTenants);
return userTenants;
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to get user tenants';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
const getTenantMembers = useCallback(async (tenantId: string): Promise<TenantMember[]> => {
try {
setIsLoading(true);
setError(null);
const response = await tenantService.getTenantMembers(tenantId);
setMembers(response.data);
return response.data;
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to get tenant members';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
const inviteUser = useCallback(async (tenantId: string, invitation: InviteUser): Promise<void> => {
try {
setIsLoading(true);
setError(null);
await tenantService.inviteUser(tenantId, invitation);
// Refresh members list
await getTenantMembers(tenantId);
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to invite user';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, [getTenantMembers]);
const removeMember = useCallback(async (tenantId: string, userId: string): Promise<void> => {
try {
setIsLoading(true);
setError(null);
await tenantService.removeMember(tenantId, userId);
setMembers(prev => prev.filter(m => m.user_id !== userId));
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to remove member';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
const updateMemberRole = useCallback(async (tenantId: string, userId: string, role: string): Promise<void> => {
try {
setIsLoading(true);
setError(null);
const updatedMember = await tenantService.updateMemberRole(tenantId, userId, role);
setMembers(prev => prev.map(m => m.user_id === userId ? updatedMember : m));
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to update member role';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
const getTenantStats = useCallback(async (tenantId: string): Promise<TenantStats> => {
try {
setIsLoading(true);
setError(null);
const tenantStats = await tenantService.getTenantStats(tenantId);
setStats(tenantStats);
return tenantStats;
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to get tenant stats';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
return {
tenants,
currentTenant,
members,
stats,
isLoading,
error,
createTenant,
getTenant,
updateTenant,
getUserTenants,
getTenantMembers,
inviteUser,
removeMember,
updateMemberRole,
getTenantStats,
clearError: () => setError(null),
};
};

View File

@@ -0,0 +1,226 @@
// frontend/src/api/hooks/useTraining.ts
/**
* Training Operations Hooks
*/
import { useState, useCallback, useEffect } from 'react';
import { trainingService } from '../services';
import type {
TrainingJobRequest,
TrainingJobResponse,
ModelInfo,
ModelTrainingStats,
SingleProductTrainingRequest,
} from '../types';
export const useTraining = () => {
const [jobs, setJobs] = useState<TrainingJobResponse[]>([]);
const [currentJob, setCurrentJob] = useState<TrainingJobResponse | null>(null);
const [models, setModels] = useState<ModelInfo[]>([]);
const [stats, setStats] = useState<ModelTrainingStats | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const startTrainingJob = useCallback(async (
tenantId: string,
request: TrainingJobRequest
): Promise<TrainingJobResponse> => {
try {
setIsLoading(true);
setError(null);
const job = await trainingService.startTrainingJob(tenantId, request);
setCurrentJob(job);
setJobs(prev => [job, ...prev]);
return job;
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to start training job';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
const startSingleProductTraining = useCallback(async (
tenantId: string,
request: SingleProductTrainingRequest
): Promise<TrainingJobResponse> => {
try {
setIsLoading(true);
setError(null);
const job = await trainingService.startSingleProductTraining(tenantId, request);
setCurrentJob(job);
setJobs(prev => [job, ...prev]);
return job;
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to start product training';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
const getTrainingJobStatus = useCallback(async (
tenantId: string,
jobId: string
): Promise<TrainingJobResponse> => {
try {
const job = await trainingService.getTrainingJobStatus(tenantId, jobId);
// Update job in state
setJobs(prev => prev.map(j => j.job_id === jobId ? job : j));
if (currentJob?.job_id === jobId) {
setCurrentJob(job);
}
return job;
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to get job status';
setError(message);
throw error;
}
}, [currentJob]);
const cancelTrainingJob = useCallback(async (
tenantId: string,
jobId: string
): Promise<void> => {
try {
setIsLoading(true);
setError(null);
await trainingService.cancelTrainingJob(tenantId, jobId);
// Update job status in state
setJobs(prev => prev.map(j =>
j.job_id === jobId ? { ...j, status: 'cancelled' } : j
));
if (currentJob?.job_id === jobId) {
setCurrentJob({ ...currentJob, status: 'cancelled' });
}
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to cancel job';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, [currentJob]);
const getTrainingJobs = useCallback(async (tenantId: string): Promise<TrainingJobResponse[]> => {
try {
setIsLoading(true);
setError(null);
const response = await trainingService.getTrainingJobs(tenantId);
setJobs(response.data);
return response.data;
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to get training jobs';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
const getModels = useCallback(async (tenantId: string): Promise<ModelInfo[]> => {
try {
setIsLoading(true);
setError(null);
const response = await trainingService.getModels(tenantId);
setModels(response.data);
return response.data;
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to get models';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
const validateTrainingData = useCallback(async (tenantId: string): Promise<{
is_valid: boolean;
message: string;
details?: any;
}> => {
try {
setIsLoading(true);
setError(null);
const result = await trainingService.validateTrainingData(tenantId);
return result;
} catch (error) {
const message = error instanceof Error ? error.message : 'Data validation failed';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
const getTrainingStats = useCallback(async (tenantId: string): Promise<ModelTrainingStats> => {
try {
setIsLoading(true);
setError(null);
const trainingStats = await trainingService.getTrainingStats(tenantId);
setStats(trainingStats);
return trainingStats;
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to get training stats';
setError(message);
throw error;
} finally {
setIsLoading(false);
}
}, []);
// Auto-refresh job status for running jobs
useEffect(() => {
const runningJobs = jobs.filter(job => job.status === 'running' || job.status === 'pending');
if (runningJobs.length === 0) return;
const interval = setInterval(async () => {
for (const job of runningJobs) {
try {
const tenantId = job.tenant_id;
await getTrainingJobStatus(tenantId, job.job_id);
} catch (error) {
console.error('Failed to refresh job status:', error);
}
}
}, 5000); // Refresh every 5 seconds
return () => clearInterval(interval);
}, [jobs, getTrainingJobStatus]);
return {
jobs,
currentJob,
models,
stats,
isLoading,
error,
startTrainingJob,
startSingleProductTraining,
getTrainingJobStatus,
cancelTrainingJob,
getTrainingJobs,
getModels,
validateTrainingData,
getTrainingStats,
clearError: () => setError(null),
};
};

View File

@@ -1,83 +0,0 @@
// src/hooks/useTrainingProgress.ts
import { useState, useEffect } from 'react';
import { useWebSocket } from '../hooks/useWebSocket';
export interface TrainingProgress {
job_id: string;
status: 'pending' | 'running' | 'completed' | 'failed';
progress: number;
current_step: string;
total_steps: number;
estimated_time_remaining?: number;
metrics?: Record<string, any>;
}
export interface TrainingProgressUpdate {
type: 'training_progress' | 'training_completed' | 'training_error';
job_id: string;
progress?: TrainingProgress;
results?: any;
error?: string;
}
export const useTrainingProgress = (jobId: string | null) => {
const [progress, setProgress] = useState<TrainingProgress | null>(null);
const [error, setError] = useState<string | null>(null);
const [isComplete, setIsComplete] = useState(false);
const handleMessage = (data: TrainingProgressUpdate) => {
switch (data.type) {
case 'training_progress':
setProgress(data.progress!);
setError(null);
break;
case 'training_completed':
setProgress(prev => ({
...prev!,
status: 'completed',
progress: 100
}));
setIsComplete(true);
break;
case 'training_error':
setError(data.error || 'Training failed');
setProgress(prev => prev ? { ...prev, status: 'failed' } : null);
break;
}
};
const { isConnected } = useWebSocket({
endpoint: jobId ? `/api/v1/training/progress/${jobId}` : '',
onMessage: handleMessage,
onError: () => setError('Connection lost'),
autoConnect: !!jobId
});
// Fetch initial status when job ID changes
useEffect(() => {
if (jobId) {
fetchTrainingStatus(jobId);
}
}, [jobId]);
const fetchTrainingStatus = async (id: string) => {
try {
const response = await fetch(`/api/v1/training/status/${id}`);
if (response.ok) {
const data = await response.json();
setProgress(data);
}
} catch (err) {
console.error('Failed to fetch training status:', err);
}
};
return {
progress,
error,
isComplete,
isConnected
};
};

View File

@@ -1,72 +0,0 @@
// src/hooks/useWebSocket.ts
import { useEffect, useRef, useCallback } from 'react';
import { wsManager, WebSocketHandlers } from '../websocket/WebSocketManager';
export interface UseWebSocketOptions {
endpoint: string;
onMessage: (data: any) => void;
onError?: (error: Event) => void;
onConnect?: () => void;
onDisconnect?: () => void;
onReconnect?: () => void;
autoConnect?: boolean;
}
export const useWebSocket = ({
endpoint,
onMessage,
onError,
onConnect,
onDisconnect,
onReconnect,
autoConnect = true
}: UseWebSocketOptions) => {
const wsRef = useRef<WebSocket | null>(null);
const connect = useCallback(async () => {
if (wsRef.current) return;
const handlers: WebSocketHandlers = {
onOpen: onConnect,
onMessage,
onError,
onClose: onDisconnect,
onReconnect
};
try {
wsRef.current = await wsManager.connect(endpoint, handlers);
} catch (error) {
console.error('WebSocket connection failed:', error);
onError?.(new Event('Connection failed'));
}
}, [endpoint, onMessage, onError, onConnect, onDisconnect, onReconnect]);
const disconnect = useCallback(() => {
if (wsRef.current) {
wsManager.disconnect(endpoint);
wsRef.current = null;
}
}, [endpoint]);
const send = useCallback((data: any) => {
wsManager.send(endpoint, data);
}, [endpoint]);
useEffect(() => {
if (autoConnect) {
connect();
}
return () => {
disconnect();
};
}, [autoConnect, connect, disconnect]);
return {
connect,
disconnect,
send,
isConnected: wsManager.isConnected(endpoint)
};
};