Fixed bug for displaying sign ups for shifts
This commit is contained in:
@@ -109,6 +109,10 @@
|
||||
|
||||
.shift-actions {
|
||||
margin-top: 15px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.no-shifts {
|
||||
@@ -118,14 +122,67 @@
|
||||
grid-column: 1 / -1; /* Span all columns */
|
||||
}
|
||||
|
||||
/* Tablet: 2 columns */
|
||||
@media (max-width: 1024px) and (min-width: 769px) {
|
||||
.shifts-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
/* Calendar dropdown styles */
|
||||
.calendar-dropdown {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Mobile: 1 column */
|
||||
.calendar-toggle {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.calendar-options {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
min-width: 180px;
|
||||
background: white;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
z-index: 1000;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.calendar-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 15px;
|
||||
text-decoration: none;
|
||||
color: var(--dark-color);
|
||||
transition: var(--transition);
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.calendar-option:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.calendar-option:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.calendar-option img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
/* Ensure dropdowns appear above other elements */
|
||||
.shift-card {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Update signup actions for My Shifts section */
|
||||
.signup-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Mobile adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.shifts-container {
|
||||
padding: 15px;
|
||||
@@ -142,8 +199,30 @@
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.shift-card {
|
||||
padding: 15px;
|
||||
.signup-actions {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.shift-actions {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.shift-actions .btn,
|
||||
.calendar-dropdown {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.calendar-toggle {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.calendar-options {
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.my-signups {
|
||||
@@ -158,4 +237,13 @@
|
||||
.filter-group {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
/* Prevent dropdown from being cut off */
|
||||
.shifts-grid {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.shift-card {
|
||||
overflow: visible;
|
||||
}
|
||||
@@ -105,7 +105,8 @@ function displayShifts(shifts) {
|
||||
${shift.Description ? `<div class="shift-description">${escapeHtml(shift.Description)}</div>` : ''}
|
||||
<div class="shift-actions">
|
||||
${isSignedUp
|
||||
? `<button class="btn btn-danger btn-sm cancel-signup-btn" data-shift-id="${shift.ID}">Cancel Signup</button>`
|
||||
? `<button class="btn btn-danger btn-sm cancel-signup-btn" data-shift-id="${shift.ID}">Cancel Signup</button>
|
||||
${generateCalendarDropdown(shift)}`
|
||||
: 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>`
|
||||
@@ -141,7 +142,10 @@ function displayMySignups() {
|
||||
<h4>${escapeHtml(signup.shift.Title)}</h4>
|
||||
<p>📅 ${shiftDate.toLocaleDateString()} ⏰ ${signup.shift['Start Time']} - ${signup.shift['End Time']}</p>
|
||||
</div>
|
||||
<button class="btn btn-danger btn-sm cancel-signup-btn" data-shift-id="${signup.shift.ID}">Cancel</button>
|
||||
<div class="signup-actions">
|
||||
${generateCalendarDropdown(signup.shift)}
|
||||
<button class="btn btn-danger btn-sm cancel-signup-btn" data-shift-id="${signup.shift.ID}">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
@@ -159,15 +163,40 @@ function setupShiftCardListeners() {
|
||||
const newGrid = grid.cloneNode(true);
|
||||
grid.parentNode.replaceChild(newGrid, grid);
|
||||
|
||||
// Add click listener for signup buttons
|
||||
// Add click listener for all buttons
|
||||
newGrid.addEventListener('click', async (e) => {
|
||||
// Handle signup buttons
|
||||
if (e.target.classList.contains('signup-btn')) {
|
||||
const shiftId = e.target.getAttribute('data-shift-id');
|
||||
await signupForShift(shiftId);
|
||||
} else if (e.target.classList.contains('cancel-signup-btn')) {
|
||||
}
|
||||
// Handle cancel buttons
|
||||
else if (e.target.classList.contains('cancel-signup-btn')) {
|
||||
const shiftId = e.target.getAttribute('data-shift-id');
|
||||
await cancelSignup(shiftId);
|
||||
}
|
||||
// Handle calendar toggle buttons
|
||||
else if (e.target.classList.contains('calendar-toggle')) {
|
||||
e.stopPropagation();
|
||||
const dropdown = e.target.closest('.calendar-dropdown');
|
||||
const options = dropdown.querySelector('.calendar-options');
|
||||
const isOpen = options.style.display !== 'none';
|
||||
|
||||
// Close all other dropdowns
|
||||
document.querySelectorAll('.calendar-options').forEach(opt => {
|
||||
opt.style.display = 'none';
|
||||
});
|
||||
|
||||
// Toggle this dropdown
|
||||
options.style.display = isOpen ? 'none' : 'block';
|
||||
}
|
||||
// Handle calendar option clicks
|
||||
else if (e.target.closest('.calendar-option')) {
|
||||
e.stopPropagation();
|
||||
const dropdown = e.target.closest('.calendar-dropdown');
|
||||
const options = dropdown.querySelector('.calendar-options');
|
||||
options.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -180,12 +209,35 @@ function setupMySignupsListeners() {
|
||||
const newList = list.cloneNode(true);
|
||||
list.parentNode.replaceChild(newList, list);
|
||||
|
||||
// Add click listener for cancel buttons
|
||||
// Add click listener for all interactions
|
||||
newList.addEventListener('click', async (e) => {
|
||||
// Handle cancel buttons
|
||||
if (e.target.classList.contains('cancel-signup-btn')) {
|
||||
const shiftId = e.target.getAttribute('data-shift-id');
|
||||
await cancelSignup(shiftId);
|
||||
}
|
||||
// Handle calendar toggle buttons
|
||||
else if (e.target.classList.contains('calendar-toggle')) {
|
||||
e.stopPropagation();
|
||||
const dropdown = e.target.closest('.calendar-dropdown');
|
||||
const options = dropdown.querySelector('.calendar-options');
|
||||
const isOpen = options.style.display !== 'none';
|
||||
|
||||
// Close all other dropdowns
|
||||
document.querySelectorAll('.calendar-options').forEach(opt => {
|
||||
opt.style.display = 'none';
|
||||
});
|
||||
|
||||
// Toggle this dropdown
|
||||
options.style.display = isOpen ? 'none' : 'block';
|
||||
}
|
||||
// Handle calendar option clicks
|
||||
else if (e.target.closest('.calendar-option')) {
|
||||
e.stopPropagation();
|
||||
const dropdown = e.target.closest('.calendar-dropdown');
|
||||
const options = dropdown.querySelector('.calendar-options');
|
||||
options.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -290,4 +342,209 @@ function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = String(text);
|
||||
return div.innerHTML;
|
||||
}
|
||||
}
|
||||
|
||||
// Add these calendar URL generation functions after the existing functions
|
||||
function generateCalendarUrls(shift) {
|
||||
const shiftDate = new Date(shift.Date);
|
||||
|
||||
// Parse start and end times
|
||||
const [startHour, startMinute] = shift['Start Time'].split(':').map(n => parseInt(n));
|
||||
const [endHour, endMinute] = shift['End Time'].split(':').map(n => parseInt(n));
|
||||
|
||||
// Create start and end datetime objects
|
||||
const startDate = new Date(shiftDate);
|
||||
startDate.setHours(startHour, startMinute, 0, 0);
|
||||
|
||||
const endDate = new Date(shiftDate);
|
||||
endDate.setHours(endHour, endMinute, 0, 0);
|
||||
|
||||
// Format dates for different calendar formats
|
||||
const formatGoogleDate = (date) => {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
return `${year}${month}${day}T${hours}${minutes}00`;
|
||||
};
|
||||
|
||||
const formatISODate = (date) => {
|
||||
return date.toISOString().replace(/[-:]/g, '').replace(/\.\d{3}/, '');
|
||||
};
|
||||
|
||||
// Event details
|
||||
const title = shift.Title;
|
||||
const description = shift.Description || 'Volunteer shift';
|
||||
const location = shift.Location || '';
|
||||
|
||||
// Google Calendar URL
|
||||
const googleStartStr = formatGoogleDate(startDate);
|
||||
const googleEndStr = formatGoogleDate(endDate);
|
||||
const googleParams = new URLSearchParams({
|
||||
action: 'TEMPLATE',
|
||||
text: title,
|
||||
dates: `${googleStartStr}/${googleEndStr}`,
|
||||
details: description,
|
||||
location: location
|
||||
});
|
||||
const googleUrl = `https://calendar.google.com/calendar/render?${googleParams.toString()}`;
|
||||
|
||||
// Outlook Web Calendar URL
|
||||
const outlookStartStr = startDate.toISOString();
|
||||
const outlookEndStr = endDate.toISOString();
|
||||
const outlookParams = new URLSearchParams({
|
||||
path: '/calendar/action/compose',
|
||||
rru: 'addevent',
|
||||
subject: title,
|
||||
startdt: outlookStartStr,
|
||||
enddt: outlookEndStr,
|
||||
body: description,
|
||||
location: location
|
||||
});
|
||||
const outlookUrl = `https://outlook.live.com/calendar/0/deeplink/compose?${outlookParams.toString()}`;
|
||||
|
||||
// Apple Calendar (.ics file) - we'll generate this dynamically
|
||||
const icsContent = [
|
||||
'BEGIN:VCALENDAR',
|
||||
'VERSION:2.0',
|
||||
'PRODID:-//BNKops//Volunteer Shifts//EN',
|
||||
'BEGIN:VEVENT',
|
||||
`UID:${shift.ID}-${Date.now()}@bnkops.com`,
|
||||
`DTSTART:${formatISODate(startDate)}`,
|
||||
`DTEND:${formatISODate(endDate)}`,
|
||||
`SUMMARY:${title}`,
|
||||
`DESCRIPTION:${description.replace(/\n/g, '\\n')}`,
|
||||
`LOCATION:${location}`,
|
||||
'STATUS:CONFIRMED',
|
||||
'END:VEVENT',
|
||||
'END:VCALENDAR'
|
||||
].join('\r\n');
|
||||
|
||||
// Create a data URL for the .ics file
|
||||
const icsDataUrl = 'data:text/calendar;charset=utf-8,' + encodeURIComponent(icsContent);
|
||||
|
||||
return {
|
||||
google: googleUrl,
|
||||
outlook: outlookUrl,
|
||||
apple: icsDataUrl,
|
||||
icsFilename: `${title.replace(/[^a-z0-9]/gi, '_')}_${shift.ID}.ics`
|
||||
};
|
||||
}
|
||||
|
||||
// Update calendar dropdown HTML generator (remove onclick handlers)
|
||||
function generateCalendarDropdown(shift) {
|
||||
const urls = generateCalendarUrls(shift);
|
||||
return `
|
||||
<div class="calendar-dropdown">
|
||||
<button class="btn btn-secondary btn-sm calendar-toggle" data-shift-id="${shift.ID}">
|
||||
📅 Add to Calendar ▼
|
||||
</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
|
||||
</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
|
||||
</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
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Update setupShiftCardListeners to handle calendar dropdowns
|
||||
function setupShiftCardListeners() {
|
||||
const grid = document.getElementById('shifts-grid');
|
||||
if (!grid) return;
|
||||
|
||||
// Remove any existing listeners by cloning
|
||||
const newGrid = grid.cloneNode(true);
|
||||
grid.parentNode.replaceChild(newGrid, grid);
|
||||
|
||||
// Add click listener for all buttons
|
||||
newGrid.addEventListener('click', async (e) => {
|
||||
// Handle signup buttons
|
||||
if (e.target.classList.contains('signup-btn')) {
|
||||
const shiftId = e.target.getAttribute('data-shift-id');
|
||||
await signupForShift(shiftId);
|
||||
}
|
||||
// Handle cancel buttons
|
||||
else if (e.target.classList.contains('cancel-signup-btn')) {
|
||||
const shiftId = e.target.getAttribute('data-shift-id');
|
||||
await cancelSignup(shiftId);
|
||||
}
|
||||
// Handle calendar toggle buttons
|
||||
else if (e.target.classList.contains('calendar-toggle')) {
|
||||
e.stopPropagation();
|
||||
const dropdown = e.target.closest('.calendar-dropdown');
|
||||
const options = dropdown.querySelector('.calendar-options');
|
||||
const isOpen = options.style.display !== 'none';
|
||||
|
||||
// Close all other dropdowns
|
||||
document.querySelectorAll('.calendar-options').forEach(opt => {
|
||||
opt.style.display = 'none';
|
||||
});
|
||||
|
||||
// Toggle this dropdown
|
||||
options.style.display = isOpen ? 'none' : 'block';
|
||||
}
|
||||
// Handle calendar option clicks
|
||||
else if (e.target.closest('.calendar-option')) {
|
||||
e.stopPropagation();
|
||||
const dropdown = e.target.closest('.calendar-dropdown');
|
||||
const options = dropdown.querySelector('.calendar-options');
|
||||
options.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update setupMySignupsListeners similarly
|
||||
function setupMySignupsListeners() {
|
||||
const list = document.getElementById('my-signups-list');
|
||||
if (!list) return;
|
||||
|
||||
// Remove any existing listeners by cloning
|
||||
const newList = list.cloneNode(true);
|
||||
list.parentNode.replaceChild(newList, list);
|
||||
|
||||
// Add click listener for all interactions
|
||||
newList.addEventListener('click', async (e) => {
|
||||
// Handle cancel buttons
|
||||
if (e.target.classList.contains('cancel-signup-btn')) {
|
||||
const shiftId = e.target.getAttribute('data-shift-id');
|
||||
await cancelSignup(shiftId);
|
||||
}
|
||||
// Handle calendar toggle buttons
|
||||
else if (e.target.classList.contains('calendar-toggle')) {
|
||||
e.stopPropagation();
|
||||
const dropdown = e.target.closest('.calendar-dropdown');
|
||||
const options = dropdown.querySelector('.calendar-options');
|
||||
const isOpen = options.style.display !== 'none';
|
||||
|
||||
// Close all other dropdowns
|
||||
document.querySelectorAll('.calendar-options').forEach(opt => {
|
||||
opt.style.display = 'none';
|
||||
});
|
||||
|
||||
// Toggle this dropdown
|
||||
options.style.display = isOpen ? 'none' : 'block';
|
||||
}
|
||||
// Handle calendar option clicks
|
||||
else if (e.target.closest('.calendar-option')) {
|
||||
e.stopPropagation();
|
||||
const dropdown = e.target.closest('.calendar-dropdown');
|
||||
const options = dropdown.querySelector('.calendar-options');
|
||||
options.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Keep the document click handler to close dropdowns when clicking outside
|
||||
document.addEventListener('click', function() {
|
||||
document.querySelectorAll('.calendar-options').forEach(opt => {
|
||||
opt.style.display = 'none';
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user