/** * Admin Shifts Management Module * Handles shift CRUD operations, volunteer management, and email functionality */ // Shift state let editingShiftId = null; let currentShiftData = null; let allUsers = []; // Add shift management functions async function loadAdminShifts() { const list = document.getElementById('admin-shifts-list'); if (list) { list.innerHTML = '

Loading shifts...

'; } try { console.log('Loading admin shifts...'); const response = await fetch('/api/shifts/admin'); const data = await response.json(); if (data.success) { console.log('Successfully loaded', data.shifts.length, 'shifts'); displayAdminShifts(data.shifts); } else { console.error('Failed to load shifts:', data.error); if (list) { list.innerHTML = '

Failed to load shifts

'; } window.adminCore.showStatus('Failed to load shifts', 'error'); } } catch (error) { console.error('Error loading admin shifts:', error); if (list) { list.innerHTML = '

Error loading shifts

'; } window.adminCore.showStatus('Failed to load shifts', 'error'); } } function displayAdminShifts(shifts) { const list = document.getElementById('admin-shifts-list'); if (!list) { console.error('Admin shifts list element not found'); return; } if (shifts.length === 0) { list.innerHTML = '

No shifts created yet.

'; return; } list.innerHTML = shifts.map(shift => { const shiftDate = window.adminCore.createLocalDate(shift.Date); const signupCount = shift.signups ? shift.signups.length : 0; const isPublic = shift['Is Public'] !== false; console.log(`Shift "${shift.Title}" (ID: ${shift.ID}) has ${signupCount} volunteers:`, shift.signups?.map(s => s['User Email']) || []); // Generate list of first names for volunteers const firstNames = shift.signups ? shift.signups.map(volunteer => { const fullName = volunteer['User Name'] || volunteer['User Email'] || 'Unknown'; // Extract first name (everything before first space, or email username if no space) const firstName = fullName.includes(' ') ? fullName.split(' ')[0] : fullName.includes('@') ? fullName.split('@')[0] : fullName; return window.adminCore.escapeHtml(firstName); }).slice(0, 8) : []; // Limit to first 8 names to avoid overflow const namesDisplay = firstNames.length > 0 ? `(${firstNames.join(', ')}${firstNames.length === 8 && signupCount > 8 ? '...' : ''})` : ''; return `

${window.adminCore.escapeHtml(shift.Title)}

📅 ${shiftDate.toLocaleDateString()} | ⏰ ${shift['Start Time']} - ${shift['End Time']}

📍 ${window.adminCore.escapeHtml(shift.Location || 'TBD')}

👥 ${signupCount}/${shift['Max Volunteers']} volunteers ${namesDisplay}

${shift.Status || 'Open'}

${isPublic ? '🌐 Public' : '🔒 Private'}

${isPublic ? ` ` : ''}
`; }).join(''); // Add event listeners using delegation setupShiftActionListeners(); } // Setup shift action listeners function setupShiftActionListeners() { const list = document.getElementById('admin-shifts-list'); if (!list) return; // Remove any existing listeners to avoid duplicates const newList = list.cloneNode(true); list.parentNode.replaceChild(newList, list); // Get the updated reference const updatedList = document.getElementById('admin-shifts-list'); updatedList.addEventListener('click', function(e) { if (e.target.classList.contains('delete-shift-btn')) { const shiftId = e.target.getAttribute('data-shift-id'); console.log('Delete button clicked for shift:', shiftId); deleteShift(shiftId); } else if (e.target.classList.contains('edit-shift-btn')) { const shiftId = e.target.getAttribute('data-shift-id'); console.log('Edit button clicked for shift:', shiftId); editShift(shiftId); } else if (e.target.classList.contains('manage-volunteers-btn')) { e.preventDefault(); e.stopPropagation(); const shiftId = e.target.getAttribute('data-shift-id'); const shiftDataStr = e.target.getAttribute('data-shift'); console.log('Manage volunteers clicked for shift:', shiftId); console.log('Checking for adminShiftVolunteers module availability...'); console.log('adminShiftVolunteers exists:', !!window.adminShiftVolunteers); if (window.adminShiftVolunteers) { console.log('adminShiftVolunteers functions:', Object.keys(window.adminShiftVolunteers)); console.log('showShiftUserModal type:', typeof window.adminShiftVolunteers.showShiftUserModal); } // Parse the shift data let shiftData; try { shiftData = JSON.parse(shiftDataStr.replace(/'/g, "'")); console.log('Parsed shift data:', shiftData); } catch (error) { console.error('Error parsing shift data:', error); if (window.adminCore && window.adminCore.showStatus) { window.adminCore.showStatus('Error parsing shift data', 'error'); } return; } // Function to attempt showing the modal const attemptShowModal = () => { if (window.adminShiftVolunteers && typeof window.adminShiftVolunteers.showShiftUserModal === 'function') { console.log('Module ready, showing modal for shift:', shiftId); try { window.adminShiftVolunteers.showShiftUserModal(shiftId, shiftData); } catch (modalError) { console.error('Error showing modal:', modalError); if (window.adminCore && window.adminCore.showStatus) { window.adminCore.showStatus('Error opening volunteer management modal', 'error'); } } return true; } else { console.log('Module not ready - adminShiftVolunteers:', !!window.adminShiftVolunteers); if (window.adminShiftVolunteers) { console.log('Available functions:', Object.keys(window.adminShiftVolunteers)); } return false; } }; // Try immediately if (!attemptShowModal()) { // If not ready, wait for the ready event or use timeout let attempts = 0; const maxAttempts = 20; const retryInterval = 250; const retryTimer = setInterval(() => { attempts++; console.log(`Retry attempt ${attempts}/${maxAttempts} for volunteer modal...`); if (attemptShowModal()) { clearInterval(retryTimer); } else if (attempts >= maxAttempts) { clearInterval(retryTimer); console.error('Failed to load volunteer management module after', maxAttempts, 'attempts'); console.log('Final state - adminShiftVolunteers:', window.adminShiftVolunteers); if (window.adminCore && window.adminCore.showStatus) { window.adminCore.showStatus('Volunteer management module failed to load. Please refresh the page.', 'error'); } } }, retryInterval); // Also listen for the ready event as backup const readyListener = () => { console.log('Received adminShiftVolunteersReady event'); if (attemptShowModal()) { clearInterval(retryTimer); window.removeEventListener('adminShiftVolunteersReady', readyListener); } }; window.addEventListener('adminShiftVolunteersReady', readyListener); } } else if (e.target.classList.contains('copy-shift-link-btn')) { const shiftId = e.target.getAttribute('data-shift-id'); console.log('Copy link button clicked for shift:', shiftId); copyShiftLink(shiftId); } else if (e.target.classList.contains('open-shift-link-btn')) { const shiftId = e.target.getAttribute('data-shift-id'); console.log('Open link button clicked for shift:', shiftId); openShiftLink(shiftId); } }); } // Delete shift async function deleteShift(shiftId) { if (!confirm('Are you sure you want to delete this shift? All signups will be cancelled.')) { return; } try { const response = await fetch(`/api/shifts/admin/${shiftId}`, { method: 'DELETE' }); const data = await response.json(); if (data.success) { window.adminCore.showStatus('Shift deleted successfully', 'success'); await loadAdminShifts(); console.log('Refreshed shifts list after deleting shift'); } else { window.adminCore.showStatus(data.error || 'Failed to delete shift', 'error'); } } catch (error) { console.error('Error deleting shift:', error); window.adminCore.showStatus('Failed to delete shift', 'error'); } } // Edit shift async function editShift(shiftId) { try { // Find the shift in the current data const response = await fetch('/api/shifts/admin'); const data = await response.json(); if (!data.success) { window.adminCore.showStatus('Failed to load shift data', 'error'); return; } const shift = data.shifts.find(s => s.ID === parseInt(shiftId)); if (!shift) { window.adminCore.showStatus('Shift not found', 'error'); return; } // Set editing mode editingShiftId = shiftId; // Populate the form const titleInput = document.getElementById('shift-title'); const descInput = document.getElementById('shift-description'); const dateInput = document.getElementById('shift-date'); const startInput = document.getElementById('shift-start'); const endInput = document.getElementById('shift-end'); const locationInput = document.getElementById('shift-location'); const maxVolInput = document.getElementById('shift-max-volunteers'); if (titleInput) titleInput.value = shift.Title || ''; if (descInput) descInput.value = shift.Description || ''; if (dateInput) dateInput.value = shift.Date || ''; if (startInput) startInput.value = shift['Start Time'] || ''; if (endInput) endInput.value = shift['End Time'] || ''; if (locationInput) locationInput.value = shift.Location || ''; if (maxVolInput) maxVolInput.value = shift['Max Volunteers'] || ''; // Update public checkbox if it exists const publicCheckbox = document.getElementById('shift-is-public'); if (publicCheckbox) { publicCheckbox.checked = shift['Is Public'] !== false; } // Change submit button text const submitBtn = document.querySelector('#shift-form button[type="submit"]'); if (submitBtn) { submitBtn.textContent = 'Update Shift'; } // Remove editing class from any previous item document.querySelectorAll('.shift-admin-item.editing').forEach(el => { el.classList.remove('editing'); }); // Add editing class to current item const shiftElement = document.querySelector(`[data-shift-id="${shiftId}"]`); if (shiftElement) { const shiftItem = shiftElement.closest('.shift-admin-item'); if (shiftItem) { shiftItem.classList.add('editing'); } } // Scroll to form const form = document.getElementById('shift-form'); if (form) { form.scrollIntoView({ behavior: 'smooth' }); } window.adminCore.showStatus('Editing shift: ' + shift.Title, 'info'); } catch (error) { console.error('Error loading shift for edit:', error); window.adminCore.showStatus('Failed to load shift for editing', 'error'); } } // Create or update shift async function createShift(e) { e.preventDefault(); const titleInput = document.getElementById('shift-title'); const descInput = document.getElementById('shift-description'); const dateInput = document.getElementById('shift-date'); const startInput = document.getElementById('shift-start'); const endInput = document.getElementById('shift-end'); const locationInput = document.getElementById('shift-location'); const maxVolInput = document.getElementById('shift-max-volunteers'); const title = titleInput?.value; const description = descInput?.value; const date = dateInput?.value; const startTime = startInput?.value; const endTime = endInput?.value; const location = locationInput?.value; const maxVolunteers = maxVolInput?.value; // Get public checkbox value const publicCheckbox = document.getElementById('shift-is-public'); const isPublic = publicCheckbox?.checked ?? true; const shiftData = { title, description, date, startTime, endTime, location, maxVolunteers: parseInt(maxVolunteers), isPublic }; try { let response; if (editingShiftId) { // Update existing shift response = await fetch(`/api/shifts/admin/${editingShiftId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(shiftData) }); } else { // Create new shift response = await fetch('/api/shifts/admin', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(shiftData) }); } const data = await response.json(); if (data.success) { window.adminCore.showStatus(editingShiftId ? 'Shift updated successfully' : 'Shift created successfully', 'success'); clearShiftForm(); await loadAdminShifts(); console.log('Refreshed shifts list after saving shift'); } else { window.adminCore.showStatus(data.error || 'Failed to save shift', 'error'); } } catch (error) { console.error('Error saving shift:', error); window.adminCore.showStatus('Failed to save shift', 'error'); } } function clearShiftForm() { const form = document.getElementById('shift-form'); if (form) { form.reset(); // Reset editing state editingShiftId = null; // Reset submit button text const submitBtn = document.querySelector('#shift-form button[type="submit"]'); if (submitBtn) { submitBtn.textContent = 'Create Shift'; } // Remove editing class from any shift items document.querySelectorAll('.shift-admin-item.editing').forEach(el => { el.classList.remove('editing'); }); window.adminCore.showStatus('Form cleared', 'info'); } } // Public Shifts Functions function generateShiftPublicLink(shiftId) { const baseUrl = window.location.origin; return `${baseUrl}/public-shifts.html#shift-${shiftId}`; } function copyShiftLink(shiftId) { const link = generateShiftPublicLink(shiftId); navigator.clipboard.writeText(link).then(() => { window.adminCore.showStatus('Public shift link copied to clipboard!', 'success'); }).catch(() => { // Fallback for older browsers const textArea = document.createElement('textarea'); textArea.value = link; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); window.adminCore.showStatus('Public shift link copied to clipboard!', 'success'); }); } function openShiftLink(shiftId) { const link = generateShiftPublicLink(shiftId); window.open(link, '_blank'); } // Update the shift form to include Is Public checkbox function updateShiftFormWithPublicOption() { const form = document.getElementById('shift-form'); if (!form) return; // Check if public checkbox already exists if (document.getElementById('shift-is-public')) return; const maxVolunteersGroup = form.querySelector('.form-group:has(#shift-max-volunteers)'); if (maxVolunteersGroup) { const publicGroup = document.createElement('div'); publicGroup.className = 'form-group'; publicGroup.innerHTML = ` `; maxVolunteersGroup.insertAdjacentElement('afterend', publicGroup); } } // Setup shift event listeners function setupShiftEventListeners() { // Shift form submission const shiftForm = document.getElementById('shift-form'); if (shiftForm) { shiftForm.addEventListener('submit', createShift); } // Clear shift form button const clearShiftBtn = document.getElementById('clear-shift-form'); if (clearShiftBtn) { clearShiftBtn.addEventListener('click', function() { const wasEditing = editingShiftId !== null; clearShiftForm(); if (wasEditing) { window.adminCore.showStatus('Edit cancelled', 'info'); } }); } } // Export shift management functions window.adminShifts = { loadAdminShifts, displayAdminShifts, deleteShift, editShift, createShift, clearShiftForm, generateShiftPublicLink, copyShiftLink, openShiftLink, updateShiftFormWithPublicOption, setupShiftEventListeners, getEditingShiftId: () => editingShiftId, getCurrentShiftData: () => currentShiftData, getAllUsers: () => allUsers };