Family History Documentation Tool

Family History Documentation Tool

Document, organize, and cherish your family's story.

Your Family Story

This dashboard displays all documented family members. Add new members in the 'Add / Edit Member' tab. Click 'Edit' on any card to update their information.

Parents: ${[parent1?.fullName, parent2?.fullName].filter(Boolean).join(' & ')}

` : ''} ${member.biography ? `

${member.biography.replace(/\n/g, '
')}

` : ''}
`; 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 = ''; 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(); });
Scroll to Top