diff --git a/public/dashboard.html b/public/dashboard.html
index 922363d..48e46b5 100644
--- a/public/dashboard.html
+++ b/public/dashboard.html
@@ -67,63 +67,14 @@
}
.main-content {
- display: flex;
height: calc(100vh - 80px);
}
- .sidebar {
- width: 100%;
- max-width: 400px;
- background: white;
- border-right: 1px solid #e1e1e1;
- overflow-y: auto;
- }
-
- .map-container {
- flex: 1;
- position: relative;
- }
-
#map {
height: 100%;
width: 100%;
}
- .tab-buttons {
- display: flex;
- background: #f8f9fa;
- border-bottom: 1px solid #e1e1e1;
- }
-
- .tab-button {
- flex: 1;
- padding: 1rem;
- background: none;
- border: none;
- cursor: pointer;
- font-size: 0.9rem;
- color: #666;
- border-bottom: 2px solid transparent;
- }
-
- .tab-button.active {
- color: #667eea;
- border-bottom-color: #667eea;
- background: white;
- }
-
- .tab-content {
- padding: 1rem;
- }
-
- .tab-panel {
- display: none;
- }
-
- .tab-panel.active {
- display: block;
- }
-
.form-group {
margin-bottom: 1rem;
}
@@ -189,83 +140,38 @@
background: #5a6268;
}
- .station-list {
- max-height: 300px;
- overflow-y: auto;
- }
- .station-item {
- padding: 0.8rem;
- border-bottom: 1px solid #e1e1e1;
- cursor: pointer;
- transition: background-color 0.3s;
- display: flex;
- }
-
- .station-item:hover {
- background: #f8f9fa;
- }
-
- .station-item.selected {
- background: #e3f2fd;
- border-left: 4px solid #667eea;
- }
-
- .station-name {
- font-weight: 500;
- color: #333;
- margin-bottom: 0.3rem;
- }
-
- .station-status {
- font-size: 0.8rem;
- color: #666;
- display: flex;
- align-items: center;
- margin-left: 2em;
- }
-
- .status-dot {
- width: 8px;
- height: 8px;
- border-radius: 50%;
- margin-right: 0.5rem;
- }
-
- .map-controls {
+ .context-menu {
position: absolute;
- top: 10px;
- right: 10px;
+ background: white;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-shadow: 0 2px 10px rgba(0,0,0,0.2);
z-index: 1000;
+ display: none;
+ min-width: 150px;
}
- .add-pin-btn {
- background: white;
- border: 1px solid #ccc;
- padding: 0.5rem;
- margin-bottom: 0.5rem;
- border-radius: 4px;
+ .context-menu-item {
+ padding: 0.75rem 1rem;
cursor: pointer;
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
- font-size: 0.8rem;
- display: inline-block;
- width: 40%;
+ font-size: 0.9rem;
+ color: #333;
+ border-bottom: 1px solid #eee;
}
- input#coordinates {
- display: inline-block;
- width: 58%;
+ .context-menu-item:last-child {
+ border-bottom: none;
}
- .map-control-btn {
- background: white;
- border: 1px solid #ccc;
- padding: 0.5rem;
- margin-bottom: 0.5rem;
- border-radius: 4px;
- cursor: pointer;
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
- font-size: 0.8rem;
+ .context-menu-item:hover {
+ background: #f5f5f5;
+ }
+
+ .context-menu-item:before {
+ content: '+ ';
+ font-weight: bold;
+ color: #667eea;
}
.message {
@@ -287,113 +193,132 @@
border: 1px solid #f5c6cb;
}
+ .modal {
+ display: none;
+ position: fixed;
+ z-index: 2000;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0,0,0,0.5);
+ }
+
+ .modal-content {
+ background-color: white;
+ margin: 10% auto;
+ padding: 2rem;
+ border-radius: 8px;
+ width: 90%;
+ max-width: 500px;
+ position: relative;
+ }
+
+ .close {
+ position: absolute;
+ right: 1rem;
+ top: 1rem;
+ font-size: 1.5rem;
+ cursor: pointer;
+ color: #666;
+ }
+
+ .close:hover {
+ color: #000;
+ }
+
+ .modal h2 {
+ margin-bottom: 1rem;
+ color: #333;
+ }
+
+ .popup-btn {
+ background: #667eea;
+ color: white;
+ border: none;
+ padding: 0.5rem 1rem;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 0.9rem;
+ margin-top: 0.5rem;
+ }
+
+ .popup-btn:hover {
+ background: #5a6fd8;
+ }
+
@media (max-width: 768px) {
- .main-content {
- flex-direction: column;
- }
-
- .sidebar {
- max-width: none;
- height: 55vh;
- border-right: none;
- border-bottom: 1px solid #e1e1e1;
- }
-
- .map-container {
- height: 40vh;
- }
-
- .header {
- flex-direction: column;
- gap: 0.5rem;
- text-align: center;
- }
-
- .header-right {
- justify-content: center;
+ .modal-content {
+ margin: 5% auto;
+ width: 95%;
+ padding: 1.5rem;
}
}
-
-
-
-
+
+
+
+
×
+
Update Station Status
+
+
+
+
+
+
+
+
+
×
+
Add New Station
+
+
@@ -402,10 +327,11 @@
let map;
let stations = [];
let selectedStation = null;
- let addMode = false;
let tempMarker = null;
let user = null;
let currentCity = null;
+ let contextMenuPosition = null;
+ let longPressTimer = null;
function initDashboard() {
// Get city name from URL
@@ -435,7 +361,6 @@
}
user = data.user;
- document.getElementById('username').textContent = user.display_name || user.username || 'User';
} catch (error) {
window.location.href = '/login';
}
@@ -448,33 +373,99 @@
attribution: '© OpenStreetMap contributors'
}).addTo(map);
- map.on('click', function(e) {
- if (addMode) {
- setStationLocation(e.latlng);
+ // Handle right-click context menu
+ map.on('contextmenu', function(e) {
+ console.log('contextmenu event:', e);
+ e.originalEvent.preventDefault();
+ const clientX = e.originalEvent.clientX || e.originalEvent.pageX;
+ const clientY = e.originalEvent.clientY || e.originalEvent.pageY;
+ console.log('Context menu coordinates:', clientX, clientY);
+ showContextMenu(clientX, clientY, e.latlng);
+ });
+
+ // Handle long-press for mobile
+ let longPressTimer = null;
+ let longPressStarted = false;
+
+ map.on('mousedown', function(e) {
+ if (e.originalEvent.button === 0) { // Left click only
+ longPressStarted = true;
+ longPressTimer = setTimeout(() => {
+ if (longPressStarted) {
+ showContextMenu(e.originalEvent.clientX, e.originalEvent.clientY, e.latlng);
+ }
+ }, 500); // 500ms for long press
}
});
+
+ map.on('mouseup', function(e) {
+ if (longPressTimer) {
+ clearTimeout(longPressTimer);
+ longPressTimer = null;
+ }
+ longPressStarted = false;
+ });
+
+ map.on('mousemove', function(e) {
+ if (longPressTimer) {
+ clearTimeout(longPressTimer);
+ longPressTimer = null;
+ }
+ longPressStarted = false;
+ });
+
+ // Hide context menu on map click
+ map.on('click', function(e) {
+ hideContextMenu();
+ });
}
- function setStationLocation(latlng) {
- if (tempMarker) {
- map.removeLayer(tempMarker);
- }
+ // Context menu functions
+ function showContextMenu(x, y, latlng) {
+ console.log('showContextMenu called with:', x, y, latlng);
+ hideContextMenu();
+ const contextMenu = document.getElementById('contextMenu');
+ contextMenuPosition = latlng;
+ console.log('contextMenuPosition set to:', contextMenuPosition);
- tempMarker = L.marker(latlng).addTo(map);
- document.getElementById('coordinates').value = `${latlng.lat.toFixed(6)}, ${latlng.lng.toFixed(6)}`;
- stopAddMode();
+ contextMenu.style.display = 'block';
+ contextMenu.style.left = x + 'px';
+ contextMenu.style.top = y + 'px';
+
+ // Adjust position if menu goes off screen
+ const rect = contextMenu.getBoundingClientRect();
+ const viewportWidth = window.innerWidth;
+ const viewportHeight = window.innerHeight;
+
+ if (rect.right > viewportWidth) {
+ contextMenu.style.left = (x - rect.width) + 'px';
+ }
+ if (rect.bottom > viewportHeight) {
+ contextMenu.style.top = (y - rect.height) + 'px';
+ }
}
- function startAddMode() {
- addMode = true;
- map.getContainer().style.cursor = 'crosshair';
+ function hideContextMenu() {
+ document.getElementById('contextMenu').style.display = 'none';
+ contextMenuPosition = null;
}
- function stopAddMode() {
- addMode = false;
- map.getContainer().style.cursor = '';
+ function contextAddStation() {
+ console.log('contextAddStation called');
+ console.log('contextMenuPosition:', contextMenuPosition);
+ hideContextMenu();
+ if (contextMenuPosition) {
+ console.log('Opening modal with position:', contextMenuPosition);
+ openAddStationModal(contextMenuPosition);
+ } else {
+ console.log('No contextMenuPosition available');
+ }
}
+ // Expose functions to global scope for onclick handlers (for modal close buttons)
+ window.closeUpdateModal = closeUpdateModal;
+ window.closeAddModal = closeAddModal;
+
async function loadStations() {
try {
const response = await fetch(`/api/cities/${currentCity}/stations`);
@@ -484,7 +475,6 @@
document.getElementById('cityName').textContent = data[0].city_name;
}
displayStations();
- populateStationList();
fitMapToStations();
} catch (error) {
console.error('Error loading stations:', error);
@@ -554,76 +544,70 @@
Estimated Empty: ${estimatedEmpty}
Last Updated: ${station.last_updated ? new Date(station.last_updated).toLocaleString() : 'Never'}
Last Updated By: ${station.updated_by_name || 'Unknown'}
+
`;
}
- function populateStationList() {
- const stationList = document.getElementById('stationList');
- stationList.innerHTML = '';
-
- stations.forEach(station => {
- const item = document.createElement('div');
- item.className = 'station-item';
- item.onclick = () => selectStation(station);
-
- const color = getStationColor(station);
- const statusText = getStatusText(station);
-
- item.innerHTML = `
-
${station.name}
-
-
- ${statusText}
-
- `;
-
- stationList.appendChild(item);
- });
- }
-
- function getStatusText(station) {
- if (!station.last_refill_time) return 'Never updated';
+ // Modal functions
+ function openUpdateModal(stationId) {
+ const station = stations.find(s => s.id === stationId);
+ if (!station) return;
- const now = new Date();
- const refillTime = new Date(station.last_refill_time);
- const daysSinceRefill = (now - refillTime) / (1000 * 60 * 60 * 24);
-
- if (daysSinceRefill > 7) return 'Old data (7+ days)';
-
- if (!station.estimated_empty_time) return 'Recently refilled';
-
- const emptyTime = new Date(station.estimated_empty_time);
- const hoursUntilEmpty = (emptyTime - now) / (1000 * 60 * 60);
-
- if (hoursUntilEmpty <= 0) return 'Empty';
- if (hoursUntilEmpty <= 3) return 'Needs refill soon';
- return 'Recently refilled';
- }
-
- function selectStation(station) {
selectedStation = station;
-
- // Update UI
- document.querySelectorAll('.station-item').forEach(item => {
- item.classList.remove('selected');
- });
-
- event.target.closest('.station-item').classList.add('selected');
-
- // Show update form
- document.getElementById('updateStationForm').style.display = 'block';
+ document.getElementById('updateModal').style.display = 'block';
// Center map on selected station
map.setView([station.latitude, station.longitude], 16);
}
- function cancelUpdate() {
+ function closeUpdateModal() {
selectedStation = null;
- document.getElementById('updateStationForm').style.display = 'none';
- document.querySelectorAll('.station-item').forEach(item => {
- item.classList.remove('selected');
- });
+ document.getElementById('updateModal').style.display = 'none';
+ document.getElementById('updateStationForm').reset();
+ document.getElementById('update-message').innerHTML = '';
+ }
+
+ function openAddStationModal(latlng = null) {
+ console.log('openAddStationModal called with:', latlng);
+ document.getElementById('addModal').style.display = 'block';
+ if (latlng) {
+ console.log('Setting coordinates:', latlng.lat, latlng.lng);
+ document.getElementById('coordinates').value = `${latlng.lat.toFixed(6)}, ${latlng.lng.toFixed(6)}`;
+ if (tempMarker) {
+ map.removeLayer(tempMarker);
+ }
+ tempMarker = L.marker(latlng).addTo(map);
+ console.log('Marker added to map');
+ }
+ }
+
+ function closeAddModal() {
+ document.getElementById('addModal').style.display = 'none';
+ document.getElementById('addStationForm').reset();
+ document.getElementById('add-message').innerHTML = '';
+ document.getElementById('coordinates').value = '';
+ if (tempMarker) {
+ map.removeLayer(tempMarker);
+ tempMarker = null;
+ }
+ }
+
+ // Close modal when clicking outside
+ window.onclick = function(event) {
+ const updateModal = document.getElementById('updateModal');
+ const addModal = document.getElementById('addModal');
+ const contextMenu = document.getElementById('contextMenu');
+
+ if (event.target === updateModal) {
+ closeUpdateModal();
+ }
+ if (event.target === addModal) {
+ closeAddModal();
+ }
+ if (!contextMenu.contains(event.target)) {
+ hideContextMenu();
+ }
}
function fitMapToStations() {
@@ -641,19 +625,6 @@
fitMapToStations();
}
- function switchTab(tabName) {
- // Update tab buttons
- document.querySelectorAll('.tab-button').forEach(btn => {
- btn.classList.remove('active');
- });
-
- document.querySelectorAll('.tab-panel').forEach(panel => {
- panel.classList.remove('active');
- });
-
- event.target.classList.add('active');
- document.getElementById(tabName + '-tab').classList.add('active');
- }
function showMessage(elementId, message, type = 'success') {
const messageDiv = document.getElementById(elementId);
@@ -670,7 +641,7 @@
const coordinates = document.getElementById('coordinates').value;
if (!coordinates) {
- showMessage('add-message', 'Please select a location on the map', 'error');
+ showMessage('add-message', 'Please right-click or long-press on the map to select a location', 'error');
return;
}
@@ -695,13 +666,10 @@
if (response.ok) {
showMessage('add-message', 'Station added successfully!');
- e.target.reset();
- document.getElementById('coordinates').value = '';
- if (tempMarker) {
- map.removeLayer(tempMarker);
- tempMarker = null;
- }
- loadStations();
+ setTimeout(() => {
+ closeAddModal();
+ loadStations();
+ }, 1500);
} else {
const result = await response.json();
showMessage('add-message', result.error || 'Failed to add station', 'error');
@@ -736,9 +704,10 @@
if (response.ok) {
showMessage('update-message', 'Station updated successfully!');
- e.target.reset();
- cancelUpdate();
- loadStations();
+ setTimeout(() => {
+ closeUpdateModal();
+ loadStations();
+ }, 1500);
} else {
const result = await response.json();
showMessage('update-message', result.error || 'Failed to update station', 'error');
@@ -758,7 +727,18 @@
}
// Initialize dashboard when page loads
- document.addEventListener('DOMContentLoaded', initDashboard);
+ document.addEventListener('DOMContentLoaded', function() {
+ initDashboard();
+
+ // Add event listener for context menu item
+ const menuItem = document.getElementById('addStationMenuItem');
+ if (menuItem) {
+ menuItem.addEventListener('click', contextAddStation);
+ console.log('Context menu event listener attached');
+ } else {
+ console.error('Could not find addStationMenuItem element');
+ }
+ });