/**
* Admin Shift Volunteers Module
* Handles volunteer management modals and shift email functionality
*/
// Volunteer management state
let currentShiftData = null;
let allUsers = [];
// Load all users for the dropdown
async function loadAllUsers() {
try {
const response = await fetch('/api/users');
const data = await response.json();
if (data.success) {
allUsers = data.users;
populateUserSelect();
} else {
console.error('Failed to load users:', data.error);
}
} catch (error) {
console.error('Error loading users:', error);
}
}
// Populate user select dropdown
function populateUserSelect() {
const select = document.getElementById('user-select');
if (!select) return;
// Clear existing options except the first one
select.innerHTML = '';
allUsers.forEach(user => {
const option = document.createElement('option');
option.value = user.email || user.Email;
option.textContent = `${user.name || user.Name || ''} (${user.email || user.Email})`;
select.appendChild(option);
});
}
// Show the shift user management modal
async function showShiftUserModal(shiftId, shiftData) {
currentShiftData = { ...shiftData, ID: shiftId };
// Update modal title and info
const modalTitle = document.getElementById('modal-shift-title');
const modalDetails = document.getElementById('modal-shift-details');
if (modalTitle) modalTitle.textContent = shiftData.Title;
if (modalDetails) {
const shiftDate = window.adminCore.createLocalDate(shiftData.Date);
modalDetails.textContent =
`${shiftDate.toLocaleDateString()} | ${shiftData['Start Time']} - ${shiftData['End Time']} | ${shiftData.Location || 'TBD'}`;
}
// Load users if not already loaded
if (allUsers.length === 0) {
await loadAllUsers();
}
// Display current volunteers
displayCurrentVolunteers(shiftData.signups || []);
// Show modal
const modal = document.getElementById('shift-user-modal');
if (modal) {
modal.style.display = 'flex';
}
}
// Display current volunteers in the modal
function displayCurrentVolunteers(volunteers) {
const container = document.getElementById('current-volunteers-list');
if (!container) return;
if (!volunteers || volunteers.length === 0) {
container.innerHTML = '
No volunteers signed up yet.
';
return;
}
container.innerHTML = volunteers.map(volunteer => `
${window.adminCore.escapeHtml(volunteer['User Name'] || volunteer['User Email'] || 'Unknown')}
${window.adminCore.escapeHtml(volunteer['User Email'])}
`).join('');
// Add event listeners for remove buttons
setupVolunteerActionListeners();
}
// Setup event listeners for volunteer actions
function setupVolunteerActionListeners() {
const container = document.getElementById('current-volunteers-list');
if (container) {
container.addEventListener('click', function(e) {
if (e.target.classList.contains('remove-volunteer-btn')) {
const volunteerId = e.target.getAttribute('data-volunteer-id');
const volunteerEmail = e.target.getAttribute('data-volunteer-email');
removeVolunteerFromShift(volunteerId, volunteerEmail);
}
});
}
}
// Add user to shift
async function addUserToShift() {
const userSelect = document.getElementById('user-select');
const userEmail = userSelect?.value;
if (!userEmail) {
window.adminCore.showStatus('Please select a user to add', 'error');
return;
}
if (!currentShiftData || !currentShiftData.ID) {
window.adminCore.showStatus('No shift selected or invalid shift data', 'error');
console.error('Invalid currentShiftData:', currentShiftData);
return;
}
try {
const response = await fetch(`/api/shifts/admin/${currentShiftData.ID}/add-user`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ userEmail })
});
const data = await response.json();
if (data.success) {
window.adminCore.showStatus('User successfully added to shift', 'success');
if (userSelect) userSelect.value = ''; // Clear selection
// Refresh the shift data and reload volunteers with better error handling
try {
await refreshCurrentShiftData();
console.log('Refreshed shift data after adding user');
} catch (refreshError) {
console.error('Error during refresh after adding user:', refreshError);
// Still show success since the add operation worked
}
} else {
window.adminCore.showStatus(data.error || 'Failed to add user to shift', 'error');
}
} catch (error) {
console.error('Error adding user to shift:', error);
window.adminCore.showStatus('Failed to add user to shift', 'error');
}
}
// Remove volunteer from shift
async function removeVolunteerFromShift(volunteerId, volunteerEmail) {
if (!confirm(`Are you sure you want to remove ${volunteerEmail} from this shift?`)) {
return;
}
if (!currentShiftData || !currentShiftData.ID) {
window.adminCore.showStatus('No shift selected or invalid shift data', 'error');
console.error('Invalid currentShiftData:', currentShiftData);
return;
}
try {
const response = await fetch(`/api/shifts/admin/${currentShiftData.ID}/remove-user/${volunteerId}`, {
method: 'DELETE'
});
const data = await response.json();
if (data.success) {
window.adminCore.showStatus('Volunteer successfully removed from shift', 'success');
// Refresh the shift data and reload volunteers with better error handling
try {
await refreshCurrentShiftData();
console.log('Refreshed shift data after removing volunteer');
} catch (refreshError) {
console.error('Error during refresh after removing volunteer:', refreshError);
// Still show success since the remove operation worked
}
} else {
window.adminCore.showStatus(data.error || 'Failed to remove volunteer from shift', 'error');
}
} catch (error) {
console.error('Error removing volunteer from shift:', error);
window.adminCore.showStatus('Failed to remove volunteer from shift', 'error');
}
}
// Refresh current shift data
async function refreshCurrentShiftData() {
if (!currentShiftData || !currentShiftData.ID) {
console.warn('No current shift data or missing ID, skipping refresh');
return;
}
try {
console.log('Refreshing shift data for shift ID:', currentShiftData.ID);
// Instead of reloading ALL admin shifts, just get this specific shift's signups
// This prevents the expensive backend call and reduces the refresh cascade
const response = await fetch(`/api/shifts/admin`);
const data = await response.json();
if (data.success && data.shifts && Array.isArray(data.shifts)) {
const updatedShift = data.shifts.find(s => s && s.ID === currentShiftData.ID);
if (updatedShift) {
console.log('Found updated shift with', updatedShift.signups?.length || 0, 'volunteers');
currentShiftData = updatedShift;
displayCurrentVolunteers(updatedShift.signups || []);
// Only update the specific shift in the main list, don't refresh everything
updateShiftInList(updatedShift);
} else {
console.warn('Could not find updated shift with ID:', currentShiftData.ID);
}
} else {
console.error('Failed to refresh shift data:', data.error || 'Invalid response format');
}
} catch (error) {
console.error('Error refreshing shift data:', error);
}
}
// Update a single shift in the list without full refresh
function updateShiftInList(updatedShift) {
const shiftElement = document.querySelector(`[data-shift-id="${updatedShift.ID}"]`);
if (shiftElement) {
const shiftItem = shiftElement.closest('.shift-admin-item');
if (shiftItem) {
const signupCount = updatedShift.signups ? updatedShift.signups.length : 0;
// Find the volunteer count paragraph (contains 👥)
const volunteerCountElement = Array.from(shiftItem.querySelectorAll('p')).find(p =>
p.textContent.includes('👥')
);
if (volunteerCountElement) {
volunteerCountElement.textContent = `👥 ${signupCount}/${updatedShift['Max Volunteers']} volunteers`;
}
// Update the data attribute with new shift data
const manageBtn = shiftItem.querySelector('.manage-volunteers-btn');
if (manageBtn) {
manageBtn.setAttribute('data-shift', JSON.stringify(updatedShift).replace(/'/g, "'"));
}
}
}
}
// Close modal
function closeShiftUserModal() {
const modal = document.getElementById('shift-user-modal');
if (modal) {
modal.style.display = 'none';
}
currentShiftData = null;
// Don't refresh the entire shifts list when closing modal
// The shifts list should already be up to date from the individual updates
console.log('Modal closed - shifts list should already be current');
}
// Email shift details to all volunteers
async function emailShiftDetails() {
if (!currentShiftData) {
window.adminCore.showStatus('No shift selected', 'error');
return;
}
// Check if there are volunteers to email
const volunteers = currentShiftData.signups || [];
if (volunteers.length === 0) {
window.adminCore.showStatus('No volunteers signed up for this shift', 'error');
return;
}
// Confirm action
const confirmMessage = `Send shift details email to ${volunteers.length} volunteer${volunteers.length !== 1 ? 's' : ''}?`;
if (!confirm(confirmMessage)) {
return;
}
// Initialize progress tracking for shift emails
initializeShiftEmailProgress(volunteers.length);
try {
const response = await fetch(`/api/shifts/admin/${currentShiftData.ID}/email-details`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
const data = await response.json();
if (data.success) {
// Display detailed results
updateShiftEmailProgress(data.results);
window.adminCore.showStatus(data.message, 'success');
console.log('Email results:', data.results);
} else {
showShiftEmailError(data.error || 'Failed to send emails');
if (data.details) {
console.error('Failed email details:', data.details);
}
}
} catch (error) {
console.error('Error sending shift details emails:', error);
showShiftEmailError('Failed to send emails - Network error');
}
}
// Initialize shift email progress display
function initializeShiftEmailProgress(totalCount) {
const progressContainer = document.getElementById('shift-email-progress-container');
const statusList = document.getElementById('shift-email-status-list');
const pendingCountEl = document.getElementById('shift-pending-count');
const successCountEl = document.getElementById('shift-success-count');
const errorCountEl = document.getElementById('shift-error-count');
const progressBar = document.getElementById('shift-email-progress-bar');
const progressText = document.getElementById('shift-progress-text');
const closeBtn = document.getElementById('close-shift-progress-btn');
if (!progressContainer) return;
// Show progress container
progressContainer.classList.add('show');
// Reset counters
if (pendingCountEl) pendingCountEl.textContent = totalCount;
if (successCountEl) successCountEl.textContent = '0';
if (errorCountEl) errorCountEl.textContent = '0';
// Reset progress bar
if (progressBar) {
progressBar.style.width = '0%';
progressBar.classList.remove('complete', 'error');
}
if (progressText) progressText.textContent = '0%';
// Clear status list
if (statusList) statusList.innerHTML = '';
// Hide close button initially
if (closeBtn) closeBtn.style.display = 'none';
// Add status items for each volunteer
const volunteers = currentShiftData.signups || [];
volunteers.forEach(volunteer => {
if (statusList) {
const statusItem = document.createElement('div');
statusItem.className = 'email-status-item';
statusItem.innerHTML = `
${volunteer['User Name'] || volunteer['User Email']}
`;
statusList.appendChild(statusItem);
}
});
}
// Update shift email progress with results
function updateShiftEmailProgress(results) {
const statusList = document.getElementById('shift-email-status-list');
const pendingCountEl = document.getElementById('shift-pending-count');
const successCountEl = document.getElementById('shift-success-count');
const errorCountEl = document.getElementById('shift-error-count');
const progressBar = document.getElementById('shift-email-progress-bar');
const progressText = document.getElementById('shift-progress-text');
const closeBtn = document.getElementById('close-shift-progress-btn');
const successful = results.successful || [];
const failed = results.failed || [];
const total = results.total || (successful.length + failed.length);
// Update counters
if (successCountEl) successCountEl.textContent = successful.length;
if (errorCountEl) errorCountEl.textContent = failed.length;
if (pendingCountEl) pendingCountEl.textContent = '0';
// Update progress bar
const percentage = ((successful.length + failed.length) / total * 100).toFixed(1);
if (progressBar) {
progressBar.style.width = percentage + '%';
progressText.textContent = percentage + '%';
if (failed.length > 0) {
progressBar.classList.add('error');
} else {
progressBar.classList.add('complete');
}
}
// Update individual status items
if (statusList) {
const statusItems = statusList.children;
// Update successful emails
successful.forEach(result => {
const statusItem = Array.from(statusItems).find(item =>
item.querySelector('.email-status-recipient').textContent.includes(result.email) ||
item.querySelector('.email-status-recipient').textContent.includes(result.name)
);
if (statusItem) {
statusItem.querySelector('.email-status-result').innerHTML = `
✓ Sent
`;
}
});
// Update failed emails
failed.forEach(result => {
const statusItem = Array.from(statusItems).find(item =>
item.querySelector('.email-status-recipient').textContent.includes(result.email) ||
item.querySelector('.email-status-recipient').textContent.includes(result.name)
);
if (statusItem) {
statusItem.querySelector('.email-status-result').innerHTML = `
✗ Failed
`;
}
});
}
// Show close button
if (closeBtn) {
closeBtn.style.display = 'block';
closeBtn.onclick = () => {
const progressContainer = document.getElementById('shift-email-progress-container');
if (progressContainer) {
progressContainer.classList.remove('show');
}
};
}
}
// Show shift email error
function showShiftEmailError(message) {
const progressContainer = document.getElementById('shift-email-progress-container');
const progressBar = document.getElementById('shift-email-progress-bar');
const progressText = document.getElementById('shift-progress-text');
const closeBtn = document.getElementById('close-shift-progress-btn');
// Show progress container if not visible
if (progressContainer) {
progressContainer.classList.add('show');
}
// Update progress bar to show error
if (progressBar) {
progressBar.style.width = '100%';
progressBar.classList.add('error');
}
if (progressText) progressText.textContent = 'Error';
// Show close button
if (closeBtn) {
closeBtn.style.display = 'block';
closeBtn.onclick = () => {
if (progressContainer) {
progressContainer.classList.remove('show');
}
};
}
window.adminCore.showStatus(message, 'error');
}
// Setup volunteer modal event listeners
function setupVolunteerModalEventListeners() {
const closeModalBtn = document.getElementById('close-user-modal');
const addUserBtn = document.getElementById('add-user-btn');
const emailShiftDetailsBtn = document.getElementById('email-shift-details-btn');
const modal = document.getElementById('shift-user-modal');
if (closeModalBtn) {
closeModalBtn.addEventListener('click', closeShiftUserModal);
}
if (addUserBtn) {
addUserBtn.addEventListener('click', addUserToShift);
}
if (emailShiftDetailsBtn) {
emailShiftDetailsBtn.addEventListener('click', emailShiftDetails);
}
// Close modal when clicking outside
if (modal) {
modal.addEventListener('click', function(e) {
if (e.target === modal) {
closeShiftUserModal();
}
});
}
}
// Export shift volunteer functions
window.adminShiftVolunteers = {
showShiftUserModal,
closeShiftUserModal,
addUserToShift,
removeVolunteerFromShift,
emailShiftDetails,
setupVolunteerModalEventListeners,
loadAllUsers,
getCurrentShiftData: () => currentShiftData,
getAllUsers: () => allUsers
};