Next Vet Visit
${nextAppointment}
Vaccinations Due
${nextVaccine}
`;
};
const renderWeightChart = () => {
const sortedWeightLog = [...state.weightLog].sort((a,b) => new Date(a.date) - new Date(b.date));
const labels = sortedWeightLog.map(w => w.date);
if (weightChart) weightChart.destroy();
weightChart = new Chart(weightChartCanvas.getContext('2d'), {
type: 'line',
data: {
labels: labels,
datasets: [{ label: 'Weight (lbs)', data: sortedWeightLog.map(w => w.weight), borderColor: '#10b981', tension: 0.1 }]
},
options: {
responsive: true, maintainAspectRatio: false,
scales: { x: { type: 'time', time: { unit: 'month' } }, y: { beginAtZero: false, title: { display: true, text: 'Weight (lbs)' } } }
}
});
};
const renderAppointments = () => {
appointmentsList.innerHTML = '';
if(state.appointments.length === 0) { appointmentsList.innerHTML = `
No upcoming appointments.
`; return; }
[...state.appointments].sort((a,b) => new Date(a.date) - new Date(b.date)).forEach(a => {
appointmentsList.innerHTML += `
${a.reason}
${a.date} at ${a.time}
`;
});
};
const renderVaccinations = () => {
vaccinationsList.innerHTML = '';
if(state.vaccinations.length === 0) { vaccinationsList.innerHTML = `
No vaccination records.
`; return; }
[...state.vaccinations].sort((a,b) => new Date(a.nextDue) - new Date(b.nextDue)).forEach(v => {
vaccinationsList.innerHTML += `
${v.name}
Next Due: ${v.nextDue}
`;
});
};
const renderConfigRows = () => {
// Pet Info
petInfoConfig.innerHTML = `
`.replaceAll('class="config-input"', 'class="config-input mt-1 w-full border-gray-300 rounded-md shadow-sm"');
// Weight
weightConfig.innerHTML = '';
[...state.weightLog].sort((a,b)=>new Date(b.date)-new Date(a.date)).forEach(w => {
weightConfig.innerHTML += `
`.replaceAll('class="config-input"', 'class="config-input w-full border-gray-300 rounded-md shadow-sm"');
});
// Appointments
apptConfig.innerHTML = '';
[...state.appointments].sort((a,b)=>new Date(b.date)-new Date(a.date)).forEach(a => {
apptConfig.innerHTML += `
`.replaceAll('class="config-input"', 'class="config-input w-full border-gray-300 rounded-md shadow-sm"');
});
// Vaccinations
vaxConfig.innerHTML = '';
[...state.vaccinations].sort((a,b)=>new Date(b.dateGiven)-new Date(a.dateGiven)).forEach(v => {
vaxConfig.innerHTML += `
`.replaceAll('class="config-input"', 'class="config-input w-full border-gray-300 rounded-md shadow-sm"');
});
addConfigEventListeners();
};
// --- EVENT HANDLERS ---
const handleConfigChange = (e) => {
const type = e.target.dataset.type;
const field = e.target.dataset.field;
const value = e.target.value;
if (type === 'petInfo') {
state.petInfo[field] = value;
} else {
const id = parseInt(e.target.dataset.id);
const entry = state[type].find(item => item.id === id);
if (entry) entry[field] = (e.target.type === 'number') ? parseFloat(value) : value;
}
renderAll();
};
const handleAdd = (type) => {
const today = new Date().toISOString().split('T')[0];
const newId = state[type].length > 0 ? Math.max(...state[type].map(item => item.id)) + 1 : 1;
if (type === 'weightLog') state.weightLog.push({ id: newId, date: today, weight: 0 });
if (type === 'appointments') state.appointments.push({ id: newId, date: today, time: "12:00", reason: "New Appointment" });
if (type === 'vaccinations') state.vaccinations.push({ id: newId, dateGiven: today, name: "New Vaccine", nextDue: today });
renderAll();
};
const handleRemove = (e) => {
const id = parseInt(e.target.dataset.id);
const type = e.target.dataset.type;
state[type] = state[type].filter(item => item.id !== id);
renderAll();
};
const addConfigEventListeners = () => {
document.querySelectorAll('.config-input').forEach(input => input.addEventListener('change', handleConfigChange));
document.querySelectorAll('.remove-btn').forEach(button => button.addEventListener('click', handleRemove));
};
const handleDownloadPdf = () => {
const { jsPDF } = window.jspdf;
const pdfContent = document.getElementById('pdf-content');
document.querySelectorAll('.no-print').forEach(el => el.style.visibility = 'hidden');
Chart.defaults.animation = false;
html2canvas(pdfContent, { scale: 2, useCORS: true, backgroundColor: '#ffffff' }).then(canvas => {
document.querySelectorAll('.no-print').forEach(el => el.style.visibility = 'visible');
Chart.defaults.animation = true;
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF({ orientation: 'p', unit: 'mm', format: 'a4' });
const pdfWidth = pdf.internal.pageSize.getWidth();
const imgWidth = pdfWidth - 20;
const imgHeight = canvas.height * imgWidth / canvas.width;
pdf.addImage(imgData, 'PNG', 10, 10, imgWidth, imgHeight);
pdf.save(`${state.petInfo.name}-Health-Summary.pdf`);
});
};
// --- TABBING LOGIC ---
let currentTabIndex = 0;
const updateTabButtons = () => {
prevTabBtn.disabled = currentTabIndex === 0;
nextTabBtn.disabled = currentTabIndex === tabContents.length - 1;
};
const switchTab = (index) => {
tabButtons.forEach(btn => btn.classList.remove('active'));
tabContents.forEach(content => content.classList.remove('active'));
tabButtons[index].classList.add('active');
tabContents[index].classList.add('active');
currentTabIndex = index;
updateTabButtons();
};
tabButtons.forEach((button, index) => button.addEventListener('click', () => switchTab(index)));
prevTabBtn.addEventListener('click', () => { if (currentTabIndex > 0) switchTab(currentTabIndex - 1); });
nextTabBtn.addEventListener('click', () => { if (currentTabIndex < tabContents.length - 1) switchTab(currentTabIndex + 1); });
// --- INITIALIZATION ---
if (kpiContainer && addWeightBtn && addApptBtn && addVaxBtn && downloadPdfBtn) {
addWeightBtn.addEventListener('click', () => handleAdd('weightLog'));
addApptBtn.addEventListener('click', () => handleAdd('appointments'));
addVaxBtn.addEventListener('click', () => handleAdd('vaccinations'));
downloadPdfBtn.addEventListener('click', handleDownloadPdf);
renderAll();
updateTabButtons();
} else {
console.error("Essential dashboard elements could not be found.");
}
});