freealberta/map/app/public/js/admin-shifts.js

420 lines
16 KiB
JavaScript

/**
* 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 = '<p>Loading shifts...</p>';
}
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 = '<p>Failed to load shifts</p>';
}
window.adminCore.showStatus('Failed to load shifts', 'error');
}
} catch (error) {
console.error('Error loading admin shifts:', error);
if (list) {
list.innerHTML = '<p>Error loading shifts</p>';
}
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 = '<p>No shifts created yet.</p>';
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']) || []);
return `
<div class="shift-admin-item">
<div>
<h4>${window.adminCore.escapeHtml(shift.Title)}</h4>
<p>📅 ${shiftDate.toLocaleDateString()} | ⏰ ${shift['Start Time']} - ${shift['End Time']}</p>
<p>📍 ${window.adminCore.escapeHtml(shift.Location || 'TBD')}</p>
<p>👥 ${signupCount}/${shift['Max Volunteers']} volunteers</p>
<p class="status-${(shift.Status || 'open').toLowerCase()}">${shift.Status || 'Open'}</p>
<p class="${isPublic ? 'public-shift' : 'private-shift'}">${isPublic ? '🌐 Public' : '🔒 Private'}</p>
${isPublic ? `
<div class="public-link-section">
<label>Public Link:</label>
<div class="input-group">
<input type="text" class="public-link-input" value="${generateShiftPublicLink(shift.ID)}" readonly />
<button type="button" class="btn btn-secondary btn-sm copy-shift-link-btn" data-shift-id="${shift.ID}">📋</button>
<button type="button" class="btn btn-info btn-sm open-shift-link-btn" data-shift-id="${shift.ID}">🔗</button>
</div>
</div>
` : ''}
</div>
<div class="shift-actions">
<button class="btn btn-primary btn-sm manage-volunteers-btn" data-shift-id="${shift.ID}" data-shift='${JSON.stringify(shift).replace(/'/g, "&#39;")}'>Manage Volunteers</button>
<button class="btn btn-secondary btn-sm edit-shift-btn" data-shift-id="${shift.ID}">Edit</button>
<button class="btn btn-danger btn-sm delete-shift-btn" data-shift-id="${shift.ID}">Delete</button>
</div>
</div>
`;
}).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')) {
const shiftId = e.target.getAttribute('data-shift-id');
const shiftData = JSON.parse(e.target.getAttribute('data-shift').replace(/&#39;/g, "'"));
console.log('Manage volunteers clicked for shift:', shiftId);
showShiftUserModal(shiftId, shiftData);
} 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 = `
<label class="checkbox-label">
<input type="checkbox" id="shift-is-public" checked />
<span>Show on public signup page</span>
</label>
`;
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
};