@atlashub/smartstack-cli 4.41.0 → 4.42.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/.documentation/apex.html +2 -2
- package/.documentation/business-analyse.html +26 -27
- package/.documentation/commands.html +6 -6
- package/dist/index.js +24 -13
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/templates/agents/ba-reader.md +2 -2
- package/templates/agents/ba-writer.md +44 -9
- package/templates/hooks/stop-hook.sh +6 -6
- package/templates/ralph/README.md +1 -1
- package/templates/scripts/setup-ralph-loop.sh +2 -2
- package/templates/skills/_resources/context-digest-template.md +1 -1
- package/templates/skills/_shared.md +13 -13
- package/templates/skills/apex/SKILL.md +14 -7
- package/templates/skills/apex/_shared.md +1 -1
- package/templates/skills/apex/references/challenge-questions.md +46 -13
- package/templates/skills/apex/references/core-seed-data.md +4 -4
- package/templates/skills/apex/references/error-classification.md +3 -3
- package/templates/skills/apex/references/smartstack-api.md +1 -1
- package/templates/skills/apex/references/smartstack-layers.md +1 -1
- package/templates/skills/apex/steps/step-00-init.md +46 -8
- package/templates/skills/apex/steps/step-01-analyze.md +1 -1
- package/templates/skills/apex/steps/step-02-plan.md +1 -1
- package/templates/skills/apex/steps/step-03-execute.md +1 -1
- package/templates/skills/business-analyse/SKILL.md +83 -22
- package/templates/skills/business-analyse/_shared.md +12 -9
- package/templates/skills/business-analyse/questionnaire/02-stakeholders-scope.md +13 -0
- package/templates/skills/business-analyse/questionnaire/03-data-ui.md +33 -0
- package/templates/skills/business-analyse/questionnaire/04-risks-metrics.md +1 -1
- package/templates/skills/business-analyse/react/components.md +1 -1
- package/templates/skills/business-analyse/react/schema.md +1 -1
- package/templates/skills/business-analyse/references/acceptance-criteria.md +3 -3
- package/templates/skills/business-analyse/references/consolidation-structural-checks.md +1 -1
- package/templates/skills/business-analyse/references/detection-strategies.md +2 -2
- package/templates/skills/business-analyse/references/entity-architecture-decision.md +1 -1
- package/templates/skills/business-analyse/references/naming-conventions.md +6 -6
- package/templates/skills/business-analyse/references/robustness-checks.md +4 -4
- package/templates/skills/business-analyse/references/spec-auto-inference.md +2 -2
- package/templates/skills/business-analyse/references/validation-checklist.md +3 -3
- package/templates/skills/business-analyse/schemas/feature-schema.json +1 -1
- package/templates/skills/business-analyse/schemas/sections/handoff-schema.json +2 -2
- package/templates/skills/business-analyse/schemas/sections/specification-schema.json +3 -2
- package/templates/skills/business-analyse/steps/step-00-init.md +15 -5
- package/templates/skills/business-analyse/steps/step-01-cadrage.md +14 -5
- package/templates/skills/business-analyse/steps/step-02-structure.md +17 -1
- package/templates/skills/business-analyse/steps/step-03-specify.md +136 -26
- package/templates/skills/business-analyse/steps/step-04-consolidate.md +44 -8
- package/templates/skills/business-analyse/templates/tpl-handoff.md +5 -5
- package/templates/skills/business-analyse/templates/tpl-launch-displays.md +4 -4
- package/templates/skills/business-analyse/templates-frd.md +4 -4
- package/templates/skills/{ba-design-ui → business-analyse-design}/SKILL.md +9 -9
- package/templates/skills/{ba-design-ui → business-analyse-design}/steps/step-01-screens.md +9 -0
- package/templates/skills/{ba-design-ui → business-analyse-design}/steps/step-03-navigation.md +9 -2
- package/templates/skills/business-analyse-develop/SKILL.md +248 -0
- package/templates/skills/{ralph-loop → business-analyse-develop}/references/category-completeness.md +1 -1
- package/templates/skills/{ralph-loop → business-analyse-develop}/references/init-resume-recovery.md +8 -8
- package/templates/skills/{ralph-loop → business-analyse-develop}/references/multi-module-queue.md +1 -1
- package/templates/skills/business-analyse-develop/references/quality-gates.md +70 -0
- package/templates/skills/{ralph-loop → business-analyse-develop}/references/task-transform-legacy.md +1 -1
- package/templates/skills/{ralph-loop → business-analyse-develop}/steps/step-00-init.md +20 -4
- package/templates/skills/{ralph-loop → business-analyse-develop}/steps/step-01-task.md +3 -2
- package/templates/skills/business-analyse-develop/steps/step-01-v4-execute.md +131 -0
- package/templates/skills/business-analyse-develop/steps/step-02-v4-verify.md +156 -0
- package/templates/skills/{ralph-loop → business-analyse-develop}/steps/step-04-check.md +1 -1
- package/templates/skills/{ralph-loop → business-analyse-develop}/steps/step-05-report.md +1 -1
- package/templates/skills/{derive-prd → business-analyse-handoff}/SKILL.md +7 -7
- package/templates/skills/{derive-prd → business-analyse-handoff}/references/acceptance-criteria.md +5 -5
- package/templates/skills/{derive-prd → business-analyse-handoff}/references/handoff-file-templates.md +1 -1
- package/templates/skills/{derive-prd → business-analyse-handoff}/references/handoff-mappings.md +1 -1
- package/templates/skills/{derive-prd → business-analyse-handoff}/references/handoff-seeddata-generation.md +2 -2
- package/templates/skills/{derive-prd → business-analyse-handoff}/references/prd-generation.md +14 -14
- package/templates/skills/{derive-prd → business-analyse-handoff}/schemas/handoff-schema.json +2 -2
- package/templates/skills/{derive-prd → business-analyse-handoff}/steps/step-00-validate.md +6 -6
- package/templates/skills/{derive-prd → business-analyse-handoff}/steps/step-01-transform.md +46 -7
- package/templates/skills/{derive-prd → business-analyse-handoff}/steps/step-02-export.md +34 -14
- package/templates/skills/{ba-generate-html → business-analyse-html}/SKILL.md +4 -4
- package/templates/skills/{ba-generate-html → business-analyse-html}/html/ba-interactive.html +709 -277
- package/templates/skills/{ba-generate-html → business-analyse-html}/html/build-html.js +25 -3
- package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/scripts/01-data-init.js +54 -0
- package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/scripts/02-navigation.js +97 -3
- package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/scripts/03-render-cadrage.js +8 -7
- package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/scripts/04-render-modules.js +7 -7
- package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/scripts/05-render-specs.js +188 -85
- package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/scripts/06-render-consolidation.js +15 -14
- package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/scripts/06-render-mockups.js +19 -19
- package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/scripts/07-render-handoff.js +24 -4
- package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/scripts/08-editing.js +6 -2
- package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/scripts/09-export.js +27 -57
- package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/scripts/10-comments.js +67 -45
- package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/scripts/11-review-panel.js +15 -13
- package/templates/skills/business-analyse-html/html/src/styles/02-layout.css +216 -0
- package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/styles/05-modules.css +36 -0
- package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/template.html +22 -12
- package/templates/skills/{ba-generate-html → business-analyse-html}/references/data-build.md +1 -1
- package/templates/skills/{ba-generate-html → business-analyse-html}/references/data-mapping.md +5 -1
- package/templates/skills/{ba-generate-html → business-analyse-html}/references/output-modes.md +7 -7
- package/templates/skills/{ba-generate-html → business-analyse-html}/steps/step-01-collect.md +25 -1
- package/templates/skills/{ba-generate-html → business-analyse-html}/steps/step-02-build-data.md +33 -5
- package/templates/skills/{ba-generate-html → business-analyse-html}/steps/step-03-render.md +2 -2
- package/templates/skills/{ba-generate-html → business-analyse-html}/steps/step-04-verify.md +2 -2
- package/templates/skills/{ba-review → business-analyse-review}/SKILL.md +11 -10
- package/templates/skills/{ba-review → business-analyse-review}/references/review-data-mapping.md +2 -2
- package/templates/skills/business-analyse-review/steps/step-00-init.md +107 -0
- package/templates/skills/{ba-review → business-analyse-review}/steps/step-01-apply.md +19 -11
- package/templates/skills/business-analyse-status/SKILL.md +118 -0
- package/templates/skills/documentation/SKILL.md +2 -2
- package/templates/skills/sketch/SKILL.md +172 -0
- package/templates/skills/sketch/references/domain-heuristics.md +116 -0
- package/templates/skills/ba-generate-html/html/src/styles/02-layout.css +0 -101
- package/templates/skills/ralph-loop/SKILL.md +0 -240
- /package/templates/skills/{ba-design-ui → business-analyse-design}/steps/step-02-wireframes.md +0 -0
- /package/templates/skills/{ralph-loop → business-analyse-develop}/references/category-rules.md +0 -0
- /package/templates/skills/{ralph-loop → business-analyse-develop}/references/compact-loop.md +0 -0
- /package/templates/skills/{ralph-loop → business-analyse-develop}/references/module-transition.md +0 -0
- /package/templates/skills/{ralph-loop → business-analyse-develop}/references/parallel-execution.md +0 -0
- /package/templates/skills/{ralph-loop → business-analyse-develop}/references/section-splitting.md +0 -0
- /package/templates/skills/{ralph-loop → business-analyse-develop}/references/team-orchestration.md +0 -0
- /package/templates/skills/{ralph-loop → business-analyse-develop}/steps/step-02-execute.md +0 -0
- /package/templates/skills/{ralph-loop → business-analyse-develop}/steps/step-03-commit.md +0 -0
- /package/templates/skills/{derive-prd → business-analyse-handoff}/references/entity-domain-mapping.md +0 -0
- /package/templates/skills/{derive-prd → business-analyse-handoff}/references/readiness-scoring.md +0 -0
- /package/templates/skills/{derive-prd → business-analyse-handoff}/templates/tpl-progress.md +0 -0
- /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/partials/cadrage-context.html +0 -0
- /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/partials/cadrage-scope.html +0 -0
- /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/partials/cadrage-stakeholders.html +0 -0
- /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/partials/cadrage-success.html +0 -0
- /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/partials/consol-datamodel.html +0 -0
- /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/partials/consol-flows.html +0 -0
- /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/partials/consol-interactions.html +0 -0
- /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/partials/consol-permissions.html +0 -0
- /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/partials/decomp-dependencies.html +0 -0
- /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/partials/decomp-modules.html +0 -0
- /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/partials/handoff-summary.html +0 -0
- /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/partials/module-spec-container.html +0 -0
- /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/styles/01-variables.css +0 -0
- /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/styles/03-navigation.css +0 -0
- /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/styles/04-cards.css +0 -0
- /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/styles/06-wireframes.css +0 -0
- /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/styles/07-comments.css +0 -0
- /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/styles/08-review-panel.css +0 -0
- /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/styles/09-mockups-html.css +0 -0
- /package/templates/skills/{ba-generate-html → business-analyse-html}/references/wireframe-svg-style-guide.md +0 -0
package/templates/skills/{ba-generate-html → business-analyse-html}/html/ba-interactive.html
RENAMED
|
@@ -128,25 +128,140 @@ body {
|
|
|
128
128
|
/* ============================================
|
|
129
129
|
RESPONSIVE
|
|
130
130
|
============================================ */
|
|
131
|
+
.hamburger-btn {
|
|
132
|
+
display: none;
|
|
133
|
+
background: none;
|
|
134
|
+
border: 1px solid var(--border);
|
|
135
|
+
border-radius: 6px;
|
|
136
|
+
color: var(--text);
|
|
137
|
+
font-size: 1.2rem;
|
|
138
|
+
padding: 0.2rem 0.5rem;
|
|
139
|
+
cursor: pointer;
|
|
140
|
+
flex-shrink: 0;
|
|
141
|
+
}
|
|
142
|
+
.sidebar-overlay {
|
|
143
|
+
display: none;
|
|
144
|
+
position: fixed;
|
|
145
|
+
inset: 0;
|
|
146
|
+
background: rgba(0,0,0,0.5);
|
|
147
|
+
z-index: 149;
|
|
148
|
+
}
|
|
149
|
+
.sidebar-overlay.visible { display: block; }
|
|
150
|
+
|
|
131
151
|
@media (max-width: 768px) {
|
|
132
|
-
.
|
|
152
|
+
.hamburger-btn { display: block; }
|
|
153
|
+
.sidebar {
|
|
154
|
+
display: none;
|
|
155
|
+
position: fixed;
|
|
156
|
+
top: 0;
|
|
157
|
+
left: 0;
|
|
158
|
+
height: 100vh;
|
|
159
|
+
z-index: 150;
|
|
160
|
+
box-shadow: 4px 0 24px rgba(0,0,0,0.3);
|
|
161
|
+
}
|
|
162
|
+
.sidebar.mobile-open { display: block; }
|
|
133
163
|
.main { padding: 1rem; }
|
|
134
164
|
.form-row { grid-template-columns: 1fr; }
|
|
135
165
|
.stakeholder-grid { grid-template-columns: 1fr; }
|
|
166
|
+
.module-grid { grid-template-columns: 1fr; }
|
|
167
|
+
.stat-grid { grid-template-columns: repeat(2, 1fr); }
|
|
168
|
+
.dm-entity-grid { grid-template-columns: 1fr; }
|
|
169
|
+
.mock-form-row { grid-template-columns: 1fr; }
|
|
170
|
+
.mock-kpi-grid { grid-template-columns: repeat(2, 1fr); }
|
|
171
|
+
.header-actions { gap: 0.25rem; }
|
|
172
|
+
.header-actions .btn-sm { padding: 0.2rem 0.4rem; font-size: 0.7rem; }
|
|
173
|
+
.header-title { display: none; }
|
|
174
|
+
.header-sep { display: none; }
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/* ============================================
|
|
178
|
+
SEARCH
|
|
179
|
+
============================================ */
|
|
180
|
+
.sidebar-search {
|
|
181
|
+
padding: 0.5rem 0.75rem;
|
|
182
|
+
border-bottom: 1px solid var(--border);
|
|
183
|
+
}
|
|
184
|
+
.sidebar-search input {
|
|
185
|
+
width: 100%;
|
|
186
|
+
padding: 0.4rem 0.6rem;
|
|
187
|
+
background: var(--bg-input);
|
|
188
|
+
border: 1px solid var(--border);
|
|
189
|
+
border-radius: 6px;
|
|
190
|
+
color: var(--text);
|
|
191
|
+
font-size: 0.8rem;
|
|
192
|
+
font-family: inherit;
|
|
193
|
+
}
|
|
194
|
+
.sidebar-search input:focus {
|
|
195
|
+
outline: none;
|
|
196
|
+
border-color: var(--primary);
|
|
197
|
+
}
|
|
198
|
+
.nav-item.search-hidden { display: none; }
|
|
199
|
+
.nav-group.search-hidden { display: none; }
|
|
200
|
+
.search-highlight { background: rgba(234,179,8,0.3); border-radius: 2px; }
|
|
201
|
+
|
|
202
|
+
/* ============================================
|
|
203
|
+
PROGRESS INDICATOR
|
|
204
|
+
============================================ */
|
|
205
|
+
.progress-bar-container {
|
|
206
|
+
width: 100%;
|
|
207
|
+
height: 8px;
|
|
208
|
+
background: var(--bg-hover);
|
|
209
|
+
border-radius: 4px;
|
|
210
|
+
overflow: hidden;
|
|
211
|
+
margin-bottom: 0.5rem;
|
|
212
|
+
}
|
|
213
|
+
.progress-bar-fill {
|
|
214
|
+
height: 100%;
|
|
215
|
+
background: linear-gradient(90deg, var(--primary), var(--success));
|
|
216
|
+
border-radius: 4px;
|
|
217
|
+
transition: width 0.5s ease;
|
|
218
|
+
}
|
|
219
|
+
.progress-label {
|
|
220
|
+
font-size: 0.8rem;
|
|
221
|
+
color: var(--text-muted);
|
|
222
|
+
margin-bottom: 0.75rem;
|
|
136
223
|
}
|
|
224
|
+
.progress-checks {
|
|
225
|
+
display: grid;
|
|
226
|
+
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
|
227
|
+
gap: 0.3rem;
|
|
228
|
+
}
|
|
229
|
+
.progress-check {
|
|
230
|
+
font-size: 0.8rem;
|
|
231
|
+
color: var(--text-muted);
|
|
232
|
+
display: flex;
|
|
233
|
+
align-items: center;
|
|
234
|
+
gap: 0.4rem;
|
|
235
|
+
}
|
|
236
|
+
.progress-check.done { color: var(--success); }
|
|
237
|
+
.progress-check-icon { font-size: 0.9rem; width: 16px; text-align: center; }
|
|
137
238
|
|
|
138
239
|
/* ============================================
|
|
139
240
|
PRINT
|
|
140
241
|
============================================ */
|
|
141
242
|
@media print {
|
|
142
|
-
.sidebar, .header-actions, .add-btn, .uc-actions, .inline-form, .module-card-remove { display: none !important; }
|
|
143
|
-
.main { max-width: 100%; padding: 0; }
|
|
144
|
-
.
|
|
145
|
-
|
|
146
|
-
.
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
.
|
|
243
|
+
.sidebar, .header-actions, .add-btn, .uc-actions, .inline-form, .module-card-remove, .hamburger-btn, .sidebar-search { display: none !important; }
|
|
244
|
+
.main { max-width: 100%; padding: 0.5cm; height: auto; overflow: visible; }
|
|
245
|
+
.body { display: block; }
|
|
246
|
+
.section { display: block !important; page-break-inside: avoid; margin-bottom: 1cm; }
|
|
247
|
+
.section + .section { page-break-before: auto; }
|
|
248
|
+
body { background: #fff !important; color: #1a1a1a !important; }
|
|
249
|
+
* { color-adjust: exact; -webkit-print-color-adjust: exact; }
|
|
250
|
+
.card, .uc-item, .br-item, .module-card, .entity-block, .interaction-item {
|
|
251
|
+
border-color: #ddd !important;
|
|
252
|
+
background: #fff !important;
|
|
253
|
+
color: #1a1a1a !important;
|
|
254
|
+
}
|
|
255
|
+
.card-label, .uc-detail-label, .section-subtitle, .form-label { color: #666 !important; }
|
|
256
|
+
.section-title { color: #1a1a1a !important; border-bottom-color: #333 !important; }
|
|
257
|
+
.tab-panel { display: block !important; page-break-inside: avoid; }
|
|
258
|
+
.tab-bar { display: none !important; }
|
|
259
|
+
.review-panel, .comment-btn-container, .notification, .wireframe-comment, [contenteditable] { display: none !important; }
|
|
260
|
+
.editable { border: none !important; padding: 0 !important; }
|
|
261
|
+
.header { position: static; background: #fff !important; border-bottom-color: #ddd !important; }
|
|
262
|
+
.header-logo { background: #333 !important; }
|
|
263
|
+
.header-title, .header-app-name { color: #1a1a1a !important; }
|
|
264
|
+
a { text-decoration: none !important; color: inherit !important; }
|
|
150
265
|
}
|
|
151
266
|
|
|
152
267
|
|
|
@@ -916,6 +1031,42 @@ body {
|
|
|
916
1031
|
color: var(--text-muted);
|
|
917
1032
|
}
|
|
918
1033
|
|
|
1034
|
+
/* ============================================
|
|
1035
|
+
SECTION GROUPS (Hierarchical Mode)
|
|
1036
|
+
============================================ */
|
|
1037
|
+
.section-group {
|
|
1038
|
+
margin-bottom: 1.5rem;
|
|
1039
|
+
border-left: 3px solid var(--primary);
|
|
1040
|
+
padding-left: 0;
|
|
1041
|
+
}
|
|
1042
|
+
.section-group-header {
|
|
1043
|
+
display: flex;
|
|
1044
|
+
align-items: center;
|
|
1045
|
+
gap: 0.5rem;
|
|
1046
|
+
padding: 0.5rem 0.75rem;
|
|
1047
|
+
background: rgba(99,102,241,0.05);
|
|
1048
|
+
border-bottom: 1px solid var(--border);
|
|
1049
|
+
margin-bottom: 0.5rem;
|
|
1050
|
+
border-radius: 0 6px 0 0;
|
|
1051
|
+
}
|
|
1052
|
+
.section-group-icon {
|
|
1053
|
+
font-size: 0.6rem;
|
|
1054
|
+
color: var(--primary-light);
|
|
1055
|
+
}
|
|
1056
|
+
.section-group-label {
|
|
1057
|
+
font-weight: 600;
|
|
1058
|
+
color: var(--primary-light);
|
|
1059
|
+
font-size: 0.9rem;
|
|
1060
|
+
}
|
|
1061
|
+
.section-group-route {
|
|
1062
|
+
font-size: 0.7rem;
|
|
1063
|
+
color: var(--text-muted);
|
|
1064
|
+
font-family: 'Courier New', monospace;
|
|
1065
|
+
background: var(--bg-input);
|
|
1066
|
+
padding: 0.1rem 0.4rem;
|
|
1067
|
+
border-radius: 3px;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
919
1070
|
|
|
920
1071
|
/* --- 06-wireframes.css --- */
|
|
921
1072
|
/* ============================================
|
|
@@ -1768,20 +1919,21 @@ body {
|
|
|
1768
1919
|
<!-- ============================================
|
|
1769
1920
|
HEADER
|
|
1770
1921
|
============================================ -->
|
|
1771
|
-
<header class="header">
|
|
1772
|
-
<
|
|
1922
|
+
<header class="header" role="banner">
|
|
1923
|
+
<button class="hamburger-btn" onclick="toggleMobileSidebar()" aria-label="Ouvrir le menu de navigation">☰</button>
|
|
1924
|
+
<div class="header-logo" aria-hidden="true">BA</div>
|
|
1773
1925
|
<span class="header-title">Analyse métier</span>
|
|
1774
1926
|
<div class="header-sep"></div>
|
|
1775
1927
|
<span class="header-app-name" id="appName">{{APPLICATION_NAME}}</span>
|
|
1776
1928
|
<div class="header-spacer"></div>
|
|
1777
1929
|
<div class="header-actions">
|
|
1778
|
-
<button class="btn btn-sm" onclick="resetToEmbedded()" title="Réinitialiser depuis les données d'origine (supprime les modifications locales)">Reset</button>
|
|
1779
|
-
<button class="btn btn-sm" onclick="saveToLocalStorage()" title="Sauvegarder les modifications dans le navigateur">Sauvegarder</button>
|
|
1780
|
-
<button class="btn btn-sm btn-review" onclick="saveReviewJSON()" title="Sauvegarder les corrections pour créer une nouvelle version">Sauvegarder corrections</button>
|
|
1781
|
-
<button class="btn btn-sm btn-primary" onclick="exportJSON()" title="Exporter les données au format JSON pour l'extraction">Exporter JSON</button>
|
|
1782
|
-
<button class="btn btn-sm review-toggle-btn" id="reviewToggleBtn" onclick="toggleReviewPanel()" title="Ouvrir/fermer le panneau de review">
|
|
1930
|
+
<button class="btn btn-sm" onclick="resetToEmbedded()" title="Réinitialiser depuis les données d'origine (supprime les modifications locales)" aria-label="Réinitialiser les données">Reset</button>
|
|
1931
|
+
<button class="btn btn-sm" onclick="saveToLocalStorage()" title="Sauvegarder les modifications dans le navigateur" aria-label="Sauvegarder">Sauvegarder</button>
|
|
1932
|
+
<button class="btn btn-sm btn-review" onclick="saveReviewJSON()" title="Sauvegarder les corrections pour créer une nouvelle version" aria-label="Sauvegarder les corrections">Sauvegarder corrections</button>
|
|
1933
|
+
<button class="btn btn-sm btn-primary" onclick="exportJSON()" title="Exporter les données au format JSON pour l'extraction" aria-label="Exporter en JSON">Exporter JSON</button>
|
|
1934
|
+
<button class="btn btn-sm review-toggle-btn" id="reviewToggleBtn" onclick="toggleReviewPanel()" title="Ouvrir/fermer le panneau de review" aria-label="Panneau de review">
|
|
1783
1935
|
Review
|
|
1784
|
-
<span class="review-badge hidden" id="reviewBadge">0</span>
|
|
1936
|
+
<span class="review-badge hidden" id="reviewBadge" aria-live="polite">0</span>
|
|
1785
1937
|
</button>
|
|
1786
1938
|
</div>
|
|
1787
1939
|
</header>
|
|
@@ -1790,11 +1942,16 @@ body {
|
|
|
1790
1942
|
<!-- ============================================
|
|
1791
1943
|
SIDEBAR - Navigation hiérarchique
|
|
1792
1944
|
============================================ -->
|
|
1793
|
-
<
|
|
1945
|
+
<div class="sidebar-overlay" id="sidebarOverlay" onclick="toggleMobileSidebar()"></div>
|
|
1946
|
+
<aside class="sidebar" id="sidebarAside" role="navigation" aria-label="Navigation principale">
|
|
1794
1947
|
<!-- Application Name -->
|
|
1795
1948
|
<div class="sidebar-header">
|
|
1796
1949
|
<span class="sidebar-app-name" id="sidebarAppName">{{APPLICATION_NAME}}</span>
|
|
1797
1950
|
</div>
|
|
1951
|
+
<!-- Search -->
|
|
1952
|
+
<div class="sidebar-search">
|
|
1953
|
+
<input type="search" id="sidebarSearchInput" placeholder="Rechercher..." oninput="filterNavItems(this.value)" aria-label="Rechercher dans le document">
|
|
1954
|
+
</div>
|
|
1798
1955
|
<!-- Dynamic Tree Navigation -->
|
|
1799
1956
|
<div id="sidebarNav">
|
|
1800
1957
|
<!-- Populated by buildNavTree() -->
|
|
@@ -1804,7 +1961,7 @@ body {
|
|
|
1804
1961
|
<!-- ============================================
|
|
1805
1962
|
MAIN CONTENT
|
|
1806
1963
|
============================================ -->
|
|
1807
|
-
<main class="main" id="mainContent">
|
|
1964
|
+
<main class="main" id="mainContent" role="main">
|
|
1808
1965
|
|
|
1809
1966
|
<!-- SECTION: Contexte (merged: problem + current + vision) -->
|
|
1810
1967
|
<div class="section" id="cadrage-context">
|
|
@@ -2125,6 +2282,10 @@ body {
|
|
|
2125
2282
|
<h2 class="section-title">Synthèse de l'analyse</h2>
|
|
2126
2283
|
<p class="section-subtitle">Vue d'ensemble de toute l'analyse métier, prête pour le développement.</p>
|
|
2127
2284
|
|
|
2285
|
+
<div class="card" style="margin-bottom:1.5rem;" id="progressIndicator">
|
|
2286
|
+
<!-- Populated by renderProgressIndicator() -->
|
|
2287
|
+
</div>
|
|
2288
|
+
|
|
2128
2289
|
<div class="stat-grid" id="handoffStats">
|
|
2129
2290
|
<!-- Populated dynamically -->
|
|
2130
2291
|
</div>
|
|
@@ -2151,10 +2312,10 @@ body {
|
|
|
2151
2312
|
<!-- ============================================
|
|
2152
2313
|
REVIEW PANEL (right sidebar)
|
|
2153
2314
|
============================================ -->
|
|
2154
|
-
<aside class="review-panel" id="reviewPanel">
|
|
2315
|
+
<aside class="review-panel" id="reviewPanel" role="complementary" aria-label="Panneau de review">
|
|
2155
2316
|
<div class="review-panel-header">
|
|
2156
2317
|
<span class="review-panel-title">Review</span>
|
|
2157
|
-
<button class="review-panel-close" onclick="toggleReviewPanel()" title="Fermer">×</button>
|
|
2318
|
+
<button class="review-panel-close" onclick="toggleReviewPanel()" title="Fermer" aria-label="Fermer le panneau de review">×</button>
|
|
2158
2319
|
</div>
|
|
2159
2320
|
<div class="review-filters">
|
|
2160
2321
|
<button class="review-filter-btn active" onclick="filterReviewComments('all')" data-filter="all">Tous</button>
|
|
@@ -2262,8 +2423,23 @@ data.moduleSpecs = data.moduleSpecs || {};
|
|
|
2262
2423
|
if (!data.moduleSpecs[m.code].screens) {
|
|
2263
2424
|
data.moduleSpecs[m.code].screens = [];
|
|
2264
2425
|
}
|
|
2426
|
+
// Ensure sections have initialized arrays for hierarchical data
|
|
2427
|
+
m.anticipatedSections.forEach(function(section) {
|
|
2428
|
+
section.useCases = section.useCases || [];
|
|
2429
|
+
section.businessRules = section.businessRules || [];
|
|
2430
|
+
section.resources = section.resources || [];
|
|
2431
|
+
section.permission = section.permission || '';
|
|
2432
|
+
section.route = section.route || '';
|
|
2433
|
+
});
|
|
2265
2434
|
});
|
|
2266
2435
|
|
|
2436
|
+
// Detect if modules use section-level specs (hierarchical mode)
|
|
2437
|
+
function hasHierarchicalSpecs(mod) {
|
|
2438
|
+
return (mod.anticipatedSections || []).some(function(s) {
|
|
2439
|
+
return (s.useCases && s.useCases.length > 0) || (s.businessRules && s.businessRules.length > 0);
|
|
2440
|
+
});
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2267
2443
|
// Defensive: normalize stakeholder tasks (string → array)
|
|
2268
2444
|
(data.cadrage.stakeholders || []).forEach(function(s) {
|
|
2269
2445
|
if (typeof s.tasks === 'string') {
|
|
@@ -2335,6 +2511,45 @@ function formatModuleType(t) {
|
|
|
2335
2511
|
return { 'data-centric': 'Données', 'workflow': 'Processus', 'reporting': 'Rapports', 'integration': 'Intégration', 'full-module': 'Complet' }[t] || t;
|
|
2336
2512
|
}
|
|
2337
2513
|
|
|
2514
|
+
function escapeHtml(s) {
|
|
2515
|
+
if (s == null) return '';
|
|
2516
|
+
var d = document.createElement('div');
|
|
2517
|
+
d.textContent = String(s);
|
|
2518
|
+
return d.innerHTML;
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
function computeProgress() {
|
|
2522
|
+
var totalModules = data.modules.length;
|
|
2523
|
+
var modulesWithUC = 0, modulesWithBR = 0, modulesWithEntities = 0, modulesWithWireframes = 0;
|
|
2524
|
+
var totalUCs = 0, totalBRs = 0, totalEntities = 0;
|
|
2525
|
+
|
|
2526
|
+
data.modules.forEach(function(m) {
|
|
2527
|
+
var spec = data.moduleSpecs[m.code] || {};
|
|
2528
|
+
var ucs = (spec.useCases || []).length;
|
|
2529
|
+
var brs = (spec.businessRules || []).length;
|
|
2530
|
+
var ents = (spec.entities || []).length;
|
|
2531
|
+
var wfs = (EMBEDDED_ARTIFACTS?.wireframes?.[m.code] || []).length + (spec.screens || []).length;
|
|
2532
|
+
totalUCs += ucs; totalBRs += brs; totalEntities += ents;
|
|
2533
|
+
if (ucs > 0) modulesWithUC++;
|
|
2534
|
+
if (brs > 0) modulesWithBR++;
|
|
2535
|
+
if (ents > 0) modulesWithEntities++;
|
|
2536
|
+
if (wfs > 0) modulesWithWireframes++;
|
|
2537
|
+
});
|
|
2538
|
+
|
|
2539
|
+
var checks = [];
|
|
2540
|
+
if (totalModules > 0) checks.push({ done: true, label: 'Modules définis (' + totalModules + ')' });
|
|
2541
|
+
checks.push({ done: modulesWithUC === totalModules && totalModules > 0, label: 'Cas d\'utilisation (' + modulesWithUC + '/' + totalModules + ' modules)' });
|
|
2542
|
+
checks.push({ done: modulesWithBR === totalModules && totalModules > 0, label: 'Règles métier (' + modulesWithBR + '/' + totalModules + ' modules)' });
|
|
2543
|
+
checks.push({ done: modulesWithEntities === totalModules && totalModules > 0, label: 'Données (' + modulesWithEntities + '/' + totalModules + ' modules)' });
|
|
2544
|
+
checks.push({ done: modulesWithWireframes === totalModules && totalModules > 0, label: 'Maquettes (' + modulesWithWireframes + '/' + totalModules + ' modules)' });
|
|
2545
|
+
checks.push({ done: data.cadrage.stakeholders.length > 0, label: 'Parties prenantes (' + data.cadrage.stakeholders.length + ')' });
|
|
2546
|
+
checks.push({ done: (data.cadrage.scope.inscope || []).length > 0, label: 'Périmètre défini' });
|
|
2547
|
+
|
|
2548
|
+
var doneCount = checks.filter(function(c) { return c.done; }).length;
|
|
2549
|
+
var pct = checks.length > 0 ? Math.round(doneCount / checks.length * 100) : 0;
|
|
2550
|
+
return { checks: checks, doneCount: doneCount, total: checks.length, pct: pct, totalUCs: totalUCs, totalBRs: totalBRs, totalEntities: totalEntities };
|
|
2551
|
+
}
|
|
2552
|
+
|
|
2338
2553
|
/* ============================================
|
|
2339
2554
|
INITIALIZATION
|
|
2340
2555
|
============================================ */
|
|
@@ -2539,17 +2754,20 @@ function renderModuleNavItem(mod) {
|
|
|
2539
2754
|
if (sections.length > 0) {
|
|
2540
2755
|
sections.forEach(function(section) {
|
|
2541
2756
|
var resources = section.resources || [];
|
|
2757
|
+
var sectionUCs = (section.useCases || []).length;
|
|
2758
|
+
var sectionBRs = (section.businessRules || []).length;
|
|
2759
|
+
var contentCount = sectionUCs + sectionBRs + resources.length;
|
|
2542
2760
|
html += '<div class="nav-section-item">';
|
|
2543
2761
|
html += '<a class="nav-item nav-section-link" onclick="showSection(\'module-spec-' + code + '\');switchTab(\'' + code + '\',\'struct\')" data-section="module-struct-' + code + '-' + section.code + '">';
|
|
2544
|
-
html += '<span class="nav-icon nav-icon-section">▷</span> ' + section.code;
|
|
2545
|
-
if (
|
|
2762
|
+
html += '<span class="nav-icon nav-icon-section">▷</span> ' + escapeHtml(section.code || section.name || '');
|
|
2763
|
+
if (contentCount > 0) html += ' <span class="nav-badge">' + contentCount + '</span>';
|
|
2546
2764
|
html += '</a>';
|
|
2547
2765
|
if (resources.length > 0) {
|
|
2548
2766
|
html += '<div class="nav-children nav-resources">';
|
|
2549
2767
|
resources.forEach(function(res) {
|
|
2550
2768
|
var resName = typeof res === 'string' ? res : (res.code || res.name || '');
|
|
2551
2769
|
html += '<a class="nav-item nav-resource-link">';
|
|
2552
|
-
html += '<span class="nav-icon nav-icon-resource">•</span> ' + resName;
|
|
2770
|
+
html += '<span class="nav-icon nav-icon-resource">•</span> ' + escapeHtml(resName);
|
|
2553
2771
|
html += '</a>';
|
|
2554
2772
|
});
|
|
2555
2773
|
html += '</div>';
|
|
@@ -2615,6 +2833,97 @@ function highlightActiveNavItem() {
|
|
|
2615
2833
|
var navItem = document.querySelector('#sidebarNav [data-section="' + currentSectionId + '"]');
|
|
2616
2834
|
if (navItem) navItem.classList.add('active');
|
|
2617
2835
|
}
|
|
2836
|
+
|
|
2837
|
+
/* ---------- Mobile Sidebar ---------- */
|
|
2838
|
+
|
|
2839
|
+
function toggleMobileSidebar() {
|
|
2840
|
+
var sidebar = document.getElementById('sidebarAside');
|
|
2841
|
+
var overlay = document.getElementById('sidebarOverlay');
|
|
2842
|
+
if (!sidebar) return;
|
|
2843
|
+
var isOpen = sidebar.classList.contains('mobile-open');
|
|
2844
|
+
sidebar.classList.toggle('mobile-open', !isOpen);
|
|
2845
|
+
if (overlay) overlay.classList.toggle('visible', !isOpen);
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2848
|
+
/* ---------- Search / Filter ---------- */
|
|
2849
|
+
|
|
2850
|
+
var _searchTimer;
|
|
2851
|
+
function filterNavItems(query) {
|
|
2852
|
+
clearTimeout(_searchTimer);
|
|
2853
|
+
_searchTimer = setTimeout(function() { doFilterNavItems(query); }, 200);
|
|
2854
|
+
}
|
|
2855
|
+
|
|
2856
|
+
function doFilterNavItems(query) {
|
|
2857
|
+
var q = (query || '').toLowerCase().trim();
|
|
2858
|
+
var nav = document.getElementById('sidebarNav');
|
|
2859
|
+
if (!nav) return;
|
|
2860
|
+
|
|
2861
|
+
// Clear previous highlights
|
|
2862
|
+
nav.querySelectorAll('.search-highlight').forEach(function(el) {
|
|
2863
|
+
el.outerHTML = el.textContent;
|
|
2864
|
+
});
|
|
2865
|
+
|
|
2866
|
+
if (!q) {
|
|
2867
|
+
// Show all items
|
|
2868
|
+
nav.querySelectorAll('.nav-item, .nav-group, .nav-module, .nav-section-item').forEach(function(el) {
|
|
2869
|
+
el.classList.remove('search-hidden');
|
|
2870
|
+
});
|
|
2871
|
+
return;
|
|
2872
|
+
}
|
|
2873
|
+
|
|
2874
|
+
// Filter nav items
|
|
2875
|
+
var allItems = nav.querySelectorAll('.nav-item');
|
|
2876
|
+
var visibleSections = new Set();
|
|
2877
|
+
|
|
2878
|
+
allItems.forEach(function(item) {
|
|
2879
|
+
var text = item.textContent.toLowerCase();
|
|
2880
|
+
if (text.includes(q)) {
|
|
2881
|
+
item.classList.remove('search-hidden');
|
|
2882
|
+
// Show parent groups
|
|
2883
|
+
var parent = item.parentElement;
|
|
2884
|
+
while (parent && parent.id !== 'sidebarNav') {
|
|
2885
|
+
parent.classList.remove('search-hidden');
|
|
2886
|
+
if (parent.querySelector(':scope > .nav-children')) {
|
|
2887
|
+
var children = parent.querySelector(':scope > .nav-children');
|
|
2888
|
+
if (children) children.style.display = '';
|
|
2889
|
+
}
|
|
2890
|
+
parent = parent.parentElement;
|
|
2891
|
+
}
|
|
2892
|
+
// Track section for content search
|
|
2893
|
+
var sectionId = item.dataset.section;
|
|
2894
|
+
if (sectionId) visibleSections.add(sectionId);
|
|
2895
|
+
} else {
|
|
2896
|
+
item.classList.add('search-hidden');
|
|
2897
|
+
}
|
|
2898
|
+
});
|
|
2899
|
+
|
|
2900
|
+
// Hide groups that have no visible children
|
|
2901
|
+
nav.querySelectorAll('.nav-group, .nav-module').forEach(function(group) {
|
|
2902
|
+
var visibleChildren = group.querySelectorAll('.nav-item:not(.search-hidden)');
|
|
2903
|
+
if (visibleChildren.length === 0) {
|
|
2904
|
+
group.classList.add('search-hidden');
|
|
2905
|
+
} else {
|
|
2906
|
+
group.classList.remove('search-hidden');
|
|
2907
|
+
}
|
|
2908
|
+
});
|
|
2909
|
+
|
|
2910
|
+
// Also search in section content and show matching sections
|
|
2911
|
+
document.querySelectorAll('.section').forEach(function(section) {
|
|
2912
|
+
var text = section.textContent.toLowerCase();
|
|
2913
|
+
if (text.includes(q) && !visibleSections.has(section.id)) {
|
|
2914
|
+
// Unhide corresponding nav item
|
|
2915
|
+
var navItem = nav.querySelector('[data-section="' + section.id + '"]');
|
|
2916
|
+
if (navItem) {
|
|
2917
|
+
navItem.classList.remove('search-hidden');
|
|
2918
|
+
var parent = navItem.parentElement;
|
|
2919
|
+
while (parent && parent.id !== 'sidebarNav') {
|
|
2920
|
+
parent.classList.remove('search-hidden');
|
|
2921
|
+
parent = parent.parentElement;
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2925
|
+
});
|
|
2926
|
+
}
|
|
2618
2927
|
|
|
2619
2928
|
|
|
2620
2929
|
/* --- 03-render-cadrage.js --- */
|
|
@@ -2662,23 +2971,24 @@ function renderStakeholders() {
|
|
|
2662
2971
|
grid.innerHTML = data.cadrage.stakeholders.map((s, i) => `
|
|
2663
2972
|
<div class="stakeholder-card">
|
|
2664
2973
|
<div style="display:flex;justify-content:space-between;align-items:start;">
|
|
2665
|
-
<div class="stakeholder-role">${s.role}</div>
|
|
2974
|
+
<div class="stakeholder-role">${escapeHtml(s.role)}</div>
|
|
2666
2975
|
<button class="btn btn-sm" onclick="removeStakeholder(${i})" style="opacity:0.5;font-size:0.7rem;">Supprimer</button>
|
|
2667
2976
|
</div>
|
|
2668
|
-
<div class="stakeholder-function">${s.function || ''}</div>
|
|
2977
|
+
<div class="stakeholder-function">${escapeHtml(s.function || '')}</div>
|
|
2669
2978
|
<ul class="stakeholder-tasks">
|
|
2670
|
-
${(Array.isArray(s.tasks) ? s.tasks : typeof s.tasks === 'string' ? s.tasks.split(',').map(t => t.trim()).filter(Boolean) : []).map(t => '<li>' + t + '</li>').join('')}
|
|
2979
|
+
${(Array.isArray(s.tasks) ? s.tasks : typeof s.tasks === 'string' ? s.tasks.split(',').map(t => t.trim()).filter(Boolean) : []).map(t => '<li>' + escapeHtml(t) + '</li>').join('')}
|
|
2671
2980
|
</ul>
|
|
2672
2981
|
<div class="stakeholder-meta">
|
|
2673
2982
|
<span>${formatFrequency(s.frequency)}</span>
|
|
2674
2983
|
<span>${formatAccess(s.access)}</span>
|
|
2675
2984
|
</div>
|
|
2676
|
-
${s.frustrations ? '<div style="font-size:0.8rem;color:var(--warning);margin-top:0.5rem;font-style:italic;">' + s.frustrations + '</div>' : ''}
|
|
2985
|
+
${s.frustrations ? '<div style="font-size:0.8rem;color:var(--warning);margin-top:0.5rem;font-style:italic;">' + escapeHtml(s.frustrations) + '</div>' : ''}
|
|
2677
2986
|
</div>
|
|
2678
2987
|
`).join('');
|
|
2679
2988
|
}
|
|
2680
2989
|
|
|
2681
2990
|
function removeStakeholder(index) {
|
|
2991
|
+
if (!confirm('Supprimer ce profil utilisateur ?')) return;
|
|
2682
2992
|
data.cadrage.stakeholders.splice(index, 1);
|
|
2683
2993
|
renderStakeholders();
|
|
2684
2994
|
updateCounts();
|
|
@@ -2709,12 +3019,12 @@ function renderScope() {
|
|
|
2709
3019
|
<div class="uc-item">
|
|
2710
3020
|
<div class="uc-header">
|
|
2711
3021
|
<span class="priority priority-${p}">${formatPriority(p)}</span>
|
|
2712
|
-
<span class="uc-title">${item.name}</span>
|
|
3022
|
+
<span class="uc-title">${escapeHtml(item.name)}</span>
|
|
2713
3023
|
<div class="uc-actions">
|
|
2714
3024
|
<button class="btn btn-sm" onclick="removeScopeItem('${p}',${i})">Supprimer</button>
|
|
2715
3025
|
</div>
|
|
2716
3026
|
</div>
|
|
2717
|
-
${item.description ? '<div class="uc-detail">' + item.description + '</div>' : ''}
|
|
3027
|
+
${item.description ? '<div class="uc-detail">' + escapeHtml(item.description) + '</div>' : ''}
|
|
2718
3028
|
</div>
|
|
2719
3029
|
`).join('');
|
|
2720
3030
|
});
|
|
@@ -2759,7 +3069,7 @@ function renderCriteria() {
|
|
|
2759
3069
|
container.innerHTML = criteria.map((c, i) => `
|
|
2760
3070
|
<div class="uc-item" style="display:flex;align-items:center;gap:0.75rem;">
|
|
2761
3071
|
<input type="checkbox" ${c.validated ? 'checked' : ''} onchange="toggleCriterion(${i})" style="cursor:pointer;width:18px;height:18px;flex-shrink:0;">
|
|
2762
|
-
<span style="flex:1;${c.validated ? 'text-decoration:line-through;color:var(--text-muted);' : ''}">${c.text}</span>
|
|
3072
|
+
<span style="flex:1;${c.validated ? 'text-decoration:line-through;color:var(--text-muted);' : ''}">${escapeHtml(c.text)}</span>
|
|
2763
3073
|
<button class="btn btn-sm" onclick="removeCriterion(${i})" style="opacity:0.5;flex-shrink:0;">✕</button>
|
|
2764
3074
|
</div>
|
|
2765
3075
|
`).join('');
|
|
@@ -2839,10 +3149,10 @@ function renderModules() {
|
|
|
2839
3149
|
<div class="module-card" onclick="showSection('module-spec-${m.code}')">
|
|
2840
3150
|
<button class="module-card-remove" onclick="event.stopPropagation();removeModule(${i})">✕</button>
|
|
2841
3151
|
<div class="module-card-header">
|
|
2842
|
-
<span class="module-card-code">${m.name}</span>
|
|
3152
|
+
<span class="module-card-code">${escapeHtml(m.name)}</span>
|
|
2843
3153
|
<span class="module-card-type">${formatModuleType(m.featureType)}</span>
|
|
2844
3154
|
</div>
|
|
2845
|
-
<div class="module-card-desc">${m.description || ''}</div>
|
|
3155
|
+
<div class="module-card-desc">${escapeHtml(m.description || '')}</div>
|
|
2846
3156
|
<div class="module-card-meta">
|
|
2847
3157
|
<span>${(m.entities || []).length} données</span>
|
|
2848
3158
|
<span>${(data.moduleSpecs[m.code]?.useCases || []).length} cas d'utilisation</span>
|
|
@@ -2899,10 +3209,10 @@ function renderDependencies() {
|
|
|
2899
3209
|
const toName = data.modules.find(m => m.code === d.to)?.name || d.to;
|
|
2900
3210
|
return `
|
|
2901
3211
|
<div class="interaction-item">
|
|
2902
|
-
<span style="font-weight:600;color:var(--text-bright);">${fromName}</span>
|
|
3212
|
+
<span style="font-weight:600;color:var(--text-bright);">${escapeHtml(fromName)}</span>
|
|
2903
3213
|
<span class="interaction-arrow">→</span>
|
|
2904
|
-
<span style="font-weight:600;color:var(--text-bright);">${toName}</span>
|
|
2905
|
-
<span style="flex:1;font-size:0.8rem;color:var(--text-muted);">${d.description || ''}</span>
|
|
3214
|
+
<span style="font-weight:600;color:var(--text-bright);">${escapeHtml(toName)}</span>
|
|
3215
|
+
<span style="flex:1;font-size:0.8rem;color:var(--text-muted);">${escapeHtml(d.description || '')}</span>
|
|
2906
3216
|
<button class="btn btn-sm" onclick="removeDependency(${i})" style="opacity:0.5;">Supprimer</button>
|
|
2907
3217
|
</div>
|
|
2908
3218
|
`;
|
|
@@ -2931,7 +3241,7 @@ function renderDepGraph() {
|
|
|
2931
3241
|
<div class="dep-layer-modules">
|
|
2932
3242
|
${layer.map(code => {
|
|
2933
3243
|
const m = data.modules.find(mod => mod.code === code);
|
|
2934
|
-
return `<div class="dep-module">${m ? (m.name || m.code) : code}</div>`;
|
|
3244
|
+
return `<div class="dep-module">${escapeHtml(m ? (m.name || m.code) : code)}</div>`;
|
|
2935
3245
|
}).join('')}
|
|
2936
3246
|
</div>
|
|
2937
3247
|
</div>
|
|
@@ -2979,7 +3289,7 @@ function renderProcessingOrder() {
|
|
|
2979
3289
|
return `
|
|
2980
3290
|
<div class="process-step">
|
|
2981
3291
|
<div class="process-step-number">Étape ${i + 1}</div>
|
|
2982
|
-
<div class="process-step-label">${m ? (m.name || m.code) : code}</div>
|
|
3292
|
+
<div class="process-step-label">${escapeHtml(m ? (m.name || m.code) : code)}</div>
|
|
2983
3293
|
</div>
|
|
2984
3294
|
${i < order.length - 1 ? '<div class="process-arrow">→</div>' : ''}
|
|
2985
3295
|
`;
|
|
@@ -3017,23 +3327,24 @@ function renderModuleSpecSection(mod) {
|
|
|
3017
3327
|
|
|
3018
3328
|
return `
|
|
3019
3329
|
<div class="section" id="module-spec-${code}" style="display:none;">
|
|
3020
|
-
<h2 class="section-title">${mod.name}</h2>
|
|
3021
|
-
<p class="section-subtitle">${mod.description || 'Spécification détaillée de ce domaine fonctionnel.'}</p>
|
|
3022
|
-
|
|
3023
|
-
<div class="tab-bar">
|
|
3024
|
-
<button class="tab-btn active" onclick="switchTab('${code}', 'uc')">Cas d'utilisation</button>
|
|
3025
|
-
<button class="tab-btn" onclick="switchTab('${code}', 'br')">Règles métier</button>
|
|
3026
|
-
<button class="tab-btn" onclick="switchTab('${code}', 'ent')">Données</button>
|
|
3027
|
-
<button class="tab-btn" onclick="switchTab('${code}', 'perm')">Droits d'accès</button>
|
|
3028
|
-
<button class="tab-btn" onclick="switchTab('${code}', 'mock')">Maquettes</button>
|
|
3029
|
-
<button class="tab-btn" onclick="switchTab('${code}', 'notes')">Notes</button>
|
|
3030
|
-
<button class="tab-btn" onclick="switchTab('${code}', 'struct')">Structure</button>
|
|
3330
|
+
<h2 class="section-title">${escapeHtml(mod.name)}</h2>
|
|
3331
|
+
<p class="section-subtitle">${escapeHtml(mod.description || 'Spécification détaillée de ce domaine fonctionnel.')}</p>
|
|
3332
|
+
|
|
3333
|
+
<div class="tab-bar" role="tablist" aria-label="Spécifications du module ${escapeHtml(mod.name)}">
|
|
3334
|
+
<button class="tab-btn active" role="tab" aria-selected="true" aria-controls="tab-${code}-uc" onclick="switchTab('${code}', 'uc')">Cas d'utilisation</button>
|
|
3335
|
+
<button class="tab-btn" role="tab" aria-selected="false" aria-controls="tab-${code}-br" onclick="switchTab('${code}', 'br')">Règles métier</button>
|
|
3336
|
+
<button class="tab-btn" role="tab" aria-selected="false" aria-controls="tab-${code}-ent" onclick="switchTab('${code}', 'ent')">Données</button>
|
|
3337
|
+
<button class="tab-btn" role="tab" aria-selected="false" aria-controls="tab-${code}-perm" onclick="switchTab('${code}', 'perm')">Droits d'accès</button>
|
|
3338
|
+
<button class="tab-btn" role="tab" aria-selected="false" aria-controls="tab-${code}-mock" onclick="switchTab('${code}', 'mock')">Maquettes</button>
|
|
3339
|
+
<button class="tab-btn" role="tab" aria-selected="false" aria-controls="tab-${code}-notes" onclick="switchTab('${code}', 'notes')">Notes</button>
|
|
3340
|
+
<button class="tab-btn" role="tab" aria-selected="false" aria-controls="tab-${code}-struct" onclick="switchTab('${code}', 'struct')">Structure</button>
|
|
3031
3341
|
</div>
|
|
3032
3342
|
|
|
3033
3343
|
<!-- TAB: Cas d'utilisation -->
|
|
3034
|
-
<div class="tab-panel active" id="tab-${code}-uc">
|
|
3344
|
+
<div class="tab-panel active" id="tab-${code}-uc" role="tabpanel" aria-hidden="false">
|
|
3035
3345
|
<p style="font-size:0.85rem;color:var(--text-muted);margin-bottom:1rem;">Décrivez ce que chaque type d'utilisateur peut faire dans ce domaine. Un cas d'utilisation = une action concrète.</p>
|
|
3036
3346
|
<div id="ucList-${code}" class="uc-list">
|
|
3347
|
+
${hasHierarchicalSpecs(mod) ? renderSectionGroupedItems(mod, 'useCases', code, renderUseCase) : ''}
|
|
3037
3348
|
${spec.useCases.map((uc, i) => renderUseCase(code, uc, i)).join('')}
|
|
3038
3349
|
</div>
|
|
3039
3350
|
<button class="add-btn" onclick="toggleForm('addUcForm-${code}')">+ Ajouter un cas d'utilisation</button>
|
|
@@ -3063,9 +3374,10 @@ function renderModuleSpecSection(mod) {
|
|
|
3063
3374
|
</div>
|
|
3064
3375
|
|
|
3065
3376
|
<!-- TAB: Règles métier -->
|
|
3066
|
-
<div class="tab-panel" id="tab-${code}-br">
|
|
3377
|
+
<div class="tab-panel" id="tab-${code}-br" role="tabpanel" aria-hidden="true">
|
|
3067
3378
|
<p style="font-size:0.85rem;color:var(--text-muted);margin-bottom:1rem;">Les règles que le système doit respecter. Formulez-les sous forme de conditions : "Si... alors... sinon..."</p>
|
|
3068
3379
|
<div id="brList-${code}">
|
|
3380
|
+
${hasHierarchicalSpecs(mod) ? renderSectionGroupedItems(mod, 'businessRules', code, renderBusinessRule) : ''}
|
|
3069
3381
|
${spec.businessRules.map((br, i) => renderBusinessRule(code, br, i)).join('')}
|
|
3070
3382
|
</div>
|
|
3071
3383
|
<button class="add-btn" onclick="toggleForm('addBrForm-${code}')">+ Ajouter une règle métier</button>
|
|
@@ -3101,7 +3413,7 @@ function renderModuleSpecSection(mod) {
|
|
|
3101
3413
|
</div>
|
|
3102
3414
|
|
|
3103
3415
|
<!-- TAB: Données (Entités) -->
|
|
3104
|
-
<div class="tab-panel" id="tab-${code}-ent">
|
|
3416
|
+
<div class="tab-panel" id="tab-${code}-ent" role="tabpanel" aria-hidden="true">
|
|
3105
3417
|
<p style="font-size:0.85rem;color:var(--text-muted);margin-bottom:1rem;">Les types de données que ce domaine gère. Décrivez les informations importantes à enregistrer pour chaque type.</p>
|
|
3106
3418
|
<div id="entList-${code}">
|
|
3107
3419
|
${spec.entities.length > 0
|
|
@@ -3138,7 +3450,7 @@ function renderModuleSpecSection(mod) {
|
|
|
3138
3450
|
</div>
|
|
3139
3451
|
|
|
3140
3452
|
<!-- TAB: Droits d'accès -->
|
|
3141
|
-
<div class="tab-panel" id="tab-${code}-perm">
|
|
3453
|
+
<div class="tab-panel" id="tab-${code}-perm" role="tabpanel" aria-hidden="true">
|
|
3142
3454
|
<p style="font-size:0.85rem;color:var(--text-muted);margin-bottom:1rem;">Définissez qui peut faire quoi dans ce domaine. Cochez les actions autorisées pour chaque profil.</p>
|
|
3143
3455
|
<div id="permGrid-${code}">
|
|
3144
3456
|
${renderPermissionGrid(code)}
|
|
@@ -3172,7 +3484,7 @@ function renderModuleSpecSection(mod) {
|
|
|
3172
3484
|
</div>
|
|
3173
3485
|
|
|
3174
3486
|
<!-- TAB: Maquettes -->
|
|
3175
|
-
<div class="tab-panel" id="tab-${code}-mock">
|
|
3487
|
+
<div class="tab-panel" id="tab-${code}-mock" role="tabpanel" aria-hidden="true">
|
|
3176
3488
|
<p style="font-size:0.85rem;color:var(--text-muted);margin-bottom:1rem;">Maquettes validées lors de l'analyse. Ces wireframes montrent la structure exacte des écrans de ce domaine.</p>
|
|
3177
3489
|
<div id="mockupContainer-${code}">
|
|
3178
3490
|
${renderModuleMockups(code)}
|
|
@@ -3180,7 +3492,7 @@ function renderModuleSpecSection(mod) {
|
|
|
3180
3492
|
</div>
|
|
3181
3493
|
|
|
3182
3494
|
<!-- TAB: Notes -->
|
|
3183
|
-
<div class="tab-panel" id="tab-${code}-notes">
|
|
3495
|
+
<div class="tab-panel" id="tab-${code}-notes" role="tabpanel" aria-hidden="true">
|
|
3184
3496
|
<p style="font-size:0.85rem;color:var(--text-muted);margin-bottom:1rem;">Notes libres, questions en suspens, éléments à clarifier pour ce domaine.</p>
|
|
3185
3497
|
<div class="card">
|
|
3186
3498
|
<div class="editable" contenteditable="true" data-module-code="${code}" data-module-field="notes" data-placeholder="Notez ici tout ce qui concerne ce domaine : questions, précisions, contraintes particulières...">${spec.notes || ''}</div>
|
|
@@ -3188,7 +3500,7 @@ function renderModuleSpecSection(mod) {
|
|
|
3188
3500
|
</div>
|
|
3189
3501
|
|
|
3190
3502
|
<!-- TAB: Structure (sections/resources) -->
|
|
3191
|
-
<div class="tab-panel" id="tab-${code}-struct">
|
|
3503
|
+
<div class="tab-panel" id="tab-${code}-struct" role="tabpanel" aria-hidden="true">
|
|
3192
3504
|
<p style="font-size:0.85rem;color:var(--text-muted);margin-bottom:1rem;">Organisation des écrans et ressources de ce domaine. Structure hiérarchique : sections regroupant des ressources.</p>
|
|
3193
3505
|
<div id="structContainer-${code}">
|
|
3194
3506
|
${renderModuleStructure(code)}
|
|
@@ -3202,19 +3514,19 @@ function renderUseCase(code, uc, index) {
|
|
|
3202
3514
|
<div class="uc-item">
|
|
3203
3515
|
<div class="uc-header">
|
|
3204
3516
|
<span class="uc-id">UC-${String(index + 1).padStart(3, '0')}</span>
|
|
3205
|
-
<span class="uc-title">${uc.name}</span>
|
|
3517
|
+
<span class="uc-title">${escapeHtml(uc.name)}</span>
|
|
3206
3518
|
<div class="uc-actions">
|
|
3207
3519
|
<button class="btn btn-sm" onclick="removeUseCase('${code}',${index})">Supprimer</button>
|
|
3208
3520
|
</div>
|
|
3209
3521
|
</div>
|
|
3210
|
-
<div class="uc-actors"><div class="uc-actor">${uc.actor}</div></div>
|
|
3211
|
-
${uc.steps ? `<div class="uc-detail-label">Déroulement</div><div class="uc-detail">${uc.steps.replace(/\n/g, '<br>')}</div>` : ''}
|
|
3212
|
-
${uc.alternative ? `<div class="uc-detail-label">En cas de problème</div><div class="uc-detail" style="color:var(--warning);">${uc.alternative}</div>` : ''}
|
|
3522
|
+
<div class="uc-actors"><div class="uc-actor">${escapeHtml(uc.actor)}</div></div>
|
|
3523
|
+
${uc.steps ? `<div class="uc-detail-label">Déroulement</div><div class="uc-detail">${escapeHtml(uc.steps).replace(/\n/g, '<br>')}</div>` : ''}
|
|
3524
|
+
${uc.alternative ? `<div class="uc-detail-label">En cas de problème</div><div class="uc-detail" style="color:var(--warning);">${escapeHtml(uc.alternative)}</div>` : ''}
|
|
3213
3525
|
<div style="padding:0.5rem 0.75rem;border-top:1px solid var(--border);background:var(--bg-input);border-radius:0 0 8px 8px;">
|
|
3214
3526
|
<label style="font-size:0.75rem;color:var(--text-muted);display:block;margin-bottom:0.25rem;">Commentaire / Feedback :</label>
|
|
3215
3527
|
<textarea class="form-textarea" placeholder="Ajouter un commentaire sur ce cas d'utilisation..."
|
|
3216
3528
|
onblur="updateSpecComment('${code}','uc',${index},this.value)"
|
|
3217
|
-
style="min-height:45px;font-size:0.8rem;resize:vertical;">${getSpecComment(code, 'uc', index)}</textarea>
|
|
3529
|
+
style="min-height:45px;font-size:0.8rem;resize:vertical;">${escapeHtml(getSpecComment(code, 'uc', index))}</textarea>
|
|
3218
3530
|
</div>
|
|
3219
3531
|
</div>`;
|
|
3220
3532
|
}
|
|
@@ -3237,6 +3549,7 @@ function addUseCase(code) {
|
|
|
3237
3549
|
}
|
|
3238
3550
|
|
|
3239
3551
|
function removeUseCase(code, index) {
|
|
3552
|
+
if (!confirm('Supprimer ce cas d\'utilisation ?')) return;
|
|
3240
3553
|
data.moduleSpecs[code].useCases.splice(index, 1);
|
|
3241
3554
|
renderAllModuleSpecs();
|
|
3242
3555
|
updateCounts();
|
|
@@ -3249,18 +3562,18 @@ function renderBusinessRule(code, br, index) {
|
|
|
3249
3562
|
return `
|
|
3250
3563
|
<div style="margin-bottom:0.5rem;">
|
|
3251
3564
|
<div class="br-item" style="margin-bottom:0;border-radius:8px 8px 0 0;">
|
|
3252
|
-
<span class="br-category ${catColors[br.category] || 'br-cat-validation'}">${catLabels[br.category] || br.category}</span>
|
|
3565
|
+
<span class="br-category ${catColors[br.category] || 'br-cat-validation'}">${escapeHtml(catLabels[br.category] || br.category)}</span>
|
|
3253
3566
|
<div class="br-text" style="flex:1;">
|
|
3254
|
-
<div style="font-weight:600;color:var(--text-bright);margin-bottom:0.2rem;">${br.name}</div>
|
|
3255
|
-
<div>${br.statement}</div>
|
|
3256
|
-
${br.example ? `<div style="font-size:0.8rem;color:var(--text-muted);margin-top:0.25rem;font-style:italic;">Exemple : ${br.example}</div>` : ''}
|
|
3567
|
+
<div style="font-weight:600;color:var(--text-bright);margin-bottom:0.2rem;">${escapeHtml(br.name)}</div>
|
|
3568
|
+
<div>${escapeHtml(br.statement)}</div>
|
|
3569
|
+
${br.example ? `<div style="font-size:0.8rem;color:var(--text-muted);margin-top:0.25rem;font-style:italic;">Exemple : ${escapeHtml(br.example)}</div>` : ''}
|
|
3257
3570
|
</div>
|
|
3258
3571
|
<button class="btn btn-sm" onclick="removeBusinessRule('${code}',${index})" style="opacity:0.5;flex-shrink:0;">✕</button>
|
|
3259
3572
|
</div>
|
|
3260
3573
|
<div style="padding:0.4rem 0.75rem 0.6rem;background:var(--bg-input);border:1px solid var(--border);border-top:0;border-radius:0 0 8px 8px;">
|
|
3261
3574
|
<textarea class="form-textarea" placeholder="Commentaire sur cette règle..."
|
|
3262
3575
|
onblur="updateSpecComment('${code}','br',${index},this.value)"
|
|
3263
|
-
style="min-height:35px;font-size:0.8rem;resize:vertical;">${getSpecComment(code, 'br', index)}</textarea>
|
|
3576
|
+
style="min-height:35px;font-size:0.8rem;resize:vertical;">${escapeHtml(getSpecComment(code, 'br', index))}</textarea>
|
|
3264
3577
|
</div>
|
|
3265
3578
|
</div>`;
|
|
3266
3579
|
}
|
|
@@ -3282,18 +3595,20 @@ function addBusinessRule(code) {
|
|
|
3282
3595
|
}
|
|
3283
3596
|
|
|
3284
3597
|
function removeBusinessRule(code, index) {
|
|
3598
|
+
if (!confirm('Supprimer cette règle métier ?')) return;
|
|
3285
3599
|
data.moduleSpecs[code].businessRules.splice(index, 1);
|
|
3286
3600
|
renderAllModuleSpecs();
|
|
3287
3601
|
autoSave();
|
|
3288
3602
|
}
|
|
3289
3603
|
|
|
3290
3604
|
function renderEntity(code, ent, index) {
|
|
3605
|
+
var attrFormId = 'addAttrForm-' + code + '-' + index;
|
|
3291
3606
|
return `
|
|
3292
3607
|
<div class="entity-block">
|
|
3293
3608
|
<div class="entity-header">
|
|
3294
3609
|
<div>
|
|
3295
|
-
<div class="entity-name">${ent.name}</div>
|
|
3296
|
-
<div class="entity-desc">${ent.description || ''}</div>
|
|
3610
|
+
<div class="entity-name">${escapeHtml(ent.name)}</div>
|
|
3611
|
+
<div class="entity-desc">${escapeHtml(ent.description || '')}</div>
|
|
3297
3612
|
</div>
|
|
3298
3613
|
<button class="btn btn-sm" onclick="removeEntity('${code}',${index})" style="opacity:0.5;">Supprimer</button>
|
|
3299
3614
|
</div>
|
|
@@ -3301,21 +3616,26 @@ function renderEntity(code, ent, index) {
|
|
|
3301
3616
|
<table class="attr-table">
|
|
3302
3617
|
<thead><tr><th>Information</th><th>Description</th></tr></thead>
|
|
3303
3618
|
<tbody>
|
|
3304
|
-
${ent.attributes.map(a => `<tr><td style="font-weight:500;color:var(--text-bright);">${a.name}</td><td>${a.description || ''}</td></tr>`).join('')}
|
|
3619
|
+
${ent.attributes.map(a => `<tr><td style="font-weight:500;color:var(--text-bright);">${escapeHtml(a.name)}</td><td>${escapeHtml(a.description || '')}</td></tr>`).join('')}
|
|
3305
3620
|
</tbody>
|
|
3306
|
-
</table
|
|
3621
|
+
</table>` : ''}
|
|
3307
3622
|
<div style="padding:0.3rem 0.75rem;">
|
|
3308
|
-
<button class="add-btn" style="font-size:0.75rem;padding:0.4rem;" onclick="
|
|
3309
|
-
|
|
3623
|
+
<button class="add-btn" style="font-size:0.75rem;padding:0.4rem;" onclick="toggleForm('${attrFormId}')">+ Ajouter un attribut</button>
|
|
3624
|
+
<div class="inline-form" id="${attrFormId}">
|
|
3625
|
+
<div class="form-group"><label class="form-label">Nom de l'attribut</label><input type="text" class="form-input" id="attr-name-${code}-${index}" placeholder="Nom de l'attribut"></div>
|
|
3626
|
+
<div class="form-group"><label class="form-label">Description (optionnel)</label><input type="text" class="form-input" id="attr-desc-${code}-${index}" placeholder="Description"></div>
|
|
3627
|
+
<div class="form-actions"><button class="btn" onclick="toggleForm('${attrFormId}')">Annuler</button><button class="btn btn-primary" onclick="addEntityAttribute('${code}',${index})">Ajouter</button></div>
|
|
3628
|
+
</div>
|
|
3629
|
+
</div>
|
|
3310
3630
|
${(ent.relationships || []).length > 0 ? `
|
|
3311
3631
|
<div style="padding:0.5rem 0.75rem;font-size:0.8rem;color:var(--text-muted);border-top:1px solid var(--border);">
|
|
3312
|
-
Relations : ${ent.relationships.map(r => `<span style="color:var(--accent);">${r}</span>`).join(', ')}
|
|
3632
|
+
Relations : ${ent.relationships.map(r => `<span style="color:var(--accent);">${escapeHtml(typeof r === 'string' ? r : (r.target || ''))}</span>`).join(', ')}
|
|
3313
3633
|
</div>` : ''}
|
|
3314
3634
|
<div style="padding:0.4rem 0.75rem 0.6rem;background:var(--bg-input);border-top:1px solid var(--border);border-radius:0 0 8px 8px;">
|
|
3315
3635
|
<label style="font-size:0.75rem;color:var(--text-muted);display:block;margin-bottom:0.25rem;">Commentaire :</label>
|
|
3316
3636
|
<textarea class="form-textarea" placeholder="Commentaire sur cette entité..."
|
|
3317
3637
|
onblur="updateSpecComment('${code}','ent',${index},this.value)"
|
|
3318
|
-
style="min-height:35px;font-size:0.8rem;resize:vertical;">${getSpecComment(code, 'ent', index)}</textarea>
|
|
3638
|
+
style="min-height:35px;font-size:0.8rem;resize:vertical;">${escapeHtml(getSpecComment(code, 'ent', index))}</textarea>
|
|
3319
3639
|
</div>
|
|
3320
3640
|
</div>`;
|
|
3321
3641
|
}
|
|
@@ -3343,20 +3663,21 @@ function addEntity(code) {
|
|
|
3343
3663
|
}
|
|
3344
3664
|
|
|
3345
3665
|
function removeEntity(code, index) {
|
|
3666
|
+
if (!confirm('Supprimer ce type de données ?')) return;
|
|
3346
3667
|
data.moduleSpecs[code].entities.splice(index, 1);
|
|
3347
3668
|
renderAllModuleSpecs();
|
|
3348
3669
|
autoSave();
|
|
3349
3670
|
}
|
|
3350
3671
|
|
|
3351
3672
|
function addEntityAttribute(code, entityIndex) {
|
|
3352
|
-
var attrName =
|
|
3353
|
-
|
|
3354
|
-
|
|
3673
|
+
var attrName = document.getElementById('attr-name-' + code + '-' + entityIndex);
|
|
3674
|
+
var attrDesc = document.getElementById('attr-desc-' + code + '-' + entityIndex);
|
|
3675
|
+
if (!attrName || !attrName.value.trim()) return;
|
|
3355
3676
|
if (!data.moduleSpecs[code]?.entities?.[entityIndex]) return;
|
|
3356
3677
|
if (!data.moduleSpecs[code].entities[entityIndex].attributes) {
|
|
3357
3678
|
data.moduleSpecs[code].entities[entityIndex].attributes = [];
|
|
3358
3679
|
}
|
|
3359
|
-
data.moduleSpecs[code].entities[entityIndex].attributes.push({ name: attrName, description: attrDesc });
|
|
3680
|
+
data.moduleSpecs[code].entities[entityIndex].attributes.push({ name: attrName.value.trim(), description: (attrDesc?.value || '').trim() });
|
|
3360
3681
|
renderAllModuleSpecs();
|
|
3361
3682
|
autoSave();
|
|
3362
3683
|
}
|
|
@@ -3382,37 +3703,6 @@ function updateWireframeComment(code, screen, value) {
|
|
|
3382
3703
|
autoSave();
|
|
3383
3704
|
}
|
|
3384
3705
|
|
|
3385
|
-
function renderModuleMockups(code) {
|
|
3386
|
-
var spec = data.moduleSpecs[code] || {};
|
|
3387
|
-
var screens = spec.screens || [];
|
|
3388
|
-
var wireframes = EMBEDDED_ARTIFACTS?.wireframes?.[code] || [];
|
|
3389
|
-
|
|
3390
|
-
// Priority 1: HTML mockups from screens[] specs
|
|
3391
|
-
if (screens.length > 0) {
|
|
3392
|
-
var html = '';
|
|
3393
|
-
if (typeof renderScreenMockups === 'function') {
|
|
3394
|
-
html = renderScreenMockups(code);
|
|
3395
|
-
}
|
|
3396
|
-
// Also show wireframes below if available
|
|
3397
|
-
if (wireframes.length > 0) {
|
|
3398
|
-
html += '<h3 style="color:var(--text-bright);font-size:1rem;margin:2rem 0 1rem;">Wireframes</h3>';
|
|
3399
|
-
html += wireframes.map(function(wf, i) { return renderWireframeMockup(code, wf, i); }).join('');
|
|
3400
|
-
}
|
|
3401
|
-
return html;
|
|
3402
|
-
}
|
|
3403
|
-
|
|
3404
|
-
// Priority 2: Wireframes from EMBEDDED_ARTIFACTS
|
|
3405
|
-
if (wireframes.length === 0) {
|
|
3406
|
-
return `
|
|
3407
|
-
<div class="card" style="text-align:center;padding:2rem;color:var(--text-muted);">
|
|
3408
|
-
<p>Aucune maquette disponible pour ce module.</p>
|
|
3409
|
-
<p style="font-size:0.85rem;margin-top:0.5rem;">Les maquettes seront générées lors de la spécification détaillée.</p>
|
|
3410
|
-
</div>`;
|
|
3411
|
-
}
|
|
3412
|
-
|
|
3413
|
-
return wireframes.map(function(wf, i) { return renderWireframeMockup(code, wf, i); }).join('');
|
|
3414
|
-
}
|
|
3415
|
-
|
|
3416
3706
|
function renderWireframeMockup(code, wf, i) {
|
|
3417
3707
|
const hasSvg = !!wf.svgContent;
|
|
3418
3708
|
const wireframeId = `wf-${code}-${i}`;
|
|
@@ -3423,7 +3713,7 @@ function renderWireframeMockup(code, wf, i) {
|
|
|
3423
3713
|
<div class="mockup-dot mockup-dot-red"></div>
|
|
3424
3714
|
<div class="mockup-dot mockup-dot-yellow"></div>
|
|
3425
3715
|
<div class="mockup-dot mockup-dot-green"></div>
|
|
3426
|
-
<span class="mockup-title">${wf.screen || wf.section}</span>
|
|
3716
|
+
<span class="mockup-title">${escapeHtml(wf.screen || wf.section)}</span>
|
|
3427
3717
|
${hasSvg ? `
|
|
3428
3718
|
<div class="wireframe-toggle">
|
|
3429
3719
|
<button class="wireframe-toggle-btn active" data-target="${wireframeId}" data-view="svg" onclick="toggleWireframeView('${wireframeId}', 'svg')">SVG</button>
|
|
@@ -3440,11 +3730,11 @@ function renderWireframeMockup(code, wf, i) {
|
|
|
3440
3730
|
</div>
|
|
3441
3731
|
${wf.description ? `
|
|
3442
3732
|
<div class="wireframe-description">
|
|
3443
|
-
<strong>Description:</strong> ${wf.description}
|
|
3733
|
+
<strong>Description:</strong> ${escapeHtml(wf.description)}
|
|
3444
3734
|
</div>` : ''}
|
|
3445
3735
|
${(wf.elements || []).length > 0 ? `
|
|
3446
3736
|
<div class="wireframe-metadata">
|
|
3447
|
-
<div><strong>Elements:</strong> ${wf.elements.map(e => typeof e === 'string' ? e : (e.type || e.label || e.id || '')).filter(Boolean).join(', ')}</div>
|
|
3737
|
+
<div><strong>Elements:</strong> ${wf.elements.map(e => escapeHtml(typeof e === 'string' ? e : (e.type || e.label || e.id || ''))).filter(Boolean).join(', ')}</div>
|
|
3448
3738
|
</div>` : ''}
|
|
3449
3739
|
${(() => {
|
|
3450
3740
|
const cm = wf.componentMapping;
|
|
@@ -3458,7 +3748,7 @@ function renderWireframeMockup(code, wf, i) {
|
|
|
3458
3748
|
<thead><tr><th>Élément maquette</th><th>Composant React</th></tr></thead>
|
|
3459
3749
|
<tbody>
|
|
3460
3750
|
${mappings.map(m =>
|
|
3461
|
-
'<tr><td>' + (m.wireframeElement || '') + '</td><td><code>' + (m.reactComponent || '') + '</code></td></tr>'
|
|
3751
|
+
'<tr><td>' + escapeHtml(m.wireframeElement || '') + '</td><td><code>' + escapeHtml(m.reactComponent || '') + '</code></td></tr>'
|
|
3462
3752
|
).join('')}
|
|
3463
3753
|
</tbody>
|
|
3464
3754
|
</table>
|
|
@@ -3533,12 +3823,12 @@ function renderPermissionGrid(code) {
|
|
|
3533
3823
|
<table class="mock-table" style="background:var(--bg-card);border-radius:8px;overflow:hidden;">
|
|
3534
3824
|
<thead><tr>
|
|
3535
3825
|
<th>Profil</th>
|
|
3536
|
-
${actions.map((a, i) => `<th style="text-align:center;">${a}${i >= baseActionsCount ? ' <span onclick="removeCustomAction('+`'${code}','${a}'`+')" style="cursor:pointer;color:var(--danger);font-size:0.7rem;" title="Supprimer cette action">✕</span>' : ''}</th>`).join('')}
|
|
3826
|
+
${actions.map((a, i) => `<th style="text-align:center;">${escapeHtml(a)}${i >= baseActionsCount ? ' <span onclick="removeCustomAction('+`'${code}','${a}'`+')" style="cursor:pointer;color:var(--danger);font-size:0.7rem;" title="Supprimer cette action">✕</span>' : ''}</th>`).join('')}
|
|
3537
3827
|
</tr></thead>
|
|
3538
3828
|
<tbody>
|
|
3539
3829
|
${roles.map((role, ri) => `
|
|
3540
3830
|
<tr>
|
|
3541
|
-
<td style="font-weight:500;color:var(--text-bright);">${role}${ri >= baseRolesCount ? ' <span onclick="removeCustomRole('+`'${code}','${role}'`+')" style="cursor:pointer;color:var(--danger);font-size:0.7rem;" title="Supprimer ce rôle">✕</span>' : ''}</td>
|
|
3831
|
+
<td style="font-weight:500;color:var(--text-bright);">${escapeHtml(role)}${ri >= baseRolesCount ? ' <span onclick="removeCustomRole('+`'${code}','${role}'`+')" style="cursor:pointer;color:var(--danger);font-size:0.7rem;" title="Supprimer ce rôle">✕</span>' : ''}</td>
|
|
3542
3832
|
${actions.map(action => {
|
|
3543
3833
|
const key = role + '|' + action;
|
|
3544
3834
|
const wildcardKey = role + '|*';
|
|
@@ -3632,24 +3922,52 @@ function renderModuleStructure(code) {
|
|
|
3632
3922
|
|
|
3633
3923
|
return sections.map(function(section) {
|
|
3634
3924
|
var resources = section.resources || [];
|
|
3925
|
+
var sectionUCs = section.useCases || [];
|
|
3926
|
+
var sectionBRs = section.businessRules || [];
|
|
3635
3927
|
var html = '<div class="struct-section">';
|
|
3636
3928
|
html += '<div class="struct-section-header">';
|
|
3637
|
-
html += '<span class="struct-section-code">' + (section.code || section.name || '') + '</span>';
|
|
3929
|
+
html += '<span class="struct-section-code">' + escapeHtml(section.code || section.name || '') + '</span>';
|
|
3638
3930
|
if (section.description) {
|
|
3639
|
-
html += '<span class="struct-section-desc">' + section.description + '</span>';
|
|
3931
|
+
html += '<span class="struct-section-desc">' + escapeHtml(section.description) + '</span>';
|
|
3640
3932
|
}
|
|
3641
3933
|
html += '<span class="struct-section-badge">' + resources.length + ' ressource' + (resources.length !== 1 ? 's' : '') + '</span>';
|
|
3642
3934
|
html += '</div>';
|
|
3643
3935
|
|
|
3936
|
+
// Section metadata (route, permission)
|
|
3937
|
+
if (section.route || section.permission) {
|
|
3938
|
+
html += '<div style="padding:0.4rem 1rem;font-size:0.75rem;color:var(--text-muted);display:flex;gap:1rem;border-bottom:1px solid var(--border);">';
|
|
3939
|
+
if (section.route) html += '<span>Route: <code style="color:var(--accent);background:var(--bg-input);padding:0.1rem 0.3rem;border-radius:3px;">' + escapeHtml(section.route) + '</code></span>';
|
|
3940
|
+
if (section.permission) html += '<span>Permission: <code style="color:var(--accent);background:var(--bg-input);padding:0.1rem 0.3rem;border-radius:3px;">' + escapeHtml(section.permission) + '</code></span>';
|
|
3941
|
+
html += '</div>';
|
|
3942
|
+
}
|
|
3943
|
+
|
|
3944
|
+
// Section-level UC summary
|
|
3945
|
+
if (sectionUCs.length > 0) {
|
|
3946
|
+
html += '<div style="padding:0.4rem 1rem;font-size:0.8rem;border-bottom:1px solid var(--border);">';
|
|
3947
|
+
html += '<span style="color:var(--text-muted);font-weight:500;">' + sectionUCs.length + ' cas d\'utilisation :</span> ';
|
|
3948
|
+
html += sectionUCs.map(function(uc) { return '<span style="color:var(--text-bright);">' + escapeHtml(uc.name || '') + '</span>'; }).join(', ');
|
|
3949
|
+
html += '</div>';
|
|
3950
|
+
}
|
|
3951
|
+
|
|
3952
|
+
// Section-level BR summary
|
|
3953
|
+
if (sectionBRs.length > 0) {
|
|
3954
|
+
html += '<div style="padding:0.4rem 1rem;font-size:0.8rem;border-bottom:1px solid var(--border);">';
|
|
3955
|
+
html += '<span style="color:var(--text-muted);font-weight:500;">' + sectionBRs.length + ' règle' + (sectionBRs.length > 1 ? 's' : '') + ' métier :</span> ';
|
|
3956
|
+
html += sectionBRs.map(function(br) { return '<span style="color:var(--text-bright);">' + escapeHtml(br.name || '') + '</span>'; }).join(', ');
|
|
3957
|
+
html += '</div>';
|
|
3958
|
+
}
|
|
3959
|
+
|
|
3644
3960
|
if (resources.length > 0) {
|
|
3645
3961
|
html += '<div class="struct-resources">';
|
|
3646
3962
|
resources.forEach(function(res) {
|
|
3647
3963
|
var resName = typeof res === 'string' ? res : (res.code || res.name || '');
|
|
3964
|
+
var resType = typeof res === 'object' ? (res.type || '') : '';
|
|
3648
3965
|
var resDesc = typeof res === 'object' ? (res.description || '') : '';
|
|
3649
3966
|
html += '<div class="struct-resource">';
|
|
3650
3967
|
html += '<span class="struct-resource-icon">•</span>';
|
|
3651
|
-
html += '<span class="struct-resource-name">' + resName + '</span>';
|
|
3652
|
-
if (
|
|
3968
|
+
html += '<span class="struct-resource-name">' + escapeHtml(resName) + '</span>';
|
|
3969
|
+
if (resType) html += '<span style="font-size:0.7rem;color:var(--accent);background:rgba(6,182,212,0.1);padding:0.1rem 0.3rem;border-radius:3px;">' + escapeHtml(resType) + '</span>';
|
|
3970
|
+
if (resDesc) html += '<span class="struct-resource-desc">' + escapeHtml(resDesc) + '</span>';
|
|
3653
3971
|
html += '</div>';
|
|
3654
3972
|
});
|
|
3655
3973
|
html += '</div>';
|
|
@@ -3660,16 +3978,111 @@ function renderModuleStructure(code) {
|
|
|
3660
3978
|
}).join('');
|
|
3661
3979
|
}
|
|
3662
3980
|
|
|
3981
|
+
/* ---------- Section-Grouped Rendering (Hierarchical Mode) ---------- */
|
|
3982
|
+
|
|
3983
|
+
function renderSectionGroupedItems(mod, itemsKey, code, renderFn) {
|
|
3984
|
+
var sections = mod.anticipatedSections || [];
|
|
3985
|
+
var html = '';
|
|
3986
|
+
sections.forEach(function(section) {
|
|
3987
|
+
var items = section[itemsKey] || [];
|
|
3988
|
+
if (items.length === 0) return;
|
|
3989
|
+
html += '<div class="section-group">';
|
|
3990
|
+
html += '<div class="section-group-header">';
|
|
3991
|
+
html += '<span class="section-group-icon">▷</span> ';
|
|
3992
|
+
html += '<span class="section-group-label">' + escapeHtml(section.code || section.name || '') + '</span>';
|
|
3993
|
+
if (section.route) html += '<span class="section-group-route">' + escapeHtml(section.route) + '</span>';
|
|
3994
|
+
html += '<span class="nav-badge">' + items.length + '</span>';
|
|
3995
|
+
html += '</div>';
|
|
3996
|
+
html += items.map(function(item, i) { return renderFn(code, item, i); }).join('');
|
|
3997
|
+
html += '</div>';
|
|
3998
|
+
});
|
|
3999
|
+
return html;
|
|
4000
|
+
}
|
|
4001
|
+
|
|
4002
|
+
function renderModuleMockups(code) {
|
|
4003
|
+
var spec = data.moduleSpecs[code] || {};
|
|
4004
|
+
var screens = spec.screens || [];
|
|
4005
|
+
var wireframes = EMBEDDED_ARTIFACTS?.wireframes?.[code] || [];
|
|
4006
|
+
var mod = data.modules.find(function(m) { return m.code === code; });
|
|
4007
|
+
var sections = mod ? (mod.anticipatedSections || []) : [];
|
|
4008
|
+
|
|
4009
|
+
// Priority 1: HTML mockups from screens[] specs
|
|
4010
|
+
if (screens.length > 0) {
|
|
4011
|
+
var html = '';
|
|
4012
|
+
if (typeof renderScreenMockups === 'function') {
|
|
4013
|
+
html = renderScreenMockups(code);
|
|
4014
|
+
}
|
|
4015
|
+
if (wireframes.length > 0) {
|
|
4016
|
+
html += '<h3 style="color:var(--text-bright);font-size:1rem;margin:2rem 0 1rem;">Wireframes</h3>';
|
|
4017
|
+
html += wireframes.map(function(wf, i) { return renderWireframeMockup(code, wf, i); }).join('');
|
|
4018
|
+
}
|
|
4019
|
+
return html;
|
|
4020
|
+
}
|
|
4021
|
+
|
|
4022
|
+
// Priority 2: Wireframes grouped by section (if sections exist)
|
|
4023
|
+
if (wireframes.length > 0 && sections.length > 1) {
|
|
4024
|
+
var grouped = {};
|
|
4025
|
+
var ungrouped = [];
|
|
4026
|
+
wireframes.forEach(function(wf, i) {
|
|
4027
|
+
var sectionCode = wf.section || wf.sectionCode || '';
|
|
4028
|
+
var matchingSection = sections.find(function(s) { return s.code === sectionCode; });
|
|
4029
|
+
if (matchingSection) {
|
|
4030
|
+
if (!grouped[sectionCode]) grouped[sectionCode] = { section: matchingSection, items: [] };
|
|
4031
|
+
grouped[sectionCode].items.push({ wf: wf, index: i });
|
|
4032
|
+
} else {
|
|
4033
|
+
ungrouped.push({ wf: wf, index: i });
|
|
4034
|
+
}
|
|
4035
|
+
});
|
|
4036
|
+
|
|
4037
|
+
var html = '';
|
|
4038
|
+
Object.keys(grouped).forEach(function(sectionCode) {
|
|
4039
|
+
var group = grouped[sectionCode];
|
|
4040
|
+
html += '<div class="section-group">';
|
|
4041
|
+
html += '<div class="section-group-header">';
|
|
4042
|
+
html += '<span class="section-group-icon">▷</span> ';
|
|
4043
|
+
html += '<span class="section-group-label">' + escapeHtml(group.section.code || '') + '</span>';
|
|
4044
|
+
html += '<span class="nav-badge">' + group.items.length + '</span>';
|
|
4045
|
+
html += '</div>';
|
|
4046
|
+
group.items.forEach(function(item) {
|
|
4047
|
+
html += renderWireframeMockup(code, item.wf, item.index);
|
|
4048
|
+
});
|
|
4049
|
+
html += '</div>';
|
|
4050
|
+
});
|
|
4051
|
+
ungrouped.forEach(function(item) {
|
|
4052
|
+
html += renderWireframeMockup(code, item.wf, item.index);
|
|
4053
|
+
});
|
|
4054
|
+
return html;
|
|
4055
|
+
}
|
|
4056
|
+
|
|
4057
|
+
// Priority 3: Flat wireframe list
|
|
4058
|
+
if (wireframes.length === 0) {
|
|
4059
|
+
return '<div class="card" style="text-align:center;padding:2rem;color:var(--text-muted);"><p>Aucune maquette disponible pour ce module.</p><p style="font-size:0.85rem;margin-top:0.5rem;">Les maquettes seront générées lors de la spécification détaillée.</p></div>';
|
|
4060
|
+
}
|
|
4061
|
+
return wireframes.map(function(wf, i) { return renderWireframeMockup(code, wf, i); }).join('');
|
|
4062
|
+
}
|
|
4063
|
+
|
|
3663
4064
|
function switchTab(code, tabId) {
|
|
3664
4065
|
const section = document.getElementById('module-spec-' + code);
|
|
3665
4066
|
if (!section) return;
|
|
3666
|
-
section.querySelectorAll('.tab-btn').forEach(btn
|
|
3667
|
-
|
|
4067
|
+
section.querySelectorAll('.tab-btn').forEach(function(btn) {
|
|
4068
|
+
btn.classList.remove('active');
|
|
4069
|
+
btn.setAttribute('aria-selected', 'false');
|
|
4070
|
+
});
|
|
4071
|
+
section.querySelectorAll('.tab-panel').forEach(function(panel) {
|
|
4072
|
+
panel.classList.remove('active');
|
|
4073
|
+
panel.setAttribute('aria-hidden', 'true');
|
|
4074
|
+
});
|
|
3668
4075
|
const targetPanel = document.getElementById('tab-' + code + '-' + tabId);
|
|
3669
|
-
if (targetPanel)
|
|
4076
|
+
if (targetPanel) {
|
|
4077
|
+
targetPanel.classList.add('active');
|
|
4078
|
+
targetPanel.setAttribute('aria-hidden', 'false');
|
|
4079
|
+
}
|
|
3670
4080
|
const buttons = section.querySelectorAll('.tab-btn');
|
|
3671
4081
|
const tabIndex = { uc: 0, br: 1, ent: 2, perm: 3, mock: 4, notes: 5, struct: 6 }[tabId];
|
|
3672
|
-
if (buttons[tabIndex])
|
|
4082
|
+
if (buttons[tabIndex]) {
|
|
4083
|
+
buttons[tabIndex].classList.add('active');
|
|
4084
|
+
buttons[tabIndex].setAttribute('aria-selected', 'true');
|
|
4085
|
+
}
|
|
3673
4086
|
}
|
|
3674
4087
|
|
|
3675
4088
|
|
|
@@ -3729,7 +4142,7 @@ function renderDataModel() {
|
|
|
3729
4142
|
|
|
3730
4143
|
html += `<div class="dm-module-group">`;
|
|
3731
4144
|
html += `<div class="dm-module-header">
|
|
3732
|
-
<span class="dm-module-name">${m.name || m.code}</span>
|
|
4145
|
+
<span class="dm-module-name">${escapeHtml(m.name || m.code)}</span>
|
|
3733
4146
|
<span class="dm-module-count">${entities.length} entité${entities.length > 1 ? 's' : ''}</span>
|
|
3734
4147
|
</div>`;
|
|
3735
4148
|
|
|
@@ -3740,15 +4153,15 @@ function renderDataModel() {
|
|
|
3740
4153
|
html += `
|
|
3741
4154
|
<div class="dm-entity-card">
|
|
3742
4155
|
<div class="dm-entity-header">
|
|
3743
|
-
<span class="dm-entity-name">${ent.name}</span>
|
|
4156
|
+
<span class="dm-entity-name">${escapeHtml(ent.name)}</span>
|
|
3744
4157
|
<span class="dm-entity-attr-count">${attrs.length} champ${attrs.length > 1 ? 's' : ''}</span>
|
|
3745
4158
|
</div>
|
|
3746
|
-
${ent.description ? `<div class="dm-entity-desc">${ent.description}</div>` : ''}
|
|
4159
|
+
${ent.description ? `<div class="dm-entity-desc">${escapeHtml(ent.description)}</div>` : ''}
|
|
3747
4160
|
${attrs.length > 0 ? `
|
|
3748
4161
|
<table class="dm-attr-table">
|
|
3749
4162
|
<thead><tr><th>Champ</th><th>Description</th></tr></thead>
|
|
3750
4163
|
<tbody>
|
|
3751
|
-
${attrs.map(a => `<tr><td class="dm-attr-name">${a.name}</td><td class="dm-attr-desc">${a.description || ''}</td></tr>`).join('')}
|
|
4164
|
+
${attrs.map(a => `<tr><td class="dm-attr-name">${escapeHtml(a.name)}</td><td class="dm-attr-desc">${escapeHtml(a.description || '')}</td></tr>`).join('')}
|
|
3752
4165
|
</tbody>
|
|
3753
4166
|
</table>` : ''}
|
|
3754
4167
|
${rels.length > 0 ? `
|
|
@@ -3756,7 +4169,7 @@ function renderDataModel() {
|
|
|
3756
4169
|
<div class="dm-relations-title">Relations</div>
|
|
3757
4170
|
${rels.map(r => {
|
|
3758
4171
|
const relText = typeof r === 'string' ? r : (r.target + ' (' + r.type + ') - ' + (r.description || ''));
|
|
3759
|
-
return `<div class="dm-relation-item">${relText}</div>`;
|
|
4172
|
+
return `<div class="dm-relation-item">${escapeHtml(relText)}</div>`;
|
|
3760
4173
|
}).join('')}
|
|
3761
4174
|
</div>` : ''}
|
|
3762
4175
|
</div>`;
|
|
@@ -3776,11 +4189,11 @@ function renderConsolInteractions() {
|
|
|
3776
4189
|
const toName = data.modules.find(m => m.code === d.to)?.name || d.to;
|
|
3777
4190
|
return `
|
|
3778
4191
|
<div class="interaction-item">
|
|
3779
|
-
<span style="font-weight:600;color:var(--text-bright);">${fromName}</span>
|
|
4192
|
+
<span style="font-weight:600;color:var(--text-bright);">${escapeHtml(fromName)}</span>
|
|
3780
4193
|
<span class="interaction-arrow">→</span>
|
|
3781
|
-
<span style="font-weight:600;color:var(--text-bright);">${toName}</span>
|
|
4194
|
+
<span style="font-weight:600;color:var(--text-bright);">${escapeHtml(toName)}</span>
|
|
3782
4195
|
<span class="interaction-type">Dépendance</span>
|
|
3783
|
-
<span style="flex:1;font-size:0.8rem;color:var(--text-muted);">${d.description || ''}</span>
|
|
4196
|
+
<span style="flex:1;font-size:0.8rem;color:var(--text-muted);">${escapeHtml(d.description || '')}</span>
|
|
3784
4197
|
</div>`;
|
|
3785
4198
|
}).join('');
|
|
3786
4199
|
}
|
|
@@ -3793,11 +4206,11 @@ function renderConsolPermissions() {
|
|
|
3793
4206
|
|
|
3794
4207
|
let html = '<table class="mock-table" style="background:var(--bg-card);border-radius:8px;overflow:hidden;">';
|
|
3795
4208
|
html += '<thead><tr><th>Profil</th>';
|
|
3796
|
-
data.modules.forEach(m => { html += `<th style="text-align:center;">${m.name || m.code}</th>`; });
|
|
4209
|
+
data.modules.forEach(m => { html += `<th style="text-align:center;">${escapeHtml(m.name || m.code)}</th>`; });
|
|
3797
4210
|
html += '</tr></thead><tbody>';
|
|
3798
4211
|
|
|
3799
4212
|
roles.forEach(role => {
|
|
3800
|
-
html += `<tr><td style="font-weight:500;color:var(--text-bright);">${role}</td>`;
|
|
4213
|
+
html += `<tr><td style="font-weight:500;color:var(--text-bright);">${escapeHtml(role)}</td>`;
|
|
3801
4214
|
data.modules.forEach(m => {
|
|
3802
4215
|
const allPerms = data.moduleSpecs[m.code]?.permissions || [];
|
|
3803
4216
|
const hasWildcard = allPerms.includes(role + '|*');
|
|
@@ -3841,6 +4254,7 @@ function addE2EFlow() {
|
|
|
3841
4254
|
}
|
|
3842
4255
|
|
|
3843
4256
|
function removeE2EFlow(index) {
|
|
4257
|
+
if (!confirm('Supprimer ce parcours bout en bout ?')) return;
|
|
3844
4258
|
data.consolidation.e2eFlows.splice(index, 1);
|
|
3845
4259
|
renderE2EFlows();
|
|
3846
4260
|
autoSave();
|
|
@@ -3853,15 +4267,15 @@ function renderE2EFlows() {
|
|
|
3853
4267
|
container.innerHTML = data.consolidation.e2eFlows.map((flow, fi) => `
|
|
3854
4268
|
<div class="card" style="margin-bottom:1rem;">
|
|
3855
4269
|
<div class="card-header">
|
|
3856
|
-
<span class="card-title">${flow.name}</span>
|
|
4270
|
+
<span class="card-title">${escapeHtml(flow.name)}</span>
|
|
3857
4271
|
<button class="btn btn-sm" onclick="removeE2EFlow(${fi})" style="opacity:0.5;">Supprimer</button>
|
|
3858
4272
|
</div>
|
|
3859
|
-
${flow.actors ? `<div style="font-size:0.8rem;color:var(--text-muted);margin-bottom:0.5rem;">Intervenants : ${flow.actors}</div>` : ''}
|
|
4273
|
+
${flow.actors ? `<div style="font-size:0.8rem;color:var(--text-muted);margin-bottom:0.5rem;">Intervenants : ${escapeHtml(flow.actors)}</div>` : ''}
|
|
3860
4274
|
<div class="e2e-flow">
|
|
3861
4275
|
${flow.steps.map((s, i) => `
|
|
3862
4276
|
<div class="e2e-step">
|
|
3863
|
-
<div class="e2e-step-module">${s.module}</div>
|
|
3864
|
-
<div class="e2e-step-action">${s.action}</div>
|
|
4277
|
+
<div class="e2e-step-module">${escapeHtml(s.module)}</div>
|
|
4278
|
+
<div class="e2e-step-action">${escapeHtml(s.action)}</div>
|
|
3865
4279
|
</div>
|
|
3866
4280
|
${i < flow.steps.length - 1 ? '<div class="process-arrow">→</div>' : ''}
|
|
3867
4281
|
`).join('')}
|
|
@@ -3887,7 +4301,7 @@ function renderScreenMockups(code) {
|
|
|
3887
4301
|
var resources = screen.resources || [];
|
|
3888
4302
|
return '<div class="screen-section" style="margin-bottom:2rem;">' +
|
|
3889
4303
|
'<h3 style="color:var(--text-bright);font-size:1rem;margin-bottom:1rem;">' +
|
|
3890
|
-
'<span style="color:var(--accent);">▸</span> ' + (screen.sectionLabel || screen.sectionCode) +
|
|
4304
|
+
'<span style="color:var(--accent);">▸</span> ' + escapeHtml(screen.sectionLabel || screen.sectionCode) +
|
|
3891
4305
|
'</h3>' +
|
|
3892
4306
|
resources.map(function(res, ri) {
|
|
3893
4307
|
return renderResourceMockup(code, screen.sectionCode, res, ri);
|
|
@@ -3905,9 +4319,9 @@ function renderResourceMockup(code, sectionCode, res, index) {
|
|
|
3905
4319
|
html += '<div class="mockup-dot mockup-dot-red"></div>';
|
|
3906
4320
|
html += '<div class="mockup-dot mockup-dot-yellow"></div>';
|
|
3907
4321
|
html += '<div class="mockup-dot mockup-dot-green"></div>';
|
|
3908
|
-
html += '<span class="mockup-title">' + (res.label || res.code) + ' (' + res.type + ')</span>';
|
|
4322
|
+
html += '<span class="mockup-title">' + escapeHtml(res.label || res.code) + ' (' + res.type + ')</span>';
|
|
3909
4323
|
if (res.permission) {
|
|
3910
|
-
html += '<span style="margin-left:auto;font-size:0.65rem;color:var(--text-muted);background:var(--bg-dark);padding:0.15rem 0.5rem;border-radius:4px;">' + res.permission + '</span>';
|
|
4324
|
+
html += '<span style="margin-left:auto;font-size:0.65rem;color:var(--text-muted);background:var(--bg-dark);padding:0.15rem 0.5rem;border-radius:4px;">' + escapeHtml(res.permission) + '</span>';
|
|
3911
4325
|
}
|
|
3912
4326
|
html += '</div>';
|
|
3913
4327
|
|
|
@@ -3940,7 +4354,7 @@ function renderResourceMockup(code, sectionCode, res, index) {
|
|
|
3940
4354
|
// Notes
|
|
3941
4355
|
if (res.notes) {
|
|
3942
4356
|
html += '<div style="padding:0.5rem 1rem;font-size:0.8rem;color:var(--text-muted);border-top:1px solid var(--border);background:var(--bg-input);">';
|
|
3943
|
-
html += '<strong>Notes:</strong> ' + res.notes;
|
|
4357
|
+
html += '<strong>Notes:</strong> ' + escapeHtml(res.notes);
|
|
3944
4358
|
html += '</div>';
|
|
3945
4359
|
}
|
|
3946
4360
|
|
|
@@ -3967,7 +4381,7 @@ function renderSmartTableMockup(res) {
|
|
|
3967
4381
|
|
|
3968
4382
|
// Header with actions
|
|
3969
4383
|
html += '<div class="mock-header">';
|
|
3970
|
-
html += '<span class="mock-title">' + (res.label || 'Liste') + '</span>';
|
|
4384
|
+
html += '<span class="mock-title">' + escapeHtml(res.label || 'Liste') + '</span>';
|
|
3971
4385
|
html += '<div style="display:flex;gap:0.4rem;">';
|
|
3972
4386
|
(res.actions || []).forEach(function(action) {
|
|
3973
4387
|
var isPrimary = action === 'create' || action === 'export';
|
|
@@ -3989,7 +4403,7 @@ function renderSmartTableMockup(res) {
|
|
|
3989
4403
|
html += '<table class="mock-table">';
|
|
3990
4404
|
html += '<thead><tr>';
|
|
3991
4405
|
columns.forEach(function(col) {
|
|
3992
|
-
html += '<th>' + (col.label || col.field);
|
|
4406
|
+
html += '<th>' + escapeHtml(col.label || col.field);
|
|
3993
4407
|
if (col.sortable) html += ' <span style="font-size:0.6rem;color:var(--text-muted);">▲▼</span>';
|
|
3994
4408
|
html += '</th>';
|
|
3995
4409
|
});
|
|
@@ -4040,7 +4454,7 @@ function renderSmartFormMockup(res) {
|
|
|
4040
4454
|
|
|
4041
4455
|
// Header
|
|
4042
4456
|
html += '<div class="mock-header">';
|
|
4043
|
-
html += '<span class="mock-title">' + (res.label || 'Formulaire') + '</span>';
|
|
4457
|
+
html += '<span class="mock-title">' + escapeHtml(res.label || 'Formulaire') + '</span>';
|
|
4044
4458
|
html += '<div style="display:flex;gap:0.4rem;">';
|
|
4045
4459
|
(res.actions || ['save', 'cancel']).forEach(function(action) {
|
|
4046
4460
|
var isPrimary = action === 'save';
|
|
@@ -4052,7 +4466,7 @@ function renderSmartFormMockup(res) {
|
|
|
4052
4466
|
if (tabs.length > 1) {
|
|
4053
4467
|
html += '<div style="display:flex;gap:0;border-bottom:1px solid var(--border);margin-bottom:1.5rem;">';
|
|
4054
4468
|
tabs.forEach(function(tab, i) {
|
|
4055
|
-
html += '<span style="padding:0.5rem 1rem;font-size:0.85rem;cursor:pointer;border-bottom:2px solid ' + (i === 0 ? 'var(--primary)' : 'transparent') + ';color:' + (i === 0 ? 'var(--primary-light)' : 'var(--text-muted)') + ';">' + tab.label + '</span>';
|
|
4469
|
+
html += '<span style="padding:0.5rem 1rem;font-size:0.85rem;cursor:pointer;border-bottom:2px solid ' + (i === 0 ? 'var(--primary)' : 'transparent') + ';color:' + (i === 0 ? 'var(--primary-light)' : 'var(--text-muted)') + ';">' + escapeHtml(tab.label) + '</span>';
|
|
4056
4470
|
});
|
|
4057
4471
|
html += '</div>';
|
|
4058
4472
|
}
|
|
@@ -4071,7 +4485,7 @@ function renderSmartFormMockup(res) {
|
|
|
4071
4485
|
html += '<div class="mock-form-row">';
|
|
4072
4486
|
row.forEach(function(field) {
|
|
4073
4487
|
html += '<div class="mock-form-group">';
|
|
4074
|
-
html += '<label class="mock-label">' + (field.label || field.field);
|
|
4488
|
+
html += '<label class="mock-label">' + escapeHtml(field.label || field.field);
|
|
4075
4489
|
if (field.required) html += ' <span style="color:var(--error);">*</span>';
|
|
4076
4490
|
html += '</label>';
|
|
4077
4491
|
html += renderFormFieldMockup(field);
|
|
@@ -4107,11 +4521,11 @@ function renderSubtableMockup(field) {
|
|
|
4107
4521
|
var entity = field.entity || 'Element';
|
|
4108
4522
|
var html = '<div style="margin:1rem 0;border:1px solid var(--border);border-radius:8px;overflow:hidden;">';
|
|
4109
4523
|
html += '<div style="display:flex;justify-content:space-between;align-items:center;padding:0.5rem 0.75rem;background:var(--bg-hover);">';
|
|
4110
|
-
html += '<span style="font-weight:500;color:var(--text-bright);font-size:0.85rem;">' + entity + '</span>';
|
|
4524
|
+
html += '<span style="font-weight:500;color:var(--text-bright);font-size:0.85rem;">' + escapeHtml(entity) + '</span>';
|
|
4111
4525
|
html += '<span class="mock-btn" style="font-size:0.75rem;padding:0.2rem 0.5rem;">+ Ajouter</span>';
|
|
4112
4526
|
html += '</div>';
|
|
4113
4527
|
html += '<table class="mock-table"><thead><tr>';
|
|
4114
|
-
cols.forEach(function(c) { html += '<th>' + c + '</th>'; });
|
|
4528
|
+
cols.forEach(function(c) { html += '<th>' + escapeHtml(c) + '</th>'; });
|
|
4115
4529
|
html += '</tr></thead><tbody>';
|
|
4116
4530
|
html += '<tr>' + cols.map(function() { return '<td style="color:var(--text-muted);font-style:italic;">...</td>'; }).join('') + '</tr>';
|
|
4117
4531
|
html += '</tbody></table></div>';
|
|
@@ -4121,7 +4535,7 @@ function renderSubtableMockup(field) {
|
|
|
4121
4535
|
/* ---------- SmartCard ---------- */
|
|
4122
4536
|
function renderSmartCardMockup(res) {
|
|
4123
4537
|
var columns = res.columns || res.fields || [];
|
|
4124
|
-
var html = '<div class="mock-header"><span class="mock-title">' + (res.label || 'Cartes') + '</span></div>';
|
|
4538
|
+
var html = '<div class="mock-header"><span class="mock-title">' + escapeHtml(res.label || 'Cartes') + '</span></div>';
|
|
4125
4539
|
html += '<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:1rem;">';
|
|
4126
4540
|
|
|
4127
4541
|
for (var i = 0; i < 4; i++) {
|
|
@@ -4129,9 +4543,9 @@ function renderSmartCardMockup(res) {
|
|
|
4129
4543
|
columns.forEach(function(col, ci) {
|
|
4130
4544
|
var label = col.label || col.field || col;
|
|
4131
4545
|
if (ci === 0) {
|
|
4132
|
-
html += '<div style="font-weight:600;color:var(--text-bright);margin-bottom:0.5rem;">' + label + ' #' + (i + 1) + '</div>';
|
|
4546
|
+
html += '<div style="font-weight:600;color:var(--text-bright);margin-bottom:0.5rem;">' + escapeHtml(label) + ' #' + (i + 1) + '</div>';
|
|
4133
4547
|
} else {
|
|
4134
|
-
html += '<div style="font-size:0.8rem;color:var(--text-muted);margin-bottom:0.25rem;">' + label + ': <span style="color:var(--text);">valeur</span></div>';
|
|
4548
|
+
html += '<div style="font-size:0.8rem;color:var(--text-muted);margin-bottom:0.25rem;">' + escapeHtml(label) + ': <span style="color:var(--text);">valeur</span></div>';
|
|
4135
4549
|
}
|
|
4136
4550
|
});
|
|
4137
4551
|
html += '</div>';
|
|
@@ -4143,14 +4557,14 @@ function renderSmartCardMockup(res) {
|
|
|
4143
4557
|
/* ---------- SmartKanban ---------- */
|
|
4144
4558
|
function renderSmartKanbanMockup(res) {
|
|
4145
4559
|
var options = res.options || res.columns || ['À faire', 'En cours', 'Terminé'];
|
|
4146
|
-
var html = '<div class="mock-header"><span class="mock-title">' + (res.label || 'Kanban') + '</span></div>';
|
|
4560
|
+
var html = '<div class="mock-header"><span class="mock-title">' + escapeHtml(res.label || 'Kanban') + '</span></div>';
|
|
4147
4561
|
html += '<div style="display:flex;gap:1rem;overflow-x:auto;padding-bottom:0.5rem;">';
|
|
4148
4562
|
|
|
4149
4563
|
options.forEach(function(col, ci) {
|
|
4150
4564
|
var colLabel = typeof col === 'string' ? col : (col.label || col.field || 'Colonne');
|
|
4151
4565
|
html += '<div style="min-width:200px;flex:1;background:var(--bg-hover);border-radius:8px;padding:0.75rem;">';
|
|
4152
4566
|
html += '<div style="font-weight:600;font-size:0.85rem;color:var(--text-bright);margin-bottom:0.75rem;display:flex;justify-content:space-between;">';
|
|
4153
|
-
html += colLabel + ' <span style="font-size:0.7rem;background:var(--bg-card);padding:0.1rem 0.4rem;border-radius:4px;color:var(--text-muted);">' + (3 - ci) + '</span>';
|
|
4567
|
+
html += escapeHtml(colLabel) + ' <span style="font-size:0.7rem;background:var(--bg-card);padding:0.1rem 0.4rem;border-radius:4px;color:var(--text-muted);">' + (3 - ci) + '</span>';
|
|
4154
4568
|
html += '</div>';
|
|
4155
4569
|
for (var j = 0; j < Math.max(1, 3 - ci); j++) {
|
|
4156
4570
|
html += '<div style="background:var(--bg-card);border:1px solid var(--border);border-radius:6px;padding:0.5rem;margin-bottom:0.5rem;font-size:0.8rem;">';
|
|
@@ -4166,7 +4580,7 @@ function renderSmartKanbanMockup(res) {
|
|
|
4166
4580
|
|
|
4167
4581
|
/* ---------- SmartDashboard ---------- */
|
|
4168
4582
|
function renderSmartDashboardMockup(res) {
|
|
4169
|
-
var html = '<div class="mock-header"><span class="mock-title">' + (res.label || 'Tableau de bord') + '</span></div>';
|
|
4583
|
+
var html = '<div class="mock-header"><span class="mock-title">' + escapeHtml(res.label || 'Tableau de bord') + '</span></div>';
|
|
4170
4584
|
|
|
4171
4585
|
// KPI cards
|
|
4172
4586
|
html += '<div class="mock-kpi-grid">';
|
|
@@ -4177,7 +4591,7 @@ function renderSmartDashboardMockup(res) {
|
|
|
4177
4591
|
{ label: 'Taux', value: '80%' }
|
|
4178
4592
|
];
|
|
4179
4593
|
kpis.forEach(function(kpi) {
|
|
4180
|
-
html += '<div class="mock-kpi"><div class="mock-kpi-value">' + (kpi.value || '0') + '</div><div class="mock-kpi-label">' + (kpi.label || '') + '</div></div>';
|
|
4594
|
+
html += '<div class="mock-kpi"><div class="mock-kpi-value">' + escapeHtml(kpi.value || '0') + '</div><div class="mock-kpi-label">' + escapeHtml(kpi.label || '') + '</div></div>';
|
|
4181
4595
|
});
|
|
4182
4596
|
html += '</div>';
|
|
4183
4597
|
|
|
@@ -4193,7 +4607,7 @@ function renderSmartFilterMockup(res) {
|
|
|
4193
4607
|
var html = '<div style="display:flex;gap:0.4rem;flex-wrap:wrap;padding:0.5rem 0;">';
|
|
4194
4608
|
html += '<span style="padding:0.3rem 0.7rem;border-radius:16px;font-size:0.8rem;background:var(--primary);color:#fff;cursor:pointer;">Tous</span>';
|
|
4195
4609
|
options.forEach(function(opt) {
|
|
4196
|
-
html += '<span style="padding:0.3rem 0.7rem;border-radius:16px;font-size:0.8rem;background:var(--bg-hover);color:var(--text);border:1px solid var(--border);cursor:pointer;">' + opt + '</span>';
|
|
4610
|
+
html += '<span style="padding:0.3rem 0.7rem;border-radius:16px;font-size:0.8rem;background:var(--bg-hover);color:var(--text);border:1px solid var(--border);cursor:pointer;">' + escapeHtml(opt) + '</span>';
|
|
4197
4611
|
});
|
|
4198
4612
|
html += '</div>';
|
|
4199
4613
|
return html;
|
|
@@ -4254,6 +4668,7 @@ function renderHandoff() {
|
|
|
4254
4668
|
renderHandoffStats();
|
|
4255
4669
|
renderHandoffModules();
|
|
4256
4670
|
renderCoverageMatrix();
|
|
4671
|
+
renderProgressIndicator();
|
|
4257
4672
|
}
|
|
4258
4673
|
|
|
4259
4674
|
function renderHandoffStats() {
|
|
@@ -4292,8 +4707,8 @@ function renderHandoffModules() {
|
|
|
4292
4707
|
<div style="display:flex;align-items:center;gap:0.75rem;">
|
|
4293
4708
|
<div style="width:28px;height:28px;border-radius:50%;background:var(--primary);display:flex;align-items:center;justify-content:center;color:#fff;font-size:0.75rem;font-weight:700;flex-shrink:0;">${i + 1}</div>
|
|
4294
4709
|
<div style="flex:1;">
|
|
4295
|
-
<div style="font-weight:600;color:var(--text-bright);">${m.name}</div>
|
|
4296
|
-
<div style="font-size:0.8rem;color:var(--text-muted);">${m.description || ''}</div>
|
|
4710
|
+
<div style="font-weight:600;color:var(--text-bright);">${escapeHtml(m.name)}</div>
|
|
4711
|
+
<div style="font-size:0.8rem;color:var(--text-muted);">${escapeHtml(m.description || '')}</div>
|
|
4297
4712
|
</div>
|
|
4298
4713
|
<div style="display:flex;gap:1rem;font-size:0.75rem;color:var(--text-muted);">
|
|
4299
4714
|
<span>${(spec.useCases || []).length} cas d'utilisation</span>
|
|
@@ -4326,24 +4741,47 @@ function renderCoverageMatrix() {
|
|
|
4326
4741
|
: (data.modules.length > 0 ? data.modules.map(m => m.name).join(', ') : 'À définir');
|
|
4327
4742
|
return `
|
|
4328
4743
|
<tr>
|
|
4329
|
-
<td>${item.name}</td>
|
|
4744
|
+
<td>${escapeHtml(item.name)}</td>
|
|
4330
4745
|
<td><span class="priority priority-${item.priority}">${formatPriority(item.priority)}</span></td>
|
|
4331
|
-
<td style="color:var(--text-muted);">${moduleName}</td>
|
|
4746
|
+
<td style="color:var(--text-muted);">${escapeHtml(moduleName)}</td>
|
|
4332
4747
|
<td style="text-align:center;color:var(--success);">✓</td>
|
|
4333
4748
|
</tr>`;
|
|
4334
4749
|
}).join('')}
|
|
4335
4750
|
</tbody>
|
|
4336
4751
|
</table>`;
|
|
4337
4752
|
}
|
|
4753
|
+
|
|
4754
|
+
function renderProgressIndicator() {
|
|
4755
|
+
var container = document.getElementById('progressIndicator');
|
|
4756
|
+
if (!container) return;
|
|
4757
|
+
var p = computeProgress();
|
|
4758
|
+
var html = '<div class="progress-bar-container">';
|
|
4759
|
+
html += '<div class="progress-bar-fill" style="width:' + p.pct + '%;"></div>';
|
|
4760
|
+
html += '</div>';
|
|
4761
|
+
html += '<div class="progress-label">' + p.pct + '% complet — ' + p.doneCount + '/' + p.total + ' critères</div>';
|
|
4762
|
+
html += '<div class="progress-checks">';
|
|
4763
|
+
p.checks.forEach(function(c) {
|
|
4764
|
+
html += '<div class="progress-check ' + (c.done ? 'done' : '') + '">';
|
|
4765
|
+
html += '<span class="progress-check-icon">' + (c.done ? '✓' : '○') + '</span> ';
|
|
4766
|
+
html += escapeHtml(c.label);
|
|
4767
|
+
html += '</div>';
|
|
4768
|
+
});
|
|
4769
|
+
html += '</div>';
|
|
4770
|
+
container.innerHTML = html;
|
|
4771
|
+
}
|
|
4338
4772
|
|
|
4339
4773
|
|
|
4340
4774
|
/* --- 08-editing.js --- */
|
|
4341
4775
|
/* ============================================
|
|
4342
4776
|
PERSISTENCE
|
|
4343
4777
|
============================================ */
|
|
4778
|
+
let _saveTimer;
|
|
4344
4779
|
function autoSave() {
|
|
4345
|
-
|
|
4346
|
-
|
|
4780
|
+
clearTimeout(_saveTimer);
|
|
4781
|
+
_saveTimer = setTimeout(function() {
|
|
4782
|
+
data.metadata.lastModified = new Date().toISOString();
|
|
4783
|
+
localStorage.setItem(APP_KEY, JSON.stringify(data));
|
|
4784
|
+
}, 500);
|
|
4347
4785
|
}
|
|
4348
4786
|
|
|
4349
4787
|
function saveToLocalStorage() {
|
|
@@ -4477,13 +4915,10 @@ function loadFromLocalStorage() {
|
|
|
4477
4915
|
/* ============================================
|
|
4478
4916
|
EXPORT JSON
|
|
4479
4917
|
============================================ */
|
|
4480
|
-
function
|
|
4481
|
-
// Collect all editable fields (cadrage)
|
|
4918
|
+
function collectEditableFields() {
|
|
4482
4919
|
document.querySelectorAll('.editable[data-field]').forEach(el => {
|
|
4483
4920
|
setNestedValue(data, 'cadrage.' + el.dataset.field, el.textContent.trim());
|
|
4484
4921
|
});
|
|
4485
|
-
|
|
4486
|
-
// Collect module editable fields
|
|
4487
4922
|
document.querySelectorAll('.editable[data-module-field]').forEach(el => {
|
|
4488
4923
|
const code = el.dataset.moduleCode;
|
|
4489
4924
|
const field = el.dataset.moduleField;
|
|
@@ -4491,17 +4926,39 @@ function exportJSON() {
|
|
|
4491
4926
|
data.moduleSpecs[code][field] = el.textContent.trim();
|
|
4492
4927
|
}
|
|
4493
4928
|
});
|
|
4929
|
+
}
|
|
4930
|
+
|
|
4931
|
+
function buildModuleSpecsExport() {
|
|
4932
|
+
var result = {};
|
|
4933
|
+
data.modules.forEach(function(m) {
|
|
4934
|
+
var spec = data.moduleSpecs[m.code] || {};
|
|
4935
|
+
result[m.code] = {
|
|
4936
|
+
module: m,
|
|
4937
|
+
useCases: (spec.useCases || []).map(function(uc, i) {
|
|
4938
|
+
return Object.assign({ id: 'UC-' + String(i + 1).padStart(3, '0') }, uc);
|
|
4939
|
+
}),
|
|
4940
|
+
businessRules: (spec.businessRules || []).map(function(br, i) {
|
|
4941
|
+
return Object.assign({ id: 'BR-' + (br.category || 'GEN').toUpperCase().substring(0, 4) + '-' + String(i + 1).padStart(3, '0') }, br);
|
|
4942
|
+
}),
|
|
4943
|
+
entities: spec.entities || [],
|
|
4944
|
+
permissions: spec.permissions || [],
|
|
4945
|
+
notes: spec.notes || ''
|
|
4946
|
+
};
|
|
4947
|
+
});
|
|
4948
|
+
return result;
|
|
4949
|
+
}
|
|
4494
4950
|
|
|
4951
|
+
function exportJSON() {
|
|
4952
|
+
collectEditableFields();
|
|
4495
4953
|
data.metadata.lastModified = new Date().toISOString();
|
|
4496
4954
|
data.metadata.exportedAt = new Date().toISOString();
|
|
4497
4955
|
|
|
4498
|
-
// Build complete export with structured data
|
|
4499
4956
|
const exportData = {
|
|
4500
4957
|
metadata: data.metadata,
|
|
4501
4958
|
cadrage: data.cadrage,
|
|
4502
4959
|
modules: data.modules,
|
|
4503
4960
|
dependencies: data.dependencies,
|
|
4504
|
-
moduleSpecifications:
|
|
4961
|
+
moduleSpecifications: buildModuleSpecsExport(),
|
|
4505
4962
|
consolidation: data.consolidation,
|
|
4506
4963
|
artifacts: EMBEDDED_ARTIFACTS,
|
|
4507
4964
|
wireframeComments: data.wireframeComments,
|
|
@@ -4511,25 +4968,6 @@ function exportJSON() {
|
|
|
4511
4968
|
comments: data.comments
|
|
4512
4969
|
};
|
|
4513
4970
|
|
|
4514
|
-
// Structure module specs for export
|
|
4515
|
-
data.modules.forEach(m => {
|
|
4516
|
-
const spec = data.moduleSpecs[m.code] || {};
|
|
4517
|
-
exportData.moduleSpecifications[m.code] = {
|
|
4518
|
-
module: m,
|
|
4519
|
-
useCases: (spec.useCases || []).map((uc, i) => ({
|
|
4520
|
-
id: 'UC-' + String(i + 1).padStart(3, '0'),
|
|
4521
|
-
...uc
|
|
4522
|
-
})),
|
|
4523
|
-
businessRules: (spec.businessRules || []).map((br, i) => ({
|
|
4524
|
-
id: 'BR-' + br.category.toUpperCase().substring(0, 4) + '-' + String(i + 1).padStart(3, '0'),
|
|
4525
|
-
...br
|
|
4526
|
-
})),
|
|
4527
|
-
entities: spec.entities || [],
|
|
4528
|
-
permissions: spec.permissions || [],
|
|
4529
|
-
notes: spec.notes || ''
|
|
4530
|
-
};
|
|
4531
|
-
});
|
|
4532
|
-
|
|
4533
4971
|
const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
|
|
4534
4972
|
const url = URL.createObjectURL(blob);
|
|
4535
4973
|
const a = document.createElement('a');
|
|
@@ -4569,24 +5007,13 @@ function detectChanges(original, current) {
|
|
|
4569
5007
|
}
|
|
4570
5008
|
|
|
4571
5009
|
function saveReviewJSON() {
|
|
4572
|
-
|
|
4573
|
-
document.querySelectorAll('.editable[data-field]').forEach(el => {
|
|
4574
|
-
setNestedValue(data, 'cadrage.' + el.dataset.field, el.textContent.trim());
|
|
4575
|
-
});
|
|
4576
|
-
document.querySelectorAll('.editable[data-module-field]').forEach(el => {
|
|
4577
|
-
const code = el.dataset.moduleCode;
|
|
4578
|
-
const field = el.dataset.moduleField;
|
|
4579
|
-
if (data.moduleSpecs[code]) {
|
|
4580
|
-
data.moduleSpecs[code][field] = el.textContent.trim();
|
|
4581
|
-
}
|
|
4582
|
-
});
|
|
5010
|
+
collectEditableFields();
|
|
4583
5011
|
|
|
4584
5012
|
const changes = detectChanges(ORIGINAL_DATA, data);
|
|
4585
5013
|
const hasChanges = changes.cadrage || changes.modulesAdded.length > 0
|
|
4586
5014
|
|| changes.modulesRemoved.length > 0 || changes.modulesModified.length > 0
|
|
4587
5015
|
|| changes.commentsCount > 0;
|
|
4588
5016
|
|
|
4589
|
-
// Build review export with metadata envelope
|
|
4590
5017
|
const reviewData = {
|
|
4591
5018
|
_reviewMeta: {
|
|
4592
5019
|
sourceVersion: data.metadata.version || '1.0',
|
|
@@ -4605,7 +5032,7 @@ function saveReviewJSON() {
|
|
|
4605
5032
|
cadrage: data.cadrage,
|
|
4606
5033
|
modules: data.modules,
|
|
4607
5034
|
dependencies: data.dependencies,
|
|
4608
|
-
moduleSpecifications:
|
|
5035
|
+
moduleSpecifications: buildModuleSpecsExport(),
|
|
4609
5036
|
consolidation: data.consolidation,
|
|
4610
5037
|
wireframeComments: data.wireframeComments,
|
|
4611
5038
|
specComments: data.specComments,
|
|
@@ -4614,25 +5041,6 @@ function saveReviewJSON() {
|
|
|
4614
5041
|
comments: data.comments
|
|
4615
5042
|
};
|
|
4616
5043
|
|
|
4617
|
-
// Structure module specs (same logic as exportJSON)
|
|
4618
|
-
data.modules.forEach(m => {
|
|
4619
|
-
const spec = data.moduleSpecs[m.code] || {};
|
|
4620
|
-
reviewData.moduleSpecifications[m.code] = {
|
|
4621
|
-
module: m,
|
|
4622
|
-
useCases: (spec.useCases || []).map((uc, i) => ({
|
|
4623
|
-
id: 'UC-' + String(i + 1).padStart(3, '0'),
|
|
4624
|
-
...uc
|
|
4625
|
-
})),
|
|
4626
|
-
businessRules: (spec.businessRules || []).map((br, i) => ({
|
|
4627
|
-
id: 'BR-' + br.category.toUpperCase().substring(0, 4) + '-' + String(i + 1).padStart(3, '0'),
|
|
4628
|
-
...br
|
|
4629
|
-
})),
|
|
4630
|
-
entities: spec.entities || [],
|
|
4631
|
-
permissions: spec.permissions || [],
|
|
4632
|
-
notes: spec.notes || ''
|
|
4633
|
-
};
|
|
4634
|
-
});
|
|
4635
|
-
|
|
4636
5044
|
const blob = new Blob([JSON.stringify(reviewData, null, 2)], { type: 'application/json' });
|
|
4637
5045
|
const url = URL.createObjectURL(blob);
|
|
4638
5046
|
const a = document.createElement('a');
|
|
@@ -4651,14 +5059,24 @@ function saveReviewJSON() {
|
|
|
4651
5059
|
|
|
4652
5060
|
/**
|
|
4653
5061
|
* Comments are stored in data.comments[] with structure:
|
|
4654
|
-
* { id, sectionId,
|
|
5062
|
+
* { id, sectionId, elementId, author, timestamp, content, status, category }
|
|
4655
5063
|
*
|
|
4656
|
-
* - sectionId: matches the section div id (e.g. "cadrage-
|
|
4657
|
-
* -
|
|
5064
|
+
* - sectionId: matches the section div id (e.g. "cadrage-context", "module-spec-Clients")
|
|
5065
|
+
* - elementId: stable identifier for the element (name, role, or generated id)
|
|
5066
|
+
* - cardIndex: (deprecated, kept for backward compat) positional index
|
|
4658
5067
|
* - status: "to-review" | "validated"
|
|
4659
5068
|
* - category: "clarification" | "correction" | "suggestion"
|
|
4660
5069
|
*/
|
|
4661
5070
|
|
|
5071
|
+
function getElementId(item, sectionId, index) {
|
|
5072
|
+
// Extract a stable ID from the element's data
|
|
5073
|
+
var title = item.querySelector('.uc-title, .entity-name, .stakeholder-role, .card-title');
|
|
5074
|
+
if (title) return title.textContent.trim();
|
|
5075
|
+
var nameEl = item.querySelector('[data-field]');
|
|
5076
|
+
if (nameEl) return nameEl.dataset.field;
|
|
5077
|
+
return sectionId + '-' + index;
|
|
5078
|
+
}
|
|
5079
|
+
|
|
4662
5080
|
function initInlineComments() {
|
|
4663
5081
|
// Cadrage sections: direct card children
|
|
4664
5082
|
document.querySelectorAll('.section > .card, .section > .stakeholder-card, .section > .uc-item').forEach(function(card) {
|
|
@@ -4668,7 +5086,8 @@ function initInlineComments() {
|
|
|
4668
5086
|
var sectionId = section ? section.id : 'unknown';
|
|
4669
5087
|
var siblings = Array.from(section.querySelectorAll(':scope > .card, :scope > .stakeholder-card, :scope > .uc-item'));
|
|
4670
5088
|
var index = siblings.indexOf(card);
|
|
4671
|
-
card
|
|
5089
|
+
var elementId = getElementId(card, sectionId, index);
|
|
5090
|
+
card.appendChild(createCommentUI(sectionId, elementId));
|
|
4672
5091
|
});
|
|
4673
5092
|
|
|
4674
5093
|
// Module spec lists: nested items in ucList, brList, entList containers
|
|
@@ -4679,7 +5098,8 @@ function initInlineComments() {
|
|
|
4679
5098
|
var listId = list.id;
|
|
4680
5099
|
var siblings = Array.from(list.children);
|
|
4681
5100
|
var index = siblings.indexOf(item);
|
|
4682
|
-
item
|
|
5101
|
+
var elementId = getElementId(item, listId, index);
|
|
5102
|
+
item.appendChild(createCommentUI(listId, elementId));
|
|
4683
5103
|
});
|
|
4684
5104
|
|
|
4685
5105
|
// Stakeholder cards in grid
|
|
@@ -4691,7 +5111,8 @@ function initInlineComments() {
|
|
|
4691
5111
|
var sectionId = section ? section.id : 'stakeholders';
|
|
4692
5112
|
var siblings = Array.from(grid.children);
|
|
4693
5113
|
var index = siblings.indexOf(card);
|
|
4694
|
-
card
|
|
5114
|
+
var elementId = getElementId(card, sectionId, index);
|
|
5115
|
+
card.appendChild(createCommentUI(sectionId, elementId));
|
|
4695
5116
|
});
|
|
4696
5117
|
|
|
4697
5118
|
// Scope items
|
|
@@ -4701,36 +5122,39 @@ function initInlineComments() {
|
|
|
4701
5122
|
container.querySelectorAll('.uc-item').forEach(function(item, index) {
|
|
4702
5123
|
if (item.dataset.commentInitialized) return;
|
|
4703
5124
|
item.dataset.commentInitialized = 'true';
|
|
4704
|
-
item
|
|
5125
|
+
var elementId = getElementId(item, containerId, index);
|
|
5126
|
+
item.appendChild(createCommentUI(containerId, elementId));
|
|
4705
5127
|
});
|
|
4706
5128
|
});
|
|
4707
5129
|
}
|
|
4708
5130
|
|
|
4709
|
-
function createCommentUI(sectionId,
|
|
4710
|
-
const comments = getCommentsForCard(sectionId,
|
|
5131
|
+
function createCommentUI(sectionId, elementId) {
|
|
5132
|
+
const comments = getCommentsForCard(sectionId, elementId);
|
|
4711
5133
|
const count = comments.length;
|
|
5134
|
+
const safeElId = String(elementId).replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
5135
|
+
const threadId = 'comment-thread-' + sectionId + '-' + safeElId;
|
|
4712
5136
|
|
|
4713
5137
|
const container = document.createElement('div');
|
|
4714
5138
|
container.className = 'comment-btn-container';
|
|
4715
5139
|
container.dataset.sectionId = sectionId;
|
|
4716
|
-
container.dataset.
|
|
5140
|
+
container.dataset.elementId = elementId;
|
|
4717
5141
|
|
|
4718
5142
|
container.innerHTML = `
|
|
4719
|
-
<button class="comment-toggle-btn" onclick="toggleCommentThread('${sectionId}', ${
|
|
5143
|
+
<button class="comment-toggle-btn" onclick="toggleCommentThread('${sectionId}', '${safeElId}')">
|
|
4720
5144
|
Commentaires <span class="comment-count ${count === 0 ? 'empty' : ''}">${count}</span>
|
|
4721
5145
|
</button>
|
|
4722
|
-
<div class="comment-thread" id="
|
|
4723
|
-
<div class="comment-items" id="comment-items-${sectionId}-${
|
|
4724
|
-
${renderCommentItems(sectionId,
|
|
5146
|
+
<div class="comment-thread" id="${threadId}">
|
|
5147
|
+
<div class="comment-items" id="comment-items-${sectionId}-${safeElId}">
|
|
5148
|
+
${renderCommentItems(sectionId, elementId)}
|
|
4725
5149
|
</div>
|
|
4726
5150
|
<div class="comment-add-form">
|
|
4727
|
-
<textarea id="comment-text-${sectionId}-${
|
|
4728
|
-
<select id="comment-cat-${sectionId}-${
|
|
5151
|
+
<textarea id="comment-text-${sectionId}-${safeElId}" placeholder="Ajouter un commentaire..."></textarea>
|
|
5152
|
+
<select id="comment-cat-${sectionId}-${safeElId}">
|
|
4729
5153
|
<option value="clarification">Clarification</option>
|
|
4730
5154
|
<option value="correction">Correction</option>
|
|
4731
5155
|
<option value="suggestion">Suggestion</option>
|
|
4732
5156
|
</select>
|
|
4733
|
-
<button onclick="addInlineComment('${sectionId}', ${
|
|
5157
|
+
<button onclick="addInlineComment('${sectionId}', '${elementId.replace(/'/g, "\\'")}')">Ajouter</button>
|
|
4734
5158
|
</div>
|
|
4735
5159
|
</div>
|
|
4736
5160
|
`;
|
|
@@ -4738,25 +5162,27 @@ function createCommentUI(sectionId, cardIndex) {
|
|
|
4738
5162
|
return container;
|
|
4739
5163
|
}
|
|
4740
5164
|
|
|
4741
|
-
function getCommentsForCard(sectionId,
|
|
4742
|
-
return (data.comments || []).filter(c
|
|
4743
|
-
c.sectionId === sectionId && c.
|
|
4744
|
-
|
|
5165
|
+
function getCommentsForCard(sectionId, elementId) {
|
|
5166
|
+
return (data.comments || []).filter(function(c) {
|
|
5167
|
+
if (c.elementId) return c.sectionId === sectionId && c.elementId === elementId;
|
|
5168
|
+
// Backward compat: match by cardIndex if elementId not set
|
|
5169
|
+
return c.sectionId === sectionId && c.cardIndex === elementId;
|
|
5170
|
+
});
|
|
4745
5171
|
}
|
|
4746
5172
|
|
|
4747
|
-
function toggleCommentThread(sectionId,
|
|
4748
|
-
const thread = document.getElementById('comment-thread-' + sectionId + '-' +
|
|
5173
|
+
function toggleCommentThread(sectionId, safeElId) {
|
|
5174
|
+
const thread = document.getElementById('comment-thread-' + sectionId + '-' + safeElId);
|
|
4749
5175
|
if (thread) thread.classList.toggle('visible');
|
|
4750
5176
|
}
|
|
4751
5177
|
|
|
4752
|
-
function renderCommentItems(sectionId,
|
|
4753
|
-
const comments = getCommentsForCard(sectionId,
|
|
5178
|
+
function renderCommentItems(sectionId, elementId) {
|
|
5179
|
+
const comments = getCommentsForCard(sectionId, elementId);
|
|
4754
5180
|
if (comments.length === 0) {
|
|
4755
5181
|
return '<div style="font-size:0.8rem;color:var(--text-muted);padding:0.5rem 0;font-style:italic;">Aucun commentaire</div>';
|
|
4756
5182
|
}
|
|
4757
5183
|
|
|
4758
|
-
return comments.map((c, i)
|
|
4759
|
-
const initials = (c.author || 'U').substring(0, 2).toUpperCase();
|
|
5184
|
+
return comments.map(function(c, i) {
|
|
5185
|
+
const initials = escapeHtml((c.author || 'U').substring(0, 2).toUpperCase());
|
|
4760
5186
|
const date = c.timestamp ? new Date(c.timestamp).toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }) : '';
|
|
4761
5187
|
const globalIndex = data.comments.indexOf(c);
|
|
4762
5188
|
|
|
@@ -4765,12 +5191,12 @@ function renderCommentItems(sectionId, cardIndex) {
|
|
|
4765
5191
|
<div class="comment-avatar">${initials}</div>
|
|
4766
5192
|
<div class="comment-body">
|
|
4767
5193
|
<div class="comment-meta">
|
|
4768
|
-
<span class="comment-author">${c.author || 'Utilisateur'}</span>
|
|
4769
|
-
<span class="comment-date">${date}</span>
|
|
4770
|
-
<span class="comment-category comment-category-${c.category}">${c.category}</span>
|
|
5194
|
+
<span class="comment-author">${escapeHtml(c.author || 'Utilisateur')}</span>
|
|
5195
|
+
<span class="comment-date">${escapeHtml(date)}</span>
|
|
5196
|
+
<span class="comment-category comment-category-${escapeHtml(c.category)}">${escapeHtml(c.category)}</span>
|
|
4771
5197
|
<span class="comment-status comment-status-${c.status}">${c.status === 'validated' ? 'Validé' : 'À revoir'}</span>
|
|
4772
5198
|
</div>
|
|
4773
|
-
<div class="comment-text">${c.content}</div>
|
|
5199
|
+
<div class="comment-text">${escapeHtml(c.content)}</div>
|
|
4774
5200
|
<div class="comment-actions">
|
|
4775
5201
|
<button class="comment-action-btn" onclick="toggleCommentStatus(${globalIndex})">${c.status === 'validated' ? 'Remettre à revoir' : 'Valider'}</button>
|
|
4776
5202
|
<button class="comment-action-btn" onclick="deleteComment(${globalIndex})" style="color:var(--error);">Supprimer</button>
|
|
@@ -4781,16 +5207,17 @@ function renderCommentItems(sectionId, cardIndex) {
|
|
|
4781
5207
|
}).join('');
|
|
4782
5208
|
}
|
|
4783
5209
|
|
|
4784
|
-
function addInlineComment(sectionId,
|
|
4785
|
-
|
|
4786
|
-
const
|
|
5210
|
+
function addInlineComment(sectionId, elementId) {
|
|
5211
|
+
var safeElId = String(elementId).replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
5212
|
+
const textEl = document.getElementById('comment-text-' + sectionId + '-' + safeElId);
|
|
5213
|
+
const catEl = document.getElementById('comment-cat-' + sectionId + '-' + safeElId);
|
|
4787
5214
|
const content = textEl.value.trim();
|
|
4788
5215
|
if (!content) return;
|
|
4789
5216
|
|
|
4790
5217
|
const comment = {
|
|
4791
5218
|
id: 'comment-' + Date.now(),
|
|
4792
5219
|
sectionId: sectionId,
|
|
4793
|
-
|
|
5220
|
+
elementId: elementId,
|
|
4794
5221
|
author: 'Utilisateur',
|
|
4795
5222
|
timestamp: new Date().toISOString(),
|
|
4796
5223
|
content: content,
|
|
@@ -4801,7 +5228,7 @@ function addInlineComment(sectionId, cardIndex) {
|
|
|
4801
5228
|
data.comments.push(comment);
|
|
4802
5229
|
textEl.value = '';
|
|
4803
5230
|
|
|
4804
|
-
refreshCommentUI(sectionId,
|
|
5231
|
+
refreshCommentUI(sectionId, elementId);
|
|
4805
5232
|
renderReviewPanel();
|
|
4806
5233
|
autoSave();
|
|
4807
5234
|
}
|
|
@@ -4810,32 +5237,35 @@ function toggleCommentStatus(globalIndex) {
|
|
|
4810
5237
|
const comment = data.comments[globalIndex];
|
|
4811
5238
|
if (!comment) return;
|
|
4812
5239
|
comment.status = comment.status === 'validated' ? 'to-review' : 'validated';
|
|
4813
|
-
|
|
5240
|
+
var elId = comment.elementId || comment.cardIndex;
|
|
5241
|
+
refreshCommentUI(comment.sectionId, elId);
|
|
4814
5242
|
renderReviewPanel();
|
|
4815
5243
|
autoSave();
|
|
4816
5244
|
}
|
|
4817
5245
|
|
|
4818
5246
|
function deleteComment(globalIndex) {
|
|
5247
|
+
if (!confirm('Supprimer ce commentaire ?')) return;
|
|
4819
5248
|
const comment = data.comments[globalIndex];
|
|
4820
5249
|
if (!comment) return;
|
|
4821
|
-
|
|
4822
|
-
|
|
5250
|
+
var sectionId = comment.sectionId;
|
|
5251
|
+
var elId = comment.elementId || comment.cardIndex;
|
|
4823
5252
|
data.comments.splice(globalIndex, 1);
|
|
4824
|
-
refreshCommentUI(sectionId,
|
|
5253
|
+
refreshCommentUI(sectionId, elId);
|
|
4825
5254
|
renderReviewPanel();
|
|
4826
5255
|
autoSave();
|
|
4827
5256
|
}
|
|
4828
5257
|
|
|
4829
|
-
function refreshCommentUI(sectionId,
|
|
5258
|
+
function refreshCommentUI(sectionId, elementId) {
|
|
5259
|
+
var safeElId = String(elementId).replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
4830
5260
|
// Update comment items
|
|
4831
|
-
const itemsContainer = document.getElementById('comment-items-' + sectionId + '-' +
|
|
5261
|
+
const itemsContainer = document.getElementById('comment-items-' + sectionId + '-' + safeElId);
|
|
4832
5262
|
if (itemsContainer) {
|
|
4833
|
-
itemsContainer.innerHTML = renderCommentItems(sectionId,
|
|
5263
|
+
itemsContainer.innerHTML = renderCommentItems(sectionId, elementId);
|
|
4834
5264
|
}
|
|
4835
5265
|
|
|
4836
5266
|
// Update count badge
|
|
4837
|
-
const count = getCommentsForCard(sectionId,
|
|
4838
|
-
const container = document.querySelector(
|
|
5267
|
+
const count = getCommentsForCard(sectionId, elementId).length;
|
|
5268
|
+
const container = document.querySelector('.comment-btn-container[data-section-id="' + sectionId + '"][data-element-id="' + elementId + '"]');
|
|
4839
5269
|
if (container) {
|
|
4840
5270
|
const badge = container.querySelector('.comment-count');
|
|
4841
5271
|
if (badge) {
|
|
@@ -4918,17 +5348,19 @@ function renderReviewPanel() {
|
|
|
4918
5348
|
return;
|
|
4919
5349
|
}
|
|
4920
5350
|
|
|
4921
|
-
container.innerHTML = filtered.map((c, i)
|
|
5351
|
+
container.innerHTML = filtered.map(function(c, i) {
|
|
4922
5352
|
const globalIndex = data.comments.indexOf(c);
|
|
4923
5353
|
const sectionLabel = getSectionLabel(c.sectionId);
|
|
4924
5354
|
const date = c.timestamp ? new Date(c.timestamp).toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit' }) : '';
|
|
5355
|
+
var elId = c.elementId || c.cardIndex;
|
|
5356
|
+
var safeElId = String(elId).replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
4925
5357
|
|
|
4926
5358
|
return `
|
|
4927
|
-
<div class="review-comment-item" onclick="navigateToComment('${c.sectionId}', ${
|
|
4928
|
-
<div class="review-comment-section">${sectionLabel}</div>
|
|
4929
|
-
<div class="review-comment-text">${c.content}</div>
|
|
5359
|
+
<div class="review-comment-item" onclick="navigateToComment('${c.sectionId}', '${safeElId}')">
|
|
5360
|
+
<div class="review-comment-section">${escapeHtml(sectionLabel)}</div>
|
|
5361
|
+
<div class="review-comment-text">${escapeHtml(c.content)}</div>
|
|
4930
5362
|
<div class="review-comment-footer">
|
|
4931
|
-
<span class="review-comment-author">${c.author || 'Utilisateur'} - ${date}</span>
|
|
5363
|
+
<span class="review-comment-author">${escapeHtml(c.author || 'Utilisateur')} - ${escapeHtml(date)}</span>
|
|
4932
5364
|
<div class="review-comment-actions">
|
|
4933
5365
|
<button class="review-action-btn ${c.status === 'validated' ? 'reject' : 'validate'}"
|
|
4934
5366
|
onclick="event.stopPropagation();toggleCommentStatus(${globalIndex})"
|
|
@@ -4984,29 +5416,29 @@ function getSectionLabel(sectionId) {
|
|
|
4984
5416
|
return sectionId;
|
|
4985
5417
|
}
|
|
4986
5418
|
|
|
4987
|
-
function navigateToComment(sectionId,
|
|
5419
|
+
function navigateToComment(sectionId, safeElId) {
|
|
4988
5420
|
// Handle list-based sectionIds (ucList-*, brList-*, entList-*) → navigate to module + tab
|
|
4989
5421
|
const listMatch = sectionId.match(/^(uc|br|ent)List-(.+)$/);
|
|
4990
5422
|
if (listMatch) {
|
|
4991
5423
|
const [, tabType, moduleCode] = listMatch;
|
|
4992
5424
|
showSection('module-spec-' + moduleCode);
|
|
4993
|
-
setTimeout(()
|
|
5425
|
+
setTimeout(function() {
|
|
4994
5426
|
switchTab(moduleCode, tabType);
|
|
4995
|
-
scrollToCommentThread(sectionId,
|
|
5427
|
+
scrollToCommentThread(sectionId, safeElId);
|
|
4996
5428
|
}, 150);
|
|
4997
5429
|
} else {
|
|
4998
5430
|
showSection(sectionId);
|
|
4999
|
-
scrollToCommentThread(sectionId,
|
|
5431
|
+
scrollToCommentThread(sectionId, safeElId);
|
|
5000
5432
|
}
|
|
5001
5433
|
}
|
|
5002
5434
|
|
|
5003
|
-
function scrollToCommentThread(sectionId,
|
|
5004
|
-
setTimeout(()
|
|
5005
|
-
const thread = document.getElementById('comment-thread-' + sectionId + '-' +
|
|
5435
|
+
function scrollToCommentThread(sectionId, safeElId) {
|
|
5436
|
+
setTimeout(function() {
|
|
5437
|
+
const thread = document.getElementById('comment-thread-' + sectionId + '-' + safeElId);
|
|
5006
5438
|
if (thread && !thread.classList.contains('visible')) {
|
|
5007
5439
|
thread.classList.add('visible');
|
|
5008
5440
|
}
|
|
5009
|
-
const container = document.querySelector(
|
|
5441
|
+
const container = document.querySelector('.comment-btn-container[data-section-id="' + sectionId + '"][data-element-id]');
|
|
5010
5442
|
if (container) {
|
|
5011
5443
|
container.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
5012
5444
|
}
|