Merge pull request #1928 from Freika/fix/map-side-panel

Fix/map side panel
This commit is contained in:
Evgenii Burmakin 2025-11-08 20:00:45 +01:00 committed by GitHub
commit 28bc68ffe2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 89 additions and 41 deletions

File diff suppressed because one or more lines are too long

View file

@ -76,33 +76,46 @@
/* Drawer Panel Styles */ /* Drawer Panel Styles */
.leaflet-drawer { .leaflet-drawer {
position: absolute; position: absolute;
top: 0; top: 10px;
right: 0; right: 70px; /* Position to the left of the control buttons with margin */
width: 338px; width: 24rem;
height: 100%; max-height: calc(100% - 20px);
background: rgba(255, 255, 255, 0.5); background: rgba(255, 255, 255, 0.5);
transform: translateX(100%); border-radius: 8px;
transition: transform 0.3s ease-in-out; opacity: 0;
visibility: hidden;
transform: scale(0.95);
transition: opacity 0.2s ease-in-out, transform 0.2s ease-in-out, visibility 0.2s;
z-index: 450; z-index: 450;
box-shadow: -2px 0 5px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
height: auto; /* Make height fit content */
cursor: default; /* Override map cursor */
}
.leaflet-drawer * {
cursor: default; /* Ensure all children have default cursor */
}
.leaflet-drawer a,
.leaflet-drawer button,
.leaflet-drawer .btn,
.leaflet-drawer input[type="checkbox"] {
cursor: pointer; /* Interactive elements get pointer cursor */
} }
.leaflet-drawer.open { .leaflet-drawer.open {
transform: translateX(0); opacity: 1;
visibility: visible;
transform: scale(1);
} }
/* Controls transition */ /* Controls remain in place - no transition needed */
.leaflet-control-layers, .leaflet-control-layers,
.leaflet-control-button, .leaflet-control-button,
.toggle-panel-button { .toggle-panel-button {
transition: right 0.3s ease-in-out;
z-index: 500; z-index: 500;
} }
.controls-shifted {
right: 338px !important;
}
/* Selection Tool Styles */ /* Selection Tool Styles */
.leaflet-control-custom { .leaflet-control-custom {
background-color: white; background-color: white;
@ -127,6 +140,5 @@
/* Cancel Selection Button */ /* Cancel Selection Button */
#cancel-selection-button { #cancel-selection-button {
margin-bottom: 1rem;
width: 100%; width: 100%;
} }

View file

@ -369,7 +369,7 @@ export class VisitsManager {
const visitsCount = dateGroups[dateStr].count || 0; const visitsCount = dateGroups[dateStr].count || 0;
return ` return `
<div class="flex justify-between items-center py-1 border-b border-base-300 last:border-0 my-2 hover:bg-accent hover:text-accent-content transition-colors"> <div class="flex justify-between items-center py-1 border-b border-base-300 last:border-0 my-2 hover:bg-accent hover:text-accent-content transition-colors border-radius-md">
<div class="font-medium">${dateStr}</div> <div class="font-medium">${dateStr}</div>
<div class="flex gap-2"> <div class="flex gap-2">
${pointsCount > 0 ? `<div class="badge badge-secondary">${pointsCount} pts</div>` : ''} ${pointsCount > 0 ? `<div class="badge badge-secondary">${pointsCount} pts</div>` : ''}
@ -379,14 +379,18 @@ export class VisitsManager {
`; `;
}).join(''); }).join('');
// Create the whole panel // Create the whole panel with collapsible content
return ` return `
<div class="bg-base-100 rounded-lg p-3 mb-4 shadow-sm"> <details id="data-section-collapse" class="collapse collapse-arrow bg-base-100 rounded-lg mb-4 shadow-sm">
<h3 class="text-lg font-bold mb-2">Data in Selected Area</h3> <summary class="collapse-title text-lg font-bold">
<div class="divide-y divide-base-300"> Data in Selected Area
${dateItems} </summary>
<div class="collapse-content">
<div class="divide-y divide-base-300">
${dateItems}
</div>
</div> </div>
</div> </details>
`; `;
} }
@ -411,20 +415,20 @@ export class VisitsManager {
// Create a button container // Create a button container
const buttonContainer = document.createElement('div'); const buttonContainer = document.createElement('div');
buttonContainer.className = 'flex gap-2 mb-4'; buttonContainer.className = 'flex flex-col gap-2 mb-4';
buttonContainer.id = 'selection-button-container'; buttonContainer.id = 'selection-button-container';
// Cancel button // Cancel button
const cancelButton = document.createElement('button'); const cancelButton = document.createElement('button');
cancelButton.id = 'cancel-selection-button'; cancelButton.id = 'cancel-selection-button';
cancelButton.className = 'btn btn-sm btn-warning flex-1'; cancelButton.className = 'btn btn-sm btn-warning w-full';
cancelButton.textContent = 'Cancel Selection'; cancelButton.textContent = 'Cancel Selection';
cancelButton.onclick = () => this.clearSelection(); cancelButton.onclick = () => this.clearSelection();
// Delete all selected points button // Delete all selected points button
const deleteButton = document.createElement('button'); const deleteButton = document.createElement('button');
deleteButton.id = 'delete-selection-button'; deleteButton.id = 'delete-selection-button';
deleteButton.className = 'btn btn-sm btn-error flex-1'; deleteButton.className = 'btn btn-sm btn-error w-full';
deleteButton.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="inline mr-1"><path d="M3 6h18"></path><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path></svg>Delete Points'; deleteButton.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="inline mr-1"><path d="M3 6h18"></path><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path></svg>Delete Points';
deleteButton.onclick = () => this.deleteSelectedPoints(); deleteButton.onclick = () => this.deleteSelectedPoints();
@ -626,11 +630,6 @@ export class VisitsManager {
drawerButton.innerHTML = this.drawerOpen ? '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-panel-right-close-icon lucide-panel-right-close"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M15 3v18"/><path d="m8 9 3 3-3 3"/></svg>' : '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-panel-right-open-icon lucide-panel-right-open"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M15 3v18"/><path d="m10 15-3-3 3-3"/></svg>'; drawerButton.innerHTML = this.drawerOpen ? '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-panel-right-close-icon lucide-panel-right-close"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M15 3v18"/><path d="m8 9 3 3-3 3"/></svg>' : '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-panel-right-open-icon lucide-panel-right-open"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M15 3v18"/><path d="m10 15-3-3 3-3"/></svg>';
} }
const controls = document.querySelectorAll('.leaflet-control-layers, .toggle-panel-button, .leaflet-right-panel, .drawer-button, #selection-tool-button');
controls.forEach(control => {
control.classList.toggle('controls-shifted');
});
// Update the drawer content if it's being opened - but don't fetch visits automatically // Update the drawer content if it's being opened - but don't fetch visits automatically
// Only show the "no data" message if there's no selection active // Only show the "no data" message if there's no selection active
if (this.drawerOpen && !this.isSelectionActive) { if (this.drawerOpen && !this.isSelectionActive) {
@ -654,16 +653,18 @@ export class VisitsManager {
createDrawer() { createDrawer() {
const drawer = document.createElement('div'); const drawer = document.createElement('div');
drawer.id = 'visits-drawer'; drawer.id = 'visits-drawer';
drawer.className = 'fixed top-0 right-0 h-full w-64 bg-base-100 shadow-lg transform translate-x-full transition-transform duration-300 ease-in-out z-39 overflow-y-auto leaflet-drawer'; drawer.className = 'bg-base-100 shadow-lg z-39 overflow-y-auto leaflet-drawer';
// Add styles to make the drawer scrollable // Add styles to make the drawer scrollable
drawer.style.overflowY = 'auto'; drawer.style.overflowY = 'auto';
drawer.style.maxHeight = '100vh';
drawer.innerHTML = ` drawer.innerHTML = `
<div class="p-3 drawer"> <div class="p-3 my-2 drawer flex flex-col items-center relative">
<h2 class="text-xl font-bold mb-4 text-accent-content">Recent Visits</h2> <button id="close-visits-drawer" class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" title="Close panel">
<div id="visits-list" class="space-y-2"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-x-icon lucide-circle-x"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg>
</button>
<h2 class="text-xl font-bold mb-4 text-accent-content w-full text-center">Recent Visits</h2>
<div id="visits-list" class="space-y-2 w-full">
<p class="text-gray-500">Loading visits...</p> <p class="text-gray-500">Loading visits...</p>
</div> </div>
</div> </div>
@ -675,6 +676,15 @@ export class VisitsManager {
L.DomEvent.disableClickPropagation(drawer); L.DomEvent.disableClickPropagation(drawer);
this.map.getContainer().appendChild(drawer); this.map.getContainer().appendChild(drawer);
// Add close button event listener
const closeButton = drawer.querySelector('#close-visits-drawer');
if (closeButton) {
closeButton.addEventListener('click', () => {
this.toggleDrawer();
});
}
return drawer; return drawer;
} }
@ -833,6 +843,10 @@ export class VisitsManager {
return; return;
} }
// Save the current state of collapsible sections before updating
const dataSectionOpen = document.querySelector('#data-section-collapse')?.open || false;
const visitsSectionOpen = document.querySelector('#visits-section-collapse')?.open || false;
// Update the drawer title if selection is active // Update the drawer title if selection is active
if (this.isSelectionActive && this.selectionRect) { if (this.isSelectionActive && this.selectionRect) {
const visitsCount = visits ? visits.filter(visit => visit.status !== 'declined').length : 0; const visitsCount = visits ? visits.filter(visit => visit.status !== 'declined').length : 0;
@ -896,7 +910,7 @@ export class VisitsManager {
const visitStyle = visit.status === 'suggested' ? 'border: 2px dashed #60a5fa;' : ''; const visitStyle = visit.status === 'suggested' ? 'border: 2px dashed #60a5fa;' : '';
return ` return `
<div class="w-full p-3 m-2 rounded-lg hover:bg-base-300 transition-colors visit-item relative ${bgClass}" <div class="w-full p-3 mt-2 rounded-lg hover:bg-base-300 transition-colors visit-item relative ${bgClass}"
style="${visitStyle}" style="${visitStyle}"
data-lat="${visit.place?.latitude || ''}" data-lat="${visit.place?.latitude || ''}"
data-lng="${visit.place?.longitude || ''}" data-lng="${visit.place?.longitude || ''}"
@ -924,8 +938,31 @@ export class VisitsManager {
`; `;
}).join(''); }).join('');
// Wrap visits in a collapsible section
const visitsSection = visits && visits.length > 0 ? `
<details id="visits-section-collapse" class="collapse collapse-arrow bg-base-100 rounded-lg mb-4 shadow-sm">
<summary class="collapse-title text-lg font-bold">
Visits (${visits.filter(v => v.status !== 'declined').length})
</summary>
<div class="collapse-content">
${visitsHtml}
</div>
</details>
` : '';
// Combine date summary and visits HTML // Combine date summary and visits HTML
container.innerHTML = dateGroupsHtml + visitsHtml; container.innerHTML = dateGroupsHtml + visitsSection;
// Restore the state of collapsible sections
const dataSection = document.querySelector('#data-section-collapse');
const visitsSection2 = document.querySelector('#visits-section-collapse');
if (dataSection && dataSectionOpen) {
dataSection.open = true;
}
if (visitsSection2 && visitsSectionOpen) {
visitsSection2.open = true;
}
// Add the circles layer to the map // Add the circles layer to the map
this.visitCircles.addTo(this.map); this.visitCircles.addTo(this.map);

View file

@ -142,7 +142,6 @@ test.describe('Side Panel', () => {
const visitCount = await visitItems.count(); const visitCount = await visitItems.count();
const noVisitsMessage = page.locator('#visits-list p.text-gray-500'); const noVisitsMessage = page.locator('#visits-list p.text-gray-500');
const hasNoVisitsMessage = await noVisitsMessage.count() > 0;
// Either we have visits OR we have a "no visits" message (not "Loading...") // Either we have visits OR we have a "no visits" message (not "Loading...")
if (visitCount > 0) { if (visitCount > 0) {