Initial commit
This commit is contained in:
266
public/index.html
Normal file
266
public/index.html
Normal file
@@ -0,0 +1,266 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Water Station</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.header p {
|
||||
opacity: 0.9;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.auth-button {
|
||||
position: fixed;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
background: rgba(255,255,255,0.2);
|
||||
color: white;
|
||||
border: 1px solid rgba(255,255,255,0.3);
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 20px;
|
||||
text-decoration: none;
|
||||
font-size: 0.9rem;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#map {
|
||||
height: calc(100vh - 120px);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.station-popup {
|
||||
min-width: 250px;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.station-popup h3 {
|
||||
color: #333;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.station-info {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.station-info p {
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.status-green { background-color: #4CAF50; }
|
||||
.status-yellow { background-color: #FFC107; }
|
||||
.status-red { background-color: #f44336; }
|
||||
.status-black { background-color: #333; }
|
||||
|
||||
.legend {
|
||||
position: fixed;
|
||||
bottom: 1rem;
|
||||
left: 1rem;
|
||||
background: white;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
font-size: 0.8rem;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.legend h4 {
|
||||
margin-bottom: 0.5rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.header h1 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.legend {
|
||||
bottom: 0.5rem;
|
||||
left: 0.5rem;
|
||||
right: 0.5rem;
|
||||
padding: 0.8rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>💧 Water Stations</h1>
|
||||
<p>Salem</p>
|
||||
</div>
|
||||
|
||||
<a href="/login" class="auth-button">Login</a>
|
||||
|
||||
<div id="map"></div>
|
||||
|
||||
<div class="legend">
|
||||
<h4>Station Status</h4>
|
||||
<div class="legend-item">
|
||||
<span class="status-indicator status-green"></span>
|
||||
Recently refilled
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="status-indicator status-yellow"></span>
|
||||
Needs refill soon
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="status-indicator status-red"></span>
|
||||
Empty
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="status-indicator status-black"></span>
|
||||
Not updated (7+ days)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||
<script>
|
||||
let map;
|
||||
let stations = [];
|
||||
|
||||
function initMap() {
|
||||
map = L.map('map').setView([37.7749, -122.4194], 13);
|
||||
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap contributors'
|
||||
}).addTo(map);
|
||||
|
||||
loadStations();
|
||||
}
|
||||
|
||||
function loadStations() {
|
||||
fetch('/api/stations')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
stations = data;
|
||||
displayStations();
|
||||
fitMapToStations();
|
||||
})
|
||||
.catch(error => console.error('Error loading stations:', error));
|
||||
}
|
||||
|
||||
function getStationColor(station) {
|
||||
if (!station.last_refill_time) {
|
||||
return '#333'; // Black for no updates
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const refillTime = new Date(station.last_refill_time);
|
||||
const timeSinceRefill = now - refillTime;
|
||||
const daysSinceRefill = timeSinceRefill / (1000 * 60 * 60 * 24);
|
||||
|
||||
if (daysSinceRefill > 7) {
|
||||
return '#333'; // Black for old data
|
||||
}
|
||||
|
||||
if (!station.estimated_empty_time) {
|
||||
return '#4CAF50'; // Green if no empty time estimate
|
||||
}
|
||||
|
||||
const emptyTime = new Date(station.estimated_empty_time);
|
||||
const timeUntilEmpty = emptyTime - now;
|
||||
const hoursUntilEmpty = timeUntilEmpty / (1000 * 60 * 60);
|
||||
|
||||
if (hoursUntilEmpty <= 0) {
|
||||
return '#f44336'; // Red for empty
|
||||
} else if (hoursUntilEmpty <= 3) {
|
||||
return '#FFC107'; // Yellow for needs refill soon
|
||||
} else {
|
||||
return '#4CAF50'; // Green for good
|
||||
}
|
||||
}
|
||||
|
||||
function displayStations() {
|
||||
stations.forEach(station => {
|
||||
const color = getStationColor(station);
|
||||
|
||||
const marker = L.circleMarker([station.latitude, station.longitude], {
|
||||
color: color,
|
||||
fillColor: color,
|
||||
fillOpacity: 0.8,
|
||||
radius: 8,
|
||||
weight: 2
|
||||
}).addTo(map);
|
||||
|
||||
const popupContent = createPopupContent(station);
|
||||
marker.bindPopup(popupContent);
|
||||
});
|
||||
}
|
||||
|
||||
function createPopupContent(station) {
|
||||
const refillTime = station.last_refill_time ?
|
||||
new Date(station.last_refill_time).toLocaleString() : 'Never';
|
||||
|
||||
const estimatedEmpty = station.estimated_empty_time ?
|
||||
new Date(station.estimated_empty_time).toLocaleString() : 'Unknown';
|
||||
|
||||
return `
|
||||
<div class="station-popup">
|
||||
<h3>${station.name}</h3>
|
||||
<div class="station-info">
|
||||
<p><strong>Description:</strong> ${station.latest_description || 'No description'}</p>
|
||||
<p><strong>Last Refill:</strong> ${refillTime}</p>
|
||||
<p><strong>Estimated Empty:</strong> ${estimatedEmpty}</p>
|
||||
<p><strong>Last Updated:</strong> ${station.last_updated ? new Date(station.last_updated).toLocaleString() : 'Never'}</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function fitMapToStations() {
|
||||
if (stations.length === 0) return;
|
||||
|
||||
const bounds = L.latLngBounds();
|
||||
stations.forEach(station => {
|
||||
bounds.extend([station.latitude, station.longitude]);
|
||||
});
|
||||
|
||||
map.fitBounds(bounds, { padding: [20, 20] });
|
||||
}
|
||||
|
||||
// Initialize map when page loads
|
||||
document.addEventListener('DOMContentLoaded', initMap);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user