No requirements added yet.
";
}
appState.requirements.forEach(req => {
const item = document.createElement("div");
item.className = "rtm-req-item";
item.innerHTML = `
${req.reqId}: ${req.desc.substring(0, 50)}...
`;
reqList.appendChild(item);
});
}
function renderDashboardTab() {
projectTitleDisplay.textContent = appState.projectName;
// Populate Source Filter
const uniqueSources = getUniqueSources();
const currentSourceFilter = filterSourceSelect.value;
filterSourceSelect.innerHTML = '
';
uniqueSources.forEach(source => {
const option = document.createElement('option');
option.value = source;
option.textContent = source;
filterSourceSelect.appendChild(option);
});
filterSourceSelect.value = currentSourceFilter;
// Filter props based on selections
const sourceFilter = filterSourceSelect.value;
const statusFilter = filterStatusSelect.value;
const filteredReqs = appState.requirements.filter(req => {
const sourceMatch = sourceFilter === 'all' || req.source === sourceFilter;
const statusMatch = statusFilter === 'all' || req.status === statusFilter;
return sourceMatch && statusMatch;
});
// Render Table
tableBody.innerHTML = "";
if (filteredReqs.length === 0) {
tableBody.innerHTML = "
| No requirements match the current filters. |
";
return;
}
filteredReqs.forEach(req => {
const tr = document.createElement("tr");
let statusClass = '';
if (req.status === 'Verified') { statusClass = 'rtm-verified'; }
else if (req.status === 'Unverified') { statusClass = 'rtm-unverified'; }
tr.innerHTML = `
${req.reqId} |
${req.desc} |
${req.source} |
${req.verifIds} |
${req.status} |
`;
tableBody.appendChild(tr);
});
}
// --- Event Handlers ---
// Update Dashboard Button
updateBtn.addEventListener("click", () => {
appState.projectName = projectNameInput.value || "Untitled Project";
saveState();
renderDashboardTab();
showTab(0);
});
// Add Requirement
addReqBtn.addEventListener("click", () => {
const reqId = reqIdInput.value.trim();
const desc = reqDescInput.value.trim();
const source = sourceIdInput.value.trim();
const verifIds = verifIdsInput.value.trim();
const status = verifStatusSelect.value;
if (!reqId || !desc || !source) {
alert("Please enter a Req ID, Description, and Source ID.");
return;
}
const newReq = {
id: Date.now(),
reqId: reqId,
desc: desc,
source: source,
verifIds: verifIds,
status: status
};
appState.requirements.push(newReq);
saveState();
renderConfigTab();
// Clear form
reqIdInput.value = "";
reqDescInput.value = "";
sourceIdInput.value = "";
verifIdsInput.value = "";
verifStatusSelect.value = "Unverified";
});
// Remove Requirement (Event Delegation)
reqList.addEventListener("click", (e) => {
if (e.target.classList.contains("rtm-remove-btn")) {
const id = parseInt(e.target.dataset.id);
appState.requirements = appState.requirements.filter(req => req.id !== id);
saveState();
renderConfigTab();
}
});
// Filters Change
filterSourceSelect.addEventListener("change", renderDashboardTab);
filterStatusSelect.addEventListener("change", renderDashboardTab);
// PDF Download
pdfBtn.addEventListener("click", () => {
const { jsPDF } = window.jspdf;
const fileName = `${appState.projectName.replace(/ /g, '_')}_RTM.pdf`;
// Temporarily adjust minimum table width for PDF quality
const table = document.getElementById('rtm-matrix-table');
const originalMinWidth = table.style.minWidth;
table.style.minWidth = '1100px';
// Hide filters for PDF
const filtersDiv = toolContainer.querySelector('.rtm-filters');
const filtersDisplayStyle = filtersDiv.style.display;
filtersDiv.style.display = 'none';
html2canvas(exportArea, {
scale: 2,
useCORS: true,
backgroundColor: '#ffffff'
}).then(canvas => {
// Restore styles
table.style.minWidth = originalMinWidth;
filtersDiv.style.display = filtersDisplayStyle;
const imgData = canvas.toDataURL('image/png');
const doc = new jsPDF({
orientation: 'l', // Landscape is better for wide tables
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 = 30;
const usableWidth = pdfWidth - (2 * margin);
const ratio = usableWidth / imgWidth;
let scaledHeight = imgHeight * ratio;
// Handle multi-page if content exceeds page height
if (scaledHeight > pdfHeight - (2 * margin)) {
const pageHeight = pdfHeight - (2 * margin);
let heightLeft = scaledHeight;
let position = 0;
while (heightLeft > 0) {
doc.addImage(imgData, 'PNG', margin, position + margin, usableWidth, scaledHeight);
heightLeft -= pageHeight;
position -= pageHeight;
if (heightLeft > 0) {
doc.addPage();
}
}
} else {
// Single page
doc.addImage(imgData, 'PNG', margin, margin, usableWidth, scaledHeight);
}
doc.save(fileName);
}).catch(err => {
// Restore styles even if PDF fails
table.style.minWidth = originalMinWidth;
filtersDiv.style.display = filtersDisplayStyle;
console.error("RTM PDF Error:", err);
// alert("An error occurred while generating the PDF."); // Per spec
});
});
// --- Local Storage ---
function saveState() {
try {
localStorage.setItem("rtmAppState", JSON.stringify(appState));
} catch (e) { console.warn("RTM: Could not save state."); }
}
function loadState() {
try {
const storedState = localStorage.getItem("rtmAppState");
if (storedState) appState = JSON.parse(storedState);
} catch (e) { console.warn("RTM: Could not load state."); }
}
// --- Initial Load ---
loadState();
renderConfigTab();
renderDashboardTab();
showTab(0);
});