Added calendar view to the shifts

This commit is contained in:
2025-07-16 18:05:49 -06:00
parent 966479b80c
commit 34f71c05c2
5 changed files with 575 additions and 19 deletions

View File

@@ -1,6 +1,8 @@
let currentUser = null;
let allShifts = [];
let mySignups = [];
let currentView = 'grid'; // 'grid' or 'calendar'
let currentCalendarDate = new Date(); // For calendar navigation
// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', async () => {
@@ -8,6 +10,7 @@ document.addEventListener('DOMContentLoaded', async () => {
await loadShifts();
await loadMySignups();
setupEventListeners();
initializeViewToggle();
// Add clear filters button handler
const clearBtn = document.getElementById('clear-filters-btn');
@@ -118,6 +121,11 @@ function displayShifts(shifts) {
// Add event listeners after rendering
setupShiftCardListeners();
// Update calendar view if it's currently active
if (currentView === 'calendar') {
renderCalendar();
}
}
function displayMySignups() {
@@ -157,6 +165,11 @@ function displayMySignups() {
// Add event listeners after rendering
setupMySignupsListeners();
// Update calendar view if it's currently active
if (currentView === 'calendar') {
renderCalendar();
}
}
// New function to setup listeners for shift cards
@@ -447,13 +460,13 @@ function generateCalendarDropdown(shift) {
</button>
<div class="calendar-options" style="display: none;">
<a href="${urls.google}" target="_blank" class="calendar-option" data-calendar-type="google">
<img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1zbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEyLjUgMkgxMy41QzE0LjMyODQgMiAxNSAyLjY3MTU3IDE1IDMuNVYxMi41QzE1IDEzLjMyODQgMTQuMzI4NCAxNCAxMy41IDE0SDIuNUMxLjY3MTU3IDE0IDEgMTMuMzI4NCAxIDEyLjVWMy41QzEgMi42NzE1NyAxLjY3MTU3IDIgMi41IDJIMy41IiBzdHJva2U9IiM0Mjg1RjQiIHN0cm9rZS13aWR0aD0iMS41Ii8+CjxwYXRoIGQ9Ik00IDFWM00xMiAxVjMiIHN0cm9rZT0iIzQyODVGNCIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgo8cGF0aCBkPSJNMSA1SDE1IiBzdHJva2U9IiM0Mjg1RjQiIHN0cm9rZS13aWR0aD0iMS41Ii8+Cjwvc3ZnPg==" alt="Google"> Google Calendar
Google Calendar
</a>
<a href="${urls.outlook}" target="_blank" class="calendar-option" data-calendar-type="outlook">
<img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1zbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE0IDJIMlYxNEgxNFYyWiIgZmlsbD0iIzAwNzhENCIvPgo8cGF0aCBkPSJNOCA4VjE0SDE0VjhIOFoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik04IDJWOEgxNFYySDhaIiBmaWxsPSIjMDA3OEQ0Ii8+Cjwvc3ZnPg==" alt="Outlook"> Outlook
Outlook
</a>
<a href="${urls.apple}" download="${urls.icsFilename}" class="calendar-option" data-calendar-type="apple">
<img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1zbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEzIDJIMy41QzIuNjcxNTcgMiAyIDIuNjcxNTcgMiAzLjVWMTIuNUMyIDEzLjMyODQgMi42NzE1NyAxNCAzLjUgMTRIMTIuNUMxMy4zMjg0IDE0IDE0IDEzLjMyODQgMTQgMTIuNVYzQzE0IDIuNDQ3NzIgMTMuNTUyMyAyIDEzIDJaIiBmaWxsPSIjRkY1NzMzIi8+CjxwYXRoIGQ9Ik00IDFWM00xMiAxVjMiIHN0cm9rZT0iI0ZGNTczMyIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiLz4KPHBhdGggZD0iTTIgNUgxNCIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSIxLjUiLz4KPHRleHQgeD0iOCIgeT0iMTEiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSI2IiBmaWxsPSJ3aGl0ZSIgdGV4dC1hbmNob3I9Im1pZGRsZSI+MzE8L3RleHQ+Cjwvc3ZnPg==" alt="Apple"> Apple Calendar
Apple Calendar
</a>
</div>
</div>
@@ -547,6 +560,268 @@ function setupMySignupsListeners() {
});
}
// Calendar View Functions
function initializeViewToggle() {
const gridBtn = document.getElementById('grid-view-btn');
const calendarBtn = document.getElementById('calendar-view-btn');
const prevBtn = document.getElementById('prev-month');
const nextBtn = document.getElementById('next-month');
if (gridBtn && calendarBtn) {
gridBtn.addEventListener('click', () => switchView('grid'));
calendarBtn.addEventListener('click', () => switchView('calendar'));
// Set initial active state
gridBtn.classList.add('active');
}
if (prevBtn && nextBtn) {
prevBtn.addEventListener('click', () => navigateCalendar(-1));
nextBtn.addEventListener('click', () => navigateCalendar(1));
}
}
function switchView(view) {
const gridView = document.getElementById('shifts-grid');
const calendarView = document.getElementById('calendar-view');
const gridBtn = document.getElementById('grid-view-btn');
const calendarBtn = document.getElementById('calendar-view-btn');
currentView = view;
if (view === 'calendar') {
gridView.style.display = 'none';
calendarView.style.display = 'block';
gridBtn.classList.remove('active');
calendarBtn.classList.add('active');
renderCalendar();
} else {
gridView.style.display = 'grid';
calendarView.style.display = 'none';
gridBtn.classList.add('active');
calendarBtn.classList.remove('active');
}
}
function navigateCalendar(direction) {
currentCalendarDate.setMonth(currentCalendarDate.getMonth() + direction);
renderCalendar();
}
function renderCalendar() {
const year = currentCalendarDate.getFullYear();
const month = currentCalendarDate.getMonth();
// Update header
const monthYearHeader = document.getElementById('calendar-month-year');
if (monthYearHeader) {
const monthNames = [
'January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'
];
monthYearHeader.textContent = `${monthNames[month]} ${year}`;
}
// Get calendar grid
const calendarGrid = document.getElementById('calendar-grid');
if (!calendarGrid) return;
calendarGrid.innerHTML = '';
// Add day headers
const dayHeaders = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
dayHeaders.forEach(day => {
const dayHeader = document.createElement('div');
dayHeader.className = 'calendar-day-header';
dayHeader.textContent = day;
calendarGrid.appendChild(dayHeader);
});
// Get first day of month and number of days
const firstDay = new Date(year, month, 1);
const lastDay = new Date(year, month + 1, 0);
const daysInMonth = lastDay.getDate();
const startingDayOfWeek = firstDay.getDay();
// Get previous month's last days
const prevMonth = new Date(year, month, 0);
const daysInPrevMonth = prevMonth.getDate();
// Add previous month's trailing days
for (let i = startingDayOfWeek - 1; i >= 0; i--) {
const dayNumber = daysInPrevMonth - i;
const dayElement = createCalendarDay(dayNumber, true, new Date(year, month - 1, dayNumber));
calendarGrid.appendChild(dayElement);
}
// Add current month's days
for (let day = 1; day <= daysInMonth; day++) {
const currentDate = new Date(year, month, day);
const dayElement = createCalendarDay(day, false, currentDate);
calendarGrid.appendChild(dayElement);
}
// Add next month's leading days to fill the grid
const totalCells = calendarGrid.children.length;
const remainingCells = 42 - totalCells; // 6 rows × 7 days
for (let day = 1; day <= remainingCells; day++) {
const dayElement = createCalendarDay(day, true, new Date(year, month + 1, day));
calendarGrid.appendChild(dayElement);
}
}
function createCalendarDay(dayNumber, isOtherMonth, date) {
const dayElement = document.createElement('div');
dayElement.className = 'calendar-day';
if (isOtherMonth) {
dayElement.classList.add('other-month');
}
// Check if it's today
const today = new Date();
if (date.toDateString() === today.toDateString()) {
dayElement.classList.add('today');
}
// Add day number
const dayNumberElement = document.createElement('div');
dayNumberElement.className = 'calendar-day-number';
dayNumberElement.textContent = dayNumber;
dayElement.appendChild(dayNumberElement);
// Add shifts for this day
const shiftsContainer = document.createElement('div');
shiftsContainer.className = 'calendar-shifts';
const dateString = date.toISOString().split('T')[0];
const dayShifts = allShifts.filter(shift => {
const shiftDate = new Date(shift.Date);
return shiftDate.toISOString().split('T')[0] === dateString;
});
dayShifts.forEach(shift => {
const shiftElement = createCalendarShift(shift);
shiftsContainer.appendChild(shiftElement);
});
dayElement.appendChild(shiftsContainer);
return dayElement;
}
function createCalendarShift(shift) {
const shiftElement = document.createElement('div');
shiftElement.className = 'calendar-shift';
// Determine shift type and color
const isSignedUp = mySignups.some(signup => signup.shift_id === shift.ID);
const isFull = shift['Current Volunteers'] >= shift['Max Volunteers'];
if (isSignedUp) {
shiftElement.classList.add('my-shift');
} else if (isFull) {
shiftElement.classList.add('full-shift');
} else {
shiftElement.classList.add('available-shift');
}
// Set shift text (time and title)
const timeText = `${shift['Start Time']} ${shift.Title}`;
shiftElement.textContent = timeText;
shiftElement.title = `${shift.Title}\n${shift['Start Time']} - ${shift['End Time']}\n${shift.Location || 'TBD'}`;
// Add click handler
shiftElement.addEventListener('click', (e) => {
e.stopPropagation();
showShiftPopup(shift, e.target);
});
return shiftElement;
}
function showShiftPopup(shift, targetElement) {
// Remove any existing popup
const existingPopup = document.querySelector('.shift-popup');
if (existingPopup) {
existingPopup.remove();
}
const popup = document.createElement('div');
popup.className = 'shift-popup';
const isSignedUp = mySignups.some(signup => signup.shift_id === shift.ID);
const isFull = shift['Current Volunteers'] >= shift['Max Volunteers'];
const shiftDate = new Date(shift.Date);
popup.innerHTML = `
<h4>${escapeHtml(shift.Title)}</h4>
<p>📅 ${shiftDate.toLocaleDateString()}</p>
<p>⏰ ${shift['Start Time']} - ${shift['End Time']}</p>
<p>📍 ${escapeHtml(shift.Location || 'TBD')}</p>
<p>👥 ${shift['Current Volunteers']}/${shift['Max Volunteers']} volunteers</p>
${shift.Description ? `<p>${escapeHtml(shift.Description)}</p>` : ''}
<div class="shift-actions">
${isSignedUp
? `<button class="btn btn-danger btn-sm cancel-signup-btn" data-shift-id="${shift.ID}">Cancel Signup</button>`
: isFull
? '<button class="btn btn-secondary btn-sm" disabled>Shift Full</button>'
: `<button class="btn btn-primary btn-sm signup-btn" data-shift-id="${shift.ID}">Sign Up</button>`
}
</div>
`;
// Position popup
document.body.appendChild(popup);
const rect = targetElement.getBoundingClientRect();
const popupRect = popup.getBoundingClientRect();
let left = rect.left + (rect.width / 2) - (popupRect.width / 2);
let top = rect.bottom + 10;
// Adjust if popup goes off screen
if (left < 10) left = 10;
if (left + popupRect.width > window.innerWidth - 10) {
left = window.innerWidth - popupRect.width - 10;
}
if (top + popupRect.height > window.innerHeight - 10) {
top = rect.top - popupRect.height - 10;
}
popup.style.left = `${left}px`;
popup.style.top = `${top}px`;
// Add event listeners for buttons in popup
const signupBtn = popup.querySelector('.signup-btn');
const cancelBtn = popup.querySelector('.cancel-signup-btn');
if (signupBtn) {
signupBtn.addEventListener('click', async () => {
await signupForShift(shift.ID);
popup.remove();
});
}
if (cancelBtn) {
cancelBtn.addEventListener('click', async () => {
await cancelSignup(shift.ID);
popup.remove();
});
}
// Close popup when clicking outside
const closePopup = (e) => {
if (!popup.contains(e.target)) {
popup.remove();
document.removeEventListener('click', closePopup);
}
};
setTimeout(() => {
document.addEventListener('click', closePopup);
}, 100);
}
// Keep the document click handler to close dropdowns when clicking outside
document.addEventListener('click', function() {
document.querySelectorAll('.calendar-options').forEach(opt => {