Cut updates. Still need to test the assignment system.

This commit is contained in:
2025-09-08 11:23:27 -06:00
parent cca8128f24
commit c3390a1a78
9 changed files with 747 additions and 160 deletions

View File

@@ -759,104 +759,109 @@
<p>Create and manage polygon overlays for the map. Cuts can be used to define areas like wards, neighborhoods, or custom regions.</p>
<div class="cuts-container">
<!-- Map and Drawing Controls -->
<div class="cuts-map-section">
<div id="cuts-map" class="admin-map"></div>
<!-- Drawing Toolbar -->
<div id="cut-drawing-toolbar" class="cut-drawing-toolbar">
<div class="toolbar-content">
<div class="vertex-count" id="vertex-count">0</div>
<div class="style-controls">
<div class="color-control">
<label>Color:</label>
<input type="color" id="toolbar-color" value="#3388ff">
</div>
<div class="opacity-control">
<label>Opacity:</label>
<input type="range" id="toolbar-opacity" min="0" max="1" step="0.05" value="0.3">
<span class="opacity-value" id="toolbar-opacity-display">30%</span>
<!-- Top Section with Existing Cuts and Form -->
<div class="cuts-top-section">
<!-- Existing Cuts List - Moved to Top -->
<div class="cuts-list-section">
<div class="cuts-management-panel">
<div class="panel-header">
<h3 class="panel-title">Existing Cuts</h3>
<div class="panel-actions">
<button id="refresh-cuts-btn" class="btn btn-secondary btn-sm">Refresh</button>
<button id="export-cuts-btn" class="btn btn-secondary btn-sm">Export All</button>
<label for="import-cuts-file" class="btn btn-secondary btn-sm" style="margin: 0;">
Import
<input type="file" id="import-cuts-file" accept=".json" style="display: none;">
</label>
</div>
</div>
<div class="toolbar-buttons">
<button type="button" id="finish-cut-btn" class="primary" disabled>Finish</button>
<button type="button" id="undo-vertex-btn" class="secondary" disabled>Undo</button>
<button type="button" id="clear-vertices-btn" class="secondary" disabled>Clear</button>
<button type="button" id="cancel-cut-btn" class="danger">Cancel</button>
</div>
</div>
</div>
</div>
<!-- Cut Form -->
<div class="cuts-form-section">
<div class="cuts-management-panel">
<div class="panel-header">
<h3 class="panel-title" id="cut-form-title">Cut Properties</h3>
<div class="panel-actions">
<button id="start-drawing-btn" class="btn btn-primary btn-sm">Start Drawing</button>
</div>
</div>
<div class="panel-content">
<form id="cut-form" class="cut-form">
<!-- Hidden fields moved to prevent duplicates -->
<input type="hidden" id="cut-id" name="id">
<input type="hidden" id="cut-geojson" name="geojson">
<input type="hidden" id="cut-bounds" name="bounds">
<div class="form-group">
<label for="cut-name">Name *</label>
<input type="text" id="cut-name" name="name" required>
</div>
<div class="form-group">
<label for="cut-description">Description</label>
<textarea id="cut-description" name="description" rows="3"></textarea>
</div>
<div class="form-group">
<label for="cut-category">Category</label>
<select id="cut-category" name="category">
<div class="panel-content">
<div class="cuts-filters">
<input type="text" id="cuts-search" placeholder="Search cuts..." class="form-control">
<select id="cuts-category-filter" class="form-control">
<option value="">All Categories</option>
<option value="Custom">Custom</option>
<option value="Ward">Ward</option>
<option value="Neighborhood">Neighborhood</option>
<option value="District">District</option>
</select>
</div>
<div class="form-group checkbox-group">
<input type="checkbox" id="cut-public" name="is_public" checked>
<label for="cut-public">Make this cut visible on the public map</label>
<div id="cuts-list" class="cuts-list">
<!-- Cuts will be populated here -->
</div>
<div class="form-group checkbox-group">
<input type="checkbox" id="cut-official" name="is_official">
<label for="cut-official">Mark as official cut</label>
</div>
</div>
</div>
<!-- Cut Properties - Streamlined Card -->
<div class="cuts-form-section">
<div class="cuts-management-panel cuts-form-card">
<div class="panel-header">
<h3 class="panel-title" id="cut-form-title">Cut Properties</h3>
<div class="panel-actions">
<button id="start-drawing-btn" class="btn btn-primary btn-sm">Start Drawing</button>
</div>
<div class="form-actions">
<button type="submit" id="save-cut-btn" class="btn btn-success" disabled>Save Cut</button>
<button type="button" id="reset-form-btn" class="btn btn-secondary">Reset</button>
<button type="button" id="cancel-edit-btn" class="btn btn-secondary" style="display: none;">Cancel Edit</button>
</div>
</form>
</div>
<div class="panel-content">
<form id="cut-form" class="cut-form cut-form-compact">
<!-- Hidden fields moved to prevent duplicates -->
<input type="hidden" id="cut-id" name="id">
<input type="hidden" id="cut-geojson" name="geojson">
<input type="hidden" id="cut-bounds" name="bounds">
<div class="form-row">
<div class="form-group">
<label for="cut-name">Name *</label>
<input type="text" id="cut-name" name="name" required>
</div>
<div class="form-group">
<label for="cut-category">Category</label>
<select id="cut-category" name="category">
<option value="Custom">Custom</option>
<option value="Ward">Ward</option>
<option value="Neighborhood">Neighborhood</option>
<option value="District">District</option>
</select>
</div>
</div>
<div class="form-group">
<label for="cut-description">Description</label>
<textarea id="cut-description" name="description" rows="2"></textarea>
</div>
<div class="form-options">
<div class="form-group checkbox-group">
<input type="checkbox" id="cut-public" name="is_public" checked>
<label for="cut-public">Visible on public map</label>
</div>
<div class="form-group checkbox-group">
<input type="checkbox" id="cut-official" name="is_official">
<label for="cut-official">Official cut</label>
</div>
<div class="form-group checkbox-group">
<input type="checkbox" id="show-locations-on-map" name="show_locations">
<label for="show-locations-on-map">Show locations on map</label>
</div>
</div>
<div class="form-actions">
<button type="submit" id="save-cut-btn" class="btn btn-success" disabled>Save Cut</button>
<button type="button" id="reset-form-btn" class="btn btn-secondary">Reset</button>
<button type="button" id="cancel-edit-btn" class="btn btn-secondary" style="display: none;">Cancel Edit</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- Cut Location Management -->
<!-- Cut Location Management - Moved Above Map -->
<div class="cuts-location-section" id="cut-location-management" style="display: none;">
<div class="cuts-management-panel">
<div class="panel-header">
<h3 class="panel-title">Location Management</h3>
<div class="panel-actions">
<button id="toggle-location-visibility" class="btn btn-primary btn-sm">Show Locations</button>
<button id="export-cut-locations" class="btn btn-success btn-sm">Export Data</button>
<button id="print-cut-view" class="btn btn-secondary btn-sm">Print View</button>
</div>
</div>
<div class="panel-content">
<!-- Location Filters -->
<div class="location-filters">
@@ -971,36 +976,45 @@
</div>
</div>
</div>
</div>
</div>
<!-- Cuts List -->
<div class="cuts-list-section">
<div class="cuts-management-panel">
<div class="panel-header">
<h3 class="panel-title">Existing Cuts</h3>
<!-- Location Management Footer Actions -->
<div class="panel-footer">
<h3 class="panel-title">Location Management</h3>
<div class="panel-actions">
<button id="refresh-cuts-btn" class="btn btn-secondary btn-sm">Refresh</button>
<button id="export-cuts-btn" class="btn btn-secondary btn-sm">Export All</button>
<label for="import-cuts-file" class="btn btn-secondary btn-sm" style="margin: 0;">
Import
<input type="file" id="import-cuts-file" accept=".json" style="display: none;">
</label>
<button id="toggle-location-visibility" class="btn btn-primary btn-sm">Show Locations</button>
<button id="export-cut-locations" class="btn btn-success btn-sm">Export Data</button>
<button id="print-cut-view" class="btn btn-secondary btn-sm">Print View</button>
</div>
</div>
<div class="panel-content">
<div class="cuts-filters">
<input type="text" id="cuts-search" placeholder="Search cuts..." class="form-control">
<select id="cuts-category-filter" class="form-control">
<option value="">All Categories</option>
<option value="Custom">Custom</option>
<option value="Ward">Ward</option>
<option value="Neighborhood">Neighborhood</option>
<option value="District">District</option>
</select>
</div>
</div>
<!-- Map and Drawing Controls -->
<div class="cuts-map-section">
<div id="cuts-map" class="admin-map"></div>
<!-- Drawing Toolbar -->
<div id="cut-drawing-toolbar" class="cut-drawing-toolbar">
<div class="toolbar-content">
<div class="vertex-count" id="vertex-count">0</div>
<div class="style-controls">
<div class="color-control">
<label>Color:</label>
<input type="color" id="toolbar-color" value="#3388ff">
</div>
<div class="opacity-control">
<label>Opacity:</label>
<input type="range" id="toolbar-opacity" min="0" max="1" step="0.05" value="0.3">
<span class="opacity-value" id="toolbar-opacity-display">30%</span>
</div>
</div>
<div id="cuts-list" class="cuts-list">
<!-- Cuts will be populated here -->
<div class="toolbar-buttons">
<button type="button" id="finish-cut-btn" class="primary" disabled>Finish</button>
<button type="button" id="undo-vertex-btn" class="secondary" disabled>Undo</button>
<button type="button" id="clear-vertices-btn" class="secondary" disabled>Clear</button>
<button type="button" id="cancel-cut-btn" class="danger">Cancel</button>
</div>
</div>
</div>

View File

@@ -8,6 +8,63 @@
gap: var(--padding-base);
}
/* Top section containing form and existing cuts */
.cuts-top-section {
display: flex;
flex-direction: column;
gap: var(--padding-base);
}
/* Streamlined form card for Cut Properties */
.cuts-form-card {
margin-bottom: 0;
}
.cuts-form-card .panel-content {
padding: 15px 20px;
}
.cut-form-compact {
display: grid;
gap: 12px;
max-width: none;
}
.cut-form-compact .form-row {
display: grid;
grid-template-columns: 1fr 200px;
gap: 15px;
}
.cut-form-compact .form-options {
display: flex;
gap: 20px;
align-items: center;
flex-wrap: wrap;
}
.cut-form-compact .checkbox-group {
display: flex;
align-items: center;
gap: 8px;
margin: 0;
}
.cut-form-compact .checkbox-group input {
margin: 0;
}
.cut-form-compact .checkbox-group label {
margin: 0;
font-weight: normal;
font-size: 14px;
}
.cut-form-compact textarea {
resize: vertical;
min-height: 60px;
}
.cuts-map-section {
position: relative;
height: 500px;
@@ -24,8 +81,9 @@
}
.cuts-form-section,
.cuts-list-section {
margin-top: var(--padding-base);
.cuts-list-section,
.cuts-location-section {
margin-top: 0;
}
.cuts-filters {
@@ -42,6 +100,122 @@
border-radius: var(--border-radius);
}
/* Location popup styling for cuts map */
.location-popup {
font-size: 13px;
line-height: 1.4;
max-width: 280px;
}
.location-popup .location-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
padding-bottom: 6px;
border-bottom: 1px solid #eee;
}
.location-popup .location-header strong {
color: #333;
font-size: 14px;
}
.location-popup .support-level {
padding: 2px 6px;
border-radius: 3px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
}
.location-popup .support-level-1 {
background: #28a745;
color: white;
}
.location-popup .support-level-2 {
background: #ffc107;
color: #212529;
}
.location-popup .support-level-3 {
background: #fd7e14;
color: white;
}
.location-popup .support-level-4 {
background: #dc3545;
color: white;
}
.location-popup .support-level-unknown {
background: #6c757d;
color: white;
}
.location-popup .location-address {
margin-bottom: 6px;
color: #555;
font-weight: 500;
}
.location-popup .location-contact {
margin-bottom: 8px;
color: #666;
font-size: 12px;
}
.location-popup .location-details {
margin-bottom: 8px;
}
.location-popup .detail-row {
display: flex;
justify-content: space-between;
margin-bottom: 3px;
font-size: 12px;
}
.location-popup .detail-label {
color: #666;
font-weight: 500;
}
.location-popup .detail-value {
color: #333;
}
.location-popup .detail-value.coordinates {
font-family: 'Courier New', monospace;
font-size: 11px;
}
.location-popup .location-notes {
margin-top: 8px;
padding-top: 6px;
border-top: 1px solid #eee;
font-size: 12px;
color: #666;
}
.location-popup .location-notes strong {
color: #333;
}
/* Popup container styling */
.leaflet-popup.location-popup-container .leaflet-popup-content {
margin: 12px 16px;
}
.leaflet-popup.location-popup-container .leaflet-popup-content-wrapper {
border-radius: 6px;
}
.leaflet-popup.location-popup-container .leaflet-popup-tip {
background: white;
}
/* Disabled Form State */
.cut-form.disabled {
opacity: 0.7;
@@ -196,23 +370,37 @@
/* Large Screen Layout for Cuts */
@media (min-width: 1200px) {
.cuts-container {
.cuts-top-section {
display: grid;
grid-template-columns: 1fr;
grid-template-columns: 2fr 1fr;
gap: var(--padding-base);
}
.cuts-form-section,
.cuts-list-section {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--padding-base);
margin-top: 0;
order: 1;
}
.cuts-form-section > *,
.cuts-list-section > * {
grid-column: span 1;
.cuts-form-section {
order: 2;
}
}
/* Medium and Small Screen Adjustments */
@media (max-width: 768px) {
.cut-form-compact .form-row {
grid-template-columns: 1fr;
gap: 12px;
}
.cut-form-compact .form-options {
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
.cuts-filters {
grid-template-columns: 1fr;
gap: 8px;
}
}
@@ -269,13 +457,46 @@
/* Input group for public links */
.input-group {
display: flex;
gap: 0.5rem;
border-radius: var(--border-radius);
}
.input-group input {
flex: 1;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-right: none;
}
.input-group .btn {
white-space: nowrap;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
/* Panel footer styling - same as header but positioned at bottom */
.panel-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
border-bottom: none;
border-top: 1px solid var(--border-color);
background: #e3f2fd;
background: linear-gradient(to bottom, #e3f2fd, #bbdefb);
border-bottom-left-radius: var(--border-radius);
border-bottom-right-radius: var(--border-radius);
margin: 0;
box-shadow: 0 -2px 4px rgba(0, 0, 0, 0.05);
border-top: 2px solid #90caf9;
}
.panel-footer h3 {
margin: 0;
font-size: 1.1rem;
color: var(--text-primary);
font-weight: 600;
}
.panel-footer .panel-actions {
display: flex;
gap: 10px;
}

View File

@@ -1185,7 +1185,7 @@
}
.cuts-location-section .panel-title {
color: white;
color: rgb(0, 0, 0);
}
.cuts-location-section .btn-sm {

View File

@@ -5,7 +5,8 @@
// Global admin state
let adminAppState = {
currentSection: 'dashboard'
currentSection: 'dashboard',
dashboardLoading: false
};
// Utility function to create a local date from YYYY-MM-DD string
@@ -173,9 +174,8 @@ function loadSectionData(sectionId) {
}
break;
case 'dashboard':
if (typeof loadDashboardData === 'function') {
loadDashboardData();
}
// Always use our guarded dashboard loading function
loadDashboardDataFromDashboardModule();
break;
case 'shifts':
if (typeof loadAdminShifts === 'function') {
@@ -188,6 +188,21 @@ function loadSectionData(sectionId) {
loadUsers();
}
break;
case 'start-location':
// Initialize or refresh map when start location section becomes visible
setTimeout(() => {
if (window.adminMap && window.adminMap.initializeAdminMap) {
console.log('Ensuring admin map is initialized for start-location section...');
// This will either initialize the map (if not done) or invalidate size (if already initialized)
window.adminMap.initializeAdminMap();
// Also load the current start location data
if (window.adminMap.loadCurrentStartLocation) {
window.adminMap.loadCurrentStartLocation();
}
}
}, 100);
break;
case 'convert-data':
// Initialize data convert event listeners when section is shown
setTimeout(() => {
@@ -289,6 +304,71 @@ function debounce(func, wait) {
};
}
// Fallback function to load dashboard data directly from dashboard.js module
async function loadDashboardDataFromDashboardModule() {
// Prevent multiple simultaneous loads
if (adminAppState.dashboardLoading) {
console.log('Dashboard already loading, skipping duplicate request');
return;
}
adminAppState.dashboardLoading = true;
console.log('Loading dashboard data from dashboard module');
try {
// Show loading state
const cards = document.querySelectorAll('.card-value');
cards.forEach(card => {
card.textContent = '...';
card.style.opacity = '0.6';
});
const response = await fetch('/api/admin/dashboard/stats');
const result = await response.json();
if (result.success) {
// Update summary cards
const data = result.data;
document.getElementById('total-locations').textContent = data.totalLocations.toLocaleString();
document.getElementById('overall-score').textContent = data.overallScore;
document.getElementById('sign-delivered').textContent = data.signDelivered.toLocaleString();
document.getElementById('total-users').textContent = data.totalUsers.toLocaleString();
// Clear loading state
cards.forEach(card => {
card.style.opacity = '1';
});
// Create charts if functions are available globally
if (typeof createSupportLevelChart === 'function') {
createSupportLevelChart(data.supportLevels);
}
if (typeof createSignSizesChart === 'function') {
createSignSizesChart(data.signSizes);
}
if (typeof createEntriesChart === 'function') {
createEntriesChart(data.dailyEntries);
}
console.log('Dashboard data loaded successfully');
} else {
showStatus('Failed to load dashboard data', 'error');
}
} catch (error) {
console.error('Dashboard loading error:', error);
showStatus('Error loading dashboard', 'error');
// Clear loading state
const cards = document.querySelectorAll('.card-value');
cards.forEach(card => {
card.style.opacity = '1';
card.textContent = 'Error';
});
} finally {
adminAppState.dashboardLoading = false;
}
}
// Initialize the admin core when DOM is loaded
function initializeAdminCore() {
// Set initial viewport dimensions and listen for resize events
@@ -324,5 +404,6 @@ window.adminCore = {
debounce,
createLocalDate,
initializeAdminCore,
loadDashboardDataFromDashboardModule,
adminAppState
};

View File

@@ -20,6 +20,9 @@ class AdminCutsManager {
this.previewLayer = null;
this.editingCutId = null;
// Location markers for map display
this.locationMarkers = null;
// Bind event handler once to avoid issues with removing listeners
this.boundHandleCutActionClick = this.handleCutActionClick.bind(this);
}
@@ -402,6 +405,12 @@ class AdminCutsManager {
if (printBtn) {
printBtn.addEventListener('click', () => this.printUtils.printCutView());
}
// Set up show locations toggle
const showLocationsToggle = document.getElementById('show-locations-on-map');
if (showLocationsToggle) {
showLocationsToggle.addEventListener('change', (e) => this.toggleLocationsOnMap(e.target.checked));
}
}
// Set up toolbar controls for real-time drawing feedback
@@ -1394,6 +1403,13 @@ class AdminCutsManager {
// Clear current cut
this.currentCutId = null;
// Reset location toggle to unchecked and hide locations
const showLocationsToggle = document.getElementById('show-locations-on-map');
if (showLocationsToggle) {
showLocationsToggle.checked = false;
this.toggleLocationsOnMap(false);
}
// Clear any preview
if (this.cutDrawing) {
this.cutDrawing.clearPreview();
@@ -1454,6 +1470,150 @@ class AdminCutsManager {
this.showStatus('Cuts exported successfully', 'success');
}
// Toggle locations visibility on the cuts map
async toggleLocationsOnMap(show) {
if (!this.cutsMap) {
console.warn('Cuts map not initialized');
return;
}
try {
if (show) {
// Load and display locations on the map
const response = await fetch('/api/locations');
const data = await response.json();
if (data.success && data.locations) {
// Remove existing location markers if any
if (this.locationMarkers) {
this.locationMarkers.clearLayers();
} else {
this.locationMarkers = L.layerGroup().addTo(this.cutsMap);
}
// Add location markers to the map
data.locations.forEach(location => {
if (location.latitude && location.longitude) {
// Handle multiple possible field names for support level
const supportLevel = location.support_level ||
location['Support Level'] ||
location.supportLevel ||
location['support level'] ||
location.Support_Level ||
'unknown';
// Debug logging for first few locations
if (data.locations.indexOf(location) < 3) {
console.log('Location debug:', {
support_level: location.support_level,
'Support Level': location['Support Level'],
supportLevel: location.supportLevel,
'support level': location['support level'],
Support_Level: location.Support_Level,
finalSupportLevel: supportLevel,
allKeys: Object.keys(location)
});
}
const marker = L.circleMarker([location.latitude, location.longitude], {
radius: 8,
fillColor: this.getLocationColor(supportLevel),
color: '#fff',
weight: 2,
opacity: 1,
fillOpacity: 0.8
});
// Add popup with location info
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 || '';
const email = location.email || location.Email || '';
const phone = location.phone || location.Phone || '';
const contact = [email, phone].filter(Boolean).join(', ');
const hasSign = location.sign || location.Sign ? 'Yes' : 'No';
const signSize = location.sign_size || location['Sign Size'] || '';
const notes = location.notes || location.Notes || '';
const supportLevelText = this.getSupportLevelText(supportLevel);
const popupContent = `
<div class="location-popup">
<div class="location-header">
<strong>${name}</strong>
<span class="support-level support-level-${supportLevel || 'unknown'}">${supportLevelText}</span>
</div>
<div class="location-address">
📍 ${address || 'No address available'}
</div>
${contact ? `<div class="location-contact">
📞 ${contact}
</div>` : ''}
<div class="location-details">
<div class="detail-row">
<span class="detail-label">Lawn Sign:</span>
<span class="detail-value">${hasSign}${signSize ? ` (${signSize})` : ''}</span>
</div>
${location.latitude && location.longitude ? `
<div class="detail-row">
<span class="detail-label">Coordinates:</span>
<span class="detail-value coordinates">${parseFloat(location.latitude).toFixed(6)}, ${parseFloat(location.longitude).toFixed(6)}</span>
</div>` : ''}
</div>
${notes ? `<div class="location-notes">
<strong>Notes:</strong> ${notes.length > 100 ? notes.substring(0, 100) + '...' : notes}
</div>` : ''}
</div>
`;
marker.bindPopup(popupContent, {
maxWidth: 300,
className: 'location-popup-container'
});
this.locationMarkers.addLayer(marker);
}
});
console.log(`Added ${data.locations.length} location markers to cuts map`);
} else {
console.warn('No locations data received');
}
} else {
// Hide locations
if (this.locationMarkers) {
this.cutsMap.removeLayer(this.locationMarkers);
this.locationMarkers = null;
}
console.log('Removed location markers from cuts map');
}
} catch (error) {
console.error('Error toggling locations on map:', error);
this.showStatus('Failed to load locations', 'error');
}
}
// Helper method to get color based on support level
getLocationColor(supportLevel) {
switch (String(supportLevel)) {
case '1': return '#28a745'; // Green - Strong Support
case '2': return '#ffc107'; // Yellow - Lean Support
case '3': return '#fd7e14'; // Orange - Lean Opposition
case '4': return '#dc3545'; // Red - Strong Opposition
default: return '#6c757d'; // Gray - Unknown
}
}
// Helper method to get support level text
getSupportLevelText(supportLevel) {
switch (String(supportLevel)) {
case '1': return 'Strong Support';
case '2': return 'Lean Support';
case '3': return 'Lean Opposition';
case '4': return 'Strong Opposition';
default: return 'Unknown';
}
}
async handleImportFile(event) {
const file = event.target.files[0];
if (!file) return;

View File

@@ -9,6 +9,28 @@ let startMarker = null;
// Initialize the admin map
function initializeAdminMap() {
// Check if the map container is visible before initializing
const mapContainer = document.getElementById('admin-map');
if (!mapContainer) {
console.warn('Admin map container not found, delaying initialization');
return;
}
// If container is hidden, delay initialization
const isVisible = mapContainer.offsetWidth > 0 && mapContainer.offsetHeight > 0;
if (!isVisible) {
console.log('Admin map container is hidden, delaying initialization until section is shown');
return;
}
// Avoid double initialization
if (adminMap) {
console.log('Admin map already initialized, invalidating size instead');
adminMap.invalidateSize();
return;
}
console.log('Initializing admin map...');
adminMap = L.map('admin-map').setView([53.5461, -113.4938], 11);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
@@ -40,6 +62,13 @@ function initializeAdminMap() {
// Update coordinates when map moves
adminMap.on('moveend', updateCoordinatesFromMap);
// Trigger size invalidation after a brief moment to ensure proper rendering
setTimeout(() => {
if (adminMap) {
adminMap.invalidateSize();
}
}, 100);
}
// Load current start location

View File

@@ -102,30 +102,31 @@ document.addEventListener('DOMContentLoaded', () => {
if (window.adminCore && typeof window.adminCore.showSection === 'function') {
window.adminCore.showSection('dashboard');
}
// Load dashboard data on initial page load
if (typeof loadDashboardData === 'function') {
loadDashboardData();
}
// Dashboard loading is handled by admin-core.js showSection/loadSectionData
// No need to manually load here to avoid duplicates
}
});
/**
* Dashboard Functions
* Loads dashboard data and displays summary statistics
* Legacy Dashboard Functions - DEPRECATED
* These are kept for backward compatibility but should not be used
* Use the dashboard.js module functions instead
*/
async function loadDashboardData() {
async function loadAdminDashboardData() {
try {
const response = await fetch('/api/admin/dashboard');
const data = await response.json();
if (data.success) {
// This was the old function that only loaded user/shift stats
// Now deprecated in favor of the full dashboard.js implementation
document.getElementById('total-users').textContent = data.stats.totalUsers;
document.getElementById('total-shifts').textContent = data.stats.totalShifts;
document.getElementById('total-signups').textContent = data.stats.totalSignups;
document.getElementById('this-month-users').textContent = data.stats.thisMonthUsers;
}
} catch (error) {
console.error('Failed to load dashboard data:', error);
console.error('Failed to load admin dashboard data:', error);
}
}
@@ -133,11 +134,11 @@ async function loadDashboardData() {
* Legacy function redirects for backward compatibility
* These ensure existing functionality continues to work
*/
window.loadDashboardData = loadDashboardData;
window.loadAdminDashboardData = loadAdminDashboardData;
// Export dashboard function for module coordination
if (typeof window.adminDashboard === 'undefined') {
window.adminDashboard = {
loadDashboardData: loadDashboardData
loadAdminDashboardData: loadAdminDashboardData
};
}

View File

@@ -2,9 +2,19 @@
let supportChart = null;
let entriesChart = null;
let signSizesChart = null;
let dashboardLoading = false;
// Load dashboard data
async function loadDashboardData() {
// Prevent multiple simultaneous loads
if (dashboardLoading) {
console.log('Dashboard already loading, skipping duplicate request');
return;
}
dashboardLoading = true;
console.log('Loading dashboard data from dashboard.js');
try {
// Show loading state
setLoadingState(true);
@@ -17,6 +27,7 @@ async function loadDashboardData() {
createSupportLevelChart(result.data.supportLevels);
createSignSizesChart(result.data.signSizes);
createEntriesChart(result.data.dailyEntries);
console.log('Dashboard data loaded successfully from dashboard.js');
} else {
showStatus('Failed to load dashboard data', 'error');
}
@@ -25,6 +36,7 @@ async function loadDashboardData() {
showStatus('Error loading dashboard', 'error');
} finally {
setLoadingState(false);
dashboardLoading = false;
}
}
@@ -307,32 +319,37 @@ function createEntriesChart(dailyEntries) {
// Add event listener for dashboard navigation
document.addEventListener('DOMContentLoaded', () => {
// Update navigation to load dashboard when clicked
// Dashboard navigation is handled by admin-core.js
// This listener is kept for backward compatibility but should not interfere
const dashboardLink = document.querySelector('.admin-nav a[href="#dashboard"]');
if (dashboardLink) {
dashboardLink.addEventListener('click', (e) => {
e.preventDefault();
// Hide all sections
document.querySelectorAll('.admin-section').forEach(section => {
section.style.display = 'none';
// Only add our listener if admin-core.js hasn't already handled it
const hasAdminCore = window.adminCore && typeof window.adminCore.showSection === 'function';
if (!hasAdminCore) {
dashboardLink.addEventListener('click', (e) => {
e.preventDefault();
// Hide all sections
document.querySelectorAll('.admin-section').forEach(section => {
section.style.display = 'none';
});
// Show dashboard
const dashboardSection = document.getElementById('dashboard');
if (dashboardSection) {
dashboardSection.style.display = 'block';
}
// Update active nav
document.querySelectorAll('.admin-nav a').forEach(link => {
link.classList.remove('active');
});
dashboardLink.classList.add('active');
// Load dashboard data
loadDashboardData();
});
// Show dashboard
const dashboardSection = document.getElementById('dashboard');
if (dashboardSection) {
dashboardSection.style.display = 'block';
}
// Update active nav
document.querySelectorAll('.admin-nav a').forEach(link => {
link.classList.remove('active');
});
dashboardLink.classList.add('active');
// Load dashboard data
loadDashboardData();
});
}
}
// Handle window resize for chart responsiveness