Spaces:
Running
Running
import React, { useState, useEffect } from 'react'; | |
import './ChargingHub.css'; | |
// Mock data for demonstration purposes | |
const mockChargingPorts = [ | |
{ id: 1, number: 1, status: 'available', type: 'DC Fast', power: '250kW', lastUsed: null }, | |
{ id: 2, number: 2, status: 'charging', type: 'DC Fast', power: '250kW', vehicle: 'Tesla Model 3', startTime: '14:30', endTime: '15:15', progress: 68 }, | |
{ id: 3, number: 3, status: 'reserved', type: 'DC Fast', power: '250kW', reservedFor: '17:00', duration: '45 min' }, | |
{ id: 4, number: 4, status: 'maintenance', type: 'DC Fast', power: '250kW', maintenanceNote: 'Scheduled maintenance', estimatedCompletion: '18:00' }, | |
{ id: 5, number: 5, status: 'available', type: 'DC Fast', power: '250kW', lastUsed: '13:45' }, | |
{ id: 6, number: 6, status: 'charging', type: 'DC Fast', power: '250kW', vehicle: 'Tesla Model Y', startTime: '15:10', endTime: '15:55', progress: 42 }, | |
{ id: 7, number: 7, status: 'available', type: 'DC Fast', power: '250kW', lastUsed: '12:30' }, | |
{ id: 8, number: 8, status: 'charging', type: 'DC Fast', power: '250kW', vehicle: 'GMC Hummer EV', startTime: '15:25', endTime: '16:10', progress: 25 }, | |
]; | |
const mockEnergyData = { | |
currentDemand: 625, | |
peakDemand: 1200, | |
solarGeneration: 85, | |
batteryStorage: 72, | |
gridConsumption: 540, | |
dailyUsage: [ | |
{ hour: '00:00', demand: 120 }, | |
{ hour: '01:00', demand: 90 }, | |
{ hour: '02:00', demand: 75 }, | |
{ hour: '03:00', demand: 60 }, | |
{ hour: '04:00', demand: 45 }, | |
{ hour: '05:00', demand: 60 }, | |
{ hour: '06:00', demand: 150 }, | |
{ hour: '07:00', demand: 300 }, | |
{ hour: '08:00', demand: 450 }, | |
{ hour: '09:00', demand: 600 }, | |
{ hour: '10:00', demand: 750 }, | |
{ hour: '11:00', demand: 900 }, | |
{ hour: '12:00', demand: 1050 }, | |
{ hour: '13:00', demand: 1150 }, | |
{ hour: '14:00', demand: 1200 }, | |
{ hour: '15:00', demand: 1100 }, | |
{ hour: '16:00', demand: 950 }, | |
{ hour: '17:00', demand: 800 }, | |
{ hour: '18:00', demand: 700 }, | |
{ hour: '19:00', demand: 600 }, | |
{ hour: '20:00', demand: 500 }, | |
{ hour: '21:00', demand: 400 }, | |
{ hour: '22:00', demand: 300 }, | |
{ hour: '23:00', demand: 200 }, | |
], | |
}; | |
const mockVisitors = { | |
current: 24, | |
capacity: 50, | |
averageDuration: 38, | |
peakHours: '12:00 - 14:00', | |
amenityUsage: { | |
lounge: 65, | |
cafe: 48, | |
retail: 32, | |
workspace: 25, | |
}, | |
}; | |
const mockReservations = [ | |
{ id: 101, name: 'John Smith', vehicle: 'Tesla Model 3', time: '17:00', duration: '45 min', port: 3, status: 'confirmed' }, | |
{ id: 102, name: 'Sarah Johnson', vehicle: 'Tesla Model Y', time: '17:30', duration: '30 min', port: 1, status: 'confirmed' }, | |
{ id: 103, name: 'Michael Brown', vehicle: 'Ford F-150 Lightning', time: '18:15', duration: '60 min', port: 5, status: 'pending' }, | |
{ id: 104, name: 'Emily Davis', vehicle: 'Rivian R1T', time: '19:00', duration: '45 min', port: 7, status: 'confirmed' }, | |
]; | |
function ChargingHub() { | |
const [activeTab, setActiveTab] = useState('dashboard'); | |
const [loading, setLoading] = useState(true); | |
const [currentTime, setCurrentTime] = useState(''); | |
const [hubStatus, setHubStatus] = useState({ | |
name: 'The Link - Downtown', | |
location: 'Springfield, IL', | |
status: 'operational', | |
occupancy: '48%', | |
availablePorts: 3, | |
totalPorts: 8, | |
energyEfficiency: '92%', | |
}); | |
useEffect(() => { | |
// Simulate loading | |
const timer = setTimeout(() => { | |
setLoading(false); | |
}, 1500); | |
// Update current time | |
const updateTime = () => { | |
const now = new Date(); | |
const hours = now.getHours().toString().padStart(2, '0'); | |
const minutes = now.getMinutes().toString().padStart(2, '0'); | |
setCurrentTime(`${hours}:${minutes}`); | |
}; | |
updateTime(); | |
const timeInterval = setInterval(updateTime, 60000); | |
return () => { | |
clearTimeout(timer); | |
clearInterval(timeInterval); | |
}; | |
}, []); | |
// Function to render status badge with appropriate color | |
const renderStatusBadge = (status) => { | |
let className = 'status-badge'; | |
switch(status) { | |
case 'available': | |
className += ' available'; | |
break; | |
case 'charging': | |
className += ' charging'; | |
break; | |
case 'reserved': | |
className += ' reserved'; | |
break; | |
case 'maintenance': | |
className += ' maintenance'; | |
break; | |
default: | |
break; | |
} | |
return <span className={className}>{status}</span>; | |
}; | |
// Function to render progress bar for charging ports | |
const renderProgressBar = (progress) => { | |
return ( | |
<div className="progress-container"> | |
<div className="progress-bar" style={{ width: `${progress}%` }}> | |
<div className="progress-glow"></div> | |
</div> | |
<span className="progress-text">{progress}%</span> | |
</div> | |
); | |
}; | |
if (loading) { | |
return ( | |
<div className="charging-hub loading"> | |
<div className="loading-logo"> | |
<img src="/images/the_link_logo.png" alt="The Link" /> | |
<div className="loading-pulse"></div> | |
</div> | |
<p>Initializing Hub Dashboard...</p> | |
</div> | |
); | |
} | |
return ( | |
<div className="charging-hub"> | |
<header className="hub-header"> | |
<div className="hub-title"> | |
<h1>THE LINK</h1> | |
<span className="hub-subtitle">Charging Hub Dashboard</span> | |
</div> | |
<div className="hub-status"> | |
<div className="status-item"> | |
<span className="status-label">Hub Status:</span> | |
<span className={`status-value ${hubStatus.status}`}>{hubStatus.status}</span> | |
</div> | |
<div className="status-item"> | |
<span className="status-label">Current Time:</span> | |
<span className="status-value">{currentTime}</span> | |
</div> | |
</div> | |
<div className="hub-actions"> | |
<button className="action-button"> | |
<span className="icon">⚙️</span> | |
<span>Settings</span> | |
</button> | |
<button className="action-button"> | |
<span className="icon">🔔</span> | |
<span>Alerts</span> | |
</button> | |
<div className="user-profile"> | |
<img src="/images/admin_avatar.png" alt="Admin" /> | |
</div> | |
</div> | |
</header> | |
<div className="hub-navigation"> | |
<button | |
className={`nav-button ${activeTab === 'dashboard' ? 'active' : ''}`} | |
onClick={() => setActiveTab('dashboard')} | |
> | |
Dashboard | |
</button> | |
<button | |
className={`nav-button ${activeTab === 'charging' ? 'active' : ''}`} | |
onClick={() => setActiveTab('charging')} | |
> | |
Charging Ports | |
</button> | |
<button | |
className={`nav-button ${activeTab === 'energy' ? 'active' : ''}`} | |
onClick={() => setActiveTab('energy')} | |
> | |
Energy Management | |
</button> | |
<button | |
className={`nav-button ${activeTab === 'visitors' ? 'active' : ''}`} | |
onClick={() => setActiveTab('visitors')} | |
> | |
Visitor Analytics | |
</button> | |
<button | |
className={`nav-button ${activeTab === 'reservations' ? 'active' : ''}`} | |
onClick={() => setActiveTab('reservations')} | |
> | |
Reservations | |
</button> | |
</div> | |
<main className="hub-content"> | |
{activeTab === 'dashboard' && ( | |
<div className="dashboard-view"> | |
<div className="hub-overview"> | |
<div className="overview-card location"> | |
<h3>Hub Location</h3> | |
<div className="location-details"> | |
<div className="location-name">{hubStatus.name}</div> | |
<div className="location-address">{hubStatus.location}</div> | |
</div> | |
<div className="location-map"> | |
<img src="/images/hub_location_map.png" alt="Hub Location" /> | |
</div> | |
</div> | |
<div className="overview-card occupancy"> | |
<h3>Current Occupancy</h3> | |
<div className="occupancy-gauge"> | |
<svg viewBox="0 0 120 120" className="gauge"> | |
<circle cx="60" cy="60" r="50" className="gauge-background" /> | |
<circle | |
cx="60" | |
cy="60" | |
r="50" | |
className="gauge-progress" | |
style={{ | |
strokeDasharray: `${parseInt(hubStatus.occupancy) * 3.14}px, 314px` | |
}} | |
/> | |
<text x="60" y="65" textAnchor="middle" className="gauge-text"> | |
{hubStatus.occupancy} | |
</text> | |
</svg> | |
</div> | |
<div className="occupancy-details"> | |
<div className="detail-item"> | |
<span className="detail-label">Available Ports:</span> | |
<span className="detail-value">{hubStatus.availablePorts}/{hubStatus.totalPorts}</span> | |
</div> | |
</div> | |
</div> | |
<div className="overview-card energy"> | |
<h3>Energy Overview</h3> | |
<div className="energy-metrics"> | |
<div className="metric"> | |
<div className="metric-value">{mockEnergyData.currentDemand} kW</div> | |
<div className="metric-label">Current Demand</div> | |
</div> | |
<div className="metric"> | |
<div className="metric-value">{mockEnergyData.solarGeneration}%</div> | |
<div className="metric-label">Solar Generation</div> | |
</div> | |
<div className="metric"> | |
<div className="metric-value">{mockEnergyData.batteryStorage}%</div> | |
<div className="metric-label">Battery Storage</div> | |
</div> | |
</div> | |
<div className="energy-chart"> | |
<div className="chart-container"> | |
{/* Simplified chart representation */} | |
<div className="chart-bars"> | |
{mockEnergyData.dailyUsage.slice(-8).map((hour, index) => ( | |
<div | |
key={index} | |
className="chart-bar" | |
style={{ height: `${(hour.demand / mockEnergyData.peakDemand) * 100}%` }} | |
> | |
<div className="bar-glow"></div> | |
</div> | |
))} | |
</div> | |
</div> | |
</div> | |
</div> | |
<div className="overview-card visitors"> | |
<h3>Visitor Analytics</h3> | |
<div className="visitor-count"> | |
<div className="count-value">{mockVisitors.current}</div> | |
<div className="count-label">Current Visitors</div> | |
</div> | |
<div className="visitor-details"> | |
<div className="detail-item"> | |
<span className="detail-label">Capacity:</span> | |
<span className="detail-value">{mockVisitors.capacity}</span> | |
</div> | |
<div className="detail-item"> | |
<span className="detail-label">Avg. Duration:</span> | |
<span className="detail-value">{mockVisitors.averageDuration} min</span> | |
</div> | |
<div className="detail-item"> | |
<span className="detail-label">Peak Hours:</span> | |
<span className="detail-value">{mockVisitors.peakHours}</span> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div className="quick-status"> | |
<h3>Charging Port Status</h3> | |
<div className="port-status-grid"> | |
{mockChargingPorts.map(port => ( | |
<div key={port.id} className={`port-status-card ${port.status}`}> | |
<div className="port-number">{port.number}</div> | |
<div className="port-status">{renderStatusBadge(port.status)}</div> | |
{port.status === 'charging' && ( | |
<div className="port-progress">{renderProgressBar(port.progress)}</div> | |
)} | |
</div> | |
))} | |
</div> | |
</div> | |
<div className="upcoming-reservations"> | |
<h3>Upcoming Reservations</h3> | |
<div className="reservation-list"> | |
{mockReservations.map(reservation => ( | |
<div key={reservation.id} className="reservation-card"> | |
<div className="reservation-time">{reservation.time}</div> | |
<div className="reservation-details"> | |
<div className="reservation-name">{reservation.name}</div> | |
<div className="reservation-vehicle">{reservation.vehicle}</div> | |
</div> | |
<div className="reservation-port">Port {reservation.port}</div> | |
<div className="reservation-duration">{reservation.duration}</div> | |
<div className={`reservation-status ${reservation.status}`}> | |
{reservation.status} | |
</div> | |
</div> | |
))} | |
</div> | |
</div> | |
</div> | |
)} | |
{activeTab === 'charging' && ( | |
<div className="charging-view"> | |
<div className="section-header"> | |
<h2>Charging Port Management</h2> | |
<div className="header-actions"> | |
<button className="action-button"> | |
<span className="icon">+</span> | |
<span>Add Port</span> | |
</button> | |
<div className="filter-dropdown"> | |
<select> | |
<option value="all">All Statuses</option> | |
<option value="available">Available</option> | |
<option value="charging">Charging</option> | |
<option value="reserved">Reserved</option> | |
<option value="maintenance">Maintenance</option> | |
</select> | |
</div> | |
</div> | |
</div> | |
<div className="charging-ports-grid"> | |
{mockChargingPorts.map(port => ( | |
<div key={port.id} className={`port-card ${port.status}`}> | |
<div className="port-header"> | |
<div className="port-number">Port {port.number}</div> | |
<div className="port-status">{renderStatusBadge(port.status)}</div> | |
</div> | |
<div className="port-details"> | |
<div className="detail-item"> | |
<span className="detail-label">Type:</span> | |
<span className="detail-value">{port.type}</span> | |
</div> | |
<div className="detail-item"> | |
<span className="detail-label">Power:</span> | |
<span className="detail-value">{port.power}</span> | |
</div> | |
{port.status === 'available' && ( | |
<div className="detail-item"> | |
<span className="detail-label">Last Used:</span> | |
<span className="detail-value">{port.lastUsed || 'N/A'}</span> | |
</div> | |
)} | |
{port.status === 'charging' && ( | |
<> | |
<div className="detail-item"> | |
<span className="detail-label">Vehicle:</span> | |
<span className="detail-value">{port.vehicle}</span> | |
</div> | |
<div className="detail-item"> | |
<span className="detail-label">Started:</span> | |
<span className="detail-value">{port.startTime}</span> | |
</div> | |
<div className="detail-item"> | |
<span className="detail-label">Est. Completion:</span> | |
<span className="detail-value">{port.endTime}</span> | |
</div> | |
<div className="detail-item full-width"> | |
<div className="progress-label">Charging Progress</div> | |
{renderProgressBar(port.progress)} | |
</div> | |
</> | |
)} | |
{port.status === 'reserved' && ( | |
<> | |
<div className="detail-item"> | |
<span className="detail-label">Reserved For:</span> | |
<span className="detail-value">{port.reservedFor}</span> | |
</div> | |
<div className="detail-item"> | |
<span className="detail-label">Duration:</span> | |
<span className="detail-value">{port.duration}</span> | |
</div> | |
</> | |
)} | |
{port.status === 'maintenance' && ( | |
<> | |
<div className="detail-item"> | |
<span className="detail-label">Note:</span> | |
<span className="detail-value">{port.maintenanceNote}</span> | |
</div> | |
<div className="detail-item"> | |
<span className="detail-label">Est. Completion:</span> | |
<span className="detail-value">{port.estimatedCompletion}</span> | |
</div> | |
</> | |
)} | |
</div> | |
<div className="port-actions"> | |
{port.status === 'available' && ( | |
<> | |
<button className="port-action reserve">Reserve</button> | |
<button className="port-action maintenance">Set Maintenance</button> | |
</> | |
)} | |
{port.status === 'charging' && ( | |
<> | |
<button className="port-action stop">Stop Charging</button> | |
<button className="port-action details">View Details</button> | |
</> | |
)} | |
{port.status === 'reserved' && ( | |
<> | |
<button className="port-action cancel">Cancel Reservation</button> | |
<button className="port-action edit">Edit Reservation</button> | |
</> | |
)} | |
{port.status === 'maintenance' && ( | |
<> | |
<button className="port-action complete">Complete Maintenance</button> | |
<button className="port-action update">Update Status</button> | |
</> | |
)} | |
</div> | |
</div> | |
))} | |
</div> | |
</div> | |
)} | |
{activeTab === 'energy' && ( | |
<div className="energy-view"> | |
<div className="section-header"> | |
<h2>Energy Management System</h2> | |
<div className="header-actions"> | |
<button className="action-button"> | |
<span className="icon">📊</span> | |
<span>Export Data</span> | |
</button> | |
<div className="time-range-selector"> | |
<select> | |
<option value="day">Today</option> | |
<option value="week">This Week</option> | |
<option value="month">This Month</option> | |
<option value="custom">Custom Range</option> | |
</select> | |
</div> | |
</div> | |
</div> | |
<div className="energy-overview"> | |
<div className="energy-card current-demand"> | |
<h3>Current Demand</h3> | |
<div className="energy-value">{mockEnergyData.currentDemand} kW</div> | |
<div className="energy-chart"> | |
<div className="chart-container"> | |
{/* Simplified chart representation */} | |
<div className="chart-line"> | |
<svg viewBox="0 0 100 30"> | |
<path | |
d="M0,25 L10,20 L20,22 L30,15 L40,18 L50,10 L60,12 L70,8 L80,15 L90,5 L100,10" | |
className="chart-path" | |
/> | |
</svg> | |
</div> | |
</div> | |
</div> | |
<div className="energy-details"> | |
<div className="detail-item"> | |
<span className="detail-label">Peak Today:</span> | |
<span className="detail-value">{mockEnergyData.peakDemand} kW</span> | |
</div> | |
<div className="detail-item"> | |
<span className="detail-label">Average:</span> | |
<span className="detail-value">780 kW</span> | |
</div> | |
</div> | |
</div> | |
<div className="energy-card energy-sources"> | |
<h3>Energy Sources</h3> | |
<div className="sources-chart"> | |
<div className="donut-chart"> | |
<svg viewBox="0 0 100 100"> | |
<circle cx="50" cy="50" r="40" className="donut-ring" /> | |
<circle | |
cx="50" | |
cy="50" | |
r="40" | |
className="donut-segment grid" | |
style={{ | |
strokeDasharray: `${mockEnergyData.gridConsumption / 10} 251.2` | |
}} | |
/> | |
<circle | |
cx="50" | |
cy="50" | |
r="40" | |
className="donut-segment solar" | |
style={{ | |
strokeDasharray: `${mockEnergyData.solarGeneration / 10} 251.2`, | |
strokeDashoffset: `-${mockEnergyData.gridConsumption / 10}` | |
}} | |
/> | |
<circle | |
cx="50" | |
cy="50" | |
r="40" | |
className="donut-segment battery" | |
style={{ | |
strokeDasharray: `${mockEnergyData.batteryStorage / 10} 251.2`, | |
strokeDashoffset: `-${(mockEnergyData.gridConsumption + mockEnergyData.solarGeneration) / 10}` | |
}} | |
/> | |
</svg> | |
</div> | |
</div> | |
<div className="sources-legend"> | |
<div className="legend-item grid"> | |
<span className="legend-color"></span> | |
<span className="legend-label">Grid</span> | |
<span className="legend-value">{mockEnergyData.gridConsumption} kW</span> | |
</div> | |
<div className="legend-item solar"> | |
<span className="legend-color"></span> | |
<span className="legend-label">Solar</span> | |
<span className="legend-value">{mockEnergyData.solarGeneration}%</span> | |
</div> | |
<div className="legend-item battery"> | |
<span className="legend-color"></span> | |
<span className="legend-label">Battery</span> | |
<span className="legend-value">{mockEnergyData.batteryStorage}%</span> | |
</div> | |
</div> | |
</div> | |
<div className="energy-card efficiency"> | |
<h3>System Efficiency</h3> | |
<div className="efficiency-gauge"> | |
<svg viewBox="0 0 120 120" className="gauge"> | |
<circle cx="60" cy="60" r="50" className="gauge-background" /> | |
<circle | |
cx="60" | |
cy="60" | |
r="50" | |
className="gauge-progress efficiency" | |
style={{ | |
strokeDasharray: `${parseInt(hubStatus.energyEfficiency) * 3.14}px, 314px` | |
}} | |
/> | |
<text x="60" y="65" textAnchor="middle" className="gauge-text"> | |
{hubStatus.energyEfficiency} | |
</text> | |
</svg> | |
</div> | |
<div className="efficiency-details"> | |
<div className="detail-item"> | |
<span className="detail-label">Power Factor:</span> | |
<span className="detail-value">0.98</span> | |
</div> | |
<div className="detail-item"> | |
<span className="detail-label">Conversion Loss:</span> | |
<span className="detail-value">8%</span> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div className="energy-daily-chart"> | |
<h3>24-Hour Energy Demand</h3> | |
<div className="chart-container"> | |
<div className="chart-y-axis"> | |
<div className="axis-label">1200 kW</div> | |
<div className="axis-label">900 kW</div> | |
<div className="axis-label">600 kW</div> | |
<div className="axis-label">300 kW</div> | |
<div className="axis-label">0 kW</div> | |
</div> | |
<div className="chart-content"> | |
<div className="chart-bars"> | |
{mockEnergyData.dailyUsage.map((hour, index) => ( | |
<div key={index} className="chart-bar-container"> | |
<div | |
className="chart-bar" | |
style={{ height: `${(hour.demand / mockEnergyData.peakDemand) * 100}%` }} | |
> | |
<div className="bar-glow"></div> | |
</div> | |
<div className="bar-label">{hour.hour}</div> | |
</div> | |
))} | |
</div> | |
</div> | |
</div> | |
</div> | |
<div className="energy-controls"> | |
<h3>Energy Management Controls</h3> | |
<div className="controls-grid"> | |
<div className="control-card"> | |
<h4>Load Balancing</h4> | |
<div className="control-status"> | |
<span className="status-indicator active"></span> | |
<span className="status-text">Active</span> | |
</div> | |
<div className="control-actions"> | |
<button className="control-action">Configure</button> | |
<button className="control-action">Disable</button> | |
</div> | |
</div> | |
<div className="control-card"> | |
<h4>Peak Shaving</h4> | |
<div className="control-status"> | |
<span className="status-indicator active"></span> | |
<span className="status-text">Active</span> | |
</div> | |
<div className="control-actions"> | |
<button className="control-action">Configure</button> | |
<button className="control-action">Disable</button> | |
</div> | |
</div> | |
<div className="control-card"> | |
<h4>Battery Storage</h4> | |
<div className="control-status"> | |
<span className="status-indicator active"></span> | |
<span className="status-text">Discharging</span> | |
</div> | |
<div className="control-actions"> | |
<button className="control-action">Switch to Charging</button> | |
<button className="control-action">Set to Auto</button> | |
</div> | |
</div> | |
<div className="control-card"> | |
<h4>Grid Connection</h4> | |
<div className="control-status"> | |
<span className="status-indicator active"></span> | |
<span className="status-text">Connected</span> | |
</div> | |
<div className="control-actions"> | |
<button className="control-action">Emergency Disconnect</button> | |
<button className="control-action">Test Backup</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
)} | |
{/* Additional tabs would be implemented here */} | |
</main> | |
</div> | |
); | |
} | |
export default ChargingHub; | |