No trials added yet.
";
return;
}
trials.forEach(t => {
const itemEl = document.createElement("div");
itemEl.className = "elg-config-list-item";
itemEl.dataset.id = t.id;
itemEl.innerHTML = `
Trial ${trials.indexOf(t) + 1}: Input ${escapeHTML(t.input)} -> Result ${escapeHTML(t.outcome.substring(0, 30))}...
`;
configTrialList.appendChild(itemEl);
});
}
// --- Dashboard Management ---
function renderDashboard(isInitial = true) {
// Get config values
const project = configProject.value;
const date = configDate.value;
const procedure = configProcedureSummary.value;
// --- Build Dashboard HTML ---
dashboardOutput.innerHTML = `
`;
// Populate Table Body
const tbody = dashboardOutput.querySelector('#elg-dash-tbody');
if (trials.length === 0) {
tbody.innerHTML = `
|
No trials logged yet.
|
`;
} else {
trials.forEach((t, index) => {
const tr = document.createElement('tr');
tr.dataset.id = t.id;
tr.innerHTML = `
${index + 1} |
|
|
|
|
`;
tbody.appendChild(tr);
});
}
setupDashboardListeners();
if (!isInitial) showTab('elg-tab-dashboard');
}
/**
* Attaches listeners to the dashboard table elements
*/
function setupDashboardListeners() {
const output = dashboardOutput;
if (!output) return;
// Event delegation for removal
output.addEventListener('click', (e) => {
if (e.target.dataset.action === 'remove-dash-item') {
const tr = e.target.closest('tr');
trials.splice(trials.findIndex(t => t.id === tr.dataset.id), 1);
// Rerender entirely to renumber and update state/config
renderDashboard(false);
updateConfigListDisplay();
}
});
// Event delegation for input/change updates
output.addEventListener('input', handleDashboardUpdate);
output.addEventListener('change', handleDashboardUpdate);
}
/**
* Handles updates made directly to the dashboard inputs/textareas
*/
function handleDashboardUpdate(e) {
const target = e.target;
const value = target.value;
// Update Config header fields
if (target.id === 'elg-dash-project') configProject.value = value;
else if (target.id === 'elg-dash-date') configDate.value = value;
else if (target.id === 'elg-dash-procedure-summary') configProcedureSummary.value = value;
// Update Trial State
const tr = target.closest('tr');
if (tr) {
const tId = tr.dataset.id;
const trial = trials.find(t => t.id === tId);
if (trial) {
const field = target.dataset.field;
if (field) {
trial[field] = value;
// Rerender config list immediately
updateConfigListDisplay();
}
}
}
}
/**
* Generates a PDF report from the dashboard data
*/
function downloadPDF() {
if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF.autoTable === 'undefined') {
alert("Error: PDF libraries could not be loaded. Please try again.");
return;
}
const projectTitle = configProject.value || "Experiment Log";
const date = configDate.value || "[Date]";
if (!projectTitle.trim()) {
alert("Please generate the log first.");
return;
}
const { jsPDF } = window.jspdf;
const doc = new jsPDF("l", "pt", "a4"); // Landscape for wide table
const margin = 40;
let yPos = margin;
const lineHeight = 16;
const usableWidth = doc.internal.pageSize.getWidth() - margin * 2;
// Function to add a section
function addSection(title, text) {
const titleHeight = 20;
const textLines = doc.splitTextToSize(text, usableWidth);
const textHeight = textLines.length * lineHeight;
if (yPos + titleHeight + textHeight > doc.internal.pageSize.getHeight() - margin) {
doc.addPage();
yPos = margin;
}
doc.setFontSize(14);
doc.setFont(undefined, 'bold');
doc.text(title, margin, yPos);
yPos += lineHeight * 1.5;
doc.setFontSize(10);
doc.setFont(undefined, 'normal');
doc.text(textLines, margin, yPos);
yPos += textHeight + lineHeight * 1.5;
}
// --- Document Title ---
doc.setFontSize(20);
doc.setFont(undefined, 'bold');
doc.text(`Experiment Log: ${projectTitle}`, doc.internal.pageSize.getWidth() / 2, yPos, { align: 'center' });
yPos += lineHeight * 1.5;
// --- Project Header ---
doc.setFontSize(11);
doc.setFont(undefined, 'normal');
doc.text(`Run Date: ${date}`, margin, yPos);
yPos += lineHeight * 2;
// --- 1. Procedure Summary ---
addSection("1. Procedure Overview", configProcedureSummary.value);
// --- 2. Trial Log ---
if (yPos > doc.internal.pageSize.getHeight() - 100) {
doc.addPage();
yPos = margin;
}
doc.setFontSize(14);
doc.setFont(undefined, 'bold');
doc.text("2. Trial Log", margin, yPos);
yPos += 20;
const tableHead = [["#", "Input / Variable", "Outcome / Result", "Observation / Notes"]];
const tableBody = trials.map((t, index) => [
index + 1,
t.input,
t.outcome,
t.observation
]);
doc.autoTable({
startY: yPos,
head: tableHead,
body: tableBody,
theme: 'striped',
headStyles: { fillColor: [0, 115, 230], textColor: [255, 255, 255], fontSize: 9 },
styles: { fontSize: 8, cellPadding: 4, overflow: 'linebreak' },
columnStyles: {
0: { cellWidth: 20, halign: 'center' },
1: { cellWidth: 150 },
2: { cellWidth: 150 },
3: { cellWidth: 'auto' }
},
margin: { left: margin, right: margin }
});
doc.save(`${projectTitle.replace(/ /g,"_")}_Log_${date}.pdf`);
}
/**
* Helper to escape HTML
*/
function escapeHTML(str) {
if (!str) return "";
return str
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
// --- 4. INITIALIZATION & EVENT LISTENERS ---
// Tab Listeners
tabButtons.forEach((btn) => {
btn.addEventListener("click", () => showTab(btn.dataset.target));
});
navButtons.forEach((btn) => {
btn.addEventListener("click", () => showTab(btn.dataset.target));
});
// Config Tab Listeners
if (addTrialForm) {
addTrialForm.addEventListener("submit", handleAddTrial);
}
if (configTrialList) {
configTrialList.addEventListener("click", handleRemoveConfigItem);
}
if (generateBtn) {
generateBtn.addEventListener("click", () => renderDashboard(false));
}
// PDF Button
if (pdfBtn) {
pdfBtn.addEventListener("click", downloadPDF);
}
// Set default date
configDate.valueAsDate = new Date();
// Initial config list display
updateConfigListDisplay();
// Initial State: Generate dashboard with samples
renderDashboard();
showTab("elg-tab-dashboard");
});