Spaces:
Running
Running
Fix map UI/UX issues: proper SVG pins, remove emojis, improve tooltips and performance
Browse files- Replace emoji pins with professional SVG markers that properly anchor to map
- Remove View button from popups - tooltip already shows tree info
- Remove ALL emojis for professional appearance
- Fix pin floating issue with proper iconAnchor configuration
- Larger, more visible pins (28x36px for trees, 24x32px for temp/user)
- Enhanced tooltip with creator info and better formatting
- Simplified popup content with cleaner layout
- Performance optimizations with debounced map fitting
- Professional color-coded markers (green=trees, red=temp, blue=user)
- Smooth hover animations and proper drop shadows
- Fixed redirect to main form page instead of static path
- static/map.html +91 -11
- static/map.js +81 -171
static/map.html
CHANGED
@@ -664,6 +664,86 @@
|
|
664 |
25%, 75% { opacity: 1; }
|
665 |
}
|
666 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
667 |
/* Focus States */
|
668 |
.btn:focus-visible,
|
669 |
.control-button:focus-visible,
|
@@ -693,17 +773,17 @@
|
|
693 |
<div class="header">
|
694 |
<div class="header-content">
|
695 |
<div class="header-brand">
|
696 |
-
<div class="logo"
|
697 |
</div>
|
698 |
|
699 |
<div class="header-stats">
|
700 |
<div class="stat-item">
|
701 |
-
<span class="stat-icon"
|
702 |
<span class="stat-text">Trees:</span>
|
703 |
<span class="stat-number" id="treeCount">0</span>
|
704 |
</div>
|
705 |
<div class="stat-item">
|
706 |
-
<span class="stat-icon"
|
707 |
<span class="stat-text">Selected:</span>
|
708 |
<span class="stat-number" id="selectedCount">0</span>
|
709 |
</div>
|
@@ -717,7 +797,7 @@
|
|
717 |
<div class="user-role" id="userRole">User</div>
|
718 |
</div>
|
719 |
</div>
|
720 |
-
<a href="/" class="btn btn-secondary"
|
721 |
<button id="logoutBtn" class="btn btn-secondary">Logout</button>
|
722 |
</div>
|
723 |
</div>
|
@@ -735,15 +815,15 @@
|
|
735 |
<div class="controls-content">
|
736 |
<div class="control-group">
|
737 |
<button id="myLocationBtn" class="control-button">
|
738 |
-
<span
|
739 |
<span>My Location</span>
|
740 |
</button>
|
741 |
<button id="clearPinsBtn" class="control-button">
|
742 |
-
<span
|
743 |
<span>Clear Pins</span>
|
744 |
</button>
|
745 |
<button id="centerMapBtn" class="control-button">
|
746 |
-
<span
|
747 |
<span>Center View</span>
|
748 |
</button>
|
749 |
</div>
|
@@ -753,7 +833,7 @@
|
|
753 |
<!-- Location Panel -->
|
754 |
<div class="location-panel" id="locationPanel">
|
755 |
<div class="location-header">
|
756 |
-
<h3 class="location-title"
|
757 |
</div>
|
758 |
<div class="location-content">
|
759 |
<div class="coordinates-grid">
|
@@ -768,10 +848,10 @@
|
|
768 |
</div>
|
769 |
<div class="location-actions">
|
770 |
<button id="useLocationBtn" class="btn btn-primary">
|
771 |
-
|
772 |
</button>
|
773 |
<button id="cancelBtn" class="btn btn-secondary">
|
774 |
-
|
775 |
</button>
|
776 |
</div>
|
777 |
</div>
|
@@ -788,7 +868,7 @@
|
|
788 |
|
789 |
<!-- Gesture Hint -->
|
790 |
<div class="gesture-hint">
|
791 |
-
|
792 |
</div>
|
793 |
</div>
|
794 |
</div>
|
|
|
664 |
25%, 75% { opacity: 1; }
|
665 |
}
|
666 |
|
667 |
+
/* Custom Map Markers */
|
668 |
+
.map-marker {
|
669 |
+
position: relative;
|
670 |
+
display: flex;
|
671 |
+
align-items: center;
|
672 |
+
justify-content: center;
|
673 |
+
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2));
|
674 |
+
transition: transform 0.2s ease, filter 0.2s ease;
|
675 |
+
}
|
676 |
+
|
677 |
+
.map-marker:hover {
|
678 |
+
transform: scale(1.1);
|
679 |
+
filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.3));
|
680 |
+
}
|
681 |
+
|
682 |
+
.tree-marker svg {
|
683 |
+
filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.3));
|
684 |
+
}
|
685 |
+
|
686 |
+
.temp-marker svg {
|
687 |
+
animation: pulse-marker 2s infinite;
|
688 |
+
}
|
689 |
+
|
690 |
+
.user-marker svg {
|
691 |
+
filter: drop-shadow(0 2px 6px rgba(59, 130, 246, 0.5));
|
692 |
+
}
|
693 |
+
|
694 |
+
@keyframes pulse-marker {
|
695 |
+
0%, 100% {
|
696 |
+
opacity: 1;
|
697 |
+
transform: scale(1);
|
698 |
+
}
|
699 |
+
50% {
|
700 |
+
opacity: 0.8;
|
701 |
+
transform: scale(1.05);
|
702 |
+
}
|
703 |
+
}
|
704 |
+
|
705 |
+
/* Tree Tooltip Improvements */
|
706 |
+
.tree-tooltip {
|
707 |
+
background: rgba(255, 255, 255, 0.95) !important;
|
708 |
+
border: 1px solid var(--gray-200) !important;
|
709 |
+
border-radius: var(--radius-md) !important;
|
710 |
+
box-shadow: var(--shadow-lg) !important;
|
711 |
+
backdrop-filter: blur(8px);
|
712 |
+
font-size: 0.8125rem;
|
713 |
+
padding: var(--space-2) var(--space-3) !important;
|
714 |
+
max-width: 200px;
|
715 |
+
}
|
716 |
+
|
717 |
+
.tree-tooltip-content {
|
718 |
+
text-align: center;
|
719 |
+
}
|
720 |
+
|
721 |
+
.tree-name {
|
722 |
+
font-weight: 600;
|
723 |
+
color: var(--gray-900);
|
724 |
+
margin-bottom: var(--space-1);
|
725 |
+
}
|
726 |
+
|
727 |
+
.tree-details {
|
728 |
+
font-size: 0.75rem;
|
729 |
+
color: var(--gray-600);
|
730 |
+
}
|
731 |
+
|
732 |
+
.tree-creator {
|
733 |
+
font-size: 0.7rem;
|
734 |
+
color: var(--gray-500);
|
735 |
+
font-style: italic;
|
736 |
+
}
|
737 |
+
|
738 |
+
/* Performance optimizations */
|
739 |
+
.leaflet-tile-pane {
|
740 |
+
filter: none;
|
741 |
+
}
|
742 |
+
|
743 |
+
.leaflet-overlay-pane {
|
744 |
+
transform: translate3d(0, 0, 0);
|
745 |
+
}
|
746 |
+
|
747 |
/* Focus States */
|
748 |
.btn:focus-visible,
|
749 |
.control-button:focus-visible,
|
|
|
773 |
<div class="header">
|
774 |
<div class="header-content">
|
775 |
<div class="header-brand">
|
776 |
+
<div class="logo">TreeTrack Map</div>
|
777 |
</div>
|
778 |
|
779 |
<div class="header-stats">
|
780 |
<div class="stat-item">
|
781 |
+
<span class="stat-icon">T</span>
|
782 |
<span class="stat-text">Trees:</span>
|
783 |
<span class="stat-number" id="treeCount">0</span>
|
784 |
</div>
|
785 |
<div class="stat-item">
|
786 |
+
<span class="stat-icon">S</span>
|
787 |
<span class="stat-text">Selected:</span>
|
788 |
<span class="stat-number" id="selectedCount">0</span>
|
789 |
</div>
|
|
|
797 |
<div class="user-role" id="userRole">User</div>
|
798 |
</div>
|
799 |
</div>
|
800 |
+
<a href="/" class="btn btn-secondary">Add Tree</a>
|
801 |
<button id="logoutBtn" class="btn btn-secondary">Logout</button>
|
802 |
</div>
|
803 |
</div>
|
|
|
815 |
<div class="controls-content">
|
816 |
<div class="control-group">
|
817 |
<button id="myLocationBtn" class="control-button">
|
818 |
+
<span>Loc</span>
|
819 |
<span>My Location</span>
|
820 |
</button>
|
821 |
<button id="clearPinsBtn" class="control-button">
|
822 |
+
<span>Clr</span>
|
823 |
<span>Clear Pins</span>
|
824 |
</button>
|
825 |
<button id="centerMapBtn" class="control-button">
|
826 |
+
<span>Ctr</span>
|
827 |
<span>Center View</span>
|
828 |
</button>
|
829 |
</div>
|
|
|
833 |
<!-- Location Panel -->
|
834 |
<div class="location-panel" id="locationPanel">
|
835 |
<div class="location-header">
|
836 |
+
<h3 class="location-title">Location Selected</h3>
|
837 |
</div>
|
838 |
<div class="location-content">
|
839 |
<div class="coordinates-grid">
|
|
|
848 |
</div>
|
849 |
<div class="location-actions">
|
850 |
<button id="useLocationBtn" class="btn btn-primary">
|
851 |
+
Use This Location
|
852 |
</button>
|
853 |
<button id="cancelBtn" class="btn btn-secondary">
|
854 |
+
Cancel
|
855 |
</button>
|
856 |
</div>
|
857 |
</div>
|
|
|
868 |
|
869 |
<!-- Gesture Hint -->
|
870 |
<div class="gesture-hint">
|
871 |
+
Tap anywhere on the map to drop a location pin
|
872 |
</div>
|
873 |
</div>
|
874 |
</div>
|
static/map.js
CHANGED
@@ -260,20 +260,20 @@ class TreeTrackMap {
|
|
260 |
// Remove existing temp marker
|
261 |
this.clearTempMarker();
|
262 |
|
263 |
-
// Create new temp marker with
|
264 |
const tempIcon = L.divIcon({
|
265 |
html: `
|
266 |
-
<div class="
|
267 |
-
<
|
268 |
-
|
269 |
-
|
270 |
-
|
271 |
</div>
|
272 |
`,
|
273 |
-
className: 'custom-
|
274 |
-
iconSize: [
|
275 |
-
iconAnchor: [
|
276 |
-
popupAnchor: [0, -
|
277 |
});
|
278 |
|
279 |
this.tempMarker = L.marker([e.latlng.lat, e.latlng.lng], { icon: tempIcon }).addTo(this.map);
|
@@ -325,13 +325,13 @@ class TreeTrackMap {
|
|
325 |
this.showMessage('Location saved! Redirecting to form...', 'success');
|
326 |
|
327 |
setTimeout(() => {
|
328 |
-
window.location.href = '/
|
329 |
-
},
|
330 |
}
|
331 |
|
332 |
cancelLocationSelection() {
|
333 |
this.clearTempMarker();
|
334 |
-
this.showMessage('Selection cancelled', '
|
335 |
}
|
336 |
|
337 |
centerMapToTrees() {
|
@@ -343,7 +343,7 @@ class TreeTrackMap {
|
|
343 |
// Fit map to show all trees
|
344 |
const group = new L.featureGroup(this.treeMarkers);
|
345 |
this.map.fitBounds(group.getBounds().pad(0.1));
|
346 |
-
this.showMessage('
|
347 |
}
|
348 |
|
349 |
getCurrentLocation() {
|
@@ -377,17 +377,17 @@ class TreeTrackMap {
|
|
377 |
|
378 |
const userIcon = L.divIcon({
|
379 |
html: `
|
380 |
-
<div class="
|
381 |
-
<
|
382 |
-
|
383 |
-
|
384 |
-
|
385 |
</div>
|
386 |
`,
|
387 |
-
className: 'custom-
|
388 |
-
iconSize: [
|
389 |
-
iconAnchor: [
|
390 |
-
popupAnchor: [0, -
|
391 |
});
|
392 |
|
393 |
this.userLocationMarker = L.marker([lat, lng], { icon: userIcon }).addTo(this.map);
|
@@ -400,7 +400,7 @@ class TreeTrackMap {
|
|
400 |
className: 'tree-tooltip'
|
401 |
});
|
402 |
|
403 |
-
this.showMessage('
|
404 |
|
405 |
myLocationBtn.textContent = 'My Location';
|
406 |
myLocationBtn.disabled = false;
|
@@ -454,11 +454,13 @@ class TreeTrackMap {
|
|
454 |
// Update tree count
|
455 |
document.getElementById('treeCount').textContent = trees.length;
|
456 |
|
457 |
-
|
458 |
-
|
|
|
459 |
const group = new L.featureGroup(this.treeMarkers);
|
460 |
this.map.fitBounds(group.getBounds().pad(0.1));
|
461 |
-
}
|
|
|
462 |
|
463 |
} catch (error) {
|
464 |
console.error('Error loading trees:', error);
|
@@ -469,17 +471,17 @@ class TreeTrackMap {
|
|
469 |
addTreeMarker(tree) {
|
470 |
const treeIcon = L.divIcon({
|
471 |
html: `
|
472 |
-
<div class="
|
473 |
-
<
|
474 |
-
|
475 |
-
|
476 |
-
|
477 |
</div>
|
478 |
`,
|
479 |
-
className: 'custom-
|
480 |
-
iconSize: [
|
481 |
-
iconAnchor: [
|
482 |
-
popupAnchor: [0, -
|
483 |
});
|
484 |
|
485 |
const marker = L.marker([tree.latitude, tree.longitude], { icon: treeIcon }).addTo(this.map);
|
@@ -489,7 +491,8 @@ class TreeTrackMap {
|
|
489 |
const tooltipContent = `
|
490 |
<div class="tree-tooltip-content">
|
491 |
<div class="tree-name">${treeName}</div>
|
492 |
-
<div class="tree-details">ID: ${tree.id}${tree.tree_code ? '
|
|
|
493 |
</div>
|
494 |
`;
|
495 |
|
@@ -500,62 +503,63 @@ class TreeTrackMap {
|
|
500 |
className: 'tree-tooltip'
|
501 |
});
|
502 |
|
503 |
-
// Enhanced popup with action buttons
|
504 |
const canEdit = this.canEditTree(tree.created_by);
|
505 |
const canDelete = this.canDeleteTree(tree.created_by);
|
506 |
|
507 |
const popupContent = `
|
508 |
-
<div style="min-width:
|
509 |
-
<div style="border-bottom: 1px solid #e5e7eb; padding-bottom:
|
510 |
-
<h3 style="margin: 0 0 8px 0; color: #1e40af; font-size:
|
511 |
${treeName}
|
512 |
</h3>
|
513 |
-
<div style="color: #6b7280; font-size:
|
514 |
<strong>Tree ID:</strong> #${tree.id}${tree.tree_code ? ' (' + tree.tree_code + ')' : ''}
|
515 |
</div>
|
516 |
</div>
|
517 |
|
518 |
-
<div style="
|
519 |
-
<div
|
520 |
-
|
521 |
-
|
522 |
-
|
523 |
-
|
524 |
-
|
525 |
-
|
526 |
-
|
527 |
-
|
528 |
-
|
529 |
-
<div>${new Date(tree.created_at).toLocaleDateString()}</div>
|
530 |
</div>
|
531 |
|
532 |
${tree.notes ? `
|
533 |
-
<div style="margin-bottom: 12px;">
|
534 |
-
<strong style="color: #374151; font-size:
|
535 |
-
<div style="color: #6b7280; font-size:
|
536 |
-
${tree.notes
|
537 |
</div>
|
538 |
</div>
|
539 |
` : ''}
|
540 |
|
541 |
-
|
542 |
-
|
543 |
-
|
544 |
-
|
545 |
-
|
546 |
-
|
547 |
-
|
548 |
-
|
549 |
-
|
550 |
-
|
551 |
-
|
552 |
-
|
553 |
-
|
554 |
-
|
555 |
-
|
556 |
-
|
557 |
-
|
558 |
-
|
|
|
|
|
559 |
</div>
|
560 |
`;
|
561 |
|
@@ -613,7 +617,7 @@ class TreeTrackMap {
|
|
613 |
if (!response) return;
|
614 |
|
615 |
if (response.ok) {
|
616 |
-
this.showMessage(
|
617 |
|
618 |
// Reload trees to update the map
|
619 |
setTimeout(() => {
|
@@ -633,100 +637,6 @@ class TreeTrackMap {
|
|
633 |
}
|
634 |
}
|
635 |
|
636 |
-
async viewTreeDetails(treeId) {
|
637 |
-
try {
|
638 |
-
const response = await this.authenticatedFetch(`/api/trees/${treeId}`);
|
639 |
-
if (!response) return;
|
640 |
-
|
641 |
-
if (!response.ok) {
|
642 |
-
throw new Error('Failed to fetch tree data');
|
643 |
-
}
|
644 |
-
|
645 |
-
const tree = await response.json();
|
646 |
-
|
647 |
-
// Create detailed view popup
|
648 |
-
const detailContent = `
|
649 |
-
<div style="max-width: 350px; font-family: 'Segoe UI', sans-serif;">
|
650 |
-
<div style="border-bottom: 2px solid #1e40af; padding-bottom: 12px; margin-bottom: 16px;">
|
651 |
-
<h2 style="margin: 0; color: #1e40af; font-size: 18px; font-weight: 700;">
|
652 |
-
🌳 ${tree.scientific_name || tree.common_name || tree.local_name || 'Unknown Tree'}
|
653 |
-
</h2>
|
654 |
-
<div style="color: #6b7280; font-size: 14px; margin-top: 4px;">
|
655 |
-
Tree #${tree.id}${tree.tree_code ? ' • Code: ' + tree.tree_code : ''}
|
656 |
-
</div>
|
657 |
-
</div>
|
658 |
-
|
659 |
-
<div style="margin-bottom: 16px;">
|
660 |
-
<div style="display: grid; grid-template-columns: auto 1fr; gap: 8px 16px; font-size: 13px;">
|
661 |
-
${tree.local_name ? `<strong>🏷️ Local Name:</strong><span>${tree.local_name}</span>` : ''}
|
662 |
-
${tree.scientific_name ? `<strong>🔬 Scientific:</strong><span><em>${tree.scientific_name}</em></span>` : ''}
|
663 |
-
${tree.common_name ? `<strong>🌍 Common:</strong><span>${tree.common_name}</span>` : ''}
|
664 |
-
${tree.height ? `<strong>📏 Height:</strong><span>${tree.height} meters</span>` : ''}
|
665 |
-
${tree.width ? `<strong>📐 Girth:</strong><span>${tree.width} cm</span>` : ''}
|
666 |
-
<strong>📍 Coordinates:</strong><span>${tree.latitude.toFixed(6)}, ${tree.longitude.toFixed(6)}</span>
|
667 |
-
<strong>👤 Created by:</strong><span>${tree.created_by || 'Unknown'}</span>
|
668 |
-
<strong>📅 Date:</strong><span>${new Date(tree.created_at).toLocaleDateString()}</span>
|
669 |
-
</div>
|
670 |
-
</div>
|
671 |
-
|
672 |
-
${tree.utility && tree.utility.length > 0 ? `
|
673 |
-
<div style="margin-bottom: 12px;">
|
674 |
-
<strong style="color: #059669; font-size: 14px;">🌿 Ecological Uses:</strong>
|
675 |
-
<div style="margin-top: 4px;">
|
676 |
-
${tree.utility.map(u => `<span style="display: inline-block; background: #dcfce7; color: #166534; padding: 2px 6px; border-radius: 4px; font-size: 11px; margin: 2px;">${u}</span>`).join('')}
|
677 |
-
</div>
|
678 |
-
</div>
|
679 |
-
` : ''}
|
680 |
-
|
681 |
-
${tree.phenology_stages && tree.phenology_stages.length > 0 ? `
|
682 |
-
<div style="margin-bottom: 12px;">
|
683 |
-
<strong style="color: #7c3aed; font-size: 14px;">🌸 Current Stages:</strong>
|
684 |
-
<div style="margin-top: 4px;">
|
685 |
-
${tree.phenology_stages.map(stage => `<span style="display: inline-block; background: #ede9fe; color: #6b21a8; padding: 2px 6px; border-radius: 4px; font-size: 11px; margin: 2px;">${stage}</span>`).join('')}
|
686 |
-
</div>
|
687 |
-
</div>
|
688 |
-
` : ''}
|
689 |
-
|
690 |
-
${tree.storytelling_text ? `
|
691 |
-
<div style="margin-bottom: 12px;">
|
692 |
-
<strong style="color: #ea580c; font-size: 14px;">📖 Stories & Culture:</strong>
|
693 |
-
<div style="color: #6b7280; font-size: 12px; margin-top: 4px; line-height: 1.4; max-height: 80px; overflow-y: auto; padding: 8px; background: #f9fafb; border-radius: 6px;">
|
694 |
-
${tree.storytelling_text}
|
695 |
-
</div>
|
696 |
-
</div>
|
697 |
-
` : ''}
|
698 |
-
|
699 |
-
${tree.notes ? `
|
700 |
-
<div style="margin-bottom: 12px;">
|
701 |
-
<strong style="color: #374151; font-size: 14px;">📝 Notes:</strong>
|
702 |
-
<div style="color: #6b7280; font-size: 12px; margin-top: 4px; line-height: 1.4; padding: 8px; background: #f9fafb; border-radius: 6px;">
|
703 |
-
${tree.notes}
|
704 |
-
</div>
|
705 |
-
</div>
|
706 |
-
` : ''}
|
707 |
-
</div>
|
708 |
-
`;
|
709 |
-
|
710 |
-
// Close current popup and show detailed one
|
711 |
-
this.map.closePopup();
|
712 |
-
|
713 |
-
// Find the marker for this tree and open detailed popup
|
714 |
-
const treeMarker = this.treeMarkers.find(marker => {
|
715 |
-
return marker.getLatLng().lat === tree.latitude && marker.getLatLng().lng === tree.longitude;
|
716 |
-
});
|
717 |
-
|
718 |
-
if (treeMarker) {
|
719 |
-
treeMarker.bindPopup(detailContent, {
|
720 |
-
maxWidth: 400,
|
721 |
-
className: 'tree-popup'
|
722 |
-
}).openPopup();
|
723 |
-
}
|
724 |
-
|
725 |
-
} catch (error) {
|
726 |
-
console.error('Error viewing tree details:', error);
|
727 |
-
this.showMessage('Error loading tree details: ' + error.message, 'error');
|
728 |
-
}
|
729 |
-
}
|
730 |
|
731 |
showLoading() {
|
732 |
document.getElementById('loading').style.display = 'block';
|
|
|
260 |
// Remove existing temp marker
|
261 |
this.clearTempMarker();
|
262 |
|
263 |
+
// Create new temp marker with proper SVG icon
|
264 |
const tempIcon = L.divIcon({
|
265 |
html: `
|
266 |
+
<div class="map-marker temp-marker">
|
267 |
+
<svg width="24" height="32" viewBox="0 0 24 32" fill="none">
|
268 |
+
<path d="M12 0C5.37 0 0 5.37 0 12C0 21 12 32 12 32S24 21 24 12C24 5.37 18.63 0 12 0Z" fill="#ef4444"/>
|
269 |
+
<circle cx="12" cy="12" r="4" fill="white"/>
|
270 |
+
</svg>
|
271 |
</div>
|
272 |
`,
|
273 |
+
className: 'custom-marker-icon',
|
274 |
+
iconSize: [24, 32],
|
275 |
+
iconAnchor: [12, 32],
|
276 |
+
popupAnchor: [0, -32]
|
277 |
});
|
278 |
|
279 |
this.tempMarker = L.marker([e.latlng.lat, e.latlng.lng], { icon: tempIcon }).addTo(this.map);
|
|
|
325 |
this.showMessage('Location saved! Redirecting to form...', 'success');
|
326 |
|
327 |
setTimeout(() => {
|
328 |
+
window.location.href = '/';
|
329 |
+
}, 1000);
|
330 |
}
|
331 |
|
332 |
cancelLocationSelection() {
|
333 |
this.clearTempMarker();
|
334 |
+
this.showMessage('Selection cancelled', 'info');
|
335 |
}
|
336 |
|
337 |
centerMapToTrees() {
|
|
|
343 |
// Fit map to show all trees
|
344 |
const group = new L.featureGroup(this.treeMarkers);
|
345 |
this.map.fitBounds(group.getBounds().pad(0.1));
|
346 |
+
this.showMessage('Map centered on all trees', 'success');
|
347 |
}
|
348 |
|
349 |
getCurrentLocation() {
|
|
|
377 |
|
378 |
const userIcon = L.divIcon({
|
379 |
html: `
|
380 |
+
<div class="map-marker user-marker">
|
381 |
+
<svg width="24" height="32" viewBox="0 0 24 32" fill="none">
|
382 |
+
<path d="M12 0C5.37 0 0 5.37 0 12C0 21 12 32 12 32S24 21 24 12C24 5.37 18.63 0 12 0Z" fill="#3b82f6"/>
|
383 |
+
<circle cx="12" cy="12" r="4" fill="white"/>
|
384 |
+
</svg>
|
385 |
</div>
|
386 |
`,
|
387 |
+
className: 'custom-marker-icon',
|
388 |
+
iconSize: [24, 32],
|
389 |
+
iconAnchor: [12, 32],
|
390 |
+
popupAnchor: [0, -32]
|
391 |
});
|
392 |
|
393 |
this.userLocationMarker = L.marker([lat, lng], { icon: userIcon }).addTo(this.map);
|
|
|
400 |
className: 'tree-tooltip'
|
401 |
});
|
402 |
|
403 |
+
this.showMessage('Location found successfully', 'success');
|
404 |
|
405 |
myLocationBtn.textContent = 'My Location';
|
406 |
myLocationBtn.disabled = false;
|
|
|
454 |
// Update tree count
|
455 |
document.getElementById('treeCount').textContent = trees.length;
|
456 |
|
457 |
+
if (trees.length > 0) {
|
458 |
+
// Fit map to show all trees with debounced execution
|
459 |
+
setTimeout(() => {
|
460 |
const group = new L.featureGroup(this.treeMarkers);
|
461 |
this.map.fitBounds(group.getBounds().pad(0.1));
|
462 |
+
}, 100);
|
463 |
+
}
|
464 |
|
465 |
} catch (error) {
|
466 |
console.error('Error loading trees:', error);
|
|
|
471 |
addTreeMarker(tree) {
|
472 |
const treeIcon = L.divIcon({
|
473 |
html: `
|
474 |
+
<div class="map-marker tree-marker">
|
475 |
+
<svg width="28" height="36" viewBox="0 0 24 32" fill="none">
|
476 |
+
<path d="M12 0C5.37 0 0 5.37 0 12C0 21 12 32 12 32S24 21 24 12C24 5.37 18.63 0 12 0Z" fill="#22c55e"/>
|
477 |
+
<circle cx="12" cy="12" r="4" fill="white"/>
|
478 |
+
</svg>
|
479 |
</div>
|
480 |
`,
|
481 |
+
className: 'custom-marker-icon',
|
482 |
+
iconSize: [28, 36],
|
483 |
+
iconAnchor: [14, 36],
|
484 |
+
popupAnchor: [0, -36]
|
485 |
});
|
486 |
|
487 |
const marker = L.marker([tree.latitude, tree.longitude], { icon: treeIcon }).addTo(this.map);
|
|
|
491 |
const tooltipContent = `
|
492 |
<div class="tree-tooltip-content">
|
493 |
<div class="tree-name">${treeName}</div>
|
494 |
+
<div class="tree-details">ID: ${tree.id}${tree.tree_code ? ' | ' + tree.tree_code : ''}</div>
|
495 |
+
${tree.created_by ? `<div class="tree-creator">by ${tree.created_by}</div>` : ''}
|
496 |
</div>
|
497 |
`;
|
498 |
|
|
|
503 |
className: 'tree-tooltip'
|
504 |
});
|
505 |
|
506 |
+
// Enhanced popup with action buttons (no emojis)
|
507 |
const canEdit = this.canEditTree(tree.created_by);
|
508 |
const canDelete = this.canDeleteTree(tree.created_by);
|
509 |
|
510 |
const popupContent = `
|
511 |
+
<div style="min-width: 320px; font-family: 'Segoe UI', sans-serif;">
|
512 |
+
<div style="border-bottom: 1px solid #e5e7eb; padding-bottom: 16px; margin-bottom: 16px;">
|
513 |
+
<h3 style="margin: 0 0 8px 0; color: #1e40af; font-size: 18px; font-weight: 600;">
|
514 |
${treeName}
|
515 |
</h3>
|
516 |
+
<div style="color: #6b7280; font-size: 14px;">
|
517 |
<strong>Tree ID:</strong> #${tree.id}${tree.tree_code ? ' (' + tree.tree_code + ')' : ''}
|
518 |
</div>
|
519 |
</div>
|
520 |
|
521 |
+
<div style="margin-bottom: 16px;">
|
522 |
+
<div style="display: grid; grid-template-columns: auto 1fr; gap: 8px 16px; font-size: 14px;">
|
523 |
+
${tree.local_name ? `<strong>Local Name:</strong><span>${tree.local_name}</span>` : ''}
|
524 |
+
${tree.scientific_name ? `<strong>Scientific:</strong><span><em>${tree.scientific_name}</em></span>` : ''}
|
525 |
+
${tree.common_name ? `<strong>Common:</strong><span>${tree.common_name}</span>` : ''}
|
526 |
+
<strong>Location:</strong><span>${tree.latitude.toFixed(6)}, ${tree.longitude.toFixed(6)}</span>
|
527 |
+
${tree.height ? `<strong>Height:</strong><span>${tree.height} meters</span>` : ''}
|
528 |
+
${tree.width ? `<strong>Girth:</strong><span>${tree.width} cm</span>` : ''}
|
529 |
+
<strong>Added by:</strong><span>${tree.created_by || 'Unknown'}</span>
|
530 |
+
<strong>Date:</strong><span>${new Date(tree.created_at).toLocaleDateString()}</span>
|
531 |
+
</div>
|
|
|
532 |
</div>
|
533 |
|
534 |
${tree.notes ? `
|
535 |
+
<div style="margin-bottom: 16px; padding: 12px; background: #f9fafb; border-radius: 8px;">
|
536 |
+
<strong style="color: #374151; font-size: 14px; display: block; margin-bottom: 8px;">Notes:</strong>
|
537 |
+
<div style="color: #6b7280; font-size: 13px; line-height: 1.5;">
|
538 |
+
${tree.notes}
|
539 |
</div>
|
540 |
</div>
|
541 |
` : ''}
|
542 |
|
543 |
+
${canEdit || canDelete ? `
|
544 |
+
<div style="display: flex; gap: 8px; margin-top: 16px; padding-top: 16px; border-top: 1px solid #e5e7eb;">
|
545 |
+
${canEdit ? `
|
546 |
+
<button onclick="mapApp.editTree(${tree.id})"
|
547 |
+
style="flex: 1; background: #3b82f6; color: white; border: none; padding: 10px 16px; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 600; transition: background-color 0.2s;"
|
548 |
+
onmouseover="this.style.backgroundColor='#2563eb'"
|
549 |
+
onmouseout="this.style.backgroundColor='#3b82f6'">
|
550 |
+
Edit Tree
|
551 |
+
</button>
|
552 |
+
` : ''}
|
553 |
+
${canDelete ? `
|
554 |
+
<button onclick="mapApp.deleteTree(${tree.id})"
|
555 |
+
style="flex: 1; background: #ef4444; color: white; border: none; padding: 10px 16px; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 600; transition: background-color 0.2s;"
|
556 |
+
onmouseover="this.style.backgroundColor='#dc2626'"
|
557 |
+
onmouseout="this.style.backgroundColor='#ef4444'">
|
558 |
+
Delete Tree
|
559 |
+
</button>
|
560 |
+
` : ''}
|
561 |
+
</div>
|
562 |
+
` : ''}
|
563 |
</div>
|
564 |
`;
|
565 |
|
|
|
617 |
if (!response) return;
|
618 |
|
619 |
if (response.ok) {
|
620 |
+
this.showMessage(`Tree #${treeId} deleted successfully`, 'success');
|
621 |
|
622 |
// Reload trees to update the map
|
623 |
setTimeout(() => {
|
|
|
637 |
}
|
638 |
}
|
639 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
640 |
|
641 |
showLoading() {
|
642 |
document.getElementById('loading').style.display = 'block';
|