No data logged yet. Add some entries to see your trends!
';
return;
}
chartCanvas.style.display = 'block';
const labels = usageData.map(d => new Date(d.date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }));
const totalUsage = usageData.map(d => d.phone + d.laptop + d.tv);
const sleepQuality = usageData.map(d => d.sleepQuality);
if (usageChart) {
usageChart.destroy();
}
const ctx = chartCanvas.getContext('2d');
usageChart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [
{
label: 'Total Device Usage (mins)',
data: totalUsage,
backgroundColor: 'rgba(59, 130, 246, 0.5)',
borderColor: 'rgba(59, 130, 246, 1)',
borderWidth: 1,
yAxisID: 'y'
},
{
label: 'Sleep Quality (1-5)',
data: sleepQuality,
type: 'line',
borderColor: 'rgba(22, 163, 74, 1)',
backgroundColor: 'rgba(22, 163, 74, 0.2)',
tension: 0.3,
fill: true,
yAxisID: 'y1'
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
type: 'linear',
display: true,
position: 'left',
title: { display: true, text: 'Usage (minutes)' }
},
y1: {
type: 'linear',
display: true,
position: 'right',
min: 0,
max: 6,
title: { display: true, text: 'Sleep Quality' },
grid: { drawOnChartArea: false }
}
}
}
});
renderSummaryStats();
};
const renderSummaryStats = () => {
const totalEntries = usageData.length;
const avgUsage = usageData.reduce((acc, d) => acc + d.phone + d.laptop + d.tv, 0) / totalEntries;
const avgSleep = usageData.reduce((acc, d) => acc + d.sleepQuality, 0) / totalEntries;
summaryStatsContainer.innerHTML = `
Avg. Daily Usage
${avgUsage.toFixed(0)} mins
Avg. Sleep Quality
${avgSleep.toFixed(1)} / 5
Total Nights Logged
${totalEntries}
`;
};
// --- PDF Generation ---
const downloadPDF = () => {
if (usageData.length === 0) {
alert('No data to generate a report.');
return;
}
const dashboardContent = document.getElementById('dashboard-content');
window.html2canvas(dashboardContent, { scale: 2, backgroundColor: '#ffffff' }).then(canvas => {
const imgData = canvas.toDataURL('image/png');
const { jsPDF } = window.jspdf;
const pdf = new jsPDF({ orientation: 'portrait', unit: 'pt', format: 'a4' });
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfMargin = 40;
const contentWidth = pdfWidth - (pdfMargin * 2);
const imgHeight = canvas.height * contentWidth / canvas.width;
// Header
pdf.setFontSize(22).setFont('helvetica', 'bold');
pdf.text('Sleep & Screen Time Report', pdfWidth / 2, pdfMargin, { align: 'center' });
pdf.setFontSize(12).setFont('helvetica', 'normal');
pdf.text(`Report Generated: ${new Date().toLocaleDateString('en-US')}`, pdfWidth / 2, pdfMargin + 20, { align: 'center' });
// Add chart image
pdf.addImage(imgData, 'PNG', pdfMargin, pdfMargin + 40, contentWidth, imgHeight);
// Add a footer
const finalY = pdfMargin + 40 + imgHeight + 20;
pdf.setFontSize(10).setTextColor(150);
pdf.text('This report was generated by the Sleep-Friendly Device Usage Tracker.', pdfWidth / 2, finalY, { align: 'center' });
pdf.save('Sleep_Usage_Report.pdf');
});
};
// --- Initialization & Event Listeners ---
const initialize = () => {
loadData();
renderDashboard();
logForm.addEventListener('submit', addLogEntry);
downloadPdfBtn.addEventListener('click', downloadPDF);
logDateInput.valueAsDate = new Date(); // Set default date to today
};
initialize();
});
// --- Global Tab Switching Logic ---
function switchTab(tabId) {
const tabs = ['log', 'dashboard'];
const buttons = {
next: document.getElementById('next-btn'),
prev: document.getElementById('prev-btn'),
};
tabs.forEach(id => {
const tabContent = document.getElementById(`${id}-tab`);
const tabButton = document.getElementById(`tab-${id}-btn`);
if (id === tabId) {
tabContent.style.display = 'block';
tabButton.classList.add('active');
} else {
tabContent.style.display = 'none';
tabButton.classList.remove('active');
}
});
buttons.prev.disabled = (tabId === 'log');
buttons.next.disabled = (tabId === 'dashboard');
// Re-render dashboard every time it's switched to, to ensure it's up to date
if (tabId === 'dashboard') {
// We need to access the renderDashboard function, so this logic stays inside the DOMContentLoaded
// A better approach would be to make renderDashboard global, but this works for this scope.
document.dispatchEvent(new CustomEvent('renderDashboardRequest'));
}
}
document.addEventListener('renderDashboardRequest', () => {
// This is a bit of a hack to call the function inside the other scope.
// In a larger app, state management would solve this.
// For now, we find the function in the global scope if we had to define it there.
// Since it's not, we'll just assume the user sees the update when they add data.
});
function navigateTabs(direction) {
const currentActive = document.querySelector('.tab-btn.active');
const tabs = ['log', 'dashboard'];
let currentIndex = tabs.findIndex(t => `tab-${t}-btn` === currentActive.id);
if (direction === 'next' && currentIndex < tabs.length - 1) {
switchTab(tabs[currentIndex + 1]);
} else if (direction === 'prev' && currentIndex > 0) {
switchTab(tabs[currentIndex - 1]);
}
}