Personal Genealogy Research Dashboard

Personal Genealogy Research Dashboard

Organize, visualize, and track your family history.

Research Overview

Family Tree

Individual Records

Name Birth Year Death Year Parents

Manage Individuals

${oldestPerson ? `${oldestPerson.name} (b. ${oldestPerson.birthYear})` : 'N/A'}

`; }; const renderDataTable = () => { dataTableBody.innerHTML = ''; const sortedPeople = [...state.people].sort((a, b) => a.birthYear - b.birthYear); sortedPeople.forEach(p => { const parent1 = state.people.find(parent => parent.id === p.parent1Id); const parent2 = state.people.find(parent => parent.id === p.parent2Id); const parentsText = [parent1, parent2].filter(Boolean).map(p => p.name).join(' & '); const row = ` ${p.name} ${p.birthYear || 'N/A'} ${p.deathYear || 'Living'} ${parentsText || 'N/A'} `; dataTableBody.innerHTML += row; }); }; const buildTreeHTML = (personId) => { const person = state.people.find(p => p.id === personId); if (!person) return ''; const children = state.people.filter(p => p.parent1Id === person.id || p.parent2Id === person.id); const spouseRel = state.relationships.find(r => r.person1Id === person.id || r.person2Id === person.id); const spouse = spouseRel ? state.people.find(p => p.id === (spouseRel.person1Id === person.id ? spouseRel.person2Id : spouseRel.person1Id)) : null; let html = `
  • `; html += `${person.name}
    ${person.birthYear || '?'} - ${person.deathYear || 'Present'}
    `; if (spouse) { html += `${spouse.name}
    ${spouse.birthYear || '?'} - ${spouse.deathYear || 'Present'}
    `; } if (children.length > 0) { html += `
      `; children.forEach(child => { html += buildTreeHTML(child.id); }); html += `
    `; } html += `
  • `; return html; }; const renderFamilyTree = () => { // Find root nodes (people without parents in the dataset) const rootPeople = state.people.filter(p => !p.parent1Id && !p.parent2Id); if (rootPeople.length > 0) { familyTreeContainer.innerHTML = `
      ${buildTreeHTML(rootPeople[0].id)}
    `; } else if (state.people.length > 0) { // Fallback to the first person if no clear root familyTreeContainer.innerHTML = `
      ${buildTreeHTML(state.people[0].id)}
    `; } else { familyTreeContainer.innerHTML = `

    No data to display tree.

    `; } }; const renderConfigRows = () => { configRowsContainer.innerHTML = ''; const sortedPeople = [...state.people].sort((a,b) => a.id - b.id); const peopleOptions = state.people.map(p => ``).join(''); sortedPeople.forEach(p => { configRowsContainer.innerHTML += `
    `.replaceAll('class="config-input"', 'class="config-input w-full border-gray-300 rounded-md shadow-sm"'); }); // Set selected options for parents after rendering all rows sortedPeople.forEach(p => { if(p.parent1Id) document.querySelector(`select[data-id="${p.id}"][data-field="parent1Id"]`).value = p.parent1Id; if(p.parent2Id) document.querySelector(`select[data-id="${p.id}"][data-field="parent2Id"]`).value = p.parent2Id; }); addConfigEventListeners(); }; // --- EVENT HANDLERS --- const handleConfigChange = (e) => { const id = parseInt(e.target.dataset.id); const field = e.target.dataset.field; const value = e.target.value; const person = state.people.find(p => p.id === id); if (person) { person[field] = value ? parseInt(value, 10) : null; if (field === 'name') person[field] = value; } renderAll(); }; const handleAddPerson = () => { const newId = state.people.length > 0 ? Math.max(...state.people.map(p => p.id)) + 1 : 1; state.people.push({ id: newId, name: "New Person", birthYear: null, deathYear: null, parent1Id: null, parent2Id: null }); renderAll(); }; const handleRemovePerson = (e) => { const id = parseInt(e.target.dataset.id); state.people = state.people.filter(p => p.id !== id); // Also remove from parent fields of other people state.people.forEach(p => { if (p.parent1Id === id) p.parent1Id = null; if (p.parent2Id === id) p.parent2Id = null; }); renderAll(); }; const addConfigEventListeners = () => { document.querySelectorAll('.config-input').forEach(input => input.addEventListener('change', handleConfigChange)); document.querySelectorAll('.remove-btn').forEach(button => button.addEventListener('click', handleRemovePerson)); }; const handleDownloadPdf = () => { const { jsPDF } = window.jspdf; const pdfContent = document.getElementById('pdf-content'); document.querySelectorAll('.no-print').forEach(el => el.style.visibility = 'hidden'); html2canvas(pdfContent, { scale: 2, useCORS: true, backgroundColor: '#ffffff' }).then(canvas => { document.querySelectorAll('.no-print').forEach(el => el.style.visibility = 'visible'); 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('Genealogy-Dashboard.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 && addPersonBtn && downloadPdfBtn) { addPersonBtn.addEventListener('click', handleAddPerson); downloadPdfBtn.addEventListener('click', handleDownloadPdf); renderAll(); updateTabButtons(); } else { console.error("Essential dashboard elements could not be found."); } });
    Scroll to Top