Add your procedure steps and materials above.
';
return;
}
let procedureHtml = '';
let materialHtml = '';
procedureMaterials.forEach((item, index) => {
const contentHtml = `
${item.type === 'Procedure' ? `${index + 1}.` : 'Item:'} ${item.content}
`;
if (item.type === 'Procedure') {
procedureHtml += `
${contentHtml}`;
} else {
materialHtml += `
${contentHtml}`;
}
});
itemList.innerHTML = `
Procedure:
${procedureHtml || '
None defined.
'}
Materials:
${materialHtml || '
None defined.
'}
`;
}
function addProcedureMaterial() {
const content = stepInput.value.trim();
const type = itemTypeSelect.value;
if (!content) {
alert("Please enter content for the step or material.");
return;
}
procedureMaterials.push({ id: generateId(), content, type });
renderProcedureMaterials();
stepInput.value = '';
}
function removeProcedureMaterial(id) {
procedureMaterials = procedureMaterials.filter(item => item.id !== id);
renderProcedureMaterials();
}
// --- Data Log Management ---
function renderObservationLog() {
logTableBody.innerHTML = '';
if (observationLog.length === 0) {
logTableBody.innerHTML = '
| Logged data will appear here. |
';
return;
}
// Sort by date ascending
observationLog.sort((a, b) => new Date(a.date) - new Date(b.date));
observationLog.forEach((log) => {
const row = document.createElement('tr');
row.innerHTML = `
${log.date} |
${log.trial} |
${log.observation} |
|
`;
logTableBody.appendChild(row);
});
}
function addLog() {
const date = logDateInput.value.trim();
const trial = logTrialInput.value.trim();
const observation = logObservationInput.value.trim();
if (!date || !trial || !observation) {
alert("Please fill in Date, Trial/Condition, and Observation/Result.");
return;
}
observationLog.push({ id: generateId(), date, trial, observation });
renderObservationLog();
logTrialInput.value = '';
logObservationInput.value = '';
}
function removeLog(id) {
observationLog = observationLog.filter(log => log.id !== id);
renderObservationLog();
}
// --- PDF Generation (Ensured functionality) ---
function downloadPDF() {
if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') {
alert('Error: jsPDF library not loaded.'); return;
}
if (typeof window.jspdf.jsPDF.autoTable === 'undefined') {
alert('Error: jsPDF-AutoTable library not loaded.'); return;
}
const { jsPDF } = window.jspdf;
const doc = new jsPDF('portrait', 'pt', 'a4');
const margin = 40;
const pageWidth = doc.internal.pageSize.getWidth();
let currentY = margin;
const experimentTitle = experimentTitleInput.value || 'Untitled Science Experiment';
// --- Helper function to add structured text sections ---
function addTextSection(title, content, startY) {
const estimatedHeight = 15 + (content.split('\n').length * 12) + 10;
if (startY + estimatedHeight > doc.internal.pageSize.getHeight() - margin) {
doc.addPage();
startY = margin;
}
doc.setFontSize(14);
doc.setFont('helvetica', 'bold');
doc.setTextColor(124, 58, 237); // violet-600
doc.text(title, margin, startY);
doc.setFontSize(10);
doc.setFont('helvetica', 'normal');
doc.setTextColor(51, 65, 85);
const lines = doc.splitTextToSize(content || 'N/A', pageWidth - margin * 2);
doc.text(lines, margin, startY + 15);
return startY + 15 + lines.length * 12 + 10;
}
// --- Header ---
doc.setFontSize(20);
doc.setFont('helvetica', 'bold');
doc.setTextColor(126, 34, 206);
doc.text("Science Experiment Lab Report", pageWidth / 2, currentY, { align: 'center' });
currentY += 15;
doc.setFontSize(14);
doc.text(experimentTitle, pageWidth / 2, currentY, { align: 'center' });
currentY += 30;
// --- Section 1: Hypothesis & Variables ---
currentY = addTextSection("1. Hypothesis & Variables", currentY);
const varData = [
["Hypothesis:", hypothesisTextarea.value || 'N/A'],
["Independent Variable:", independentVarInput.value || 'N/A'],
["Dependent Variable:", dependentVarInput.value || 'N/A'],
];
doc.autoTable({
startY: currentY,
body: varData,
theme: 'striped',
styles: { cellPadding: 3, fontSize: 10 },
columnStyles: { 0: { fontStyle: 'bold', fillColor: [245, 243, 255] } }
});
currentY = doc.autoTable.previous.finalY + 15;
// --- Section 2: Procedure & Materials ---
currentY = addTextSection("2. Procedure & Materials", currentY);
const procedureData = procedureMaterials.filter(item => item.type === 'Procedure').map((item, index) => [index + 1, item.content]);
const materialData = procedureMaterials.filter(item => item.type === 'Material').map(item => [item.content]);
// Procedure Table
if (procedureData.length > 0) {
doc.setFontSize(10);
doc.setFont('helvetica', 'bold');
doc.text("Procedure Steps:", margin, currentY);
currentY += 12;
doc.autoTable({
startY: currentY,
head: [['#', 'Action Step']],
body: procedureData,
theme: 'grid',
headStyles: { fillColor: [147, 51, 234] },
styles: { fontSize: 9, minCellHeight: 12 },
columnStyles: { 0: { cellWidth: 30, halign: 'center' } }
});
currentY = doc.autoTable.previous.finalY + 10;
}
// Materials Table
if (materialData.length > 0) {
doc.setFontSize(10);
doc.setFont('helvetica', 'bold');
doc.text("Materials Used:", margin, currentY);
currentY += 12;
doc.autoTable({
startY: currentY,
head: [['Material / Equipment']],
body: materialData,
theme: 'grid',
styles: { fontSize: 9, minCellHeight: 12 },
margin: { left: margin, right: pageWidth / 2 }
});
currentY = doc.autoTable.previous.finalY + 15;
}
// --- Section 3: Data Log ---
currentY = addTextSection("3. Data Observation Log", currentY);
if (observationLog.length > 0) {
const logData = observationLog.map(log => [log.date, log.trial, log.observation]);
doc.autoTable({
startY: currentY,
head: [['Date', 'Trial / Condition', 'Observation / Result']],
body: logData,
theme: 'grid',
headStyles: { fillColor: [147, 51, 234] },
styles: { fontSize: 9, minCellHeight: 15, overflow: 'linebreak' },
margin: { left: margin, right: margin }
});
currentY = doc.autoTable.previous.finalY + 15;
} else {
doc.setFontSize(10);
doc.text("No data was logged for this experiment.", margin, currentY);
currentY += 20;
}
// --- Section 4: Conclusion ---
currentY = addTextSection("4. Conclusion & Reflection", conclusionTextarea.value, currentY);
doc.save('science-lab-report.pdf');
}
// --- Event Listeners and Initial Load ---
addItemBtn.addEventListener('click', addProcedureMaterial);
stepInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
addProcedureMaterial();
}
});
logTableBody.addEventListener('click', (e) => {
if (e.target.classList.contains('setg-btn-red')) {
removeLog(e.target.dataset.id);
}
});
addLogBtn.addEventListener('click', addLog);
// Set current date for log
logDateInput.value = new Date().toISOString().split('T')[0];
pdfBtn.addEventListener('click', downloadPDF);
// Initial load
renderProcedureMaterials();
renderObservationLog();
});