`;
};
// --- Data Extraction & Persistence ---
const updateStageData = (index) => {
const stageElement = contentArea.querySelector(`[data-stage-index="${index}"]`);
if (!stageElement) return;
// Ensure the data structure exists for this index
while (pipelineData.stages.length <= index) {
pipelineData.stages.push({
id: stageIdCounter++,
name: `Stage ${pipelineData.stages.length + 1}`,
prob: 0,
entry: '',
exit: ''
});
}
const stage = pipelineData.stages[index];
stage.name = stageElement.querySelector(`#spd-stage-name-${index}`).value;
stage.prob = parseFloat(stageElement.querySelector(`#spd-stage-prob-${index}`).value) || 0;
stage.entry = stageElement.querySelector(`#spd-stage-entry-${index}`).value;
stage.exit = stageElement.querySelector(`#spd-stage-exit-${index}`).value;
};
const getReportData = () => {
// Recalculate stages just in case user edited fields without clicking next/prev
if (currentTab > 1 && currentTab <= numStages + 1) {
updateStageData(currentTab - 2);
}
const rawDate = new Date().toISOString().substring(0, 10);
return {
name: pipelineData.name,
version: pipelineData.version,
date: rawDate,
stages: pipelineData.stages.slice(0, numStages).map((s, index) => ({
order: index + 1,
name: s.name,
prob: s.prob,
entry: s.entry,
exit: s.exit
}))
};
};
// --- Event Listener Attachments ---
const attachSetupListeners = () => {
const nameInput = toolRoot.querySelector('#spd-pipeline-name');
const versionInput = toolRoot.querySelector('#spd-pipeline-version');
const numStagesSelect = toolRoot.querySelector('#spd-num-stages');
if (nameInput) nameInput.addEventListener('input', (e) => { pipelineData.name = e.target.value; renderTabs(); });
if (versionInput) versionInput.addEventListener('input', (e) => { pipelineData.version = e.target.value; });
if (numStagesSelect) numStagesSelect.addEventListener('change', (e) => {
numStages = parseInt(e.target.value, 10);
// Ensure state array length matches new stage count before rendering tabs
while (pipelineData.stages.length < numStages) {
pipelineData.stages.push({
id: stageIdCounter++,
name: `Stage ${pipelineData.stages.length + 1}`,
prob: 0,
entry: 'TBD',
exit: 'TBD'
});
}
renderTabs();
});
};
const attachStageListeners = (index) => {
const stageElement = contentArea.querySelector(`[data-stage-index="${index}"]`);
if (!stageElement) return;
// Use 'blur' for textareas to minimize listener calls while preserving typing flow
stageElement.querySelectorAll('input, textarea').forEach(input => {
input.addEventListener('blur', () => updateStageData(index));
if (input.id.includes('name')) {
input.addEventListener('input', () => { updateStageData(index); renderTabs(); });
}
});
};
const attachSummaryListeners = () => {
const data = getReportData();
downloadPdfBtn.onclick = () => downloadPDF(data);
downloadTxtBtn.onclick = () => downloadTxt(data);
};
// --- Download Functions ---
const downloadTxt = (data) => {
let content = `SALES PIPELINE PROTOCOL: ${data.name.toUpperCase()}\n`;
content += `Version: ${data.version} | Date: ${data.date}\n`;
content += "========================================\n\n";
data.stages.forEach(stage => {
content += `STAGE ${stage.order}: ${stage.name.toUpperCase()} (${stage.prob}% PROBABILITY)\n`;
content += "----------------------------------------\n";
content += `ENTRY GATE: ${stage.entry}\n`;
content += `EXIT GATE: ${stage.exit}\n\n`;
});
const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = `pipeline_protocol_${data.name.replace(/ /g, '_')}.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(a.href);
};
const downloadPDF = (data) => {
if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') {
alert('Error: jsPDF library not loaded.');
return;
}
const { jsPDF } = window.jspdf;
const doc = new jsPDF('p', 'mm', 'a4');
const margin = 15;
const pageWidth = doc.internal.pageSize.getWidth();
const usableWidth = pageWidth - margin * 2;
let yPos = 20;
// --- Helper for wrapping and section titles
const addText = (text, size = 11, style = 'normal', color = [52, 73, 94]) => {
doc.setFontSize(size);
doc.setFont(undefined, style);
doc.setTextColor(color[0], color[1], color[2]);
const lines = doc.splitTextToSize(text, usableWidth);
if (lines.length * 6 + yPos > 280) { doc.addPage(); yPos = 20; }
doc.text(lines, margin, yPos);
yPos += (lines.length * 6) + 4;
};
const addSectionTitle = (text) => {
yPos += 5;
addText(text, 14, 'bold', [26, 115, 232]); // Blue
doc.setDrawColor(224, 224, 224);
doc.line(margin, yPos - 3, pageWidth - margin, yPos - 3);
yPos += 2;
};
// 1. Header
doc.setFontSize(18);
doc.setFont(undefined, 'bold');
doc.setTextColor(44, 62, 80);
doc.text(`Sales Pipeline Protocol: ${data.name}`, pageWidth / 2, yPos, { align: 'center' });
yPos += 8;
doc.setFontSize(10);
doc.setTextColor(108, 117, 125);
doc.text(`Version: ${data.version} | Date: ${data.date}`, pageWidth / 2, yPos, { align: 'center' });
yPos += 10;
// 2. Stages Overview Table
addSectionTitle("1. Pipeline Stages Overview");
const tableHead = [['#', 'Stage Name', 'Win Prob. (%)']];
const tableBody = data.stages.map(s => [s.order, s.name, `${s.prob}%`]);
doc.autoTable({
startY: yPos,
head: tableHead,
body: tableBody,
theme: 'grid',
styles: { fontSize: 10, cellPadding: 3, textColor: [52, 73, 94] },
headStyles: { fillColor: [26, 115, 232], textColor: [255, 255, 255] },
columnStyles: {
0: { cellWidth: 10 },
2: { cellWidth: 25, fontStyle: 'bold', halign: 'center' }
},
margin: { left: margin, right: margin }
});
yPos = doc.autoTable.previous.finalY + 10;
// 3. Detailed Stage Gates
addSectionTitle("2. Detailed Stage Gates");
data.stages.forEach(stage => {
addText(`Stage ${stage.order}: ${stage.name} (${stage.prob}%)`, 12, 'bold');
addText("ENTRY GATE:", 10, 'bold', 5);
addText(stage.entry, 10, 'normal', 5);
addText("EXIT GATE:", 10, 'bold', 5);
addText(stage.exit, 10, 'normal', 5);
yPos += 5; // Extra space between stages
});
doc.save(`pipeline_protocol_${data.name.replace(/ /g, '_')}.pdf`);
};
// --- Initialization ---
// Initial setup for the state array
while (pipelineData.stages.length < numStages) {
pipelineData.stages.push({
id: stageIdCounter++,
name: `Stage ${pipelineData.stages.length + 1}`,
prob: 0,
entry: 'TBD',
exit: 'TBD'
});
}
// Event Listeners for Navigation
nextBtn.addEventListener('click', () => showTab(currentTab + 1));
prevBtn.addEventListener('click', () => showTab(currentTab - 1));
// Initial render
showTab(1);
});