diff --git a/server/mock-socket-server.js b/server/mock-socket-server.js index 8512172..909f060 100644 --- a/server/mock-socket-server.js +++ b/server/mock-socket-server.js @@ -101,6 +101,14 @@ io.on('connection', (socket) => { socket.emit('sensorData', { temperature: initialData.temperature, humidity: initialData.humidity }); socket.emit('powerStatus', { powerCut: initialData.powerCut }); + // Send initial system startup log + socket.emit('systemLog', { + type: 'success', + message: 'System initialized successfully', + source: 'system', + timestamp: new Date() + }); + // intervals object to clear later const intervals = {}; diff --git a/src/components/EnergyCharts.jsx b/src/components/EnergyCharts.jsx index 2e1c8a9..c4038c2 100644 --- a/src/components/EnergyCharts.jsx +++ b/src/components/EnergyCharts.jsx @@ -62,8 +62,7 @@ const EnergyCharts = () => { return (
- - {/* Real-time Charts */} + {/* Real-time Energy Consumption */} { +const StatusCard = ({ title, value: propValue, subtitle: propSubtitle, icon, color: propColor, onClick }) => { const { state } = useApp(); const getStatusColor = () => { @@ -38,6 +38,11 @@ const StatusCard = ({ title, value, subtitle, icon, color, onClick }) => { const warningLevel = getWarningLevel(); + // Build derived values without mutating props + let value = propValue; + let subtitle = propSubtitle; + let color = propColor || 'status-offline'; + if (title === 'Inverter Status') { value = `${getStatusIcon()} ${state.inverterStatus.toUpperCase()}`; color = getStatusColor(); @@ -77,11 +82,25 @@ const StatusCard = ({ title, value, subtitle, icon, color, onClick }) => { } } + if (title === 'Energy Usage') { + value = `${state.energyConsumption}W`; + if (state.energyConsumption > 500) { + subtitle = 'High consumption'; + color = 'status-warning-battery'; + } else if (state.energyConsumption > 300) { + subtitle = 'Moderate consumption'; + color = 'status-standby'; + } else { + subtitle = 'Low consumption'; + color = 'status-online'; + } + } + return ( { @@ -89,10 +97,28 @@ export function SocketProvider({ children }) { newSocket.on('inverterStatus', (data) => { dispatch({ type: 'UPDATE_INVERTER_STATUS', payload: data.status }); + dispatch({ + type: 'ADD_LOG', + payload: { + type: 'info', + message: `Inverter status changed to ${data.status}`, + source: 'inverter' + } + }); }); newSocket.on('powerStatus', (data) => { dispatch({ type: 'UPDATE_POWER_STATUS', payload: data.powerCut }); + const logType = data.powerCut ? 'warning' : 'success'; + const logMessage = data.powerCut ? 'Power cut detected! Running on inverter' : 'Grid power restored'; + dispatch({ + type: 'ADD_LOG', + payload: { + type: logType, + message: logMessage, + source: 'power' + } + }); if (data.powerCut) { addNotification({ type: 'warning', @@ -104,14 +130,65 @@ export function SocketProvider({ children }) { newSocket.on('batteryUpdate', (data) => { dispatch({ type: 'UPDATE_BATTERY_LEVEL', payload: data.level }); + let logType = 'info'; + let logMessage = `Battery level updated to ${data.level}%`; + + if (data.level <= 20) { + logType = 'error'; + logMessage = `Critical battery level: ${data.level}%`; + } else if (data.level <= 30) { + logType = 'warning'; + logMessage = `Low battery warning: ${data.level}%`; + } + + dispatch({ + type: 'ADD_LOG', + payload: { + type: logType, + message: logMessage, + source: 'battery' + } + }); }); newSocket.on('energyUpdate', (data) => { dispatch({ type: 'UPDATE_ENERGY_DATA', payload: data.consumption }); + dispatch({ + type: 'ADD_LOG', + payload: { + type: 'info', + message: `Energy consumption: ${data.consumption}W`, + source: 'energy' + } + }); }); newSocket.on('sensorData', (data) => { dispatch({ type: 'UPDATE_SENSOR_DATA', payload: data }); + let logType = 'info'; + let logMessage = `Temperature: ${data.temperature}Β°C, Humidity: ${data.humidity}%`; + + if (data.temperature > 60) { + logType = 'error'; + logMessage = `Critical temperature warning: ${data.temperature}Β°C`; + } else if (data.temperature > 50) { + logType = 'warning'; + logMessage = `High temperature: ${data.temperature}Β°C`; + } + + dispatch({ + type: 'ADD_LOG', + payload: { + type: logType, + message: logMessage, + source: 'sensor' + } + }); + }); + + // Listen for direct log events from server + newSocket.on('systemLog', (logData) => { + dispatch({ type: 'ADD_LOG', payload: logData }); }); // Surface server-side notifications and command acknowledgements diff --git a/src/pages/Dashboard.jsx b/src/pages/Dashboard.jsx index 5075456..46c6d45 100644 --- a/src/pages/Dashboard.jsx +++ b/src/pages/Dashboard.jsx @@ -133,34 +133,10 @@ const Dashboard = () => { role="region" aria-label="System status cards" > - - - - + + + + {/* Energy Usage Card - Separate row for better mobile layout */} @@ -172,41 +148,9 @@ const Dashboard = () => { role="region" aria-label="Energy usage information" > - - - - {/* Utility Functions Display Cards */} - - - diff --git a/src/pages/Logs.jsx b/src/pages/Logs.jsx index 3d8a669..27d3eea 100644 --- a/src/pages/Logs.jsx +++ b/src/pages/Logs.jsx @@ -1,62 +1,48 @@ -import React, { useState } from 'react'; +import React, { useState, useMemo, useCallback } from 'react'; import { motion } from 'framer-motion'; import { formatTime, formatDate } from '../utils/format'; +import { useApp } from '../context/AppContext'; const Logs = () => { + const { state, dispatch } = useApp(); const [filter, setFilter] = useState('all'); + const [sourceFilter, setSourceFilter] = useState('all'); const [searchTerm, setSearchTerm] = useState(''); + const [isAutoScroll, setIsAutoScroll] = useState(true); - // Mock logs data - const mockLogs = [ - { - id: 1, - type: 'info', - message: 'System started successfully', - timestamp: new Date(Date.now() - 3600000), - source: 'system' - }, - { - id: 2, - type: 'warning', - message: 'Power cut detected, switching to inverter', - timestamp: new Date(Date.now() - 3000000), - source: 'power' - }, - { - id: 3, - type: 'error', - message: 'Battery level critical (15%)', - timestamp: new Date(Date.now() - 2400000), - source: 'battery' - }, - { - id: 4, - type: 'info', - message: 'Grid power restored', - timestamp: new Date(Date.now() - 1800000), - source: 'power' - }, - { - id: 5, - type: 'success', - message: 'Battery charging started', - timestamp: new Date(Date.now() - 1200000), - source: 'battery' - }, - { - id: 6, - type: 'info', - message: 'Scheduled maintenance check completed', - timestamp: new Date(Date.now() - 600000), - source: 'system' - } - ]; + // Real-time logs from state + const logs = state.logs || []; + + const filteredLogs = useMemo(() => { + return logs.filter(log => { + const matchesFilter = filter === 'all' || log.type === filter; + const matchesSource = sourceFilter === 'all' || log.source === sourceFilter; + const matchesSearch = log.message.toLowerCase().includes(searchTerm.toLowerCase()); + return matchesFilter && matchesSource && matchesSearch; + }); + }, [logs, filter, sourceFilter, searchTerm]); - const filteredLogs = mockLogs.filter(log => { - const matchesFilter = filter === 'all' || log.type === filter; - const matchesSearch = log.message.toLowerCase().includes(searchTerm.toLowerCase()); - return matchesFilter && matchesSearch; - }); + const clearLogs = useCallback(() => { + dispatch({ type: 'CLEAR_LOGS' }); + }, [dispatch]); + + const exportLogs = useCallback(() => { + const logsData = filteredLogs.map(log => ({ + timestamp: log.timestamp, + type: log.type, + source: log.source, + message: log.message + })); + + const dataStr = JSON.stringify(logsData, null, 2); + const dataBlob = new Blob([dataStr], { type: 'application/json' }); + const url = URL.createObjectURL(dataBlob); + const link = document.createElement('a'); + link.href = url; + link.download = `system-logs-${new Date().toISOString().split('T')[0]}.json`; + link.click(); + URL.revokeObjectURL(url); + }, [filteredLogs]); const getLogColor = (type) => { switch (type) { @@ -78,6 +64,26 @@ const Logs = () => { } }; + const getSeverityBadge = (type) => { + switch (type) { + case 'error': return 'HIGH'; + case 'warning': return 'MED'; + case 'success': return 'LOW'; + case 'info': return 'LOW'; + default: return 'LOW'; + } + }; + + const getSeverityColor = (type) => { + switch (type) { + case 'error': return 'bg-red-600 text-white'; + case 'warning': return 'bg-orange-500 text-white'; + default: return 'bg-green-500 text-white'; + } + }; + + const uniqueSources = [...new Set(logs.map(log => log.source))]; + return (
{ animate={{ opacity: 1, y: 0 }} className="mb-8" > -

System Logs

-

Detailed history of system events and activities

+
+
+

Real-Time System Logs

+

Live monitoring of system events and activities

+
+ πŸ“Š Total Logs: {logs.length} + πŸ” Filtered: {filteredLogs.length} + + πŸ”„ + Auto-scroll: {isAutoScroll ? 'ON' : 'OFF'} + +
+
+
+ + + +
+
- {/* Filters and Search */} + {/* Enhanced Filters and Search */} -
-
- {['all', 'info', 'success', 'warning', 'error'].map((type) => ( +
+ {/* Log Type Filters */} +
+ +
+ {['all', 'info', 'success', 'warning', 'error'].map((type) => ( + + ))} +
+
+ + {/* Source Filters */} +
+ +
- ))} + {uniqueSources.map((source) => ( + + ))} +
-
+ + {/* Search */} +
+ setSearchTerm(e.target.value)} - className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" + className="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" />
- {/* Logs List */} + {/* Real-Time Logs List */} -
+
+
+

Live Log Stream

+
+
+ Live +
+
+
+ +
- + + @@ -153,11 +253,16 @@ const Logs = () => { {filteredLogs.map((log, index) => ( +
+ Severity + Type + + {getSeverityBadge(log.type)} + + {getLogIcon(log.type)} @@ -165,10 +270,14 @@ const Logs = () => { -
{log.message}
+
+ {log.message} +
- {log.source} + + {log.source} +
{formatTime(log.timestamp)}
@@ -183,8 +292,18 @@ const Logs = () => { {filteredLogs.length === 0 && (
πŸ“‹
-

No logs found

-

Try changing your filters or search term

+

+ {logs.length === 0 + ? 'No logs yet - start the socket server to see real-time events' + : 'No logs match your filters' + } +

+

+ {logs.length === 0 + ? 'Run: node server/mock-socket-server.js' + : 'Try changing your filters or search term' + } +

)}