@atlashub/smartstack-cli 3.19.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/agents/gitflow/cleanup.md +5 -1
- package/templates/agents/gitflow/finish.md +2 -0
- package/templates/agents/gitflow/init-clone.md +13 -0
- package/templates/agents/gitflow/init-validate.md +14 -0
- package/templates/agents/gitflow/status.md +6 -0
- 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-02-decomposition.md +16 -3
- 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
- package/templates/skills/gitflow/_shared.md +65 -17
- package/templates/skills/gitflow/phases/status.md +8 -3
- package/templates/skills/gitflow/steps/step-start.md +3 -0
|
@@ -139,7 +139,7 @@ body {
|
|
|
139
139
|
PRINT
|
|
140
140
|
============================================ */
|
|
141
141
|
@media print {
|
|
142
|
-
.sidebar, .header-actions, .add-btn, .uc-actions, .inline-form, .module-card-remove
|
|
142
|
+
.sidebar, .header-actions, .add-btn, .uc-actions, .inline-form, .module-card-remove { display: none !important; }
|
|
143
143
|
.main { max-width: 100%; padding: 0; }
|
|
144
144
|
.section { display: block !important; page-break-inside: avoid; }
|
|
145
145
|
body { background: #fff; color: #1a1a1a; }
|
|
@@ -152,67 +152,125 @@ body {
|
|
|
152
152
|
|
|
153
153
|
/* --- 03-navigation.css --- */
|
|
154
154
|
/* ============================================
|
|
155
|
-
SIDEBAR -
|
|
155
|
+
SIDEBAR - Hierarchical Tree Navigation
|
|
156
156
|
============================================ */
|
|
157
|
-
|
|
157
|
+
|
|
158
|
+
/* Sidebar header */
|
|
159
|
+
.sidebar-header {
|
|
160
|
+
padding: 0.75rem 1rem;
|
|
161
|
+
border-bottom: 1px solid var(--border);
|
|
162
|
+
background: var(--bg-hover);
|
|
163
|
+
}
|
|
164
|
+
.sidebar-app-name {
|
|
165
|
+
font-size: 0.95rem;
|
|
166
|
+
font-weight: 600;
|
|
167
|
+
color: var(--primary-light);
|
|
168
|
+
display: block;
|
|
169
|
+
white-space: nowrap;
|
|
170
|
+
overflow: hidden;
|
|
171
|
+
text-overflow: ellipsis;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/* Nav groups (top-level: Cadrage, Modules, Consolidation, Synthese) */
|
|
175
|
+
.nav-group { padding: 0.4rem 0; }
|
|
158
176
|
.nav-group + .nav-group { border-top: 1px solid var(--border); }
|
|
159
177
|
|
|
160
178
|
.nav-group-title {
|
|
161
|
-
font-size: 0.
|
|
179
|
+
font-size: 0.7rem;
|
|
162
180
|
text-transform: uppercase;
|
|
163
181
|
letter-spacing: 0.1em;
|
|
164
182
|
color: var(--text-muted);
|
|
165
|
-
padding: 0 1rem;
|
|
166
|
-
margin-bottom: 0.
|
|
183
|
+
padding: 0.4rem 1rem;
|
|
184
|
+
margin-bottom: 0.15rem;
|
|
167
185
|
font-weight: 600;
|
|
186
|
+
cursor: pointer;
|
|
187
|
+
display: flex;
|
|
188
|
+
align-items: center;
|
|
189
|
+
gap: 0.3rem;
|
|
190
|
+
user-select: none;
|
|
191
|
+
transition: color var(--transition-fast);
|
|
168
192
|
}
|
|
193
|
+
.nav-group-title:hover { color: var(--text-bright); }
|
|
169
194
|
|
|
195
|
+
/* Chevron icon for expand/collapse */
|
|
196
|
+
.nav-chevron {
|
|
197
|
+
font-size: 0.6rem;
|
|
198
|
+
display: inline-block;
|
|
199
|
+
transition: transform var(--transition-fast);
|
|
200
|
+
color: var(--text-muted);
|
|
201
|
+
width: 12px;
|
|
202
|
+
text-align: center;
|
|
203
|
+
flex-shrink: 0;
|
|
204
|
+
}
|
|
205
|
+
.nav-chevron.expanded { transform: rotate(90deg); }
|
|
206
|
+
|
|
207
|
+
/* Nav items (leaf nodes) */
|
|
170
208
|
.nav-item {
|
|
171
209
|
display: flex;
|
|
172
210
|
align-items: center;
|
|
173
|
-
gap: 0.
|
|
174
|
-
padding: 0.
|
|
211
|
+
gap: 0.4rem;
|
|
212
|
+
padding: 0.35rem 1rem;
|
|
175
213
|
color: var(--text);
|
|
176
214
|
text-decoration: none;
|
|
177
|
-
font-size: 0.
|
|
215
|
+
font-size: 0.82rem;
|
|
178
216
|
cursor: pointer;
|
|
179
217
|
transition: all var(--transition-fast);
|
|
180
218
|
border-left: 3px solid transparent;
|
|
181
219
|
}
|
|
182
220
|
.nav-item:hover { background: var(--bg-hover); color: var(--text-bright); }
|
|
183
|
-
.nav-item.active {
|
|
221
|
+
.nav-item.active {
|
|
222
|
+
background: rgba(99,102,241,0.1);
|
|
223
|
+
border-left-color: var(--primary);
|
|
224
|
+
color: var(--primary-light);
|
|
225
|
+
font-weight: 500;
|
|
226
|
+
}
|
|
184
227
|
|
|
185
|
-
.nav-item .nav-icon {
|
|
228
|
+
.nav-item .nav-icon {
|
|
229
|
+
font-size: 0.45rem;
|
|
230
|
+
width: 12px;
|
|
231
|
+
text-align: center;
|
|
232
|
+
color: var(--border-light);
|
|
233
|
+
flex-shrink: 0;
|
|
234
|
+
}
|
|
186
235
|
.nav-item .nav-badge {
|
|
187
236
|
margin-left: auto;
|
|
188
|
-
font-size: 0.
|
|
237
|
+
font-size: 0.6rem;
|
|
189
238
|
background: var(--bg-hover);
|
|
190
|
-
padding: 0.1rem 0.
|
|
239
|
+
padding: 0.1rem 0.35rem;
|
|
191
240
|
border-radius: 10px;
|
|
192
241
|
color: var(--text-muted);
|
|
242
|
+
min-width: 16px;
|
|
243
|
+
text-align: center;
|
|
193
244
|
}
|
|
194
245
|
|
|
195
|
-
|
|
196
|
-
.nav-children
|
|
246
|
+
/* Nested children (indented) */
|
|
247
|
+
.nav-children { margin-left: 0; }
|
|
248
|
+
.nav-children .nav-item { padding-left: 1.6rem; font-size: 0.8rem; }
|
|
249
|
+
.nav-children .nav-children .nav-item { padding-left: 2.4rem; font-size: 0.78rem; }
|
|
250
|
+
.nav-children .nav-children .nav-children .nav-item { padding-left: 3rem; font-size: 0.75rem; }
|
|
197
251
|
|
|
198
|
-
/*
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
padding: 0.75rem 1rem; border-bottom: 1px solid var(--border);
|
|
252
|
+
/* Module header in nav (collapsible) */
|
|
253
|
+
.nav-module-header {
|
|
254
|
+
font-weight: 500;
|
|
255
|
+
color: var(--text-bright);
|
|
256
|
+
font-size: 0.85rem;
|
|
204
257
|
}
|
|
205
|
-
.
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
258
|
+
.nav-module-header:hover { color: var(--primary-light); }
|
|
259
|
+
|
|
260
|
+
/* Section/Resource items in nav */
|
|
261
|
+
.nav-section-item { margin-top: 0.1rem; }
|
|
262
|
+
.nav-section-link { font-style: italic; }
|
|
263
|
+
.nav-icon-section { font-size: 0.5rem; color: var(--accent); }
|
|
264
|
+
.nav-icon-resource { font-size: 0.7rem; color: var(--border-light); }
|
|
265
|
+
.nav-resource-link {
|
|
266
|
+
cursor: default;
|
|
267
|
+
font-size: 0.72rem;
|
|
268
|
+
color: var(--text-muted);
|
|
269
|
+
padding-top: 0.2rem;
|
|
270
|
+
padding-bottom: 0.2rem;
|
|
211
271
|
}
|
|
212
|
-
.
|
|
213
|
-
.
|
|
214
|
-
.phase-line { flex: 1; height: 2px; background: var(--border); }
|
|
215
|
-
.phase-line.completed { background: var(--success); }
|
|
272
|
+
.nav-resource-link:hover { background: transparent; color: var(--text-muted); }
|
|
273
|
+
.nav-resources { margin-left: 0.5rem; }
|
|
216
274
|
|
|
217
275
|
|
|
218
276
|
/* --- 04-cards.css --- */
|
|
@@ -806,6 +864,70 @@ body {
|
|
|
806
864
|
margin-bottom: 0.2rem;
|
|
807
865
|
}
|
|
808
866
|
|
|
867
|
+
/* ============================================
|
|
868
|
+
STRUCTURE TAB (Sections/Resources)
|
|
869
|
+
============================================ */
|
|
870
|
+
.struct-section {
|
|
871
|
+
background: var(--bg-card);
|
|
872
|
+
border: 1px solid var(--border);
|
|
873
|
+
border-radius: 10px;
|
|
874
|
+
margin-bottom: 1rem;
|
|
875
|
+
overflow: hidden;
|
|
876
|
+
}
|
|
877
|
+
.struct-section-header {
|
|
878
|
+
display: flex;
|
|
879
|
+
align-items: center;
|
|
880
|
+
gap: 0.75rem;
|
|
881
|
+
padding: 0.75rem 1rem;
|
|
882
|
+
background: var(--bg-hover);
|
|
883
|
+
border-bottom: 1px solid var(--border);
|
|
884
|
+
}
|
|
885
|
+
.struct-section-code {
|
|
886
|
+
font-weight: 600;
|
|
887
|
+
color: var(--text-bright);
|
|
888
|
+
font-size: 0.95rem;
|
|
889
|
+
}
|
|
890
|
+
.struct-section-desc {
|
|
891
|
+
flex: 1;
|
|
892
|
+
font-size: 0.8rem;
|
|
893
|
+
color: var(--text-muted);
|
|
894
|
+
}
|
|
895
|
+
.struct-section-badge {
|
|
896
|
+
font-size: 0.65rem;
|
|
897
|
+
color: var(--text-muted);
|
|
898
|
+
background: rgba(99,102,241,0.1);
|
|
899
|
+
padding: 0.1rem 0.5rem;
|
|
900
|
+
border-radius: 4px;
|
|
901
|
+
white-space: nowrap;
|
|
902
|
+
}
|
|
903
|
+
.struct-resources {
|
|
904
|
+
padding: 0.5rem 0;
|
|
905
|
+
}
|
|
906
|
+
.struct-resource {
|
|
907
|
+
display: flex;
|
|
908
|
+
align-items: baseline;
|
|
909
|
+
gap: 0.5rem;
|
|
910
|
+
padding: 0.35rem 1rem 0.35rem 1.5rem;
|
|
911
|
+
font-size: 0.85rem;
|
|
912
|
+
transition: background var(--transition-fast);
|
|
913
|
+
}
|
|
914
|
+
.struct-resource:hover {
|
|
915
|
+
background: var(--bg-hover);
|
|
916
|
+
}
|
|
917
|
+
.struct-resource-icon {
|
|
918
|
+
color: var(--primary-light);
|
|
919
|
+
font-size: 0.7rem;
|
|
920
|
+
flex-shrink: 0;
|
|
921
|
+
}
|
|
922
|
+
.struct-resource-name {
|
|
923
|
+
font-weight: 500;
|
|
924
|
+
color: var(--text-bright);
|
|
925
|
+
}
|
|
926
|
+
.struct-resource-desc {
|
|
927
|
+
font-size: 0.8rem;
|
|
928
|
+
color: var(--text-muted);
|
|
929
|
+
}
|
|
930
|
+
|
|
809
931
|
|
|
810
932
|
/* --- 06-wireframes.css --- */
|
|
811
933
|
/* ============================================
|
|
@@ -1484,6 +1606,7 @@ body {
|
|
|
1484
1606
|
<span class="header-app-name" id="appName">{{APPLICATION_NAME}}</span>
|
|
1485
1607
|
<div class="header-spacer"></div>
|
|
1486
1608
|
<div class="header-actions">
|
|
1609
|
+
<button class="btn btn-sm" onclick="resetToEmbedded()" title="Reinitialiser depuis les donnees d'origine (supprime les modifications locales)">Reset</button>
|
|
1487
1610
|
<button class="btn btn-sm" onclick="saveToLocalStorage()" title="Sauvegarder les modifications dans le navigateur">Sauvegarder</button>
|
|
1488
1611
|
<button class="btn btn-sm btn-review" onclick="saveReviewJSON()" title="Sauvegarder les corrections pour creer une nouvelle version">Sauvegarder corrections</button>
|
|
1489
1612
|
<button class="btn btn-sm btn-primary" onclick="exportJSON()" title="Exporter les donnees au format JSON pour l'extraction">Exporter JSON</button>
|
|
@@ -1496,85 +1619,16 @@ body {
|
|
|
1496
1619
|
|
|
1497
1620
|
<div class="body" id="appBody">
|
|
1498
1621
|
<!-- ============================================
|
|
1499
|
-
SIDEBAR - Navigation
|
|
1622
|
+
SIDEBAR - Navigation hierarchique
|
|
1500
1623
|
============================================ -->
|
|
1501
1624
|
<aside class="sidebar">
|
|
1502
|
-
<!--
|
|
1503
|
-
<div class="
|
|
1504
|
-
<
|
|
1505
|
-
<div class="phase-line" id="pline-1"></div>
|
|
1506
|
-
<div class="phase-dot" id="phase-2" title="Decomposition">2</div>
|
|
1507
|
-
<div class="phase-line" id="pline-2"></div>
|
|
1508
|
-
<div class="phase-dot" id="phase-3" title="Specification">3</div>
|
|
1509
|
-
<div class="phase-line" id="pline-3"></div>
|
|
1510
|
-
<div class="phase-dot" id="phase-4" title="Consolidation">4</div>
|
|
1511
|
-
<div class="phase-line" id="pline-4"></div>
|
|
1512
|
-
<div class="phase-dot" id="phase-5" title="Synthese">5</div>
|
|
1625
|
+
<!-- Application Name -->
|
|
1626
|
+
<div class="sidebar-header">
|
|
1627
|
+
<span class="sidebar-app-name" id="sidebarAppName">{{APPLICATION_NAME}}</span>
|
|
1513
1628
|
</div>
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
<div class="nav-group-title">1. Cadrage</div>
|
|
1518
|
-
<a class="nav-item active" onclick="showSection('cadrage-context')" data-section="cadrage-context">
|
|
1519
|
-
<span class="nav-icon">●</span> Contexte
|
|
1520
|
-
</a>
|
|
1521
|
-
<a class="nav-item" onclick="showSection('cadrage-stakeholders')" data-section="cadrage-stakeholders">
|
|
1522
|
-
<span class="nav-icon">●</span> Parties prenantes
|
|
1523
|
-
<span class="nav-badge" id="stakeholderCount">0</span>
|
|
1524
|
-
</a>
|
|
1525
|
-
<a class="nav-item" onclick="showSection('cadrage-scope')" data-section="cadrage-scope">
|
|
1526
|
-
<span class="nav-icon">●</span> Perimetre fonctionnel
|
|
1527
|
-
</a>
|
|
1528
|
-
<a class="nav-item" onclick="showSection('cadrage-risks')" data-section="cadrage-risks">
|
|
1529
|
-
<span class="nav-icon">●</span> Risques et hypotheses
|
|
1530
|
-
</a>
|
|
1531
|
-
<a class="nav-item" onclick="showSection('cadrage-success')" data-section="cadrage-success">
|
|
1532
|
-
<span class="nav-icon">●</span> Criteres de reussite
|
|
1533
|
-
</a>
|
|
1534
|
-
</div>
|
|
1535
|
-
|
|
1536
|
-
<!-- Phase 2 : Decomposition -->
|
|
1537
|
-
<div class="nav-group">
|
|
1538
|
-
<div class="nav-group-title">2. Decomposition</div>
|
|
1539
|
-
<a class="nav-item" onclick="showSection('decomp-modules')" data-section="decomp-modules">
|
|
1540
|
-
<span class="nav-icon">●</span> Domaines fonctionnels
|
|
1541
|
-
<span class="nav-badge" id="moduleCount">0</span>
|
|
1542
|
-
</a>
|
|
1543
|
-
<a class="nav-item" onclick="showSection('decomp-dependencies')" data-section="decomp-dependencies">
|
|
1544
|
-
<span class="nav-icon">●</span> Dependances
|
|
1545
|
-
</a>
|
|
1546
|
-
</div>
|
|
1547
|
-
|
|
1548
|
-
<!-- Phase 3 : Specification par module -->
|
|
1549
|
-
<div class="nav-group" id="modulesNav">
|
|
1550
|
-
<div class="nav-group-title">3. Specification</div>
|
|
1551
|
-
<!-- Populated dynamically per module -->
|
|
1552
|
-
</div>
|
|
1553
|
-
|
|
1554
|
-
<!-- Phase 4 : Consolidation -->
|
|
1555
|
-
<div class="nav-group">
|
|
1556
|
-
<div class="nav-group-title">4. Consolidation</div>
|
|
1557
|
-
<a class="nav-item" onclick="showSection('consol-datamodel')" data-section="consol-datamodel">
|
|
1558
|
-
<span class="nav-icon">●</span> Modele de donnees
|
|
1559
|
-
<span class="nav-badge" id="entityCount">0</span>
|
|
1560
|
-
</a>
|
|
1561
|
-
<a class="nav-item" onclick="showSection('consol-interactions')" data-section="consol-interactions">
|
|
1562
|
-
<span class="nav-icon">●</span> Interactions
|
|
1563
|
-
</a>
|
|
1564
|
-
<a class="nav-item" onclick="showSection('consol-permissions')" data-section="consol-permissions">
|
|
1565
|
-
<span class="nav-icon">●</span> Coherence des acces
|
|
1566
|
-
</a>
|
|
1567
|
-
<a class="nav-item" onclick="showSection('consol-flows')" data-section="consol-flows">
|
|
1568
|
-
<span class="nav-icon">●</span> Parcours bout en bout
|
|
1569
|
-
</a>
|
|
1570
|
-
</div>
|
|
1571
|
-
|
|
1572
|
-
<!-- Phase 5 : Synthese -->
|
|
1573
|
-
<div class="nav-group">
|
|
1574
|
-
<div class="nav-group-title">5. Synthese</div>
|
|
1575
|
-
<a class="nav-item" onclick="showSection('handoff-summary')" data-section="handoff-summary">
|
|
1576
|
-
<span class="nav-icon">●</span> Vue d'ensemble
|
|
1577
|
-
</a>
|
|
1629
|
+
<!-- Dynamic Tree Navigation -->
|
|
1630
|
+
<div id="sidebarNav">
|
|
1631
|
+
<!-- Populated by buildNavTree() -->
|
|
1578
1632
|
</div>
|
|
1579
1633
|
</aside>
|
|
1580
1634
|
|
|
@@ -2092,6 +2146,14 @@ data.cadrage.scope = data.cadrage.scope || { vital: [], important: [], optional:
|
|
|
2092
2146
|
|
|
2093
2147
|
// Defensive init: moduleSpecs (may be missing if LLM didn't follow mapping)
|
|
2094
2148
|
data.moduleSpecs = data.moduleSpecs || {};
|
|
2149
|
+
// Ensure ALL modules have a moduleSpecs entry (prevents missing schemas)
|
|
2150
|
+
(data.modules || []).forEach(function(m) {
|
|
2151
|
+
if (!data.moduleSpecs[m.code]) {
|
|
2152
|
+
data.moduleSpecs[m.code] = { useCases: [], businessRules: [], entities: [], permissions: [], notes: '' };
|
|
2153
|
+
}
|
|
2154
|
+
// Ensure anticipatedSections array exists
|
|
2155
|
+
m.anticipatedSections = m.anticipatedSections || [];
|
|
2156
|
+
});
|
|
2095
2157
|
|
|
2096
2158
|
// Vibe coding mode: hide non-relevant sections
|
|
2097
2159
|
const isVibeCoding = data.metadata?.vibeCoding === true;
|
|
@@ -2132,11 +2194,7 @@ function setNestedValue(obj, path, value) {
|
|
|
2132
2194
|
}
|
|
2133
2195
|
|
|
2134
2196
|
function updateCounts() {
|
|
2135
|
-
|
|
2136
|
-
document.getElementById('moduleCount').textContent = data.modules.length;
|
|
2137
|
-
const totalEntities = data.modules.reduce((sum, m) => sum + (data.moduleSpecs[m.code]?.entities || []).length, 0);
|
|
2138
|
-
document.getElementById('entityCount').textContent = totalEntities;
|
|
2139
|
-
updateModulesNav();
|
|
2197
|
+
buildNavTree();
|
|
2140
2198
|
updateDepSelects();
|
|
2141
2199
|
}
|
|
2142
2200
|
|
|
@@ -2184,7 +2242,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
2184
2242
|
renderConsolidation();
|
|
2185
2243
|
renderHandoff();
|
|
2186
2244
|
renderE2EFlows();
|
|
2187
|
-
|
|
2245
|
+
buildNavTree();
|
|
2246
|
+
updateDepSelects();
|
|
2188
2247
|
initInlineComments();
|
|
2189
2248
|
renderReviewPanel();
|
|
2190
2249
|
});
|
|
@@ -2192,9 +2251,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
2192
2251
|
|
|
2193
2252
|
/* --- 02-navigation.js --- */
|
|
2194
2253
|
/* ============================================
|
|
2195
|
-
NAVIGATION
|
|
2254
|
+
NAVIGATION - Hierarchical Tree
|
|
2196
2255
|
============================================ */
|
|
2197
2256
|
let currentSectionId = 'cadrage-context';
|
|
2257
|
+
let navCollapseState = {};
|
|
2258
|
+
|
|
2259
|
+
/* ---------- Core ---------- */
|
|
2198
2260
|
|
|
2199
2261
|
function showSection(sectionId) {
|
|
2200
2262
|
currentSectionId = sectionId;
|
|
@@ -2202,9 +2264,30 @@ function showSection(sectionId) {
|
|
|
2202
2264
|
const section = document.getElementById(sectionId);
|
|
2203
2265
|
if (section) section.style.display = 'block';
|
|
2204
2266
|
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2267
|
+
// Highlight active nav item
|
|
2268
|
+
document.querySelectorAll('#sidebarNav .nav-item').forEach(n => n.classList.remove('active'));
|
|
2269
|
+
const navItem = document.querySelector('#sidebarNav [data-section="' + sectionId + '"]');
|
|
2270
|
+
if (navItem) {
|
|
2271
|
+
navItem.classList.add('active');
|
|
2272
|
+
// Auto-expand parent groups to reveal active item
|
|
2273
|
+
let parent = navItem.parentElement;
|
|
2274
|
+
while (parent && parent.id !== 'sidebarNav') {
|
|
2275
|
+
if (parent.classList.contains('nav-children') && parent.style.display === 'none') {
|
|
2276
|
+
parent.style.display = '';
|
|
2277
|
+
const groupEl = parent.parentElement;
|
|
2278
|
+
if (groupEl) {
|
|
2279
|
+
const groupId = groupEl.dataset.groupId;
|
|
2280
|
+
if (groupId) {
|
|
2281
|
+
navCollapseState[groupId] = false;
|
|
2282
|
+
const chevron = groupEl.querySelector(':scope > .nav-group-title .nav-chevron, :scope > .nav-item.nav-module-header .nav-chevron');
|
|
2283
|
+
if (chevron) chevron.classList.add('expanded');
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
parent = parent.parentElement;
|
|
2288
|
+
}
|
|
2289
|
+
saveNavState();
|
|
2290
|
+
}
|
|
2208
2291
|
}
|
|
2209
2292
|
|
|
2210
2293
|
function restoreCurrentSection() {
|
|
@@ -2214,6 +2297,187 @@ function restoreCurrentSection() {
|
|
|
2214
2297
|
}
|
|
2215
2298
|
}
|
|
2216
2299
|
|
|
2300
|
+
/* ---------- Tree Builder ---------- */
|
|
2301
|
+
|
|
2302
|
+
function buildNavTree() {
|
|
2303
|
+
const nav = document.getElementById('sidebarNav');
|
|
2304
|
+
if (!nav) return;
|
|
2305
|
+
|
|
2306
|
+
nav.innerHTML =
|
|
2307
|
+
renderNavGroup('cadrage', 'Cadrage', buildCadrageItems()) +
|
|
2308
|
+
renderNavGroup('modules', 'Modules (' + data.modules.length + ')', buildModuleItems()) +
|
|
2309
|
+
renderNavGroup('consolidation', 'Consolidation', buildConsolidationItems()) +
|
|
2310
|
+
renderNavGroup('synthese', 'Synthese', buildSyntheseItems());
|
|
2311
|
+
|
|
2312
|
+
restoreNavState();
|
|
2313
|
+
highlightActiveNavItem();
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
function renderNavGroup(id, title, itemsHtml) {
|
|
2317
|
+
const collapsed = navCollapseState[id] === true;
|
|
2318
|
+
return '<div class="nav-group" data-group-id="' + id + '">' +
|
|
2319
|
+
'<div class="nav-group-title" onclick="toggleNavGroup(\'' + id + '\')">' +
|
|
2320
|
+
'<span class="nav-chevron ' + (collapsed ? '' : 'expanded') + '">▸</span> ' +
|
|
2321
|
+
title +
|
|
2322
|
+
'</div>' +
|
|
2323
|
+
'<div class="nav-children"' + (collapsed ? ' style="display:none;"' : '') + '>' +
|
|
2324
|
+
itemsHtml +
|
|
2325
|
+
'</div>' +
|
|
2326
|
+
'</div>';
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
function renderNavItem(sectionId, label, badge) {
|
|
2330
|
+
return '<a class="nav-item" onclick="showSection(\'' + sectionId + '\')" data-section="' + sectionId + '">' +
|
|
2331
|
+
'<span class="nav-icon">●</span> ' + label +
|
|
2332
|
+
(badge !== undefined ? ' <span class="nav-badge">' + badge + '</span>' : '') +
|
|
2333
|
+
'</a>';
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
/* ---------- Group Builders ---------- */
|
|
2337
|
+
|
|
2338
|
+
function buildCadrageItems() {
|
|
2339
|
+
return renderNavItem('cadrage-context', 'Contexte') +
|
|
2340
|
+
renderNavItem('cadrage-stakeholders', 'Parties prenantes', data.cadrage.stakeholders.length) +
|
|
2341
|
+
renderNavItem('cadrage-scope', 'Perimetre fonctionnel') +
|
|
2342
|
+
renderNavItem('cadrage-risks', 'Risques et hypotheses', data.cadrage.risks.length) +
|
|
2343
|
+
renderNavItem('cadrage-success', 'Criteres de reussite');
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
function buildModuleItems() {
|
|
2347
|
+
let html = '';
|
|
2348
|
+
data.modules.forEach(function(mod) {
|
|
2349
|
+
html += renderModuleNavItem(mod);
|
|
2350
|
+
});
|
|
2351
|
+
// Global module views at bottom
|
|
2352
|
+
html += renderNavItem('decomp-modules', 'Vue d\'ensemble', data.modules.length);
|
|
2353
|
+
html += renderNavItem('decomp-dependencies', 'Dependances', data.dependencies.length);
|
|
2354
|
+
return html;
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
function buildConsolidationItems() {
|
|
2358
|
+
var totalEntities = data.modules.reduce(function(sum, m) {
|
|
2359
|
+
return sum + (data.moduleSpecs[m.code]?.entities || []).length;
|
|
2360
|
+
}, 0);
|
|
2361
|
+
return renderNavItem('consol-datamodel', 'Modele de donnees', totalEntities) +
|
|
2362
|
+
renderNavItem('consol-interactions', 'Interactions') +
|
|
2363
|
+
renderNavItem('consol-permissions', 'Coherence des acces') +
|
|
2364
|
+
renderNavItem('consol-flows', 'Parcours bout en bout', data.consolidation.e2eFlows.length);
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
function buildSyntheseItems() {
|
|
2368
|
+
return renderNavItem('handoff-summary', 'Vue d\'ensemble');
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
/* ---------- Module Sub-Tree ---------- */
|
|
2372
|
+
|
|
2373
|
+
function renderModuleNavItem(mod) {
|
|
2374
|
+
var code = mod.code;
|
|
2375
|
+
var spec = data.moduleSpecs[code] || {};
|
|
2376
|
+
var ucCount = (spec.useCases || []).length;
|
|
2377
|
+
var brCount = (spec.businessRules || []).length;
|
|
2378
|
+
var entCount = (spec.entities || []).length;
|
|
2379
|
+
var sections = mod.anticipatedSections || [];
|
|
2380
|
+
var groupId = 'mod-' + code;
|
|
2381
|
+
var collapsed = navCollapseState[groupId] === true;
|
|
2382
|
+
|
|
2383
|
+
var html = '<div class="nav-module" data-group-id="' + groupId + '">';
|
|
2384
|
+
|
|
2385
|
+
// Module header (clickable to expand + navigate to module spec)
|
|
2386
|
+
html += '<a class="nav-item nav-module-header" onclick="toggleNavGroup(\'' + groupId + '\');showSection(\'module-spec-' + code + '\')" data-section="module-spec-' + code + '">';
|
|
2387
|
+
html += '<span class="nav-chevron ' + (collapsed ? '' : 'expanded') + '">▸</span> ';
|
|
2388
|
+
html += (mod.name || mod.code);
|
|
2389
|
+
html += '</a>';
|
|
2390
|
+
|
|
2391
|
+
// Children: tabs
|
|
2392
|
+
html += '<div class="nav-children"' + (collapsed ? ' style="display:none;"' : '') + '>';
|
|
2393
|
+
html += renderModuleTabNavItem(code, 'uc', 'Cas d\'utilisation', ucCount);
|
|
2394
|
+
html += renderModuleTabNavItem(code, 'br', 'Regles metier', brCount);
|
|
2395
|
+
html += renderModuleTabNavItem(code, 'ent', 'Donnees', entCount);
|
|
2396
|
+
html += renderModuleTabNavItem(code, 'perm', 'Droits d\'acces');
|
|
2397
|
+
html += renderModuleTabNavItem(code, 'mock', 'Maquettes');
|
|
2398
|
+
html += renderModuleTabNavItem(code, 'struct', 'Structure', sections.length);
|
|
2399
|
+
|
|
2400
|
+
// Children: sections/resources (deeper tree)
|
|
2401
|
+
if (sections.length > 0) {
|
|
2402
|
+
sections.forEach(function(section) {
|
|
2403
|
+
var resources = section.resources || [];
|
|
2404
|
+
html += '<div class="nav-section-item">';
|
|
2405
|
+
html += '<a class="nav-item nav-section-link" onclick="showSection(\'module-spec-' + code + '\');switchTab(\'' + code + '\',\'struct\')" data-section="module-struct-' + code + '-' + section.code + '">';
|
|
2406
|
+
html += '<span class="nav-icon nav-icon-section">▷</span> ' + section.code;
|
|
2407
|
+
if (resources.length > 0) html += ' <span class="nav-badge">' + resources.length + '</span>';
|
|
2408
|
+
html += '</a>';
|
|
2409
|
+
if (resources.length > 0) {
|
|
2410
|
+
html += '<div class="nav-children nav-resources">';
|
|
2411
|
+
resources.forEach(function(res) {
|
|
2412
|
+
var resName = typeof res === 'string' ? res : (res.code || res.name || '');
|
|
2413
|
+
html += '<a class="nav-item nav-resource-link">';
|
|
2414
|
+
html += '<span class="nav-icon nav-icon-resource">•</span> ' + resName;
|
|
2415
|
+
html += '</a>';
|
|
2416
|
+
});
|
|
2417
|
+
html += '</div>';
|
|
2418
|
+
}
|
|
2419
|
+
html += '</div>';
|
|
2420
|
+
});
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
html += '</div>'; // nav-children
|
|
2424
|
+
html += '</div>'; // nav-module
|
|
2425
|
+
return html;
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
function renderModuleTabNavItem(code, tabId, label, badge) {
|
|
2429
|
+
var sectionId = 'module-spec-' + code;
|
|
2430
|
+
return '<a class="nav-item" onclick="showSection(\'' + sectionId + '\');switchTab(\'' + code + '\',\'' + tabId + '\')" data-section="' + sectionId + '-' + tabId + '">' +
|
|
2431
|
+
'<span class="nav-icon">●</span> ' + label +
|
|
2432
|
+
(badge !== undefined ? ' <span class="nav-badge">' + badge + '</span>' : '') +
|
|
2433
|
+
'</a>';
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
/* ---------- Collapse/Expand ---------- */
|
|
2437
|
+
|
|
2438
|
+
function toggleNavGroup(groupId) {
|
|
2439
|
+
navCollapseState[groupId] = !navCollapseState[groupId];
|
|
2440
|
+
var groupEl = document.querySelector('[data-group-id="' + groupId + '"]');
|
|
2441
|
+
if (groupEl) {
|
|
2442
|
+
var children = groupEl.querySelector(':scope > .nav-children');
|
|
2443
|
+
var chevron = groupEl.querySelector(':scope > .nav-group-title .nav-chevron, :scope > .nav-item.nav-module-header .nav-chevron');
|
|
2444
|
+
if (children) children.style.display = navCollapseState[groupId] ? 'none' : '';
|
|
2445
|
+
if (chevron) chevron.classList.toggle('expanded', !navCollapseState[groupId]);
|
|
2446
|
+
}
|
|
2447
|
+
saveNavState();
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
function saveNavState() {
|
|
2451
|
+
try { localStorage.setItem(APP_KEY + '-nav', JSON.stringify(navCollapseState)); } catch(e) {}
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
function restoreNavState() {
|
|
2455
|
+
try {
|
|
2456
|
+
var saved = localStorage.getItem(APP_KEY + '-nav');
|
|
2457
|
+
if (saved) {
|
|
2458
|
+
navCollapseState = JSON.parse(saved);
|
|
2459
|
+
// Apply collapse state to all groups
|
|
2460
|
+
Object.keys(navCollapseState).forEach(function(groupId) {
|
|
2461
|
+
if (navCollapseState[groupId]) {
|
|
2462
|
+
var groupEl = document.querySelector('[data-group-id="' + groupId + '"]');
|
|
2463
|
+
if (groupEl) {
|
|
2464
|
+
var children = groupEl.querySelector(':scope > .nav-children');
|
|
2465
|
+
var chevron = groupEl.querySelector(':scope > .nav-group-title .nav-chevron, :scope > .nav-item.nav-module-header .nav-chevron');
|
|
2466
|
+
if (children) children.style.display = 'none';
|
|
2467
|
+
if (chevron) chevron.classList.remove('expanded');
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
});
|
|
2471
|
+
}
|
|
2472
|
+
} catch(e) {}
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
function highlightActiveNavItem() {
|
|
2476
|
+
if (!currentSectionId) return;
|
|
2477
|
+
var navItem = document.querySelector('#sidebarNav [data-section="' + currentSectionId + '"]');
|
|
2478
|
+
if (navItem) navItem.classList.add('active');
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2217
2481
|
|
|
2218
2482
|
/* --- 03-render-cadrage.js --- */
|
|
2219
2483
|
/* ============================================
|
|
@@ -2433,6 +2697,7 @@ function addModule() {
|
|
|
2433
2697
|
featureType: document.getElementById('mod-type').value,
|
|
2434
2698
|
priority: document.getElementById('mod-priority').value,
|
|
2435
2699
|
entities: document.getElementById('mod-entities').value.split('\n').filter(e => e.trim()),
|
|
2700
|
+
anticipatedSections: [],
|
|
2436
2701
|
status: 'pending'
|
|
2437
2702
|
});
|
|
2438
2703
|
|
|
@@ -2489,14 +2754,7 @@ function renderModules() {
|
|
|
2489
2754
|
}
|
|
2490
2755
|
|
|
2491
2756
|
function updateModulesNav() {
|
|
2492
|
-
|
|
2493
|
-
const navItems = data.modules.map(m => `
|
|
2494
|
-
<a class="nav-item" onclick="showSection('module-spec-${m.code}')" data-section="module-spec-${m.code}">
|
|
2495
|
-
<span class="nav-icon">●</span> ${m.name}
|
|
2496
|
-
<span class="nav-badge">${(data.moduleSpecs[m.code]?.useCases || []).length}</span>
|
|
2497
|
-
</a>
|
|
2498
|
-
`).join('');
|
|
2499
|
-
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>');
|
|
2757
|
+
buildNavTree();
|
|
2500
2758
|
}
|
|
2501
2759
|
|
|
2502
2760
|
/* ============================================
|
|
@@ -2671,6 +2929,7 @@ function renderModuleSpecSection(mod) {
|
|
|
2671
2929
|
<button class="tab-btn" onclick="switchTab('${code}', 'perm')">Droits d'acces</button>
|
|
2672
2930
|
<button class="tab-btn" onclick="switchTab('${code}', 'mock')">Maquettes</button>
|
|
2673
2931
|
<button class="tab-btn" onclick="switchTab('${code}', 'notes')">Notes</button>
|
|
2932
|
+
<button class="tab-btn" onclick="switchTab('${code}', 'struct')">Structure</button>
|
|
2674
2933
|
</div>
|
|
2675
2934
|
|
|
2676
2935
|
<!-- TAB: Cas d'utilisation -->
|
|
@@ -2747,7 +3006,12 @@ function renderModuleSpecSection(mod) {
|
|
|
2747
3006
|
<div class="tab-panel" id="tab-${code}-ent">
|
|
2748
3007
|
<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>
|
|
2749
3008
|
<div id="entList-${code}">
|
|
2750
|
-
${spec.entities.
|
|
3009
|
+
${spec.entities.length > 0
|
|
3010
|
+
? spec.entities.map((ent, i) => renderEntity(code, ent, i)).join('')
|
|
3011
|
+
: (mod.entities || []).length > 0
|
|
3012
|
+
? '<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>'
|
|
3013
|
+
: ''
|
|
3014
|
+
}
|
|
2751
3015
|
</div>
|
|
2752
3016
|
<button class="add-btn" onclick="toggleForm('addEntForm-${code}')">+ Ajouter un type de donnees</button>
|
|
2753
3017
|
<div class="inline-form" id="addEntForm-${code}">
|
|
@@ -2824,6 +3088,14 @@ function renderModuleSpecSection(mod) {
|
|
|
2824
3088
|
<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>
|
|
2825
3089
|
</div>
|
|
2826
3090
|
</div>
|
|
3091
|
+
|
|
3092
|
+
<!-- TAB: Structure (sections/resources) -->
|
|
3093
|
+
<div class="tab-panel" id="tab-${code}-struct">
|
|
3094
|
+
<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>
|
|
3095
|
+
<div id="structContainer-${code}">
|
|
3096
|
+
${renderModuleStructure(code)}
|
|
3097
|
+
</div>
|
|
3098
|
+
</div>
|
|
2827
3099
|
</div>`;
|
|
2828
3100
|
}
|
|
2829
3101
|
|
|
@@ -3181,6 +3453,47 @@ function refreshAllPermGrids() {
|
|
|
3181
3453
|
});
|
|
3182
3454
|
}
|
|
3183
3455
|
|
|
3456
|
+
function renderModuleStructure(code) {
|
|
3457
|
+
const mod = data.modules.find(m => m.code === code);
|
|
3458
|
+
const sections = mod ? (mod.anticipatedSections || []) : [];
|
|
3459
|
+
|
|
3460
|
+
if (sections.length === 0) {
|
|
3461
|
+
return '<div class="card" style="text-align:center;padding:2rem;color:var(--text-muted);">' +
|
|
3462
|
+
'<p>Aucune section definie pour ce module.</p>' +
|
|
3463
|
+
'<p style="font-size:0.85rem;margin-top:0.5rem;">Les sections et ressources seront identifiees lors de l\'analyse approfondie.</p>' +
|
|
3464
|
+
'</div>';
|
|
3465
|
+
}
|
|
3466
|
+
|
|
3467
|
+
return sections.map(function(section) {
|
|
3468
|
+
var resources = section.resources || [];
|
|
3469
|
+
var html = '<div class="struct-section">';
|
|
3470
|
+
html += '<div class="struct-section-header">';
|
|
3471
|
+
html += '<span class="struct-section-code">' + (section.code || section.name || '') + '</span>';
|
|
3472
|
+
if (section.description) {
|
|
3473
|
+
html += '<span class="struct-section-desc">' + section.description + '</span>';
|
|
3474
|
+
}
|
|
3475
|
+
html += '<span class="struct-section-badge">' + resources.length + ' ressource' + (resources.length !== 1 ? 's' : '') + '</span>';
|
|
3476
|
+
html += '</div>';
|
|
3477
|
+
|
|
3478
|
+
if (resources.length > 0) {
|
|
3479
|
+
html += '<div class="struct-resources">';
|
|
3480
|
+
resources.forEach(function(res) {
|
|
3481
|
+
var resName = typeof res === 'string' ? res : (res.code || res.name || '');
|
|
3482
|
+
var resDesc = typeof res === 'object' ? (res.description || '') : '';
|
|
3483
|
+
html += '<div class="struct-resource">';
|
|
3484
|
+
html += '<span class="struct-resource-icon">•</span>';
|
|
3485
|
+
html += '<span class="struct-resource-name">' + resName + '</span>';
|
|
3486
|
+
if (resDesc) html += '<span class="struct-resource-desc">' + resDesc + '</span>';
|
|
3487
|
+
html += '</div>';
|
|
3488
|
+
});
|
|
3489
|
+
html += '</div>';
|
|
3490
|
+
}
|
|
3491
|
+
|
|
3492
|
+
html += '</div>';
|
|
3493
|
+
return html;
|
|
3494
|
+
}).join('');
|
|
3495
|
+
}
|
|
3496
|
+
|
|
3184
3497
|
function switchTab(code, tabId) {
|
|
3185
3498
|
const section = document.getElementById('module-spec-' + code);
|
|
3186
3499
|
if (!section) return;
|
|
@@ -3189,7 +3502,7 @@ function switchTab(code, tabId) {
|
|
|
3189
3502
|
const targetPanel = document.getElementById('tab-' + code + '-' + tabId);
|
|
3190
3503
|
if (targetPanel) targetPanel.classList.add('active');
|
|
3191
3504
|
const buttons = section.querySelectorAll('.tab-btn');
|
|
3192
|
-
const tabIndex = { uc: 0, br: 1, ent: 2, perm: 3, mock: 4, notes: 5 }[tabId];
|
|
3505
|
+
const tabIndex = { uc: 0, br: 1, ent: 2, perm: 3, mock: 4, notes: 5, struct: 6 }[tabId];
|
|
3193
3506
|
if (buttons[tabIndex]) buttons[tabIndex].classList.add('active');
|
|
3194
3507
|
}
|
|
3195
3508
|
|
|
@@ -3471,7 +3784,9 @@ function renderCoverageMatrix() {
|
|
|
3471
3784
|
<thead><tr><th>Besoin</th><th>Priorite</th><th>Domaine</th><th>Couvert</th></tr></thead>
|
|
3472
3785
|
<tbody>
|
|
3473
3786
|
${allScope.map(item => {
|
|
3474
|
-
const moduleName =
|
|
3787
|
+
const moduleName = item.module
|
|
3788
|
+
? (data.modules.find(m => m.code === item.module)?.name || item.module)
|
|
3789
|
+
: (data.modules.length > 0 ? data.modules.map(m => m.name).join(', ') : 'A definir');
|
|
3475
3790
|
return `
|
|
3476
3791
|
<tr>
|
|
3477
3792
|
<td>${item.name}</td>
|
|
@@ -3499,37 +3814,127 @@ function saveToLocalStorage() {
|
|
|
3499
3814
|
showNotification('Modifications sauvegardees');
|
|
3500
3815
|
}
|
|
3501
3816
|
|
|
3817
|
+
function resetToEmbedded() {
|
|
3818
|
+
if (!confirm('Reinitialiser toutes les donnees depuis la version d\'origine ? Vos modifications locales (commentaires, notes) seront perdues.')) return;
|
|
3819
|
+
localStorage.removeItem(APP_KEY);
|
|
3820
|
+
// Restore data from ORIGINAL_DATA
|
|
3821
|
+
Object.keys(data).forEach(k => delete data[k]);
|
|
3822
|
+
Object.assign(data, JSON.parse(JSON.stringify(ORIGINAL_DATA)));
|
|
3823
|
+
// Re-render everything
|
|
3824
|
+
initEditableFields();
|
|
3825
|
+
renderStakeholders();
|
|
3826
|
+
renderScope();
|
|
3827
|
+
renderRisks();
|
|
3828
|
+
renderCriteria();
|
|
3829
|
+
renderModules();
|
|
3830
|
+
renderDependencies();
|
|
3831
|
+
renderAllModuleSpecs();
|
|
3832
|
+
renderConsolidation();
|
|
3833
|
+
renderHandoff();
|
|
3834
|
+
renderE2EFlows();
|
|
3835
|
+
updateCounts();
|
|
3836
|
+
renderReviewPanel();
|
|
3837
|
+
showNotification('Donnees reinitialisees depuis la version d\'origine');
|
|
3838
|
+
}
|
|
3839
|
+
|
|
3502
3840
|
function loadFromLocalStorage() {
|
|
3503
3841
|
const saved = localStorage.getItem(APP_KEY);
|
|
3504
|
-
if (saved)
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3842
|
+
if (!saved) return;
|
|
3843
|
+
|
|
3844
|
+
try {
|
|
3845
|
+
const parsed = JSON.parse(saved);
|
|
3846
|
+
|
|
3847
|
+
// Build fingerprint of embedded structural data to detect HTML regeneration
|
|
3848
|
+
const embeddedFingerprint = ORIGINAL_DATA.modules.map(m => m.code).sort().join(',')
|
|
3849
|
+
+ '|' + (ORIGINAL_DATA.metadata?.createdAt || '')
|
|
3850
|
+
+ '|' + ORIGINAL_DATA.modules.length;
|
|
3851
|
+
const cachedFingerprint = parsed._structureFingerprint || '';
|
|
3852
|
+
|
|
3853
|
+
const structureChanged = embeddedFingerprint !== cachedFingerprint;
|
|
3854
|
+
const embeddedModuleCount = ORIGINAL_DATA.modules?.length || 0;
|
|
3855
|
+
const cachedModuleCount = (parsed.modules || []).length;
|
|
3856
|
+
const hasNewModules = embeddedModuleCount > cachedModuleCount;
|
|
3857
|
+
const embeddedModuleCodes = new Set(ORIGINAL_DATA.modules.map(m => m.code));
|
|
3858
|
+
const cachedModuleCodes = new Set((parsed.modules || []).map(m => m.code));
|
|
3859
|
+
const missingModules = [...embeddedModuleCodes].filter(c => !cachedModuleCodes.has(c));
|
|
3860
|
+
|
|
3861
|
+
if (structureChanged || hasNewModules || missingModules.length > 0) {
|
|
3862
|
+
// HTML was regenerated or has new modules — keep embedded structural data
|
|
3863
|
+
// Only restore user-specific edits (comments, custom roles, notes)
|
|
3864
|
+
data.wireframeComments = parsed.wireframeComments || {};
|
|
3865
|
+
data.specComments = parsed.specComments || {};
|
|
3866
|
+
data.customRoles = parsed.customRoles || [];
|
|
3867
|
+
data.customActions = parsed.customActions || [];
|
|
3868
|
+
data.comments = parsed.comments || [];
|
|
3869
|
+
|
|
3870
|
+
// Merge user-added notes per module (preserve existing module notes)
|
|
3871
|
+
for (const code of Object.keys(parsed.moduleSpecs || {})) {
|
|
3872
|
+
if (data.moduleSpecs[code] && parsed.moduleSpecs[code]?.notes) {
|
|
3873
|
+
data.moduleSpecs[code].notes = parsed.moduleSpecs[code].notes;
|
|
3874
|
+
}
|
|
3875
|
+
}
|
|
3876
|
+
|
|
3877
|
+
// Save fresh embedded data with fingerprint
|
|
3878
|
+
data._structureFingerprint = embeddedFingerprint;
|
|
3879
|
+
autoSave();
|
|
3880
|
+
} else {
|
|
3881
|
+
// Cache matches current structure — safe to restore user edits on cadrage/notes
|
|
3882
|
+
// IMPORTANT: Always keep embedded modules and moduleSpecs as structural source of truth
|
|
3883
|
+
// Only merge cadrage user-editable fields and notes
|
|
3884
|
+
if (parsed.cadrage) {
|
|
3885
|
+
// Merge cadrage context (user may have edited text fields)
|
|
3886
|
+
if (parsed.cadrage.context) {
|
|
3887
|
+
data.cadrage.context = { ...data.cadrage.context, ...parsed.cadrage.context };
|
|
3888
|
+
}
|
|
3889
|
+
// Merge scope only if user added items interactively
|
|
3890
|
+
if (parsed.cadrage.scope) {
|
|
3891
|
+
data.cadrage.scope = { ...data.cadrage.scope, ...parsed.cadrage.scope };
|
|
3892
|
+
}
|
|
3893
|
+
// Merge stakeholders and risks if user edited them
|
|
3894
|
+
if (parsed.cadrage.stakeholders) data.cadrage.stakeholders = parsed.cadrage.stakeholders;
|
|
3895
|
+
if (parsed.cadrage.risks) data.cadrage.risks = parsed.cadrage.risks;
|
|
3896
|
+
}
|
|
3897
|
+
data.dependencies = parsed.dependencies || data.dependencies;
|
|
3517
3898
|
data.consolidation = { ...data.consolidation, ...(parsed.consolidation || {}) };
|
|
3518
|
-
|
|
3899
|
+
|
|
3900
|
+
// Merge moduleSpecs: keep embedded structure, overlay user-editable fields (notes, custom permissions)
|
|
3901
|
+
for (const code of Object.keys(data.moduleSpecs)) {
|
|
3902
|
+
const cached = parsed.moduleSpecs?.[code];
|
|
3903
|
+
if (cached) {
|
|
3904
|
+
// Preserve user notes
|
|
3905
|
+
if (cached.notes) data.moduleSpecs[code].notes = cached.notes;
|
|
3906
|
+
// Preserve user-added use cases/rules/entities (merged with embedded)
|
|
3907
|
+
// Only add items that don't exist in embedded (by name match)
|
|
3908
|
+
const embeddedUcNames = new Set((data.moduleSpecs[code].useCases || []).map(uc => uc.name));
|
|
3909
|
+
(cached.useCases || []).forEach(uc => {
|
|
3910
|
+
if (!embeddedUcNames.has(uc.name)) data.moduleSpecs[code].useCases.push(uc);
|
|
3911
|
+
});
|
|
3912
|
+
const embeddedBrNames = new Set((data.moduleSpecs[code].businessRules || []).map(br => br.name));
|
|
3913
|
+
(cached.businessRules || []).forEach(br => {
|
|
3914
|
+
if (!embeddedBrNames.has(br.name)) data.moduleSpecs[code].businessRules.push(br);
|
|
3915
|
+
});
|
|
3916
|
+
// Preserve user permissions edits
|
|
3917
|
+
if (cached.permissions) data.moduleSpecs[code].permissions = cached.permissions;
|
|
3918
|
+
}
|
|
3919
|
+
}
|
|
3920
|
+
|
|
3519
3921
|
data.wireframeComments = parsed.wireframeComments || {};
|
|
3520
3922
|
data.specComments = parsed.specComments || {};
|
|
3521
3923
|
data.customRoles = parsed.customRoles || [];
|
|
3522
3924
|
data.customActions = parsed.customActions || [];
|
|
3523
3925
|
data.comments = parsed.comments || [];
|
|
3524
3926
|
|
|
3525
|
-
//
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3927
|
+
// Update fingerprint
|
|
3928
|
+
data._structureFingerprint = embeddedFingerprint;
|
|
3929
|
+
autoSave();
|
|
3930
|
+
}
|
|
3931
|
+
|
|
3932
|
+
// Restore editable fields
|
|
3933
|
+
document.querySelectorAll('.editable[data-field]').forEach(el => {
|
|
3934
|
+
const value = getNestedValue(data, 'cadrage.' + el.dataset.field);
|
|
3935
|
+
if (value) el.textContent = value;
|
|
3936
|
+
});
|
|
3937
|
+
} catch (e) { console.error('Error loading saved data:', e); }
|
|
3533
3938
|
}
|
|
3534
3939
|
|
|
3535
3940
|
|
|
@@ -3999,6 +4404,13 @@ function getSectionLabel(sectionId) {
|
|
|
3999
4404
|
const mod = data.modules.find(m => m.code === code);
|
|
4000
4405
|
return mod ? mod.name : code;
|
|
4001
4406
|
}
|
|
4407
|
+
// Handle module structure sections (module-struct-{code}-{section})
|
|
4408
|
+
const structMatch = sectionId.match(/^module-struct-(.+?)-(.+)$/);
|
|
4409
|
+
if (structMatch) {
|
|
4410
|
+
const mod = data.modules.find(m => m.code === structMatch[1]);
|
|
4411
|
+
const modName = mod ? mod.name : structMatch[1];
|
|
4412
|
+
return modName + ' > Structure > ' + structMatch[2];
|
|
4413
|
+
}
|
|
4002
4414
|
// Handle list-based sectionIds (ucList-*, brList-*, entList-*)
|
|
4003
4415
|
const listMatch = sectionId.match(/^(uc|br|ent)List-(.+)$/);
|
|
4004
4416
|
if (listMatch) {
|