diff --git a/client/components/editor/editor-modal-maturitymatrix.vue b/client/components/editor/editor-modal-maturitymatrix.vue
index 620c015e..e7615a70 100644
--- a/client/components/editor/editor-modal-maturitymatrix.vue
+++ b/client/components/editor/editor-modal-maturitymatrix.vue
@@ -2,7 +2,7 @@
v-card.editor-modal-maturitymatrix.animated.fadeIn(flat, tile)
iframe(
ref='matrix'
- src='/_assets/maturity-matrix/index.html'
+ :src='iframeSrc'
frameborder='0'
)
@@ -12,7 +12,10 @@ import { sync } from 'vuex-pathify'
export default {
computed: {
- activeModal: sync('editor/activeModal')
+ activeModal: sync('editor/activeModal'),
+ iframeSrc () {
+ return `/_assets/maturity-matrix/index.html?v=${Date.now()}`
+ }
},
methods: {
close () {
diff --git a/client/static/maturity-matrix/index.html b/client/static/maturity-matrix/index.html
index 887ffd6c..5950ee12 100644
--- a/client/static/maturity-matrix/index.html
+++ b/client/static/maturity-matrix/index.html
@@ -210,6 +210,7 @@ select.cell-input { cursor: pointer; }
Use Case Maturity Assessment Matrix
+
@@ -255,9 +256,6 @@ select.cell-input { cursor: pointer; }
-
-
-
@@ -389,7 +387,9 @@ let state = {
]
};
/* __STATE_END__ */
-let nextId = 300;
+const INITIAL_STATE_JSON = JSON.stringify(state);
+const INITIAL_NEXT_ID = 300;
+let nextId = INITIAL_NEXT_ID;
let modalTargetSection = null;
let sectionCharts = {};
@@ -471,6 +471,12 @@ function buildSectionEl(section) {
+
+
${section.name} KPIs
+
+
+
+
@@ -774,27 +780,20 @@ function radarOptions() {
}
function initCharts() {
- const container = document.getElementById('charts-container');
- container.innerHTML = '';
sectionCharts = {};
state.sections.forEach(section => {
- // Size the canvas based on KPI count so labels always have room
+ const canvas = document.getElementById(`chart-${section.id}`);
+ if (!canvas) return;
+
const kpiCount = section.kpis.filter(k => k.applicable === 'y').length;
const SIZE = Math.max(700, kpiCount * 62);
+ canvas.width = SIZE;
+ canvas.height = SIZE;
+ canvas.style.width = SIZE + 'px';
+ canvas.style.height = SIZE + 'px';
- const card = document.createElement('div');
- card.className = 'chart-card';
- // Wrapper keeps the fixed-size canvas centred inside the full-width card
- card.innerHTML = `
- ${section.name} KPIs
-
-
-
- `;
- container.appendChild(card);
-
- const ctx = document.getElementById(`chart-${section.id}`).getContext('2d');
+ const ctx = canvas.getContext('2d');
sectionCharts[section.id] = new Chart(ctx, {
type: 'radar',
data: {
@@ -882,6 +881,19 @@ function exitToWiki() {
}
}
+function resetToInitial() {
+ if (!confirm('Reset to the initial template? All current edits will be discarded.')) return;
+ state = JSON.parse(INITIAL_STATE_JSON);
+ nextId = INITIAL_NEXT_ID;
+ const cn = document.getElementById('customerName');
+ const uc = document.getElementById('useCaseName');
+ if (cn) cn.value = state.customer;
+ if (uc) uc.value = state.useCase;
+ render();
+ initCharts();
+ showToast('Reset to initial template', 'success');
+}
+
// ═══════════════════════════════════════════════════
// EXPORT MARKDOWN w/ VEGA RADAR CHARTS
// ═══════════════════════════════════════════════════
@@ -1166,21 +1178,42 @@ function buildMarkdown() {
md += `**Generated:** ${ts}\n\n`;
md += `---\n\n`;
md += `## Summary Scores\n\n`;
- md += `| Section | Score | Status |\n`;
- md += `|---------|-------|--------|\n`;
+ md += `\`\`\`infographic height=300\n`;
+ md += `infographic list-grid-progress-card\n`;
+ md += `data\n`;
+ md += ` lists\n`;
+
+ const SECTION_ICONS = {
+ 'Performance': 'speedometer',
+ 'Availability': 'shield check',
+ 'Excellence': 'star'
+ };
+ const palette = [];
+ let totalFinal = 0, totalMax = 0;
state.sections.forEach(section => {
const { finalScore, maxScore, ratio } = calcSection(section);
+ totalFinal += finalScore;
+ totalMax += maxScore;
const pct = (ratio * 100).toFixed(1);
- const status = statusForRatio(ratio);
- const statusEmoji = status === 'red' ? '🔴' : status === 'amber' ? '🟡' : '🟢';
- md += `| ${section.name} | ${pct}% (${finalScore}/${maxScore}) | ${statusEmoji} **${status.toUpperCase()}** |\n`;
+ const icon = SECTION_ICONS[section.name] || 'chart line';
+ md += ` - label ${section.name}\n`;
+ md += ` value ${pct}\n`;
+ md += ` desc ${finalScore} / ${maxScore} pts\n`;
+ md += ` icon ${icon}\n`;
+ palette.push(section.color || '#6B7280');
});
const overall = calcOverall();
- const overallStatus = statusForRatio(overall);
- const overallEmoji = overallStatus === 'red' ? '🔴' : overallStatus === 'amber' ? '🟡' : '🟢';
- md += `| **Overall** | **${(overall*100).toFixed(1)}%** | ${overallEmoji} **${overallStatus.toUpperCase()}** |\n\n`;
+ md += ` - label Overall\n`;
+ md += ` value ${(overall * 100).toFixed(1)}\n`;
+ md += ` desc ${totalFinal} / ${totalMax} pts\n`;
+ md += ` icon trophy\n`;
+ palette.push('#C0392B');
+
+ md += `theme\n`;
+ md += ` palette ${palette.join(' ')}\n`;
+ md += `\`\`\`\n\n`;
state.sections.forEach(section => {
const { finalScore, maxScore, ratio } = calcSection(section);
@@ -1194,12 +1227,24 @@ function buildMarkdown() {
md += `| KPI | Score | Weight | Final | Max | Applicable | Notes | Action Plan |\n`;
md += `|-----|-------|--------|-------|-----|------------|-------|-------------|\n`;
+ const SCORE_COLOR = ['red', 'orange', 'green'];
section.kpis.forEach(k => {
- const fs = k.applicable==='y' ? k.score*k.weight : 0;
- const ms = k.applicable==='y' ? 2*k.weight : 0;
+ const applicable = k.applicable === 'y';
+ const fs = applicable ? k.score * k.weight : 0;
+ const ms = applicable ? 2 * k.weight : 0;
const sl = ['Not Started','In Progress','Complete'][k.score];
const wl = ['','Low','Medium','High'][k.weight];
- md += `| ${k.kpi} | ${k.score} (${sl}) | ${k.weight} (${wl}) | ${fs} | ${ms} | ${k.applicable.toUpperCase()} | ${k.notes||'—'} | ${k.action||'—'} |\n`;
+ const color = SCORE_COLOR[k.score];
+ const wrap = (s) => applicable ? s : `~~${s}~~`;
+ const scoreCell = wrap(`${k.score} (${sl})`);
+ const weightCell = `${k.weight} (${wl})`;
+ const finalCell = wrap(`${fs}`);
+ const maxCell = wrap(`${ms}`);
+ const kpiCell = wrap(`**${k.kpi}**`);
+ const notesCell = wrap(k.notes || '—');
+ const actionCell = wrap(k.action || '—');
+ const appCell = applicable ? '✅' : '❌';
+ md += `| ${kpiCell} | ${scoreCell} | ${weightCell} | ${finalCell} | ${maxCell} | ${appCell} | ${notesCell} | ${actionCell} |\n`;
});
md += `\n`;
});
@@ -1227,9 +1272,23 @@ function parseMarkdown(markdownString) {
let maxKpiId = 0;
function parseScoreCell(cell) {
- const m = cell.match(/^\s*(\d+)/);
+ // Cell may be prefixed with emoji (🔴/🟡/🟢/⚪/⬇️/➡️/⬆️) or wrapped in markdown emphasis.
+ const m = cell.match(/(\d+)/);
return m ? parseInt(m[1], 10) : 0;
}
+ function stripEmphasis(cell) {
+ return cell
+ .replace(/^~~(.+)~~$/, '$1')
+ .replace(/^\*\*(.+)\*\*$/, '$1')
+ .replace(/^_(.+)_$/, '$1')
+ .trim();
+ }
+ function isApplicable(cell) {
+ const t = cell.trim();
+ if (t.includes('✅')) return true;
+ if (t.includes('❌')) return false;
+ return t.toLowerCase().startsWith('y');
+ }
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
@@ -1292,14 +1351,16 @@ function parseMarkdown(markdownString) {
const [kpiName, scoreCell, weightCell, finalCell, maxCell, applicableCell, notesCell, actionCell] = cells;
const id = ++nextId;
if (id > maxKpiId) maxKpiId = id;
+ const notesStripped = stripEmphasis(notesCell);
+ const actionStripped = stripEmphasis(actionCell);
const kpi = {
id,
- kpi: kpiName,
+ kpi: stripEmphasis(kpiName),
score: parseScoreCell(scoreCell),
weight: parseScoreCell(weightCell),
- applicable: applicableCell.toLowerCase().startsWith('y') ? 'y' : 'n',
- notes: notesCell === '—' ? '' : notesCell,
- action: actionCell === '—' ? '' : actionCell
+ applicable: isApplicable(applicableCell) ? 'y' : 'n',
+ notes: notesStripped === '—' ? '' : notesStripped,
+ action: actionStripped === '—' ? '' : actionStripped
};
currentSection.kpis.push(kpi);
}