No logs yet. Go to the "Routine Planner" to start.
`;
return;
}
let tableHTML = `
| Date |
Consistency |
Actions |
`;
routineData.logs.forEach((log, index) => {
const visibleTasks = Object.keys(ROUTINE_TASKS).filter(key => routineData.goals.some(goal => ROUTINE_TASKS[key].goals.includes(goal)));
const totalTasks = visibleTasks.length * 2; // Morning and evening
const completedCount = log.completed.length;
const consistency = totalTasks > 0 ? Math.round((completedCount / totalTasks) * 100) : 0;
tableHTML += `
| ${new Date(log.date + 'T00:00:00').toLocaleDateString()} |
${consistency}% |
|
`;
});
tableHTML += `
`;
historyTableContainer.innerHTML = tableHTML;
};
const renderConsistencyChart = () => {
if (consistencyChart) consistencyChart.destroy();
if (routineData.logs.length === 0) {
chartCanvas.style.display = 'none';
return;
}
chartCanvas.style.display = 'block';
const chartData = [...routineData.logs].reverse().slice(-30);
const labels = chartData.map(d => new Date(d.date + 'T00:00:00').toLocaleDateString());
const scores = chartData.map(log => {
const visibleTasks = Object.keys(ROUTINE_TASKS).filter(key => routineData.goals.some(goal => ROUTINE_TASKS[key].goals.includes(goal)));
const totalTasks = visibleTasks.length * 2;
return totalTasks > 0 ? Math.round((log.completed.length / totalTasks) * 100) : 0;
});
consistencyChart = new Chart(chartCanvas, {
type: 'line',
data: {
labels: labels,
datasets: [{
label: 'Consistency Score (%)',
data: scores,
borderColor: '#0ea5e9',
backgroundColor: 'rgba(14, 165, 233, 0.1)',
fill: true,
tension: 0.2
}]
},
options: { responsive: true, scales: { y: { beginAtZero: true, max: 100 } } }
});
};
// IV. Event Listeners & Tab Logic
tabs.forEach(tab => {
tab.addEventListener('click', () => {
tabs.forEach(t => t.classList.replace('active', 'inactive'));
tab.classList.replace('inactive', 'active');
tabContents.forEach(c => c.classList.add('hidden'));
document.getElementById(tab.dataset.tab).classList.remove('hidden');
});
});
goalsContainer.addEventListener('change', () => {
routineData.goals = Array.from(document.querySelectorAll('input[name="goal"]:checked')).map(cb => cb.value);
updateVisibleTasks();
});
logDateInput.addEventListener('change', () => {
populateRoutineChecklists();
});
saveEntryBtn.addEventListener('click', saveAndNotify);
historyTableContainer.addEventListener('click', (e) => {
if (e.target.classList.contains('delete-btn')) {
const dateToDelete = e.target.dataset.date;
if (confirm(`Are you sure you want to delete the log for ${new Date(dateToDelete + 'T00:00:00').toLocaleDateString()}?`)) {
routineData.logs = routineData.logs.filter(log => log.date !== dateToDelete);
localStorage.setItem('dentalPlannerData', JSON.stringify(routineData));
renderDashboard();
}
}
});
// V. PDF Generation
pdfDownloadBtn.addEventListener('click', () => {
if (routineData.logs.length === 0) return alert('No data to generate a report.');
saveCurrentState(); // Ensure latest data is used
const { jsPDF } = jspdf;
const doc = new jsPDF();
const pageW = doc.internal.pageSize.getWidth();
const margin = 15;
doc.setFillColor(14, 165, 233);
doc.rect(0, 0, pageW, 25, 'F');
doc.setFontSize(16);
doc.setFont('helvetica', 'bold');
doc.setTextColor(255, 255, 255);
doc.text('Dental Hygiene Progress Report', margin, 16);
doc.setFontSize(12);
doc.setFont('helvetica', 'bold');
doc.setTextColor(30, 41, 59);
doc.text('Your Goals:', margin, 40);
doc.setFontSize(10);
doc.setFont('helvetica', 'normal');
doc.text(routineData.goals.map(g => GOALS[g].label).join(', '), margin, 48);
if (consistencyChart && chartCanvas.style.display !== 'none') {
const chartImage = chartCanvas.toDataURL('image/png', 1.0);
doc.setFontSize(12);
doc.setFont('helvetica', 'bold');
doc.text('Consistency Chart', margin, 60);
doc.addImage(chartImage, 'PNG', margin, 65, pageW - (margin * 2), (pageW - (margin * 2)) * 0.5);
}
const tableStartY = 140;
doc.setFontSize(12);
doc.setFont('helvetica', 'bold');
doc.text('Detailed Log', margin, tableStartY);
const tableData = routineData.logs.map(log => {
const visibleTasks = Object.keys(ROUTINE_TASKS).filter(key => routineData.goals.some(goal => ROUTINE_TASKS[key].goals.includes(goal)));
const totalTasks = visibleTasks.length * 2;
const score = totalTasks > 0 ? `${log.completed.length}/${totalTasks}` : 'N/A';
return [new Date(log.date + 'T00:00:00').toLocaleDateString(), score];
});
doc.autoTable({
startY: tableStartY + 5,
head: [['Date', 'Tasks Completed']],
body: tableData,
headStyles: { fillColor: [71, 85, 105] },
theme: 'grid'
});
doc.save('Dental_Hygiene_Report.pdf');
});
// Initial Load
logDateInput.valueAsDate = new Date();
populateGoals();
populateRoutineChecklists();
updateVisibleTasks();
renderDashboard();
});