579 lines
25 KiB
JavaScript
579 lines
25 KiB
JavaScript
Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJjZTY0ZTQ1Yi0zYmYxLTQ5MjItODdkOS05ZDY0ZGRjYjQwM2QiLCJpZCI6MjA5ODgwLCJpYXQiOjE3MTM4MTY3OTB9.A-3Jt_G0K81s-A-XLpT2bn5aY2H3s-n2p-2jYf-i-g';
|
|
|
|
|
|
|
|
function initializeGlobe() {
|
|
try {
|
|
const viewer = new Cesium.Viewer('cesiumContainer', {
|
|
imageryProvider: new Cesium.OpenStreetMapImageryProvider({
|
|
url : 'https://a.tile.openstreetmap.org/'
|
|
}),
|
|
animation: false,
|
|
baseLayerPicker: false,
|
|
fullscreenButton: false,
|
|
geocoder: false,
|
|
homeButton: false,
|
|
infoBox: false, // Disable default infobox
|
|
sceneModePicker: false,
|
|
selectionIndicator: false,
|
|
timeline: false,
|
|
navigationHelpButton: false,
|
|
scene3DOnly: true
|
|
});
|
|
viewer.scene.globe.depthTestAgainstTerrain = false;
|
|
|
|
|
|
|
|
// --- Layer Toggles ---
|
|
const layers = {
|
|
weather: new Cesium.UrlTemplateImageryProvider({ url: `api/weather.php?layer=clouds_new&z={z}&x={x}&y={y}` }),
|
|
wildfires: new Cesium.GeoJsonDataSource('wildfires'),
|
|
spc: new Cesium.CustomDataSource('spcOutlook'),
|
|
weatherAlerts: new Cesium.CustomDataSource('weatherAlerts'),
|
|
hurricanes: new Cesium.CustomDataSource('hurricanes')
|
|
};
|
|
|
|
const weatherLayer = viewer.imageryLayers.addImageryProvider(layers.weather);
|
|
weatherLayer.alpha = 0.6;
|
|
viewer.dataSources.add(layers.wildfires);
|
|
viewer.dataSources.add(layers.spc);
|
|
viewer.dataSources.add(layers.weatherAlerts);
|
|
viewer.dataSources.add(layers.hurricanes);
|
|
|
|
document.getElementById('weatherLayerCheckbox').addEventListener('change', e => { weatherLayer.show = e.target.checked; });
|
|
document.getElementById('wildfireLayerCheckbox').addEventListener('change', e => { layers.wildfires.show = e.target.checked; });
|
|
document.getElementById('spcLayerCheckbox').addEventListener('change', e => { layers.spc.show = e.target.checked; });
|
|
document.getElementById('weatherAlertsLayerCheckbox').addEventListener('change', e => { layers.weatherAlerts.show = e.target.checked; });
|
|
document.getElementById('hurricaneLayerCheckbox').addEventListener('change', e => { layers.hurricanes.show = e.target.checked; });
|
|
|
|
// --- Wind Layer ---
|
|
const windLayer = new WindLayer(viewer, { particleHeight: 10000 });
|
|
document.getElementById('windLayerCheckbox').addEventListener('change', e => { windLayer.setVisible(e.target.checked); });
|
|
document.getElementById('windAltitudeSlider').addEventListener('input', e => {
|
|
const altitude = parseInt(e.target.value, 10);
|
|
document.getElementById('windAltitudeLabel').textContent = `${altitude} m`;
|
|
windLayer.setOptions({ particleHeight: altitude });
|
|
});
|
|
document.getElementById('playPauseWind').addEventListener('click', e => {
|
|
const isPlaying = windLayer.isPlaying();
|
|
isPlaying ? windLayer.pause() : windLayer.play();
|
|
e.target.textContent = isPlaying ? 'Play' : 'Pause';
|
|
});
|
|
document.getElementById('particleDensitySlider').addEventListener('input', e => { windLayer.setParticleDensity(parseFloat(e.target.value)); });
|
|
|
|
// --- Data Loading ---
|
|
const loadDataSources = () => {
|
|
// Wildfires
|
|
fetch('api/wildfires.php').then(res => res.json()).then(data => {
|
|
layers.wildfires.load(data, {
|
|
stroke: Cesium.Color.RED,
|
|
fill: Cesium.Color.RED.withAlpha(0.5),
|
|
strokeWidth: 3
|
|
}).then(() => {
|
|
layers.wildfires.entities.values.forEach(entity => {
|
|
entity.point = new Cesium.PointGraphics({
|
|
color: Cesium.Color.FIREBRICK,
|
|
pixelSize: 8
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
// Hurricanes
|
|
fetch('api/hurricanes.php').then(res => res.json()).then(data => {
|
|
processHurricaneData(data, layers.hurricanes);
|
|
});
|
|
|
|
// Weather Alerts
|
|
fetch('api/weather_alerts.php').then(res => res.json()).then(data => {
|
|
processWeatherAlerts(data, layers.weatherAlerts);
|
|
});
|
|
|
|
// SPC Outlook
|
|
fetch('api/spc.php').then(res => res.json()).then(data => {
|
|
processSpcOutlook(data, layers.spc);
|
|
});
|
|
|
|
// Other data sources like SPC/Alerts can be loaded here too if needed on init
|
|
};
|
|
|
|
function processSpcOutlook(spcData, dataSource) {
|
|
dataSource.entities.removeAll();
|
|
if (!spcData || !Array.isArray(spcData)) return;
|
|
|
|
spcData.forEach(feature => {
|
|
const riskLevel = feature.name;
|
|
const color = getSpcColor(riskLevel);
|
|
|
|
dataSource.entities.add({
|
|
name: `SPC Outlook: ${riskLevel}`,
|
|
polygon: {
|
|
hierarchy: new Cesium.PolygonHierarchy(
|
|
Cesium.Cartesian3.fromDegreesArray(feature.coordinates)
|
|
),
|
|
material: color.withAlpha(0.5),
|
|
outline: true,
|
|
outlineColor: color
|
|
},
|
|
description: `
|
|
<h1>SPC Convective Outlook</h1>
|
|
<p><strong>Risk Level:</strong> ${riskLevel}</p>
|
|
`
|
|
});
|
|
});
|
|
}
|
|
|
|
function getSpcColor(riskLevel) {
|
|
switch (riskLevel) {
|
|
case 'TSTM': return Cesium.Color.fromCssColorString('#c1e2c1'); // Light Green
|
|
case 'MRGL': return Cesium.Color.fromCssColorString('#a4d0a4'); // Green
|
|
case 'SLGT': return Cesium.Color.fromCssColorString('#fdfd96'); // Yellow
|
|
case 'ENH': return Cesium.Color.fromCssColorString('#ffb347'); // Orange
|
|
case 'MDT': return Cesium.Color.fromCssColorString('#ff6961'); // Red
|
|
case 'HIGH': return Cesium.Color.fromCssColorString('#ff00ff'); // Magenta
|
|
default: return Cesium.Color.GRAY;
|
|
}
|
|
}
|
|
|
|
function processWeatherAlerts(alertsData, dataSource) {
|
|
dataSource.entities.removeAll();
|
|
const alertsListContainer = document.getElementById('alerts-list-container');
|
|
alertsListContainer.innerHTML = ''; // Clear previous list
|
|
|
|
const alertColors = {
|
|
"warning": Cesium.Color.RED,
|
|
"watch": Cesium.Color.ORANGE,
|
|
"advisory": Cesium.Color.YELLOW,
|
|
"statement": Cesium.Color.SKYBLUE,
|
|
"default": Cesium.Color.LIGHTGRAY
|
|
};
|
|
|
|
if (!alertsData || !Array.isArray(alertsData) || alertsData.length === 0) {
|
|
alertsListContainer.innerHTML = '<p>No active alerts in the covered area.</p>';
|
|
return;
|
|
}
|
|
|
|
const ul = document.createElement('ul');
|
|
ul.className = 'alerts-list';
|
|
|
|
alertsData.forEach(alert => {
|
|
// Add to list in modal
|
|
const li = document.createElement('li');
|
|
li.innerHTML = `
|
|
<h4>${alert.event}</h4>
|
|
<p><strong>Sender:</strong> ${alert.sender_name}</p>
|
|
<p>${alert.description || 'No description provided.'}</p>
|
|
`;
|
|
ul.appendChild(li);
|
|
|
|
// Draw on map
|
|
if (alert.polygons && Array.isArray(alert.polygons)) {
|
|
const eventName = (alert.event || 'alert').toLowerCase();
|
|
let color = alertColors.default;
|
|
for (const key in alertColors) {
|
|
if (eventName.includes(key)) {
|
|
color = alertColors[key];
|
|
break;
|
|
}
|
|
}
|
|
|
|
alert.polygons.forEach(polygonCoords => {
|
|
const coordinates = polygonCoords.flat();
|
|
dataSource.entities.add({
|
|
name: alert.event || 'Weather Alert',
|
|
polygon: {
|
|
hierarchy: new Cesium.PolygonHierarchy(
|
|
Cesium.Cartesian3.fromDegreesArray(coordinates)
|
|
),
|
|
material: color.withAlpha(0.4),
|
|
outline: true,
|
|
outlineColor: color.withAlpha(0.8)
|
|
},
|
|
description: `
|
|
<h1>${alert.event}</h1>
|
|
<p><strong>Sender:</strong> ${alert.sender_name}</p>
|
|
<p>${alert.description || 'No description provided.'}</p>
|
|
`
|
|
});
|
|
});
|
|
}
|
|
});
|
|
|
|
alertsListContainer.appendChild(ul);
|
|
}
|
|
|
|
function processHurricaneData(geoJson, dataSource) {
|
|
dataSource.entities.removeAll();
|
|
const features = geoJson.features;
|
|
|
|
// Separate features by type
|
|
const cones = features.filter(f => f.geometry.type === 'Polygon');
|
|
const tracks = features.filter(f => f.geometry.type === 'LineString');
|
|
const points = features.filter(f => f.geometry.type === 'Point');
|
|
|
|
// Process cones first (bottom layer)
|
|
cones.forEach(feature => {
|
|
dataSource.entities.add({
|
|
polygon: {
|
|
hierarchy: new Cesium.PolygonHierarchy(
|
|
Cesium.Cartesian3.fromDegreesArray(feature.geometry.coordinates[0].flat())
|
|
),
|
|
material: Cesium.Color.WHITE.withAlpha(0.15),
|
|
outline: true,
|
|
outlineColor: Cesium.Color.WHITE.withAlpha(0.3)
|
|
}
|
|
});
|
|
});
|
|
|
|
// Process tracks
|
|
tracks.forEach(feature => {
|
|
dataSource.entities.add({
|
|
polyline: {
|
|
positions: Cesium.Cartesian3.fromDegreesArray(feature.geometry.coordinates.flat()),
|
|
width: 2,
|
|
material: new Cesium.PolylineDashMaterialProperty({
|
|
color: Cesium.Color.WHITE
|
|
})
|
|
}
|
|
});
|
|
});
|
|
|
|
// Process points (top layer)
|
|
points.forEach(feature => {
|
|
const props = feature.properties;
|
|
const windSpeed = parseInt(props.WINDSPEED, 10);
|
|
dataSource.entities.add({
|
|
position: Cesium.Cartesian3.fromDegrees(feature.geometry.coordinates[0], feature.geometry.coordinates[1]),
|
|
point: {
|
|
pixelSize: 10,
|
|
color: Cesium.Color.fromCssColorString(getStormColor(windSpeed)),
|
|
outlineColor: Cesium.Color.WHITE,
|
|
outlineWidth: 2
|
|
},
|
|
label: {
|
|
text: `${props.STORMNAME} (${windSpeed} mph)`,
|
|
font: '12pt Inter, sans-serif',
|
|
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
|
|
outlineWidth: 2,
|
|
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
|
|
pixelOffset: new Cesium.Cartesian2(0, -12)
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function getStormColor(windspeed) {
|
|
if (windspeed >= 157) return '#c83226'; // Cat 5
|
|
if (windspeed >= 130) return '#ff6900'; // Cat 4
|
|
if (windspeed >= 111) return '#ff9c00'; // Cat 3
|
|
if (windspeed >= 96) return '#ffcf00'; // Cat 2
|
|
if (windspeed >= 74) return '#ffff00'; // Cat 1
|
|
return '#00c8ff'; // Tropical Storm
|
|
}
|
|
|
|
loadDataSources();
|
|
|
|
// --- Modal & Click Handling ---
|
|
const crossSectionModal = document.getElementById("crossSectionModal");
|
|
const crossSectionCloseButton = crossSectionModal.querySelector(".close-button");
|
|
const alertsModal = document.getElementById("alertsModal");
|
|
const alertsCloseButton = alertsModal.querySelector(".close-button");
|
|
const alertsNavButton = document.getElementById("show-alerts-btn");
|
|
let crossSectionChart;
|
|
|
|
crossSectionCloseButton.onclick = () => { crossSectionModal.style.display = "none"; };
|
|
alertsCloseButton.onclick = () => { alertsModal.style.display = "none"; };
|
|
alertsNavButton.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
alertsModal.style.display = "block";
|
|
});
|
|
|
|
window.onclick = (event) => {
|
|
if (event.target == crossSectionModal) crossSectionModal.style.display = "none";
|
|
if (event.target == alertsModal) alertsModal.style.display = "none";
|
|
};
|
|
|
|
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
|
|
handler.setInputAction(function (click) {
|
|
const cartesian = viewer.scene.pickPosition(click.position);
|
|
if (!Cesium.defined(cartesian)) {
|
|
return; // Didn't pick a point on the globe
|
|
}
|
|
|
|
const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
|
|
const longitude = Cesium.Math.toDegrees(cartographic.longitude);
|
|
const latitude = Cesium.Math.toDegrees(cartographic.latitude);
|
|
|
|
|
|
|
|
// --- Now, handle the risk assessment modal ---
|
|
crossSectionModal.style.display = "block";
|
|
|
|
// Reset UI
|
|
document.getElementById('riskScoreValue').textContent = 'Calculating...';
|
|
document.getElementById('wildfireRisk').textContent = 'Calculating...';
|
|
document.getElementById('hurricaneRisk').textContent = 'Calculating...';
|
|
document.getElementById('historicalHurricaneRisk').textContent = 'Calculating...';
|
|
document.getElementById("loadingIndicator").style.display = 'block';
|
|
if (crossSectionChart) {
|
|
crossSectionChart.destroy();
|
|
}
|
|
|
|
// Fetch Risk Score
|
|
fetch(`api/risk_score.php?lat=${latitude}&lon=${longitude}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.error) throw new Error(data.error);
|
|
document.getElementById('riskScoreValue').textContent = data.risk_score ? `${data.risk_score} / 100` : 'N/A';
|
|
document.getElementById('wildfireRisk').textContent = data.factors?.wildfire?.details || 'N/A';
|
|
document.getElementById('hurricaneRisk').textContent = data.factors?.live_hurricane?.details || 'N/A';
|
|
document.getElementById('historicalHurricaneRisk').textContent = data.factors?.historical_hurricane?.details || 'N/A';
|
|
})
|
|
.catch(error => {
|
|
console.error("Error fetching risk score:", error);
|
|
document.getElementById('riskScoreValue').textContent = 'Error';
|
|
});
|
|
|
|
// Fetch Cross-Section Data
|
|
fetch(`/api/cross_section.php?lat=${latitude}&lon=${longitude}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
document.getElementById("loadingIndicator").style.display = 'none';
|
|
if (data.error) throw new Error(data.error);
|
|
|
|
const { pressure_level, temperature, relative_humidity, wind_speed } = data.hourly;
|
|
crossSectionChart = new Chart(document.getElementById("crossSectionChart"), {
|
|
type: "line",
|
|
data: {
|
|
labels: pressure_level,
|
|
datasets: [
|
|
{ label: "Temperature (C)", data: temperature, borderColor: "red", yAxisID: "y" },
|
|
{ label: "Humidity (%)", data: relative_humidity, borderColor: "blue", yAxisID: "y1" },
|
|
{ label: "Wind Speed (km/h)", data: wind_speed, borderColor: "green", yAxisID: "y2" }
|
|
]
|
|
},
|
|
options: {
|
|
scales: {
|
|
x: { title: { display: true, text: "Pressure (hPa)" }, reverse: true },
|
|
y: { type: 'linear', position: 'left', title: { display: true, text: 'Temp (C)' } },
|
|
y1: { type: 'linear', position: 'right', title: { display: true, text: 'Humidity (%)' }, grid: { drawOnChartArea: false } },
|
|
y2: { type: 'linear', position: 'right', title: { display: true, text: 'Wind (km/h)' }, grid: { drawOnChartArea: false } }
|
|
}
|
|
}
|
|
});
|
|
})
|
|
.catch(error => {
|
|
console.error("Error fetching cross-section:", error);
|
|
document.getElementById("loadingIndicator").style.display = 'none';
|
|
});
|
|
|
|
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
|
|
|
|
} catch (error) {
|
|
console.error('Critical error initializing Cesium:', error);
|
|
document.getElementById('cesiumContainer').innerHTML = '<p>Error loading 3D scene.</p>';
|
|
}
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const sidebar = document.getElementById('sidebar');
|
|
const hamburgerMenu = document.getElementById('hamburger-menu');
|
|
const homeLink = document.getElementById('home-link');
|
|
|
|
const mainContent = document.getElementById('main-content');
|
|
|
|
// Initially hide the sidebar for a cleaner look on load
|
|
sidebar.classList.add('sidebar-hidden');
|
|
mainContent.classList.add('sidebar-hidden');
|
|
|
|
hamburgerMenu.addEventListener('click', () => {
|
|
sidebar.classList.toggle('sidebar-hidden');
|
|
mainContent.classList.toggle('sidebar-hidden');
|
|
});
|
|
|
|
homeLink.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
showPanel(cesiumContainer);
|
|
});
|
|
|
|
initializeGlobe();
|
|
|
|
// --- Dashboard ---
|
|
const cesiumContainer = document.getElementById('cesiumContainer');
|
|
const dashboardContent = document.getElementById('dashboard-content');
|
|
const analyticsContent = document.getElementById('analytics-content');
|
|
const reportsContent = document.getElementById('reports-content');
|
|
const settingsContent = document.getElementById('settings-content');
|
|
const summaryContent = document.getElementById('summary-content');
|
|
const helpContent = document.getElementById('help-content');
|
|
|
|
const contentPanels = [cesiumContainer, dashboardContent, analyticsContent, reportsContent, settingsContent, summaryContent, helpContent];
|
|
|
|
function showPanel(panelToShow) {
|
|
contentPanels.forEach(panel => {
|
|
if (panel === panelToShow) {
|
|
panel.classList.remove('hidden');
|
|
} else {
|
|
panel.classList.add('hidden');
|
|
}
|
|
});
|
|
}
|
|
|
|
function getRiskScoreClass(score) {
|
|
if (score > 75) return 'risk-high';
|
|
if (score > 50) return 'risk-medium';
|
|
if (score > 25) return 'risk-low';
|
|
return 'risk-very-low';
|
|
}
|
|
|
|
async function loadDashboardData() {
|
|
const tbody = dashboardContent.querySelector('tbody');
|
|
tbody.innerHTML = '<tr><td colspan="4">Loading facility data...</td></tr>';
|
|
|
|
try {
|
|
const facilitiesResponse = await fetch('api/facilities.php');
|
|
const facilitiesData = await facilitiesResponse.json();
|
|
|
|
if (!facilitiesData.success || facilitiesData.facilities.length === 0) {
|
|
tbody.innerHTML = '<tr><td colspan="4">No facilities found.</td></tr>';
|
|
return;
|
|
}
|
|
|
|
let tableHtml = '';
|
|
facilitiesData.facilities.forEach(facility => {
|
|
tableHtml += `
|
|
<tr>
|
|
<td>${facility.name}</td>
|
|
<td>${facility.city}</td>
|
|
<td>${facility.state}</td>
|
|
<td class="${getRiskScoreClass(facility.risk)}">${facility.risk !== null ? facility.risk : 'N/A'}</td>
|
|
</tr>
|
|
`;
|
|
});
|
|
tbody.innerHTML = tableHtml;
|
|
|
|
} catch (error) {
|
|
console.error('Error loading dashboard data:', error);
|
|
tbody.innerHTML = '<tr><td colspan="4">Error loading data.</td></tr>';
|
|
}
|
|
}
|
|
|
|
let facilitiesByStateChart = null;
|
|
async function loadAnalyticsData() {
|
|
const ctx = document.getElementById('facilitiesByStateChart').getContext('2d');
|
|
|
|
try {
|
|
const facilitiesResponse = await fetch('api/facilities.php');
|
|
const facilitiesData = await facilitiesResponse.json();
|
|
|
|
if (!facilitiesData.success || facilitiesData.facilities.length === 0) {
|
|
return;
|
|
}
|
|
|
|
const facilitiesByState = facilitiesData.facilities.reduce((acc, facility) => {
|
|
const state = facility.state || 'Unknown';
|
|
acc[state] = (acc[state] || 0) + 1;
|
|
return acc;
|
|
}, {});
|
|
|
|
const chartData = {
|
|
labels: Object.keys(facilitiesByState),
|
|
datasets: [{
|
|
label: 'Number of Facilities',
|
|
data: Object.values(facilitiesByState),
|
|
backgroundColor: 'rgba(0, 170, 255, 0.5)',
|
|
borderColor: 'rgba(0, 170, 255, 1)',
|
|
borderWidth: 1
|
|
}]
|
|
};
|
|
|
|
if (facilitiesByStateChart) {
|
|
facilitiesByStateChart.destroy();
|
|
}
|
|
|
|
facilitiesByStateChart = new Chart(ctx, {
|
|
type: 'bar',
|
|
data: chartData,
|
|
options: {
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Error loading analytics data:', error);
|
|
}
|
|
}
|
|
|
|
async function downloadReport() {
|
|
try {
|
|
const facilitiesResponse = await fetch('api/facilities.php');
|
|
const facilitiesData = await facilitiesResponse.json();
|
|
|
|
if (!facilitiesData.success || facilitiesData.facilities.length === 0) {
|
|
alert('No facilities to generate a report for.');
|
|
return;
|
|
}
|
|
|
|
let csvContent = 'data:text/csv;charset=utf-8,';
|
|
csvContent += 'Facility Name,City,State,Latitude,Longitude,Risk Score\n';
|
|
|
|
facilitiesData.facilities.forEach(facility => {
|
|
csvContent += `"${facility.name}","${facility.city}","${facility.state}",${facility.latitude},${facility.longitude},${facility.risk || 'N/A'}\n`;
|
|
});
|
|
|
|
const encodedUri = encodeURI(csvContent);
|
|
const link = document.createElement('a');
|
|
link.setAttribute('href', encodedUri);
|
|
link.setAttribute('download', 'facility_risk_report.csv');
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
|
|
} catch (error) {
|
|
console.error('Error generating report:', error);
|
|
alert('Failed to generate report.');
|
|
}
|
|
}
|
|
|
|
document.getElementById('dashboard-nav-link').addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
showPanel(dashboardContent);
|
|
loadDashboardData();
|
|
});
|
|
|
|
document.getElementById('analytics-nav-link').addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
showPanel(analyticsContent);
|
|
loadAnalyticsData();
|
|
});
|
|
|
|
document.getElementById('reports-nav-link').addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
showPanel(reportsContent);
|
|
});
|
|
|
|
const downloadReportBtn = document.getElementById('download-report-btn');
|
|
downloadReportBtn.addEventListener('click', () => {
|
|
downloadReport();
|
|
});
|
|
|
|
document.getElementById('settings-nav-link').addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
showPanel(settingsContent);
|
|
});
|
|
|
|
document.getElementById('summary-nav-link').addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
showPanel(summaryContent);
|
|
});
|
|
|
|
document.getElementById('help-nav-link').addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
showPanel(helpContent);
|
|
});
|
|
});
|