fix(client, editor) #11: improve maturity matrix reset and reload behavior

pull/8001/head
Tayeb Chlyah 3 weeks ago
parent dafb6ac7d4
commit 54db12c3d3

@ -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'
)
</template>
@ -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 () {

@ -210,6 +210,7 @@ select.cell-input { cursor: pointer; }
<div class="topbar-title">Use Case Maturity Assessment Matrix</div>
</div>
<div class="topbar-actions">
<button class="btn btn-secondary" onclick="resetToInitial()">↺ Reset</button>
<button class="btn btn-primary" onclick="saveToWiki()">💾 Save</button>
<button class="btn btn-secondary" onclick="exitToWiki()">✕ Close</button>
</div>
@ -255,9 +256,6 @@ select.cell-input { cursor: pointer; }
</div>
</div>
<div class="chart-row" id="charts-container">
</div>
<div id="sections-container"></div>
</div>
@ -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) {
</div>
</div>
<div id="body-${section.id}">
<div class="chart-card" style="margin:16px;">
<div class="chart-title">${section.name} KPIs</div>
<div style="display:flex;justify-content:center;overflow:auto;">
<canvas id="chart-${section.id}"></canvas>
</div>
</div>
<div class="table-wrap">
<table>
<thead>
@ -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 = `
<div class="chart-title">${section.name} KPIs</div>
<div style="display:flex;justify-content:center;overflow:auto;">
<canvas id="chart-${section.id}" width="${SIZE}" height="${SIZE}" style="width:${SIZE}px;height:${SIZE}px;"></canvas>
</div>
`;
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(`<font color="${color}">${k.score} (${sl})</font>`);
const weightCell = `${k.weight} (${wl})`;
const finalCell = wrap(`<font color="${color}"><b>${fs}</b></font>`);
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);
}

Loading…
Cancel
Save