// Public shifts JavaScript
let allShifts = [];
let filteredShifts = [];
// Utility function to create a local date from YYYY-MM-DD string
function createLocalDate(dateString) {
if (!dateString) return new Date();
const parts = dateString.split('-');
if (parts.length !== 3) return new Date(dateString);
return new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2]));
}
// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
loadPublicShifts();
setupEventListeners();
// Check if there's a specific shift in the URL hash
const hash = window.location.hash;
if (hash.startsWith('#shift-')) {
const shiftId = hash.replace('#shift-', '');
setTimeout(() => highlightShift(shiftId), 1000); // Wait for shifts to load
}
});
// Load public shifts from API
async function loadPublicShifts() {
const loadingEl = document.getElementById('shifts-loading');
const gridEl = document.getElementById('shifts-grid');
const noShiftsEl = document.getElementById('no-shifts');
try {
showLoadingState(true);
const response = await fetch('/api/public/shifts');
const data = await response.json();
if (data.success) {
allShifts = data.shifts || [];
filteredShifts = [...allShifts];
displayShifts(filteredShifts);
} else {
throw new Error(data.error || 'Failed to load shifts');
}
} catch (error) {
console.error('Error loading shifts:', error);
showStatus('Failed to load volunteer opportunities. Please try again later.', 'error');
if (noShiftsEl) {
noShiftsEl.innerHTML = `
Unable to load opportunities
There was a problem loading volunteer opportunities. Please refresh the page or try again later.
`;
noShiftsEl.style.display = 'block';
}
} finally {
showLoadingState(false);
}
}
// Show/hide loading state
function showLoadingState(show) {
const loadingEl = document.getElementById('shifts-loading');
const gridEl = document.getElementById('shifts-grid');
const noShiftsEl = document.getElementById('no-shifts');
if (loadingEl) loadingEl.style.display = show ? 'flex' : 'none';
if (gridEl) gridEl.style.display = show ? 'none' : 'grid';
if (noShiftsEl && show) noShiftsEl.style.display = 'none';
}
// Utility function to escape HTML
function escapeHtml(text) {
// Handle null/undefined values
if (text === null || text === undefined) {
return '';
}
// Convert to string if not already
text = String(text);
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return text.replace(/[&<>"']/g, m => map[m]);
}
// Display shifts in the grid
function displayShifts(shifts) {
const grid = document.getElementById('shifts-grid');
if (!shifts || shifts.length === 0) {
grid.innerHTML = '
No volunteer opportunities available at this time.
';
return;
}
grid.innerHTML = shifts.map(shift => {
// NocoDB may use 'Id', 'ID', or 'id' - check all variations
const shiftId = shift.id || shift.Id || shift.ID || shift.ncRecordId;
// Debug log to see what fields are available
console.log('Shift object keys:', Object.keys(shift));
console.log('Shift ID found:', shiftId);
const shiftDate = createLocalDate(shift.Date);
const currentVolunteers = shift['Current Volunteers'] || 0;
const maxVolunteers = shift['Max Volunteers'] || 0;
const spotsLeft = Math.max(0, maxVolunteers - currentVolunteers);
const isFull = spotsLeft === 0;
// Use empty string as fallback for any missing fields
const title = shift.Title || 'Untitled Shift';
const location = shift.Location || 'Location TBD';
const startTime = shift['Start Time'] || '';
const endTime = shift['End Time'] || '';
const description = shift.Description || '';
return `