Add new frontend - fix 2
This commit is contained in:
@@ -322,128 +322,3 @@ class ApiClient {
|
||||
export const apiClient = new ApiClient({
|
||||
baseURL: process.env.REACT_APP_API_URL || 'http://localhost:8000/api'
|
||||
});
|
||||
|
||||
// src/api/base/circuitBreaker.ts
|
||||
export class CircuitBreaker {
|
||||
private failures: number = 0;
|
||||
private lastFailureTime: number = 0;
|
||||
private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED';
|
||||
|
||||
constructor(
|
||||
private threshold: number = 5,
|
||||
private timeout: number = 60000 // 1 minute
|
||||
) {}
|
||||
|
||||
async execute<T>(fn: () => Promise<T>): Promise<T> {
|
||||
if (this.state === 'OPEN') {
|
||||
if (Date.now() - this.lastFailureTime > this.timeout) {
|
||||
this.state = 'HALF_OPEN';
|
||||
} else {
|
||||
throw new Error('Circuit breaker is OPEN');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await fn();
|
||||
this.onSuccess();
|
||||
return result;
|
||||
} catch (error) {
|
||||
this.onFailure();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private onSuccess(): void {
|
||||
this.failures = 0;
|
||||
this.state = 'CLOSED';
|
||||
}
|
||||
|
||||
private onFailure(): void {
|
||||
this.failures++;
|
||||
this.lastFailureTime = Date.now();
|
||||
|
||||
if (this.failures >= this.threshold) {
|
||||
this.state = 'OPEN';
|
||||
}
|
||||
}
|
||||
|
||||
getState(): string {
|
||||
return this.state;
|
||||
}
|
||||
}
|
||||
|
||||
// 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';
|
||||
|
||||
// src/components/common/ErrorBoundary.tsx
|
||||
import React, { Component, ErrorInfo, ReactNode } from 'react';
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
fallback?: ReactNode;
|
||||
}
|
||||
|
||||
interface State {
|
||||
hasError: boolean;
|
||||
error: Error | null;
|
||||
}
|
||||
|
||||
export class ErrorBoundary extends Component<Props, State> {
|
||||
state: State = {
|
||||
hasError: false,
|
||||
error: null
|
||||
};
|
||||
|
||||
static getDerivedStateFromError(error: Error): State {
|
||||
return { hasError: true, error };
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||
console.error('ErrorBoundary caught:', error, errorInfo);
|
||||
|
||||
// Send error to monitoring service
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
// logErrorToService(error, errorInfo);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return this.props.fallback || (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-4">
|
||||
Algo salió mal
|
||||
</h1>
|
||||
<p className="text-gray-600 mb-6">
|
||||
Ha ocurrido un error inesperado. Por favor, recarga la página.
|
||||
</p>
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700"
|
||||
>
|
||||
Recargar página
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// src/api/base/circuitBreaker.ts
|
||||
export class CircuitBreaker {
|
||||
private failures: number = 0;
|
||||
private lastFailureTime: number = 0;
|
||||
private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED';
|
||||
|
||||
constructor(
|
||||
private threshold: number = 5,
|
||||
private timeout: number = 60000 // 1 minute
|
||||
) {}
|
||||
|
||||
async execute<T>(fn: () => Promise<T>): Promise<T> {
|
||||
if (this.state === 'OPEN') {
|
||||
if (Date.now() - this.lastFailureTime > this.timeout) {
|
||||
this.state = 'HALF_OPEN';
|
||||
} else {
|
||||
throw new Error('Circuit breaker is OPEN');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await fn();
|
||||
this.onSuccess();
|
||||
return result;
|
||||
} catch (error) {
|
||||
this.onFailure();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private onSuccess(): void {
|
||||
this.failures = 0;
|
||||
this.state = 'CLOSED';
|
||||
}
|
||||
|
||||
private onFailure(): void {
|
||||
this.failures++;
|
||||
this.lastFailureTime = Date.now();
|
||||
|
||||
if (this.failures >= this.threshold) {
|
||||
this.state = 'OPEN';
|
||||
}
|
||||
}
|
||||
|
||||
getState(): string {
|
||||
return this.state;
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
// frontend/dashboard/src/api/cache.ts
|
||||
/**
|
||||
* Simple in-memory cache for API responses
|
||||
*/
|
||||
|
||||
interface CacheEntry<T> {
|
||||
data: T;
|
||||
timestamp: number;
|
||||
ttl: number;
|
||||
}
|
||||
|
||||
class ApiCache {
|
||||
private cache = new Map<string, CacheEntry<any>>();
|
||||
|
||||
set<T>(key: string, data: T, ttl: number = 300000): void { // 5 minutes default
|
||||
this.cache.set(key, {
|
||||
data,
|
||||
timestamp: Date.now(),
|
||||
ttl,
|
||||
});
|
||||
}
|
||||
|
||||
get<T>(key: string): T | null {
|
||||
const entry = this.cache.get(key);
|
||||
|
||||
if (!entry) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Date.now() - entry.timestamp > entry.ttl) {
|
||||
this.cache.delete(key);
|
||||
return null;
|
||||
}
|
||||
|
||||
return entry.data;
|
||||
}
|
||||
|
||||
delete(key: string): void {
|
||||
this.cache.delete(key);
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.cache.clear();
|
||||
}
|
||||
|
||||
has(key: string): boolean {
|
||||
const entry = this.cache.get(key);
|
||||
if (!entry) return false;
|
||||
|
||||
if (Date.now() - entry.timestamp > entry.ttl) {
|
||||
this.cache.delete(key);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export const apiCache = new ApiCache();
|
||||
|
||||
// Cache helper for API client
|
||||
export function withCache<T>(
|
||||
key: string,
|
||||
fetcher: () => Promise<T>,
|
||||
ttl?: number
|
||||
): Promise<T> {
|
||||
const cached = apiCache.get<T>(key);
|
||||
if (cached) {
|
||||
return Promise.resolve(cached);
|
||||
}
|
||||
|
||||
return fetcher().then(data => {
|
||||
apiCache.set(key, data, ttl);
|
||||
return data;
|
||||
});
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
// frontend/dashboard/src/api/config.ts
|
||||
/**
|
||||
* API configuration and constants
|
||||
*/
|
||||
|
||||
export const API_CONFIG = {
|
||||
BASE_URL: process.env.REACT_APP_API_URL || 'http://localhost:8000',
|
||||
TIMEOUT: 10000,
|
||||
RETRY_ATTEMPTS: 3,
|
||||
RETRY_DELAY: 1000,
|
||||
} as const;
|
||||
|
||||
export const ENDPOINTS = {
|
||||
AUTH: {
|
||||
LOGIN: '/auth/login',
|
||||
REGISTER: '/auth/register',
|
||||
LOGOUT: '/auth/logout',
|
||||
REFRESH: '/auth/refresh',
|
||||
PROFILE: '/auth/me',
|
||||
CHANGE_PASSWORD: '/auth/change-password',
|
||||
PASSWORD_RESET: '/auth/password-reset',
|
||||
},
|
||||
TRAINING: {
|
||||
TRAIN: '/training/train',
|
||||
STATUS: '/training/status',
|
||||
JOBS: '/training/jobs',
|
||||
MODELS: '/training/models',
|
||||
PROGRESS_WS: '/training/progress',
|
||||
},
|
||||
FORECASTING: {
|
||||
FORECASTS: '/forecasting/forecasts',
|
||||
GENERATE: '/forecasting/generate',
|
||||
PERFORMANCE: '/forecasting/performance',
|
||||
},
|
||||
DATA: {
|
||||
SALES: '/data/sales',
|
||||
SALES_UPLOAD: '/data/sales/upload',
|
||||
SALES_ANALYTICS: '/data/sales/analytics',
|
||||
WEATHER: '/data/weather',
|
||||
TRAFFIC: '/data/traffic',
|
||||
SYNC: '/data/sync',
|
||||
QUALITY: '/data/quality',
|
||||
},
|
||||
TENANTS: {
|
||||
CURRENT: '/tenants/current',
|
||||
NOTIFICATIONS: '/tenants/notifications',
|
||||
STATS: '/tenants/stats',
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const HTTP_STATUS = {
|
||||
OK: 200,
|
||||
CREATED: 201,
|
||||
NO_CONTENT: 204,
|
||||
BAD_REQUEST: 400,
|
||||
UNAUTHORIZED: 401,
|
||||
FORBIDDEN: 403,
|
||||
NOT_FOUND: 404,
|
||||
CONFLICT: 409,
|
||||
INTERNAL_SERVER_ERROR: 500,
|
||||
SERVICE_UNAVAILABLE: 503,
|
||||
} as const;
|
||||
|
||||
export const STORAGE_KEYS = {
|
||||
ACCESS_TOKEN: 'access_token',
|
||||
REFRESH_TOKEN: 'refresh_token',
|
||||
USER_PROFILE: 'user_profile',
|
||||
THEME: 'theme',
|
||||
LANGUAGE: 'language',
|
||||
} as const;
|
||||
@@ -1,92 +0,0 @@
|
||||
// frontend/dashboard/src/api/hooks/useApi.ts
|
||||
/**
|
||||
* React hooks for API state management
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { ApiError } from '../../types/api';
|
||||
|
||||
export interface ApiState<T> {
|
||||
data: T | null;
|
||||
loading: boolean;
|
||||
error: ApiError | null;
|
||||
}
|
||||
|
||||
export function useApi<T>(
|
||||
apiCall: () => Promise<T>,
|
||||
dependencies: any[] = []
|
||||
): ApiState<T> & {
|
||||
refetch: () => Promise<void>;
|
||||
reset: () => void;
|
||||
} {
|
||||
const [state, setState] = useState<ApiState<T>>({
|
||||
data: null,
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
const execute = useCallback(async () => {
|
||||
setState(prev => ({ ...prev, loading: true, error: null }));
|
||||
|
||||
try {
|
||||
const data = await apiCall();
|
||||
setState({ data, loading: false, error: null });
|
||||
} catch (error) {
|
||||
setState({
|
||||
data: null,
|
||||
loading: false,
|
||||
error: error as ApiError,
|
||||
});
|
||||
}
|
||||
}, dependencies);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setState({ data: null, loading: false, error: null });
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
execute();
|
||||
}, [execute]);
|
||||
|
||||
return {
|
||||
...state,
|
||||
refetch: execute,
|
||||
reset,
|
||||
};
|
||||
}
|
||||
|
||||
export function useAsyncAction<T, P extends any[] = []>(
|
||||
action: (...params: P) => Promise<T>
|
||||
): {
|
||||
execute: (...params: P) => Promise<T>;
|
||||
loading: boolean;
|
||||
error: ApiError | null;
|
||||
reset: () => void;
|
||||
} {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<ApiError | null>(null);
|
||||
|
||||
const execute = useCallback(async (...params: P) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const result = await action(...params);
|
||||
setLoading(false);
|
||||
return result;
|
||||
} catch (err) {
|
||||
const apiError = err as ApiError;
|
||||
setError(apiError);
|
||||
setLoading(false);
|
||||
throw apiError;
|
||||
}
|
||||
}, [action]);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setLoading(false);
|
||||
setError(null);
|
||||
}, []);
|
||||
|
||||
return { execute, loading, error, reset };
|
||||
}
|
||||
|
||||
71
frontend/src/api/hooks/useSessionTimeout.ts
Normal file
71
frontend/src/api/hooks/useSessionTimeout.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
// 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 };
|
||||
};
|
||||
83
frontend/src/api/hooks/useTrainingProgress.ts
Normal file
83
frontend/src/api/hooks/useTrainingProgress.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
// 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 ? `/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/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
|
||||
};
|
||||
};
|
||||
72
frontend/src/api/hooks/useWebSocket.ts
Normal file
72
frontend/src/api/hooks/useWebSocket.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
// 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)
|
||||
};
|
||||
};
|
||||
@@ -1,35 +0,0 @@
|
||||
// frontend/dashboard/src/api/index.ts
|
||||
/**
|
||||
* Main API exports - centralized access to all services
|
||||
*/
|
||||
|
||||
import { ApiClient, apiClient } from './base/apiClient';
|
||||
import { AuthApi } from './services/authApi';
|
||||
import { TrainingApi } from './services/trainingApi';
|
||||
import { ForecastingApi } from './services/forecastingApi';
|
||||
import { SalesApi } from './services/salesApi';
|
||||
import { DataApi } from './services/dataApi';
|
||||
import { TenantApi } from './services/tenantApi';
|
||||
|
||||
// Service instances using the default client
|
||||
export const authApi = new AuthApi(apiClient);
|
||||
export const trainingApi = new TrainingApi(apiClient);
|
||||
export const forecastingApi = new ForecastingApi(apiClient);
|
||||
export const salesApi = new SalesApi(apiClient);
|
||||
export const dataApi = new DataApi(apiClient);
|
||||
export const tenantApi = new TenantApi(apiClient);
|
||||
|
||||
// Export everything for flexibility
|
||||
export * from './base/apiClient';
|
||||
export * from './services/authApi';
|
||||
export * from './services/trainingApi';
|
||||
export * from './services/forecastingApi';
|
||||
export * from './services/salesApi';
|
||||
export * from './services/dataApi';
|
||||
export * from './services/tenantApi';
|
||||
export * from '../types/api';
|
||||
|
||||
// Convenience hooks for React
|
||||
export { useApi } from './hooks/useApi';
|
||||
export { useAuth } from './hooks/useAuth';
|
||||
export { useTraining } from './hooks/useTraining';
|
||||
@@ -1,96 +0,0 @@
|
||||
// frontend/dashboard/src/api/interceptors.ts
|
||||
/**
|
||||
* Request/Response interceptors for additional functionality
|
||||
*/
|
||||
|
||||
import { AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||
import { apiCache } from './cache';
|
||||
|
||||
export function addLoggingInterceptor(client: any) {
|
||||
// Request logging
|
||||
client.interceptors.request.use(
|
||||
(config: AxiosRequestConfig) => {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log(`🚀 API Request: ${config.method?.toUpperCase()} ${config.url}`, {
|
||||
params: config.params,
|
||||
data: config.data,
|
||||
});
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error: any) => {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.error('❌ API Request Error:', error);
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// Response logging
|
||||
client.interceptors.response.use(
|
||||
(response: AxiosResponse) => {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log(`✅ API Response: ${response.config.method?.toUpperCase()} ${response.config.url}`, {
|
||||
status: response.status,
|
||||
data: response.data,
|
||||
});
|
||||
}
|
||||
return response;
|
||||
},
|
||||
(error: any) => {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.error('❌ API Response Error:', {
|
||||
url: error.config?.url,
|
||||
status: error.response?.status,
|
||||
data: error.response?.data,
|
||||
});
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function addCacheInterceptor(client: any) {
|
||||
// Response caching for GET requests
|
||||
client.interceptors.response.use(
|
||||
(response: AxiosResponse) => {
|
||||
const { method, url } = response.config;
|
||||
|
||||
if (method === 'get' && url) {
|
||||
const cacheKey = `${method}:${url}`;
|
||||
apiCache.set(cacheKey, response.data, 300000); // 5 minutes
|
||||
}
|
||||
|
||||
return response;
|
||||
},
|
||||
(error: any) => Promise.reject(error)
|
||||
);
|
||||
}
|
||||
|
||||
export function addRetryInterceptor(client: any, maxRetries: number = 3) {
|
||||
client.interceptors.response.use(
|
||||
(response: AxiosResponse) => response,
|
||||
async (error: any) => {
|
||||
const { config } = error;
|
||||
|
||||
if (!config || config.__retryCount >= maxRetries) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
config.__retryCount = config.__retryCount || 0;
|
||||
config.__retryCount += 1;
|
||||
|
||||
// Only retry on network errors or 5xx errors
|
||||
if (
|
||||
!error.response ||
|
||||
(error.response.status >= 500 && error.response.status < 600)
|
||||
) {
|
||||
const delay = Math.pow(2, config.__retryCount) * 1000; // Exponential backoff
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
return client(config);
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// src/api/auth/authService.ts
|
||||
import { tokenManager } from './tokenManager';
|
||||
import { tokenManager } from '../auth/tokenManager';
|
||||
import { apiClient } from '../base/apiClient';
|
||||
|
||||
export interface LoginCredentials {
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
170
frontend/src/api/types/api.ts
Normal file
170
frontend/src/api/types/api.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
// frontend/dashboard/src/types/api.ts
|
||||
/**
|
||||
* Shared TypeScript interfaces for API communication
|
||||
*/
|
||||
|
||||
// Base response types
|
||||
export interface ApiResponse<T = any> {
|
||||
data?: T;
|
||||
message?: string;
|
||||
status: string;
|
||||
timestamp?: string;
|
||||
}
|
||||
|
||||
export interface ApiError {
|
||||
detail: string;
|
||||
service?: string;
|
||||
error_code?: string;
|
||||
timestamp?: string;
|
||||
}
|
||||
|
||||
// Auth types
|
||||
export interface LoginRequest {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface RegisterRequest {
|
||||
email: string;
|
||||
password: string;
|
||||
full_name: string;
|
||||
phone?: string;
|
||||
language?: string;
|
||||
}
|
||||
|
||||
export interface TokenResponse {
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
token_type: string;
|
||||
expires_in: number;
|
||||
}
|
||||
|
||||
export interface UserProfile {
|
||||
id: string;
|
||||
email: string;
|
||||
full_name: string;
|
||||
is_active: boolean;
|
||||
is_verified: boolean;
|
||||
tenant_id?: string;
|
||||
role: string;
|
||||
phone?: string;
|
||||
language: string;
|
||||
timezone: string;
|
||||
created_at?: string;
|
||||
last_login?: string;
|
||||
}
|
||||
|
||||
// Tenant types
|
||||
export interface TenantInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
address: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
business_type: string;
|
||||
is_active: boolean;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
// Sales types
|
||||
export interface SalesRecord {
|
||||
id: string;
|
||||
tenant_id: string;
|
||||
product_name: string;
|
||||
quantity_sold: number;
|
||||
revenue: number;
|
||||
date: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface CreateSalesRequest {
|
||||
product_name: string;
|
||||
quantity_sold: number;
|
||||
revenue: number;
|
||||
date: string;
|
||||
}
|
||||
|
||||
// Training types
|
||||
export interface TrainingJobStatus {
|
||||
id: string;
|
||||
tenant_id: string;
|
||||
status: 'queued' | 'running' | 'completed' | 'failed' | 'cancelled';
|
||||
progress: number;
|
||||
current_step?: string;
|
||||
started_at: string;
|
||||
completed_at?: string;
|
||||
duration_seconds?: number;
|
||||
models_trained?: Record<string, any>;
|
||||
metrics?: Record<string, any>;
|
||||
error_message?: string;
|
||||
}
|
||||
|
||||
export interface TrainingRequest {
|
||||
force_retrain?: boolean;
|
||||
products?: string[];
|
||||
training_days?: number;
|
||||
}
|
||||
|
||||
export interface TrainedModel {
|
||||
id: string;
|
||||
product_name: string;
|
||||
model_type: string;
|
||||
model_version: string;
|
||||
mape?: number;
|
||||
rmse?: number;
|
||||
mae?: number;
|
||||
r2_score?: number;
|
||||
training_samples?: number;
|
||||
features_used?: string[];
|
||||
is_active: boolean;
|
||||
created_at: string;
|
||||
last_used_at?: string;
|
||||
}
|
||||
|
||||
// Forecast types
|
||||
export interface ForecastRecord {
|
||||
id: string;
|
||||
tenant_id: string;
|
||||
product_name: string;
|
||||
forecast_date: string;
|
||||
predicted_quantity: number;
|
||||
confidence_lower: number;
|
||||
confidence_upper: number;
|
||||
model_version: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface ForecastRequest {
|
||||
product_name?: string;
|
||||
forecast_days?: number;
|
||||
include_confidence?: boolean;
|
||||
}
|
||||
|
||||
// Data types
|
||||
export interface WeatherData {
|
||||
date: string;
|
||||
temperature: number;
|
||||
humidity: number;
|
||||
precipitation: number;
|
||||
wind_speed: number;
|
||||
}
|
||||
|
||||
export interface TrafficData {
|
||||
date: string;
|
||||
traffic_volume: number;
|
||||
pedestrian_count: number;
|
||||
}
|
||||
|
||||
// Notification types
|
||||
export interface NotificationSettings {
|
||||
email_enabled: boolean;
|
||||
whatsapp_enabled: boolean;
|
||||
training_notifications: boolean;
|
||||
forecast_notifications: boolean;
|
||||
alert_thresholds: {
|
||||
low_stock_percentage: number;
|
||||
high_demand_increase: number;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user