Fixed the print view and things seem to be working now.

This commit is contained in:
admin 2025-09-07 11:50:44 -06:00
parent b3cd1a3331
commit 459cea0c3b
11 changed files with 4055 additions and 3400 deletions

View File

@ -1377,7 +1377,11 @@
<!-- Dashboard JavaScript -->
<script src="js/dashboard.js"></script>
<!-- Admin Cuts JavaScript -->
<!-- Admin Cuts JavaScript - Load modules first, then main file -->
<script src="js/cut-drawing.js"></script>
<script src="js/cut-location-manager.js"></script>
<script src="js/cut-print-utils.js"></script>
<script src="js/admin-cuts-manager.js"></script>
<script src="js/admin-cuts.js"></script>
<!-- Listmonk Status and Admin -->

View File

@ -16,3 +16,4 @@
/* Legacy import compatibility */
@import url("modules/dashboard.css");
@import url("modules/cuts.css");

View File

@ -209,7 +209,11 @@ function loadSectionData(sectionId) {
console.error('Failed to initialize cuts manager:', error);
});
} else {
console.log('Admin cuts manager already initialized');
console.log('Admin cuts manager already initialized, refreshing map...');
// Refresh map size when section becomes visible
if (window.adminCutsManager.refreshMapSize) {
window.adminCutsManager.refreshMapSize();
}
}
} else {
console.error('adminCutsManager not found in showSection');

View File

@ -0,0 +1,110 @@
/**
* Admin Cuts Management Module
* Main initialization file that imports and orchestrates all cut management modules
*/
// Global admin cuts manager instance
let adminCutsManager = null;
// Initialize the admin cuts system when DOM is ready
document.addEventListener('DOMContentLoaded', async function() {
console.log('DOM loaded, initializing admin cuts system...');
try {
// Wait for all module classes to be loaded
if (typeof AdminCutsManager === 'undefined') {
console.error('AdminCutsManager class not loaded');
return;
}
if (typeof CutDrawing === 'undefined') {
console.error('CutDrawing class not loaded');
return;
}
if (typeof CutLocationManager === 'undefined') {
console.error('CutLocationManager class not loaded');
return;
}
if (typeof CutPrintUtils === 'undefined') {
console.error('CutPrintUtils class not loaded');
return;
}
// Create manager instance
adminCutsManager = new AdminCutsManager();
// Initialize the system
await adminCutsManager.initialize();
console.log('Admin cuts system initialized successfully');
// Make manager globally accessible for debugging
window.adminCutsManager = adminCutsManager;
} catch (error) {
console.error('Failed to initialize admin cuts system:', error);
// Show error notification if available
if (typeof showNotification === 'function') {
showNotification('Failed to initialize cuts management system', 'error');
}
}
});
// Global functions for backward compatibility
function startDrawing() {
if (adminCutsManager && adminCutsManager.cutDrawing) {
adminCutsManager.handleStartDrawing();
}
}
function finishDrawing() {
if (adminCutsManager && adminCutsManager.cutDrawing) {
adminCutsManager.cutDrawing.finishDrawing();
}
}
function cancelDrawing() {
if (adminCutsManager && adminCutsManager.cutDrawing) {
adminCutsManager.cutDrawing.cancelDrawing();
}
}
function resetForm() {
if (adminCutsManager) {
adminCutsManager.resetForm();
}
}
function exportCuts() {
if (adminCutsManager) {
adminCutsManager.exportCuts();
}
}
function refreshCuts() {
if (adminCutsManager) {
adminCutsManager.loadCuts();
}
}
// Debug functions
function debugFormState() {
if (adminCutsManager) {
adminCutsManager.debugFormState();
}
}
function debugOpacityState() {
if (adminCutsManager) {
adminCutsManager.debugOpacityState();
}
}
function forceUpdateDrawingStyle() {
if (adminCutsManager) {
adminCutsManager.forceUpdateDrawingStyle();
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,425 @@
/**
* Cut Drawing Module
* Handles polygon drawing functionality for cut creation
*/
class CutDrawing {
constructor(map) {
this.map = map;
this.vertices = [];
this.markers = [];
this.polyline = null;
this.polygon = null; // Add polygon preview
this.finalPolygon = null; // Final polygon after finishing
this.isDrawing = false;
this.onFinishCallback = null;
this.onCancelCallback = null;
this.currentColor = '#3388ff';
this.currentOpacity = 0.3;
}
startDrawing(onFinish, onCancel) {
if (this.isDrawing) {
this.cancelDrawing();
}
this.isDrawing = true;
this.onFinishCallback = onFinish;
this.onCancelCallback = onCancel;
this.vertices = [];
this.markers = [];
this.map.getContainer().style.cursor = 'crosshair';
this.map.on('click', this.onMapClick.bind(this));
this.map.doubleClickZoom.disable();
console.log('Cut drawing started - click to add points');
}
onMapClick(e) {
if (!this.isDrawing) return;
// Check if clicking on first vertex to close polygon
if (this.vertices.length >= 3) {
const firstVertex = this.vertices[0];
const clickPoint = this.map.latLngToContainerPoint(e.latlng);
const firstPoint = this.map.latLngToContainerPoint(firstVertex);
const distance = Math.sqrt(
Math.pow(clickPoint.x - firstPoint.x, 2) +
Math.pow(clickPoint.y - firstPoint.y, 2)
);
// If clicked within 15 pixels of first vertex, close the polygon
if (distance < 15) {
console.log('Closing polygon by clicking first vertex');
this.finishDrawing();
return;
}
}
// Add vertex marker with special styling for first vertex
const isFirstVertex = this.vertices.length === 0;
const marker = L.marker(e.latlng, {
icon: L.divIcon({
className: 'cut-vertex-marker' + (isFirstVertex ? ' first-vertex' : ''),
html: `<div class="vertex-point${isFirstVertex ? ' first' : ''}"></div>`,
iconSize: [12, 12],
iconAnchor: [6, 6]
}),
draggable: false
}).addTo(this.map);
// Add tooltip to first vertex after 3 points
if (isFirstVertex) {
marker.bindTooltip('Click to close polygon', {
permanent: false,
direction: 'top',
offset: [0, -10]
});
}
this.vertices.push(e.latlng);
this.markers.push(marker);
this.updatePolyline();
// Show tooltip on first vertex when we have enough points
if (this.vertices.length === 3 && this.markers[0]) {
this.markers[0].openTooltip();
}
// Call update callback if available
if (this.onUpdate) {
this.onUpdate();
}
console.log(`Added vertex ${this.vertices.length} at`, e.latlng);
}
updatePolyline() {
// Use stored color and opacity values
const color = this.currentColor;
const opacity = this.currentOpacity;
if (this.polyline) {
this.map.removeLayer(this.polyline);
}
if (this.polygon) {
this.map.removeLayer(this.polygon);
this.polygon = null;
}
if (this.vertices.length > 1) {
// Show polyline for incomplete polygon
this.polyline = L.polyline(this.vertices, {
color: color,
weight: 2,
dashArray: '5, 5',
opacity: 1.0 // Keep polyline stroke visible
}).addTo(this.map);
// Show preview polygon if we have 3+ vertices
if (this.vertices.length >= 3) {
this.polygon = L.polygon(this.vertices, {
color: color,
fillColor: color,
fillOpacity: opacity,
weight: 2,
opacity: 0.8,
dashArray: '5, 5'
}).addTo(this.map);
// Add cut-polygon class and force inline style
if (this.polygon._path) {
this.polygon._path.classList.add('cut-polygon');
// Use setProperty with important flag for stronger override
this.polygon._path.style.setProperty('fill-opacity', opacity, 'important');
this.polygon._path.style.setProperty('fill', color, 'important');
console.log(`Created/updated polygon with opacity: ${opacity}`);
}
console.log('Created/updated polygon with opacity:', opacity);
}
}
}
finishDrawing() {
console.log('finishDrawing() called');
console.log('isDrawing:', this.isDrawing);
console.log('vertices count:', this.vertices.length);
if (!this.isDrawing) {
console.log('Not in drawing mode, returning null');
return null;
}
if (this.vertices.length < 3) {
alert('A cut must have at least 3 points. Click more points or cancel drawing.');
return null;
}
// Store vertices before cleanup
const finalVertices = [...this.vertices];
// Use stored color and opacity values
const color = this.currentColor;
const opacity = this.currentOpacity;
// Create polygon from vertices
const polygon = L.polygon(finalVertices, {
color: color,
fillColor: color,
fillOpacity: opacity,
weight: 2,
opacity: 1.0 // Keep stroke visible
});
// Get GeoJSON and bounds
const geojson = polygon.toGeoJSON().geometry; // Get just the geometry part
const bounds = polygon.getBounds();
// Clean up drawing elements but keep the final polygon
this.cleanupDrawingElements();
// Add the final polygon to the map
this.finalPolygon = polygon.addTo(this.map);
// Call finish callback with the data
console.log('Calling finish callbacks...');
const callbackData = {
geojson: JSON.stringify(geojson),
bounds: JSON.stringify([
[bounds.getSouth(), bounds.getWest()],
[bounds.getNorth(), bounds.getEast()]
]),
vertexCount: finalVertices.length,
polygon: this.finalPolygon
};
if (this.onFinishCallback) {
console.log('Calling onFinishCallback');
this.onFinishCallback(callbackData);
} else {
console.log('No onFinishCallback set');
}
// Also call the onFinish callback if set
if (this.onFinish) {
console.log('Calling onFinish');
this.onFinish(callbackData);
} else {
console.log('No onFinish callback set');
}
console.log('Cut drawing finished with', finalVertices.length, 'vertices');
return geojson;
}
cancelDrawing() {
if (!this.isDrawing) return;
console.log('Cut drawing cancelled');
this.cleanup();
if (this.onCancelCallback) {
this.onCancelCallback();
}
// Also call the onCancel callback if set
if (this.onCancel) {
this.onCancel();
}
}
undoLastVertex() {
if (!this.isDrawing || this.vertices.length === 0) return;
this.vertices.pop();
const lastMarker = this.markers.pop();
if (lastMarker) {
this.map.removeLayer(lastMarker);
}
this.updatePolyline();
// Call update callback if available
if (this.onUpdate) {
this.onUpdate();
}
console.log('Removed last vertex, remaining:', this.vertices.length);
}
clearVertices() {
if (!this.isDrawing) return;
this.markers.forEach(marker => {
this.map.removeLayer(marker);
});
if (this.polyline) {
this.map.removeLayer(this.polyline);
this.polyline = null;
}
if (this.polygon) {
this.map.removeLayer(this.polygon);
this.polygon = null;
}
this.vertices = [];
this.markers = [];
// Call update callback if available
if (this.onUpdate) {
this.onUpdate();
}
console.log('Cleared all vertices');
}
cleanupDrawingElements() {
// Remove drawing elements but preserve final polygon
this.markers.forEach(marker => {
this.map.removeLayer(marker);
});
if (this.polyline) {
this.map.removeLayer(this.polyline);
}
if (this.polygon) {
this.map.removeLayer(this.polygon);
}
this.map.getContainer().style.cursor = '';
this.map.off('click', this.onMapClick);
this.map.doubleClickZoom.enable();
this.isDrawing = false;
this.vertices = [];
this.markers = [];
this.polyline = null;
this.polygon = null;
this.onFinishCallback = null;
this.onCancelCallback = null;
}
cleanup() {
this.markers.forEach(marker => {
this.map.removeLayer(marker);
});
if (this.polyline) {
this.map.removeLayer(this.polyline);
}
if (this.polygon) {
this.map.removeLayer(this.polygon);
}
if (this.finalPolygon) {
this.map.removeLayer(this.finalPolygon);
}
this.map.getContainer().style.cursor = '';
this.map.off('click', this.onMapClick);
this.map.doubleClickZoom.enable();
this.isDrawing = false;
this.vertices = [];
this.markers = [];
this.polyline = null;
this.polygon = null;
this.finalPolygon = null;
this.onFinishCallback = null;
this.onCancelCallback = null;
}
getState() {
return {
isDrawing: this.isDrawing,
vertexCount: this.vertices.length,
canFinish: this.vertices.length >= 3
};
}
// Add method to update current drawing style
updateDrawingStyle(color, opacity) {
this.currentColor = color;
this.currentOpacity = opacity;
console.log(`CutDrawing.updateDrawingStyle called with color: ${color}, opacity: ${opacity}`);
// Update polyline color
if (this.polyline) {
this.polyline.setStyle({ color: color });
}
// Update polygon if it exists
if (this.polygon) {
this.polygon.setStyle({
color: color,
fillColor: color,
fillOpacity: opacity,
opacity: 0.8 // Border opacity
});
// Force inline style update and browser reflow
if (this.polygon._path) {
// Use setProperty with important flag for stronger override
this.polygon._path.style.setProperty('fill-opacity', opacity, 'important');
this.polygon._path.style.setProperty('fill', color, 'important');
// Force multiple reflows to ensure update
void this.polygon._path.offsetHeight;
void this.polygon._path.offsetWidth;
// Force repaint by temporarily changing a property
const oldDisplay = this.polygon._path.style.display;
this.polygon._path.style.display = 'none';
void this.polygon._path.offsetHeight;
this.polygon._path.style.display = oldDisplay;
}
// Also try Leaflet's internal redraw
if (this.polygon._updatePath) {
this.polygon._updatePath();
}
console.log(`Updated active drawing polygon with opacity: ${opacity}`);
} else {
// If no polygon exists but we have vertices, force a complete redraw
console.log('No polygon exists, forcing complete redraw');
this.forceRedraw();
}
}
clearPreview() {
// Clear any preview polygons but not the final polygon
if (this.polygon) {
this.map.removeLayer(this.polygon);
this.polygon = null;
}
if (this.polyline && this.isDrawing) {
this.map.removeLayer(this.polyline);
this.polyline = null;
}
}
// Force a complete redraw with current style settings
forceRedraw() {
if (this.vertices.length > 1) {
console.log('Forcing complete redraw with vertices:', this.vertices.length);
this.updatePolyline();
}
}
}
// Export the class if using modules, otherwise it's global
if (typeof module !== 'undefined' && module.exports) {
module.exports = CutDrawing;
} else {
window.CutDrawing = CutDrawing;
}

View File

@ -1,22 +1,23 @@
/**
* Cut Drawing Module
* Handles polygon drawing functionality for creating map cuts
* Handles polygon drawing functionality for cut creation
*/
export class CutDrawing {
constructor(map, options = {}) {
class CutDrawing {
constructor(map) {
this.map = map;
this.vertices = [];
this.markers = [];
this.polyline = null;
this.previewPolygon = null; // Add preview polygon
this.polygon = null; // Add polygon preview
this.finalPolygon = null; // Final polygon after finishing
this.isDrawing = false;
this.onComplete = options.onComplete || null;
this.onFinishCallback = null;
this.onCancelCallback = null;
this.currentColor = '#3388ff';
this.currentOpacity = 0.3;
}
/**
* Start drawing mode
*/
startDrawing(onFinish, onCancel) {
if (this.isDrawing) {
this.cancelDrawing();
@ -28,39 +29,64 @@ export class CutDrawing {
this.vertices = [];
this.markers = [];
// Change cursor and add click listener
this.map.getContainer().style.cursor = 'crosshair';
this.map.on('click', this.onMapClick.bind(this));
// Disable double-click zoom while drawing
this.map.doubleClickZoom.disable();
console.log('Cut drawing started - click to add points');
}
/**
* Handle map clicks to add vertices
*/
onMapClick(e) {
if (!this.isDrawing) return;
// Add vertex marker
// Check if clicking on first vertex to close polygon
if (this.vertices.length >= 3) {
const firstVertex = this.vertices[0];
const clickPoint = this.map.latLngToContainerPoint(e.latlng);
const firstPoint = this.map.latLngToContainerPoint(firstVertex);
const distance = Math.sqrt(
Math.pow(clickPoint.x - firstPoint.x, 2) +
Math.pow(clickPoint.y - firstPoint.y, 2)
);
// If clicked within 15 pixels of first vertex, close the polygon
if (distance < 15) {
console.log('Closing polygon by clicking first vertex');
this.finishDrawing();
return;
}
}
// Add vertex marker with special styling for first vertex
const isFirstVertex = this.vertices.length === 0;
const marker = L.marker(e.latlng, {
icon: L.divIcon({
className: 'cut-vertex-marker',
html: '<div class="vertex-point"></div>',
className: 'cut-vertex-marker' + (isFirstVertex ? ' first-vertex' : ''),
html: `<div class="vertex-point${isFirstVertex ? ' first' : ''}"></div>`,
iconSize: [12, 12],
iconAnchor: [6, 6]
}),
draggable: false
}).addTo(this.map);
// Add tooltip to first vertex after 3 points
if (isFirstVertex) {
marker.bindTooltip('Click to close polygon', {
permanent: false,
direction: 'top',
offset: [0, -10]
});
}
this.vertices.push(e.latlng);
this.markers.push(marker);
// Update the polyline
this.updatePolyline();
// Show tooltip on first vertex when we have enough points
if (this.vertices.length === 3 && this.markers[0]) {
this.markers[0].openTooltip();
}
// Call update callback if available
if (this.onUpdate) {
this.onUpdate();
@ -69,81 +95,126 @@ export class CutDrawing {
console.log(`Added vertex ${this.vertices.length} at`, e.latlng);
}
/**
* Update the polyline connecting vertices
*/
updatePolyline() {
// Remove existing polyline
// Use stored color and opacity values
const color = this.currentColor;
const opacity = this.currentOpacity;
if (this.polyline) {
this.map.removeLayer(this.polyline);
}
if (this.polygon) {
this.map.removeLayer(this.polygon);
this.polygon = null;
}
if (this.vertices.length > 1) {
// Create polyline connecting all vertices
// Show polyline for incomplete polygon
this.polyline = L.polyline(this.vertices, {
color: '#3388ff',
color: color,
weight: 2,
dashArray: '5, 5',
opacity: 0.8
opacity: 1.0 // Keep polyline stroke visible
}).addTo(this.map);
// Show preview polygon if we have 3+ vertices
if (this.vertices.length >= 3) {
this.polygon = L.polygon(this.vertices, {
color: color,
fillColor: color,
fillOpacity: opacity,
weight: 2,
opacity: 0.8,
dashArray: '5, 5'
}).addTo(this.map);
// Add cut-polygon class and force inline style
if (this.polygon._path) {
this.polygon._path.classList.add('cut-polygon');
// Use setProperty with important flag for stronger override
this.polygon._path.style.setProperty('fill-opacity', opacity, 'important');
this.polygon._path.style.setProperty('fill', color, 'important');
console.log(`Created/updated polygon with opacity: ${opacity}`);
}
console.log('Created/updated polygon with opacity:', opacity);
}
}
}
/**
* Finish drawing and create polygon
*/
finishDrawing() {
console.log('finishDrawing() called');
console.log('isDrawing:', this.isDrawing);
console.log('vertices count:', this.vertices.length);
if (!this.isDrawing) {
console.log('Not in drawing mode, returning null');
return null;
}
if (this.vertices.length < 3) {
alert('A cut must have at least 3 points');
return;
alert('A cut must have at least 3 points. Click more points or cancel drawing.');
return null;
}
// Create the polygon
const latlngs = this.vertices.map(v => v.getLatLng());
// Store vertices before cleanup
const finalVertices = [...this.vertices];
// Close the polygon
latlngs.push(latlngs[0]);
// Use stored color and opacity values
const color = this.currentColor;
const opacity = this.currentOpacity;
// Generate GeoJSON
const geojson = {
type: 'Polygon',
coordinates: [latlngs.map(ll => [ll.lng, ll.lat])]
// Create polygon from vertices
const polygon = L.polygon(finalVertices, {
color: color,
fillColor: color,
fillOpacity: opacity,
weight: 2,
opacity: 1.0 // Keep stroke visible
});
// Get GeoJSON and bounds
const geojson = polygon.toGeoJSON().geometry; // Get just the geometry part
const bounds = polygon.getBounds();
// Clean up drawing elements but keep the final polygon
this.cleanupDrawingElements();
// Add the final polygon to the map
this.finalPolygon = polygon.addTo(this.map);
// Call finish callback with the data
console.log('Calling finish callbacks...');
const callbackData = {
geojson: JSON.stringify(geojson),
bounds: JSON.stringify([
[bounds.getSouth(), bounds.getWest()],
[bounds.getNorth(), bounds.getEast()]
]),
vertexCount: finalVertices.length,
polygon: this.finalPolygon
};
// Calculate bounds
const bounds = {
north: Math.max(...latlngs.map(ll => ll.lat)),
south: Math.min(...latlngs.map(ll => ll.lat)),
east: Math.max(...latlngs.map(ll => ll.lng)),
west: Math.min(...latlngs.map(ll => ll.lng))
};
console.log('Cut drawing finished with', this.vertices.length, 'vertices');
// Show preview before clearing drawing
const color = document.getElementById('cut-color')?.value || '#3388ff';
const opacity = parseFloat(document.getElementById('cut-opacity')?.value) || 0.3;
this.showPreview(geojson, color, opacity);
// Clean up drawing elements
this.clearDrawing();
// Call completion callback with the data
if (this.onComplete && typeof this.onComplete === 'function') {
console.log('Calling completion callback with geojson and bounds');
this.onComplete(geojson, bounds);
if (this.onFinishCallback) {
console.log('Calling onFinishCallback');
this.onFinishCallback(callbackData);
} else {
console.error('No completion callback defined');
console.log('No onFinishCallback set');
}
// Reset state
this.isDrawing = false;
this.updateToolbar();
// Also call the onFinish callback if set
if (this.onFinish) {
console.log('Calling onFinish');
this.onFinish(callbackData);
} else {
console.log('No onFinish callback set');
}
console.log('Cut drawing finished with', finalVertices.length, 'vertices');
return geojson;
}
/**
* Cancel drawing
*/
cancelDrawing() {
if (!this.isDrawing) return;
@ -153,22 +224,22 @@ export class CutDrawing {
if (this.onCancelCallback) {
this.onCancelCallback();
}
// Also call the onCancel callback if set
if (this.onCancel) {
this.onCancel();
}
}
/**
* Remove the last added vertex
*/
undoLastVertex() {
if (!this.isDrawing || this.vertices.length === 0) return;
// Remove last vertex and marker
this.vertices.pop();
const lastMarker = this.markers.pop();
if (lastMarker) {
this.map.removeLayer(lastMarker);
}
// Update polyline
this.updatePolyline();
// Call update callback if available
@ -179,24 +250,23 @@ export class CutDrawing {
console.log('Removed last vertex, remaining:', this.vertices.length);
}
/**
* Clear all vertices and start over
*/
clearVertices() {
if (!this.isDrawing) return;
// Remove all markers
this.markers.forEach(marker => {
this.map.removeLayer(marker);
});
// Remove polyline
if (this.polyline) {
this.map.removeLayer(this.polyline);
this.polyline = null;
}
// Reset arrays
if (this.polygon) {
this.map.removeLayer(this.polygon);
this.polygon = null;
}
this.vertices = [];
this.markers = [];
@ -208,41 +278,64 @@ export class CutDrawing {
console.log('Cleared all vertices');
}
/**
* Cleanup drawing state
*/
cleanup() {
// Remove all markers
cleanupDrawingElements() {
// Remove drawing elements but preserve final polygon
this.markers.forEach(marker => {
this.map.removeLayer(marker);
});
// Remove polyline
if (this.polyline) {
this.map.removeLayer(this.polyline);
}
// Reset cursor
if (this.polygon) {
this.map.removeLayer(this.polygon);
}
this.map.getContainer().style.cursor = '';
// Remove event listeners
this.map.off('click', this.onMapClick);
// Re-enable double-click zoom
this.map.doubleClickZoom.enable();
// Reset state
this.isDrawing = false;
this.vertices = [];
this.markers = [];
this.polyline = null;
this.polygon = null;
this.onFinishCallback = null;
this.onCancelCallback = null;
}
cleanup() {
this.markers.forEach(marker => {
this.map.removeLayer(marker);
});
if (this.polyline) {
this.map.removeLayer(this.polyline);
}
if (this.polygon) {
this.map.removeLayer(this.polygon);
}
if (this.finalPolygon) {
this.map.removeLayer(this.finalPolygon);
}
this.map.getContainer().style.cursor = '';
this.map.off('click', this.onMapClick);
this.map.doubleClickZoom.enable();
this.isDrawing = false;
this.vertices = [];
this.markers = [];
this.polyline = null;
this.polygon = null;
this.finalPolygon = null;
this.onFinishCallback = null;
this.onCancelCallback = null;
}
/**
* Get current drawing state
*/
getState() {
return {
isDrawing: this.isDrawing,
@ -251,86 +344,82 @@ export class CutDrawing {
};
}
/**
* Preview polygon without finishing
*/
showPreview(geojson, color = '#3388ff', opacity = 0.3) {
this.clearPreview();
// Add method to update current drawing style
updateDrawingStyle(color, opacity) {
this.currentColor = color;
this.currentOpacity = opacity;
if (!geojson) return;
console.log(`CutDrawing.updateDrawingStyle called with color: ${color}, opacity: ${opacity}`);
try {
const coordinates = geojson.coordinates[0];
const latlngs = coordinates.map(coord => L.latLng(coord[1], coord[0]));
// Update polyline color
if (this.polyline) {
this.polyline.setStyle({ color: color });
}
this.previewPolygon = L.polygon(latlngs, {
// Update polygon if it exists
if (this.polygon) {
this.polygon.setStyle({
color: color,
weight: 2,
opacity: 0.8,
fillColor: color,
fillOpacity: opacity,
className: 'cut-preview-polygon'
}).addTo(this.map);
// Add CSS class for opacity control
const pathElement = this.previewPolygon.getElement();
if (pathElement) {
pathElement.classList.add('cut-polygon');
console.log('Added cut-polygon class to preview polygon');
}
console.log('Preview polygon shown with opacity:', opacity);
} catch (error) {
console.error('Error showing preview polygon:', error);
}
}
/**
* Update preview polygon style without recreating it
*/
updatePreview(color = '#3388ff', opacity = 0.3) {
if (this.previewPolygon) {
this.previewPolygon.setStyle({
color: color,
weight: 2,
opacity: 0.8,
fillColor: color,
fillOpacity: opacity
opacity: 0.8 // Border opacity
});
// Ensure CSS class is still present
const pathElement = this.previewPolygon.getElement();
if (pathElement) {
pathElement.classList.add('cut-polygon');
// Force inline style update and browser reflow
if (this.polygon._path) {
// Use setProperty with important flag for stronger override
this.polygon._path.style.setProperty('fill-opacity', opacity, 'important');
this.polygon._path.style.setProperty('fill', color, 'important');
// Force multiple reflows to ensure update
void this.polygon._path.offsetHeight;
void this.polygon._path.offsetWidth;
// Force repaint by temporarily changing a property
const oldDisplay = this.polygon._path.style.display;
this.polygon._path.style.display = 'none';
void this.polygon._path.offsetHeight;
this.polygon._path.style.display = oldDisplay;
}
console.log('Preview polygon style updated with opacity:', opacity);
// Also try Leaflet's internal redraw
if (this.polygon._updatePath) {
this.polygon._updatePath();
}
console.log(`Updated active drawing polygon with opacity: ${opacity}`);
} else {
// If no polygon exists but we have vertices, force a complete redraw
console.log('No polygon exists, forcing complete redraw');
this.forceRedraw();
}
}
clearPreview() {
if (this.previewPolygon) {
this.map.removeLayer(this.previewPolygon);
this.previewPolygon = null;
// Clear any preview polygons but not the final polygon
if (this.polygon) {
this.map.removeLayer(this.polygon);
this.polygon = null;
}
if (this.polyline && this.isDrawing) {
this.map.removeLayer(this.polyline);
this.polyline = null;
}
}
/**
* Update drawing style (called from admin cuts manager)
*/
updateDrawingStyle(color = '#3388ff', opacity = 0.3) {
// Update the polyline connecting vertices if it exists
if (this.polyline) {
this.polyline.setStyle({
color: color,
weight: 2,
opacity: 0.8
});
// Force a complete redraw with current style settings
forceRedraw() {
if (this.vertices.length > 1) {
console.log('Forcing complete redraw with vertices:', this.vertices.length);
this.updatePolyline();
}
}
}
// Update preview polygon if it exists
this.updatePreview(color, opacity);
console.log('Cut drawing style updated with color:', color, 'opacity:', opacity);
}
// Export the class if using modules, otherwise it's global
if (typeof module !== 'undefined' && module.exports) {
module.exports = CutDrawing;
} else {
window.CutDrawing = CutDrawing;
}

View File

@ -0,0 +1,443 @@
/**
* Cut Location Management Module
* Handles location display, filtering, statistics, and management for cuts
*/
class CutLocationManager {
constructor(map, cutsManager) {
this.map = map;
this.cutsManager = cutsManager;
this.currentCutId = null;
this.currentCutLocations = [];
this.currentFilters = {};
this.locationMarkersLayer = null;
this.showingLocations = false;
this.setupEventHandlers();
}
setupEventHandlers() {
// Toggle location visibility
const toggleLocationBtn = document.getElementById('toggle-location-visibility');
if (toggleLocationBtn) {
toggleLocationBtn.addEventListener('click', () => this.toggleLocationVisibility());
}
// Export cut locations
const exportBtn = document.getElementById('export-cut-locations');
if (exportBtn) {
exportBtn.addEventListener('click', () => this.exportCutLocations());
}
// Apply filters
const applyFiltersBtn = document.getElementById('apply-filters');
if (applyFiltersBtn) {
applyFiltersBtn.addEventListener('click', () => this.applyLocationFilters());
}
// Clear filters
const clearFiltersBtn = document.getElementById('clear-filters');
if (clearFiltersBtn) {
clearFiltersBtn.addEventListener('click', () => this.clearLocationFilters());
}
// Save cut settings
const saveSettingsBtn = document.getElementById('save-cut-settings');
if (saveSettingsBtn) {
saveSettingsBtn.addEventListener('click', () => this.saveCutSettings());
}
}
async toggleLocationVisibility() {
if (!this.currentCutId) {
this.showStatus('No cut selected', 'warning');
return;
}
const toggleBtn = document.getElementById('toggle-location-visibility');
if (this.showingLocations) {
// Hide locations
if (this.locationMarkersLayer) {
this.map.removeLayer(this.locationMarkersLayer);
}
this.showingLocations = false;
toggleBtn.textContent = 'Show Locations';
toggleBtn.classList.remove('active');
toggleBtn.classList.add('inactive');
} else {
// Show locations
await this.loadCutLocations();
toggleBtn.textContent = 'Hide Locations';
toggleBtn.classList.add('active');
toggleBtn.classList.remove('inactive');
}
}
async loadCutLocations() {
if (!this.currentCutId) return;
try {
const filters = this.getCurrentFilters();
const queryParams = new URLSearchParams(filters);
const response = await fetch(`/api/cuts/${this.currentCutId}/locations?${queryParams}`, {
credentials: 'include'
});
if (!response.ok) {
throw new Error(`Failed to load locations: ${response.statusText}`);
}
const data = await response.json();
this.currentCutLocations = data.locations || [];
// Update statistics
this.updateStatistics(data.statistics);
// Display locations on map
this.displayLocationsOnMap(this.currentCutLocations);
this.showingLocations = true;
this.showStatus(`Loaded ${this.currentCutLocations.length} locations`, 'success');
} catch (error) {
console.error('Error loading cut locations:', error);
this.showStatus('Failed to load locations', 'error');
}
}
displayLocationsOnMap(locations) {
// Remove existing markers
if (this.locationMarkersLayer) {
this.map.removeLayer(this.locationMarkersLayer);
}
// Create new markers layer
this.locationMarkersLayer = L.layerGroup();
locations.forEach(location => {
if (location.latitude && location.longitude) {
const marker = L.marker([location.latitude, location.longitude], {
icon: this.createLocationIcon(location)
});
const popupContent = this.createLocationPopup(location);
marker.bindPopup(popupContent);
this.locationMarkersLayer.addLayer(marker);
}
});
this.locationMarkersLayer.addTo(this.map);
}
createLocationIcon(location) {
// Create different icons based on support level
const supportLevel = location.support_level || location['Support Level'] || 'unknown';
const colors = {
'1': '#28a745', // Green - Strong support
'2': '#ffc107', // Yellow - Lean support
'3': '#fd7e14', // Orange - Lean opposition
'4': '#dc3545', // Red - Strong opposition
'unknown': '#6c757d' // Grey - Unknown
};
const color = colors[supportLevel] || colors['unknown'];
return L.divIcon({
className: 'location-marker',
html: `<div style="background-color: ${color}; width: 12px; height: 12px; border-radius: 50%; border: 2px solid white; box-shadow: 0 2px 4px rgba(0,0,0,0.3);"></div>`,
iconSize: [16, 16],
iconAnchor: [8, 8]
});
}
createLocationPopup(location) {
// Handle different possible field names for NocoDB
const firstName = location.first_name || location['First Name'] || '';
const lastName = location.last_name || location['Last Name'] || '';
const name = [firstName, lastName].filter(Boolean).join(' ') || 'Unknown';
const address = location.address || location.Address || 'No address';
const supportLevel = location.support_level || location['Support Level'] || 'Unknown';
const hasSign = location.sign || location.Sign ? 'Yes' : 'No';
const email = location.email || location.Email || '';
const phone = location.phone || location.Phone || '';
const notes = location.notes || location.Notes || '';
return `
<div class="location-popup">
<h4>${name}</h4>
<p><strong>Address:</strong> ${address}</p>
<p><strong>Support Level:</strong> ${supportLevel}</p>
<p><strong>Lawn Sign:</strong> ${hasSign}</p>
${email ? `<p><strong>Email:</strong> ${email}</p>` : ''}
${phone ? `<p><strong>Phone:</strong> ${phone}</p>` : ''}
${notes ? `<p><strong>Notes:</strong> ${notes}</p>` : ''}
</div>
`;
}
getCurrentFilters() {
return {
support_level: document.getElementById('support-level-filter')?.value || '',
has_sign: document.getElementById('sign-status-filter')?.value || '',
sign_size: document.getElementById('sign-size-filter')?.value || '',
contact_filter: document.getElementById('contact-filter')?.value || ''
};
}
async applyLocationFilters() {
if (!this.currentCutId) {
this.showStatus('No cut selected', 'warning');
return;
}
await this.loadCutLocations();
}
clearLocationFilters() {
document.getElementById('support-level-filter').value = '';
document.getElementById('sign-status-filter').value = '';
document.getElementById('sign-size-filter').value = '';
document.getElementById('contact-filter').value = '';
if (this.currentCutId && this.showingLocations) {
this.loadCutLocations();
}
}
updateStatistics(statistics) {
if (!statistics) return;
document.getElementById('total-locations').textContent = statistics.total_locations || 0;
document.getElementById('support-1').textContent = statistics.support_levels?.['1'] || 0;
document.getElementById('support-2').textContent = statistics.support_levels?.['2'] || 0;
document.getElementById('has-signs').textContent = statistics.lawn_signs?.has_sign || 0;
document.getElementById('has-email').textContent = statistics.contact_info?.has_email || 0;
document.getElementById('has-phone').textContent = statistics.contact_info?.has_phone || 0;
// Show statistics panel
document.getElementById('cut-statistics').style.display = 'block';
}
async exportCutLocations() {
if (!this.currentCutId) {
this.showStatus('No cut selected', 'warning');
return;
}
try {
const filters = this.getCurrentFilters();
const queryParams = new URLSearchParams(filters);
const response = await fetch(`/api/cuts/${this.currentCutId}/locations/export?${queryParams}`, {
credentials: 'include'
});
if (!response.ok) {
throw new Error(`Export failed: ${response.statusText}`);
}
// Download the CSV file
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
// Get filename from response header or use default
const contentDisposition = response.headers.get('content-disposition');
const filename = contentDisposition
? contentDisposition.split('filename=')[1]?.replace(/"/g, '')
: `cut_locations_${new Date().toISOString().split('T')[0]}.csv`;
a.download = filename;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
this.showStatus('Locations exported successfully', 'success');
} catch (error) {
console.error('Error exporting locations:', error);
this.showStatus('Failed to export locations', 'error');
}
}
async saveCutSettings() {
if (!this.currentCutId) {
this.showStatus('No cut selected', 'warning');
return;
}
try {
const settings = {
show_locations: document.getElementById('show-locations-toggle').checked,
export_enabled: document.getElementById('export-enabled-toggle').checked,
assigned_to: document.getElementById('assigned-to').value,
completion_percentage: parseInt(document.getElementById('completion-percentage').value) || 0
};
const response = await fetch(`/api/cuts/${this.currentCutId}/settings`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify(settings)
});
if (!response.ok) {
throw new Error(`Failed to save settings: ${response.statusText}`);
}
this.showStatus('Cut settings saved successfully', 'success');
} catch (error) {
console.error('Error saving cut settings:', error);
this.showStatus('Failed to save cut settings', 'error');
}
}
showLocationManagement(cutId) {
console.log('showLocationManagement called with cutId:', cutId);
this.currentCutId = cutId;
const locationSection = document.getElementById('cut-location-management');
console.log('Location section element:', locationSection);
if (locationSection) {
console.log('Setting location section display to block');
locationSection.style.display = 'block';
// Load cut data to populate settings - use multiple possible ID field names for NocoDB
const cut = this.cutsManager.allCuts.find(c =>
(c.id || c.Id || c.ID || c._id) == cutId
);
console.log('Found cut for location management:', cut);
if (cut) {
const toggleElement = document.getElementById('show-locations-toggle');
const exportElement = document.getElementById('export-enabled-toggle');
const assignedElement = document.getElementById('assigned-to');
const completionElement = document.getElementById('completion-percentage');
console.log('Setting up toggles:', {
toggleElement: !!toggleElement,
exportElement: !!exportElement,
assignedElement: !!assignedElement,
completionElement: !!completionElement
});
if (toggleElement) toggleElement.checked = (cut.show_locations || cut.Show_locations || cut['Show Locations']) !== false;
if (exportElement) exportElement.checked = (cut.export_enabled || cut.Export_enabled || cut['Export Enabled']) !== false;
if (assignedElement) assignedElement.value = cut.assigned_to || cut.Assigned_to || cut['Assigned To'] || '';
if (completionElement) completionElement.value = cut.completion_percentage || cut.Completion_percentage || cut['Completion Percentage'] || '';
}
} else {
console.error('Location management section not found!');
}
}
hideLocationManagement() {
const locationSection = document.getElementById('cut-location-management');
if (locationSection) {
locationSection.style.display = 'none';
}
// Clear current data
this.currentCutId = null;
this.currentCutLocations = [];
// Hide locations on map
if (this.locationMarkersLayer) {
this.map.removeLayer(this.locationMarkersLayer);
}
this.showingLocations = false;
// Reset button state
const toggleBtn = document.getElementById('toggle-location-visibility');
if (toggleBtn) {
toggleBtn.textContent = 'Show Locations';
toggleBtn.classList.remove('active');
toggleBtn.classList.remove('inactive');
}
}
showStatus(message, type) {
// Use existing admin notification system if available
if (typeof showNotification === 'function') {
showNotification(message, type);
} else if (this.cutsManager && this.cutsManager.showStatus) {
this.cutsManager.showStatus(message, type);
} else {
console.log(`[${type.toUpperCase()}] ${message}`);
}
}
getSupportColor(supportLevel) {
switch(supportLevel) {
case '1': return '#28a745'; // Strong Support - Green
case '2': return '#ffc107'; // Lean Support - Yellow
case '3': return '#fd7e14'; // Oppose - Orange
case '4': return '#dc3545'; // Strong Oppose - Red
default: return '#6c757d'; // Unknown - Gray
}
}
getCurrentFiltersDisplay() {
const activeFilters = [];
// Check location filters
const supportFilter = document.getElementById('support-filter')?.value;
if (supportFilter && supportFilter !== 'all') {
activeFilters.push(`Support Level: ${supportFilter}`);
}
const signFilter = document.getElementById('sign-filter')?.value;
if (signFilter && signFilter !== 'all') {
activeFilters.push(`Lawn Signs: ${signFilter === 'true' ? 'Yes' : 'No'}`);
}
const contactFilter = document.getElementById('contact-filter')?.value;
if (contactFilter && contactFilter !== 'all') {
const contactLabels = {
'email': 'Has Email',
'phone': 'Has Phone',
'both': 'Has Both Email & Phone',
'none': 'No Contact Info'
};
activeFilters.push(`Contact: ${contactLabels[contactFilter] || contactFilter}`);
}
if (activeFilters.length === 0) {
return '<div class="filters-info"><strong>Filters:</strong> None (showing all locations)</div>';
}
return `<div class="filters-info"><strong>Active Filters:</strong> ${activeFilters.join(', ')}</div>`;
}
getActiveFiltersCount() {
let count = 0;
const supportFilter = document.getElementById('support-filter')?.value;
if (supportFilter && supportFilter !== 'all') count++;
const signFilter = document.getElementById('sign-filter')?.value;
if (signFilter && signFilter !== 'all') count++;
const contactFilter = document.getElementById('contact-filter')?.value;
if (contactFilter && contactFilter !== 'all') count++;
return count;
}
}
// Export the class if using modules, otherwise it's global
if (typeof module !== 'undefined' && module.exports) {
module.exports = CutLocationManager;
} else {
window.CutLocationManager = CutLocationManager;
}

File diff suppressed because it is too large Load Diff

89
map/test_print_debug.html Normal file
View File

@ -0,0 +1,89 @@
<!DOCTYPE html>
<html>
<head>
<title>Print Debug Test</title>
<script>
// Simple test to verify our changes
function testPrintUtils() {
console.log('Testing CutPrintUtils improvements...');
// Mock objects for testing
const mockMap = {
getContainer: () => document.createElement('div'),
getBounds: () => ({
getNorth: () => 53.6,
getSouth: () => 53.4,
getEast: () => -113.3,
getWest: () => -113.7
}),
getCenter: () => ({ lat: 53.5, lng: -113.5 }),
getZoom: () => 12
};
const mockLocationManager = {
currentCutId: '123',
currentCutLocations: [
{ latitude: 53.5, longitude: -113.5, first_name: 'Test', last_name: 'User', support_level: '1' }
],
showingLocations: false,
loadCutLocations: async () => console.log('Mock loadCutLocations called'),
displayLocationsOnMap: (locations) => {
console.log('Mock displayLocationsOnMap called with', locations.length, 'locations');
mockLocationManager.showingLocations = true;
},
getSupportColor: (level) => '#28a745'
};
const mockCutsManager = {
allCuts: [{ id: '123', name: 'Test Cut' }],
currentCutLayer: null
};
// Test our enhanced print utils
const printUtils = new CutPrintUtils(mockMap, mockCutsManager, mockLocationManager);
console.log('CutPrintUtils created successfully with enhanced features');
console.log('Available methods:', Object.getOwnPropertyNames(CutPrintUtils.prototype));
return true;
}
// Run test when page loads
window.addEventListener('load', () => {
if (typeof CutPrintUtils !== 'undefined') {
testPrintUtils();
} else {
console.log('CutPrintUtils not loaded - this is expected in test environment');
}
});
</script>
</head>
<body>
<h1>Print Debug Test</h1>
<p>Check the browser console for test results.</p>
<p>This tests the enhanced CutPrintUtils functionality.</p>
<h2>Key Improvements Made:</h2>
<ul>
<li>✅ Auto-load locations when printing if not already loaded</li>
<li>✅ Auto-display locations on map for print capture</li>
<li>✅ Enhanced map capture with html2canvas (priority #1)</li>
<li>✅ Improved dom-to-image capture with better filtering</li>
<li>✅ Better UI state management (toggle button updates)</li>
<li>✅ Enhanced debugging and logging</li>
<li>✅ Auto-show locations when viewing cuts (if enabled)</li>
</ul>
<h2>Root Cause Analysis:</h2>
<p>The issue was that locations were not automatically displayed on the map when viewing a cut or printing.
The print function expected locations to be visible but they were only shown when the user manually clicked "Show Locations".</p>
<h2>Solution:</h2>
<ol>
<li><strong>Print Enhancement:</strong> The print function now ensures locations are loaded and displayed before capturing the map</li>
<li><strong>View Enhancement:</strong> When viewing a cut, locations are automatically loaded if the cut has show_locations enabled</li>
<li><strong>Capture Enhancement:</strong> Improved map capture methods with html2canvas as primary method</li>
<li><strong>State Management:</strong> Better synchronization between location visibility and UI state</li>
</ol>
</body>
</html>