Add new frontend - fix 9
This commit is contained in:
@@ -1,49 +0,0 @@
|
||||
// src/api/services/api.ts
|
||||
import { apiClient } from '../base/apiClient';
|
||||
import {
|
||||
ApiResponse,
|
||||
LoginRequest,
|
||||
RegisterRequest,
|
||||
TokenResponse,
|
||||
UserProfile,
|
||||
TenantInfo,
|
||||
SalesRecord,
|
||||
TrainingRequest,
|
||||
TrainedModel,
|
||||
ForecastRecord,
|
||||
ForecastRequest,
|
||||
WeatherData,
|
||||
TrafficData,
|
||||
NotificationSettings,
|
||||
// ... other types from your api.ts file
|
||||
} from '../types/api'; // This should point to your main types file (api.ts)
|
||||
|
||||
// Assuming your api.ts defines these interfaces:
|
||||
// interface DashboardStats { ... }
|
||||
// interface ApiResponse<T> { ... }
|
||||
|
||||
|
||||
// Define DashboardStats interface here or ensure it's imported from your main types file
|
||||
export interface DashboardStats {
|
||||
totalSales: number;
|
||||
totalRevenue: number;
|
||||
lastTrainingDate: string | null;
|
||||
forecastAccuracy: number; // e.g., MAPE or RMSE
|
||||
}
|
||||
|
||||
|
||||
export const dataApi = {
|
||||
uploadSalesHistory: (file: File, additionalData?: Record<string, any>) =>
|
||||
apiClient.upload<ApiResponse<any>>('/data/upload-sales', file, additionalData),
|
||||
getDashboardStats: () =>
|
||||
apiClient.get<ApiResponse<DashboardStats>>('/dashboard/stats'),
|
||||
};
|
||||
|
||||
export const forecastingApi = {
|
||||
getForecast: (params: ForecastRequest) =>
|
||||
apiClient.get<ApiResponse<ForecastRecord[]>>('/forecast', { params }),
|
||||
};
|
||||
|
||||
|
||||
// Re-export all types from the original api.ts file
|
||||
export * from '../types/api'
|
||||
@@ -1,116 +1,126 @@
|
||||
// frontend/src/api/services/authService.ts - UPDATED TO HANDLE TOKENS FROM REGISTRATION
|
||||
import { tokenManager } from '../auth/tokenManager';
|
||||
// src/api/services/AuthService.ts
|
||||
import { apiClient } from '../base/apiClient';
|
||||
import {
|
||||
ApiResponse,
|
||||
LoginRequest,
|
||||
RegisterRequest,
|
||||
TokenResponse,
|
||||
UserProfile,
|
||||
} from '../types/api';
|
||||
|
||||
export interface LoginCredentials {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface RegisterData {
|
||||
email: string;
|
||||
password: string;
|
||||
full_name: string;
|
||||
tenant_name?: string;
|
||||
}
|
||||
|
||||
export interface UserProfile {
|
||||
id: string;
|
||||
email: string;
|
||||
full_name: string;
|
||||
tenant_id?: string;
|
||||
role?: string;
|
||||
is_active: boolean;
|
||||
is_verified?: boolean;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface TokenResponse {
|
||||
access_token: string;
|
||||
refresh_token?: string;
|
||||
token_type: string;
|
||||
expires_in?: number;
|
||||
user?: UserProfile;
|
||||
}
|
||||
|
||||
class AuthService {
|
||||
async register(data: RegisterData): Promise<UserProfile> {
|
||||
// NEW: Registration now returns tokens directly - no auto-login needed!
|
||||
const response: TokenResponse = await apiClient.post('/api/v1/auth/register', data);
|
||||
|
||||
// Store tokens immediately from registration response
|
||||
await tokenManager.storeTokens(response);
|
||||
|
||||
// Return user profile from registration response
|
||||
if (response.user) {
|
||||
return response.user;
|
||||
} else {
|
||||
// Fallback: get user profile if not included in response
|
||||
return this.getCurrentUser();
|
||||
}
|
||||
export class AuthService {
|
||||
/**
|
||||
* User login
|
||||
*/
|
||||
async login(credentials: LoginRequest): Promise<TokenResponse> {
|
||||
const response = await apiClient.post<ApiResponse<TokenResponse>>(
|
||||
'/auth/login',
|
||||
credentials
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
async login(credentials: LoginCredentials): Promise<UserProfile> {
|
||||
// UPDATED: Use correct endpoint and unified response handling
|
||||
const response: TokenResponse = await apiClient.post('/api/v1/auth/login', credentials);
|
||||
|
||||
// Store tokens from login response
|
||||
await tokenManager.storeTokens(response);
|
||||
|
||||
// Return user profile from login response
|
||||
if (response.user) {
|
||||
return response.user;
|
||||
} else {
|
||||
// Fallback: get user profile if not included in response
|
||||
return this.getCurrentUser();
|
||||
}
|
||||
/**
|
||||
* User registration
|
||||
*/
|
||||
async register(userData: RegisterRequest): Promise<UserProfile> {
|
||||
const response = await apiClient.post<ApiResponse<UserProfile>>(
|
||||
'/auth/register',
|
||||
userData
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
async logout(): Promise<void> {
|
||||
try {
|
||||
// Get refresh token for logout request
|
||||
const refreshToken = tokenManager.getRefreshToken();
|
||||
if (refreshToken) {
|
||||
await apiClient.post('/api/v1/auth/logout', {
|
||||
refresh_token: refreshToken
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Logout API call failed:', error);
|
||||
// Continue with local cleanup even if API fails
|
||||
} finally {
|
||||
tokenManager.clearTokens();
|
||||
window.location.href = '/login';
|
||||
}
|
||||
/**
|
||||
* Refresh access token
|
||||
*/
|
||||
async refreshToken(refreshToken: string): Promise<TokenResponse> {
|
||||
const response = await apiClient.post<ApiResponse<TokenResponse>>(
|
||||
'/auth/refresh',
|
||||
{ refresh_token: refreshToken }
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
async getCurrentUser(): Promise<UserProfile> {
|
||||
return apiClient.get('/api/v1/auth/me');
|
||||
/**
|
||||
* Get current user profile
|
||||
*/
|
||||
async getProfile(): Promise<UserProfile> {
|
||||
const response = await apiClient.get<ApiResponse<UserProfile>>('/users/me');
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user profile
|
||||
*/
|
||||
async updateProfile(updates: Partial<UserProfile>): Promise<UserProfile> {
|
||||
return apiClient.patch('/api/v1/auth/profile', updates);
|
||||
const response = await apiClient.put<ApiResponse<UserProfile>>(
|
||||
'/users/me',
|
||||
updates
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
async changePassword(currentPassword: string, newPassword: string): Promise<void> {
|
||||
await apiClient.post('/api/v1/auth/change-password', {
|
||||
/**
|
||||
* Change password
|
||||
*/
|
||||
async changePassword(
|
||||
currentPassword: string,
|
||||
newPassword: string
|
||||
): Promise<void> {
|
||||
await apiClient.post('/auth/change-password', {
|
||||
current_password: currentPassword,
|
||||
new_password: newPassword
|
||||
new_password: newPassword,
|
||||
});
|
||||
}
|
||||
|
||||
async refreshToken(): Promise<void> {
|
||||
await tokenManager.refreshAccessToken();
|
||||
/**
|
||||
* Request password reset
|
||||
*/
|
||||
async requestPasswordReset(email: string): Promise<void> {
|
||||
await apiClient.post('/auth/reset-password', { email });
|
||||
}
|
||||
|
||||
isAuthenticated(): boolean {
|
||||
return tokenManager.isAuthenticated();
|
||||
/**
|
||||
* Confirm password reset
|
||||
*/
|
||||
async confirmPasswordReset(
|
||||
token: string,
|
||||
newPassword: string
|
||||
): Promise<void> {
|
||||
await apiClient.post('/auth/confirm-reset', {
|
||||
token,
|
||||
new_password: newPassword,
|
||||
});
|
||||
}
|
||||
|
||||
getUser(): UserProfile | null {
|
||||
// This method would need to be implemented to return cached user data
|
||||
// For now, it returns null and components should use getCurrentUser()
|
||||
return null;
|
||||
/**
|
||||
* Verify email
|
||||
*/
|
||||
async verifyEmail(token: string): Promise<void> {
|
||||
await apiClient.post('/auth/verify-email', { token });
|
||||
}
|
||||
|
||||
/**
|
||||
* Resend verification email
|
||||
*/
|
||||
async resendVerification(): Promise<void> {
|
||||
await apiClient.post('/auth/resend-verification');
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout (invalidate tokens)
|
||||
*/
|
||||
async logout(): Promise<void> {
|
||||
await apiClient.post('/auth/logout');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user permissions
|
||||
*/
|
||||
async getPermissions(): Promise<string[]> {
|
||||
const response = await apiClient.get<ApiResponse<string[]>>('/auth/permissions');
|
||||
return response.data!;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
208
frontend/src/api/services/dataService.ts
Normal file
208
frontend/src/api/services/dataService.ts
Normal file
@@ -0,0 +1,208 @@
|
||||
// src/api/services/DataService.ts
|
||||
import { apiClient } from '../base/apiClient';
|
||||
import {
|
||||
ApiResponse,
|
||||
SalesRecord,
|
||||
CreateSalesRequest,
|
||||
WeatherData,
|
||||
TrafficData,
|
||||
} from '../types/api';
|
||||
|
||||
export interface DashboardStats {
|
||||
totalSales: number;
|
||||
totalRevenue: number;
|
||||
lastTrainingDate: string | null;
|
||||
forecastAccuracy: number;
|
||||
totalProducts: number;
|
||||
activeTenants: number;
|
||||
lastDataUpdate: string;
|
||||
}
|
||||
|
||||
export interface UploadResponse {
|
||||
message: string;
|
||||
records_processed: number;
|
||||
errors?: string[];
|
||||
upload_id?: string;
|
||||
}
|
||||
|
||||
export interface DataValidation {
|
||||
valid: boolean;
|
||||
errors: string[];
|
||||
warnings: string[];
|
||||
recordCount: number;
|
||||
duplicates: number;
|
||||
}
|
||||
|
||||
export class DataService {
|
||||
/**
|
||||
* Upload sales history file
|
||||
*/
|
||||
async uploadSalesHistory(
|
||||
file: File,
|
||||
additionalData?: Record<string, any>
|
||||
): Promise<UploadResponse> {
|
||||
const response = await apiClient.upload<ApiResponse<UploadResponse>>(
|
||||
'/data/upload-sales',
|
||||
file,
|
||||
additionalData
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate sales data before upload
|
||||
*/
|
||||
async validateSalesData(file: File): Promise<DataValidation> {
|
||||
const response = await apiClient.upload<ApiResponse<DataValidation>>(
|
||||
'/data/validate-sales',
|
||||
file
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get dashboard statistics
|
||||
*/
|
||||
async getDashboardStats(): Promise<DashboardStats> {
|
||||
const response = await apiClient.get<ApiResponse<DashboardStats>>(
|
||||
'/data/dashboard/stats'
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sales records
|
||||
*/
|
||||
async getSalesRecords(params?: {
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
productName?: string;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}): Promise<{ records: SalesRecord[]; total: number; page: number; pages: number }> {
|
||||
const response = await apiClient.get<ApiResponse<{
|
||||
records: SalesRecord[];
|
||||
total: number;
|
||||
page: number;
|
||||
pages: number;
|
||||
}>>('/data/sales', { params });
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create single sales record
|
||||
*/
|
||||
async createSalesRecord(record: CreateSalesRequest): Promise<SalesRecord> {
|
||||
const response = await apiClient.post<ApiResponse<SalesRecord>>(
|
||||
'/data/sales',
|
||||
record
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update sales record
|
||||
*/
|
||||
async updateSalesRecord(
|
||||
id: string,
|
||||
updates: Partial<CreateSalesRequest>
|
||||
): Promise<SalesRecord> {
|
||||
const response = await apiClient.put<ApiResponse<SalesRecord>>(
|
||||
`/data/sales/${id}`,
|
||||
updates
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete sales record
|
||||
*/
|
||||
async deleteSalesRecord(id: string): Promise<void> {
|
||||
await apiClient.delete(`/data/sales/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get weather data
|
||||
*/
|
||||
async getWeatherData(params?: {
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
location?: string;
|
||||
}): Promise<WeatherData[]> {
|
||||
const response = await apiClient.get<ApiResponse<WeatherData[]>>(
|
||||
'/data/weather',
|
||||
{ params }
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get traffic data
|
||||
*/
|
||||
async getTrafficData(params?: {
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
location?: string;
|
||||
}): Promise<TrafficData[]> {
|
||||
const response = await apiClient.get<ApiResponse<TrafficData[]>>(
|
||||
'/data/traffic',
|
||||
{ params }
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data quality report
|
||||
*/
|
||||
async getDataQuality(): Promise<{
|
||||
salesData: { completeness: number; quality: number; lastUpdate: string };
|
||||
weatherData: { completeness: number; quality: number; lastUpdate: string };
|
||||
trafficData: { completeness: number; quality: number; lastUpdate: string };
|
||||
}> {
|
||||
const response = await apiClient.get<ApiResponse<any>>('/data/quality');
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export sales data
|
||||
*/
|
||||
async exportSalesData(params?: {
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
format?: 'csv' | 'excel';
|
||||
}): Promise<Blob> {
|
||||
const response = await apiClient.get('/data/sales/export', {
|
||||
params,
|
||||
responseType: 'blob',
|
||||
});
|
||||
return response as unknown as Blob;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get product list
|
||||
*/
|
||||
async getProducts(): Promise<string[]> {
|
||||
const response = await apiClient.get<ApiResponse<string[]>>('/data/products');
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data sync status
|
||||
*/
|
||||
async getSyncStatus(): Promise<{
|
||||
weather: { lastSync: string; status: 'ok' | 'error'; nextSync: string };
|
||||
traffic: { lastSync: string; status: 'ok' | 'error'; nextSync: string };
|
||||
}> {
|
||||
const response = await apiClient.get<ApiResponse<any>>('/data/sync/status');
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger manual data sync
|
||||
*/
|
||||
async triggerSync(dataType: 'weather' | 'traffic' | 'all'): Promise<void> {
|
||||
await apiClient.post('/data/sync/trigger', { data_type: dataType });
|
||||
}
|
||||
}
|
||||
|
||||
export const dataService = new DataService();
|
||||
296
frontend/src/api/services/forecastingService.ts
Normal file
296
frontend/src/api/services/forecastingService.ts
Normal file
@@ -0,0 +1,296 @@
|
||||
// src/api/services/ForecastingService.ts
|
||||
import { apiClient } from '../base/apiClient';
|
||||
import {
|
||||
ApiResponse,
|
||||
ForecastRecord,
|
||||
ForecastRequest,
|
||||
} from '../types/api';
|
||||
|
||||
export interface SingleForecastRequest {
|
||||
product_name: string;
|
||||
forecast_date: string;
|
||||
include_weather?: boolean;
|
||||
include_traffic?: boolean;
|
||||
confidence_level?: number;
|
||||
}
|
||||
|
||||
export interface BatchForecastRequest {
|
||||
products: string[];
|
||||
start_date: string;
|
||||
end_date: string;
|
||||
include_weather?: boolean;
|
||||
include_traffic?: boolean;
|
||||
confidence_level?: number;
|
||||
batch_name?: string;
|
||||
}
|
||||
|
||||
export interface ForecastAlert {
|
||||
id: string;
|
||||
forecast_id: string;
|
||||
alert_type: 'high_demand' | 'low_demand' | 'anomaly' | 'model_drift';
|
||||
severity: 'low' | 'medium' | 'high';
|
||||
message: string;
|
||||
threshold_value?: number;
|
||||
actual_value?: number;
|
||||
is_active: boolean;
|
||||
created_at: string;
|
||||
acknowledged_at?: string;
|
||||
notification_sent: boolean;
|
||||
}
|
||||
|
||||
export interface QuickForecast {
|
||||
product_name: string;
|
||||
forecasts: {
|
||||
date: string;
|
||||
predicted_quantity: number;
|
||||
confidence_lower: number;
|
||||
confidence_upper: number;
|
||||
}[];
|
||||
model_info: {
|
||||
model_id: string;
|
||||
algorithm: string;
|
||||
accuracy: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface BatchForecastStatus {
|
||||
id: string;
|
||||
batch_name: string;
|
||||
status: 'queued' | 'running' | 'completed' | 'failed';
|
||||
total_products: number;
|
||||
completed_products: number;
|
||||
failed_products: number;
|
||||
progress: number;
|
||||
created_at: string;
|
||||
completed_at?: string;
|
||||
error_message?: string;
|
||||
}
|
||||
|
||||
export class ForecastingService {
|
||||
/**
|
||||
* Generate single forecast
|
||||
*/
|
||||
async createSingleForecast(request: SingleForecastRequest): Promise<ForecastRecord> {
|
||||
const response = await apiClient.post<ApiResponse<ForecastRecord>>(
|
||||
'/forecasting/single',
|
||||
request
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate batch forecasts
|
||||
*/
|
||||
async createBatchForecast(request: BatchForecastRequest): Promise<BatchForecastStatus> {
|
||||
const response = await apiClient.post<ApiResponse<BatchForecastStatus>>(
|
||||
'/forecasting/batch',
|
||||
request
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get forecast records
|
||||
*/
|
||||
async getForecasts(params?: {
|
||||
productName?: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}): Promise<{
|
||||
forecasts: ForecastRecord[];
|
||||
total: number;
|
||||
page: number;
|
||||
pages: number;
|
||||
}> {
|
||||
const response = await apiClient.get<ApiResponse<any>>('/forecasting/list', { params });
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get specific forecast
|
||||
*/
|
||||
async getForecast(forecastId: string): Promise<ForecastRecord> {
|
||||
const response = await apiClient.get<ApiResponse<ForecastRecord>>(
|
||||
`/forecasting/forecasts/${forecastId}`
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get forecast alerts
|
||||
*/
|
||||
async getForecastAlerts(params?: {
|
||||
active?: boolean;
|
||||
severity?: string;
|
||||
alertType?: string;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}): Promise<{
|
||||
alerts: ForecastAlert[];
|
||||
total: number;
|
||||
page: number;
|
||||
pages: number;
|
||||
}> {
|
||||
const response = await apiClient.get<ApiResponse<any>>('/forecasting/alerts', { params });
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Acknowledge alert
|
||||
*/
|
||||
async acknowledgeAlert(alertId: string): Promise<ForecastAlert> {
|
||||
const response = await apiClient.put<ApiResponse<ForecastAlert>>(
|
||||
`/forecasting/alerts/${alertId}/acknowledge`
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get quick forecast for product (next 7 days)
|
||||
*/
|
||||
async getQuickForecast(productName: string, days: number = 7): Promise<QuickForecast> {
|
||||
const response = await apiClient.get<ApiResponse<QuickForecast>>(
|
||||
`/forecasting/quick/${productName}`,
|
||||
{ params: { days } }
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get real-time prediction
|
||||
*/
|
||||
async getRealtimePrediction(
|
||||
productName: string,
|
||||
date: string,
|
||||
includeWeather: boolean = true,
|
||||
includeTraffic: boolean = true
|
||||
): Promise<{
|
||||
product_name: string;
|
||||
forecast_date: string;
|
||||
predicted_quantity: number;
|
||||
confidence_lower: number;
|
||||
confidence_upper: number;
|
||||
external_factors: {
|
||||
weather?: any;
|
||||
traffic?: any;
|
||||
holidays?: any;
|
||||
};
|
||||
processing_time_ms: number;
|
||||
}> {
|
||||
const response = await apiClient.post<ApiResponse<any>>(
|
||||
'/forecasting/realtime',
|
||||
{
|
||||
product_name: productName,
|
||||
forecast_date: date,
|
||||
include_weather: includeWeather,
|
||||
include_traffic: includeTraffic,
|
||||
}
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get batch forecast status
|
||||
*/
|
||||
async getBatchStatus(batchId: string): Promise<BatchForecastStatus> {
|
||||
const response = await apiClient.get<ApiResponse<BatchForecastStatus>>(
|
||||
`/forecasting/batch/${batchId}/status`
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel batch forecast
|
||||
*/
|
||||
async cancelBatchForecast(batchId: string): Promise<void> {
|
||||
await apiClient.post(`/forecasting/batch/${batchId}/cancel`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get forecasting statistics
|
||||
*/
|
||||
async getForecastingStats(): Promise<{
|
||||
total_forecasts: number;
|
||||
accuracy_avg: number;
|
||||
active_alerts: number;
|
||||
forecasts_today: number;
|
||||
products_forecasted: number;
|
||||
last_forecast_date: string | null;
|
||||
}> {
|
||||
const response = await apiClient.get<ApiResponse<any>>('/forecasting/stats');
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare forecast vs actual
|
||||
*/
|
||||
async compareForecastActual(params?: {
|
||||
productName?: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
}): Promise<{
|
||||
comparisons: {
|
||||
date: string;
|
||||
product_name: string;
|
||||
predicted: number;
|
||||
actual: number;
|
||||
error: number;
|
||||
percentage_error: number;
|
||||
}[];
|
||||
summary: {
|
||||
mape: number;
|
||||
rmse: number;
|
||||
mae: number;
|
||||
accuracy: number;
|
||||
};
|
||||
}> {
|
||||
const response = await apiClient.get<ApiResponse<any>>('/forecasting/compare', { params });
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export forecasts
|
||||
*/
|
||||
async exportForecasts(params?: {
|
||||
productName?: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
format?: 'csv' | 'excel';
|
||||
}): Promise<Blob> {
|
||||
const response = await apiClient.get('/forecasting/export', {
|
||||
params,
|
||||
responseType: 'blob',
|
||||
});
|
||||
return response as unknown as Blob;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get business insights
|
||||
*/
|
||||
async getBusinessInsights(params?: {
|
||||
period?: 'week' | 'month' | 'quarter';
|
||||
products?: string[];
|
||||
}): Promise<{
|
||||
insights: {
|
||||
type: 'trend' | 'seasonality' | 'anomaly' | 'opportunity';
|
||||
title: string;
|
||||
description: string;
|
||||
confidence: number;
|
||||
impact: 'low' | 'medium' | 'high';
|
||||
products_affected: string[];
|
||||
}[];
|
||||
recommendations: {
|
||||
title: string;
|
||||
description: string;
|
||||
priority: number;
|
||||
estimated_impact: string;
|
||||
}[];
|
||||
}> {
|
||||
const response = await apiClient.get<ApiResponse<any>>('/forecasting/insights', { params });
|
||||
return response.data!;
|
||||
}
|
||||
}
|
||||
|
||||
export const forecastingService = new ForecastingService();
|
||||
275
frontend/src/api/services/index.ts
Normal file
275
frontend/src/api/services/index.ts
Normal file
@@ -0,0 +1,275 @@
|
||||
// src/api/services/index.ts
|
||||
/**
|
||||
* Main API Services Index
|
||||
* Central import point for all service modules
|
||||
*/
|
||||
|
||||
// Import all service classes
|
||||
export { AuthService, authService } from './AuthService';
|
||||
export { DataService, dataService } from './DataService';
|
||||
export { TrainingService, trainingService } from './TrainingService';
|
||||
export { ForecastingService, forecastingService } from './ForecastingService';
|
||||
export { NotificationService, notificationService } from './NotificationService';
|
||||
export { TenantService, tenantService } from './TenantService';
|
||||
|
||||
// Import base API client for custom implementations
|
||||
export { apiClient } from '../base/apiClient';
|
||||
|
||||
// Re-export all types from the main types file
|
||||
export * from '../types/api';
|
||||
|
||||
// Export additional service-specific types
|
||||
export type {
|
||||
DashboardStats,
|
||||
UploadResponse,
|
||||
DataValidation,
|
||||
} from './DataService';
|
||||
|
||||
export type {
|
||||
TrainingJobProgress,
|
||||
ModelMetrics,
|
||||
TrainingConfiguration,
|
||||
} from './TrainingService';
|
||||
|
||||
export type {
|
||||
SingleForecastRequest,
|
||||
BatchForecastRequest,
|
||||
ForecastAlert,
|
||||
QuickForecast,
|
||||
BatchForecastStatus,
|
||||
} from './ForecastingService';
|
||||
|
||||
export type {
|
||||
NotificationCreate,
|
||||
NotificationResponse,
|
||||
NotificationHistory,
|
||||
NotificationTemplate,
|
||||
NotificationStats,
|
||||
BulkNotificationRequest,
|
||||
BulkNotificationStatus,
|
||||
} from './NotificationService';
|
||||
|
||||
export type {
|
||||
TenantCreate,
|
||||
TenantUpdate,
|
||||
TenantSettings,
|
||||
TenantStats,
|
||||
TenantUser,
|
||||
InviteUser,
|
||||
} from './TenantService';
|
||||
|
||||
// Create a unified API object for convenience
|
||||
export const api = {
|
||||
auth: authService,
|
||||
data: dataService,
|
||||
training: trainingService,
|
||||
forecasting: forecastingService,
|
||||
notifications: notificationService,
|
||||
tenant: tenantService,
|
||||
} as const;
|
||||
|
||||
// Type for the unified API object
|
||||
export type ApiServices = typeof api;
|
||||
|
||||
// Service status type for monitoring
|
||||
export interface ServiceStatus {
|
||||
service: string;
|
||||
status: 'healthy' | 'degraded' | 'down';
|
||||
lastChecked: Date;
|
||||
responseTime?: number;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
// Health check utilities
|
||||
export class ApiHealthChecker {
|
||||
private static healthCheckEndpoints = {
|
||||
auth: '/auth/health',
|
||||
data: '/data/health',
|
||||
training: '/training/health',
|
||||
forecasting: '/forecasting/health',
|
||||
notifications: '/notifications/health',
|
||||
tenant: '/tenants/health',
|
||||
};
|
||||
|
||||
/**
|
||||
* Check health of all services
|
||||
*/
|
||||
static async checkAllServices(): Promise<Record<string, ServiceStatus>> {
|
||||
const results: Record<string, ServiceStatus> = {};
|
||||
|
||||
for (const [serviceName, endpoint] of Object.entries(this.healthCheckEndpoints)) {
|
||||
results[serviceName] = await this.checkService(serviceName, endpoint);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check health of a specific service
|
||||
*/
|
||||
static async checkService(serviceName: string, endpoint: string): Promise<ServiceStatus> {
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
const response = await apiClient.get(endpoint, { timeout: 5000 });
|
||||
const responseTime = Date.now() - startTime;
|
||||
|
||||
return {
|
||||
service: serviceName,
|
||||
status: response.status === 200 ? 'healthy' : 'degraded',
|
||||
lastChecked: new Date(),
|
||||
responseTime,
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
service: serviceName,
|
||||
status: 'down',
|
||||
lastChecked: new Date(),
|
||||
responseTime: Date.now() - startTime,
|
||||
error: error.message || 'Unknown error',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if core services are available
|
||||
*/
|
||||
static async checkCoreServices(): Promise<boolean> {
|
||||
const coreServices = ['auth', 'data', 'forecasting'];
|
||||
const results = await this.checkAllServices();
|
||||
|
||||
return coreServices.every(
|
||||
service => results[service]?.status === 'healthy'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Error handling utilities
|
||||
export class ApiErrorHandler {
|
||||
/**
|
||||
* Handle common API errors
|
||||
*/
|
||||
static handleError(error: any): never {
|
||||
if (error.response) {
|
||||
// Server responded with error status
|
||||
const { status, data } = error.response;
|
||||
|
||||
switch (status) {
|
||||
case 401:
|
||||
throw new Error('Authentication required. Please log in again.');
|
||||
case 403:
|
||||
throw new Error('You do not have permission to perform this action.');
|
||||
case 404:
|
||||
throw new Error('The requested resource was not found.');
|
||||
case 429:
|
||||
throw new Error('Too many requests. Please try again later.');
|
||||
case 500:
|
||||
throw new Error('Server error. Please try again later.');
|
||||
default:
|
||||
throw new Error(data?.message || `Request failed with status ${status}`);
|
||||
}
|
||||
} else if (error.request) {
|
||||
// Network error
|
||||
throw new Error('Network error. Please check your connection.');
|
||||
} else {
|
||||
// Other error
|
||||
throw new Error(error.message || 'An unexpected error occurred.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry failed requests with exponential backoff
|
||||
*/
|
||||
static async retryRequest<T>(
|
||||
requestFn: () => Promise<T>,
|
||||
maxRetries: number = 3,
|
||||
baseDelay: number = 1000
|
||||
): Promise<T> {
|
||||
let lastError: any;
|
||||
|
||||
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
return await requestFn();
|
||||
} catch (error: any) {
|
||||
lastError = error;
|
||||
|
||||
// Don't retry on certain errors
|
||||
if (error.response?.status === 401 || error.response?.status === 403) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Don't retry on last attempt
|
||||
if (attempt === maxRetries) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Wait before retrying with exponential backoff
|
||||
const delay = baseDelay * Math.pow(2, attempt);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError;
|
||||
}
|
||||
}
|
||||
|
||||
// Request cache utilities for performance optimization
|
||||
export class ApiCache {
|
||||
private static cache = new Map<string, { data: any; expires: number }>();
|
||||
|
||||
/**
|
||||
* Get cached response
|
||||
*/
|
||||
static get<T>(key: string): T | null {
|
||||
const cached = this.cache.get(key);
|
||||
|
||||
if (cached && cached.expires > Date.now()) {
|
||||
return cached.data;
|
||||
}
|
||||
|
||||
// Remove expired cache entry
|
||||
if (cached) {
|
||||
this.cache.delete(key);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set cached response
|
||||
*/
|
||||
static set(key: string, data: any, ttlMs: number = 300000): void { // 5 minutes default
|
||||
const expires = Date.now() + ttlMs;
|
||||
this.cache.set(key, { data, expires });
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cache
|
||||
*/
|
||||
static clear(): void {
|
||||
this.cache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear expired entries
|
||||
*/
|
||||
static cleanup(): void {
|
||||
const now = Date.now();
|
||||
for (const [key, value] of this.cache.entries()) {
|
||||
if (value.expires <= now) {
|
||||
this.cache.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate cache key
|
||||
*/
|
||||
static generateKey(method: string, url: string, params?: any): string {
|
||||
const paramStr = params ? JSON.stringify(params) : '';
|
||||
return `${method}:${url}:${paramStr}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Export default as the unified API object
|
||||
export default api;
|
||||
351
frontend/src/api/services/notificationServices.ts
Normal file
351
frontend/src/api/services/notificationServices.ts
Normal file
@@ -0,0 +1,351 @@
|
||||
// src/api/services/NotificationService.ts
|
||||
import { apiClient } from '../base/apiClient';
|
||||
import {
|
||||
ApiResponse,
|
||||
NotificationSettings,
|
||||
} from '../types/api';
|
||||
|
||||
export interface NotificationCreate {
|
||||
type: 'email' | 'whatsapp' | 'push';
|
||||
recipient_email?: string;
|
||||
recipient_phone?: string;
|
||||
recipient_push_token?: string;
|
||||
subject?: string;
|
||||
message: string;
|
||||
template_id?: string;
|
||||
template_data?: Record<string, any>;
|
||||
scheduled_for?: string;
|
||||
broadcast?: boolean;
|
||||
priority?: 'low' | 'normal' | 'high';
|
||||
}
|
||||
|
||||
export interface NotificationResponse {
|
||||
id: string;
|
||||
type: string;
|
||||
recipient_email?: string;
|
||||
recipient_phone?: string;
|
||||
subject?: string;
|
||||
message: string;
|
||||
status: 'pending' | 'sent' | 'delivered' | 'failed';
|
||||
created_at: string;
|
||||
sent_at?: string;
|
||||
delivered_at?: string;
|
||||
error_message?: string;
|
||||
}
|
||||
|
||||
export interface NotificationHistory {
|
||||
id: string;
|
||||
type: string;
|
||||
recipient: string;
|
||||
subject?: string;
|
||||
status: string;
|
||||
created_at: string;
|
||||
sent_at?: string;
|
||||
delivered_at?: string;
|
||||
opened_at?: string;
|
||||
clicked_at?: string;
|
||||
error_message?: string;
|
||||
}
|
||||
|
||||
export interface NotificationTemplate {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
type: 'email' | 'whatsapp' | 'push';
|
||||
subject?: string;
|
||||
content: string;
|
||||
variables: string[];
|
||||
is_system: boolean;
|
||||
is_active: boolean;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface NotificationStats {
|
||||
total_sent: number;
|
||||
total_delivered: number;
|
||||
total_failed: number;
|
||||
delivery_rate: number;
|
||||
open_rate: number;
|
||||
click_rate: number;
|
||||
by_type: {
|
||||
email: { sent: number; delivered: number; opened: number; clicked: number };
|
||||
whatsapp: { sent: number; delivered: number; read: number };
|
||||
push: { sent: number; delivered: number; opened: number };
|
||||
};
|
||||
}
|
||||
|
||||
export interface BulkNotificationRequest {
|
||||
type: 'email' | 'whatsapp' | 'push';
|
||||
recipients: {
|
||||
email?: string;
|
||||
phone?: string;
|
||||
push_token?: string;
|
||||
template_data?: Record<string, any>;
|
||||
}[];
|
||||
template_id?: string;
|
||||
subject?: string;
|
||||
message?: string;
|
||||
scheduled_for?: string;
|
||||
batch_name?: string;
|
||||
}
|
||||
|
||||
export interface BulkNotificationStatus {
|
||||
id: string;
|
||||
batch_name?: string;
|
||||
total_recipients: number;
|
||||
sent: number;
|
||||
delivered: number;
|
||||
failed: number;
|
||||
status: 'queued' | 'processing' | 'completed' | 'failed';
|
||||
created_at: string;
|
||||
completed_at?: string;
|
||||
}
|
||||
|
||||
export class NotificationService {
|
||||
/**
|
||||
* Send single notification
|
||||
*/
|
||||
async sendNotification(notification: NotificationCreate): Promise<NotificationResponse> {
|
||||
const response = await apiClient.post<ApiResponse<NotificationResponse>>(
|
||||
'/notifications/send',
|
||||
notification
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send bulk notifications
|
||||
*/
|
||||
async sendBulkNotifications(request: BulkNotificationRequest): Promise<BulkNotificationStatus> {
|
||||
const response = await apiClient.post<ApiResponse<BulkNotificationStatus>>(
|
||||
'/notifications/bulk',
|
||||
request
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get notification history
|
||||
*/
|
||||
async getNotificationHistory(params?: {
|
||||
type?: string;
|
||||
status?: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}): Promise<{
|
||||
notifications: NotificationHistory[];
|
||||
total: number;
|
||||
page: number;
|
||||
pages: number;
|
||||
}> {
|
||||
const response = await apiClient.get<ApiResponse<any>>('/notifications/history', { params });
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get notification by ID
|
||||
*/
|
||||
async getNotification(notificationId: string): Promise<NotificationResponse> {
|
||||
const response = await apiClient.get<ApiResponse<NotificationResponse>>(
|
||||
`/notifications/${notificationId}`
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry failed notification
|
||||
*/
|
||||
async retryNotification(notificationId: string): Promise<NotificationResponse> {
|
||||
const response = await apiClient.post<ApiResponse<NotificationResponse>>(
|
||||
`/notifications/${notificationId}/retry`
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel scheduled notification
|
||||
*/
|
||||
async cancelNotification(notificationId: string): Promise<void> {
|
||||
await apiClient.post(`/notifications/${notificationId}/cancel`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get notification statistics
|
||||
*/
|
||||
async getNotificationStats(params?: {
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
type?: string;
|
||||
}): Promise<NotificationStats> {
|
||||
const response = await apiClient.get<ApiResponse<NotificationStats>>(
|
||||
'/notifications/stats',
|
||||
{ params }
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bulk notification status
|
||||
*/
|
||||
async getBulkStatus(batchId: string): Promise<BulkNotificationStatus> {
|
||||
const response = await apiClient.get<ApiResponse<BulkNotificationStatus>>(
|
||||
`/notifications/bulk/${batchId}/status`
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get notification templates
|
||||
*/
|
||||
async getTemplates(params?: {
|
||||
type?: string;
|
||||
active?: boolean;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}): Promise<{
|
||||
templates: NotificationTemplate[];
|
||||
total: number;
|
||||
page: number;
|
||||
pages: number;
|
||||
}> {
|
||||
const response = await apiClient.get<ApiResponse<any>>('/notifications/templates', { params });
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get template by ID
|
||||
*/
|
||||
async getTemplate(templateId: string): Promise<NotificationTemplate> {
|
||||
const response = await apiClient.get<ApiResponse<NotificationTemplate>>(
|
||||
`/notifications/templates/${templateId}`
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create notification template
|
||||
*/
|
||||
async createTemplate(template: {
|
||||
name: string;
|
||||
description: string;
|
||||
type: 'email' | 'whatsapp' | 'push';
|
||||
subject?: string;
|
||||
content: string;
|
||||
variables?: string[];
|
||||
}): Promise<NotificationTemplate> {
|
||||
const response = await apiClient.post<ApiResponse<NotificationTemplate>>(
|
||||
'/notifications/templates',
|
||||
template
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update notification template
|
||||
*/
|
||||
async updateTemplate(
|
||||
templateId: string,
|
||||
updates: Partial<NotificationTemplate>
|
||||
): Promise<NotificationTemplate> {
|
||||
const response = await apiClient.put<ApiResponse<NotificationTemplate>>(
|
||||
`/notifications/templates/${templateId}`,
|
||||
updates
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete notification template
|
||||
*/
|
||||
async deleteTemplate(templateId: string): Promise<void> {
|
||||
await apiClient.delete(`/notifications/templates/${templateId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user notification preferences
|
||||
*/
|
||||
async getPreferences(): Promise<NotificationSettings> {
|
||||
const response = await apiClient.get<ApiResponse<NotificationSettings>>(
|
||||
'/notifications/preferences'
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user notification preferences
|
||||
*/
|
||||
async updatePreferences(preferences: Partial<NotificationSettings>): Promise<NotificationSettings> {
|
||||
const response = await apiClient.put<ApiResponse<NotificationSettings>>(
|
||||
'/notifications/preferences',
|
||||
preferences
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test notification delivery
|
||||
*/
|
||||
async testNotification(type: 'email' | 'whatsapp' | 'push', recipient: string): Promise<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
delivery_time_ms?: number;
|
||||
}> {
|
||||
const response = await apiClient.post<ApiResponse<any>>(
|
||||
'/notifications/test',
|
||||
{ type, recipient }
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get delivery webhooks
|
||||
*/
|
||||
async getWebhooks(params?: {
|
||||
type?: string;
|
||||
status?: string;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}): Promise<{
|
||||
webhooks: {
|
||||
id: string;
|
||||
notification_id: string;
|
||||
event_type: string;
|
||||
status: string;
|
||||
payload: any;
|
||||
received_at: string;
|
||||
}[];
|
||||
total: number;
|
||||
page: number;
|
||||
pages: number;
|
||||
}> {
|
||||
const response = await apiClient.get<ApiResponse<any>>('/notifications/webhooks', { params });
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to notification events
|
||||
*/
|
||||
async subscribeToEvents(events: string[], webhookUrl: string): Promise<{
|
||||
subscription_id: string;
|
||||
events: string[];
|
||||
webhook_url: string;
|
||||
created_at: string;
|
||||
}> {
|
||||
const response = await apiClient.post<ApiResponse<any>>('/notifications/subscribe', {
|
||||
events,
|
||||
webhook_url: webhookUrl,
|
||||
});
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from notification events
|
||||
*/
|
||||
async unsubscribeFromEvents(subscriptionId: string): Promise<void> {
|
||||
await apiClient.delete(`/notifications/subscribe/${subscriptionId}`);
|
||||
}
|
||||
}
|
||||
|
||||
export const notificationService = new NotificationService();
|
||||
369
frontend/src/api/services/tenantServices.ts
Normal file
369
frontend/src/api/services/tenantServices.ts
Normal file
@@ -0,0 +1,369 @@
|
||||
// src/api/services/TenantService.ts
|
||||
import { apiClient } from '../base/apiClient';
|
||||
import {
|
||||
ApiResponse,
|
||||
TenantInfo,
|
||||
} from '../types/api';
|
||||
|
||||
export interface TenantCreate {
|
||||
name: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
address: string;
|
||||
latitude?: number;
|
||||
longitude?: number;
|
||||
business_type: 'individual_bakery' | 'central_workshop';
|
||||
subscription_plan?: string;
|
||||
settings?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface TenantUpdate extends Partial<TenantCreate> {
|
||||
is_active?: boolean;
|
||||
}
|
||||
|
||||
export interface TenantSettings {
|
||||
business_hours: {
|
||||
monday: { open: string; close: string; closed: boolean };
|
||||
tuesday: { open: string; close: string; closed: boolean };
|
||||
wednesday: { open: string; close: string; closed: boolean };
|
||||
thursday: { open: string; close: string; closed: boolean };
|
||||
friday: { open: string; close: string; closed: boolean };
|
||||
saturday: { open: string; close: string; closed: boolean };
|
||||
sunday: { open: string; close: string; closed: boolean };
|
||||
};
|
||||
timezone: string;
|
||||
currency: string;
|
||||
language: string;
|
||||
notification_preferences: {
|
||||
email_enabled: boolean;
|
||||
whatsapp_enabled: boolean;
|
||||
forecast_alerts: boolean;
|
||||
training_notifications: boolean;
|
||||
weekly_reports: boolean;
|
||||
};
|
||||
forecast_preferences: {
|
||||
default_forecast_days: number;
|
||||
confidence_level: number;
|
||||
include_weather: boolean;
|
||||
include_traffic: boolean;
|
||||
alert_thresholds: {
|
||||
high_demand_increase: number;
|
||||
low_demand_decrease: number;
|
||||
};
|
||||
};
|
||||
data_retention_days: number;
|
||||
}
|
||||
|
||||
export interface TenantStats {
|
||||
total_users: number;
|
||||
active_users: number;
|
||||
total_sales_records: number;
|
||||
total_forecasts: number;
|
||||
total_notifications_sent: number;
|
||||
storage_used_mb: number;
|
||||
api_calls_this_month: number;
|
||||
last_activity: string;
|
||||
subscription_status: 'active' | 'inactive' | 'suspended';
|
||||
subscription_expires: string;
|
||||
}
|
||||
|
||||
export interface TenantUser {
|
||||
id: string;
|
||||
email: string;
|
||||
full_name: string;
|
||||
role: string;
|
||||
is_active: boolean;
|
||||
last_login: string | null;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface InviteUser {
|
||||
email: string;
|
||||
role: 'admin' | 'manager' | 'user';
|
||||
full_name?: string;
|
||||
send_invitation_email?: boolean;
|
||||
}
|
||||
|
||||
export class TenantService {
|
||||
/**
|
||||
* Get current tenant info
|
||||
*/
|
||||
async getCurrentTenant(): Promise<TenantInfo> {
|
||||
const response = await apiClient.get<ApiResponse<TenantInfo>>('/tenants/current');
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update current tenant
|
||||
*/
|
||||
async updateCurrentTenant(updates: TenantUpdate): Promise<TenantInfo> {
|
||||
const response = await apiClient.put<ApiResponse<TenantInfo>>('/tenants/current', updates);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tenant settings
|
||||
*/
|
||||
async getTenantSettings(): Promise<TenantSettings> {
|
||||
const response = await apiClient.get<ApiResponse<TenantSettings>>('/tenants/settings');
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update tenant settings
|
||||
*/
|
||||
async updateTenantSettings(settings: Partial<TenantSettings>): Promise<TenantSettings> {
|
||||
const response = await apiClient.put<ApiResponse<TenantSettings>>(
|
||||
'/tenants/settings',
|
||||
settings
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tenant statistics
|
||||
*/
|
||||
async getTenantStats(): Promise<TenantStats> {
|
||||
const response = await apiClient.get<ApiResponse<TenantStats>>('/tenants/stats');
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tenant users
|
||||
*/
|
||||
async getTenantUsers(params?: {
|
||||
role?: string;
|
||||
active?: boolean;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}): Promise<{
|
||||
users: TenantUser[];
|
||||
total: number;
|
||||
page: number;
|
||||
pages: number;
|
||||
}> {
|
||||
const response = await apiClient.get<ApiResponse<any>>('/tenants/users', { params });
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invite user to tenant
|
||||
*/
|
||||
async inviteUser(invitation: InviteUser): Promise<{
|
||||
invitation_id: string;
|
||||
email: string;
|
||||
role: string;
|
||||
expires_at: string;
|
||||
invitation_token: string;
|
||||
}> {
|
||||
const response = await apiClient.post<ApiResponse<any>>('/tenants/users/invite', invitation);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user role
|
||||
*/
|
||||
async updateUserRole(userId: string, role: string): Promise<TenantUser> {
|
||||
const response = await apiClient.patch<ApiResponse<TenantUser>>(
|
||||
`/tenants/users/${userId}`,
|
||||
{ role }
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivate user
|
||||
*/
|
||||
async deactivateUser(userId: string): Promise<TenantUser> {
|
||||
const response = await apiClient.patch<ApiResponse<TenantUser>>(
|
||||
`/tenants/users/${userId}`,
|
||||
{ is_active: false }
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reactivate user
|
||||
*/
|
||||
async reactivateUser(userId: string): Promise<TenantUser> {
|
||||
const response = await apiClient.patch<ApiResponse<TenantUser>>(
|
||||
`/tenants/users/${userId}`,
|
||||
{ is_active: true }
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove user from tenant
|
||||
*/
|
||||
async removeUser(userId: string): Promise<void> {
|
||||
await apiClient.delete(`/tenants/users/${userId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get pending invitations
|
||||
*/
|
||||
async getPendingInvitations(): Promise<{
|
||||
id: string;
|
||||
email: string;
|
||||
role: string;
|
||||
invited_at: string;
|
||||
expires_at: string;
|
||||
invited_by: string;
|
||||
}[]> {
|
||||
const response = await apiClient.get<ApiResponse<any>>('/tenants/invitations');
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel invitation
|
||||
*/
|
||||
async cancelInvitation(invitationId: string): Promise<void> {
|
||||
await apiClient.delete(`/tenants/invitations/${invitationId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resend invitation
|
||||
*/
|
||||
async resendInvitation(invitationId: string): Promise<void> {
|
||||
await apiClient.post(`/tenants/invitations/${invitationId}/resend`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tenant activity log
|
||||
*/
|
||||
async getActivityLog(params?: {
|
||||
userId?: string;
|
||||
action?: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}): Promise<{
|
||||
activities: {
|
||||
id: string;
|
||||
user_id: string;
|
||||
user_name: string;
|
||||
action: string;
|
||||
resource: string;
|
||||
resource_id: string;
|
||||
details?: Record<string, any>;
|
||||
ip_address?: string;
|
||||
user_agent?: string;
|
||||
created_at: string;
|
||||
}[];
|
||||
total: number;
|
||||
page: number;
|
||||
pages: number;
|
||||
}> {
|
||||
const response = await apiClient.get<ApiResponse<any>>('/tenants/activity', { params });
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tenant billing info
|
||||
*/
|
||||
async getBillingInfo(): Promise<{
|
||||
subscription_plan: string;
|
||||
billing_cycle: 'monthly' | 'yearly';
|
||||
next_billing_date: string;
|
||||
amount: number;
|
||||
currency: string;
|
||||
payment_method: {
|
||||
type: string;
|
||||
last_four: string;
|
||||
expires: string;
|
||||
};
|
||||
usage: {
|
||||
api_calls: number;
|
||||
storage_mb: number;
|
||||
users: number;
|
||||
limits: {
|
||||
api_calls_per_month: number;
|
||||
storage_mb: number;
|
||||
max_users: number;
|
||||
};
|
||||
};
|
||||
}> {
|
||||
const response = await apiClient.get<ApiResponse<any>>('/tenants/billing');
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update billing info
|
||||
*/
|
||||
async updateBillingInfo(billingData: {
|
||||
payment_method_token?: string;
|
||||
billing_address?: {
|
||||
street: string;
|
||||
city: string;
|
||||
state: string;
|
||||
zip: string;
|
||||
country: string;
|
||||
};
|
||||
}): Promise<void> {
|
||||
await apiClient.put('/tenants/billing', billingData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change subscription plan
|
||||
*/
|
||||
async changeSubscriptionPlan(
|
||||
planId: string,
|
||||
billingCycle: 'monthly' | 'yearly'
|
||||
): Promise<{
|
||||
subscription_id: string;
|
||||
plan: string;
|
||||
billing_cycle: string;
|
||||
next_billing_date: string;
|
||||
proration_amount?: number;
|
||||
}> {
|
||||
const response = await apiClient.post<ApiResponse<any>>('/tenants/subscription/change', {
|
||||
plan_id: planId,
|
||||
billing_cycle: billingCycle,
|
||||
});
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel subscription
|
||||
*/
|
||||
async cancelSubscription(cancelAt: 'immediately' | 'end_of_period'): Promise<{
|
||||
cancelled_at: string;
|
||||
will_cancel_at: string;
|
||||
refund_amount?: number;
|
||||
}> {
|
||||
const response = await apiClient.post<ApiResponse<any>>('/tenants/subscription/cancel', {
|
||||
cancel_at: cancelAt,
|
||||
});
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export tenant data
|
||||
*/
|
||||
async exportTenantData(dataTypes: string[], format: 'json' | 'csv'): Promise<Blob> {
|
||||
const response = await apiClient.post('/tenants/export', {
|
||||
data_types: dataTypes,
|
||||
format,
|
||||
responseType: 'blob',
|
||||
});
|
||||
return response as unknown as Blob;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete tenant (GDPR compliance)
|
||||
*/
|
||||
async deleteTenant(confirmationToken: string): Promise<{
|
||||
deletion_scheduled_at: string;
|
||||
data_retention_until: string;
|
||||
recovery_period_days: number;
|
||||
}> {
|
||||
const response = await apiClient.delete<ApiResponse<any>>('/tenants/current', {
|
||||
data: { confirmation_token: confirmationToken },
|
||||
});
|
||||
return response.data!;
|
||||
}
|
||||
}
|
||||
|
||||
export const tenantService = new TenantService();
|
||||
221
frontend/src/api/services/trainingService.ts
Normal file
221
frontend/src/api/services/trainingService.ts
Normal file
@@ -0,0 +1,221 @@
|
||||
// src/api/services/TrainingService.ts
|
||||
import { apiClient } from '../base/apiClient';
|
||||
import {
|
||||
ApiResponse,
|
||||
TrainingRequest,
|
||||
TrainingJobStatus,
|
||||
TrainedModel,
|
||||
} from '../types/api';
|
||||
|
||||
export interface TrainingJobProgress {
|
||||
id: string;
|
||||
status: 'queued' | 'running' | 'completed' | 'failed' | 'cancelled';
|
||||
progress: number;
|
||||
current_step?: string;
|
||||
total_steps?: number;
|
||||
step_details?: string;
|
||||
estimated_completion?: string;
|
||||
logs?: string[];
|
||||
}
|
||||
|
||||
export interface ModelMetrics {
|
||||
mape: number;
|
||||
rmse: number;
|
||||
mae: number;
|
||||
r2_score: number;
|
||||
training_samples: number;
|
||||
validation_samples: number;
|
||||
features_used: string[];
|
||||
}
|
||||
|
||||
export interface TrainingConfiguration {
|
||||
include_weather: boolean;
|
||||
include_traffic: boolean;
|
||||
min_data_points: number;
|
||||
forecast_horizon_days: number;
|
||||
cross_validation_folds: number;
|
||||
hyperparameter_tuning: boolean;
|
||||
products?: string[];
|
||||
}
|
||||
|
||||
export class TrainingService {
|
||||
/**
|
||||
* Start new training job
|
||||
*/
|
||||
async startTraining(config: TrainingConfiguration): Promise<TrainingJobStatus> {
|
||||
const response = await apiClient.post<ApiResponse<TrainingJobStatus>>(
|
||||
'/training/jobs',
|
||||
config
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get training job status
|
||||
*/
|
||||
async getTrainingStatus(jobId: string): Promise<TrainingJobProgress> {
|
||||
const response = await apiClient.get<ApiResponse<TrainingJobProgress>>(
|
||||
`/training/jobs/${jobId}`
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all training jobs
|
||||
*/
|
||||
async getTrainingHistory(params?: {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
status?: string;
|
||||
}): Promise<{
|
||||
jobs: TrainingJobStatus[];
|
||||
total: number;
|
||||
page: number;
|
||||
pages: number;
|
||||
}> {
|
||||
const response = await apiClient.get<ApiResponse<any>>('/training/jobs', { params });
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel training job
|
||||
*/
|
||||
async cancelTraining(jobId: string): Promise<void> {
|
||||
await apiClient.post(`/training/jobs/${jobId}/cancel`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get trained models
|
||||
*/
|
||||
async getModels(params?: {
|
||||
productName?: string;
|
||||
active?: boolean;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}): Promise<{
|
||||
models: TrainedModel[];
|
||||
total: number;
|
||||
page: number;
|
||||
pages: number;
|
||||
}> {
|
||||
const response = await apiClient.get<ApiResponse<any>>('/training/models', { params });
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get specific model details
|
||||
*/
|
||||
async getModel(modelId: string): Promise<TrainedModel> {
|
||||
const response = await apiClient.get<ApiResponse<TrainedModel>>(
|
||||
`/training/models/${modelId}`
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get model metrics
|
||||
*/
|
||||
async getModelMetrics(modelId: string): Promise<ModelMetrics> {
|
||||
const response = await apiClient.get<ApiResponse<ModelMetrics>>(
|
||||
`/training/models/${modelId}/metrics`
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate/deactivate model
|
||||
*/
|
||||
async toggleModelStatus(modelId: string, active: boolean): Promise<TrainedModel> {
|
||||
const response = await apiClient.patch<ApiResponse<TrainedModel>>(
|
||||
`/training/models/${modelId}`,
|
||||
{ is_active: active }
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete model
|
||||
*/
|
||||
async deleteModel(modelId: string): Promise<void> {
|
||||
await apiClient.delete(`/training/models/${modelId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Train specific product
|
||||
*/
|
||||
async trainProduct(productName: string, config?: Partial<TrainingConfiguration>): Promise<TrainingJobStatus> {
|
||||
const response = await apiClient.post<ApiResponse<TrainingJobStatus>>(
|
||||
'/training/products/train',
|
||||
{
|
||||
product_name: productName,
|
||||
...config,
|
||||
}
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get training statistics
|
||||
*/
|
||||
async getTrainingStats(): Promise<{
|
||||
total_models: number;
|
||||
active_models: number;
|
||||
avg_accuracy: number;
|
||||
last_training_date: string | null;
|
||||
products_trained: number;
|
||||
training_time_avg_minutes: number;
|
||||
}> {
|
||||
const response = await apiClient.get<ApiResponse<any>>('/training/stats');
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate training data
|
||||
*/
|
||||
async validateTrainingData(products?: string[]): Promise<{
|
||||
valid: boolean;
|
||||
errors: string[];
|
||||
warnings: string[];
|
||||
product_data_points: Record<string, number>;
|
||||
recommendation: string;
|
||||
}> {
|
||||
const response = await apiClient.post<ApiResponse<any>>('/training/validate', {
|
||||
products,
|
||||
});
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get training recommendations
|
||||
*/
|
||||
async getTrainingRecommendations(): Promise<{
|
||||
should_retrain: boolean;
|
||||
reasons: string[];
|
||||
recommended_products: string[];
|
||||
optimal_config: TrainingConfiguration;
|
||||
}> {
|
||||
const response = await apiClient.get<ApiResponse<any>>('/training/recommendations');
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get training logs
|
||||
*/
|
||||
async getTrainingLogs(jobId: string): Promise<string[]> {
|
||||
const response = await apiClient.get<ApiResponse<string[]>>(`/training/jobs/${jobId}/logs`);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export model
|
||||
*/
|
||||
async exportModel(modelId: string, format: 'pickle' | 'onnx' = 'pickle'): Promise<Blob> {
|
||||
const response = await apiClient.get(`/training/models/${modelId}/export`, {
|
||||
params: { format },
|
||||
responseType: 'blob',
|
||||
});
|
||||
return response as unknown as Blob;
|
||||
}
|
||||
}
|
||||
|
||||
export const trainingService = new TrainingService();
|
||||
Reference in New Issue
Block a user