Spouse: ${escapeHTML(data.wife.name)}
Birth: ${escapeHTML(data.wife.birth)}
Death: ${escapeHTML(data.wife.death)}
Marriage Details
Date: ${escapeHTML(data.marriage.date)}
Place: ${escapeHTML(data.marriage.place)}
Children (${data.children.length} Total)
| # | Full Name | Birth Date & Place | Spouse (Optional) |
|---|---|---|---|
| No children listed. | |||
General Notes
${formatMultiLine(data.notes) || '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 });