Family Group Sheet Creator
Family Head (Husband)
Spouse (Wife)
Marriage Details
Children (Dashboard)
| # | Full Name | Birth Date & Place | Spouse (Optional) | Actions |
|---|
General Notes
Source Citations
List the records and documents verifying this data (one source per line).
Review the finalized Family Group Sheet document.
Click "Next" or "Previous" to refresh this preview.
No general notes provided.
'}Source Citations
${formatMultiLine(data.sources) || 'No source citations provided.
'} `; }; const downloadTxt = () => { const data = getSheetData(); let content = `FAMILY GROUP SHEET: ${data.husband.name} & ${data.wife.name}\n`; content += "========================================================\n\n"; content += "I. FAMILY HEAD (HUSBAND)\n"; content += "--------------------------\n"; content += `Name: ${data.husband.name}\n`; content += `Birth: ${data.husband.birth}\n`; content += `Death: ${data.husband.death}\n\n`; content += "II. SPOUSE (WIFE)\n"; content += "--------------------------\n"; content += `Name: ${data.wife.name}\n`; content += `Birth: ${data.wife.birth}\n`; content += `Death: ${data.wife.death}\n\n`; content += "III. MARRIAGE DETAILS\n"; content += "--------------------------\n"; content += `Date: ${data.marriage.date}\n`; content += `Place: ${data.marriage.place}\n\n`; content += "IV. CHILDREN\n"; content += "--------------------------\n"; content += " # | Name\t\t\t| Birth Data\t\t| Spouse\n"; content += "---|-----------------------|-----------------------|-----------------------\n"; data.children.forEach((child, index) => { content += `${(index + 1).toString().padEnd(2)} | ${child.name.padEnd(21).substring(0, 21)} | ${child.birth.padEnd(21).substring(0, 21)} | ${child.spouse}\n`; }); content += "\n"; content += "V. GENERAL NOTES\n"; content += "--------------------------\n"; content += `${data.notes}\n\n`; content += "VI. SOURCE CITATIONS\n"; content += "--------------------------\n"; content += `${data.sources}\n`; const blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `family_group_sheet_${data.husband.name.replace(/ /g, '_')}.txt`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(a.href); }; const downloadPDF = () => { if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') { alert('Error: jsPDF library not loaded.'); return; } const { jsPDF } = window.jspdf; const doc = new jsPDF('p', 'mm', 'a4'); const data = getSheetData(); const margin = 15; const pageWidth = doc.internal.pageSize.getWidth(); const usableWidth = pageWidth - margin * 2; let yPos = 20; const addWrappedText = (text, size, style, color = [52, 73, 94], lineSpacing = 6, indent = 0) => { doc.setFontSize(size); doc.setFont(undefined, style); doc.setTextColor(color[0], color[1], color[2]); const splitText = doc.splitTextToSize(text, usableWidth - indent); if (yPos + (splitText.length * lineSpacing) > 280) { doc.addPage(); yPos = 20; } doc.text(splitText, margin + indent, yPos); yPos += (splitText.length * lineSpacing); }; const addSectionTitle = (title) => { yPos += 5; addWrappedText(title, 14, 'bold', [0, 128, 128]); // Teal doc.setDrawColor(224, 224, 224); doc.line(margin, yPos, pageWidth - margin, yPos); yPos += 5; }; // 1. Title doc.setFontSize(18); doc.setFont(undefined, 'bold'); doc.setTextColor(44, 62, 80); doc.text(`Family Group Sheet: ${data.husband.name} & ${data.wife.name}`, pageWidth / 2, yPos, { align: 'center' }); yPos += 10; // 2. Parents addSectionTitle("I. FAMILY HEAD (HUSBAND)"); addWrappedText(`Name: ${data.husband.name}`, 10, 'bold', [52, 73, 94]); addWrappedText(`Birth: ${data.husband.birth} | Death: ${data.husband.death}`, 10, 'normal', [52, 73, 94], 5, 5); yPos += 5; addSectionTitle("II. SPOUSE (WIFE)"); addWrappedText(`Name: ${data.wife.name}`, 10, 'bold', [52, 73, 94]); addWrappedText(`Birth: ${data.wife.birth} | Death: ${data.wife.death}`, 10, 'normal', [52, 73, 94], 5, 5); yPos += 5; // 3. Marriage addSectionTitle("III. MARRIAGE DETAILS"); addWrappedText(`Date: ${data.marriage.date} | Place: ${data.marriage.place}`, 10, 'normal', [52, 73, 94], 5, 5); yPos += 5; // 4. Children Table addSectionTitle(`IV. CHILDREN (${data.children.length} Total)`); const childHead = [['#', 'Full Name', 'Birth Date & Place', 'Spouse (Optional)']]; const childBody = data.children.map((child, index) => [ index + 1, child.name, child.birth, child.spouse ]); // FIX: Only call autoTable if there is data, or handle yPos update gracefully if (childBody.length > 0) { doc.autoTable({ startY: yPos, head: childHead, body: childBody, theme: 'grid', styles: { fontSize: 9, cellPadding: 2, textColor: [52, 73, 94], valign: 'top' }, headStyles: { fillColor: [200, 200, 200], textColor: [44, 62, 80] }, columnStyles: { 0: { cellWidth: 8, halign: 'center' }, 1: { cellWidth: 45, fontStyle: 'bold' } }, margin: { left: margin, right: margin } }); yPos = doc.autoTable.previous.finalY + 10; } else { addWrappedText("No children listed for this family unit.", 10, 'italic', [108, 117, 125], 5, 5); yPos += 5; } // 5. Notes & Sources addSectionTitle("V. GENERAL NOTES"); addWrappedText(data.notes || 'No general notes provided.', 10, 'normal', [52, 73, 94]); yPos += 5; addSectionTitle("VI. SOURCE CITATIONS"); addWrappedText(data.sources || 'No source citations provided.', 10, 'normal', [52, 73, 94]); doc.save(`family_group_sheet_${data.husband.name.replace(/ /g, '_')}.pdf`); }; // --- Event Listeners --- // Tab Buttons tabButtons.forEach((btn, index) => { btn.addEventListener('click', () => showTab(index + 1)); }); // Next/Prev Navigation nextBtn.addEventListener('click', () => showTab(currentTab + 1)); prevBtn.addEventListener('click', () => showTab(currentTab - 1)); // Tab 2 Actions (Children) addChildBtn.addEventListener('click', addChild); childrenTbody.addEventListener('click', (e) => { if (e.target.dataset.removeId) { removeChild(parseInt(e.target.dataset.removeId)); } }); childrenTbody.addEventListener('blur', (e) => { if (e.target.tagName === 'TD' && e.target.isContentEditable) { const id = parseInt(e.target.dataset.id); const field = e.target.dataset.field; updateChild(id, field, e.target.textContent); } }, true); // Tab 4 Actions refreshBtn.addEventListener('click', generatePreview); downloadPdfBtn.addEventListener('click', downloadPDF); downloadTxtBtn.addEventListener('click', downloadTxt); // --- Initialization --- showTab(1); // Set initial state });