No observations logged yet.
";
}
appState.observations.forEach(obs => {
const item = document.createElement("div");
item.className = "ptl-observation-item";
item.innerHTML = `
Task: ${obs.task} (${obs.severity})
`;
observationsList.appendChild(item);
});
}
function renderDashboardTab() {
projectTitleDisplay.textContent = appState.projectName;
displayDate.textContent = appState.sessionDate;
displayTester.textContent = appState.tester;
totalObs.textContent = appState.observations.length;
const severityFilter = filterSeveritySelect.value;
const sortBy = sortBySelect.value;
let filteredAndSortedObs = appState.observations.filter(obs => {
return severityFilter === 'all' || obs.severity === severityFilter;
});
// Sorting Logic
if (sortBy === 'task') {
filteredAndSortedObs.sort((a, b) => a.task.localeCompare(b.task));
} else if (sortBy === 'severity') {
filteredAndSortedObs.sort((a, b) => severityMap[b.severity] - severityMap[a.severity]);
}
// Render Table
tableBody.innerHTML = "";
if (filteredAndSortedObs.length === 0) {
tableBody.innerHTML = "
| No observations match the current filter. |
";
return;
}
filteredAndSortedObs.forEach(obs => {
const tr = document.createElement("tr");
const severityClass = `tag-${obs.severity.toLowerCase().replace(' ', '')}`;
tr.innerHTML = `
${obs.task} |
${obs.severity} |
${obs.details} |
${obs.fix || 'N/A'} |
`;
tableBody.appendChild(tr);
});
}
// --- Event Handlers ---
// Update Dashboard Button
updateBtn.addEventListener("click", () => {
appState.projectName = configProject.value;
appState.sessionDate = configDate.value;
appState.tester = configTester.value;
saveState();
renderDashboardTab();
showTab(0);
});
// Add Observation
addObsBtn.addEventListener("click", () => {
const task = obsTaskInput.value.trim();
const details = obsDetailsInput.value.trim();
const severity = obsSeveritySelect.value;
const fix = obsFixInput.value.trim();
if (!task || !details) {
alert("Please enter the Task and Observation details.");
return;
}
const newObs = {
id: Date.now(),
task: task,
details: details,
severity: severity,
fix: fix
};
appState.observations.push(newObs);
saveState();
renderConfigTab();
// Clear form
obsTaskInput.value = "";
obsDetailsInput.value = "";
obsFixInput.value = "";
obsSeveritySelect.value = "Medium";
});
// Remove Observation (Event Delegation)
observationsList.addEventListener("click", (e) => {
if (e.target.classList.contains("ptl-remove-btn")) {
const id = parseInt(e.target.dataset.id);
appState.observations = appState.observations.filter(obs => obs.id !== id);
saveState();
renderConfigTab();
}
});
// Filters Change
filterSeveritySelect.addEventListener("change", renderDashboardTab);
sortBySelect.addEventListener("change", renderDashboardTab);
// PDF Download
pdfBtn.addEventListener("click", () => {
const { jsPDF } = window.jspdf;
const fileName = `${appState.projectName.replace(/ /g, '_')}_Test_Log.pdf`;
// Ensure filters are not visible in the PDF
const filtersDiv = toolContainer.querySelector('.ptl-filters');
const filtersDisplayStyle = filtersDiv.style.display;
filtersDiv.style.display = 'none';
html2canvas(exportArea, {
scale: 2,
useCORS: true,
backgroundColor: '#ffffff'
}).then(canvas => {
// Restore filters display
filtersDiv.style.display = filtersDisplayStyle;
const imgData = canvas.toDataURL('image/png');
const doc = new jsPDF({
orientation: 'p',
unit: 'pt',
format: 'a4'
});
const pdfWidth = doc.internal.pageSize.getWidth();
const pdfHeight = doc.internal.pageSize.getHeight();
const imgProps = doc.getImageProperties(imgData);
const imgWidth = imgProps.width;
const imgHeight = imgProps.height;
const margin = 40;
const usableWidth = pdfWidth - (2 * margin);
const ratio = usableWidth / imgWidth;
let scaledHeight = imgHeight * ratio;
let position = margin;
// Handle multi-page if needed
if (scaledHeight > pdfHeight - (2 * margin)) {
const pageHeight = pdfHeight - (2 * margin);
let heightLeft = scaledHeight;
while (heightLeft > 0) {
const pageImageHeight = Math.min(pageHeight / ratio, imgHeight - (scaledHeight - heightLeft) / ratio);
const imageOffsetY = (scaledHeight - heightLeft) / ratio;
const tempCanvas = document.createElement('canvas');
tempCanvas.width = imgWidth;
tempCanvas.height = pageImageHeight;
const tempCtx = tempCanvas.getContext('2d');
tempCtx.drawImage(canvas, 0, imageOffsetY, imgWidth, pageImageHeight, 0, 0, imgWidth, pageImageHeight);
const pageImgData = tempCanvas.toDataURL('image/png');
if (position !== margin) {
doc.addPage();
}
doc.addImage(pageImgData, 'PNG', margin, margin, usableWidth, pageHeight);
heightLeft -= pageHeight;
position += pageHeight;
}
} else {
// Single page
doc.addImage(imgData, 'PNG', margin, margin, usableWidth, scaledHeight);
}
doc.save(fileName);
}).catch(err => {
filtersDiv.style.display = filtersDisplayStyle; // Restore on error
console.error("PTL PDF Error:", err);
// alert("An error occurred while generating the PDF."); // Per spec
});
});
// --- Local Storage ---
function saveState() {
try {
localStorage.setItem("ptlAppState", JSON.stringify(appState));
} catch (e) { console.warn("PTL: Could not save state."); }
}
function loadState() {
try {
const storedState = localStorage.getItem("ptlAppState");
if (storedState) appState = JSON.parse(storedState);
} catch (e) { console.warn("PTL: Could not load state."); }
}
// --- Initial Load ---
loadState();
renderConfigTab();
renderDashboardTab();
showTab(0);
});