290 lines
9.0 KiB
HTML
290 lines
9.0 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Water Station Tracker</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;
|
|
}
|
|
.header h1, #cityName {
|
|
width: 49%;
|
|
}
|
|
|
|
.legend {
|
|
bottom: 0.5rem;
|
|
left: 0.5rem;
|
|
right: 0.5rem;
|
|
padding: 0.8rem;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="header">
|
|
<h1>💧 Water Stations</h1>
|
|
<p id="cityName">Loading...</p>
|
|
</div>
|
|
|
|
<a href="/login" class="auth-button">Login</a>
|
|
<a href="/city-select" class="auth-button" style="top: 1rem; right: 6rem;">All Cities</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 = [];
|
|
let currentCity = null;
|
|
|
|
function initMap() {
|
|
// Get city name from URL
|
|
const pathParts = window.location.pathname.split('/');
|
|
const cityName = pathParts[2];
|
|
|
|
if (!cityName) {
|
|
// If no city in URL, redirect to city selection
|
|
window.location.href = '/city-select';
|
|
return;
|
|
}
|
|
|
|
currentCity = cityName;
|
|
document.getElementById('cityName').textContent = cityName.charAt(0).toUpperCase() + cityName.slice(1);
|
|
|
|
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/cities/${currentCity}/stations`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
stations = data;
|
|
if (data.length > 0) {
|
|
document.getElementById('cityName').textContent = data[0].city_name;
|
|
}
|
|
displayStations();
|
|
fitMapToStations();
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading stations:', error);
|
|
document.getElementById('cityName').textContent = 'City Not Found';
|
|
});
|
|
}
|
|
|
|
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> |