Setup Users & Their Availability
Manage Users
Existing Users:
Availability for
Standard Weekly Working Hours (in User's Local Time)
| Day | Available From | Available To | Not Working |
|---|
Specific Unavailability Events (in User's Local Time)
Logged Unavailability:
Check Team Availability
Select Users to Check:
Check Results:
No users configured in Setup tab.
Reason: ${reason}` : ''}`; resultsListEl.appendChild(li); }); } function sac_convertInstantToUserLocalParts(instant, userTimeZone) { try { const formatter = new Intl.DateTimeFormat('en-CA', { // YYYY-MM-DD timeZone: userTimeZone, year: 'numeric', month: '2-digit', day: '2-digit' }); const timeFormatter = new Intl.DateTimeFormat('en-GB', { // HH:MM timeZone: userTimeZone, hour: '2-digit', minute: '2-digit', hour12: false }); return { dateStr: formatter.format(instant), timeStr: timeFormatter.format(instant), dayKey: sac_daysOfWeekKeys[instant.toLocaleDateString('en-US', {timeZone: userTimeZone, weekday: 'long'}).toLowerCase().substring(0,3) === 'sun' ? 6 : (new Date(instant.toLocaleString('en-US', {timeZone: userTimeZone})).getDay() -1) ] // This is complex }; } catch (e) { console.error(`Error in sac_convertInstantToUserLocalParts for TZ ${userTimeZone}:`, e); return { dateStr: "Err", timeStr: "Err", dayKey: "Err"}; } } function sac_checkSinglePoint(user, dateTimeInstant) { const userLocalParts = sac_convertInstantToUserLocalParts(dateTimeInstant, user.timeZone); if(userLocalParts.dateStr === "Err") return { status: "Unavailable", reason: "Timezone conversion error for user."}; const userDateStr = userLocalParts.dateStr; // YYYY-MM-DD const userTimeStr = userLocalParts.timeStr; // HH:MM // 1. Check specific unavailability events const specificBlock = sac_specificUnavailability.find(event => event.userId === user.id && event.date === userDateStr && userTimeStr >= event.startTime && userTimeStr < event.endTime ); if (specificBlock) { return { status: "Unavailable", reason: specificBlock.reason }; } // 2. Check standard working hours // Get day of week for userDateStr in user's timezone // This is tricky because userDateStr IS ALREADY IN user's timezone. // So new Date(userDateStr) will be interpreted by browser in its *own* TZ. // We need day of week for userDateStr as per user's calendar. const tempDateForDay = new Date(userDateStr + "T12:00:00"); // Midday to avoid DST crossover issues for *just getting day* const dayOfWeekJs = tempDateForDay.getDay(); // 0 for Sun, 1 for Mon... const dayKey = sac_daysOfWeekKeys[dayOfWeekJs === 0 ? 6 : dayOfWeekJs - 1]; // Our Mon-Sun keys const stdHours = sac_standardAvailability[user.id] ? sac_standardAvailability[user.id][dayKey] : null; if (stdHours) { if (stdHours.notWorking || !stdHours.start || !stdHours.end) { return { status: "Unavailable", reason: "Outside working hours (Day off)" }; } if (userTimeStr < stdHours.start || userTimeStr >= stdHours.end) { return { status: "Unavailable", reason: "Outside standard working hours" }; } } else { // No standard hours defined for this day, assume unavailable unless it's a general "always available" policy return { status: "Unavailable", reason: "Working hours not defined for this day" }; } return { status: "Available", reason: "" }; } // --- PDF Download --- document.getElementById('sac-downloadPdfBtn').onclick = async () => { const checkerTZ = document.getElementById('sac-checkerTimeZone').value; const checkDateStr = document.getElementById('sac-checkDate').value; const checkTimeStr = document.getElementById('sac-checkTime').value; const checkDurationMins = parseInt(document.getElementById('sac-checkDuration').value) || 0; const resultsContent = Array.from(document.querySelectorAll('#sac-availabilityResultsList li')); if (!checkerTZ || !checkDateStr || !checkTimeStr || resultsContent.length === 0 || resultsContent[0].textContent.includes("Perform a check")) { alert("Please perform an availability check first before downloading."); return; } const { jsPDF } = window.jspdf; const pdf = new jsPDF('p', 'mm', 'a4'); let currentY = 15; const margin = 15; const pageWidth = pdf.internal.pageSize.getWidth(); pdf.setFontSize(18); pdf.setTextColor('#007BFF'); pdf.text("Team Availability Check Report", pageWidth / 2, currentY, { align: 'center' }); currentY += 8; pdf.setFontSize(10); pdf.setTextColor('#333'); pdf.text(`Check Performed: ${new Date().toLocaleString()}`, pageWidth / 2, currentY, { align: 'center' }); currentY += 8; pdf.text(`Proposed Time (in ${checkerTZ}): ${new Date(checkDateStr+"T"+checkTimeStr).toLocaleString(undefined, {dateStyle:'medium', timeStyle:'short'})} ${checkDurationMins > 0 ? `for ${checkDurationMins} min` : ''}`, margin, currentY); currentY += 10; pdf.setFontSize(12); pdf.setTextColor('#007BFF'); pdf.text("Results:", margin, currentY); currentY += 6; const tableBody = resultsContent.map(li => { const parts = li.textContent.split(':'); const userName = parts[0].trim(); const statusAndDetails = parts.slice(1).join(':').trim(); const statusMatch = statusAndDetails.match(/^(Available|Unavailable|Partially Available)/); const status = statusMatch ? statusMatch[0] : 'Unknown'; const details = statusAndDetails.replace(status, '').trim(); return [userName, status, details]; }); pdf.autoTable({ head: [["User", "Status", "Details (Proposed time in user's local TZ & Reason)"]], body: tableBody, startY: currentY, theme: 'grid', headStyles: { fillColor: '#007BFF', textColor: '#FFFFFF' }, styles: { fontSize: 9, cellPadding: 2 }, columnStyles: { 0: { cellWidth: 40 }, 1: { cellWidth: 30 }, 2: { cellWidth: 'auto'} }, didParseCell: function (data) { if (data.section === 'body') { if (data.cell.raw === 'Available') data.cell.styles.textColor = '#2ECC71'; if (data.cell.raw === 'Unavailable') data.cell.styles.textColor = '#E74C3C'; if (data.cell.raw === 'Partially Available') data.cell.styles.textColor = '#F39C12'; } } }); pdf.save(`Availability_Check_Report_${checkDateStr}.pdf`); }; // --- Initial Load --- document.addEventListener('DOMContentLoaded', () => { sac_openTab(null, sac_tabs[0]); const firstTabButton = document.querySelector(`.sac-tab-button[onclick*="${sac_tabs[0]}"]`); if (firstTabButton) firstTabButton.classList.add('active'); sac_updateNavButtons(); });
