feat: Enhance system initialization and mock mode handling with improved UI feedback and error handling
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { Routes, Route, Navigate } from 'react-router-dom'
|
import { Routes, Route, Navigate } from 'react-router-dom'
|
||||||
import { useWebSocket } from './hooks/useWebSocket'
|
import { useWebSocket } from './hooks/useWebSocket'
|
||||||
import { useSystemStore } from './stores/systemStore'
|
import { useSystemStore } from './stores/systemStore'
|
||||||
@@ -19,16 +19,19 @@ import ErrorBoundary from './components/common/ErrorBoundary'
|
|||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const { connect, disconnect, isConnected } = useWebSocket()
|
const { connect, disconnect, isConnected } = useWebSocket()
|
||||||
const { initializeSystem, isInitialized } = useSystemStore()
|
const { initializeSystem, isInitialized, isLoading, error } = useSystemStore()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Initialize the system on app start
|
// Initialize the system on app start (this will handle mock mode automatically)
|
||||||
initializeSystem()
|
initializeSystem()
|
||||||
|
|
||||||
// Connect to WebSocket for real-time updates
|
// Try to connect to WebSocket for real-time updates (optional in mock mode)
|
||||||
connect()
|
const timer = setTimeout(() => {
|
||||||
|
connect()
|
||||||
|
}, 1000) // Small delay to let system initialize first
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
clearTimeout(timer)
|
||||||
disconnect()
|
disconnect()
|
||||||
}
|
}
|
||||||
}, [initializeSystem, connect, disconnect])
|
}, [initializeSystem, connect, disconnect])
|
||||||
@@ -37,13 +40,26 @@ function App() {
|
|||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-100 flex items-center justify-center">
|
<div className="min-h-screen bg-gray-100 flex items-center justify-center">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="animate-spin rounded-full h-16 w-16 border-b-2 border-primary-600 mx-auto mb-4"></div>
|
<div className="animate-spin rounded-full h-16 w-16 border-b-2 border-red-600 mx-auto mb-4"></div>
|
||||||
<h2 className="text-xl font-semibold text-gray-900">
|
<h2 className="text-xl font-semibold text-gray-900">
|
||||||
Initializing Tempering System...
|
{isLoading ? 'Initializing Tempering System...' : 'System Initialization'}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-gray-600 mt-2">
|
<p className="text-gray-600 mt-2">
|
||||||
Please wait while we connect to the hardware
|
{error
|
||||||
|
? `Error: ${error}`
|
||||||
|
: isLoading
|
||||||
|
? 'Connecting to backend services...'
|
||||||
|
: 'Please wait while we set up the system'
|
||||||
|
}
|
||||||
</p>
|
</p>
|
||||||
|
{error && (
|
||||||
|
<button
|
||||||
|
onClick={() => initializeSystem()}
|
||||||
|
className="mt-4 px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700"
|
||||||
|
>
|
||||||
|
Retry Connection
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { useSystemStore } from '../../stores/systemStore'
|
||||||
|
|
||||||
|
export function MockModeIndicator() {
|
||||||
|
const { systemInfo, isConnected } = useSystemStore()
|
||||||
|
|
||||||
|
const isMockMode = systemInfo?.environment === 'mock' || !isConnected
|
||||||
|
|
||||||
|
if (!isMockMode) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-yellow-100 border-l-4 border-yellow-500 p-3 mb-4">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div className="flex-shrink-0">
|
||||||
|
<span className="text-yellow-600 text-lg">⚠️</span>
|
||||||
|
</div>
|
||||||
|
<div className="ml-3">
|
||||||
|
<p className="text-sm text-yellow-700">
|
||||||
|
<strong>Demo Mode:</strong> Running without hardware connection.
|
||||||
|
All data is simulated for testing purposes.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import React, { ReactNode } from 'react'
|
|||||||
import Header from './Header'
|
import Header from './Header'
|
||||||
import Sidebar from './Sidebar'
|
import Sidebar from './Sidebar'
|
||||||
import StatusBar from './StatusBar'
|
import StatusBar from './StatusBar'
|
||||||
|
import { MockModeIndicator } from '../common/MockModeIndicator'
|
||||||
|
|
||||||
interface LayoutProps {
|
interface LayoutProps {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
@@ -21,6 +22,7 @@ const Layout: React.FC<LayoutProps> = ({ children, isConnected }) => {
|
|||||||
{/* Main content area */}
|
{/* Main content area */}
|
||||||
<main className="flex-1 overflow-auto p-4">
|
<main className="flex-1 overflow-auto p-4">
|
||||||
<div className="max-w-none">
|
<div className="max-w-none">
|
||||||
|
<MockModeIndicator />
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export const useWebSocket = (): UseWebSocketReturn => {
|
|||||||
const maxReconnectAttempts = 10
|
const maxReconnectAttempts = 10
|
||||||
|
|
||||||
// Store actions
|
// Store actions
|
||||||
const { updateSystemStatus, updateConnectionStatus, setError } = useSystemStore()
|
const { updateSystemStatus, updateConnectionStatus } = useSystemStore()
|
||||||
|
|
||||||
// Get WebSocket URL from environment
|
// Get WebSocket URL from environment
|
||||||
const getSocketUrl = useCallback(() => {
|
const getSocketUrl = useCallback(() => {
|
||||||
@@ -31,8 +31,8 @@ export const useWebSocket = (): UseWebSocketReturn => {
|
|||||||
// Handle reconnection with exponential backoff
|
// Handle reconnection with exponential backoff
|
||||||
const scheduleReconnect = useCallback(() => {
|
const scheduleReconnect = useCallback(() => {
|
||||||
if (reconnectAttempts.current >= maxReconnectAttempts) {
|
if (reconnectAttempts.current >= maxReconnectAttempts) {
|
||||||
console.error('Max reconnection attempts reached')
|
console.warn('Max WebSocket reconnection attempts reached - continuing in mock mode')
|
||||||
setError('Lost connection to server. Please refresh the page.')
|
// Don't set error, just stop trying to reconnect
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ export const useWebSocket = (): UseWebSocketReturn => {
|
|||||||
reconnectTimeoutRef.current = setTimeout(() => {
|
reconnectTimeoutRef.current = setTimeout(() => {
|
||||||
connect()
|
connect()
|
||||||
}, delay)
|
}, delay)
|
||||||
}, [setError])
|
}, [])
|
||||||
|
|
||||||
// Connect to WebSocket
|
// Connect to WebSocket
|
||||||
const connect = useCallback(() => {
|
const connect = useCallback(() => {
|
||||||
@@ -143,8 +143,9 @@ export const useWebSocket = (): UseWebSocketReturn => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
} else if (data.overall_status === 'warning') {
|
} else if (data.overall_status === 'warning') {
|
||||||
toast.warning('Safety warning', {
|
toast('Safety warning', {
|
||||||
duration: 5000,
|
duration: 5000,
|
||||||
|
icon: '⚠️',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import axios, { AxiosResponse, AxiosError } from 'axios'
|
import axios, { AxiosResponse, AxiosError } from 'axios'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
|
import { mockApi } from './mockApi'
|
||||||
import type {
|
import type {
|
||||||
SystemInfo,
|
SystemInfo,
|
||||||
SystemStatus,
|
SystemStatus,
|
||||||
@@ -19,6 +20,15 @@ import type {
|
|||||||
ApiError,
|
ApiError,
|
||||||
} from '../types'
|
} from '../types'
|
||||||
|
|
||||||
|
// Global flag to track if we should use mock mode
|
||||||
|
let useMockMode = false
|
||||||
|
let mockModeDetected = false
|
||||||
|
|
||||||
|
// Check if we should use mock mode
|
||||||
|
const shouldUseMockMode = () => {
|
||||||
|
return useMockMode || import.meta.env.VITE_USE_MOCK_API === 'true'
|
||||||
|
}
|
||||||
|
|
||||||
// Create axios instance with default configuration
|
// Create axios instance with default configuration
|
||||||
const apiClient = axios.create({
|
const apiClient = axios.create({
|
||||||
baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000',
|
baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000',
|
||||||
@@ -81,6 +91,40 @@ apiClient.interceptors.response.use(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Helper function to handle API responses with mock fallback
|
||||||
|
const handleApiCall = async <T>(
|
||||||
|
apiCall: () => Promise<T>,
|
||||||
|
mockCall: () => Promise<T>,
|
||||||
|
operationName: string
|
||||||
|
): Promise<T> => {
|
||||||
|
if (shouldUseMockMode()) {
|
||||||
|
console.info(`[MOCK MODE] Using mock API for ${operationName}`)
|
||||||
|
return await mockCall()
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await apiCall()
|
||||||
|
} catch (error) {
|
||||||
|
// If we get a connection error, switch to mock mode
|
||||||
|
if (error instanceof AxiosError && (!error.response || error.code === 'ECONNREFUSED' || error.code === 'ERR_NETWORK')) {
|
||||||
|
if (!mockModeDetected) {
|
||||||
|
console.warn('Backend unavailable, switching to mock mode')
|
||||||
|
toast.error('Hardware backend unavailable - running in demo mode', {
|
||||||
|
duration: 5000,
|
||||||
|
icon: '⚠️',
|
||||||
|
})
|
||||||
|
mockModeDetected = true
|
||||||
|
useMockMode = true
|
||||||
|
}
|
||||||
|
console.info(`[MOCK MODE] Backend unavailable, using mock API for ${operationName}`)
|
||||||
|
return await mockCall()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-throw other errors
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Helper function to handle API responses
|
// Helper function to handle API responses
|
||||||
const handleResponse = <T>(response: AxiosResponse<T>): T => {
|
const handleResponse = <T>(response: AxiosResponse<T>): T => {
|
||||||
return response.data
|
return response.data
|
||||||
@@ -90,18 +134,36 @@ const handleResponse = <T>(response: AxiosResponse<T>): T => {
|
|||||||
class ApiService {
|
class ApiService {
|
||||||
// System endpoints
|
// System endpoints
|
||||||
async getSystemInfo(): Promise<SystemInfo> {
|
async getSystemInfo(): Promise<SystemInfo> {
|
||||||
const response = await apiClient.get('/api/v1/info')
|
return handleApiCall(
|
||||||
return handleResponse(response)
|
async () => {
|
||||||
|
const response = await apiClient.get('/api/v1/info')
|
||||||
|
return handleResponse(response)
|
||||||
|
},
|
||||||
|
() => mockApi.getSystemInfo(),
|
||||||
|
'getSystemInfo'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSystemStatus(): Promise<SystemStatus> {
|
async getSystemStatus(): Promise<SystemStatus> {
|
||||||
const response = await apiClient.get('/api/v1/system/status')
|
return handleApiCall(
|
||||||
return handleResponse(response)
|
async () => {
|
||||||
|
const response = await apiClient.get('/api/v1/system/status')
|
||||||
|
return handleResponse(response)
|
||||||
|
},
|
||||||
|
() => mockApi.getSystemStatus(),
|
||||||
|
'getSystemStatus'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async getHealthCheck(): Promise<{ status: string }> {
|
async getHealthCheck(): Promise<{ status: string }> {
|
||||||
const response = await apiClient.get('/health')
|
return handleApiCall(
|
||||||
return handleResponse(response)
|
async () => {
|
||||||
|
const response = await apiClient.get('/health')
|
||||||
|
return handleResponse(response)
|
||||||
|
},
|
||||||
|
() => mockApi.getHealthCheck(),
|
||||||
|
'getHealthCheck'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recipe endpoints
|
// Recipe endpoints
|
||||||
@@ -111,58 +173,124 @@ class ApiService {
|
|||||||
active_only?: boolean
|
active_only?: boolean
|
||||||
search?: string
|
search?: string
|
||||||
}): Promise<RecipeList> {
|
}): Promise<RecipeList> {
|
||||||
const response = await apiClient.get('/api/v1/recipes', { params })
|
return handleApiCall(
|
||||||
return handleResponse(response)
|
async () => {
|
||||||
|
const response = await apiClient.get('/api/v1/recipes', { params })
|
||||||
|
return handleResponse(response)
|
||||||
|
},
|
||||||
|
() => mockApi.getRecipes(params),
|
||||||
|
'getRecipes'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRecipe(id: number): Promise<Recipe> {
|
async getRecipe(id: number): Promise<Recipe> {
|
||||||
const response = await apiClient.get(`/api/v1/recipes/${id}`)
|
return handleApiCall(
|
||||||
return handleResponse(response)
|
async () => {
|
||||||
|
const response = await apiClient.get(`/api/v1/recipes/${id}`)
|
||||||
|
return handleResponse(response)
|
||||||
|
},
|
||||||
|
() => mockApi.getRecipe(id),
|
||||||
|
'getRecipe'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async createRecipe(recipe: RecipeCreate): Promise<Recipe> {
|
async createRecipe(recipe: RecipeCreate): Promise<Recipe> {
|
||||||
const response = await apiClient.post('/api/v1/recipes', recipe)
|
return handleApiCall(
|
||||||
return handleResponse(response)
|
async () => {
|
||||||
|
const response = await apiClient.post('/api/v1/recipes', recipe)
|
||||||
|
return handleResponse(response)
|
||||||
|
},
|
||||||
|
() => mockApi.createRecipe(recipe),
|
||||||
|
'createRecipe'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateRecipe(id: number, recipe: RecipeUpdate): Promise<Recipe> {
|
async updateRecipe(id: number, recipe: RecipeUpdate): Promise<Recipe> {
|
||||||
const response = await apiClient.put(`/api/v1/recipes/${id}`, recipe)
|
return handleApiCall(
|
||||||
return handleResponse(response)
|
async () => {
|
||||||
|
const response = await apiClient.put(`/api/v1/recipes/${id}`, recipe)
|
||||||
|
return handleResponse(response)
|
||||||
|
},
|
||||||
|
() => mockApi.updateRecipe(id, recipe),
|
||||||
|
'updateRecipe'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteRecipe(id: number): Promise<void> {
|
async deleteRecipe(id: number): Promise<void> {
|
||||||
await apiClient.delete(`/api/v1/recipes/${id}`)
|
return handleApiCall(
|
||||||
|
async () => {
|
||||||
|
await apiClient.delete(`/api/v1/recipes/${id}`)
|
||||||
|
},
|
||||||
|
() => mockApi.deleteRecipe(id),
|
||||||
|
'deleteRecipe'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process control endpoints
|
// Process control endpoints
|
||||||
async getProcessStatus(): Promise<ProcessStatus> {
|
async getProcessStatus(): Promise<ProcessStatus> {
|
||||||
const response = await apiClient.get('/api/v1/process/status')
|
return handleApiCall(
|
||||||
return handleResponse(response)
|
async () => {
|
||||||
|
const response = await apiClient.get('/api/v1/process/status')
|
||||||
|
return handleResponse(response)
|
||||||
|
},
|
||||||
|
() => mockApi.getProcessStatus(),
|
||||||
|
'getProcessStatus'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async startProcess(request: ProcessStartRequest): Promise<ProcessActionResponse> {
|
async startProcess(request: ProcessStartRequest): Promise<ProcessActionResponse> {
|
||||||
const response = await apiClient.post('/api/v1/process/start', request)
|
return handleApiCall(
|
||||||
return handleResponse(response)
|
async () => {
|
||||||
|
const response = await apiClient.post('/api/v1/process/start', request)
|
||||||
|
return handleResponse(response)
|
||||||
|
},
|
||||||
|
() => mockApi.startProcess(request),
|
||||||
|
'startProcess'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async pauseProcess(): Promise<ProcessActionResponse> {
|
async pauseProcess(): Promise<ProcessActionResponse> {
|
||||||
const response = await apiClient.post('/api/v1/process/pause')
|
return handleApiCall(
|
||||||
return handleResponse(response)
|
async () => {
|
||||||
|
const response = await apiClient.post('/api/v1/process/pause')
|
||||||
|
return handleResponse(response)
|
||||||
|
},
|
||||||
|
() => mockApi.pauseProcess(),
|
||||||
|
'pauseProcess'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async resumeProcess(): Promise<ProcessActionResponse> {
|
async resumeProcess(): Promise<ProcessActionResponse> {
|
||||||
const response = await apiClient.post('/api/v1/process/resume')
|
return handleApiCall(
|
||||||
return handleResponse(response)
|
async () => {
|
||||||
|
const response = await apiClient.post('/api/v1/process/resume')
|
||||||
|
return handleResponse(response)
|
||||||
|
},
|
||||||
|
() => mockApi.resumeProcess(),
|
||||||
|
'resumeProcess'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async stopProcess(): Promise<ProcessActionResponse> {
|
async stopProcess(): Promise<ProcessActionResponse> {
|
||||||
const response = await apiClient.post('/api/v1/process/stop')
|
return handleApiCall(
|
||||||
return handleResponse(response)
|
async () => {
|
||||||
|
const response = await apiClient.post('/api/v1/process/stop')
|
||||||
|
return handleResponse(response)
|
||||||
|
},
|
||||||
|
() => mockApi.stopProcess(),
|
||||||
|
'stopProcess'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async emergencyStop(): Promise<ProcessActionResponse> {
|
async emergencyStop(): Promise<ProcessActionResponse> {
|
||||||
const response = await apiClient.post('/api/v1/process/emergency-stop')
|
return handleApiCall(
|
||||||
return handleResponse(response)
|
async () => {
|
||||||
|
const response = await apiClient.post('/api/v1/process/emergency-stop')
|
||||||
|
return handleResponse(response)
|
||||||
|
},
|
||||||
|
() => mockApi.emergencyStop(),
|
||||||
|
'emergencyStop'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hardware endpoints
|
// Hardware endpoints
|
||||||
|
|||||||
@@ -204,7 +204,6 @@ const delay = (ms: number = 500) => new Promise(resolve => setTimeout(resolve, m
|
|||||||
|
|
||||||
// Mock API service class
|
// Mock API service class
|
||||||
class MockApiService {
|
class MockApiService {
|
||||||
private currentUser: User = mockUsers[0]
|
|
||||||
private recipes: Recipe[] = [...mockRecipes]
|
private recipes: Recipe[] = [...mockRecipes]
|
||||||
private processStatus: ProcessStatus = { ...mockProcessStatus }
|
private processStatus: ProcessStatus = { ...mockProcessStatus }
|
||||||
private hardwareStatus: HardwareStatus = { ...mockHardwareStatus }
|
private hardwareStatus: HardwareStatus = { ...mockHardwareStatus }
|
||||||
@@ -244,7 +243,7 @@ class MockApiService {
|
|||||||
const search = params.search.toLowerCase()
|
const search = params.search.toLowerCase()
|
||||||
filteredRecipes = filteredRecipes.filter(r =>
|
filteredRecipes = filteredRecipes.filter(r =>
|
||||||
r.name.toLowerCase().includes(search) ||
|
r.name.toLowerCase().includes(search) ||
|
||||||
r.description.toLowerCase().includes(search)
|
(r.description && r.description.toLowerCase().includes(search))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,6 +272,7 @@ class MockApiService {
|
|||||||
const newRecipe: Recipe = {
|
const newRecipe: Recipe = {
|
||||||
...recipe,
|
...recipe,
|
||||||
id: Math.max(...this.recipes.map(r => r.id)) + 1,
|
id: Math.max(...this.recipes.map(r => r.id)) + 1,
|
||||||
|
is_active: true,
|
||||||
created_at: new Date().toISOString(),
|
created_at: new Date().toISOString(),
|
||||||
updated_at: new Date().toISOString(),
|
updated_at: new Date().toISOString(),
|
||||||
}
|
}
|
||||||
@@ -309,12 +309,8 @@ class MockApiService {
|
|||||||
async getProcessStatus(): Promise<ProcessStatus> {
|
async getProcessStatus(): Promise<ProcessStatus> {
|
||||||
await delay(200)
|
await delay(200)
|
||||||
// Simulate temperature fluctuations if process is running
|
// Simulate temperature fluctuations if process is running
|
||||||
if (this.processStatus.is_running) {
|
if (this.processStatus.is_running && this.processStatus.current_temperature !== null) {
|
||||||
const temps = this.processStatus.current_temperatures
|
this.processStatus.current_temperature += (Math.random() - 0.5) * 0.2
|
||||||
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 }
|
return { ...this.processStatus }
|
||||||
}
|
}
|
||||||
@@ -329,21 +325,15 @@ class MockApiService {
|
|||||||
this.processStatus = {
|
this.processStatus = {
|
||||||
...this.processStatus,
|
...this.processStatus,
|
||||||
is_running: true,
|
is_running: true,
|
||||||
|
status: 'running',
|
||||||
current_phase: 'heating',
|
current_phase: 'heating',
|
||||||
recipe_id: recipe.id,
|
recipe_id: recipe.id,
|
||||||
recipe_name: recipe.name,
|
recipe_name: recipe.name,
|
||||||
progress_percentage: 0,
|
target_temperature: recipe.target_temperature_c,
|
||||||
phase_time_remaining: recipe.heating_time,
|
duration_seconds: 0,
|
||||||
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()}`,
|
session_id: `mock-session-${Date.now()}`,
|
||||||
started_at: new Date().toISOString(),
|
started_at: new Date().toISOString(),
|
||||||
estimated_completion: new Date(Date.now() + (recipe.heating_time + recipe.cooling_time + recipe.working_time) * 1000).toISOString(),
|
started_by: request.user_id || 'mock-user',
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -355,7 +345,7 @@ class MockApiService {
|
|||||||
|
|
||||||
async pauseProcess(): Promise<ProcessActionResponse> {
|
async pauseProcess(): Promise<ProcessActionResponse> {
|
||||||
await delay(300)
|
await delay(300)
|
||||||
this.processStatus.current_phase = 'paused'
|
this.processStatus.status = 'paused'
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Process paused successfully',
|
message: 'Process paused successfully',
|
||||||
@@ -364,7 +354,7 @@ class MockApiService {
|
|||||||
|
|
||||||
async resumeProcess(): Promise<ProcessActionResponse> {
|
async resumeProcess(): Promise<ProcessActionResponse> {
|
||||||
await delay(300)
|
await delay(300)
|
||||||
this.processStatus.current_phase = 'heating' // or whatever phase it was in
|
this.processStatus.status = 'running'
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Process resumed successfully',
|
message: 'Process resumed successfully',
|
||||||
@@ -388,7 +378,7 @@ class MockApiService {
|
|||||||
this.processStatus = {
|
this.processStatus = {
|
||||||
...mockProcessStatus,
|
...mockProcessStatus,
|
||||||
session_id: null,
|
session_id: null,
|
||||||
error: 'Emergency stop activated',
|
status: 'error',
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
@@ -404,27 +394,33 @@ class MockApiService {
|
|||||||
|
|
||||||
async getTemperatures(): Promise<any[]> {
|
async getTemperatures(): Promise<any[]> {
|
||||||
await delay(200)
|
await delay(200)
|
||||||
return [
|
return this.hardwareStatus.temperature_sensors.map(sensor => ({
|
||||||
{ sensor: 'tank_bottom', temperature: this.hardwareStatus.temperatures.tank_bottom, timestamp: new Date().toISOString() },
|
sensor: sensor.id,
|
||||||
{ sensor: 'tank_wall', temperature: this.hardwareStatus.temperatures.tank_wall, timestamp: new Date().toISOString() },
|
temperature: sensor.current_temp_c,
|
||||||
{ sensor: 'pump', temperature: this.hardwareStatus.temperatures.pump, timestamp: new Date().toISOString() },
|
timestamp: new Date().toISOString(),
|
||||||
{ sensor: 'fountain', temperature: this.hardwareStatus.temperatures.fountain, timestamp: new Date().toISOString() },
|
}))
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMotorStatus(): Promise<any[]> {
|
async getMotorStatus(): Promise<any[]> {
|
||||||
await delay(200)
|
await delay(200)
|
||||||
return [
|
return this.hardwareStatus.motors.map(motor => ({
|
||||||
{ motor: 'mixer', ...this.hardwareStatus.motors.mixer },
|
motor: motor.id,
|
||||||
{ motor: 'pump', ...this.hardwareStatus.motors.pump },
|
name: motor.name,
|
||||||
]
|
is_running: motor.is_running,
|
||||||
|
current_speed_rpm: motor.current_speed_rpm,
|
||||||
|
target_speed_rpm: motor.target_speed_rpm,
|
||||||
|
current_amps: motor.current_amps,
|
||||||
|
status: motor.status,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
async setMotorSpeed(motorId: string, speed: number): Promise<void> {
|
async setMotorSpeed(motorId: string, speed: number): Promise<void> {
|
||||||
await delay(400)
|
await delay(400)
|
||||||
if (motorId in this.hardwareStatus.motors) {
|
const motor = this.hardwareStatus.motors.find(m => m.id === motorId)
|
||||||
(this.hardwareStatus.motors as any)[motorId].speed = speed
|
if (motor) {
|
||||||
;(this.hardwareStatus.motors as any)[motorId].running = speed > 0
|
motor.target_speed_rpm = speed
|
||||||
|
motor.current_speed_rpm = speed
|
||||||
|
motor.is_running = speed > 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -434,7 +430,7 @@ class MockApiService {
|
|||||||
return mockSafetyStatus
|
return mockSafetyStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
async acknowledgeAlarm(alarmId: string): Promise<void> {
|
async acknowledgeAlarm(_alarmId: string): Promise<void> {
|
||||||
await delay(300)
|
await delay(300)
|
||||||
// Mock alarm acknowledgment
|
// Mock alarm acknowledgment
|
||||||
}
|
}
|
||||||
@@ -481,8 +477,9 @@ class MockApiService {
|
|||||||
const newUser: User = {
|
const newUser: User = {
|
||||||
...user,
|
...user,
|
||||||
id: (mockUsers.length + 1).toString(),
|
id: (mockUsers.length + 1).toString(),
|
||||||
|
is_active: true,
|
||||||
created_at: new Date().toISOString(),
|
created_at: new Date().toISOString(),
|
||||||
last_login: null,
|
last_login: undefined,
|
||||||
}
|
}
|
||||||
mockUsers.push(newUser)
|
mockUsers.push(newUser)
|
||||||
return newUser
|
return newUser
|
||||||
@@ -558,7 +555,7 @@ class MockApiService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProcessMetrics(days: number = 30): Promise<any> {
|
async getProcessMetrics(_days: number = 30): Promise<any> {
|
||||||
await delay(500)
|
await delay(500)
|
||||||
return {
|
return {
|
||||||
total_sessions: 42,
|
total_sessions: 42,
|
||||||
|
|||||||
@@ -33,29 +33,46 @@ export const useSystemStore = create<SystemState>()(
|
|||||||
isLoading: false,
|
isLoading: false,
|
||||||
error: null,
|
error: null,
|
||||||
|
|
||||||
// Initialize system - called on app startup
|
// Initialize system - called on app startup
|
||||||
initializeSystem: async () => {
|
initializeSystem: async () => {
|
||||||
const { isInitialized } = get()
|
const { isInitialized } = get()
|
||||||
if (isInitialized) return
|
if (isInitialized) return
|
||||||
|
|
||||||
|
console.log('Starting system initialization...')
|
||||||
set({ isLoading: true, error: null })
|
set({ isLoading: true, error: null })
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Fetch system information
|
// Fetch system information
|
||||||
|
console.log('Fetching system info...')
|
||||||
const systemInfo = await api.getSystemInfo()
|
const systemInfo = await api.getSystemInfo()
|
||||||
|
console.log('System info received:', systemInfo)
|
||||||
|
|
||||||
// Fetch initial system status
|
// Fetch initial system status
|
||||||
|
console.log('Fetching system status...')
|
||||||
const systemStatus = await api.getSystemStatus()
|
const systemStatus = await api.getSystemStatus()
|
||||||
|
console.log('System status received:', systemStatus)
|
||||||
|
|
||||||
|
// Check if we're in mock mode based on the system info
|
||||||
|
const isMockMode = systemInfo.environment === 'mock' || systemStatus.hardware_status === 'disconnected'
|
||||||
|
console.log('Mock mode detected:', isMockMode)
|
||||||
|
|
||||||
set({
|
set({
|
||||||
systemInfo,
|
systemInfo,
|
||||||
systemStatus,
|
systemStatus,
|
||||||
isInitialized: true,
|
isInitialized: true,
|
||||||
|
isConnected: !isMockMode, // Set connection status based on mock mode
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
error: null,
|
error: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Show mock mode warning if applicable
|
||||||
|
if (isMockMode && systemInfo.environment === 'mock') {
|
||||||
|
console.info('✅ Running in mock mode - no hardware required')
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('✅ System initialization completed successfully')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to initialize system:', error)
|
console.error('❌ Failed to initialize system:', error)
|
||||||
set({
|
set({
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
error: error instanceof Error ? error.message : 'Failed to initialize system',
|
error: error instanceof Error ? error.message : 'Failed to initialize system',
|
||||||
|
|||||||
Reference in New Issue
Block a user