diff --git a/python_rewrite/frontend/src/services/mockApi.ts b/python_rewrite/frontend/src/services/mockApi.ts new file mode 100644 index 0000000..5524891 --- /dev/null +++ b/python_rewrite/frontend/src/services/mockApi.ts @@ -0,0 +1,569 @@ +import type { + SystemInfo, + SystemStatus, + Recipe, + RecipeCreate, + RecipeUpdate, + RecipeList, + ProcessStatus, + ProcessStartRequest, + ProcessActionResponse, + HardwareStatus, + SafetyStatus, + User, + UserCreate, + UserUpdate, +} from '../types' + +// Mock data +const mockSystemInfo: SystemInfo = { + name: 'Chocolate Tempering Machine', + version: '2.0.0-mock', + environment: 'mock', + api_version: '1.0.0', + features: { + recipe_management: true, + process_control: true, + hardware_monitoring: false, // Disabled in mock mode + safety_monitoring: true, + user_management: true, + real_time_monitoring: true, + }, + endpoints: { + recipes: '/api/v1/recipes', + process: '/api/v1/process', + hardware: '/api/v1/hardware', + users: '/api/v1/users', + system: '/api/v1/system', + health: '/health', + }, +} + +const mockSystemStatus: SystemStatus = { + system_status: 'warning', // Warning due to mock mode + process_status: 'idle', + hardware_status: 'disconnected', // No hardware in mock mode + safety_status: 'safe', + active_alarms: 0, + uptime_seconds: 3600, + last_updated: new Date().toISOString(), +} + +const mockRecipes: Recipe[] = [ + { + id: 1, + name: 'Dark Chocolate 70%', + description: 'Premium dark chocolate tempering recipe', + heating_temp: 50.0, + cooling_temp: 27.0, + working_temp: 31.0, + heating_time: 300, + cooling_time: 180, + working_time: 600, + mixer_speed: 50, + fountain_delay: 30, + pedal_enabled: true, + is_active: true, + created_at: '2024-01-01T00:00:00Z', + updated_at: '2024-01-01T00:00:00Z', + }, + { + id: 2, + name: 'Milk Chocolate', + description: 'Creamy milk chocolate tempering recipe', + heating_temp: 45.0, + cooling_temp: 26.0, + working_temp: 29.0, + heating_time: 240, + cooling_time: 150, + working_time: 480, + mixer_speed: 45, + fountain_delay: 25, + pedal_enabled: true, + is_active: true, + created_at: '2024-01-02T00:00:00Z', + updated_at: '2024-01-02T00:00:00Z', + }, + { + id: 3, + name: 'White Chocolate', + description: 'Delicate white chocolate tempering recipe', + heating_temp: 43.0, + cooling_temp: 25.0, + working_temp: 28.0, + heating_time: 200, + cooling_time: 120, + working_time: 400, + mixer_speed: 40, + fountain_delay: 20, + pedal_enabled: false, + is_active: true, + created_at: '2024-01-03T00:00:00Z', + updated_at: '2024-01-03T00:00:00Z', + }, +] + +const mockProcessStatus: ProcessStatus = { + is_running: false, + current_phase: 'idle', + recipe_id: null, + recipe_name: null, + progress_percentage: 0, + phase_time_remaining: 0, + total_time_remaining: 0, + current_temperatures: { + tank_bottom: 22.5, + tank_wall: 22.3, + pump: 22.1, + fountain: 22.0, + }, + target_temperatures: { + tank_bottom: 0, + tank_wall: 0, + pump: 0, + fountain: 0, + }, + motor_speeds: { + mixer: 0, + pump: 0, + }, + session_id: null, + started_at: null, + estimated_completion: null, + error: null, +} + +const mockHardwareStatus: HardwareStatus = { + connected: false, + port: '/dev/ttyUSB0', + baud_rate: 9600, + modbus_address: 1, + last_communication: null, + communication_errors: 0, + temperatures: { + tank_bottom: 22.5, + tank_wall: 22.3, + pump: 22.1, + fountain: 22.0, + }, + motors: { + mixer: { running: false, speed: 0, current: 0 }, + pump: { running: false, speed: 0, current: 0 }, + }, + inputs: { + pedal: false, + emergency_stop: false, + door_sensor: true, + }, + outputs: { + heating_element_1: false, + heating_element_2: false, + alarm: false, + fountain_valve: false, + }, +} + +const mockSafetyStatus: SafetyStatus = { + emergency_stop_active: false, + door_open: false, + temperature_alarms: [], + motor_alarms: [], + system_alarms: [], + last_safety_check: new Date().toISOString(), +} + +const mockUsers: User[] = [ + { + id: '1', + username: 'admin', + email: 'admin@example.com', + role: 'admin', + full_name: 'System Administrator', + is_active: true, + created_at: '2024-01-01T00:00:00Z', + last_login: new Date().toISOString(), + }, + { + id: '2', + username: 'operator', + email: 'operator@example.com', + role: 'operator', + full_name: 'Machine Operator', + is_active: true, + created_at: '2024-01-01T00:00:00Z', + last_login: '2024-01-15T08:30:00Z', + }, +] + +// Helper function to simulate API delays +const delay = (ms: number = 500) => new Promise(resolve => setTimeout(resolve, ms)) + +// Mock API service class +class MockApiService { + private currentUser: User = mockUsers[0] + private recipes: Recipe[] = [...mockRecipes] + private processStatus: ProcessStatus = { ...mockProcessStatus } + private hardwareStatus: HardwareStatus = { ...mockHardwareStatus } + + // System endpoints + async getSystemInfo(): Promise { + await delay(300) + return mockSystemInfo + } + + async getSystemStatus(): Promise { + await delay(200) + return mockSystemStatus + } + + async getHealthCheck(): Promise<{ status: string }> { + await delay(100) + return { status: 'healthy' } + } + + // Recipe endpoints + async getRecipes(params?: { + skip?: number + limit?: number + active_only?: boolean + search?: string + }): Promise { + await delay(300) + + let filteredRecipes = [...this.recipes] + + if (params?.active_only) { + filteredRecipes = filteredRecipes.filter(r => r.is_active) + } + + if (params?.search) { + const search = params.search.toLowerCase() + filteredRecipes = filteredRecipes.filter(r => + r.name.toLowerCase().includes(search) || + r.description.toLowerCase().includes(search) + ) + } + + const skip = params?.skip || 0 + const limit = params?.limit || 10 + + return { + recipes: filteredRecipes.slice(skip, skip + limit), + total: filteredRecipes.length, + skip, + limit, + } + } + + async getRecipe(id: number): Promise { + await delay(200) + const recipe = this.recipes.find(r => r.id === id) + if (!recipe) { + throw new Error(`Recipe with id ${id} not found`) + } + return recipe + } + + async createRecipe(recipe: RecipeCreate): Promise { + await delay(500) + const newRecipe: Recipe = { + ...recipe, + id: Math.max(...this.recipes.map(r => r.id)) + 1, + created_at: new Date().toISOString(), + updated_at: new Date().toISOString(), + } + this.recipes.push(newRecipe) + return newRecipe + } + + async updateRecipe(id: number, recipe: RecipeUpdate): Promise { + await delay(500) + const index = this.recipes.findIndex(r => r.id === id) + if (index === -1) { + throw new Error(`Recipe with id ${id} not found`) + } + + this.recipes[index] = { + ...this.recipes[index], + ...recipe, + updated_at: new Date().toISOString(), + } + + return this.recipes[index] + } + + async deleteRecipe(id: number): Promise { + await delay(300) + const index = this.recipes.findIndex(r => r.id === id) + if (index === -1) { + throw new Error(`Recipe with id ${id} not found`) + } + this.recipes.splice(index, 1) + } + + // Process control endpoints + async getProcessStatus(): Promise { + await delay(200) + // Simulate temperature fluctuations if process is running + if (this.processStatus.is_running) { + const temps = this.processStatus.current_temperatures + temps.tank_bottom += (Math.random() - 0.5) * 0.2 + temps.tank_wall += (Math.random() - 0.5) * 0.2 + temps.pump += (Math.random() - 0.5) * 0.1 + temps.fountain += (Math.random() - 0.5) * 0.1 + } + return { ...this.processStatus } + } + + async startProcess(request: ProcessStartRequest): Promise { + await delay(800) + const recipe = this.recipes.find(r => r.id === request.recipe_id) + if (!recipe) { + throw new Error(`Recipe with id ${request.recipe_id} not found`) + } + + this.processStatus = { + ...this.processStatus, + is_running: true, + current_phase: 'heating', + recipe_id: recipe.id, + recipe_name: recipe.name, + progress_percentage: 0, + phase_time_remaining: recipe.heating_time, + total_time_remaining: recipe.heating_time + recipe.cooling_time + recipe.working_time, + target_temperatures: { + tank_bottom: recipe.heating_temp, + tank_wall: recipe.heating_temp, + pump: recipe.heating_temp, + fountain: recipe.heating_temp, + }, + session_id: `mock-session-${Date.now()}`, + started_at: new Date().toISOString(), + estimated_completion: new Date(Date.now() + (recipe.heating_time + recipe.cooling_time + recipe.working_time) * 1000).toISOString(), + } + + return { + success: true, + message: `Started tempering process with recipe: ${recipe.name}`, + session_id: this.processStatus.session_id!, + } + } + + async pauseProcess(): Promise { + await delay(300) + this.processStatus.current_phase = 'paused' + return { + success: true, + message: 'Process paused successfully', + } + } + + async resumeProcess(): Promise { + await delay(300) + this.processStatus.current_phase = 'heating' // or whatever phase it was in + return { + success: true, + message: 'Process resumed successfully', + } + } + + async stopProcess(): Promise { + await delay(500) + this.processStatus = { + ...mockProcessStatus, + session_id: null, + } + return { + success: true, + message: 'Process stopped successfully', + } + } + + async emergencyStop(): Promise { + await delay(100) + this.processStatus = { + ...mockProcessStatus, + session_id: null, + error: 'Emergency stop activated', + } + return { + success: true, + message: 'Emergency stop activated', + } + } + + // Hardware endpoints + async getHardwareStatus(): Promise { + await delay(300) + return { ...this.hardwareStatus } + } + + async getTemperatures(): Promise { + await delay(200) + return [ + { sensor: 'tank_bottom', temperature: this.hardwareStatus.temperatures.tank_bottom, timestamp: new Date().toISOString() }, + { sensor: 'tank_wall', temperature: this.hardwareStatus.temperatures.tank_wall, timestamp: new Date().toISOString() }, + { sensor: 'pump', temperature: this.hardwareStatus.temperatures.pump, timestamp: new Date().toISOString() }, + { sensor: 'fountain', temperature: this.hardwareStatus.temperatures.fountain, timestamp: new Date().toISOString() }, + ] + } + + async getMotorStatus(): Promise { + await delay(200) + return [ + { motor: 'mixer', ...this.hardwareStatus.motors.mixer }, + { motor: 'pump', ...this.hardwareStatus.motors.pump }, + ] + } + + async setMotorSpeed(motorId: string, speed: number): Promise { + await delay(400) + if (motorId in this.hardwareStatus.motors) { + (this.hardwareStatus.motors as any)[motorId].speed = speed + ;(this.hardwareStatus.motors as any)[motorId].running = speed > 0 + } + } + + // Safety endpoints + async getSafetyStatus(): Promise { + await delay(200) + return mockSafetyStatus + } + + async acknowledgeAlarm(alarmId: string): Promise { + await delay(300) + // Mock alarm acknowledgment + } + + async clearAlarms(): Promise { + await delay(500) + // Mock alarm clearing + } + + // User management endpoints + async getUsers(params?: { + skip?: number + limit?: number + active_only?: boolean + }): Promise<{ users: User[]; total: number }> { + await delay(300) + + let filteredUsers = [...mockUsers] + + if (params?.active_only) { + filteredUsers = filteredUsers.filter(u => u.is_active) + } + + const skip = params?.skip || 0 + const limit = params?.limit || 10 + + return { + users: filteredUsers.slice(skip, skip + limit), + total: filteredUsers.length, + } + } + + async getUser(id: string): Promise { + await delay(200) + const user = mockUsers.find(u => u.id === id) + if (!user) { + throw new Error(`User with id ${id} not found`) + } + return user + } + + async createUser(user: UserCreate): Promise { + await delay(500) + const newUser: User = { + ...user, + id: (mockUsers.length + 1).toString(), + created_at: new Date().toISOString(), + last_login: null, + } + mockUsers.push(newUser) + return newUser + } + + async updateUser(id: string, user: UserUpdate): Promise { + await delay(500) + const index = mockUsers.findIndex(u => u.id === id) + if (index === -1) { + throw new Error(`User with id ${id} not found`) + } + + mockUsers[index] = { + ...mockUsers[index], + ...user, + } + + return mockUsers[index] + } + + async deleteUser(id: string): Promise { + await delay(300) + const index = mockUsers.findIndex(u => u.id === id) + if (index === -1) { + throw new Error(`User with id ${id} not found`) + } + mockUsers.splice(index, 1) + } + + // Authentication endpoints + async login(username: string, password: string): Promise<{ access_token: string; token_type: string; user: User }> { + await delay(800) + + // Mock authentication - accept any user from mockUsers with password "password" + const user = mockUsers.find(u => u.username === username) + if (!user || password !== 'password') { + throw new Error('Invalid username or password') + } + + const token = `mock-token-${Date.now()}-${Math.random().toString(36).substr(2, 9)}` + + // Store token + localStorage.setItem('auth_token', token) + + return { + access_token: token, + token_type: 'bearer', + user, + } + } + + async logout(): Promise { + await delay(200) + localStorage.removeItem('auth_token') + } + + async refreshToken(): Promise<{ access_token: string }> { + await delay(300) + const token = `mock-token-${Date.now()}-${Math.random().toString(36).substr(2, 9)}` + localStorage.setItem('auth_token', token) + return { access_token: token } + } + + // Data export endpoints + async exportProcessData(sessionId: string, format: 'csv' | 'json' = 'csv'): Promise { + await delay(1000) + const mockData = format === 'csv' + ? 'timestamp,temperature,phase\n2024-01-01T00:00:00Z,25.0,heating\n2024-01-01T00:01:00Z,26.0,heating' + : JSON.stringify({ session: sessionId, data: [] }) + + return new Blob([mockData], { + type: format === 'csv' ? 'text/csv' : 'application/json' + }) + } + + async getProcessMetrics(days: number = 30): Promise { + await delay(500) + return { + total_sessions: 42, + successful_sessions: 38, + average_duration: 3600, + temperature_stability: 98.5, + efficiency_rating: 94.2, + } + } +} + +export const mockApi = new MockApiService()