Spaces:
Running
Running
import React, { useState, useEffect } from 'react'; | |
import './DeploymentMap.css'; | |
// Mock data for demonstration purposes | |
const mockDeploymentData = { | |
phases: [ | |
{ | |
id: 1, | |
name: "Phase 1", | |
timeline: "Q3 2025 - Q1 2026", | |
status: "planning", | |
locations: [ | |
{ id: 101, name: "Springfield Downtown", lat: 39.801, lng: -89.644, status: "planned", type: "urban", capacity: 12 }, | |
{ id: 102, name: "Champaign-Urbana Hub", lat: 40.116, lng: -88.243, status: "planned", type: "urban", capacity: 8 }, | |
{ id: 103, name: "Bloomington-Normal", lat: 40.484, lng: -88.993, status: "planned", type: "urban", capacity: 8 }, | |
{ id: 104, name: "Peoria Central", lat: 40.694, lng: -89.589, status: "planned", type: "urban", capacity: 8 } | |
] | |
}, | |
{ | |
id: 2, | |
name: "Phase 2", | |
timeline: "Q2 2026 - Q4 2026", | |
status: "future", | |
locations: [ | |
{ id: 201, name: "Rockford Hub", lat: 42.271, lng: -89.093, status: "future", type: "urban", capacity: 8 }, | |
{ id: 202, name: "Quad Cities", lat: 41.512, lng: -90.578, status: "future", type: "urban", capacity: 8 }, | |
{ id: 203, name: "Carbondale", lat: 37.727, lng: -89.216, status: "future", type: "urban", capacity: 6 }, | |
{ id: 204, name: "Quincy", lat: 39.936, lng: -91.410, status: "future", type: "urban", capacity: 6 }, | |
{ id: 205, name: "I-55 Corridor North", lat: 41.097, lng: -88.841, status: "future", type: "corridor", capacity: 4 }, | |
{ id: 206, name: "I-55 Corridor South", lat: 39.144, lng: -89.479, status: "future", type: "corridor", capacity: 4 } | |
] | |
}, | |
{ | |
id: 3, | |
name: "Phase 3", | |
timeline: "Q1 2027 - Q3 2027", | |
status: "future", | |
locations: [ | |
{ id: 301, name: "Decatur", lat: 39.840, lng: -88.954, status: "future", type: "urban", capacity: 6 }, | |
{ id: 302, name: "Danville", lat: 40.124, lng: -87.630, status: "future", type: "urban", capacity: 6 }, | |
{ id: 303, name: "Galesburg", lat: 40.947, lng: -90.371, status: "future", type: "urban", capacity: 4 }, | |
{ id: 304, name: "Mt. Vernon", lat: 38.317, lng: -88.903, status: "future", type: "urban", capacity: 4 }, | |
{ id: 305, name: "I-57 Corridor North", lat: 41.201, lng: -87.866, status: "future", type: "corridor", capacity: 4 }, | |
{ id: 306, name: "I-57 Corridor South", lat: 38.729, lng: -88.978, status: "future", type: "corridor", capacity: 4 }, | |
{ id: 307, name: "I-70 Corridor", lat: 39.123, lng: -88.542, status: "future", type: "corridor", capacity: 4 }, | |
{ id: 308, name: "I-74 Corridor", lat: 40.681, lng: -89.893, status: "future", type: "corridor", capacity: 4 } | |
] | |
} | |
], | |
demographics: { | |
population: [ | |
{ county: "Cook", population: 5150233, evAdoption: 2.8, chargingStations: 487 }, | |
{ county: "DuPage", population: 922921, evAdoption: 3.2, chargingStations: 112 }, | |
{ county: "Lake", population: 696535, evAdoption: 2.9, chargingStations: 78 }, | |
{ county: "Will", population: 677560, evAdoption: 2.1, chargingStations: 54 }, | |
{ county: "Kane", population: 532403, evAdoption: 2.3, chargingStations: 42 }, | |
{ county: "McHenry", population: 307774, evAdoption: 1.9, chargingStations: 28 }, | |
{ county: "Winnebago", population: 285350, evAdoption: 1.2, chargingStations: 18 }, | |
{ county: "Madison", population: 264461, evAdoption: 0.9, chargingStations: 14 }, | |
{ county: "St. Clair", population: 259686, evAdoption: 0.7, chargingStations: 12 }, | |
{ county: "Champaign", population: 205865, evAdoption: 1.8, chargingStations: 22 } | |
], | |
evProjections: [ | |
{ year: 2025, percentage: 3.5 }, | |
{ year: 2026, percentage: 5.2 }, | |
{ year: 2027, percentage: 7.8 }, | |
{ year: 2028, percentage: 11.5 }, | |
{ year: 2029, percentage: 16.3 }, | |
{ year: 2030, percentage: 22.0 } | |
] | |
}, | |
impactMetrics: { | |
economic: { | |
jobsCreated: 450, | |
localBusinessImpact: 28, | |
taxRevenue: 3.2 | |
}, | |
environmental: { | |
co2Reduction: 12500, | |
gasDisplaced: 1.4, | |
renewableIntegration: 65 | |
}, | |
social: { | |
underservedCommunities: 14, | |
accessibilityScore: 72, | |
publicTransportIntegration: 8 | |
} | |
} | |
}; | |
function DeploymentMap() { | |
const [activeTab, setActiveTab] = useState('map'); | |
const [loading, setLoading] = useState(true); | |
const [selectedPhase, setSelectedPhase] = useState(1); | |
const [mapView, setMapView] = useState('phases'); | |
const [selectedLocation, setSelectedLocation] = useState(null); | |
const [mapZoom, setMapZoom] = useState(1); | |
useEffect(() => { | |
// Simulate loading | |
const timer = setTimeout(() => { | |
setLoading(false); | |
}, 1500); | |
return () => clearTimeout(timer); | |
}, []); | |
const handlePhaseChange = (phaseId) => { | |
setSelectedPhase(phaseId); | |
setSelectedLocation(null); | |
}; | |
const handleMapViewChange = (view) => { | |
setMapView(view); | |
setSelectedLocation(null); | |
}; | |
const handleLocationSelect = (location) => { | |
setSelectedLocation(location); | |
setMapZoom(2); | |
}; | |
const handleZoomChange = (zoom) => { | |
setMapZoom(zoom); | |
if (zoom === 1) { | |
setSelectedLocation(null); | |
} | |
}; | |
// Helper function to render the Illinois map with deployment locations | |
const renderMap = () => { | |
const currentPhase = mockDeploymentData.phases.find(phase => phase.id === selectedPhase); | |
const allLocations = mockDeploymentData.phases.flatMap(phase => phase.locations); | |
const displayLocations = mapView === 'phases' ? currentPhase.locations : allLocations; | |
return ( | |
<div className="map-container"> | |
<div className="illinois-map"> | |
<img src="/images/illinois_map.png" alt="Illinois Map" className="map-image" /> | |
{displayLocations.map(location => ( | |
<div | |
key={location.id} | |
className={`map-marker ${location.status} ${location.type} ${selectedLocation && selectedLocation.id === location.id ? 'selected' : ''}`} | |
style={{ | |
top: `${(1 - (location.lat - 37) / 6) * 100}%`, | |
left: `${((location.lng + 91.5) / 4) * 100}%` | |
}} | |
onClick={() => handleLocationSelect(location)} | |
> | |
<div className="marker-pulse"></div> | |
{mapZoom > 1 && ( | |
<div className="marker-label">{location.name}</div> | |
)} | |
</div> | |
))} | |
{selectedLocation && ( | |
<div | |
className="location-detail-popup" | |
style={{ | |
top: `${(1 - (selectedLocation.lat - 37) / 6) * 100}%`, | |
left: `${((selectedLocation.lng + 91.5) / 4) * 100}%` | |
}} | |
> | |
<h4>{selectedLocation.name}</h4> | |
<div className="detail-item"> | |
<span className="detail-label">Status:</span> | |
<span className={`detail-value ${selectedLocation.status}`}> | |
{selectedLocation.status.charAt(0).toUpperCase() + selectedLocation.status.slice(1)} | |
</span> | |
</div> | |
<div className="detail-item"> | |
<span className="detail-label">Type:</span> | |
<span className="detail-value"> | |
{selectedLocation.type === 'urban' ? 'Urban Hub' : 'Corridor Station'} | |
</span> | |
</div> | |
<div className="detail-item"> | |
<span className="detail-label">Capacity:</span> | |
<span className="detail-value">{selectedLocation.capacity} Chargers</span> | |
</div> | |
<div className="detail-item"> | |
<span className="detail-label">Phase:</span> | |
<span className="detail-value"> | |
{mockDeploymentData.phases.find(phase => | |
phase.locations.some(loc => loc.id === selectedLocation.id) | |
).name} | |
</span> | |
</div> | |
<button className="close-popup" onClick={() => setSelectedLocation(null)}>Γ</button> | |
</div> | |
)} | |
</div> | |
<div className="map-controls"> | |
<div className="zoom-controls"> | |
<button | |
className={`zoom-button ${mapZoom === 1 ? 'active' : ''}`} | |
onClick={() => handleZoomChange(1)} | |
> | |
State View | |
</button> | |
<button | |
className={`zoom-button ${mapZoom === 2 ? 'active' : ''}`} | |
onClick={() => handleZoomChange(2)} | |
> | |
Detailed View | |
</button> | |
</div> | |
<div className="view-controls"> | |
<button | |
className={`view-button ${mapView === 'phases' ? 'active' : ''}`} | |
onClick={() => handleMapViewChange('phases')} | |
> | |
Phase View | |
</button> | |
<button | |
className={`view-button ${mapView === 'all' ? 'active' : ''}`} | |
onClick={() => handleMapViewChange('all')} | |
> | |
All Locations | |
</button> | |
</div> | |
</div> | |
<div className="map-legend"> | |
<h4>Map Legend</h4> | |
<div className="legend-items"> | |
<div className="legend-item"> | |
<div className="legend-marker planned"></div> | |
<span>Planned (Phase 1)</span> | |
</div> | |
<div className="legend-item"> | |
<div className="legend-marker future"></div> | |
<span>Future Phases</span> | |
</div> | |
<div className="legend-item"> | |
<div className="legend-marker urban"></div> | |
<span>Urban Hub</span> | |
</div> | |
<div className="legend-item"> | |
<div className="legend-marker corridor"></div> | |
<span>Corridor Station</span> | |
</div> | |
</div> | |
</div> | |
</div> | |
); | |
}; | |
// Helper function to render the deployment phases | |
const renderPhases = () => { | |
return ( | |
<div className="deployment-phases"> | |
<div className="phase-tabs"> | |
{mockDeploymentData.phases.map(phase => ( | |
<button | |
key={phase.id} | |
className={`phase-tab ${selectedPhase === phase.id ? 'active' : ''} ${phase.status}`} | |
onClick={() => handlePhaseChange(phase.id)} | |
> | |
{phase.name} | |
</button> | |
))} | |
</div> | |
{mockDeploymentData.phases.map(phase => ( | |
phase.id === selectedPhase && ( | |
<div key={phase.id} className="phase-details"> | |
<div className="phase-header"> | |
<h3>{phase.name}</h3> | |
<div className="phase-timeline">{phase.timeline}</div> | |
<div className={`phase-status ${phase.status}`}> | |
{phase.status.charAt(0).toUpperCase() + phase.status.slice(1)} | |
</div> | |
</div> | |
<div className="phase-metrics"> | |
<div className="metric-card"> | |
<div className="metric-value">{phase.locations.length}</div> | |
<div className="metric-label">Charging Locations</div> | |
</div> | |
<div className="metric-card"> | |
<div className="metric-value"> | |
{phase.locations.reduce((sum, location) => sum + location.capacity, 0)} | |
</div> | |
<div className="metric-label">Total Chargers</div> | |
</div> | |
<div className="metric-card"> | |
<div className="metric-value"> | |
{phase.locations.filter(location => location.type === 'urban').length} | |
</div> | |
<div className="metric-label">Urban Hubs</div> | |
</div> | |
<div className="metric-card"> | |
<div className="metric-value"> | |
{phase.locations.filter(location => location.type === 'corridor').length} | |
</div> | |
<div className="metric-label">Corridor Stations</div> | |
</div> | |
</div> | |
<div className="location-list"> | |
<h4>Deployment Locations</h4> | |
<div className="location-grid"> | |
{phase.locations.map(location => ( | |
<div | |
key={location.id} | |
className={`location-card ${selectedLocation && selectedLocation.id === location.id ? 'selected' : ''}`} | |
onClick={() => handleLocationSelect(location)} | |
> | |
<div className="location-name">{location.name}</div> | |
<div className="location-type"> | |
{location.type === 'urban' ? 'Urban Hub' : 'Corridor Station'} | |
</div> | |
<div className="location-capacity">{location.capacity} Chargers</div> | |
</div> | |
))} | |
</div> | |
</div> | |
</div> | |
) | |
))} | |
</div> | |
); | |
}; | |
// Helper function to render the demographics tab | |
const renderDemographics = () => { | |
return ( | |
<div className="demographics-tab"> | |
<div className="demographics-section"> | |
<h3>Population & EV Adoption by County</h3> | |
<div className="county-chart"> | |
<div className="chart-header"> | |
<div className="chart-column county">County</div> | |
<div className="chart-column population">Population</div> | |
<div className="chart-column adoption">EV Adoption %</div> | |
<div className="chart-column stations">Charging Stations</div> | |
</div> | |
{mockDeploymentData.demographics.population.map((county, index) => ( | |
<div key={index} className="chart-row"> | |
<div className="chart-column county">{county.county}</div> | |
<div className="chart-column population">{county.population.toLocaleString()}</div> | |
<div className="chart-column adoption"> | |
<div className="adoption-bar-container"> | |
<div | |
className="adoption-bar" | |
style={{ width: `${county.evAdoption * 10}%` }} | |
></div> | |
<span className="adoption-value">{county.evAdoption}%</span> | |
</div> | |
</div> | |
<div className="chart-column stations">{county.chargingStations}</div> | |
</div> | |
))} | |
</div> | |
</div> | |
<div className="demographics-section"> | |
<h3>EV Adoption Projections</h3> | |
<div className="projection-chart"> | |
<div className="projection-bars"> | |
{mockDeploymentData.demographics.evProjections.map((projection, index) => ( | |
<div key={index} className="projection-bar-container"> | |
<div className="projection-year">{projection.year}</div> | |
<div className="projection-bar-wrapper"> | |
<div | |
className="projection-bar" | |
style={{ height: `${projection.percentage * 3}%` }} | |
></div> | |
</div> | |
<div className="projection-value">{projection.percentage}%</div> | |
</div> | |
))} | |
</div> | |
<div className="projection-axis"> | |
<div className="axis-label">Projected EV Adoption Rate (%)</div> | |
</div> | |
</div> | |
<div className="projection-notes"> | |
<p>Projections based on current adoption trends, state incentives, and manufacturer production forecasts.</p> | |
<p>The Link deployment strategy is designed to stay ahead of projected adoption curves to ensure adequate charging infrastructure.</p> | |
</div> | |
</div> | |
</div> | |
); | |
}; | |
// Helper function to render the impact metrics tab | |
const renderImpactMetrics = () => { | |
const { economic, environmental, social } = mockDeploymentData.impactMetrics; | |
return ( | |
<div className="impact-metrics-tab"> | |
<div className="impact-section economic"> | |
<h3>Economic Impact</h3> | |
<div className="impact-metrics"> | |
<div className="impact-metric"> | |
<div className="impact-icon">πΌ</div> | |
<div className="impact-value">{economic.jobsCreated}</div> | |
<div className="impact-label">Jobs Created</div> | |
<div className="impact-description"> | |
Direct and indirect employment opportunities across all deployment phases | |
</div> | |
</div> | |
<div className="impact-metric"> | |
<div className="impact-icon">πͺ</div> | |
<div className="impact-value">{economic.localBusinessImpact}%</div> | |
<div className="impact-label">Local Business Growth</div> | |
<div className="impact-description"> | |
Projected increase in revenue for businesses near charging hubs | |
</div> | |
</div> | |
<div className="impact-metric"> | |
<div className="impact-icon">π°</div> | |
<div className="impact-value">${economic.taxRevenue}M</div> | |
<div className="impact-label">Annual Tax Revenue</div> | |
<div className="impact-description"> | |
Estimated additional tax revenue for state and local governments | |
</div> | |
</div> | |
</div> | |
</div> | |
<div className="impact-section environmental"> | |
<h3>Environmental Impact</h3> | |
<div className="impact-metrics"> | |
<div className="impact-metric"> | |
<div className="impact-icon">π±</div> | |
<div className="impact-value">{environmental.co2Reduction}</div> | |
<div className="impact-label">Tons COβ Reduced</div> | |
<div className="impact-description"> | |
Annual carbon emissions reduction from EV adoption enabled by The Link | |
</div> | |
</div> | |
<div className="impact-metric"> | |
<div className="impact-icon">β½</div> | |
<div className="impact-value">{environmental.gasDisplaced}M</div> | |
<div className="impact-label">Gallons Gas Displaced</div> | |
<div className="impact-description"> | |
Annual reduction in gasoline consumption | |
</div> | |
</div> | |
<div className="impact-metric"> | |
<div className="impact-icon">βοΈ</div> | |
<div className="impact-value">{environmental.renewableIntegration}%</div> | |
<div className="impact-label">Renewable Energy</div> | |
<div className="impact-description"> | |
Percentage of charging energy from renewable sources | |
</div> | |
</div> | |
</div> | |
</div> | |
<div className="impact-section social"> | |
<h3>Social Impact</h3> | |
<div className="impact-metrics"> | |
<div className="impact-metric"> | |
<div className="impact-icon">ποΈ</div> | |
<div className="impact-value">{social.underservedCommunities}</div> | |
<div className="impact-label">Underserved Communities</div> | |
<div className="impact-description"> | |
Number of underserved communities with improved EV access | |
</div> | |
</div> | |
<div className="impact-metric"> | |
<div className="impact-icon">βΏ</div> | |
<div className="impact-value">{social.accessibilityScore}/100</div> | |
<div className="impact-label">Accessibility Score</div> | |
<div className="impact-description"> | |
Rating of charging infrastructure accessibility for all users | |
</div> | |
</div> | |
<div className="impact-metric"> | |
<div className="impact-icon">π</div> | |
<div className="impact-value">{social.publicTransportIntegration}</div> | |
<div className="impact-label">Transit Connections</div> | |
<div className="impact-description"> | |
Number of charging hubs with public transportation connections | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
); | |
}; | |
if (loading) { | |
return ( | |
<div className="deployment-map loading"> | |
<div className="loading-logo"> | |
<img src="/images/unity_fleet_logo.png" alt="Unity Fleet" /> | |
<div className="loading-pulse"></div> | |
</div> | |
<p>Loading Deployment Map...</p> | |
</div> | |
); | |
} | |
return ( | |
<div className="deployment-map"> | |
<header className="map-header"> | |
<div className="map-title"> | |
<h1>Deployment Map</h1> | |
<span className="map-subtitle">The Link Charging Network</span> | |
</div> | |
</header> | |
<div className="map-navigation"> | |
<button | |
className={`nav-button ${activeTab === 'map' ? 'active' : ''}`} | |
onClick={() => setActiveTab('map')} | |
> | |
Interactive Map | |
</button> | |
<button | |
className={`nav-button ${activeTab === 'phases' ? 'active' : ''}`} | |
onClick={() => setActiveTab('phases')} | |
> | |
Deployment Phases | |
</button> | |
<button | |
className={`nav-button ${activeTab === 'demographics' ? 'active' : ''}`} | |
onClick={() => setActiveTab('demographics')} | |
> | |
Demographics | |
</button> | |
<button | |
className={`nav-button ${activeTab === 'impact' ? 'active' : ''}`} | |
onClick={() => setActiveTab('impact')} | |
> | |
Impact Metrics | |
</button> | |
</div> | |
<main className="map-content"> | |
{activeTab === 'map' && renderMap()} | |
{activeTab === 'phases' && renderPhases()} | |
{activeTab === 'demographics' && renderDemographics()} | |
{activeTab === 'impact' && renderImpactMetrics()} | |
</main> | |
<footer className="map-footer"> | |
<div className="footer-info"> | |
<span>Unity Fleet Deployment Strategy</span> | |
<span>Powered by Atlas Intelligence</span> | |
</div> | |
<div className="footer-actions"> | |
<button className="footer-button">Download Map Data</button> | |
<button className="footer-button">Share Deployment Plan</button> | |
</div> | |
</footer> | |
</div> | |
); | |
} | |
export default DeploymentMap; | |