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) => (
+
+ ))}
+
-
- {/* Logs List */}
+ {/* Real-Time Logs List */}
-
+
+
+
-
+
+ |
+ Severity
+ |
Type
|
@@ -153,11 +253,16 @@ const Logs = () => {
{filteredLogs.map((log, index) => (
+
+
+ {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'
+ }
+
)}
|