Add new frontend - fix 2
This commit is contained in:
49
frontend/src/api/services/api.ts
Normal file
49
frontend/src/api/services/api.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
// 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,98 +0,0 @@
|
||||
// frontend/dashboard/src/api/services/authApi.ts
|
||||
/**
|
||||
* Authentication API service
|
||||
*/
|
||||
|
||||
import { ApiClient } from '../base/apiClient';
|
||||
import {
|
||||
LoginRequest,
|
||||
RegisterRequest,
|
||||
TokenResponse,
|
||||
UserProfile,
|
||||
ApiResponse,
|
||||
} from '../../types/api';
|
||||
|
||||
export class AuthApi {
|
||||
constructor(private client: ApiClient) {}
|
||||
|
||||
async login(credentials: LoginRequest): Promise<TokenResponse> {
|
||||
const response = await this.client.post<TokenResponse>('/auth/login', credentials);
|
||||
|
||||
// Store tokens
|
||||
localStorage.setItem('access_token', response.access_token);
|
||||
localStorage.setItem('refresh_token', response.refresh_token);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
async register(userData: RegisterRequest): Promise<UserProfile> {
|
||||
return this.client.post<UserProfile>('/auth/register', userData);
|
||||
}
|
||||
|
||||
async logout(): Promise<void> {
|
||||
try {
|
||||
await this.client.post('/auth/logout');
|
||||
} finally {
|
||||
// Always clear local storage
|
||||
localStorage.removeItem('access_token');
|
||||
localStorage.removeItem('refresh_token');
|
||||
localStorage.removeItem('user_profile');
|
||||
}
|
||||
}
|
||||
|
||||
async refreshToken(): Promise<TokenResponse> {
|
||||
const refreshToken = localStorage.getItem('refresh_token');
|
||||
|
||||
if (!refreshToken) {
|
||||
throw new Error('No refresh token available');
|
||||
}
|
||||
|
||||
const response = await this.client.post<TokenResponse>('/auth/refresh', {
|
||||
refresh_token: refreshToken,
|
||||
});
|
||||
|
||||
// Update stored tokens
|
||||
localStorage.setItem('access_token', response.access_token);
|
||||
localStorage.setItem('refresh_token', response.refresh_token);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
async getCurrentUser(): Promise<UserProfile> {
|
||||
const profile = await this.client.get<UserProfile>('/auth/me');
|
||||
localStorage.setItem('user_profile', JSON.stringify(profile));
|
||||
return profile;
|
||||
}
|
||||
|
||||
async updateProfile(updates: Partial<UserProfile>): Promise<UserProfile> {
|
||||
return this.client.patch<UserProfile>('/auth/profile', updates);
|
||||
}
|
||||
|
||||
async changePassword(currentPassword: string, newPassword: string): Promise<void> {
|
||||
return this.client.post('/auth/change-password', {
|
||||
current_password: currentPassword,
|
||||
new_password: newPassword,
|
||||
});
|
||||
}
|
||||
|
||||
async requestPasswordReset(email: string): Promise<void> {
|
||||
return this.client.post('/auth/password-reset', { email });
|
||||
}
|
||||
|
||||
async confirmPasswordReset(token: string, newPassword: string): Promise<void> {
|
||||
return this.client.post('/auth/password-reset/confirm', {
|
||||
token,
|
||||
new_password: newPassword,
|
||||
});
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
isAuthenticated(): boolean {
|
||||
return !!localStorage.getItem('access_token');
|
||||
}
|
||||
|
||||
getStoredUser(): UserProfile | null {
|
||||
const stored = localStorage.getItem('user_profile');
|
||||
return stored ? JSON.parse(stored) : null;
|
||||
}
|
||||
}
|
||||
96
frontend/src/api/services/authService.ts
Normal file
96
frontend/src/api/services/authService.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
// src/api/auth/authService.ts
|
||||
import { tokenManager } from '../auth/tokenManager';
|
||||
import { apiClient } from '../base/apiClient';
|
||||
|
||||
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;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
class AuthService {
|
||||
async login(credentials: LoginCredentials): Promise<UserProfile> {
|
||||
// OAuth2 password flow
|
||||
const formData = new URLSearchParams();
|
||||
formData.append('username', credentials.email);
|
||||
formData.append('password', credentials.password);
|
||||
formData.append('grant_type', 'password');
|
||||
|
||||
const response = await fetch('/api/auth/token', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.detail || 'Login failed');
|
||||
}
|
||||
|
||||
const tokenResponse = await response.json();
|
||||
await tokenManager.storeTokens(tokenResponse);
|
||||
|
||||
// Get user profile
|
||||
return this.getCurrentUser();
|
||||
}
|
||||
|
||||
async register(data: RegisterData): Promise<UserProfile> {
|
||||
const response = await apiClient.post('/auth/register', data);
|
||||
|
||||
// Auto-login after registration
|
||||
await this.login({
|
||||
email: data.email,
|
||||
password: data.password
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
async logout(): Promise<void> {
|
||||
try {
|
||||
await apiClient.post('/auth/logout');
|
||||
} finally {
|
||||
tokenManager.clearTokens();
|
||||
window.location.href = '/login';
|
||||
}
|
||||
}
|
||||
|
||||
async getCurrentUser(): Promise<UserProfile> {
|
||||
return apiClient.get('/auth/me');
|
||||
}
|
||||
|
||||
async updateProfile(updates: Partial<UserProfile>): Promise<UserProfile> {
|
||||
return apiClient.patch('/auth/profile', updates);
|
||||
}
|
||||
|
||||
async changePassword(currentPassword: string, newPassword: string): Promise<void> {
|
||||
await apiClient.post('/auth/change-password', {
|
||||
current_password: currentPassword,
|
||||
new_password: newPassword
|
||||
});
|
||||
}
|
||||
|
||||
isAuthenticated(): boolean {
|
||||
return tokenManager.isAuthenticated();
|
||||
}
|
||||
}
|
||||
|
||||
export const authService = new AuthService();
|
||||
@@ -1,53 +0,0 @@
|
||||
// frontend/dashboard/src/api/services/dataApi.ts
|
||||
/**
|
||||
* External data API service (weather, traffic, etc.)
|
||||
*/
|
||||
|
||||
import { ApiClient } from '../base/apiClient';
|
||||
import { WeatherData, TrafficData } from '../../types/api';
|
||||
|
||||
export class DataApi {
|
||||
constructor(private client: ApiClient) {}
|
||||
|
||||
async getWeatherData(
|
||||
startDate: string,
|
||||
endDate: string
|
||||
): Promise<WeatherData[]> {
|
||||
return this.client.get<WeatherData[]>('/data/weather', {
|
||||
params: { start_date: startDate, end_date: endDate },
|
||||
});
|
||||
}
|
||||
|
||||
async getTrafficData(
|
||||
startDate: string,
|
||||
endDate: string
|
||||
): Promise<TrafficData[]> {
|
||||
return this.client.get<TrafficData[]>('/data/traffic', {
|
||||
params: { start_date: startDate, end_date: endDate },
|
||||
});
|
||||
}
|
||||
|
||||
async getCurrentWeather(): Promise<WeatherData> {
|
||||
return this.client.get<WeatherData>('/data/weather/current');
|
||||
}
|
||||
|
||||
async getWeatherForecast(days: number = 7): Promise<WeatherData[]> {
|
||||
return this.client.get<WeatherData[]>('/data/weather/forecast', {
|
||||
params: { days },
|
||||
});
|
||||
}
|
||||
|
||||
async syncExternalData(): Promise<{ message: string; synced_records: number }> {
|
||||
return this.client.post('/data/sync');
|
||||
}
|
||||
|
||||
async getDataQuality(): Promise<{
|
||||
weather_coverage: number;
|
||||
traffic_coverage: number;
|
||||
last_sync: string;
|
||||
issues: string[];
|
||||
}> {
|
||||
return this.client.get('/data/quality');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
// frontend/dashboard/src/api/services/forecastingApi.ts
|
||||
/**
|
||||
* Forecasting API service
|
||||
*/
|
||||
|
||||
import { ApiClient } from '../base/apiClient';
|
||||
import {
|
||||
ForecastRecord,
|
||||
ForecastRequest,
|
||||
ApiResponse,
|
||||
} from '../../types/api';
|
||||
|
||||
export class ForecastingApi {
|
||||
constructor(private client: ApiClient) {}
|
||||
|
||||
async getForecasts(request: ForecastRequest = {}): Promise<ForecastRecord[]> {
|
||||
return this.client.get<ForecastRecord[]>('/forecasting/forecasts', {
|
||||
params: request,
|
||||
});
|
||||
}
|
||||
|
||||
async getForecastByProduct(
|
||||
productName: string,
|
||||
daysAhead: number = 7
|
||||
): Promise<ForecastRecord[]> {
|
||||
return this.client.get<ForecastRecord[]>(`/forecasting/forecasts/${productName}`, {
|
||||
params: { days_ahead: daysAhead },
|
||||
});
|
||||
}
|
||||
|
||||
async generateForecast(request: ForecastRequest): Promise<ForecastRecord[]> {
|
||||
return this.client.post<ForecastRecord[]>('/forecasting/generate', request);
|
||||
}
|
||||
|
||||
async updateForecast(forecastId: string, adjustments: Partial<ForecastRecord>): Promise<ForecastRecord> {
|
||||
return this.client.patch<ForecastRecord>(`/forecasting/forecasts/${forecastId}`, adjustments);
|
||||
}
|
||||
|
||||
async deleteForecast(forecastId: string): Promise<void> {
|
||||
return this.client.delete(`/forecasting/forecasts/${forecastId}`);
|
||||
}
|
||||
|
||||
async getProductPerformance(productName: string, days: number = 30): Promise<any> {
|
||||
return this.client.get(`/forecasting/performance/${productName}`, {
|
||||
params: { days },
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
// src/api/services/index.ts
|
||||
import { apiClient } from '../base/apiClient';
|
||||
import { AuthService } from './authService';
|
||||
import { TrainingService } from './trainingService';
|
||||
import { ForecastingService } from './forecastingService';
|
||||
import { DataService } from './dataService';
|
||||
import { TenantService } from './tenantService';
|
||||
|
||||
// Service instances with circuit breakers
|
||||
export const authService = new AuthService(apiClient);
|
||||
export const trainingService = new TrainingService(apiClient);
|
||||
export const forecastingService = new ForecastingService(apiClient);
|
||||
export const dataService = new DataService(apiClient);
|
||||
export const tenantService = new TenantService(apiClient);
|
||||
|
||||
// Export types
|
||||
export * from '../types';
|
||||
@@ -1,70 +0,0 @@
|
||||
// frontend/dashboard/src/api/services/salesApi.ts
|
||||
/**
|
||||
* Sales data API service
|
||||
*/
|
||||
|
||||
import { ApiClient } from '../base/apiClient';
|
||||
import {
|
||||
SalesRecord,
|
||||
CreateSalesRequest,
|
||||
ApiResponse,
|
||||
} from '../../types/api';
|
||||
|
||||
export interface SalesQuery {
|
||||
start_date?: string;
|
||||
end_date?: string;
|
||||
product_name?: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
}
|
||||
|
||||
export class SalesApi {
|
||||
constructor(private client: ApiClient) {}
|
||||
|
||||
async getSales(query: SalesQuery = {}): Promise<SalesRecord[]> {
|
||||
return this.client.get<SalesRecord[]>('/data/sales', {
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
async createSalesRecord(salesData: CreateSalesRequest): Promise<SalesRecord> {
|
||||
return this.client.post<SalesRecord>('/data/sales', salesData);
|
||||
}
|
||||
|
||||
async updateSalesRecord(id: string, updates: Partial<CreateSalesRequest>): Promise<SalesRecord> {
|
||||
return this.client.patch<SalesRecord>(`/data/sales/${id}`, updates);
|
||||
}
|
||||
|
||||
async deleteSalesRecord(id: string): Promise<void> {
|
||||
return this.client.delete(`/data/sales/${id}`);
|
||||
}
|
||||
|
||||
async bulkCreateSales(salesData: CreateSalesRequest[]): Promise<SalesRecord[]> {
|
||||
return this.client.post<SalesRecord[]>('/data/sales/bulk', salesData);
|
||||
}
|
||||
|
||||
async uploadSalesFile(
|
||||
file: File,
|
||||
onProgress?: (progress: number) => void
|
||||
): Promise<{ imported: number; errors: any[] }> {
|
||||
return this.client.uploadFile('/data/sales/upload', file, onProgress);
|
||||
}
|
||||
|
||||
async getSalesAnalytics(
|
||||
startDate: string,
|
||||
endDate: string
|
||||
): Promise<{
|
||||
totalRevenue: number;
|
||||
totalQuantity: number;
|
||||
topProducts: Array<{ product_name: string; quantity: number; revenue: number }>;
|
||||
dailyTrends: Array<{ date: string; quantity: number; revenue: number }>;
|
||||
}> {
|
||||
return this.client.get('/data/sales/analytics', {
|
||||
params: { start_date: startDate, end_date: endDate },
|
||||
});
|
||||
}
|
||||
|
||||
async getProductList(): Promise<string[]> {
|
||||
return this.client.get<string[]>('/data/sales/products');
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
// frontend/dashboard/src/api/services/tenantApi.ts
|
||||
/**
|
||||
* Tenant management API service
|
||||
*/
|
||||
|
||||
import { ApiClient } from '../base/apiClient';
|
||||
import { TenantInfo, NotificationSettings } from '../../types/api';
|
||||
|
||||
export class TenantApi {
|
||||
constructor(private client: ApiClient) {}
|
||||
|
||||
async getCurrentTenant(): Promise<TenantInfo> {
|
||||
return this.client.get<TenantInfo>('/tenants/current');
|
||||
}
|
||||
|
||||
async updateTenant(updates: Partial<TenantInfo>): Promise<TenantInfo> {
|
||||
return this.client.patch<TenantInfo>('/tenants/current', updates);
|
||||
}
|
||||
|
||||
async getNotificationSettings(): Promise<NotificationSettings> {
|
||||
return this.client.get<NotificationSettings>('/tenants/notifications');
|
||||
}
|
||||
|
||||
async updateNotificationSettings(settings: Partial<NotificationSettings>): Promise<NotificationSettings> {
|
||||
return this.client.patch<NotificationSettings>('/tenants/notifications', settings);
|
||||
}
|
||||
|
||||
async testNotification(type: 'email' | 'whatsapp'): Promise<{ sent: boolean; message: string }> {
|
||||
return this.client.post(`/tenants/notifications/test/${type}`);
|
||||
}
|
||||
|
||||
async getTenantStats(): Promise<{
|
||||
total_sales_records: number;
|
||||
total_forecasts: number;
|
||||
active_models: number;
|
||||
last_training: string;
|
||||
data_quality_score: number;
|
||||
}> {
|
||||
return this.client.get('/tenants/stats');
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
// frontend/dashboard/src/api/services/trainingApi.ts
|
||||
/**
|
||||
* Training API service
|
||||
*/
|
||||
|
||||
import { ApiClient } from '../base/apiClient';
|
||||
import {
|
||||
TrainingJobStatus,
|
||||
TrainingRequest,
|
||||
TrainedModel,
|
||||
ApiResponse,
|
||||
} from '../../types/api';
|
||||
|
||||
export class TrainingApi {
|
||||
constructor(private client: ApiClient) {}
|
||||
|
||||
async startTraining(request: TrainingRequest = {}): Promise<TrainingJobStatus> {
|
||||
return this.client.post<TrainingJobStatus>('/training/train', request);
|
||||
}
|
||||
|
||||
async getTrainingStatus(jobId: string): Promise<TrainingJobStatus> {
|
||||
return this.client.get<TrainingJobStatus>(`/training/status/${jobId}`);
|
||||
}
|
||||
|
||||
async getTrainingJobs(limit: number = 10, offset: number = 0): Promise<TrainingJobStatus[]> {
|
||||
return this.client.get<TrainingJobStatus[]>('/training/jobs', {
|
||||
params: { limit, offset },
|
||||
});
|
||||
}
|
||||
|
||||
async getTrainedModels(): Promise<TrainedModel[]> {
|
||||
return this.client.get<TrainedModel[]>('/training/models');
|
||||
}
|
||||
|
||||
async cancelTraining(jobId: string): Promise<void> {
|
||||
return this.client.delete(`/training/jobs/${jobId}`);
|
||||
}
|
||||
|
||||
// WebSocket for real-time training progress
|
||||
subscribeToTrainingProgress(
|
||||
jobId: string,
|
||||
onProgress: (progress: TrainingJobStatus) => void,
|
||||
onError?: (error: Error) => void
|
||||
): WebSocket {
|
||||
const ws = this.client.createWebSocket(`/training/progress/${jobId}`);
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
onProgress(data);
|
||||
} catch (error) {
|
||||
onError?.(new Error('Failed to parse progress data'));
|
||||
}
|
||||
};
|
||||
|
||||
ws.onerror = (event) => {
|
||||
onError?.(new Error('WebSocket connection error'));
|
||||
};
|
||||
|
||||
return ws;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user