@atlashub/smartstack-cli 3.20.0 → 3.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +53 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-entry.mjs +1 -0
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -1
- package/templates/project/api.ts.template +8 -29
- package/templates/project/appsettings.json.template +1 -0
- package/templates/skills/business-analyse/html/ba-interactive.html +562 -150
- package/templates/skills/business-analyse/html/src/scripts/01-data-init.js +11 -6
- package/templates/skills/business-analyse/html/src/scripts/02-navigation.js +209 -4
- package/templates/skills/business-analyse/html/src/scripts/04-render-modules.js +2 -8
- package/templates/skills/business-analyse/html/src/scripts/05-render-specs.js +57 -2
- package/templates/skills/business-analyse/html/src/scripts/07-render-handoff.js +3 -1
- package/templates/skills/business-analyse/html/src/scripts/08-editing.js +112 -22
- package/templates/skills/business-analyse/html/src/scripts/11-review-panel.js +7 -0
- package/templates/skills/business-analyse/html/src/styles/02-layout.css +1 -1
- package/templates/skills/business-analyse/html/src/styles/03-navigation.css +89 -31
- package/templates/skills/business-analyse/html/src/styles/05-modules.css +64 -0
- package/templates/skills/business-analyse/html/src/template.html +8 -76
- package/templates/skills/business-analyse/references/deploy-data-build.md +9 -7
- package/templates/skills/business-analyse/references/html-data-mapping.md +20 -28
- package/templates/skills/business-analyse/references/validate-incremental-html.md +2 -1
- package/templates/skills/business-analyse/steps/step-03c-compile.md +55 -2
- package/templates/skills/business-analyse/steps/step-03d-validate.md +82 -15
- package/templates/skills/business-analyse/steps/step-05a-handoff.md +77 -3
- package/templates/skills/business-analyse/steps/step-05b-deploy.md +27 -0
|
@@ -47,6 +47,14 @@ data.cadrage.scope = data.cadrage.scope || { vital: [], important: [], optional:
|
|
|
47
47
|
|
|
48
48
|
// Defensive init: moduleSpecs (may be missing if LLM didn't follow mapping)
|
|
49
49
|
data.moduleSpecs = data.moduleSpecs || {};
|
|
50
|
+
// Ensure ALL modules have a moduleSpecs entry (prevents missing schemas)
|
|
51
|
+
(data.modules || []).forEach(function(m) {
|
|
52
|
+
if (!data.moduleSpecs[m.code]) {
|
|
53
|
+
data.moduleSpecs[m.code] = { useCases: [], businessRules: [], entities: [], permissions: [], notes: '' };
|
|
54
|
+
}
|
|
55
|
+
// Ensure anticipatedSections array exists
|
|
56
|
+
m.anticipatedSections = m.anticipatedSections || [];
|
|
57
|
+
});
|
|
50
58
|
|
|
51
59
|
// Vibe coding mode: hide non-relevant sections
|
|
52
60
|
const isVibeCoding = data.metadata?.vibeCoding === true;
|
|
@@ -87,11 +95,7 @@ function setNestedValue(obj, path, value) {
|
|
|
87
95
|
}
|
|
88
96
|
|
|
89
97
|
function updateCounts() {
|
|
90
|
-
|
|
91
|
-
document.getElementById('moduleCount').textContent = data.modules.length;
|
|
92
|
-
const totalEntities = data.modules.reduce((sum, m) => sum + (data.moduleSpecs[m.code]?.entities || []).length, 0);
|
|
93
|
-
document.getElementById('entityCount').textContent = totalEntities;
|
|
94
|
-
updateModulesNav();
|
|
98
|
+
buildNavTree();
|
|
95
99
|
updateDepSelects();
|
|
96
100
|
}
|
|
97
101
|
|
|
@@ -139,7 +143,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
139
143
|
renderConsolidation();
|
|
140
144
|
renderHandoff();
|
|
141
145
|
renderE2EFlows();
|
|
142
|
-
|
|
146
|
+
buildNavTree();
|
|
147
|
+
updateDepSelects();
|
|
143
148
|
initInlineComments();
|
|
144
149
|
renderReviewPanel();
|
|
145
150
|
});
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/* ============================================
|
|
2
|
-
NAVIGATION
|
|
2
|
+
NAVIGATION - Hierarchical Tree
|
|
3
3
|
============================================ */
|
|
4
4
|
let currentSectionId = 'cadrage-context';
|
|
5
|
+
let navCollapseState = {};
|
|
6
|
+
|
|
7
|
+
/* ---------- Core ---------- */
|
|
5
8
|
|
|
6
9
|
function showSection(sectionId) {
|
|
7
10
|
currentSectionId = sectionId;
|
|
@@ -9,9 +12,30 @@ function showSection(sectionId) {
|
|
|
9
12
|
const section = document.getElementById(sectionId);
|
|
10
13
|
if (section) section.style.display = 'block';
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
// Highlight active nav item
|
|
16
|
+
document.querySelectorAll('#sidebarNav .nav-item').forEach(n => n.classList.remove('active'));
|
|
17
|
+
const navItem = document.querySelector('#sidebarNav [data-section="' + sectionId + '"]');
|
|
18
|
+
if (navItem) {
|
|
19
|
+
navItem.classList.add('active');
|
|
20
|
+
// Auto-expand parent groups to reveal active item
|
|
21
|
+
let parent = navItem.parentElement;
|
|
22
|
+
while (parent && parent.id !== 'sidebarNav') {
|
|
23
|
+
if (parent.classList.contains('nav-children') && parent.style.display === 'none') {
|
|
24
|
+
parent.style.display = '';
|
|
25
|
+
const groupEl = parent.parentElement;
|
|
26
|
+
if (groupEl) {
|
|
27
|
+
const groupId = groupEl.dataset.groupId;
|
|
28
|
+
if (groupId) {
|
|
29
|
+
navCollapseState[groupId] = false;
|
|
30
|
+
const chevron = groupEl.querySelector(':scope > .nav-group-title .nav-chevron, :scope > .nav-item.nav-module-header .nav-chevron');
|
|
31
|
+
if (chevron) chevron.classList.add('expanded');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
parent = parent.parentElement;
|
|
36
|
+
}
|
|
37
|
+
saveNavState();
|
|
38
|
+
}
|
|
15
39
|
}
|
|
16
40
|
|
|
17
41
|
function restoreCurrentSection() {
|
|
@@ -20,3 +44,184 @@ function restoreCurrentSection() {
|
|
|
20
44
|
if (section) section.style.display = 'block';
|
|
21
45
|
}
|
|
22
46
|
}
|
|
47
|
+
|
|
48
|
+
/* ---------- Tree Builder ---------- */
|
|
49
|
+
|
|
50
|
+
function buildNavTree() {
|
|
51
|
+
const nav = document.getElementById('sidebarNav');
|
|
52
|
+
if (!nav) return;
|
|
53
|
+
|
|
54
|
+
nav.innerHTML =
|
|
55
|
+
renderNavGroup('cadrage', 'Cadrage', buildCadrageItems()) +
|
|
56
|
+
renderNavGroup('modules', 'Modules (' + data.modules.length + ')', buildModuleItems()) +
|
|
57
|
+
renderNavGroup('consolidation', 'Consolidation', buildConsolidationItems()) +
|
|
58
|
+
renderNavGroup('synthese', 'Synthese', buildSyntheseItems());
|
|
59
|
+
|
|
60
|
+
restoreNavState();
|
|
61
|
+
highlightActiveNavItem();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function renderNavGroup(id, title, itemsHtml) {
|
|
65
|
+
const collapsed = navCollapseState[id] === true;
|
|
66
|
+
return '<div class="nav-group" data-group-id="' + id + '">' +
|
|
67
|
+
'<div class="nav-group-title" onclick="toggleNavGroup(\'' + id + '\')">' +
|
|
68
|
+
'<span class="nav-chevron ' + (collapsed ? '' : 'expanded') + '">▸</span> ' +
|
|
69
|
+
title +
|
|
70
|
+
'</div>' +
|
|
71
|
+
'<div class="nav-children"' + (collapsed ? ' style="display:none;"' : '') + '>' +
|
|
72
|
+
itemsHtml +
|
|
73
|
+
'</div>' +
|
|
74
|
+
'</div>';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function renderNavItem(sectionId, label, badge) {
|
|
78
|
+
return '<a class="nav-item" onclick="showSection(\'' + sectionId + '\')" data-section="' + sectionId + '">' +
|
|
79
|
+
'<span class="nav-icon">●</span> ' + label +
|
|
80
|
+
(badge !== undefined ? ' <span class="nav-badge">' + badge + '</span>' : '') +
|
|
81
|
+
'</a>';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* ---------- Group Builders ---------- */
|
|
85
|
+
|
|
86
|
+
function buildCadrageItems() {
|
|
87
|
+
return renderNavItem('cadrage-context', 'Contexte') +
|
|
88
|
+
renderNavItem('cadrage-stakeholders', 'Parties prenantes', data.cadrage.stakeholders.length) +
|
|
89
|
+
renderNavItem('cadrage-scope', 'Perimetre fonctionnel') +
|
|
90
|
+
renderNavItem('cadrage-risks', 'Risques et hypotheses', data.cadrage.risks.length) +
|
|
91
|
+
renderNavItem('cadrage-success', 'Criteres de reussite');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function buildModuleItems() {
|
|
95
|
+
let html = '';
|
|
96
|
+
data.modules.forEach(function(mod) {
|
|
97
|
+
html += renderModuleNavItem(mod);
|
|
98
|
+
});
|
|
99
|
+
// Global module views at bottom
|
|
100
|
+
html += renderNavItem('decomp-modules', 'Vue d\'ensemble', data.modules.length);
|
|
101
|
+
html += renderNavItem('decomp-dependencies', 'Dependances', data.dependencies.length);
|
|
102
|
+
return html;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function buildConsolidationItems() {
|
|
106
|
+
var totalEntities = data.modules.reduce(function(sum, m) {
|
|
107
|
+
return sum + (data.moduleSpecs[m.code]?.entities || []).length;
|
|
108
|
+
}, 0);
|
|
109
|
+
return renderNavItem('consol-datamodel', 'Modele de donnees', totalEntities) +
|
|
110
|
+
renderNavItem('consol-interactions', 'Interactions') +
|
|
111
|
+
renderNavItem('consol-permissions', 'Coherence des acces') +
|
|
112
|
+
renderNavItem('consol-flows', 'Parcours bout en bout', data.consolidation.e2eFlows.length);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function buildSyntheseItems() {
|
|
116
|
+
return renderNavItem('handoff-summary', 'Vue d\'ensemble');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* ---------- Module Sub-Tree ---------- */
|
|
120
|
+
|
|
121
|
+
function renderModuleNavItem(mod) {
|
|
122
|
+
var code = mod.code;
|
|
123
|
+
var spec = data.moduleSpecs[code] || {};
|
|
124
|
+
var ucCount = (spec.useCases || []).length;
|
|
125
|
+
var brCount = (spec.businessRules || []).length;
|
|
126
|
+
var entCount = (spec.entities || []).length;
|
|
127
|
+
var sections = mod.anticipatedSections || [];
|
|
128
|
+
var groupId = 'mod-' + code;
|
|
129
|
+
var collapsed = navCollapseState[groupId] === true;
|
|
130
|
+
|
|
131
|
+
var html = '<div class="nav-module" data-group-id="' + groupId + '">';
|
|
132
|
+
|
|
133
|
+
// Module header (clickable to expand + navigate to module spec)
|
|
134
|
+
html += '<a class="nav-item nav-module-header" onclick="toggleNavGroup(\'' + groupId + '\');showSection(\'module-spec-' + code + '\')" data-section="module-spec-' + code + '">';
|
|
135
|
+
html += '<span class="nav-chevron ' + (collapsed ? '' : 'expanded') + '">▸</span> ';
|
|
136
|
+
html += (mod.name || mod.code);
|
|
137
|
+
html += '</a>';
|
|
138
|
+
|
|
139
|
+
// Children: tabs
|
|
140
|
+
html += '<div class="nav-children"' + (collapsed ? ' style="display:none;"' : '') + '>';
|
|
141
|
+
html += renderModuleTabNavItem(code, 'uc', 'Cas d\'utilisation', ucCount);
|
|
142
|
+
html += renderModuleTabNavItem(code, 'br', 'Regles metier', brCount);
|
|
143
|
+
html += renderModuleTabNavItem(code, 'ent', 'Donnees', entCount);
|
|
144
|
+
html += renderModuleTabNavItem(code, 'perm', 'Droits d\'acces');
|
|
145
|
+
html += renderModuleTabNavItem(code, 'mock', 'Maquettes');
|
|
146
|
+
html += renderModuleTabNavItem(code, 'struct', 'Structure', sections.length);
|
|
147
|
+
|
|
148
|
+
// Children: sections/resources (deeper tree)
|
|
149
|
+
if (sections.length > 0) {
|
|
150
|
+
sections.forEach(function(section) {
|
|
151
|
+
var resources = section.resources || [];
|
|
152
|
+
html += '<div class="nav-section-item">';
|
|
153
|
+
html += '<a class="nav-item nav-section-link" onclick="showSection(\'module-spec-' + code + '\');switchTab(\'' + code + '\',\'struct\')" data-section="module-struct-' + code + '-' + section.code + '">';
|
|
154
|
+
html += '<span class="nav-icon nav-icon-section">▷</span> ' + section.code;
|
|
155
|
+
if (resources.length > 0) html += ' <span class="nav-badge">' + resources.length + '</span>';
|
|
156
|
+
html += '</a>';
|
|
157
|
+
if (resources.length > 0) {
|
|
158
|
+
html += '<div class="nav-children nav-resources">';
|
|
159
|
+
resources.forEach(function(res) {
|
|
160
|
+
var resName = typeof res === 'string' ? res : (res.code || res.name || '');
|
|
161
|
+
html += '<a class="nav-item nav-resource-link">';
|
|
162
|
+
html += '<span class="nav-icon nav-icon-resource">•</span> ' + resName;
|
|
163
|
+
html += '</a>';
|
|
164
|
+
});
|
|
165
|
+
html += '</div>';
|
|
166
|
+
}
|
|
167
|
+
html += '</div>';
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
html += '</div>'; // nav-children
|
|
172
|
+
html += '</div>'; // nav-module
|
|
173
|
+
return html;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function renderModuleTabNavItem(code, tabId, label, badge) {
|
|
177
|
+
var sectionId = 'module-spec-' + code;
|
|
178
|
+
return '<a class="nav-item" onclick="showSection(\'' + sectionId + '\');switchTab(\'' + code + '\',\'' + tabId + '\')" data-section="' + sectionId + '-' + tabId + '">' +
|
|
179
|
+
'<span class="nav-icon">●</span> ' + label +
|
|
180
|
+
(badge !== undefined ? ' <span class="nav-badge">' + badge + '</span>' : '') +
|
|
181
|
+
'</a>';
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/* ---------- Collapse/Expand ---------- */
|
|
185
|
+
|
|
186
|
+
function toggleNavGroup(groupId) {
|
|
187
|
+
navCollapseState[groupId] = !navCollapseState[groupId];
|
|
188
|
+
var groupEl = document.querySelector('[data-group-id="' + groupId + '"]');
|
|
189
|
+
if (groupEl) {
|
|
190
|
+
var children = groupEl.querySelector(':scope > .nav-children');
|
|
191
|
+
var chevron = groupEl.querySelector(':scope > .nav-group-title .nav-chevron, :scope > .nav-item.nav-module-header .nav-chevron');
|
|
192
|
+
if (children) children.style.display = navCollapseState[groupId] ? 'none' : '';
|
|
193
|
+
if (chevron) chevron.classList.toggle('expanded', !navCollapseState[groupId]);
|
|
194
|
+
}
|
|
195
|
+
saveNavState();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function saveNavState() {
|
|
199
|
+
try { localStorage.setItem(APP_KEY + '-nav', JSON.stringify(navCollapseState)); } catch(e) {}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function restoreNavState() {
|
|
203
|
+
try {
|
|
204
|
+
var saved = localStorage.getItem(APP_KEY + '-nav');
|
|
205
|
+
if (saved) {
|
|
206
|
+
navCollapseState = JSON.parse(saved);
|
|
207
|
+
// Apply collapse state to all groups
|
|
208
|
+
Object.keys(navCollapseState).forEach(function(groupId) {
|
|
209
|
+
if (navCollapseState[groupId]) {
|
|
210
|
+
var groupEl = document.querySelector('[data-group-id="' + groupId + '"]');
|
|
211
|
+
if (groupEl) {
|
|
212
|
+
var children = groupEl.querySelector(':scope > .nav-children');
|
|
213
|
+
var chevron = groupEl.querySelector(':scope > .nav-group-title .nav-chevron, :scope > .nav-item.nav-module-header .nav-chevron');
|
|
214
|
+
if (children) children.style.display = 'none';
|
|
215
|
+
if (chevron) chevron.classList.remove('expanded');
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
} catch(e) {}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function highlightActiveNavItem() {
|
|
224
|
+
if (!currentSectionId) return;
|
|
225
|
+
var navItem = document.querySelector('#sidebarNav [data-section="' + currentSectionId + '"]');
|
|
226
|
+
if (navItem) navItem.classList.add('active');
|
|
227
|
+
}
|
|
@@ -13,6 +13,7 @@ function addModule() {
|
|
|
13
13
|
featureType: document.getElementById('mod-type').value,
|
|
14
14
|
priority: document.getElementById('mod-priority').value,
|
|
15
15
|
entities: document.getElementById('mod-entities').value.split('\n').filter(e => e.trim()),
|
|
16
|
+
anticipatedSections: [],
|
|
16
17
|
status: 'pending'
|
|
17
18
|
});
|
|
18
19
|
|
|
@@ -69,14 +70,7 @@ function renderModules() {
|
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
function updateModulesNav() {
|
|
72
|
-
|
|
73
|
-
const navItems = data.modules.map(m => `
|
|
74
|
-
<a class="nav-item" onclick="showSection('module-spec-${m.code}')" data-section="module-spec-${m.code}">
|
|
75
|
-
<span class="nav-icon">●</span> ${m.name}
|
|
76
|
-
<span class="nav-badge">${(data.moduleSpecs[m.code]?.useCases || []).length}</span>
|
|
77
|
-
</a>
|
|
78
|
-
`).join('');
|
|
79
|
-
nav.innerHTML = '<div class="nav-group-title">3. Specification</div>' + (navItems || '<div style="padding:0.3rem 1rem;font-size:0.8rem;color:var(--text-muted);font-style:italic;">Aucun domaine</div>');
|
|
73
|
+
buildNavTree();
|
|
80
74
|
}
|
|
81
75
|
|
|
82
76
|
/* ============================================
|
|
@@ -37,6 +37,7 @@ function renderModuleSpecSection(mod) {
|
|
|
37
37
|
<button class="tab-btn" onclick="switchTab('${code}', 'perm')">Droits d'acces</button>
|
|
38
38
|
<button class="tab-btn" onclick="switchTab('${code}', 'mock')">Maquettes</button>
|
|
39
39
|
<button class="tab-btn" onclick="switchTab('${code}', 'notes')">Notes</button>
|
|
40
|
+
<button class="tab-btn" onclick="switchTab('${code}', 'struct')">Structure</button>
|
|
40
41
|
</div>
|
|
41
42
|
|
|
42
43
|
<!-- TAB: Cas d'utilisation -->
|
|
@@ -113,7 +114,12 @@ function renderModuleSpecSection(mod) {
|
|
|
113
114
|
<div class="tab-panel" id="tab-${code}-ent">
|
|
114
115
|
<p style="font-size:0.85rem;color:var(--text-muted);margin-bottom:1rem;">Les types de donnees que ce domaine gere. Decrivez les informations importantes a enregistrer pour chaque type.</p>
|
|
115
116
|
<div id="entList-${code}">
|
|
116
|
-
${spec.entities.
|
|
117
|
+
${spec.entities.length > 0
|
|
118
|
+
? spec.entities.map((ent, i) => renderEntity(code, ent, i)).join('')
|
|
119
|
+
: (mod.entities || []).length > 0
|
|
120
|
+
? '<div class="card" style="color:var(--text-muted);padding:1.5rem;"><p style="margin-bottom:0.75rem;">Les schemas detailles ne sont pas encore disponibles. Entites identifiees :</p><ul style="list-style:disc;padding-left:1.5rem;">' + mod.entities.map(e => '<li>' + e + '</li>').join('') + '</ul><p style="font-size:0.8rem;margin-top:0.75rem;font-style:italic;">Utilisez le bouton ci-dessous pour ajouter les schemas detailles.</p></div>'
|
|
121
|
+
: ''
|
|
122
|
+
}
|
|
117
123
|
</div>
|
|
118
124
|
<button class="add-btn" onclick="toggleForm('addEntForm-${code}')">+ Ajouter un type de donnees</button>
|
|
119
125
|
<div class="inline-form" id="addEntForm-${code}">
|
|
@@ -190,6 +196,14 @@ function renderModuleSpecSection(mod) {
|
|
|
190
196
|
<div class="editable" contenteditable="true" data-module-code="${code}" data-module-field="notes" data-placeholder="Notez ici tout ce qui concerne ce domaine : questions, precisions, contraintes particulieres...">${spec.notes || ''}</div>
|
|
191
197
|
</div>
|
|
192
198
|
</div>
|
|
199
|
+
|
|
200
|
+
<!-- TAB: Structure (sections/resources) -->
|
|
201
|
+
<div class="tab-panel" id="tab-${code}-struct">
|
|
202
|
+
<p style="font-size:0.85rem;color:var(--text-muted);margin-bottom:1rem;">Organisation des ecrans et ressources de ce domaine. Structure hierarchique : sections regroupant des ressources.</p>
|
|
203
|
+
<div id="structContainer-${code}">
|
|
204
|
+
${renderModuleStructure(code)}
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
193
207
|
</div>`;
|
|
194
208
|
}
|
|
195
209
|
|
|
@@ -547,6 +561,47 @@ function refreshAllPermGrids() {
|
|
|
547
561
|
});
|
|
548
562
|
}
|
|
549
563
|
|
|
564
|
+
function renderModuleStructure(code) {
|
|
565
|
+
const mod = data.modules.find(m => m.code === code);
|
|
566
|
+
const sections = mod ? (mod.anticipatedSections || []) : [];
|
|
567
|
+
|
|
568
|
+
if (sections.length === 0) {
|
|
569
|
+
return '<div class="card" style="text-align:center;padding:2rem;color:var(--text-muted);">' +
|
|
570
|
+
'<p>Aucune section definie pour ce module.</p>' +
|
|
571
|
+
'<p style="font-size:0.85rem;margin-top:0.5rem;">Les sections et ressources seront identifiees lors de l\'analyse approfondie.</p>' +
|
|
572
|
+
'</div>';
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
return sections.map(function(section) {
|
|
576
|
+
var resources = section.resources || [];
|
|
577
|
+
var html = '<div class="struct-section">';
|
|
578
|
+
html += '<div class="struct-section-header">';
|
|
579
|
+
html += '<span class="struct-section-code">' + (section.code || section.name || '') + '</span>';
|
|
580
|
+
if (section.description) {
|
|
581
|
+
html += '<span class="struct-section-desc">' + section.description + '</span>';
|
|
582
|
+
}
|
|
583
|
+
html += '<span class="struct-section-badge">' + resources.length + ' ressource' + (resources.length !== 1 ? 's' : '') + '</span>';
|
|
584
|
+
html += '</div>';
|
|
585
|
+
|
|
586
|
+
if (resources.length > 0) {
|
|
587
|
+
html += '<div class="struct-resources">';
|
|
588
|
+
resources.forEach(function(res) {
|
|
589
|
+
var resName = typeof res === 'string' ? res : (res.code || res.name || '');
|
|
590
|
+
var resDesc = typeof res === 'object' ? (res.description || '') : '';
|
|
591
|
+
html += '<div class="struct-resource">';
|
|
592
|
+
html += '<span class="struct-resource-icon">•</span>';
|
|
593
|
+
html += '<span class="struct-resource-name">' + resName + '</span>';
|
|
594
|
+
if (resDesc) html += '<span class="struct-resource-desc">' + resDesc + '</span>';
|
|
595
|
+
html += '</div>';
|
|
596
|
+
});
|
|
597
|
+
html += '</div>';
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
html += '</div>';
|
|
601
|
+
return html;
|
|
602
|
+
}).join('');
|
|
603
|
+
}
|
|
604
|
+
|
|
550
605
|
function switchTab(code, tabId) {
|
|
551
606
|
const section = document.getElementById('module-spec-' + code);
|
|
552
607
|
if (!section) return;
|
|
@@ -555,6 +610,6 @@ function switchTab(code, tabId) {
|
|
|
555
610
|
const targetPanel = document.getElementById('tab-' + code + '-' + tabId);
|
|
556
611
|
if (targetPanel) targetPanel.classList.add('active');
|
|
557
612
|
const buttons = section.querySelectorAll('.tab-btn');
|
|
558
|
-
const tabIndex = { uc: 0, br: 1, ent: 2, perm: 3, mock: 4, notes: 5 }[tabId];
|
|
613
|
+
const tabIndex = { uc: 0, br: 1, ent: 2, perm: 3, mock: 4, notes: 5, struct: 6 }[tabId];
|
|
559
614
|
if (buttons[tabIndex]) buttons[tabIndex].classList.add('active');
|
|
560
615
|
}
|
|
@@ -76,7 +76,9 @@ function renderCoverageMatrix() {
|
|
|
76
76
|
<thead><tr><th>Besoin</th><th>Priorite</th><th>Domaine</th><th>Couvert</th></tr></thead>
|
|
77
77
|
<tbody>
|
|
78
78
|
${allScope.map(item => {
|
|
79
|
-
const moduleName =
|
|
79
|
+
const moduleName = item.module
|
|
80
|
+
? (data.modules.find(m => m.code === item.module)?.name || item.module)
|
|
81
|
+
: (data.modules.length > 0 ? data.modules.map(m => m.name).join(', ') : 'A definir');
|
|
80
82
|
return `
|
|
81
83
|
<tr>
|
|
82
84
|
<td>${item.name}</td>
|
|
@@ -11,35 +11,125 @@ function saveToLocalStorage() {
|
|
|
11
11
|
showNotification('Modifications sauvegardees');
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
function resetToEmbedded() {
|
|
15
|
+
if (!confirm('Reinitialiser toutes les donnees depuis la version d\'origine ? Vos modifications locales (commentaires, notes) seront perdues.')) return;
|
|
16
|
+
localStorage.removeItem(APP_KEY);
|
|
17
|
+
// Restore data from ORIGINAL_DATA
|
|
18
|
+
Object.keys(data).forEach(k => delete data[k]);
|
|
19
|
+
Object.assign(data, JSON.parse(JSON.stringify(ORIGINAL_DATA)));
|
|
20
|
+
// Re-render everything
|
|
21
|
+
initEditableFields();
|
|
22
|
+
renderStakeholders();
|
|
23
|
+
renderScope();
|
|
24
|
+
renderRisks();
|
|
25
|
+
renderCriteria();
|
|
26
|
+
renderModules();
|
|
27
|
+
renderDependencies();
|
|
28
|
+
renderAllModuleSpecs();
|
|
29
|
+
renderConsolidation();
|
|
30
|
+
renderHandoff();
|
|
31
|
+
renderE2EFlows();
|
|
32
|
+
updateCounts();
|
|
33
|
+
renderReviewPanel();
|
|
34
|
+
showNotification('Donnees reinitialisees depuis la version d\'origine');
|
|
35
|
+
}
|
|
36
|
+
|
|
14
37
|
function loadFromLocalStorage() {
|
|
15
38
|
const saved = localStorage.getItem(APP_KEY);
|
|
16
|
-
if (saved)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
39
|
+
if (!saved) return;
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const parsed = JSON.parse(saved);
|
|
43
|
+
|
|
44
|
+
// Build fingerprint of embedded structural data to detect HTML regeneration
|
|
45
|
+
const embeddedFingerprint = ORIGINAL_DATA.modules.map(m => m.code).sort().join(',')
|
|
46
|
+
+ '|' + (ORIGINAL_DATA.metadata?.createdAt || '')
|
|
47
|
+
+ '|' + ORIGINAL_DATA.modules.length;
|
|
48
|
+
const cachedFingerprint = parsed._structureFingerprint || '';
|
|
49
|
+
|
|
50
|
+
const structureChanged = embeddedFingerprint !== cachedFingerprint;
|
|
51
|
+
const embeddedModuleCount = ORIGINAL_DATA.modules?.length || 0;
|
|
52
|
+
const cachedModuleCount = (parsed.modules || []).length;
|
|
53
|
+
const hasNewModules = embeddedModuleCount > cachedModuleCount;
|
|
54
|
+
const embeddedModuleCodes = new Set(ORIGINAL_DATA.modules.map(m => m.code));
|
|
55
|
+
const cachedModuleCodes = new Set((parsed.modules || []).map(m => m.code));
|
|
56
|
+
const missingModules = [...embeddedModuleCodes].filter(c => !cachedModuleCodes.has(c));
|
|
57
|
+
|
|
58
|
+
if (structureChanged || hasNewModules || missingModules.length > 0) {
|
|
59
|
+
// HTML was regenerated or has new modules — keep embedded structural data
|
|
60
|
+
// Only restore user-specific edits (comments, custom roles, notes)
|
|
61
|
+
data.wireframeComments = parsed.wireframeComments || {};
|
|
62
|
+
data.specComments = parsed.specComments || {};
|
|
63
|
+
data.customRoles = parsed.customRoles || [];
|
|
64
|
+
data.customActions = parsed.customActions || [];
|
|
65
|
+
data.comments = parsed.comments || [];
|
|
66
|
+
|
|
67
|
+
// Merge user-added notes per module (preserve existing module notes)
|
|
68
|
+
for (const code of Object.keys(parsed.moduleSpecs || {})) {
|
|
69
|
+
if (data.moduleSpecs[code] && parsed.moduleSpecs[code]?.notes) {
|
|
70
|
+
data.moduleSpecs[code].notes = parsed.moduleSpecs[code].notes;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Save fresh embedded data with fingerprint
|
|
75
|
+
data._structureFingerprint = embeddedFingerprint;
|
|
76
|
+
autoSave();
|
|
77
|
+
} else {
|
|
78
|
+
// Cache matches current structure — safe to restore user edits on cadrage/notes
|
|
79
|
+
// IMPORTANT: Always keep embedded modules and moduleSpecs as structural source of truth
|
|
80
|
+
// Only merge cadrage user-editable fields and notes
|
|
81
|
+
if (parsed.cadrage) {
|
|
82
|
+
// Merge cadrage context (user may have edited text fields)
|
|
83
|
+
if (parsed.cadrage.context) {
|
|
84
|
+
data.cadrage.context = { ...data.cadrage.context, ...parsed.cadrage.context };
|
|
85
|
+
}
|
|
86
|
+
// Merge scope only if user added items interactively
|
|
87
|
+
if (parsed.cadrage.scope) {
|
|
88
|
+
data.cadrage.scope = { ...data.cadrage.scope, ...parsed.cadrage.scope };
|
|
89
|
+
}
|
|
90
|
+
// Merge stakeholders and risks if user edited them
|
|
91
|
+
if (parsed.cadrage.stakeholders) data.cadrage.stakeholders = parsed.cadrage.stakeholders;
|
|
92
|
+
if (parsed.cadrage.risks) data.cadrage.risks = parsed.cadrage.risks;
|
|
93
|
+
}
|
|
94
|
+
data.dependencies = parsed.dependencies || data.dependencies;
|
|
29
95
|
data.consolidation = { ...data.consolidation, ...(parsed.consolidation || {}) };
|
|
30
|
-
|
|
96
|
+
|
|
97
|
+
// Merge moduleSpecs: keep embedded structure, overlay user-editable fields (notes, custom permissions)
|
|
98
|
+
for (const code of Object.keys(data.moduleSpecs)) {
|
|
99
|
+
const cached = parsed.moduleSpecs?.[code];
|
|
100
|
+
if (cached) {
|
|
101
|
+
// Preserve user notes
|
|
102
|
+
if (cached.notes) data.moduleSpecs[code].notes = cached.notes;
|
|
103
|
+
// Preserve user-added use cases/rules/entities (merged with embedded)
|
|
104
|
+
// Only add items that don't exist in embedded (by name match)
|
|
105
|
+
const embeddedUcNames = new Set((data.moduleSpecs[code].useCases || []).map(uc => uc.name));
|
|
106
|
+
(cached.useCases || []).forEach(uc => {
|
|
107
|
+
if (!embeddedUcNames.has(uc.name)) data.moduleSpecs[code].useCases.push(uc);
|
|
108
|
+
});
|
|
109
|
+
const embeddedBrNames = new Set((data.moduleSpecs[code].businessRules || []).map(br => br.name));
|
|
110
|
+
(cached.businessRules || []).forEach(br => {
|
|
111
|
+
if (!embeddedBrNames.has(br.name)) data.moduleSpecs[code].businessRules.push(br);
|
|
112
|
+
});
|
|
113
|
+
// Preserve user permissions edits
|
|
114
|
+
if (cached.permissions) data.moduleSpecs[code].permissions = cached.permissions;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
31
118
|
data.wireframeComments = parsed.wireframeComments || {};
|
|
32
119
|
data.specComments = parsed.specComments || {};
|
|
33
120
|
data.customRoles = parsed.customRoles || [];
|
|
34
121
|
data.customActions = parsed.customActions || [];
|
|
35
122
|
data.comments = parsed.comments || [];
|
|
36
123
|
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
124
|
+
// Update fingerprint
|
|
125
|
+
data._structureFingerprint = embeddedFingerprint;
|
|
126
|
+
autoSave();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Restore editable fields
|
|
130
|
+
document.querySelectorAll('.editable[data-field]').forEach(el => {
|
|
131
|
+
const value = getNestedValue(data, 'cadrage.' + el.dataset.field);
|
|
132
|
+
if (value) el.textContent = value;
|
|
133
|
+
});
|
|
134
|
+
} catch (e) { console.error('Error loading saved data:', e); }
|
|
45
135
|
}
|
|
@@ -118,6 +118,13 @@ function getSectionLabel(sectionId) {
|
|
|
118
118
|
const mod = data.modules.find(m => m.code === code);
|
|
119
119
|
return mod ? mod.name : code;
|
|
120
120
|
}
|
|
121
|
+
// Handle module structure sections (module-struct-{code}-{section})
|
|
122
|
+
const structMatch = sectionId.match(/^module-struct-(.+?)-(.+)$/);
|
|
123
|
+
if (structMatch) {
|
|
124
|
+
const mod = data.modules.find(m => m.code === structMatch[1]);
|
|
125
|
+
const modName = mod ? mod.name : structMatch[1];
|
|
126
|
+
return modName + ' > Structure > ' + structMatch[2];
|
|
127
|
+
}
|
|
121
128
|
// Handle list-based sectionIds (ucList-*, brList-*, entList-*)
|
|
122
129
|
const listMatch = sectionId.match(/^(uc|br|ent)List-(.+)$/);
|
|
123
130
|
if (listMatch) {
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
PRINT
|
|
91
91
|
============================================ */
|
|
92
92
|
@media print {
|
|
93
|
-
.sidebar, .header-actions, .add-btn, .uc-actions, .inline-form, .module-card-remove
|
|
93
|
+
.sidebar, .header-actions, .add-btn, .uc-actions, .inline-form, .module-card-remove { display: none !important; }
|
|
94
94
|
.main { max-width: 100%; padding: 0; }
|
|
95
95
|
.section { display: block !important; page-break-inside: avoid; }
|
|
96
96
|
body { background: #fff; color: #1a1a1a; }
|