added a move system for the pins

This commit is contained in:
admin 2025-07-18 10:46:23 -06:00
parent b98207b118
commit 7989ea07c4
3 changed files with 590 additions and 1 deletions

View File

@ -111,6 +111,61 @@ body {
z-index: 1000; z-index: 1000;
} }
/* Move Controls */
.move-controls {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 10000;
min-width: 300px;
text-align: center;
}
/* Mobile-specific move controls */
@media (max-width: 768px) {
.move-controls {
top: auto;
bottom: 20px;
left: 10px;
right: 10px;
transform: none;
width: auto;
min-width: unset;
max-width: 100%;
padding: 15px;
z-index: 10001; /* Ensure it's on top */
background: white; /* Add background */
box-shadow: 0 -2px 10px rgba(0,0,0,0.1); /* Add shadow for visibility */
}
.move-controls-content h3 {
font-size: 18px;
margin-bottom: 8px;
}
.move-controls-content p {
font-size: 14px;
margin: 3px 0;
}
.move-controls-actions {
flex-direction: column;
gap: 8px;
margin-top: 12px;
}
.move-controls-actions .btn {
width: 100%;
padding: 12px 16px;
font-size: 16px;
}
}
/* Buttons */ /* Buttons */
.btn { .btn {
padding: 10px 16px; padding: 10px 16px;
@ -986,3 +1041,96 @@ path.leaflet-interactive {
stroke-width: 2px !important; stroke-width: 2px !important;
fill-opacity: 0.8 !important; fill-opacity: 0.8 !important;
} }
/* Marker being moved */
.location-marker.leaflet-drag-target {
cursor: move !important;
}
/* Popup actions buttons spacing */
.popup-actions {
display: flex;
gap: 5px;
margin-top: 10px;
}
/* Pulsing animation for marker being moved */
@keyframes pulse-marker {
0% {
transform: scale(1);
opacity: 0.9;
}
50% {
transform: scale(1.2);
opacity: 1;
}
100% {
transform: scale(1);
opacity: 0.9;
}
}
/* Ensure marker animations work */
.leaflet-overlay-pane svg path {
transform-origin: center;
}
/* Move confirmation popup styles */
.move-confirm-popup-wrapper .leaflet-popup-content-wrapper {
background: white;
border: 2px solid var(--primary-color);
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
.move-confirm-popup {
text-align: center;
padding: 10px;
}
.move-confirm-popup h3 {
margin: 0 0 10px 0;
color: var(--dark-color);
font-size: 16px;
}
.move-confirm-popup p {
margin: 0 0 15px 0;
color: #666;
}
.move-confirm-popup .popup-actions {
display: flex;
gap: 10px;
justify-content: center;
}
/* Cancel move button styles */
#cancel-move-btn,
#mobile-cancel-move-btn {
background-color: var(--danger-color);
color: white;
}
#cancel-move-btn:hover,
#mobile-cancel-move-btn:hover {
background-color: #c0392b;
}
/* Ensure crosshairs are visible during move */
.crosshair {
z-index: 1100;
}
/* Mobile-friendly popup buttons */
@media (max-width: 768px) {
.popup-content .popup-actions .btn {
padding: 10px 12px;
font-size: 14px;
min-height: 44px; /* Ensure touch-friendly size */
}
.move-confirm-popup .popup-actions .btn {
min-width: 100px;
min-height: 44px;
}
}

View File

@ -7,6 +7,12 @@ import { resetAddressConfirmation } from './ui-controls.js';
export let markers = []; export let markers = [];
export let currentEditingLocation = null; export let currentEditingLocation = null;
// Add these variables at the top with other exports
export let isMovingMarker = false;
export let movingMarker = null;
export let originalPosition = null;
export let movingLocationData = null;
export async function loadLocations() { export async function loadLocations() {
try { try {
const response = await fetch('/api/locations'); const response = await fetch('/api/locations');
@ -96,12 +102,69 @@ function createLocationMarker(location) {
weight: 2, weight: 2,
opacity: 1, opacity: 1,
fillOpacity: 0.8, fillOpacity: 0.8,
className: 'location-marker' // Add a class for CSS targeting className: 'location-marker'
}); });
// Add to map // Add to map
marker.addTo(map); marker.addTo(map);
// Add custom dragging functionality for circle markers
let _draggingEnabled = false;
let isDragging = false;
let dragStartLatLng = null;
marker.dragging = {
enabled: function() {
return _draggingEnabled;
},
enable: function() {
_draggingEnabled = true;
marker.on('mousedown', startDrag);
marker._path.style.cursor = 'move';
},
disable: function() {
_draggingEnabled = false;
marker.off('mousedown', startDrag);
marker.off('mousemove', drag);
marker.off('mouseup', endDrag);
map.off('mousemove', drag);
map.off('mouseup', endDrag);
marker._path.style.cursor = 'pointer';
}
};
function startDrag(e) {
if (!_draggingEnabled) return;
isDragging = true;
dragStartLatLng = e.latlng;
map.dragging.disable();
marker.on('mousemove', drag);
map.on('mousemove', drag);
marker.on('mouseup', endDrag);
map.on('mouseup', endDrag);
L.DomEvent.stopPropagation(e);
L.DomEvent.preventDefault(e);
}
function drag(e) {
if (!isDragging) return;
marker.setLatLng(e.latlng);
L.DomEvent.stopPropagation(e);
L.DomEvent.preventDefault(e);
}
function endDrag(e) {
if (!isDragging) return;
isDragging = false;
map.dragging.enable();
marker.off('mousemove', drag);
map.off('mousemove', drag);
marker.off('mouseup', endDrag);
map.off('mouseup', endDrag);
L.DomEvent.stopPropagation(e);
L.DomEvent.preventDefault(e);
}
const popupContent = createPopupContent(location); const popupContent = createPopupContent(location);
marker.bindPopup(popupContent); marker.bindPopup(popupContent);
marker._locationData = location; marker._locationData = location;
@ -120,6 +183,9 @@ function createPopupContent(location) {
const supportLevel = location['Support Level'] ? const supportLevel = location['Support Level'] ?
`Level ${location['Support Level']}` : 'Not specified'; `Level ${location['Support Level']}` : 'Not specified';
// Add debugging
console.log('Creating popup for location:', locationId, location);
return ` return `
<div class="popup-content"> <div class="popup-content">
<h3>${escapeHtml(name)}</h3> <h3>${escapeHtml(name)}</h3>
@ -136,6 +202,10 @@ function createPopupContent(location) {
data-location='${escapeHtml(JSON.stringify(location))}'> data-location='${escapeHtml(JSON.stringify(location))}'>
Edit Edit
</button> </button>
<button class="btn btn-primary btn-sm move-location-popup-btn"
data-location='${escapeHtml(JSON.stringify(location))}'>
📍 Move
</button>
</div> </div>
` : ''} ` : ''}
</div> </div>
@ -388,3 +458,198 @@ export function openAddModal(lat, lng) {
}); });
document.dispatchEvent(autoLookupEvent); document.dispatchEvent(autoLookupEvent);
} }
// Replace the startMovingMarker function
export function startMovingMarker(location, marker) {
console.log('startMovingMarker called with:', location, marker);
if (!location) {
console.error('Missing location data');
return;
}
const locationId = location.Id || location.id || location.ID || location._id;
if (!locationId) {
showStatus('Error: Location ID not found', 'error');
return;
}
// Store the location data and original position
movingLocationData = location;
originalPosition = marker ? marker.getLatLng() : null;
isMovingMarker = true;
// Close any popups
map.closePopup();
// Show crosshairs
const crosshair = document.getElementById('crosshair');
const crosshairInfo = crosshair.querySelector('.crosshair-info');
crosshairInfo.textContent = 'Click to move location here';
crosshair.classList.remove('hidden');
// Update buttons to show cancel state - use event system
document.dispatchEvent(new CustomEvent('updateMoveButtons', { detail: { isMoving: true } }));
// Show instructions
const name = [location['First Name'], location['Last Name']]
.filter(Boolean).join(' ') || 'Location';
showStatus(`Moving "${name}". Click anywhere on the map to set new position.`, 'info');
// Add click handler to map
map.on('click', handleMoveMapClick);
// Highlight the marker being moved
if (marker) {
movingMarker = marker;
marker.setStyle({
fillColor: '#ff7800',
fillOpacity: 0.9,
radius: 10
});
}
}
// Add new function to handle map clicks during move
function handleMoveMapClick(e) {
if (!isMovingMarker || !movingLocationData) return;
const { lat, lng } = e.latlng;
// Show confirmation dialog
showMoveConfirmation(lat, lng);
}
// Add function to show move confirmation
function showMoveConfirmation(lat, lng) {
const name = [movingLocationData['First Name'], movingLocationData['Last Name']]
.filter(Boolean).join(' ') || 'Location';
// Create a temporary marker at the new position
const tempMarker = L.circleMarker([lat, lng], {
radius: 10,
fillColor: '#ff7800',
color: '#fff',
weight: 3,
opacity: 1,
fillOpacity: 0.5
}).addTo(map);
// Create confirmation popup
const confirmContent = `
<div class="move-confirm-popup">
<h3>Confirm Move</h3>
<p>Move "${escapeHtml(name)}" to this location?</p>
<div class="popup-actions">
<button id="confirm-move-btn" class="btn btn-primary btn-sm"> Confirm</button>
<button id="cancel-move-btn" class="btn btn-secondary btn-sm"> Cancel</button>
</div>
</div>
`;
tempMarker.bindPopup(confirmContent, {
closeButton: false,
className: 'move-confirm-popup-wrapper'
}).openPopup();
// Add event listeners
setTimeout(() => {
document.getElementById('confirm-move-btn')?.addEventListener('click', async () => {
map.removeLayer(tempMarker);
await saveMovePosition(lat, lng);
});
document.getElementById('cancel-move-btn')?.addEventListener('click', () => {
map.removeLayer(tempMarker);
// Continue move mode
});
}, 100);
}
// Update saveMovePosition to accept coordinates
async function saveMovePosition(lat, lng) {
if (!movingLocationData) return;
const locationId = movingLocationData.Id || movingLocationData.id ||
movingLocationData.ID || movingLocationData._id;
if (!locationId) {
showStatus('Error: Location ID not found', 'error');
cancelMove();
return;
}
// Prepare update data
const updateData = {
latitude: lat.toFixed(8),
longitude: lng.toFixed(8),
'Geo-Location': `${lat.toFixed(8)};${lng.toFixed(8)}`
};
try {
const response = await fetch(`/api/locations/${locationId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(updateData)
});
const result = await response.json();
if (result.success) {
showStatus('Location moved successfully!', 'success');
cleanupMoveState(); // Use cleanup instead of cancelMove
// Reload locations to update the map
await loadLocations();
} else {
throw new Error(result.error || 'Failed to update location');
}
} catch (error) {
console.error('Error moving location:', error);
showStatus(`Failed to move location: ${error.message}`, 'error');
cancelMove();
}
}
// Add new function to clean up move state without showing cancellation message
function cleanupMoveState() {
// Hide crosshairs
const crosshair = document.getElementById('crosshair');
crosshair.classList.add('hidden');
// Remove map click handler
map.off('click', handleMoveMapClick);
// Reset buttons - use event system
document.dispatchEvent(new CustomEvent('updateMoveButtons', { detail: { isMoving: false } }));
// Reset marker style if exists
if (movingMarker && originalPosition) {
// Restore original marker style based on support level
const location = movingMarker._locationData;
let markerColor = '#3388ff';
if (location && location['Support Level']) {
const level = parseInt(location['Support Level']);
switch(level) {
case 1: markerColor = '#27ae60'; break;
case 2: markerColor = '#f1c40f'; break;
case 3: markerColor = '#e67e22'; break;
case 4: markerColor = '#e74c3c'; break;
}
}
movingMarker.setStyle({
fillColor: markerColor,
fillOpacity: 0.8,
radius: 8
});
}
// Reset state
isMovingMarker = false;
movingMarker = null;
originalPosition = null;
movingLocationData = null;
}

View File

@ -8,6 +8,9 @@ export let isAddingLocation = false;
export let isAddressConfirmed = false; export let isAddressConfirmed = false;
export let isEditAddressConfirmed = false; export let isEditAddressConfirmed = false;
// Add this after the existing imports
let isMoveMode = false;
// Export function to get current confirmation states // Export function to get current confirmation states
export function getAddressConfirmationState() { export function getAddressConfirmationState() {
return { return {
@ -509,5 +512,178 @@ export function setupEventListeners() {
showStatus('Error opening edit form', 'error'); showStatus('Error opening edit form', 'error');
} }
} }
// Add handler for move button
if (e.target.classList.contains('move-location-popup-btn')) {
e.preventDefault();
try {
const locationData = JSON.parse(e.target.getAttribute('data-location'));
// Find the marker that corresponds to this location
import('./location-manager.js').then(({ markers, startMovingMarker }) => {
const marker = markers.find(m => {
const markerData = m._locationData;
const locationId = locationData.Id || locationData.id || locationData.ID || locationData._id;
const markerId = markerData?.Id || markerData?.id || markerData?.ID || markerData?._id;
return markerId === locationId;
});
if (marker) {
startMovingMarker(locationData, marker);
} else {
console.error('Could not find marker for location:', locationData);
showStatus('Could not find marker for this location', 'error');
}
});
} catch (error) {
console.error('Error starting move:', error);
showStatus('Failed to start move mode', 'error');
}
}
}); });
// Improve popup button handling for mobile
map.on('popupopen', function(e) {
const popup = e.popup;
const content = popup.getContent();
// Create a temporary container to parse the HTML
const tempDiv = document.createElement('div');
tempDiv.innerHTML = content;
// Find buttons in the popup
const editBtn = tempDiv.querySelector('.edit-location-popup-btn');
const moveBtn = tempDiv.querySelector('.move-location-popup-btn');
if (editBtn || moveBtn) {
// Re-render popup with proper event handlers
setTimeout(() => {
const popupNode = popup._contentNode || popup._container;
if (!popupNode) return;
// Edit button
const editButton = popupNode.querySelector('.edit-location-popup-btn');
if (editButton) {
editButton.addEventListener('click', function(event) {
event.preventDefault();
event.stopPropagation();
try {
const locationData = JSON.parse(this.getAttribute('data-location'));
openEditForm(locationData);
map.closePopup();
} catch (error) {
console.error('Error parsing location data:', error);
showStatus('Error opening edit form', 'error');
}
});
}
// Move button
const moveButton = popupNode.querySelector('.move-location-popup-btn');
if (moveButton) {
moveButton.addEventListener('click', async function(event) {
event.preventDefault();
event.stopPropagation();
try {
const locationData = JSON.parse(this.getAttribute('data-location'));
const { startMovingMarker } = await import('./location-manager.js');
startMovingMarker(locationData, e.popup._source);
} catch (error) {
console.error('Error starting move:', error);
showStatus('Failed to start move mode', 'error');
}
});
}
}, 0);
}
});
// Add this after the existing event listeners
document.addEventListener('updateMoveButtons', (e) => {
isMoveMode = e.detail.isMoving;
if (isMoveMode) {
// Hide add location buttons during move mode
const addBtn = document.getElementById('add-location-btn');
const mobileAddBtn = document.getElementById('mobile-add-location-btn');
if (addBtn) addBtn.style.display = 'none';
if (mobileAddBtn) mobileAddBtn.style.display = 'none';
// Add cancel move button
addCancelMoveButton();
} else {
// Restore add location buttons
const addBtn = document.getElementById('add-location-btn');
const mobileAddBtn = document.getElementById('mobile-add-location-btn');
if (addBtn) addBtn.style.display = '';
if (mobileAddBtn) mobileAddBtn.style.display = '';
// Remove cancel move button
removeCancelMoveButton();
}
});
}
// Add this to the setupMapEventListeners function
export function setupMapEventListeners(mapInstance) {
// ...existing code...
// Handle move location button clicks in popups
mapInstance.on('popupopen', function(e) {
// ...existing code for edit button...
// Handle move button
const moveBtn = e.popup._contentNode?.querySelector('.move-location-popup-btn');
if (moveBtn) {
moveBtn.addEventListener('click', async function() {
try {
const locationData = JSON.parse(this.getAttribute('data-location'));
const marker = e.popup._source;
const { startMovingMarker } = await import('./location-manager.js');
startMovingMarker(locationData, marker);
} catch (error) {
console.error('Error starting move:', error);
showStatus('Failed to start move mode', 'error');
}
});
}
});
}
function addCancelMoveButton() {
// Desktop button
const mapControls = document.querySelector('.map-controls');
if (mapControls && !document.getElementById('cancel-move-btn')) {
const cancelBtn = document.createElement('button');
cancelBtn.id = 'cancel-move-btn';
cancelBtn.className = 'btn btn-danger';
cancelBtn.innerHTML = '<span class="btn-icon">✕</span><span class="btn-text">Cancel Move</span>';
cancelBtn.addEventListener('click', async () => {
const { cancelMove } = await import('./location-manager.js');
cancelMove();
});
mapControls.insertBefore(cancelBtn, mapControls.firstChild);
}
// Mobile button
const mobileSidebar = document.getElementById('mobile-sidebar');
if (mobileSidebar && !document.getElementById('mobile-cancel-move-btn')) {
const mobileCancelBtn = document.createElement('button');
mobileCancelBtn.id = 'mobile-cancel-move-btn';
mobileCancelBtn.className = 'btn btn-danger';
mobileCancelBtn.title = 'Cancel Move';
mobileCancelBtn.innerHTML = '✕';
mobileCancelBtn.addEventListener('click', async () => {
const { cancelMove } = await import('./location-manager.js');
cancelMove();
});
mobileSidebar.insertBefore(mobileCancelBtn, mobileSidebar.firstChild);
}
}
function removeCancelMoveButton() {
document.getElementById('cancel-move-btn')?.remove();
document.getElementById('mobile-cancel-move-btn')?.remove();
} }