Add new Frontend API folder
This commit is contained in:
30
frontend/src/api/hooks/index.ts
Normal file
30
frontend/src/api/hooks/index.ts
Normal 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,
|
||||
};
|
||||
};
|
||||
193
frontend/src/api/hooks/useAuth.ts
Normal file
193
frontend/src/api/hooks/useAuth.ts
Normal 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 };
|
||||
};
|
||||
172
frontend/src/api/hooks/useData.ts
Normal file
172
frontend/src/api/hooks/useData.ts
Normal 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),
|
||||
};
|
||||
};
|
||||
212
frontend/src/api/hooks/useForecast.ts
Normal file
212
frontend/src/api/hooks/useForecast.ts
Normal 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),
|
||||
};
|
||||
};
|
||||
151
frontend/src/api/hooks/useNotification.ts
Normal file
151
frontend/src/api/hooks/useNotification.ts
Normal 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),
|
||||
};
|
||||
};
|
||||
@@ -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 };
|
||||
};
|
||||
203
frontend/src/api/hooks/useTenant.ts
Normal file
203
frontend/src/api/hooks/useTenant.ts
Normal 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),
|
||||
};
|
||||
};
|
||||
226
frontend/src/api/hooks/useTraining.ts
Normal file
226
frontend/src/api/hooks/useTraining.ts
Normal 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),
|
||||
};
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
};
|
||||
@@ -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)
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user