diff --git a/app/javascript/maps/visits.js b/app/javascript/maps/visits.js
index 32c4af06..43e11b5e 100644
--- a/app/javascript/maps/visits.js
+++ b/app/javascript/maps/visits.js
@@ -227,7 +227,8 @@ export class VisitsManager {
this.toggleDrawer();
}
- // Add cancel selection button to the drawer
+ // Add cancel selection button to the drawer AFTER displayVisits
+ // This needs to be after because displayVisits sets innerHTML which would wipe out the buttons
this.addSelectionCancelButton();
} catch (error) {
@@ -390,42 +391,53 @@ export class VisitsManager {
*/
addSelectionCancelButton() {
const container = document.getElementById('visits-list');
- if (!container) return;
-
- // Add buttons at the top of the drawer if they don't exist
- if (!document.getElementById('cancel-selection-button')) {
- // Create a button container
- const buttonContainer = document.createElement('div');
- buttonContainer.className = 'flex gap-2 mb-4';
-
- // Cancel button
- const cancelButton = document.createElement('button');
- cancelButton.id = 'cancel-selection-button';
- cancelButton.className = 'btn btn-sm btn-warning flex-1';
- cancelButton.textContent = 'Cancel Selection';
- cancelButton.onclick = () => this.clearSelection();
-
- // Delete all selected points button
- const deleteButton = document.createElement('button');
- deleteButton.id = 'delete-selection-button';
- deleteButton.className = 'btn btn-sm btn-error flex-1';
- deleteButton.innerHTML = 'Delete Points';
- deleteButton.onclick = () => this.deleteSelectedPoints();
-
- // Add count badge if we have selected points
- if (this.selectedPoints && this.selectedPoints.length > 0) {
- const badge = document.createElement('span');
- badge.className = 'badge badge-sm ml-1';
- badge.textContent = this.selectedPoints.length;
- deleteButton.appendChild(badge);
- }
-
- buttonContainer.appendChild(cancelButton);
- buttonContainer.appendChild(deleteButton);
-
- // Insert at the beginning of the container
- container.insertBefore(buttonContainer, container.firstChild);
+ if (!container) {
+ console.warn('addSelectionCancelButton: visits-list container not found');
+ return;
}
+
+ // Remove any existing button container first to avoid duplicates
+ const existingButtonContainer = container.querySelector('.flex.gap-2.mb-4');
+ if (existingButtonContainer) {
+ existingButtonContainer.remove();
+ }
+
+ // Create a button container
+ const buttonContainer = document.createElement('div');
+ buttonContainer.className = 'flex gap-2 mb-4';
+ buttonContainer.id = 'selection-button-container';
+
+ // Cancel button
+ const cancelButton = document.createElement('button');
+ cancelButton.id = 'cancel-selection-button';
+ cancelButton.className = 'btn btn-sm btn-warning flex-1';
+ cancelButton.textContent = 'Cancel Selection';
+ cancelButton.onclick = () => this.clearSelection();
+
+ // Delete all selected points button
+ const deleteButton = document.createElement('button');
+ deleteButton.id = 'delete-selection-button';
+ deleteButton.className = 'btn btn-sm btn-error flex-1';
+ deleteButton.innerHTML = 'Delete Points';
+ deleteButton.onclick = () => this.deleteSelectedPoints();
+
+ // Add count badge if we have selected points
+ if (this.selectedPoints && this.selectedPoints.length > 0) {
+ const badge = document.createElement('span');
+ badge.className = 'badge badge-sm ml-1';
+ badge.textContent = this.selectedPoints.length;
+ deleteButton.appendChild(badge);
+ console.log(`addSelectionCancelButton: Added button with badge showing ${this.selectedPoints.length} points`);
+ } else {
+ console.warn('addSelectionCancelButton: No selected points found');
+ }
+
+ buttonContainer.appendChild(cancelButton);
+ buttonContainer.appendChild(deleteButton);
+
+ // Insert at the beginning of the container
+ container.insertBefore(buttonContainer, container.firstChild);
+ console.log('addSelectionCancelButton: Buttons added successfully');
}
/**
diff --git a/e2e/bulk-delete-points.spec.js b/e2e/bulk-delete-points.spec.js
index 789d9a9c..32d5551c 100644
--- a/e2e/bulk-delete-points.spec.js
+++ b/e2e/bulk-delete-points.spec.js
@@ -1,5 +1,38 @@
const { test, expect } = require('@playwright/test');
+// Helper function to draw selection rectangle and wait for delete button
+async function drawSelectionRectangle(page) {
+ // Click area selection tool
+ const selectionButton = page.locator('#selection-tool-button');
+ await selectionButton.click();
+ await page.waitForTimeout(500);
+
+ // Draw a rectangle on the map to select points
+ const mapContainer = page.locator('#map [data-maps-target="container"]');
+ const bbox = await mapContainer.boundingBox();
+
+ // Draw rectangle covering most of the map to ensure we select points
+ const startX = bbox.x + bbox.width * 0.2;
+ const startY = bbox.y + bbox.height * 0.2;
+ const endX = bbox.x + bbox.width * 0.8;
+ const endY = bbox.y + bbox.height * 0.8;
+
+ await page.mouse.move(startX, startY);
+ await page.mouse.down();
+ await page.mouse.move(endX, endY, { steps: 10 }); // Add steps for smoother drag
+ await page.mouse.up();
+
+ // Wait longer for API calls and drawer animations
+ await page.waitForTimeout(2000);
+
+ // Wait for drawer to open (it should open automatically after selection)
+ await page.waitForSelector('#visits-drawer.open', { timeout: 15000 });
+
+ // Wait for delete button to appear in the drawer (indicates selection is complete)
+ await page.waitForSelector('#delete-selection-button', { timeout: 15000 });
+ await page.waitForTimeout(500); // Brief wait for UI to stabilize
+}
+
test.describe('Bulk Delete Points', () => {
test.beforeEach(async ({ page }) => {
// Navigate to map page
@@ -72,54 +105,18 @@ test.describe('Bulk Delete Points', () => {
});
test('should select points in drawn area and show delete button', async ({ page }) => {
- // Click area selection tool
- const selectionButton = page.locator('#selection-tool-button');
- await selectionButton.click();
- await page.waitForTimeout(500);
-
- // Draw a rectangle on the map to select points
- const mapContainer = page.locator('#map [data-maps-target="container"]');
- const bbox = await mapContainer.boundingBox();
-
- // Draw rectangle from top-left to bottom-right
- const startX = bbox.x + bbox.width * 0.3;
- const startY = bbox.y + bbox.height * 0.3;
- const endX = bbox.x + bbox.width * 0.7;
- const endY = bbox.y + bbox.height * 0.7;
-
- await page.mouse.move(startX, startY);
- await page.mouse.down();
- await page.mouse.move(endX, endY);
- await page.mouse.up();
- await page.waitForTimeout(1000);
+ await drawSelectionRectangle(page);
// Check that delete button appears
const deleteButton = page.locator('#delete-selection-button');
- await expect(deleteButton).toBeVisible();
+ await expect(deleteButton).toBeVisible({ timeout: 10000 });
// Check button has text "Delete Points"
await expect(deleteButton).toContainText('Delete Points');
});
test('should show point count badge on delete button', async ({ page }) => {
- // Click area selection tool
- const selectionButton = page.locator('#selection-tool-button');
- await selectionButton.click();
- await page.waitForTimeout(500);
-
- // Draw rectangle
- const mapContainer = page.locator('#map [data-maps-target="container"]');
- const bbox = await mapContainer.boundingBox();
-
- const startX = bbox.x + bbox.width * 0.3;
- const startY = bbox.y + bbox.height * 0.3;
- const endX = bbox.x + bbox.width * 0.7;
- const endY = bbox.y + bbox.height * 0.7;
-
- await page.mouse.move(startX, startY);
- await page.mouse.down();
- await page.mouse.move(endX, endY);
- await page.mouse.up();
+ await drawSelectionRectangle(page);
await page.waitForTimeout(1000);
// Check for badge with count
@@ -132,24 +129,7 @@ test.describe('Bulk Delete Points', () => {
});
test('should show cancel button alongside delete button', async ({ page }) => {
- // Click area selection tool
- const selectionButton = page.locator('#selection-tool-button');
- await selectionButton.click();
- await page.waitForTimeout(500);
-
- // Draw rectangle
- const mapContainer = page.locator('#map [data-maps-target="container"]');
- const bbox = await mapContainer.boundingBox();
-
- const startX = bbox.x + bbox.width * 0.3;
- const startY = bbox.y + bbox.height * 0.3;
- const endX = bbox.x + bbox.width * 0.7;
- const endY = bbox.y + bbox.height * 0.7;
-
- await page.mouse.move(startX, startY);
- await page.mouse.down();
- await page.mouse.move(endX, endY);
- await page.mouse.up();
+ await drawSelectionRectangle(page);
await page.waitForTimeout(1000);
// Check both buttons exist
@@ -162,23 +142,7 @@ test.describe('Bulk Delete Points', () => {
});
test('should cancel selection when cancel button is clicked', async ({ page }) => {
- // Click area selection tool and draw rectangle
- const selectionButton = page.locator('#selection-tool-button');
- await selectionButton.click();
- await page.waitForTimeout(500);
-
- const mapContainer = page.locator('#map [data-maps-target="container"]');
- const bbox = await mapContainer.boundingBox();
-
- const startX = bbox.x + bbox.width * 0.3;
- const startY = bbox.y + bbox.height * 0.3;
- const endX = bbox.x + bbox.width * 0.7;
- const endY = bbox.y + bbox.height * 0.7;
-
- await page.mouse.move(startX, startY);
- await page.mouse.down();
- await page.mouse.move(endX, endY);
- await page.mouse.up();
+ await drawSelectionRectangle(page);
await page.waitForTimeout(1000);
// Click cancel button
@@ -207,23 +171,7 @@ test.describe('Bulk Delete Points', () => {
await dialog.dismiss(); // Dismiss to prevent actual deletion
});
- // Click area selection tool and draw rectangle
- const selectionButton = page.locator('#selection-tool-button');
- await selectionButton.click();
- await page.waitForTimeout(500);
-
- const mapContainer = page.locator('#map [data-maps-target="container"]');
- const bbox = await mapContainer.boundingBox();
-
- const startX = bbox.x + bbox.width * 0.3;
- const startY = bbox.y + bbox.height * 0.3;
- const endX = bbox.x + bbox.width * 0.7;
- const endY = bbox.y + bbox.height * 0.7;
-
- await page.mouse.move(startX, startY);
- await page.mouse.down();
- await page.mouse.move(endX, endY);
- await page.mouse.up();
+ await drawSelectionRectangle(page);
await page.waitForTimeout(1000);
// Click delete button
@@ -249,23 +197,7 @@ test.describe('Bulk Delete Points', () => {
return controller?.markers?.length || 0;
});
- // Click area selection tool and draw rectangle
- const selectionButton = page.locator('#selection-tool-button');
- await selectionButton.click();
- await page.waitForTimeout(500);
-
- const mapContainer = page.locator('#map [data-maps-target="container"]');
- const bbox = await mapContainer.boundingBox();
-
- const startX = bbox.x + bbox.width * 0.3;
- const startY = bbox.y + bbox.height * 0.3;
- const endX = bbox.x + bbox.width * 0.7;
- const endY = bbox.y + bbox.height * 0.7;
-
- await page.mouse.move(startX, startY);
- await page.mouse.down();
- await page.mouse.move(endX, endY);
- await page.mouse.up();
+ await drawSelectionRectangle(page);
await page.waitForTimeout(1000);
// Click delete button
@@ -273,8 +205,8 @@ test.describe('Bulk Delete Points', () => {
await deleteButton.click();
await page.waitForTimeout(2000); // Wait for deletion to complete
- // Check for success flash message
- const flashMessage = page.locator('#flash-messages [role="alert"]');
+ // Check for success flash message with specific text
+ const flashMessage = page.locator('#flash-messages [role="alert"]:has-text("Successfully deleted")');
await expect(flashMessage).toBeVisible({ timeout: 5000 });
const messageText = await flashMessage.textContent();
@@ -306,7 +238,7 @@ test.describe('Bulk Delete Points', () => {
await dialog.accept();
});
- // Perform deletion
+ // Perform deletion using same selection logic as helper
const selectionButton = page.locator('#selection-tool-button');
await selectionButton.click();
await page.waitForTimeout(500);
@@ -314,16 +246,21 @@ test.describe('Bulk Delete Points', () => {
const mapContainer = page.locator('#map [data-maps-target="container"]');
const bbox = await mapContainer.boundingBox();
- const startX = bbox.x + bbox.width * 0.4;
- const startY = bbox.y + bbox.height * 0.4;
- const endX = bbox.x + bbox.width * 0.6;
- const endY = bbox.y + bbox.height * 0.6;
+ // Use larger selection area to ensure we select points
+ const startX = bbox.x + bbox.width * 0.2;
+ const startY = bbox.y + bbox.height * 0.2;
+ const endX = bbox.x + bbox.width * 0.8;
+ const endY = bbox.y + bbox.height * 0.8;
await page.mouse.move(startX, startY);
await page.mouse.down();
- await page.mouse.move(endX, endY);
+ await page.mouse.move(endX, endY, { steps: 10 });
await page.mouse.up();
- await page.waitForTimeout(1000);
+ await page.waitForTimeout(2000);
+
+ // Wait for drawer and button to appear
+ await page.waitForSelector('#visits-drawer.open', { timeout: 15000 });
+ await page.waitForSelector('#delete-selection-button', { timeout: 15000 });
const deleteButton = page.locator('#delete-selection-button');
await deleteButton.click();
@@ -355,7 +292,7 @@ test.describe('Bulk Delete Points', () => {
await dialog.accept();
});
- // Perform deletion
+ // Perform deletion using same selection logic as helper
const selectionButton = page.locator('#selection-tool-button');
await selectionButton.click();
await page.waitForTimeout(500);
@@ -363,16 +300,21 @@ test.describe('Bulk Delete Points', () => {
const mapContainer = page.locator('#map [data-maps-target="container"]');
const bbox = await mapContainer.boundingBox();
- const startX = bbox.x + bbox.width * 0.4;
- const startY = bbox.y + bbox.height * 0.4;
- const endX = bbox.x + bbox.width * 0.6;
- const endY = bbox.y + bbox.height * 0.6;
+ // Use larger selection area to ensure we select points
+ const startX = bbox.x + bbox.width * 0.2;
+ const startY = bbox.y + bbox.height * 0.2;
+ const endX = bbox.x + bbox.width * 0.8;
+ const endY = bbox.y + bbox.height * 0.8;
await page.mouse.move(startX, startY);
await page.mouse.down();
- await page.mouse.move(endX, endY);
+ await page.mouse.move(endX, endY, { steps: 10 });
await page.mouse.up();
- await page.waitForTimeout(1000);
+ await page.waitForTimeout(2000);
+
+ // Wait for drawer and button to appear
+ await page.waitForSelector('#visits-drawer.open', { timeout: 15000 });
+ await page.waitForSelector('#delete-selection-button', { timeout: 15000 });
const deleteButton = page.locator('#delete-selection-button');
await deleteButton.click();
@@ -410,7 +352,7 @@ test.describe('Bulk Delete Points', () => {
await dialog.accept();
});
- // Perform deletion
+ // Perform deletion using same selection logic as helper
const selectionButton = page.locator('#selection-tool-button');
await selectionButton.click();
await page.waitForTimeout(500);
@@ -418,16 +360,21 @@ test.describe('Bulk Delete Points', () => {
const mapContainer = page.locator('#map [data-maps-target="container"]');
const bbox = await mapContainer.boundingBox();
- const startX = bbox.x + bbox.width * 0.3;
- const startY = bbox.y + bbox.height * 0.3;
- const endX = bbox.x + bbox.width * 0.7;
- const endY = bbox.y + bbox.height * 0.7;
+ // Use larger selection area to ensure we select points
+ const startX = bbox.x + bbox.width * 0.2;
+ const startY = bbox.y + bbox.height * 0.2;
+ const endX = bbox.x + bbox.width * 0.8;
+ const endY = bbox.y + bbox.height * 0.8;
await page.mouse.move(startX, startY);
await page.mouse.down();
- await page.mouse.move(endX, endY);
+ await page.mouse.move(endX, endY, { steps: 10 });
await page.mouse.up();
- await page.waitForTimeout(1000);
+ await page.waitForTimeout(2000);
+
+ // Wait for drawer and button to appear
+ await page.waitForSelector('#visits-drawer.open', { timeout: 15000 });
+ await page.waitForSelector('#delete-selection-button', { timeout: 15000 });
const deleteButton = page.locator('#delete-selection-button');
await deleteButton.click();
@@ -448,7 +395,7 @@ test.describe('Bulk Delete Points', () => {
await dialog.accept();
});
- // Perform deletion
+ // Perform deletion using same selection logic as helper
const selectionButton = page.locator('#selection-tool-button');
await selectionButton.click();
await page.waitForTimeout(500);
@@ -456,16 +403,21 @@ test.describe('Bulk Delete Points', () => {
const mapContainer = page.locator('#map [data-maps-target="container"]');
const bbox = await mapContainer.boundingBox();
- const startX = bbox.x + bbox.width * 0.3;
- const startY = bbox.y + bbox.height * 0.3;
- const endX = bbox.x + bbox.width * 0.7;
- const endY = bbox.y + bbox.height * 0.7;
+ // Use larger selection area to ensure we select points
+ const startX = bbox.x + bbox.width * 0.2;
+ const startY = bbox.y + bbox.height * 0.2;
+ const endX = bbox.x + bbox.width * 0.8;
+ const endY = bbox.y + bbox.height * 0.8;
await page.mouse.move(startX, startY);
await page.mouse.down();
- await page.mouse.move(endX, endY);
+ await page.mouse.move(endX, endY, { steps: 10 });
await page.mouse.up();
- await page.waitForTimeout(1000);
+ await page.waitForTimeout(2000);
+
+ // Wait for drawer and button to appear
+ await page.waitForSelector('#visits-drawer.open', { timeout: 15000 });
+ await page.waitForSelector('#delete-selection-button', { timeout: 15000 });
const deleteButton = page.locator('#delete-selection-button');
await deleteButton.click();
diff --git a/e2e/temp/.auth/user.json b/e2e/temp/.auth/user.json
index 7f178f83..da478652 100644
--- a/e2e/temp/.auth/user.json
+++ b/e2e/temp/.auth/user.json
@@ -2,7 +2,7 @@
"cookies": [
{
"name": "_dawarich_session",
- "value": "EpVyt%2F73ZRFf3PGkUELvrrnllSZ7fNY8oLGYvmO0STevmBL9bT9XZb9JK4NE6KSDMYqDLPFSRrZTNAlmyOuYi7kett2QE3TjNcAVVtE8%2BRhUweTPTcFs9wwAbf%2FlKYqQkMLF4NYz%2FA0Mr39M2fLxx0qAiqAo0Cg4y1jHQlWS1Slrp%2FkXkHt3obK5z6biG8gqXk9ldBqa6Uh3ymuBJOe%2BQE0rvhnsGRfD%2FIFbsgUzCuU3BEHw%2BUaO%2FYR%2BrlASj4sNiBr6%2FBRLlI0pecI4G8avHHSasFigpw2JEslgP12ifFtoLd5yw7uqO0K7eUF9oGAWV3KWvj7xScfDi4mYagFDpu8q5msipd6Wo6e5D7i8GjnxhoGDLuBRHqIxS76EhxTHl%2FE%2FkV146ZFH--YqOqiWcq7Oafo4bF--F2LpPqfUyhiln%2B9dabKFxQ%3D%3D",
+ "value": "uSq%2BCBWS9YujuNhicVcBHgR62tcYRgXVobd6flpFfMCPEGldb7vMuvaYDitf%2Fr%2FRMXC2Vb4VKE0dHz00pXp1S%2F6gBJuXHZ05is2zoZPGQ5gRaGVRbEG%2F%2FfKVSKgOb3B87WmmrB4I%2B1jq10hT0MI%2FKzeAhIR%2BI1hVmGYeozx%2BNttmWjIgtYk%2FN9JnsN7jzmYvON65mrRgJQyPftaIEpOYpMCdbPl1uosRoQpO6WroGxQ4J89lFvoSbyTspqJje8A0i5JNJThSfRkyPcIPXNtIFGHUUBX376%2BOLZds7sLor6%2BMu%2FQs%2FnjQl2xWFxejo6lSp%2BZrplztjPwquQtjm1BmnWN1PPvFsrphEw2scIk%2BQUyk2F3ZQcVwBUExB0dm5qA9M3uSbvR%2BV8OU--8oAuBe0ygLxh0x4N--STVFL7XMmRGm17xka6AU3A%3D%3D",
"domain": "localhost",
"path": "/",
"expires": -1,