Online Meeting Scheduling Conflict Resolver

Online Meeting Scheduling Conflict Resolver

Attendees

Availability Window

Please fill in the meeting duration and the start/end dates.

`; pdfDownloadContainer.classList.add('hidden'); changeTab(1); // Force back to input tab return; } const startDate = new Date(startDateStr + 'T00:00:00Z'); const endDate = new Date(endDateStr + 'T23:59:59Z'); if (startDate > endDate) { resultsOutput.innerHTML = `

The 'From Date' cannot be after the 'To Date'.

`; pdfDownloadContainer.classList.add('hidden'); changeTab(1); return; } const attendeeElements = document.querySelectorAll('.attendee-row'); if (attendeeElements.length < 2) { resultsOutput.innerHTML = `

Please add at least two attendees to find common slots.

`; pdfDownloadContainer.classList.add('hidden'); changeTab(1); return; } const attendees = Array.from(attendeeElements).map(row => ({ name: row.querySelector('.attendee-name').value || 'Unnamed Attendee', email: row.querySelector('.attendee-email').value, timezoneOffset: parseFloat(row.querySelector('.attendee-timezone').value) })); // 2. Generate potential slots in UTC // Assuming standard business hours 9 AM to 5 PM for this example const businessHoursStart = 9; const businessHoursEnd = 17; const allSlotsUTC = []; let currentDate = new Date(startDate); while (currentDate <= endDate) { for (let hour = 0; hour < 24; hour++) { for (let minute = 0; minute < 60; minute += 15) { // 15-min increments const slotStart = new Date(Date.UTC(currentDate.getUTCFullYear(), currentDate.getUTCMonth(), currentDate.getUTCDate(), hour, minute)); allSlotsUTC.push(slotStart); } } currentDate.setUTCDate(currentDate.getUTCDate() + 1); } // 3. Filter for common slots const commonSlots = allSlotsUTC.filter(utcSlot => { return attendees.every(attendee => { const localHour = utcSlot.getUTCHours() + attendee.timezoneOffset; // Normalize hour to be within 0-23 range const normalizedHour = (localHour + 24) % 24; return normalizedHour >= businessHoursStart && (normalizedHour + duration / 60) <= businessHoursEnd; }); }); // 4. Group consecutive slots const groupedSlots = []; if (commonSlots.length > 0) { let currentGroup = [commonSlots[0]]; for (let i = 1; i < commonSlots.length; i++) { const prevSlot = commonSlots[i-1]; const currentSlot = commonSlots[i]; // Check if slots are consecutive (15 minutes apart) if (currentSlot.getTime() - prevSlot.getTime() === 15 * 60 * 1000) { currentGroup.push(currentSlot); } else { groupedSlots.push(currentGroup); currentGroup = [currentSlot]; } } groupedSlots.push(currentGroup); } // 5. Filter groups by meeting duration const finalSlots = groupedSlots.filter(group => (group.length * 15) >= duration).map(group => group[0]); // 6. Display results displayResults(finalSlots, attendees, meetingTitle, duration); changeTab(2); }; /** * Renders the found slots in the results tab. */ const displayResults = (slots, attendees, meetingTitle, duration) => { if (slots.length === 0) { resultsOutput.innerHTML = `

No common time slots found for all attendees within the specified window and business hours (9 AM - 5 PM local time).

`; pdfDownloadContainer.classList.add('hidden'); return; } const attendeesHeader = attendees.map(a => `${a.name}'s Time`).join(''); const slotRows = slots.map(utcSlot => { const attendeeCells = attendees.map(attendee => { const localTime = new Date(utcSlot.getTime() + attendee.timezoneOffset * 3600 * 1000); return `${formatDate(localTime)}`; }).join(''); return `${attendeeCells}`; }).join(''); resultsOutput.innerHTML = `

${meetingTitle}

Suggested meeting times (${duration} minutes)

${attendeesHeader} ${slotRows}
`; pdfDownloadContainer.classList.remove('hidden'); }; /** * Formats a Date object into a readable string. */ const formatDate = (date) => { return date.toLocaleString('en-US', { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit', hour12: true, timeZone: 'UTC' // Important: treat the date object's values as UTC }); }; /** * Generates and triggers the download of a PDF report. */ const generatePdf = () => { const { jsPDF } = window.jspdf; const doc = new jsPDF(); const meetingTitle = document.getElementById('meeting-title').value || 'Meeting'; const duration = document.getElementById('meeting-duration').value; doc.setFontSize(18); doc.text(`Meeting Schedule: ${meetingTitle}`, 14, 22); doc.setFontSize(11); doc.setTextColor(100); doc.text(`Duration: ${duration} minutes`, 14, 30); const table = resultsOutput.querySelector('table'); if (table) { doc.autoTable({ html: table, startY: 35, theme: 'grid', headStyles: { fillColor: [243, 244, 246], textColor: [55, 65, 81], fontStyle: 'bold' }, styles: { cellPadding: 2.5, fontSize: 9 }, }); } else { doc.text("No results to display.", 14, 40); } doc.save(`${meetingTitle.replace(/\s+/g, '_')}_schedule.pdf`); }; // --- Event Listeners --- addAttendeeBtn.addEventListener('click', () => addAttendee()); downloadPdfBtn.addEventListener('click', generatePdf); findSlotsBtn.addEventListener('click', resolveConflicts); // Also linked via updateNavButtons logic // --- Initial Setup --- const today = new Date().toISOString().split('T')[0]; document.getElementById('start-date').value = today; const nextWeek = new Date(); nextWeek.setDate(nextWeek.getDate() + 7); document.getElementById('end-date').value = nextWeek.toISOString().split('T')[0]; addAttendee('Alice (USA)', 'alice@example.com', -7); // PST/MST addAttendee('Bob (USA)', 'bob@example.com', -5); // EST addAttendee('Charlie (International)', 'charlie@example.com', 1); // CET updateNavButtons(); });
Scroll to Top