`;
clocksContainer.appendChild(card);
});
updateAllClocks();
updateDownloadButtonState();
}
function updateAllClocks() {
const now = new Date();
const meetingDateTime = getMeetingDateTime();
document.querySelectorAll('[data-role="current-time"]').forEach(el => {
const timezone = el.dataset.timezone;
if (!timezone) return;
el.textContent = now.toLocaleTimeString('en-US', { timeZone: timezone, hour12: false });
});
document.querySelectorAll('[data-role="current-date"]').forEach(el => {
const timezone = el.dataset.timezone;
if (!timezone) return;
el.textContent = now.toLocaleDateString('en-US', { timeZone: timezone, weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });
});
if (meetingDateTime) {
updateMeetingTimes(meetingDateTime);
}
}
function getMeetingDateTime() {
if (meetingDateInput.value && meetingTimeInput.value) {
return new Date(`${meetingDateInput.value}T${meetingTimeInput.value}`);
}
return null;
}
function updateMeetingTimes(dateTime) {
if (!dateTime || isNaN(dateTime)) return;
document.querySelectorAll('[data-role="meeting-time"]').forEach(el => {
const timezone = el.dataset.timezone;
if (!timezone) return;
el.textContent = dateTime.toLocaleTimeString('en-US', { timeZone: timezone, hour: '2-digit', minute: '2-digit', hour12: true });
});
document.querySelectorAll('[data-role="meeting-date"]').forEach(el => {
const timezone = el.dataset.timezone;
if (!timezone) return;
el.textContent = dateTime.toLocaleDateString('en-US', { timeZone: timezone, weekday: 'short', month: 'short', day: 'numeric' });
});
}
// --- TIME CONVERTER LOGIC ---
function runConversion() {
const fromTz = fromCityInput.dataset.timezone;
const toTz = toCityInput.dataset.timezone;
const fromDate = fromDateInput.value;
const fromTime = fromTimeInput.value;
if (!fromTz || !toTz || !fromDate || !fromTime) {
conversionResult.classList.add('hidden');
return;
}
try {
const fromDateTimeStr = `${fromDate}T${fromTime}`;
const localDate = new Date(fromDateTimeStr);
const targetDate = new Date(localDate.toLocaleString("en-US", {timeZone: toTz}));
const sourceDate = new Date(localDate.toLocaleString("en-US", {timeZone: fromTz}));
const timeDiff = (targetDate.getTime() - sourceDate.getTime());
const convertedDate = new Date(localDate.getTime() + timeDiff);
resultTime.textContent = convertedDate.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true });
resultDate.textContent = convertedDate.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });
conversionResult.classList.remove('hidden');
} catch (e) {
console.error("Error during time conversion:", e);
conversionResult.classList.add('hidden');
}
}
// --- PDF GENERATION (NEW VISUAL REPORT) ---
async function generatePdf() {
const { jsPDF } = window.jspdf;
const doc = new jsPDF({
orientation: 'portrait',
unit: 'pt',
format: 'a4'
});
const meetingDateTime = getMeetingDateTime();
if (!meetingDateTime || selectedLocations.length === 0) {
// In a real app, you'd show a modal, not an alert. For this context, console log is fine.
console.error("Please select locations and a meeting time first.");
return;
}
const pageHeight = doc.internal.pageSize.height;
const pageWidth = doc.internal.pageSize.width;
const margin = 40;
let yPos = margin + 20;
// --- PDF Header ---
doc.setFont("helvetica", "bold");
doc.setFontSize(24);
doc.setTextColor("#2d3748"); // Dark Gray
doc.text("Optimized Meeting Schedule", pageWidth / 2, yPos, { align: "center" });
yPos += 25;
doc.setFont("helvetica", "normal");
doc.setFontSize(12);
doc.setTextColor("#718096"); // Lighter Gray
const meetingDateStr = meetingDateTime.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });
const meetingTimeStr = meetingDateTime.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true });
doc.text(`Primary Meeting Time: ${meetingDateStr} at ${meetingTimeStr} (Your Local Time)`, pageWidth / 2, yPos, { align: "center" });
yPos += 45;
// --- PDF Main Content ---
selectedLocations.forEach((loc, index) => {
if (yPos > pageHeight - 100) { // Page break logic
doc.addPage();
yPos = margin;
}
const localMeetingTime = meetingDateTime.toLocaleTimeString('en-US', { timeZone: loc.timezone, hour: '2-digit', minute: '2-digit', hour12: true });
const localMeetingDate = meetingDateTime.toLocaleDateString('en-US', { timeZone: loc.timezone, weekday: 'short', month: 'short', day: 'numeric' });
const localHour = parseInt(meetingDateTime.toLocaleTimeString('en-US', { timeZone: loc.timezone, hour: '2-digit', hour12: false }), 10);
// --- Time of Day Visualization ---
let timeOfDayColor, timeOfDayText;
if (localHour >= 6 && localHour < 12) {
timeOfDayColor = "#FBBF24"; // Amber 400
timeOfDayText = "Morning";
} else if (localHour >= 12 && localHour < 18) {
timeOfDayColor = "#3B82F6"; // Blue 500
timeOfDayText = "Afternoon";
} else if (localHour >= 18 && localHour < 22) {
timeOfDayColor = "#6366F1"; // Indigo 500
timeOfDayText = "Evening";
} else {
timeOfDayColor = "#4B5563"; // Gray 600
timeOfDayText = "Night";
}
// --- Draw Entry ---
doc.setFillColor("#F9FAFB"); // Gray 50
doc.setDrawColor("#E5E7EB"); // Gray 200
doc.roundedRect(margin, yPos, pageWidth - (margin * 2), 60, 5, 5, 'FD');
// Location Info
doc.setFont("helvetica", "bold");
doc.setFontSize(14);
doc.setTextColor("#1F2937"); // Gray 800
doc.text(`${loc.city}, ${loc.country}`, margin + 20, yPos + 25);
// Meeting Time Info
doc.setFont("helvetica", "normal");
doc.setFontSize(12);
doc.setTextColor("#4B5563"); // Gray 600
doc.text(`${localMeetingTime} - ${localMeetingDate}`, margin + 20, yPos + 45);
// Time of Day Visualization on the right
doc.setFillColor(timeOfDayColor);
doc.roundedRect(pageWidth - margin - 100, yPos + 15, 80, 30, 15, 15, 'F');
doc.setFont("helvetica", "bold");
doc.setFontSize(10);
doc.setTextColor("#FFFFFF"); // White text on the color block
doc.text(timeOfDayText, pageWidth - margin - 60, yPos + 34, { align: 'center' });
yPos += 75; // Increment y position for the next entry
});
// --- PDF Footer ---
const footerY = pageHeight - 20;
doc.setFontSize(8);
doc.setTextColor("#9CA3AF"); // Gray 400
doc.text(`Report generated on ${new Date().toLocaleDateString()}`, margin, footerY);
doc.text("Online Time Optimization Tool", pageWidth - margin, footerY, { align: "right" });
// --- Save the PDF ---
const fileName = `Optimized_Schedule_${meetingDateTime.toISOString().split('T')[0]}.pdf`;
doc.save(fileName);
}
// --- TAB NAVIGATION LOGIC ---
function switchTab(tabIndex) {
currentTabIndex = tabIndex;
tabsContainer.querySelectorAll('button').forEach((btn, index) => {
btn.classList.toggle('tab-active', index === tabIndex);
btn.classList.toggle('tab-inactive', index !== tabIndex);
});
tabContents.forEach((content, index) => {
content.classList.toggle('hidden', index !== tabIndex);
});
updateButtonStates();
}
function updateButtonStates() {
prevBtn.disabled = currentTabIndex === 0;
nextBtn.disabled = currentTabIndex === tabs.length - 1;
prevBtn.classList.toggle('opacity-50', prevBtn.disabled);
nextBtn.classList.toggle('opacity-50', nextBtn.disabled);
}
function updateDownloadButtonState() {
downloadPdfBtn.disabled = selectedLocations.length === 0;
}
// --- EVENT LISTENERS ---
meetingDateInput.addEventListener('change', () => updateAllClocks());
meetingTimeInput.addEventListener('change', () => updateAllClocks());
clocksContainer.addEventListener('click', function(e) {
if (e.target && e.target.classList.contains('remove-btn')) {
const timezone = e.target.dataset.timezone;
removeLocation(timezone);
}
});
downloadPdfBtn.addEventListener('click', generatePdf);
tabsContainer.querySelectorAll('button').forEach((btn, index) => {
btn.addEventListener('click', () => switchTab(index));
});
prevBtn.addEventListener('click', () => {
if (currentTabIndex > 0) switchTab(currentTabIndex - 1);
});
nextBtn.addEventListener('click', () => {
if (currentTabIndex < tabs.length - 1) switchTab(currentTabIndex + 1);
});
[fromCityInput, toCityInput, fromDateInput, fromTimeInput].forEach(el => {
el.addEventListener('change', runConversion);
el.addEventListener('input', runConversion);
});
document.addEventListener('click', function (e) {
if (!e.target.matches('#cityInput, #fromCity, #toCity')) {
closeAllLists();
}
});
// --- RUN INITIALIZATION ---
if (cityInput && meetingDateInput && meetingTimeInput && clocksContainer) {
initialize();
} else {
console.error("A critical element for the Time Optimization Tool was not found.");
}
});
