No stakeholders added yet.
";
}
appState.stakeholders.forEach(sh => {
const item = document.createElement("div");
item.className = "psa-stakeholder-item";
item.innerHTML = `
${sh.name} (${sh.role})
`;
stakeholdersList.appendChild(item);
});
}
function renderDashboardTab() {
projectTitleDisplay.textContent = appState.projectName;
// Clear previous dots & table rows
matrixContainer.querySelectorAll('.psa-stakeholder-dot').forEach(dot => dot.remove());
tableBody.innerHTML = "";
if (appState.stakeholders.length === 0) {
tableBody.innerHTML = "
| No stakeholders configured. |
";
return;
}
const levelToValue = { low: 1, medium: 2, high: 3 };
const valueToPercent = { 1: 25, 2: 50, 3: 75 }; // Position within quadrant
appState.stakeholders.forEach(sh => {
// 1. Render Matrix Dot
const influenceVal = levelToValue[sh.influence] || 1;
const interestVal = levelToValue[sh.interest] || 1;
// Y position (Influence): High influence = low Y value (top of div)
const yPercent = 100 - valueToPercent[influenceVal];
// X position (Interest): High interest = high X value (right of div)
const xPercent = valueToPercent[interestVal];
const dot = document.createElement("div");
dot.className = `psa-stakeholder-dot psa-dot-${sh.influence}`; // Color by influence
dot.style.top = `${yPercent}%`;
dot.style.left = `${xPercent}%`;
dot.innerHTML = `
${sh.name}`;
matrixContainer.appendChild(dot);
// 2. Render Table Row
const tr = document.createElement("tr");
tr.innerHTML = `
${sh.name} |
${sh.role} |
${sh.interest.charAt(0).toUpperCase() + sh.interest.slice(1)} |
${sh.influence.charAt(0).toUpperCase() + sh.influence.slice(1)} |
${sh.strategy} |
`;
tableBody.appendChild(tr);
});
}
// --- Event Handlers ---
// Update Dashboard Button
updateBtn.addEventListener("click", () => {
appState.projectName = projectNameInput.value; // Update project name
saveState();
renderDashboardTab();
showTab(0);
});
// Add Stakeholder
addShBtn.addEventListener("click", () => {
const name = shNameInput.value.trim();
const role = shRoleInput.value.trim();
const interest = shInterestSelect.value;
const influence = shInfluenceSelect.value;
const strategy = shStrategyInput.value.trim();
if (!name || !role) {
alert("Please enter both stakeholder name and role.");
return;
}
const newStakeholder = {
id: Date.now(),
name: name,
role: role,
interest: interest,
influence: influence,
strategy: strategy
};
appState.stakeholders.push(newStakeholder);
saveState();
renderConfigTab();
// Clear form
shNameInput.value = "";
shRoleInput.value = "";
shInterestSelect.value = "medium";
shInfluenceSelect.value = "high";
shStrategyInput.value = "";
});
// Remove Stakeholder (Event Delegation)
stakeholdersList.addEventListener("click", (e) => {
if (e.target.classList.contains("psa-remove-btn")) {
const id = parseInt(e.target.dataset.id);
appState.stakeholders = appState.stakeholders.filter(s => s.id !== id);
saveState();
renderConfigTab();
}
});
// PDF Download
pdfBtn.addEventListener("click", () => {
const { jsPDF } = window.jspdf;
const fileName = `${appState.projectName.replace(/ /g, '_')}_Stakeholder_Analysis.pdf`;
// Temporarily adjust styles for better PDF layout if needed
const matrixOriginalHeight = matrixContainer.style.height;
matrixContainer.style.height = 'auto'; // Let it expand if needed for PDF capture
html2canvas(exportArea, {
scale: 2,
useCORS: true,
backgroundColor: '#ffffff',
// Attempt to capture full height if content overflows default viewport capture
windowHeight: exportArea.scrollHeight
}).then(canvas => {
matrixContainer.style.height = matrixOriginalHeight; // Restore height
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;
// Check if content exceeds page height
if (scaledHeight > pdfHeight - (2 * margin)) {
// Handle multi-page
const pageHeight = pdfHeight - (2 * margin);
let heightLeft = scaledHeight;
doc.addImage(imgData, 'PNG', margin, position, usableWidth, scaledHeight);
heightLeft -= pageHeight;
while (heightLeft > 0) {
position = margin - heightLeft; // Adjust position for the next page
doc.addPage();
doc.addImage(imgData, 'PNG', margin, position, usableWidth, scaledHeight);
heightLeft -= pageHeight;
}
} else {
// Single page
doc.addImage(imgData, 'PNG', margin, margin, usableWidth, scaledHeight);
}
doc.save(fileName);
}).catch(err => {
matrixContainer.style.height = matrixOriginalHeight; // Restore height on error too
console.error("PSA PDF Error:", err);
// alert("An error occurred while generating the PDF."); // Per spec
});
});
// --- Local Storage ---
function saveState() {
try {
localStorage.setItem("psaAppState", JSON.stringify(appState));
} catch (e) { console.warn("PSA: Could not save state."); }
}
function loadState() {
try {
const storedState = localStorage.getItem("psaAppState");
if (storedState) appState = JSON.parse(storedState);
} catch (e) { console.warn("PSA: Could not load state."); }
}
// --- Initial Load ---
loadState();
renderConfigTab(); // Load state into config first
renderDashboardTab(); // Then render dashboard
showTab(0); // Start on Dashboard
});