`;
familyGrid.innerHTML += card;
});
}
};
// Function to populate relationship dropdowns in the form
const populateDropdowns = (currentMemberId = null) => {
const selects = [document.getElementById('spouse'), document.getElementById('parent1'), document.getElementById('parent2')];
if (!selects[0]) return;
selects.forEach(select => {
const currentValue = select.value;
select.innerHTML = '
None ';
familyData.forEach(person => {
// A person cannot be their own relative
if (person.id !== currentMemberId) {
const option = document.createElement('option');
option.value = person.id;
option.textContent = person.fullName;
select.appendChild(option);
}
});
select.value = currentValue;
});
};
// Function to reset the form
const resetForm = () => {
memberForm.reset();
memberIdInput.value = '';
formTitle.textContent = 'Add a New Family Member';
populateDropdowns();
};
// Handle form submission
memberForm.addEventListener('submit', (e) => {
e.preventDefault();
const id = memberIdInput.value;
const memberData = {
id: id || `member_${new Date().getTime()}`,
fullName: document.getElementById('full-name').value,
relationshipSelf: document.getElementById('relationship-self').value,
dob: document.getElementById('dob').value,
pob: document.getElementById('pob').value,
dod: document.getElementById('dod').value,
pod: document.getElementById('pod').value,
spouseId: document.getElementById('spouse').value,
parent1Id: document.getElementById('parent1').value,
parent2Id: document.getElementById('parent2').value,
biography: document.getElementById('biography').value,
};
// Guard against empty name
if (!memberData.fullName.trim()) {
// Using a custom modal/alert in the future would be better, but for now, standard alert is used as per spec simplicity.
alert("Full Name is a required field.");
return;
}
if (id) { // Editing existing member
const index = familyData.findIndex(m => m.id === id);
if (index !== -1) familyData[index] = memberData;
} else { // Adding new member
familyData.push(memberData);
}
saveDataToLocalStorage();
renderDashboard();
resetForm();
switchTab('dashboard'); // Switch to dashboard after saving
});
// Edit a member
const editMember = (id) => {
const member = getMemberById(id);
if (member) {
switchTab('form');
formTitle.textContent = `Editing: ${member.fullName}`;
memberIdInput.value = member.id;
document.getElementById('full-name').value = member.fullName;
document.getElementById('relationship-self').value = member.relationshipSelf;
document.getElementById('dob').value = member.dob;
document.getElementById('pob').value = member.pob;
document.getElementById('dod').value = member.dod;
document.getElementById('pod').value = member.pod;
document.getElementById('biography').value = member.biography;
populateDropdowns(member.id);
document.getElementById('spouse').value = member.spouseId || '';
document.getElementById('parent1').value = member.parent1Id || '';
document.getElementById('parent2').value = member.parent2Id || '';
}
};
// Delete a member
const deleteMember = (id) => {
if (confirm('Are you sure you want to delete this family member? This action cannot be undone.')) {
familyData = familyData.filter(member => member.id !== id);
// Also remove this person from any relationship fields
familyData.forEach(member => {
if (member.spouseId === id) member.spouseId = '';
if (member.parent1Id === id) member.parent1Id = '';
if (member.parent2Id === id) member.parent2Id = '';
});
saveDataToLocalStorage();
renderDashboard();
populateDropdowns();
}
};
// --- UI/NAVIGATION FUNCTIONS ---
// Switch between tabs
const switchTab = (tabName) => {
if (!tabs.includes(tabName)) return;
currentTab = tabName;
Object.values(tabContent).forEach(content => content.classList.add('hidden'));
if(tabContent[tabName]) tabContent[tabName].classList.remove('hidden');
Object.values(tabButtons).forEach(button => button.classList.remove('active'));
if(tabButtons[tabName]) tabButtons[tabName].classList.add('active');
// If switching to the form tab, ensure dropdowns are up-to-date
if(tabName === 'form') {
populateDropdowns(memberIdInput.value || null);
}
updateNavButtons();
};
const updateNavButtons = () => {
const currentIndex = tabs.indexOf(currentTab);
prevBtn.disabled = currentIndex === 0;
nextBtn.disabled = currentIndex === tabs.length - 1;
};
const navigateTabs = (direction) => {
const currentIndex = tabs.indexOf(currentTab);
let newIndex;
if (direction === 'next') {
newIndex = Math.min(currentIndex + 1, tabs.length - 1);
} else {
newIndex = Math.max(currentIndex - 1, 0);
}
switchTab(tabs[newIndex]);
};
// --- PDF GENERATION (NEW & IMPROVED) ---
const generateStyledPDF = () => {
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
// --- PDF Styling Constants ---
const pageHeight = doc.internal.pageSize.height || doc.internal.pageSize.getHeight();
const pageWidth = doc.internal.pageSize.width || doc.internal.pageSize.getWidth();
const margin = 20;
const maxLineWidth = pageWidth - margin * 2;
let y = margin; // Vertical position tracker
// --- Helper function to check for page overflow ---
const checkPageBreak = (neededHeight) => {
if (y + neededHeight > pageHeight - margin) {
doc.addPage();
y = margin;
addHeaderFooter();
}
};
// --- Helper function for headers and footers ---
const addHeaderFooter = () => {
// Header
doc.setFontSize(18);
doc.setFont('helvetica', 'bold');
doc.setTextColor(40, 52, 148); // Dark Blue
doc.text("Family History Report", pageWidth / 2, margin - 5, { align: 'center' });
// Footer
const pageCount = doc.internal.getNumberOfPages();
doc.setFontSize(10);
doc.setFont('helvetica', 'normal');
doc.setTextColor(150); // Gray
doc.text(`Page ${pageCount}`, pageWidth / 2, pageHeight - 10, { align: 'center' });
};
addHeaderFooter();
y += 10; // Initial space after header
familyData.forEach((member, index) => {
const spouse = member.spouseId ? getMemberById(member.spouseId) : null;
const parent1 = member.parent1Id ? getMemberById(member.parent1Id) : null;
const parent2 = member.parent2Id ? getMemberById(member.parent2Id) : null;
// Estimate height needed for this member's section and check for page break
let requiredHeight = 25; // Base height for name and line
if(member.biography) requiredHeight += (doc.splitTextToSize(member.biography, maxLineWidth).length * 5) + 5;
checkPageBreak(requiredHeight);
// --- Member Name and Separator ---
doc.setFontSize(16);
doc.setFont('helvetica', 'bold');
doc.setTextColor(23, 37, 84); // Darker Blue
doc.text(member.fullName, margin, y);
y += 6;
doc.setDrawColor(229, 231, 235); // Light Gray line
doc.line(margin, y, pageWidth - margin, y);
y += 8;
// --- Member Details ---
doc.setFontSize(11);
doc.setFont('helvetica', 'normal');
doc.setTextColor(55, 65, 81); // Dark Gray text
const addDetail = (label, value) => {
if (value && value.trim() !== '') {
checkPageBreak(10);
doc.setFont('helvetica', 'bold');
doc.text(label, margin, y);
doc.setFont('helvetica', 'normal');
doc.text(value, margin + 35, y);
y += 7;
}
};
addDetail("Relation:", member.relationshipSelf);
addDetail("Born:", `${member.dob || 'N/A'} in ${member.pob || 'N/A'}`);
if(member.dod) addDetail("Died:", `${member.dod} in ${member.pod || 'N/A'}`);
if(spouse) addDetail("Spouse:", spouse.fullName);
if(parent1 || parent2) addDetail("Parents:", [parent1?.fullName, parent2?.fullName].filter(Boolean).join(' & '));
// --- Biography with word wrap ---
if (member.biography) {
y += 3; // Extra space before biography
checkPageBreak(15);
doc.setFont('helvetica', 'bold');
doc.text("Biography:", margin, y);
y += 6;
doc.setFont('helvetica', 'normal');
doc.setTextColor(107, 114, 128); // Lighter gray for bio text
const bioLines = doc.splitTextToSize(member.biography, maxLineWidth);
checkPageBreak(bioLines.length * 5); // Check if bio fits
doc.text(bioLines, margin, y);
y += bioLines.length * 5 + 5;
}
y += 10; // Space between entries
});
// Finalize page numbering after all pages are added
const pageCount = doc.internal.getNumberOfPages();
for(let i = 1; i <= pageCount; i++) {
doc.setPage(i);
doc.setFontSize(10);
doc.setTextColor(150);
doc.text(`Page ${i} of ${pageCount}`, pageWidth / 2, pageHeight - 10, { align: 'center' });
}
doc.save('Family-History-Report.pdf');
};
pdfButton.addEventListener('click', generateStyledPDF);
// --- LOCAL STORAGE & INITIALIZATION ---
const saveDataToLocalStorage = () => {
localStorage.setItem('familyHistoryData', JSON.stringify(familyData));
};
const loadDataFromLocalStorage = () => {
const savedData = localStorage.getItem('familyHistoryData');
if (savedData) {
familyData = JSON.parse(savedData);
} else {
// Pre-populate with some USA-relevant sample data if nothing is saved
familyData = [
{ id: 'member_1', fullName: 'Johnathan P. Smith', relationshipSelf: 'Paternal Grandfather', dob: '1945-05-10', pob: 'Chicago, IL', dod: '2015-11-22', pod: 'Miami, FL', spouseId: 'member_2', parent1Id: '', parent2Id: '', biography: 'Served in the army and later became a successful carpenter. Loved fishing and spending time with his grandchildren. He was known for his incredible stories and hearty laugh.' },
{ id: 'member_2', fullName: 'Eleanor R. Smith (née Davis)', relationshipSelf: 'Paternal Grandmother', dob: '1948-02-20', pob: 'Boston, MA', dod: '2018-01-15', pod: 'Miami, FL', spouseId: 'member_1', parent1Id: '', parent2Id: '', biography: 'A dedicated nurse and a loving mother of two. Her apple pie was legendary in the neighborhood. She was an avid reader and volunteered at the local library.' },
{ id: 'member_3', fullName: 'Robert D. Smith', relationshipSelf: 'Father', dob: '1970-08-30', pob: 'Miami, FL', dod: '', pod: '', spouseId: '', parent1Id: 'member_1', parent2Id: 'member_2', biography: 'An accountant with a passion for classic cars. Spends his weekends restoring a 1967 Mustang.' },
];
saveDataToLocalStorage();
}
};
// --- INITIALIZE APP ---
window.app = {
switchTab,
editMember,
deleteMember,
resetForm,
navigateTabs,
};
loadDataFromLocalStorage();
renderDashboard();
updateNavButtons();
});