@atlashub/smartstack-cli 4.41.0 → 4.43.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 +24 -9
- 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 -22
- 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 +15 -6
- 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 +7 -7
- package/templates/skills/{derive-prd → business-analyse-handoff}/steps/step-01-transform.md +50 -11
- package/templates/skills/{derive-prd → business-analyse-handoff}/steps/step-02-export.md +36 -16
- 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 +714 -286
- 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 +102 -12
- 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
|
============================================ */
|
|
@@ -2521,35 +2736,34 @@ function renderModuleNavItem(mod) {
|
|
|
2521
2736
|
var html = '<div class="nav-module" data-group-id="' + groupId + '">';
|
|
2522
2737
|
|
|
2523
2738
|
// Module header (clickable to expand + navigate to module spec)
|
|
2739
|
+
var totalItems = ucCount + brCount + entCount;
|
|
2524
2740
|
html += '<a class="nav-item nav-module-header" onclick="toggleNavGroup(\'' + groupId + '\');showSection(\'module-spec-' + code + '\')" data-section="module-spec-' + code + '">';
|
|
2525
2741
|
html += '<span class="nav-chevron ' + (collapsed ? '' : 'expanded') + '">▸</span> ';
|
|
2526
2742
|
html += (mod.name || mod.code);
|
|
2743
|
+
if (totalItems > 0) html += ' <span class="nav-badge">' + totalItems + '</span>';
|
|
2527
2744
|
html += '</a>';
|
|
2528
2745
|
|
|
2529
|
-
// Children:
|
|
2746
|
+
// Children: sections/resources (navigable sub-tree)
|
|
2747
|
+
// Tab-level items (UC, BR, Données, etc.) are NOT shown here — they are accessed
|
|
2748
|
+
// via the tab bar in the module content area to avoid redundancy.
|
|
2530
2749
|
html += '<div class="nav-children"' + (collapsed ? ' style="display:none;"' : '') + '>';
|
|
2531
|
-
html += renderModuleTabNavItem(code, 'uc', 'Cas d\'utilisation', ucCount);
|
|
2532
|
-
html += renderModuleTabNavItem(code, 'br', 'Règles métier', brCount);
|
|
2533
|
-
html += renderModuleTabNavItem(code, 'ent', 'Données', entCount);
|
|
2534
|
-
html += renderModuleTabNavItem(code, 'perm', 'Droits d\'accès');
|
|
2535
|
-
html += renderModuleTabNavItem(code, 'mock', 'Maquettes');
|
|
2536
|
-
html += renderModuleTabNavItem(code, 'struct', 'Structure', sections.length);
|
|
2537
|
-
|
|
2538
|
-
// Children: sections/resources (deeper tree)
|
|
2539
2750
|
if (sections.length > 0) {
|
|
2540
2751
|
sections.forEach(function(section) {
|
|
2541
2752
|
var resources = section.resources || [];
|
|
2753
|
+
var sectionUCs = (section.useCases || []).length;
|
|
2754
|
+
var sectionBRs = (section.businessRules || []).length;
|
|
2755
|
+
var contentCount = sectionUCs + sectionBRs + resources.length;
|
|
2542
2756
|
html += '<div class="nav-section-item">';
|
|
2543
2757
|
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 (
|
|
2758
|
+
html += '<span class="nav-icon nav-icon-section">▷</span> ' + escapeHtml(section.code || section.name || '');
|
|
2759
|
+
if (contentCount > 0) html += ' <span class="nav-badge">' + contentCount + '</span>';
|
|
2546
2760
|
html += '</a>';
|
|
2547
2761
|
if (resources.length > 0) {
|
|
2548
2762
|
html += '<div class="nav-children nav-resources">';
|
|
2549
2763
|
resources.forEach(function(res) {
|
|
2550
2764
|
var resName = typeof res === 'string' ? res : (res.code || res.name || '');
|
|
2551
2765
|
html += '<a class="nav-item nav-resource-link">';
|
|
2552
|
-
html += '<span class="nav-icon nav-icon-resource">•</span> ' + resName;
|
|
2766
|
+
html += '<span class="nav-icon nav-icon-resource">•</span> ' + escapeHtml(resName);
|
|
2553
2767
|
html += '</a>';
|
|
2554
2768
|
});
|
|
2555
2769
|
html += '</div>';
|
|
@@ -2615,6 +2829,97 @@ function highlightActiveNavItem() {
|
|
|
2615
2829
|
var navItem = document.querySelector('#sidebarNav [data-section="' + currentSectionId + '"]');
|
|
2616
2830
|
if (navItem) navItem.classList.add('active');
|
|
2617
2831
|
}
|
|
2832
|
+
|
|
2833
|
+
/* ---------- Mobile Sidebar ---------- */
|
|
2834
|
+
|
|
2835
|
+
function toggleMobileSidebar() {
|
|
2836
|
+
var sidebar = document.getElementById('sidebarAside');
|
|
2837
|
+
var overlay = document.getElementById('sidebarOverlay');
|
|
2838
|
+
if (!sidebar) return;
|
|
2839
|
+
var isOpen = sidebar.classList.contains('mobile-open');
|
|
2840
|
+
sidebar.classList.toggle('mobile-open', !isOpen);
|
|
2841
|
+
if (overlay) overlay.classList.toggle('visible', !isOpen);
|
|
2842
|
+
}
|
|
2843
|
+
|
|
2844
|
+
/* ---------- Search / Filter ---------- */
|
|
2845
|
+
|
|
2846
|
+
var _searchTimer;
|
|
2847
|
+
function filterNavItems(query) {
|
|
2848
|
+
clearTimeout(_searchTimer);
|
|
2849
|
+
_searchTimer = setTimeout(function() { doFilterNavItems(query); }, 200);
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2852
|
+
function doFilterNavItems(query) {
|
|
2853
|
+
var q = (query || '').toLowerCase().trim();
|
|
2854
|
+
var nav = document.getElementById('sidebarNav');
|
|
2855
|
+
if (!nav) return;
|
|
2856
|
+
|
|
2857
|
+
// Clear previous highlights
|
|
2858
|
+
nav.querySelectorAll('.search-highlight').forEach(function(el) {
|
|
2859
|
+
el.outerHTML = el.textContent;
|
|
2860
|
+
});
|
|
2861
|
+
|
|
2862
|
+
if (!q) {
|
|
2863
|
+
// Show all items
|
|
2864
|
+
nav.querySelectorAll('.nav-item, .nav-group, .nav-module, .nav-section-item').forEach(function(el) {
|
|
2865
|
+
el.classList.remove('search-hidden');
|
|
2866
|
+
});
|
|
2867
|
+
return;
|
|
2868
|
+
}
|
|
2869
|
+
|
|
2870
|
+
// Filter nav items
|
|
2871
|
+
var allItems = nav.querySelectorAll('.nav-item');
|
|
2872
|
+
var visibleSections = new Set();
|
|
2873
|
+
|
|
2874
|
+
allItems.forEach(function(item) {
|
|
2875
|
+
var text = item.textContent.toLowerCase();
|
|
2876
|
+
if (text.includes(q)) {
|
|
2877
|
+
item.classList.remove('search-hidden');
|
|
2878
|
+
// Show parent groups
|
|
2879
|
+
var parent = item.parentElement;
|
|
2880
|
+
while (parent && parent.id !== 'sidebarNav') {
|
|
2881
|
+
parent.classList.remove('search-hidden');
|
|
2882
|
+
if (parent.querySelector(':scope > .nav-children')) {
|
|
2883
|
+
var children = parent.querySelector(':scope > .nav-children');
|
|
2884
|
+
if (children) children.style.display = '';
|
|
2885
|
+
}
|
|
2886
|
+
parent = parent.parentElement;
|
|
2887
|
+
}
|
|
2888
|
+
// Track section for content search
|
|
2889
|
+
var sectionId = item.dataset.section;
|
|
2890
|
+
if (sectionId) visibleSections.add(sectionId);
|
|
2891
|
+
} else {
|
|
2892
|
+
item.classList.add('search-hidden');
|
|
2893
|
+
}
|
|
2894
|
+
});
|
|
2895
|
+
|
|
2896
|
+
// Hide groups that have no visible children
|
|
2897
|
+
nav.querySelectorAll('.nav-group, .nav-module').forEach(function(group) {
|
|
2898
|
+
var visibleChildren = group.querySelectorAll('.nav-item:not(.search-hidden)');
|
|
2899
|
+
if (visibleChildren.length === 0) {
|
|
2900
|
+
group.classList.add('search-hidden');
|
|
2901
|
+
} else {
|
|
2902
|
+
group.classList.remove('search-hidden');
|
|
2903
|
+
}
|
|
2904
|
+
});
|
|
2905
|
+
|
|
2906
|
+
// Also search in section content and show matching sections
|
|
2907
|
+
document.querySelectorAll('.section').forEach(function(section) {
|
|
2908
|
+
var text = section.textContent.toLowerCase();
|
|
2909
|
+
if (text.includes(q) && !visibleSections.has(section.id)) {
|
|
2910
|
+
// Unhide corresponding nav item
|
|
2911
|
+
var navItem = nav.querySelector('[data-section="' + section.id + '"]');
|
|
2912
|
+
if (navItem) {
|
|
2913
|
+
navItem.classList.remove('search-hidden');
|
|
2914
|
+
var parent = navItem.parentElement;
|
|
2915
|
+
while (parent && parent.id !== 'sidebarNav') {
|
|
2916
|
+
parent.classList.remove('search-hidden');
|
|
2917
|
+
parent = parent.parentElement;
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
}
|
|
2921
|
+
});
|
|
2922
|
+
}
|
|
2618
2923
|
|
|
2619
2924
|
|
|
2620
2925
|
/* --- 03-render-cadrage.js --- */
|
|
@@ -2662,23 +2967,24 @@ function renderStakeholders() {
|
|
|
2662
2967
|
grid.innerHTML = data.cadrage.stakeholders.map((s, i) => `
|
|
2663
2968
|
<div class="stakeholder-card">
|
|
2664
2969
|
<div style="display:flex;justify-content:space-between;align-items:start;">
|
|
2665
|
-
<div class="stakeholder-role">${s.role}</div>
|
|
2970
|
+
<div class="stakeholder-role">${escapeHtml(s.role)}</div>
|
|
2666
2971
|
<button class="btn btn-sm" onclick="removeStakeholder(${i})" style="opacity:0.5;font-size:0.7rem;">Supprimer</button>
|
|
2667
2972
|
</div>
|
|
2668
|
-
<div class="stakeholder-function">${s.function || ''}</div>
|
|
2973
|
+
<div class="stakeholder-function">${escapeHtml(s.function || '')}</div>
|
|
2669
2974
|
<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('')}
|
|
2975
|
+
${(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
2976
|
</ul>
|
|
2672
2977
|
<div class="stakeholder-meta">
|
|
2673
2978
|
<span>${formatFrequency(s.frequency)}</span>
|
|
2674
2979
|
<span>${formatAccess(s.access)}</span>
|
|
2675
2980
|
</div>
|
|
2676
|
-
${s.frustrations ? '<div style="font-size:0.8rem;color:var(--warning);margin-top:0.5rem;font-style:italic;">' + s.frustrations + '</div>' : ''}
|
|
2981
|
+
${s.frustrations ? '<div style="font-size:0.8rem;color:var(--warning);margin-top:0.5rem;font-style:italic;">' + escapeHtml(s.frustrations) + '</div>' : ''}
|
|
2677
2982
|
</div>
|
|
2678
2983
|
`).join('');
|
|
2679
2984
|
}
|
|
2680
2985
|
|
|
2681
2986
|
function removeStakeholder(index) {
|
|
2987
|
+
if (!confirm('Supprimer ce profil utilisateur ?')) return;
|
|
2682
2988
|
data.cadrage.stakeholders.splice(index, 1);
|
|
2683
2989
|
renderStakeholders();
|
|
2684
2990
|
updateCounts();
|
|
@@ -2709,12 +3015,12 @@ function renderScope() {
|
|
|
2709
3015
|
<div class="uc-item">
|
|
2710
3016
|
<div class="uc-header">
|
|
2711
3017
|
<span class="priority priority-${p}">${formatPriority(p)}</span>
|
|
2712
|
-
<span class="uc-title">${item.name}</span>
|
|
3018
|
+
<span class="uc-title">${escapeHtml(item.name)}</span>
|
|
2713
3019
|
<div class="uc-actions">
|
|
2714
3020
|
<button class="btn btn-sm" onclick="removeScopeItem('${p}',${i})">Supprimer</button>
|
|
2715
3021
|
</div>
|
|
2716
3022
|
</div>
|
|
2717
|
-
${item.description ? '<div class="uc-detail">' + item.description + '</div>' : ''}
|
|
3023
|
+
${item.description ? '<div class="uc-detail">' + escapeHtml(item.description) + '</div>' : ''}
|
|
2718
3024
|
</div>
|
|
2719
3025
|
`).join('');
|
|
2720
3026
|
});
|
|
@@ -2759,7 +3065,7 @@ function renderCriteria() {
|
|
|
2759
3065
|
container.innerHTML = criteria.map((c, i) => `
|
|
2760
3066
|
<div class="uc-item" style="display:flex;align-items:center;gap:0.75rem;">
|
|
2761
3067
|
<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>
|
|
3068
|
+
<span style="flex:1;${c.validated ? 'text-decoration:line-through;color:var(--text-muted);' : ''}">${escapeHtml(c.text)}</span>
|
|
2763
3069
|
<button class="btn btn-sm" onclick="removeCriterion(${i})" style="opacity:0.5;flex-shrink:0;">✕</button>
|
|
2764
3070
|
</div>
|
|
2765
3071
|
`).join('');
|
|
@@ -2839,10 +3145,10 @@ function renderModules() {
|
|
|
2839
3145
|
<div class="module-card" onclick="showSection('module-spec-${m.code}')">
|
|
2840
3146
|
<button class="module-card-remove" onclick="event.stopPropagation();removeModule(${i})">✕</button>
|
|
2841
3147
|
<div class="module-card-header">
|
|
2842
|
-
<span class="module-card-code">${m.name}</span>
|
|
3148
|
+
<span class="module-card-code">${escapeHtml(m.name)}</span>
|
|
2843
3149
|
<span class="module-card-type">${formatModuleType(m.featureType)}</span>
|
|
2844
3150
|
</div>
|
|
2845
|
-
<div class="module-card-desc">${m.description || ''}</div>
|
|
3151
|
+
<div class="module-card-desc">${escapeHtml(m.description || '')}</div>
|
|
2846
3152
|
<div class="module-card-meta">
|
|
2847
3153
|
<span>${(m.entities || []).length} données</span>
|
|
2848
3154
|
<span>${(data.moduleSpecs[m.code]?.useCases || []).length} cas d'utilisation</span>
|
|
@@ -2899,10 +3205,10 @@ function renderDependencies() {
|
|
|
2899
3205
|
const toName = data.modules.find(m => m.code === d.to)?.name || d.to;
|
|
2900
3206
|
return `
|
|
2901
3207
|
<div class="interaction-item">
|
|
2902
|
-
<span style="font-weight:600;color:var(--text-bright);">${fromName}</span>
|
|
3208
|
+
<span style="font-weight:600;color:var(--text-bright);">${escapeHtml(fromName)}</span>
|
|
2903
3209
|
<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>
|
|
3210
|
+
<span style="font-weight:600;color:var(--text-bright);">${escapeHtml(toName)}</span>
|
|
3211
|
+
<span style="flex:1;font-size:0.8rem;color:var(--text-muted);">${escapeHtml(d.description || '')}</span>
|
|
2906
3212
|
<button class="btn btn-sm" onclick="removeDependency(${i})" style="opacity:0.5;">Supprimer</button>
|
|
2907
3213
|
</div>
|
|
2908
3214
|
`;
|
|
@@ -2931,7 +3237,7 @@ function renderDepGraph() {
|
|
|
2931
3237
|
<div class="dep-layer-modules">
|
|
2932
3238
|
${layer.map(code => {
|
|
2933
3239
|
const m = data.modules.find(mod => mod.code === code);
|
|
2934
|
-
return `<div class="dep-module">${m ? (m.name || m.code) : code}</div>`;
|
|
3240
|
+
return `<div class="dep-module">${escapeHtml(m ? (m.name || m.code) : code)}</div>`;
|
|
2935
3241
|
}).join('')}
|
|
2936
3242
|
</div>
|
|
2937
3243
|
</div>
|
|
@@ -2979,7 +3285,7 @@ function renderProcessingOrder() {
|
|
|
2979
3285
|
return `
|
|
2980
3286
|
<div class="process-step">
|
|
2981
3287
|
<div class="process-step-number">Étape ${i + 1}</div>
|
|
2982
|
-
<div class="process-step-label">${m ? (m.name || m.code) : code}</div>
|
|
3288
|
+
<div class="process-step-label">${escapeHtml(m ? (m.name || m.code) : code)}</div>
|
|
2983
3289
|
</div>
|
|
2984
3290
|
${i < order.length - 1 ? '<div class="process-arrow">→</div>' : ''}
|
|
2985
3291
|
`;
|
|
@@ -3017,23 +3323,24 @@ function renderModuleSpecSection(mod) {
|
|
|
3017
3323
|
|
|
3018
3324
|
return `
|
|
3019
3325
|
<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>
|
|
3326
|
+
<h2 class="section-title">${escapeHtml(mod.name)}</h2>
|
|
3327
|
+
<p class="section-subtitle">${escapeHtml(mod.description || 'Spécification détaillée de ce domaine fonctionnel.')}</p>
|
|
3328
|
+
|
|
3329
|
+
<div class="tab-bar" role="tablist" aria-label="Spécifications du module ${escapeHtml(mod.name)}">
|
|
3330
|
+
<button class="tab-btn active" role="tab" aria-selected="true" aria-controls="tab-${code}-uc" onclick="switchTab('${code}', 'uc')">Cas d'utilisation</button>
|
|
3331
|
+
<button class="tab-btn" role="tab" aria-selected="false" aria-controls="tab-${code}-br" onclick="switchTab('${code}', 'br')">Règles métier</button>
|
|
3332
|
+
<button class="tab-btn" role="tab" aria-selected="false" aria-controls="tab-${code}-ent" onclick="switchTab('${code}', 'ent')">Données</button>
|
|
3333
|
+
<button class="tab-btn" role="tab" aria-selected="false" aria-controls="tab-${code}-perm" onclick="switchTab('${code}', 'perm')">Droits d'accès</button>
|
|
3334
|
+
<button class="tab-btn" role="tab" aria-selected="false" aria-controls="tab-${code}-mock" onclick="switchTab('${code}', 'mock')">Maquettes</button>
|
|
3335
|
+
<button class="tab-btn" role="tab" aria-selected="false" aria-controls="tab-${code}-notes" onclick="switchTab('${code}', 'notes')">Notes</button>
|
|
3336
|
+
<button class="tab-btn" role="tab" aria-selected="false" aria-controls="tab-${code}-struct" onclick="switchTab('${code}', 'struct')">Structure</button>
|
|
3031
3337
|
</div>
|
|
3032
3338
|
|
|
3033
3339
|
<!-- TAB: Cas d'utilisation -->
|
|
3034
|
-
<div class="tab-panel active" id="tab-${code}-uc">
|
|
3340
|
+
<div class="tab-panel active" id="tab-${code}-uc" role="tabpanel" aria-hidden="false">
|
|
3035
3341
|
<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
3342
|
<div id="ucList-${code}" class="uc-list">
|
|
3343
|
+
${hasHierarchicalSpecs(mod) ? renderSectionGroupedItems(mod, 'useCases', code, renderUseCase) : ''}
|
|
3037
3344
|
${spec.useCases.map((uc, i) => renderUseCase(code, uc, i)).join('')}
|
|
3038
3345
|
</div>
|
|
3039
3346
|
<button class="add-btn" onclick="toggleForm('addUcForm-${code}')">+ Ajouter un cas d'utilisation</button>
|
|
@@ -3063,9 +3370,10 @@ function renderModuleSpecSection(mod) {
|
|
|
3063
3370
|
</div>
|
|
3064
3371
|
|
|
3065
3372
|
<!-- TAB: Règles métier -->
|
|
3066
|
-
<div class="tab-panel" id="tab-${code}-br">
|
|
3373
|
+
<div class="tab-panel" id="tab-${code}-br" role="tabpanel" aria-hidden="true">
|
|
3067
3374
|
<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
3375
|
<div id="brList-${code}">
|
|
3376
|
+
${hasHierarchicalSpecs(mod) ? renderSectionGroupedItems(mod, 'businessRules', code, renderBusinessRule) : ''}
|
|
3069
3377
|
${spec.businessRules.map((br, i) => renderBusinessRule(code, br, i)).join('')}
|
|
3070
3378
|
</div>
|
|
3071
3379
|
<button class="add-btn" onclick="toggleForm('addBrForm-${code}')">+ Ajouter une règle métier</button>
|
|
@@ -3101,7 +3409,7 @@ function renderModuleSpecSection(mod) {
|
|
|
3101
3409
|
</div>
|
|
3102
3410
|
|
|
3103
3411
|
<!-- TAB: Données (Entités) -->
|
|
3104
|
-
<div class="tab-panel" id="tab-${code}-ent">
|
|
3412
|
+
<div class="tab-panel" id="tab-${code}-ent" role="tabpanel" aria-hidden="true">
|
|
3105
3413
|
<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
3414
|
<div id="entList-${code}">
|
|
3107
3415
|
${spec.entities.length > 0
|
|
@@ -3138,7 +3446,7 @@ function renderModuleSpecSection(mod) {
|
|
|
3138
3446
|
</div>
|
|
3139
3447
|
|
|
3140
3448
|
<!-- TAB: Droits d'accès -->
|
|
3141
|
-
<div class="tab-panel" id="tab-${code}-perm">
|
|
3449
|
+
<div class="tab-panel" id="tab-${code}-perm" role="tabpanel" aria-hidden="true">
|
|
3142
3450
|
<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
3451
|
<div id="permGrid-${code}">
|
|
3144
3452
|
${renderPermissionGrid(code)}
|
|
@@ -3172,7 +3480,7 @@ function renderModuleSpecSection(mod) {
|
|
|
3172
3480
|
</div>
|
|
3173
3481
|
|
|
3174
3482
|
<!-- TAB: Maquettes -->
|
|
3175
|
-
<div class="tab-panel" id="tab-${code}-mock">
|
|
3483
|
+
<div class="tab-panel" id="tab-${code}-mock" role="tabpanel" aria-hidden="true">
|
|
3176
3484
|
<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
3485
|
<div id="mockupContainer-${code}">
|
|
3178
3486
|
${renderModuleMockups(code)}
|
|
@@ -3180,7 +3488,7 @@ function renderModuleSpecSection(mod) {
|
|
|
3180
3488
|
</div>
|
|
3181
3489
|
|
|
3182
3490
|
<!-- TAB: Notes -->
|
|
3183
|
-
<div class="tab-panel" id="tab-${code}-notes">
|
|
3491
|
+
<div class="tab-panel" id="tab-${code}-notes" role="tabpanel" aria-hidden="true">
|
|
3184
3492
|
<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
3493
|
<div class="card">
|
|
3186
3494
|
<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 +3496,7 @@ function renderModuleSpecSection(mod) {
|
|
|
3188
3496
|
</div>
|
|
3189
3497
|
|
|
3190
3498
|
<!-- TAB: Structure (sections/resources) -->
|
|
3191
|
-
<div class="tab-panel" id="tab-${code}-struct">
|
|
3499
|
+
<div class="tab-panel" id="tab-${code}-struct" role="tabpanel" aria-hidden="true">
|
|
3192
3500
|
<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
3501
|
<div id="structContainer-${code}">
|
|
3194
3502
|
${renderModuleStructure(code)}
|
|
@@ -3202,19 +3510,19 @@ function renderUseCase(code, uc, index) {
|
|
|
3202
3510
|
<div class="uc-item">
|
|
3203
3511
|
<div class="uc-header">
|
|
3204
3512
|
<span class="uc-id">UC-${String(index + 1).padStart(3, '0')}</span>
|
|
3205
|
-
<span class="uc-title">${uc.name}</span>
|
|
3513
|
+
<span class="uc-title">${escapeHtml(uc.name)}</span>
|
|
3206
3514
|
<div class="uc-actions">
|
|
3207
3515
|
<button class="btn btn-sm" onclick="removeUseCase('${code}',${index})">Supprimer</button>
|
|
3208
3516
|
</div>
|
|
3209
3517
|
</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>` : ''}
|
|
3518
|
+
<div class="uc-actors"><div class="uc-actor">${escapeHtml(uc.actor)}</div></div>
|
|
3519
|
+
${uc.steps ? `<div class="uc-detail-label">Déroulement</div><div class="uc-detail">${escapeHtml(uc.steps).replace(/\n/g, '<br>')}</div>` : ''}
|
|
3520
|
+
${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
3521
|
<div style="padding:0.5rem 0.75rem;border-top:1px solid var(--border);background:var(--bg-input);border-radius:0 0 8px 8px;">
|
|
3214
3522
|
<label style="font-size:0.75rem;color:var(--text-muted);display:block;margin-bottom:0.25rem;">Commentaire / Feedback :</label>
|
|
3215
3523
|
<textarea class="form-textarea" placeholder="Ajouter un commentaire sur ce cas d'utilisation..."
|
|
3216
3524
|
onblur="updateSpecComment('${code}','uc',${index},this.value)"
|
|
3217
|
-
style="min-height:45px;font-size:0.8rem;resize:vertical;">${getSpecComment(code, 'uc', index)}</textarea>
|
|
3525
|
+
style="min-height:45px;font-size:0.8rem;resize:vertical;">${escapeHtml(getSpecComment(code, 'uc', index))}</textarea>
|
|
3218
3526
|
</div>
|
|
3219
3527
|
</div>`;
|
|
3220
3528
|
}
|
|
@@ -3237,6 +3545,7 @@ function addUseCase(code) {
|
|
|
3237
3545
|
}
|
|
3238
3546
|
|
|
3239
3547
|
function removeUseCase(code, index) {
|
|
3548
|
+
if (!confirm('Supprimer ce cas d\'utilisation ?')) return;
|
|
3240
3549
|
data.moduleSpecs[code].useCases.splice(index, 1);
|
|
3241
3550
|
renderAllModuleSpecs();
|
|
3242
3551
|
updateCounts();
|
|
@@ -3249,18 +3558,18 @@ function renderBusinessRule(code, br, index) {
|
|
|
3249
3558
|
return `
|
|
3250
3559
|
<div style="margin-bottom:0.5rem;">
|
|
3251
3560
|
<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>
|
|
3561
|
+
<span class="br-category ${catColors[br.category] || 'br-cat-validation'}">${escapeHtml(catLabels[br.category] || br.category)}</span>
|
|
3253
3562
|
<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>` : ''}
|
|
3563
|
+
<div style="font-weight:600;color:var(--text-bright);margin-bottom:0.2rem;">${escapeHtml(br.name)}</div>
|
|
3564
|
+
<div>${escapeHtml(br.statement)}</div>
|
|
3565
|
+
${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
3566
|
</div>
|
|
3258
3567
|
<button class="btn btn-sm" onclick="removeBusinessRule('${code}',${index})" style="opacity:0.5;flex-shrink:0;">✕</button>
|
|
3259
3568
|
</div>
|
|
3260
3569
|
<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
3570
|
<textarea class="form-textarea" placeholder="Commentaire sur cette règle..."
|
|
3262
3571
|
onblur="updateSpecComment('${code}','br',${index},this.value)"
|
|
3263
|
-
style="min-height:35px;font-size:0.8rem;resize:vertical;">${getSpecComment(code, 'br', index)}</textarea>
|
|
3572
|
+
style="min-height:35px;font-size:0.8rem;resize:vertical;">${escapeHtml(getSpecComment(code, 'br', index))}</textarea>
|
|
3264
3573
|
</div>
|
|
3265
3574
|
</div>`;
|
|
3266
3575
|
}
|
|
@@ -3282,18 +3591,20 @@ function addBusinessRule(code) {
|
|
|
3282
3591
|
}
|
|
3283
3592
|
|
|
3284
3593
|
function removeBusinessRule(code, index) {
|
|
3594
|
+
if (!confirm('Supprimer cette règle métier ?')) return;
|
|
3285
3595
|
data.moduleSpecs[code].businessRules.splice(index, 1);
|
|
3286
3596
|
renderAllModuleSpecs();
|
|
3287
3597
|
autoSave();
|
|
3288
3598
|
}
|
|
3289
3599
|
|
|
3290
3600
|
function renderEntity(code, ent, index) {
|
|
3601
|
+
var attrFormId = 'addAttrForm-' + code + '-' + index;
|
|
3291
3602
|
return `
|
|
3292
3603
|
<div class="entity-block">
|
|
3293
3604
|
<div class="entity-header">
|
|
3294
3605
|
<div>
|
|
3295
|
-
<div class="entity-name">${ent.name}</div>
|
|
3296
|
-
<div class="entity-desc">${ent.description || ''}</div>
|
|
3606
|
+
<div class="entity-name">${escapeHtml(ent.name)}</div>
|
|
3607
|
+
<div class="entity-desc">${escapeHtml(ent.description || '')}</div>
|
|
3297
3608
|
</div>
|
|
3298
3609
|
<button class="btn btn-sm" onclick="removeEntity('${code}',${index})" style="opacity:0.5;">Supprimer</button>
|
|
3299
3610
|
</div>
|
|
@@ -3301,21 +3612,26 @@ function renderEntity(code, ent, index) {
|
|
|
3301
3612
|
<table class="attr-table">
|
|
3302
3613
|
<thead><tr><th>Information</th><th>Description</th></tr></thead>
|
|
3303
3614
|
<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('')}
|
|
3615
|
+
${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
3616
|
</tbody>
|
|
3306
|
-
</table
|
|
3617
|
+
</table>` : ''}
|
|
3307
3618
|
<div style="padding:0.3rem 0.75rem;">
|
|
3308
|
-
<button class="add-btn" style="font-size:0.75rem;padding:0.4rem;" onclick="
|
|
3309
|
-
|
|
3619
|
+
<button class="add-btn" style="font-size:0.75rem;padding:0.4rem;" onclick="toggleForm('${attrFormId}')">+ Ajouter un attribut</button>
|
|
3620
|
+
<div class="inline-form" id="${attrFormId}">
|
|
3621
|
+
<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>
|
|
3622
|
+
<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>
|
|
3623
|
+
<div class="form-actions"><button class="btn" onclick="toggleForm('${attrFormId}')">Annuler</button><button class="btn btn-primary" onclick="addEntityAttribute('${code}',${index})">Ajouter</button></div>
|
|
3624
|
+
</div>
|
|
3625
|
+
</div>
|
|
3310
3626
|
${(ent.relationships || []).length > 0 ? `
|
|
3311
3627
|
<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(', ')}
|
|
3628
|
+
Relations : ${ent.relationships.map(r => `<span style="color:var(--accent);">${escapeHtml(typeof r === 'string' ? r : (r.target || ''))}</span>`).join(', ')}
|
|
3313
3629
|
</div>` : ''}
|
|
3314
3630
|
<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
3631
|
<label style="font-size:0.75rem;color:var(--text-muted);display:block;margin-bottom:0.25rem;">Commentaire :</label>
|
|
3316
3632
|
<textarea class="form-textarea" placeholder="Commentaire sur cette entité..."
|
|
3317
3633
|
onblur="updateSpecComment('${code}','ent',${index},this.value)"
|
|
3318
|
-
style="min-height:35px;font-size:0.8rem;resize:vertical;">${getSpecComment(code, 'ent', index)}</textarea>
|
|
3634
|
+
style="min-height:35px;font-size:0.8rem;resize:vertical;">${escapeHtml(getSpecComment(code, 'ent', index))}</textarea>
|
|
3319
3635
|
</div>
|
|
3320
3636
|
</div>`;
|
|
3321
3637
|
}
|
|
@@ -3343,20 +3659,21 @@ function addEntity(code) {
|
|
|
3343
3659
|
}
|
|
3344
3660
|
|
|
3345
3661
|
function removeEntity(code, index) {
|
|
3662
|
+
if (!confirm('Supprimer ce type de données ?')) return;
|
|
3346
3663
|
data.moduleSpecs[code].entities.splice(index, 1);
|
|
3347
3664
|
renderAllModuleSpecs();
|
|
3348
3665
|
autoSave();
|
|
3349
3666
|
}
|
|
3350
3667
|
|
|
3351
3668
|
function addEntityAttribute(code, entityIndex) {
|
|
3352
|
-
var attrName =
|
|
3353
|
-
|
|
3354
|
-
|
|
3669
|
+
var attrName = document.getElementById('attr-name-' + code + '-' + entityIndex);
|
|
3670
|
+
var attrDesc = document.getElementById('attr-desc-' + code + '-' + entityIndex);
|
|
3671
|
+
if (!attrName || !attrName.value.trim()) return;
|
|
3355
3672
|
if (!data.moduleSpecs[code]?.entities?.[entityIndex]) return;
|
|
3356
3673
|
if (!data.moduleSpecs[code].entities[entityIndex].attributes) {
|
|
3357
3674
|
data.moduleSpecs[code].entities[entityIndex].attributes = [];
|
|
3358
3675
|
}
|
|
3359
|
-
data.moduleSpecs[code].entities[entityIndex].attributes.push({ name: attrName, description: attrDesc });
|
|
3676
|
+
data.moduleSpecs[code].entities[entityIndex].attributes.push({ name: attrName.value.trim(), description: (attrDesc?.value || '').trim() });
|
|
3360
3677
|
renderAllModuleSpecs();
|
|
3361
3678
|
autoSave();
|
|
3362
3679
|
}
|
|
@@ -3382,37 +3699,6 @@ function updateWireframeComment(code, screen, value) {
|
|
|
3382
3699
|
autoSave();
|
|
3383
3700
|
}
|
|
3384
3701
|
|
|
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
3702
|
function renderWireframeMockup(code, wf, i) {
|
|
3417
3703
|
const hasSvg = !!wf.svgContent;
|
|
3418
3704
|
const wireframeId = `wf-${code}-${i}`;
|
|
@@ -3423,7 +3709,7 @@ function renderWireframeMockup(code, wf, i) {
|
|
|
3423
3709
|
<div class="mockup-dot mockup-dot-red"></div>
|
|
3424
3710
|
<div class="mockup-dot mockup-dot-yellow"></div>
|
|
3425
3711
|
<div class="mockup-dot mockup-dot-green"></div>
|
|
3426
|
-
<span class="mockup-title">${wf.screen || wf.section}</span>
|
|
3712
|
+
<span class="mockup-title">${escapeHtml(wf.screen || wf.section)}</span>
|
|
3427
3713
|
${hasSvg ? `
|
|
3428
3714
|
<div class="wireframe-toggle">
|
|
3429
3715
|
<button class="wireframe-toggle-btn active" data-target="${wireframeId}" data-view="svg" onclick="toggleWireframeView('${wireframeId}', 'svg')">SVG</button>
|
|
@@ -3440,11 +3726,11 @@ function renderWireframeMockup(code, wf, i) {
|
|
|
3440
3726
|
</div>
|
|
3441
3727
|
${wf.description ? `
|
|
3442
3728
|
<div class="wireframe-description">
|
|
3443
|
-
<strong>Description:</strong> ${wf.description}
|
|
3729
|
+
<strong>Description:</strong> ${escapeHtml(wf.description)}
|
|
3444
3730
|
</div>` : ''}
|
|
3445
3731
|
${(wf.elements || []).length > 0 ? `
|
|
3446
3732
|
<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>
|
|
3733
|
+
<div><strong>Elements:</strong> ${wf.elements.map(e => escapeHtml(typeof e === 'string' ? e : (e.type || e.label || e.id || ''))).filter(Boolean).join(', ')}</div>
|
|
3448
3734
|
</div>` : ''}
|
|
3449
3735
|
${(() => {
|
|
3450
3736
|
const cm = wf.componentMapping;
|
|
@@ -3458,7 +3744,7 @@ function renderWireframeMockup(code, wf, i) {
|
|
|
3458
3744
|
<thead><tr><th>Élément maquette</th><th>Composant React</th></tr></thead>
|
|
3459
3745
|
<tbody>
|
|
3460
3746
|
${mappings.map(m =>
|
|
3461
|
-
'<tr><td>' + (m.wireframeElement || '') + '</td><td><code>' + (m.reactComponent || '') + '</code></td></tr>'
|
|
3747
|
+
'<tr><td>' + escapeHtml(m.wireframeElement || '') + '</td><td><code>' + escapeHtml(m.reactComponent || '') + '</code></td></tr>'
|
|
3462
3748
|
).join('')}
|
|
3463
3749
|
</tbody>
|
|
3464
3750
|
</table>
|
|
@@ -3533,12 +3819,12 @@ function renderPermissionGrid(code) {
|
|
|
3533
3819
|
<table class="mock-table" style="background:var(--bg-card);border-radius:8px;overflow:hidden;">
|
|
3534
3820
|
<thead><tr>
|
|
3535
3821
|
<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('')}
|
|
3822
|
+
${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
3823
|
</tr></thead>
|
|
3538
3824
|
<tbody>
|
|
3539
3825
|
${roles.map((role, ri) => `
|
|
3540
3826
|
<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>
|
|
3827
|
+
<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
3828
|
${actions.map(action => {
|
|
3543
3829
|
const key = role + '|' + action;
|
|
3544
3830
|
const wildcardKey = role + '|*';
|
|
@@ -3632,24 +3918,52 @@ function renderModuleStructure(code) {
|
|
|
3632
3918
|
|
|
3633
3919
|
return sections.map(function(section) {
|
|
3634
3920
|
var resources = section.resources || [];
|
|
3921
|
+
var sectionUCs = section.useCases || [];
|
|
3922
|
+
var sectionBRs = section.businessRules || [];
|
|
3635
3923
|
var html = '<div class="struct-section">';
|
|
3636
3924
|
html += '<div class="struct-section-header">';
|
|
3637
|
-
html += '<span class="struct-section-code">' + (section.code || section.name || '') + '</span>';
|
|
3925
|
+
html += '<span class="struct-section-code">' + escapeHtml(section.code || section.name || '') + '</span>';
|
|
3638
3926
|
if (section.description) {
|
|
3639
|
-
html += '<span class="struct-section-desc">' + section.description + '</span>';
|
|
3927
|
+
html += '<span class="struct-section-desc">' + escapeHtml(section.description) + '</span>';
|
|
3640
3928
|
}
|
|
3641
3929
|
html += '<span class="struct-section-badge">' + resources.length + ' ressource' + (resources.length !== 1 ? 's' : '') + '</span>';
|
|
3642
3930
|
html += '</div>';
|
|
3643
3931
|
|
|
3932
|
+
// Section metadata (route, permission)
|
|
3933
|
+
if (section.route || section.permission) {
|
|
3934
|
+
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);">';
|
|
3935
|
+
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>';
|
|
3936
|
+
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>';
|
|
3937
|
+
html += '</div>';
|
|
3938
|
+
}
|
|
3939
|
+
|
|
3940
|
+
// Section-level UC summary
|
|
3941
|
+
if (sectionUCs.length > 0) {
|
|
3942
|
+
html += '<div style="padding:0.4rem 1rem;font-size:0.8rem;border-bottom:1px solid var(--border);">';
|
|
3943
|
+
html += '<span style="color:var(--text-muted);font-weight:500;">' + sectionUCs.length + ' cas d\'utilisation :</span> ';
|
|
3944
|
+
html += sectionUCs.map(function(uc) { return '<span style="color:var(--text-bright);">' + escapeHtml(uc.name || '') + '</span>'; }).join(', ');
|
|
3945
|
+
html += '</div>';
|
|
3946
|
+
}
|
|
3947
|
+
|
|
3948
|
+
// Section-level BR summary
|
|
3949
|
+
if (sectionBRs.length > 0) {
|
|
3950
|
+
html += '<div style="padding:0.4rem 1rem;font-size:0.8rem;border-bottom:1px solid var(--border);">';
|
|
3951
|
+
html += '<span style="color:var(--text-muted);font-weight:500;">' + sectionBRs.length + ' règle' + (sectionBRs.length > 1 ? 's' : '') + ' métier :</span> ';
|
|
3952
|
+
html += sectionBRs.map(function(br) { return '<span style="color:var(--text-bright);">' + escapeHtml(br.name || '') + '</span>'; }).join(', ');
|
|
3953
|
+
html += '</div>';
|
|
3954
|
+
}
|
|
3955
|
+
|
|
3644
3956
|
if (resources.length > 0) {
|
|
3645
3957
|
html += '<div class="struct-resources">';
|
|
3646
3958
|
resources.forEach(function(res) {
|
|
3647
3959
|
var resName = typeof res === 'string' ? res : (res.code || res.name || '');
|
|
3960
|
+
var resType = typeof res === 'object' ? (res.type || '') : '';
|
|
3648
3961
|
var resDesc = typeof res === 'object' ? (res.description || '') : '';
|
|
3649
3962
|
html += '<div class="struct-resource">';
|
|
3650
3963
|
html += '<span class="struct-resource-icon">•</span>';
|
|
3651
|
-
html += '<span class="struct-resource-name">' + resName + '</span>';
|
|
3652
|
-
if (
|
|
3964
|
+
html += '<span class="struct-resource-name">' + escapeHtml(resName) + '</span>';
|
|
3965
|
+
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>';
|
|
3966
|
+
if (resDesc) html += '<span class="struct-resource-desc">' + escapeHtml(resDesc) + '</span>';
|
|
3653
3967
|
html += '</div>';
|
|
3654
3968
|
});
|
|
3655
3969
|
html += '</div>';
|
|
@@ -3660,16 +3974,111 @@ function renderModuleStructure(code) {
|
|
|
3660
3974
|
}).join('');
|
|
3661
3975
|
}
|
|
3662
3976
|
|
|
3977
|
+
/* ---------- Section-Grouped Rendering (Hierarchical Mode) ---------- */
|
|
3978
|
+
|
|
3979
|
+
function renderSectionGroupedItems(mod, itemsKey, code, renderFn) {
|
|
3980
|
+
var sections = mod.anticipatedSections || [];
|
|
3981
|
+
var html = '';
|
|
3982
|
+
sections.forEach(function(section) {
|
|
3983
|
+
var items = section[itemsKey] || [];
|
|
3984
|
+
if (items.length === 0) return;
|
|
3985
|
+
html += '<div class="section-group">';
|
|
3986
|
+
html += '<div class="section-group-header">';
|
|
3987
|
+
html += '<span class="section-group-icon">▷</span> ';
|
|
3988
|
+
html += '<span class="section-group-label">' + escapeHtml(section.code || section.name || '') + '</span>';
|
|
3989
|
+
if (section.route) html += '<span class="section-group-route">' + escapeHtml(section.route) + '</span>';
|
|
3990
|
+
html += '<span class="nav-badge">' + items.length + '</span>';
|
|
3991
|
+
html += '</div>';
|
|
3992
|
+
html += items.map(function(item, i) { return renderFn(code, item, i); }).join('');
|
|
3993
|
+
html += '</div>';
|
|
3994
|
+
});
|
|
3995
|
+
return html;
|
|
3996
|
+
}
|
|
3997
|
+
|
|
3998
|
+
function renderModuleMockups(code) {
|
|
3999
|
+
var spec = data.moduleSpecs[code] || {};
|
|
4000
|
+
var screens = spec.screens || [];
|
|
4001
|
+
var wireframes = EMBEDDED_ARTIFACTS?.wireframes?.[code] || [];
|
|
4002
|
+
var mod = data.modules.find(function(m) { return m.code === code; });
|
|
4003
|
+
var sections = mod ? (mod.anticipatedSections || []) : [];
|
|
4004
|
+
|
|
4005
|
+
// Priority 1: HTML mockups from screens[] specs
|
|
4006
|
+
if (screens.length > 0) {
|
|
4007
|
+
var html = '';
|
|
4008
|
+
if (typeof renderScreenMockups === 'function') {
|
|
4009
|
+
html = renderScreenMockups(code);
|
|
4010
|
+
}
|
|
4011
|
+
if (wireframes.length > 0) {
|
|
4012
|
+
html += '<h3 style="color:var(--text-bright);font-size:1rem;margin:2rem 0 1rem;">Wireframes</h3>';
|
|
4013
|
+
html += wireframes.map(function(wf, i) { return renderWireframeMockup(code, wf, i); }).join('');
|
|
4014
|
+
}
|
|
4015
|
+
return html;
|
|
4016
|
+
}
|
|
4017
|
+
|
|
4018
|
+
// Priority 2: Wireframes grouped by section (if sections exist)
|
|
4019
|
+
if (wireframes.length > 0 && sections.length > 1) {
|
|
4020
|
+
var grouped = {};
|
|
4021
|
+
var ungrouped = [];
|
|
4022
|
+
wireframes.forEach(function(wf, i) {
|
|
4023
|
+
var sectionCode = wf.section || wf.sectionCode || '';
|
|
4024
|
+
var matchingSection = sections.find(function(s) { return s.code === sectionCode; });
|
|
4025
|
+
if (matchingSection) {
|
|
4026
|
+
if (!grouped[sectionCode]) grouped[sectionCode] = { section: matchingSection, items: [] };
|
|
4027
|
+
grouped[sectionCode].items.push({ wf: wf, index: i });
|
|
4028
|
+
} else {
|
|
4029
|
+
ungrouped.push({ wf: wf, index: i });
|
|
4030
|
+
}
|
|
4031
|
+
});
|
|
4032
|
+
|
|
4033
|
+
var html = '';
|
|
4034
|
+
Object.keys(grouped).forEach(function(sectionCode) {
|
|
4035
|
+
var group = grouped[sectionCode];
|
|
4036
|
+
html += '<div class="section-group">';
|
|
4037
|
+
html += '<div class="section-group-header">';
|
|
4038
|
+
html += '<span class="section-group-icon">▷</span> ';
|
|
4039
|
+
html += '<span class="section-group-label">' + escapeHtml(group.section.code || '') + '</span>';
|
|
4040
|
+
html += '<span class="nav-badge">' + group.items.length + '</span>';
|
|
4041
|
+
html += '</div>';
|
|
4042
|
+
group.items.forEach(function(item) {
|
|
4043
|
+
html += renderWireframeMockup(code, item.wf, item.index);
|
|
4044
|
+
});
|
|
4045
|
+
html += '</div>';
|
|
4046
|
+
});
|
|
4047
|
+
ungrouped.forEach(function(item) {
|
|
4048
|
+
html += renderWireframeMockup(code, item.wf, item.index);
|
|
4049
|
+
});
|
|
4050
|
+
return html;
|
|
4051
|
+
}
|
|
4052
|
+
|
|
4053
|
+
// Priority 3: Flat wireframe list
|
|
4054
|
+
if (wireframes.length === 0) {
|
|
4055
|
+
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>';
|
|
4056
|
+
}
|
|
4057
|
+
return wireframes.map(function(wf, i) { return renderWireframeMockup(code, wf, i); }).join('');
|
|
4058
|
+
}
|
|
4059
|
+
|
|
3663
4060
|
function switchTab(code, tabId) {
|
|
3664
4061
|
const section = document.getElementById('module-spec-' + code);
|
|
3665
4062
|
if (!section) return;
|
|
3666
|
-
section.querySelectorAll('.tab-btn').forEach(btn
|
|
3667
|
-
|
|
4063
|
+
section.querySelectorAll('.tab-btn').forEach(function(btn) {
|
|
4064
|
+
btn.classList.remove('active');
|
|
4065
|
+
btn.setAttribute('aria-selected', 'false');
|
|
4066
|
+
});
|
|
4067
|
+
section.querySelectorAll('.tab-panel').forEach(function(panel) {
|
|
4068
|
+
panel.classList.remove('active');
|
|
4069
|
+
panel.setAttribute('aria-hidden', 'true');
|
|
4070
|
+
});
|
|
3668
4071
|
const targetPanel = document.getElementById('tab-' + code + '-' + tabId);
|
|
3669
|
-
if (targetPanel)
|
|
4072
|
+
if (targetPanel) {
|
|
4073
|
+
targetPanel.classList.add('active');
|
|
4074
|
+
targetPanel.setAttribute('aria-hidden', 'false');
|
|
4075
|
+
}
|
|
3670
4076
|
const buttons = section.querySelectorAll('.tab-btn');
|
|
3671
4077
|
const tabIndex = { uc: 0, br: 1, ent: 2, perm: 3, mock: 4, notes: 5, struct: 6 }[tabId];
|
|
3672
|
-
if (buttons[tabIndex])
|
|
4078
|
+
if (buttons[tabIndex]) {
|
|
4079
|
+
buttons[tabIndex].classList.add('active');
|
|
4080
|
+
buttons[tabIndex].setAttribute('aria-selected', 'true');
|
|
4081
|
+
}
|
|
3673
4082
|
}
|
|
3674
4083
|
|
|
3675
4084
|
|
|
@@ -3729,7 +4138,7 @@ function renderDataModel() {
|
|
|
3729
4138
|
|
|
3730
4139
|
html += `<div class="dm-module-group">`;
|
|
3731
4140
|
html += `<div class="dm-module-header">
|
|
3732
|
-
<span class="dm-module-name">${m.name || m.code}</span>
|
|
4141
|
+
<span class="dm-module-name">${escapeHtml(m.name || m.code)}</span>
|
|
3733
4142
|
<span class="dm-module-count">${entities.length} entité${entities.length > 1 ? 's' : ''}</span>
|
|
3734
4143
|
</div>`;
|
|
3735
4144
|
|
|
@@ -3740,15 +4149,15 @@ function renderDataModel() {
|
|
|
3740
4149
|
html += `
|
|
3741
4150
|
<div class="dm-entity-card">
|
|
3742
4151
|
<div class="dm-entity-header">
|
|
3743
|
-
<span class="dm-entity-name">${ent.name}</span>
|
|
4152
|
+
<span class="dm-entity-name">${escapeHtml(ent.name)}</span>
|
|
3744
4153
|
<span class="dm-entity-attr-count">${attrs.length} champ${attrs.length > 1 ? 's' : ''}</span>
|
|
3745
4154
|
</div>
|
|
3746
|
-
${ent.description ? `<div class="dm-entity-desc">${ent.description}</div>` : ''}
|
|
4155
|
+
${ent.description ? `<div class="dm-entity-desc">${escapeHtml(ent.description)}</div>` : ''}
|
|
3747
4156
|
${attrs.length > 0 ? `
|
|
3748
4157
|
<table class="dm-attr-table">
|
|
3749
4158
|
<thead><tr><th>Champ</th><th>Description</th></tr></thead>
|
|
3750
4159
|
<tbody>
|
|
3751
|
-
${attrs.map(a => `<tr><td class="dm-attr-name">${a.name}</td><td class="dm-attr-desc">${a.description || ''}</td></tr>`).join('')}
|
|
4160
|
+
${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
4161
|
</tbody>
|
|
3753
4162
|
</table>` : ''}
|
|
3754
4163
|
${rels.length > 0 ? `
|
|
@@ -3756,7 +4165,7 @@ function renderDataModel() {
|
|
|
3756
4165
|
<div class="dm-relations-title">Relations</div>
|
|
3757
4166
|
${rels.map(r => {
|
|
3758
4167
|
const relText = typeof r === 'string' ? r : (r.target + ' (' + r.type + ') - ' + (r.description || ''));
|
|
3759
|
-
return `<div class="dm-relation-item">${relText}</div>`;
|
|
4168
|
+
return `<div class="dm-relation-item">${escapeHtml(relText)}</div>`;
|
|
3760
4169
|
}).join('')}
|
|
3761
4170
|
</div>` : ''}
|
|
3762
4171
|
</div>`;
|
|
@@ -3776,11 +4185,11 @@ function renderConsolInteractions() {
|
|
|
3776
4185
|
const toName = data.modules.find(m => m.code === d.to)?.name || d.to;
|
|
3777
4186
|
return `
|
|
3778
4187
|
<div class="interaction-item">
|
|
3779
|
-
<span style="font-weight:600;color:var(--text-bright);">${fromName}</span>
|
|
4188
|
+
<span style="font-weight:600;color:var(--text-bright);">${escapeHtml(fromName)}</span>
|
|
3780
4189
|
<span class="interaction-arrow">→</span>
|
|
3781
|
-
<span style="font-weight:600;color:var(--text-bright);">${toName}</span>
|
|
4190
|
+
<span style="font-weight:600;color:var(--text-bright);">${escapeHtml(toName)}</span>
|
|
3782
4191
|
<span class="interaction-type">Dépendance</span>
|
|
3783
|
-
<span style="flex:1;font-size:0.8rem;color:var(--text-muted);">${d.description || ''}</span>
|
|
4192
|
+
<span style="flex:1;font-size:0.8rem;color:var(--text-muted);">${escapeHtml(d.description || '')}</span>
|
|
3784
4193
|
</div>`;
|
|
3785
4194
|
}).join('');
|
|
3786
4195
|
}
|
|
@@ -3793,11 +4202,11 @@ function renderConsolPermissions() {
|
|
|
3793
4202
|
|
|
3794
4203
|
let html = '<table class="mock-table" style="background:var(--bg-card);border-radius:8px;overflow:hidden;">';
|
|
3795
4204
|
html += '<thead><tr><th>Profil</th>';
|
|
3796
|
-
data.modules.forEach(m => { html += `<th style="text-align:center;">${m.name || m.code}</th>`; });
|
|
4205
|
+
data.modules.forEach(m => { html += `<th style="text-align:center;">${escapeHtml(m.name || m.code)}</th>`; });
|
|
3797
4206
|
html += '</tr></thead><tbody>';
|
|
3798
4207
|
|
|
3799
4208
|
roles.forEach(role => {
|
|
3800
|
-
html += `<tr><td style="font-weight:500;color:var(--text-bright);">${role}</td>`;
|
|
4209
|
+
html += `<tr><td style="font-weight:500;color:var(--text-bright);">${escapeHtml(role)}</td>`;
|
|
3801
4210
|
data.modules.forEach(m => {
|
|
3802
4211
|
const allPerms = data.moduleSpecs[m.code]?.permissions || [];
|
|
3803
4212
|
const hasWildcard = allPerms.includes(role + '|*');
|
|
@@ -3841,6 +4250,7 @@ function addE2EFlow() {
|
|
|
3841
4250
|
}
|
|
3842
4251
|
|
|
3843
4252
|
function removeE2EFlow(index) {
|
|
4253
|
+
if (!confirm('Supprimer ce parcours bout en bout ?')) return;
|
|
3844
4254
|
data.consolidation.e2eFlows.splice(index, 1);
|
|
3845
4255
|
renderE2EFlows();
|
|
3846
4256
|
autoSave();
|
|
@@ -3853,15 +4263,15 @@ function renderE2EFlows() {
|
|
|
3853
4263
|
container.innerHTML = data.consolidation.e2eFlows.map((flow, fi) => `
|
|
3854
4264
|
<div class="card" style="margin-bottom:1rem;">
|
|
3855
4265
|
<div class="card-header">
|
|
3856
|
-
<span class="card-title">${flow.name}</span>
|
|
4266
|
+
<span class="card-title">${escapeHtml(flow.name)}</span>
|
|
3857
4267
|
<button class="btn btn-sm" onclick="removeE2EFlow(${fi})" style="opacity:0.5;">Supprimer</button>
|
|
3858
4268
|
</div>
|
|
3859
|
-
${flow.actors ? `<div style="font-size:0.8rem;color:var(--text-muted);margin-bottom:0.5rem;">Intervenants : ${flow.actors}</div>` : ''}
|
|
4269
|
+
${flow.actors ? `<div style="font-size:0.8rem;color:var(--text-muted);margin-bottom:0.5rem;">Intervenants : ${escapeHtml(flow.actors)}</div>` : ''}
|
|
3860
4270
|
<div class="e2e-flow">
|
|
3861
4271
|
${flow.steps.map((s, i) => `
|
|
3862
4272
|
<div class="e2e-step">
|
|
3863
|
-
<div class="e2e-step-module">${s.module}</div>
|
|
3864
|
-
<div class="e2e-step-action">${s.action}</div>
|
|
4273
|
+
<div class="e2e-step-module">${escapeHtml(s.module)}</div>
|
|
4274
|
+
<div class="e2e-step-action">${escapeHtml(s.action)}</div>
|
|
3865
4275
|
</div>
|
|
3866
4276
|
${i < flow.steps.length - 1 ? '<div class="process-arrow">→</div>' : ''}
|
|
3867
4277
|
`).join('')}
|
|
@@ -3887,7 +4297,7 @@ function renderScreenMockups(code) {
|
|
|
3887
4297
|
var resources = screen.resources || [];
|
|
3888
4298
|
return '<div class="screen-section" style="margin-bottom:2rem;">' +
|
|
3889
4299
|
'<h3 style="color:var(--text-bright);font-size:1rem;margin-bottom:1rem;">' +
|
|
3890
|
-
'<span style="color:var(--accent);">▸</span> ' + (screen.sectionLabel || screen.sectionCode) +
|
|
4300
|
+
'<span style="color:var(--accent);">▸</span> ' + escapeHtml(screen.sectionLabel || screen.sectionCode) +
|
|
3891
4301
|
'</h3>' +
|
|
3892
4302
|
resources.map(function(res, ri) {
|
|
3893
4303
|
return renderResourceMockup(code, screen.sectionCode, res, ri);
|
|
@@ -3905,9 +4315,9 @@ function renderResourceMockup(code, sectionCode, res, index) {
|
|
|
3905
4315
|
html += '<div class="mockup-dot mockup-dot-red"></div>';
|
|
3906
4316
|
html += '<div class="mockup-dot mockup-dot-yellow"></div>';
|
|
3907
4317
|
html += '<div class="mockup-dot mockup-dot-green"></div>';
|
|
3908
|
-
html += '<span class="mockup-title">' + (res.label || res.code) + ' (' + res.type + ')</span>';
|
|
4318
|
+
html += '<span class="mockup-title">' + escapeHtml(res.label || res.code) + ' (' + res.type + ')</span>';
|
|
3909
4319
|
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>';
|
|
4320
|
+
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
4321
|
}
|
|
3912
4322
|
html += '</div>';
|
|
3913
4323
|
|
|
@@ -3940,7 +4350,7 @@ function renderResourceMockup(code, sectionCode, res, index) {
|
|
|
3940
4350
|
// Notes
|
|
3941
4351
|
if (res.notes) {
|
|
3942
4352
|
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;
|
|
4353
|
+
html += '<strong>Notes:</strong> ' + escapeHtml(res.notes);
|
|
3944
4354
|
html += '</div>';
|
|
3945
4355
|
}
|
|
3946
4356
|
|
|
@@ -3967,7 +4377,7 @@ function renderSmartTableMockup(res) {
|
|
|
3967
4377
|
|
|
3968
4378
|
// Header with actions
|
|
3969
4379
|
html += '<div class="mock-header">';
|
|
3970
|
-
html += '<span class="mock-title">' + (res.label || 'Liste') + '</span>';
|
|
4380
|
+
html += '<span class="mock-title">' + escapeHtml(res.label || 'Liste') + '</span>';
|
|
3971
4381
|
html += '<div style="display:flex;gap:0.4rem;">';
|
|
3972
4382
|
(res.actions || []).forEach(function(action) {
|
|
3973
4383
|
var isPrimary = action === 'create' || action === 'export';
|
|
@@ -3989,7 +4399,7 @@ function renderSmartTableMockup(res) {
|
|
|
3989
4399
|
html += '<table class="mock-table">';
|
|
3990
4400
|
html += '<thead><tr>';
|
|
3991
4401
|
columns.forEach(function(col) {
|
|
3992
|
-
html += '<th>' + (col.label || col.field);
|
|
4402
|
+
html += '<th>' + escapeHtml(col.label || col.field);
|
|
3993
4403
|
if (col.sortable) html += ' <span style="font-size:0.6rem;color:var(--text-muted);">▲▼</span>';
|
|
3994
4404
|
html += '</th>';
|
|
3995
4405
|
});
|
|
@@ -4040,7 +4450,7 @@ function renderSmartFormMockup(res) {
|
|
|
4040
4450
|
|
|
4041
4451
|
// Header
|
|
4042
4452
|
html += '<div class="mock-header">';
|
|
4043
|
-
html += '<span class="mock-title">' + (res.label || 'Formulaire') + '</span>';
|
|
4453
|
+
html += '<span class="mock-title">' + escapeHtml(res.label || 'Formulaire') + '</span>';
|
|
4044
4454
|
html += '<div style="display:flex;gap:0.4rem;">';
|
|
4045
4455
|
(res.actions || ['save', 'cancel']).forEach(function(action) {
|
|
4046
4456
|
var isPrimary = action === 'save';
|
|
@@ -4052,7 +4462,7 @@ function renderSmartFormMockup(res) {
|
|
|
4052
4462
|
if (tabs.length > 1) {
|
|
4053
4463
|
html += '<div style="display:flex;gap:0;border-bottom:1px solid var(--border);margin-bottom:1.5rem;">';
|
|
4054
4464
|
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>';
|
|
4465
|
+
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
4466
|
});
|
|
4057
4467
|
html += '</div>';
|
|
4058
4468
|
}
|
|
@@ -4071,7 +4481,7 @@ function renderSmartFormMockup(res) {
|
|
|
4071
4481
|
html += '<div class="mock-form-row">';
|
|
4072
4482
|
row.forEach(function(field) {
|
|
4073
4483
|
html += '<div class="mock-form-group">';
|
|
4074
|
-
html += '<label class="mock-label">' + (field.label || field.field);
|
|
4484
|
+
html += '<label class="mock-label">' + escapeHtml(field.label || field.field);
|
|
4075
4485
|
if (field.required) html += ' <span style="color:var(--error);">*</span>';
|
|
4076
4486
|
html += '</label>';
|
|
4077
4487
|
html += renderFormFieldMockup(field);
|
|
@@ -4107,11 +4517,11 @@ function renderSubtableMockup(field) {
|
|
|
4107
4517
|
var entity = field.entity || 'Element';
|
|
4108
4518
|
var html = '<div style="margin:1rem 0;border:1px solid var(--border);border-radius:8px;overflow:hidden;">';
|
|
4109
4519
|
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>';
|
|
4520
|
+
html += '<span style="font-weight:500;color:var(--text-bright);font-size:0.85rem;">' + escapeHtml(entity) + '</span>';
|
|
4111
4521
|
html += '<span class="mock-btn" style="font-size:0.75rem;padding:0.2rem 0.5rem;">+ Ajouter</span>';
|
|
4112
4522
|
html += '</div>';
|
|
4113
4523
|
html += '<table class="mock-table"><thead><tr>';
|
|
4114
|
-
cols.forEach(function(c) { html += '<th>' + c + '</th>'; });
|
|
4524
|
+
cols.forEach(function(c) { html += '<th>' + escapeHtml(c) + '</th>'; });
|
|
4115
4525
|
html += '</tr></thead><tbody>';
|
|
4116
4526
|
html += '<tr>' + cols.map(function() { return '<td style="color:var(--text-muted);font-style:italic;">...</td>'; }).join('') + '</tr>';
|
|
4117
4527
|
html += '</tbody></table></div>';
|
|
@@ -4121,7 +4531,7 @@ function renderSubtableMockup(field) {
|
|
|
4121
4531
|
/* ---------- SmartCard ---------- */
|
|
4122
4532
|
function renderSmartCardMockup(res) {
|
|
4123
4533
|
var columns = res.columns || res.fields || [];
|
|
4124
|
-
var html = '<div class="mock-header"><span class="mock-title">' + (res.label || 'Cartes') + '</span></div>';
|
|
4534
|
+
var html = '<div class="mock-header"><span class="mock-title">' + escapeHtml(res.label || 'Cartes') + '</span></div>';
|
|
4125
4535
|
html += '<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:1rem;">';
|
|
4126
4536
|
|
|
4127
4537
|
for (var i = 0; i < 4; i++) {
|
|
@@ -4129,9 +4539,9 @@ function renderSmartCardMockup(res) {
|
|
|
4129
4539
|
columns.forEach(function(col, ci) {
|
|
4130
4540
|
var label = col.label || col.field || col;
|
|
4131
4541
|
if (ci === 0) {
|
|
4132
|
-
html += '<div style="font-weight:600;color:var(--text-bright);margin-bottom:0.5rem;">' + label + ' #' + (i + 1) + '</div>';
|
|
4542
|
+
html += '<div style="font-weight:600;color:var(--text-bright);margin-bottom:0.5rem;">' + escapeHtml(label) + ' #' + (i + 1) + '</div>';
|
|
4133
4543
|
} 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>';
|
|
4544
|
+
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
4545
|
}
|
|
4136
4546
|
});
|
|
4137
4547
|
html += '</div>';
|
|
@@ -4143,14 +4553,14 @@ function renderSmartCardMockup(res) {
|
|
|
4143
4553
|
/* ---------- SmartKanban ---------- */
|
|
4144
4554
|
function renderSmartKanbanMockup(res) {
|
|
4145
4555
|
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>';
|
|
4556
|
+
var html = '<div class="mock-header"><span class="mock-title">' + escapeHtml(res.label || 'Kanban') + '</span></div>';
|
|
4147
4557
|
html += '<div style="display:flex;gap:1rem;overflow-x:auto;padding-bottom:0.5rem;">';
|
|
4148
4558
|
|
|
4149
4559
|
options.forEach(function(col, ci) {
|
|
4150
4560
|
var colLabel = typeof col === 'string' ? col : (col.label || col.field || 'Colonne');
|
|
4151
4561
|
html += '<div style="min-width:200px;flex:1;background:var(--bg-hover);border-radius:8px;padding:0.75rem;">';
|
|
4152
4562
|
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>';
|
|
4563
|
+
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
4564
|
html += '</div>';
|
|
4155
4565
|
for (var j = 0; j < Math.max(1, 3 - ci); j++) {
|
|
4156
4566
|
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 +4576,7 @@ function renderSmartKanbanMockup(res) {
|
|
|
4166
4576
|
|
|
4167
4577
|
/* ---------- SmartDashboard ---------- */
|
|
4168
4578
|
function renderSmartDashboardMockup(res) {
|
|
4169
|
-
var html = '<div class="mock-header"><span class="mock-title">' + (res.label || 'Tableau de bord') + '</span></div>';
|
|
4579
|
+
var html = '<div class="mock-header"><span class="mock-title">' + escapeHtml(res.label || 'Tableau de bord') + '</span></div>';
|
|
4170
4580
|
|
|
4171
4581
|
// KPI cards
|
|
4172
4582
|
html += '<div class="mock-kpi-grid">';
|
|
@@ -4177,7 +4587,7 @@ function renderSmartDashboardMockup(res) {
|
|
|
4177
4587
|
{ label: 'Taux', value: '80%' }
|
|
4178
4588
|
];
|
|
4179
4589
|
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>';
|
|
4590
|
+
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
4591
|
});
|
|
4182
4592
|
html += '</div>';
|
|
4183
4593
|
|
|
@@ -4193,7 +4603,7 @@ function renderSmartFilterMockup(res) {
|
|
|
4193
4603
|
var html = '<div style="display:flex;gap:0.4rem;flex-wrap:wrap;padding:0.5rem 0;">';
|
|
4194
4604
|
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
4605
|
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>';
|
|
4606
|
+
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
4607
|
});
|
|
4198
4608
|
html += '</div>';
|
|
4199
4609
|
return html;
|
|
@@ -4254,6 +4664,7 @@ function renderHandoff() {
|
|
|
4254
4664
|
renderHandoffStats();
|
|
4255
4665
|
renderHandoffModules();
|
|
4256
4666
|
renderCoverageMatrix();
|
|
4667
|
+
renderProgressIndicator();
|
|
4257
4668
|
}
|
|
4258
4669
|
|
|
4259
4670
|
function renderHandoffStats() {
|
|
@@ -4292,8 +4703,8 @@ function renderHandoffModules() {
|
|
|
4292
4703
|
<div style="display:flex;align-items:center;gap:0.75rem;">
|
|
4293
4704
|
<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
4705
|
<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>
|
|
4706
|
+
<div style="font-weight:600;color:var(--text-bright);">${escapeHtml(m.name)}</div>
|
|
4707
|
+
<div style="font-size:0.8rem;color:var(--text-muted);">${escapeHtml(m.description || '')}</div>
|
|
4297
4708
|
</div>
|
|
4298
4709
|
<div style="display:flex;gap:1rem;font-size:0.75rem;color:var(--text-muted);">
|
|
4299
4710
|
<span>${(spec.useCases || []).length} cas d'utilisation</span>
|
|
@@ -4326,24 +4737,47 @@ function renderCoverageMatrix() {
|
|
|
4326
4737
|
: (data.modules.length > 0 ? data.modules.map(m => m.name).join(', ') : 'À définir');
|
|
4327
4738
|
return `
|
|
4328
4739
|
<tr>
|
|
4329
|
-
<td>${item.name}</td>
|
|
4740
|
+
<td>${escapeHtml(item.name)}</td>
|
|
4330
4741
|
<td><span class="priority priority-${item.priority}">${formatPriority(item.priority)}</span></td>
|
|
4331
|
-
<td style="color:var(--text-muted);">${moduleName}</td>
|
|
4742
|
+
<td style="color:var(--text-muted);">${escapeHtml(moduleName)}</td>
|
|
4332
4743
|
<td style="text-align:center;color:var(--success);">✓</td>
|
|
4333
4744
|
</tr>`;
|
|
4334
4745
|
}).join('')}
|
|
4335
4746
|
</tbody>
|
|
4336
4747
|
</table>`;
|
|
4337
4748
|
}
|
|
4749
|
+
|
|
4750
|
+
function renderProgressIndicator() {
|
|
4751
|
+
var container = document.getElementById('progressIndicator');
|
|
4752
|
+
if (!container) return;
|
|
4753
|
+
var p = computeProgress();
|
|
4754
|
+
var html = '<div class="progress-bar-container">';
|
|
4755
|
+
html += '<div class="progress-bar-fill" style="width:' + p.pct + '%;"></div>';
|
|
4756
|
+
html += '</div>';
|
|
4757
|
+
html += '<div class="progress-label">' + p.pct + '% complet — ' + p.doneCount + '/' + p.total + ' critères</div>';
|
|
4758
|
+
html += '<div class="progress-checks">';
|
|
4759
|
+
p.checks.forEach(function(c) {
|
|
4760
|
+
html += '<div class="progress-check ' + (c.done ? 'done' : '') + '">';
|
|
4761
|
+
html += '<span class="progress-check-icon">' + (c.done ? '✓' : '○') + '</span> ';
|
|
4762
|
+
html += escapeHtml(c.label);
|
|
4763
|
+
html += '</div>';
|
|
4764
|
+
});
|
|
4765
|
+
html += '</div>';
|
|
4766
|
+
container.innerHTML = html;
|
|
4767
|
+
}
|
|
4338
4768
|
|
|
4339
4769
|
|
|
4340
4770
|
/* --- 08-editing.js --- */
|
|
4341
4771
|
/* ============================================
|
|
4342
4772
|
PERSISTENCE
|
|
4343
4773
|
============================================ */
|
|
4774
|
+
let _saveTimer;
|
|
4344
4775
|
function autoSave() {
|
|
4345
|
-
|
|
4346
|
-
|
|
4776
|
+
clearTimeout(_saveTimer);
|
|
4777
|
+
_saveTimer = setTimeout(function() {
|
|
4778
|
+
data.metadata.lastModified = new Date().toISOString();
|
|
4779
|
+
localStorage.setItem(APP_KEY, JSON.stringify(data));
|
|
4780
|
+
}, 500);
|
|
4347
4781
|
}
|
|
4348
4782
|
|
|
4349
4783
|
function saveToLocalStorage() {
|
|
@@ -4477,13 +4911,10 @@ function loadFromLocalStorage() {
|
|
|
4477
4911
|
/* ============================================
|
|
4478
4912
|
EXPORT JSON
|
|
4479
4913
|
============================================ */
|
|
4480
|
-
function
|
|
4481
|
-
// Collect all editable fields (cadrage)
|
|
4914
|
+
function collectEditableFields() {
|
|
4482
4915
|
document.querySelectorAll('.editable[data-field]').forEach(el => {
|
|
4483
4916
|
setNestedValue(data, 'cadrage.' + el.dataset.field, el.textContent.trim());
|
|
4484
4917
|
});
|
|
4485
|
-
|
|
4486
|
-
// Collect module editable fields
|
|
4487
4918
|
document.querySelectorAll('.editable[data-module-field]').forEach(el => {
|
|
4488
4919
|
const code = el.dataset.moduleCode;
|
|
4489
4920
|
const field = el.dataset.moduleField;
|
|
@@ -4491,17 +4922,39 @@ function exportJSON() {
|
|
|
4491
4922
|
data.moduleSpecs[code][field] = el.textContent.trim();
|
|
4492
4923
|
}
|
|
4493
4924
|
});
|
|
4925
|
+
}
|
|
4926
|
+
|
|
4927
|
+
function buildModuleSpecsExport() {
|
|
4928
|
+
var result = {};
|
|
4929
|
+
data.modules.forEach(function(m) {
|
|
4930
|
+
var spec = data.moduleSpecs[m.code] || {};
|
|
4931
|
+
result[m.code] = {
|
|
4932
|
+
module: m,
|
|
4933
|
+
useCases: (spec.useCases || []).map(function(uc, i) {
|
|
4934
|
+
return Object.assign({ id: 'UC-' + String(i + 1).padStart(3, '0') }, uc);
|
|
4935
|
+
}),
|
|
4936
|
+
businessRules: (spec.businessRules || []).map(function(br, i) {
|
|
4937
|
+
return Object.assign({ id: 'BR-' + (br.category || 'GEN').toUpperCase().substring(0, 4) + '-' + String(i + 1).padStart(3, '0') }, br);
|
|
4938
|
+
}),
|
|
4939
|
+
entities: spec.entities || [],
|
|
4940
|
+
permissions: spec.permissions || [],
|
|
4941
|
+
notes: spec.notes || ''
|
|
4942
|
+
};
|
|
4943
|
+
});
|
|
4944
|
+
return result;
|
|
4945
|
+
}
|
|
4494
4946
|
|
|
4947
|
+
function exportJSON() {
|
|
4948
|
+
collectEditableFields();
|
|
4495
4949
|
data.metadata.lastModified = new Date().toISOString();
|
|
4496
4950
|
data.metadata.exportedAt = new Date().toISOString();
|
|
4497
4951
|
|
|
4498
|
-
// Build complete export with structured data
|
|
4499
4952
|
const exportData = {
|
|
4500
4953
|
metadata: data.metadata,
|
|
4501
4954
|
cadrage: data.cadrage,
|
|
4502
4955
|
modules: data.modules,
|
|
4503
4956
|
dependencies: data.dependencies,
|
|
4504
|
-
moduleSpecifications:
|
|
4957
|
+
moduleSpecifications: buildModuleSpecsExport(),
|
|
4505
4958
|
consolidation: data.consolidation,
|
|
4506
4959
|
artifacts: EMBEDDED_ARTIFACTS,
|
|
4507
4960
|
wireframeComments: data.wireframeComments,
|
|
@@ -4511,25 +4964,6 @@ function exportJSON() {
|
|
|
4511
4964
|
comments: data.comments
|
|
4512
4965
|
};
|
|
4513
4966
|
|
|
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
4967
|
const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
|
|
4534
4968
|
const url = URL.createObjectURL(blob);
|
|
4535
4969
|
const a = document.createElement('a');
|
|
@@ -4569,24 +5003,13 @@ function detectChanges(original, current) {
|
|
|
4569
5003
|
}
|
|
4570
5004
|
|
|
4571
5005
|
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
|
-
});
|
|
5006
|
+
collectEditableFields();
|
|
4583
5007
|
|
|
4584
5008
|
const changes = detectChanges(ORIGINAL_DATA, data);
|
|
4585
5009
|
const hasChanges = changes.cadrage || changes.modulesAdded.length > 0
|
|
4586
5010
|
|| changes.modulesRemoved.length > 0 || changes.modulesModified.length > 0
|
|
4587
5011
|
|| changes.commentsCount > 0;
|
|
4588
5012
|
|
|
4589
|
-
// Build review export with metadata envelope
|
|
4590
5013
|
const reviewData = {
|
|
4591
5014
|
_reviewMeta: {
|
|
4592
5015
|
sourceVersion: data.metadata.version || '1.0',
|
|
@@ -4605,7 +5028,7 @@ function saveReviewJSON() {
|
|
|
4605
5028
|
cadrage: data.cadrage,
|
|
4606
5029
|
modules: data.modules,
|
|
4607
5030
|
dependencies: data.dependencies,
|
|
4608
|
-
moduleSpecifications:
|
|
5031
|
+
moduleSpecifications: buildModuleSpecsExport(),
|
|
4609
5032
|
consolidation: data.consolidation,
|
|
4610
5033
|
wireframeComments: data.wireframeComments,
|
|
4611
5034
|
specComments: data.specComments,
|
|
@@ -4614,25 +5037,6 @@ function saveReviewJSON() {
|
|
|
4614
5037
|
comments: data.comments
|
|
4615
5038
|
};
|
|
4616
5039
|
|
|
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
5040
|
const blob = new Blob([JSON.stringify(reviewData, null, 2)], { type: 'application/json' });
|
|
4637
5041
|
const url = URL.createObjectURL(blob);
|
|
4638
5042
|
const a = document.createElement('a');
|
|
@@ -4651,14 +5055,24 @@ function saveReviewJSON() {
|
|
|
4651
5055
|
|
|
4652
5056
|
/**
|
|
4653
5057
|
* Comments are stored in data.comments[] with structure:
|
|
4654
|
-
* { id, sectionId,
|
|
5058
|
+
* { id, sectionId, elementId, author, timestamp, content, status, category }
|
|
4655
5059
|
*
|
|
4656
|
-
* - sectionId: matches the section div id (e.g. "cadrage-
|
|
4657
|
-
* -
|
|
5060
|
+
* - sectionId: matches the section div id (e.g. "cadrage-context", "module-spec-Clients")
|
|
5061
|
+
* - elementId: stable identifier for the element (name, role, or generated id)
|
|
5062
|
+
* - cardIndex: (deprecated, kept for backward compat) positional index
|
|
4658
5063
|
* - status: "to-review" | "validated"
|
|
4659
5064
|
* - category: "clarification" | "correction" | "suggestion"
|
|
4660
5065
|
*/
|
|
4661
5066
|
|
|
5067
|
+
function getElementId(item, sectionId, index) {
|
|
5068
|
+
// Extract a stable ID from the element's data
|
|
5069
|
+
var title = item.querySelector('.uc-title, .entity-name, .stakeholder-role, .card-title');
|
|
5070
|
+
if (title) return title.textContent.trim();
|
|
5071
|
+
var nameEl = item.querySelector('[data-field]');
|
|
5072
|
+
if (nameEl) return nameEl.dataset.field;
|
|
5073
|
+
return sectionId + '-' + index;
|
|
5074
|
+
}
|
|
5075
|
+
|
|
4662
5076
|
function initInlineComments() {
|
|
4663
5077
|
// Cadrage sections: direct card children
|
|
4664
5078
|
document.querySelectorAll('.section > .card, .section > .stakeholder-card, .section > .uc-item').forEach(function(card) {
|
|
@@ -4668,7 +5082,8 @@ function initInlineComments() {
|
|
|
4668
5082
|
var sectionId = section ? section.id : 'unknown';
|
|
4669
5083
|
var siblings = Array.from(section.querySelectorAll(':scope > .card, :scope > .stakeholder-card, :scope > .uc-item'));
|
|
4670
5084
|
var index = siblings.indexOf(card);
|
|
4671
|
-
card
|
|
5085
|
+
var elementId = getElementId(card, sectionId, index);
|
|
5086
|
+
card.appendChild(createCommentUI(sectionId, elementId));
|
|
4672
5087
|
});
|
|
4673
5088
|
|
|
4674
5089
|
// Module spec lists: nested items in ucList, brList, entList containers
|
|
@@ -4679,7 +5094,8 @@ function initInlineComments() {
|
|
|
4679
5094
|
var listId = list.id;
|
|
4680
5095
|
var siblings = Array.from(list.children);
|
|
4681
5096
|
var index = siblings.indexOf(item);
|
|
4682
|
-
item
|
|
5097
|
+
var elementId = getElementId(item, listId, index);
|
|
5098
|
+
item.appendChild(createCommentUI(listId, elementId));
|
|
4683
5099
|
});
|
|
4684
5100
|
|
|
4685
5101
|
// Stakeholder cards in grid
|
|
@@ -4691,7 +5107,8 @@ function initInlineComments() {
|
|
|
4691
5107
|
var sectionId = section ? section.id : 'stakeholders';
|
|
4692
5108
|
var siblings = Array.from(grid.children);
|
|
4693
5109
|
var index = siblings.indexOf(card);
|
|
4694
|
-
card
|
|
5110
|
+
var elementId = getElementId(card, sectionId, index);
|
|
5111
|
+
card.appendChild(createCommentUI(sectionId, elementId));
|
|
4695
5112
|
});
|
|
4696
5113
|
|
|
4697
5114
|
// Scope items
|
|
@@ -4701,36 +5118,39 @@ function initInlineComments() {
|
|
|
4701
5118
|
container.querySelectorAll('.uc-item').forEach(function(item, index) {
|
|
4702
5119
|
if (item.dataset.commentInitialized) return;
|
|
4703
5120
|
item.dataset.commentInitialized = 'true';
|
|
4704
|
-
item
|
|
5121
|
+
var elementId = getElementId(item, containerId, index);
|
|
5122
|
+
item.appendChild(createCommentUI(containerId, elementId));
|
|
4705
5123
|
});
|
|
4706
5124
|
});
|
|
4707
5125
|
}
|
|
4708
5126
|
|
|
4709
|
-
function createCommentUI(sectionId,
|
|
4710
|
-
const comments = getCommentsForCard(sectionId,
|
|
5127
|
+
function createCommentUI(sectionId, elementId) {
|
|
5128
|
+
const comments = getCommentsForCard(sectionId, elementId);
|
|
4711
5129
|
const count = comments.length;
|
|
5130
|
+
const safeElId = String(elementId).replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
5131
|
+
const threadId = 'comment-thread-' + sectionId + '-' + safeElId;
|
|
4712
5132
|
|
|
4713
5133
|
const container = document.createElement('div');
|
|
4714
5134
|
container.className = 'comment-btn-container';
|
|
4715
5135
|
container.dataset.sectionId = sectionId;
|
|
4716
|
-
container.dataset.
|
|
5136
|
+
container.dataset.elementId = elementId;
|
|
4717
5137
|
|
|
4718
5138
|
container.innerHTML = `
|
|
4719
|
-
<button class="comment-toggle-btn" onclick="toggleCommentThread('${sectionId}', ${
|
|
5139
|
+
<button class="comment-toggle-btn" onclick="toggleCommentThread('${sectionId}', '${safeElId}')">
|
|
4720
5140
|
Commentaires <span class="comment-count ${count === 0 ? 'empty' : ''}">${count}</span>
|
|
4721
5141
|
</button>
|
|
4722
|
-
<div class="comment-thread" id="
|
|
4723
|
-
<div class="comment-items" id="comment-items-${sectionId}-${
|
|
4724
|
-
${renderCommentItems(sectionId,
|
|
5142
|
+
<div class="comment-thread" id="${threadId}">
|
|
5143
|
+
<div class="comment-items" id="comment-items-${sectionId}-${safeElId}">
|
|
5144
|
+
${renderCommentItems(sectionId, elementId)}
|
|
4725
5145
|
</div>
|
|
4726
5146
|
<div class="comment-add-form">
|
|
4727
|
-
<textarea id="comment-text-${sectionId}-${
|
|
4728
|
-
<select id="comment-cat-${sectionId}-${
|
|
5147
|
+
<textarea id="comment-text-${sectionId}-${safeElId}" placeholder="Ajouter un commentaire..."></textarea>
|
|
5148
|
+
<select id="comment-cat-${sectionId}-${safeElId}">
|
|
4729
5149
|
<option value="clarification">Clarification</option>
|
|
4730
5150
|
<option value="correction">Correction</option>
|
|
4731
5151
|
<option value="suggestion">Suggestion</option>
|
|
4732
5152
|
</select>
|
|
4733
|
-
<button onclick="addInlineComment('${sectionId}', ${
|
|
5153
|
+
<button onclick="addInlineComment('${sectionId}', '${elementId.replace(/'/g, "\\'")}')">Ajouter</button>
|
|
4734
5154
|
</div>
|
|
4735
5155
|
</div>
|
|
4736
5156
|
`;
|
|
@@ -4738,25 +5158,27 @@ function createCommentUI(sectionId, cardIndex) {
|
|
|
4738
5158
|
return container;
|
|
4739
5159
|
}
|
|
4740
5160
|
|
|
4741
|
-
function getCommentsForCard(sectionId,
|
|
4742
|
-
return (data.comments || []).filter(c
|
|
4743
|
-
c.sectionId === sectionId && c.
|
|
4744
|
-
|
|
5161
|
+
function getCommentsForCard(sectionId, elementId) {
|
|
5162
|
+
return (data.comments || []).filter(function(c) {
|
|
5163
|
+
if (c.elementId) return c.sectionId === sectionId && c.elementId === elementId;
|
|
5164
|
+
// Backward compat: match by cardIndex if elementId not set
|
|
5165
|
+
return c.sectionId === sectionId && c.cardIndex === elementId;
|
|
5166
|
+
});
|
|
4745
5167
|
}
|
|
4746
5168
|
|
|
4747
|
-
function toggleCommentThread(sectionId,
|
|
4748
|
-
const thread = document.getElementById('comment-thread-' + sectionId + '-' +
|
|
5169
|
+
function toggleCommentThread(sectionId, safeElId) {
|
|
5170
|
+
const thread = document.getElementById('comment-thread-' + sectionId + '-' + safeElId);
|
|
4749
5171
|
if (thread) thread.classList.toggle('visible');
|
|
4750
5172
|
}
|
|
4751
5173
|
|
|
4752
|
-
function renderCommentItems(sectionId,
|
|
4753
|
-
const comments = getCommentsForCard(sectionId,
|
|
5174
|
+
function renderCommentItems(sectionId, elementId) {
|
|
5175
|
+
const comments = getCommentsForCard(sectionId, elementId);
|
|
4754
5176
|
if (comments.length === 0) {
|
|
4755
5177
|
return '<div style="font-size:0.8rem;color:var(--text-muted);padding:0.5rem 0;font-style:italic;">Aucun commentaire</div>';
|
|
4756
5178
|
}
|
|
4757
5179
|
|
|
4758
|
-
return comments.map((c, i)
|
|
4759
|
-
const initials = (c.author || 'U').substring(0, 2).toUpperCase();
|
|
5180
|
+
return comments.map(function(c, i) {
|
|
5181
|
+
const initials = escapeHtml((c.author || 'U').substring(0, 2).toUpperCase());
|
|
4760
5182
|
const date = c.timestamp ? new Date(c.timestamp).toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }) : '';
|
|
4761
5183
|
const globalIndex = data.comments.indexOf(c);
|
|
4762
5184
|
|
|
@@ -4765,12 +5187,12 @@ function renderCommentItems(sectionId, cardIndex) {
|
|
|
4765
5187
|
<div class="comment-avatar">${initials}</div>
|
|
4766
5188
|
<div class="comment-body">
|
|
4767
5189
|
<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>
|
|
5190
|
+
<span class="comment-author">${escapeHtml(c.author || 'Utilisateur')}</span>
|
|
5191
|
+
<span class="comment-date">${escapeHtml(date)}</span>
|
|
5192
|
+
<span class="comment-category comment-category-${escapeHtml(c.category)}">${escapeHtml(c.category)}</span>
|
|
4771
5193
|
<span class="comment-status comment-status-${c.status}">${c.status === 'validated' ? 'Validé' : 'À revoir'}</span>
|
|
4772
5194
|
</div>
|
|
4773
|
-
<div class="comment-text">${c.content}</div>
|
|
5195
|
+
<div class="comment-text">${escapeHtml(c.content)}</div>
|
|
4774
5196
|
<div class="comment-actions">
|
|
4775
5197
|
<button class="comment-action-btn" onclick="toggleCommentStatus(${globalIndex})">${c.status === 'validated' ? 'Remettre à revoir' : 'Valider'}</button>
|
|
4776
5198
|
<button class="comment-action-btn" onclick="deleteComment(${globalIndex})" style="color:var(--error);">Supprimer</button>
|
|
@@ -4781,16 +5203,17 @@ function renderCommentItems(sectionId, cardIndex) {
|
|
|
4781
5203
|
}).join('');
|
|
4782
5204
|
}
|
|
4783
5205
|
|
|
4784
|
-
function addInlineComment(sectionId,
|
|
4785
|
-
|
|
4786
|
-
const
|
|
5206
|
+
function addInlineComment(sectionId, elementId) {
|
|
5207
|
+
var safeElId = String(elementId).replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
5208
|
+
const textEl = document.getElementById('comment-text-' + sectionId + '-' + safeElId);
|
|
5209
|
+
const catEl = document.getElementById('comment-cat-' + sectionId + '-' + safeElId);
|
|
4787
5210
|
const content = textEl.value.trim();
|
|
4788
5211
|
if (!content) return;
|
|
4789
5212
|
|
|
4790
5213
|
const comment = {
|
|
4791
5214
|
id: 'comment-' + Date.now(),
|
|
4792
5215
|
sectionId: sectionId,
|
|
4793
|
-
|
|
5216
|
+
elementId: elementId,
|
|
4794
5217
|
author: 'Utilisateur',
|
|
4795
5218
|
timestamp: new Date().toISOString(),
|
|
4796
5219
|
content: content,
|
|
@@ -4801,7 +5224,7 @@ function addInlineComment(sectionId, cardIndex) {
|
|
|
4801
5224
|
data.comments.push(comment);
|
|
4802
5225
|
textEl.value = '';
|
|
4803
5226
|
|
|
4804
|
-
refreshCommentUI(sectionId,
|
|
5227
|
+
refreshCommentUI(sectionId, elementId);
|
|
4805
5228
|
renderReviewPanel();
|
|
4806
5229
|
autoSave();
|
|
4807
5230
|
}
|
|
@@ -4810,32 +5233,35 @@ function toggleCommentStatus(globalIndex) {
|
|
|
4810
5233
|
const comment = data.comments[globalIndex];
|
|
4811
5234
|
if (!comment) return;
|
|
4812
5235
|
comment.status = comment.status === 'validated' ? 'to-review' : 'validated';
|
|
4813
|
-
|
|
5236
|
+
var elId = comment.elementId || comment.cardIndex;
|
|
5237
|
+
refreshCommentUI(comment.sectionId, elId);
|
|
4814
5238
|
renderReviewPanel();
|
|
4815
5239
|
autoSave();
|
|
4816
5240
|
}
|
|
4817
5241
|
|
|
4818
5242
|
function deleteComment(globalIndex) {
|
|
5243
|
+
if (!confirm('Supprimer ce commentaire ?')) return;
|
|
4819
5244
|
const comment = data.comments[globalIndex];
|
|
4820
5245
|
if (!comment) return;
|
|
4821
|
-
|
|
4822
|
-
|
|
5246
|
+
var sectionId = comment.sectionId;
|
|
5247
|
+
var elId = comment.elementId || comment.cardIndex;
|
|
4823
5248
|
data.comments.splice(globalIndex, 1);
|
|
4824
|
-
refreshCommentUI(sectionId,
|
|
5249
|
+
refreshCommentUI(sectionId, elId);
|
|
4825
5250
|
renderReviewPanel();
|
|
4826
5251
|
autoSave();
|
|
4827
5252
|
}
|
|
4828
5253
|
|
|
4829
|
-
function refreshCommentUI(sectionId,
|
|
5254
|
+
function refreshCommentUI(sectionId, elementId) {
|
|
5255
|
+
var safeElId = String(elementId).replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
4830
5256
|
// Update comment items
|
|
4831
|
-
const itemsContainer = document.getElementById('comment-items-' + sectionId + '-' +
|
|
5257
|
+
const itemsContainer = document.getElementById('comment-items-' + sectionId + '-' + safeElId);
|
|
4832
5258
|
if (itemsContainer) {
|
|
4833
|
-
itemsContainer.innerHTML = renderCommentItems(sectionId,
|
|
5259
|
+
itemsContainer.innerHTML = renderCommentItems(sectionId, elementId);
|
|
4834
5260
|
}
|
|
4835
5261
|
|
|
4836
5262
|
// Update count badge
|
|
4837
|
-
const count = getCommentsForCard(sectionId,
|
|
4838
|
-
const container = document.querySelector(
|
|
5263
|
+
const count = getCommentsForCard(sectionId, elementId).length;
|
|
5264
|
+
const container = document.querySelector('.comment-btn-container[data-section-id="' + sectionId + '"][data-element-id="' + elementId + '"]');
|
|
4839
5265
|
if (container) {
|
|
4840
5266
|
const badge = container.querySelector('.comment-count');
|
|
4841
5267
|
if (badge) {
|
|
@@ -4918,17 +5344,19 @@ function renderReviewPanel() {
|
|
|
4918
5344
|
return;
|
|
4919
5345
|
}
|
|
4920
5346
|
|
|
4921
|
-
container.innerHTML = filtered.map((c, i)
|
|
5347
|
+
container.innerHTML = filtered.map(function(c, i) {
|
|
4922
5348
|
const globalIndex = data.comments.indexOf(c);
|
|
4923
5349
|
const sectionLabel = getSectionLabel(c.sectionId);
|
|
4924
5350
|
const date = c.timestamp ? new Date(c.timestamp).toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit' }) : '';
|
|
5351
|
+
var elId = c.elementId || c.cardIndex;
|
|
5352
|
+
var safeElId = String(elId).replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
4925
5353
|
|
|
4926
5354
|
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>
|
|
5355
|
+
<div class="review-comment-item" onclick="navigateToComment('${c.sectionId}', '${safeElId}')">
|
|
5356
|
+
<div class="review-comment-section">${escapeHtml(sectionLabel)}</div>
|
|
5357
|
+
<div class="review-comment-text">${escapeHtml(c.content)}</div>
|
|
4930
5358
|
<div class="review-comment-footer">
|
|
4931
|
-
<span class="review-comment-author">${c.author || 'Utilisateur'} - ${date}</span>
|
|
5359
|
+
<span class="review-comment-author">${escapeHtml(c.author || 'Utilisateur')} - ${escapeHtml(date)}</span>
|
|
4932
5360
|
<div class="review-comment-actions">
|
|
4933
5361
|
<button class="review-action-btn ${c.status === 'validated' ? 'reject' : 'validate'}"
|
|
4934
5362
|
onclick="event.stopPropagation();toggleCommentStatus(${globalIndex})"
|
|
@@ -4984,29 +5412,29 @@ function getSectionLabel(sectionId) {
|
|
|
4984
5412
|
return sectionId;
|
|
4985
5413
|
}
|
|
4986
5414
|
|
|
4987
|
-
function navigateToComment(sectionId,
|
|
5415
|
+
function navigateToComment(sectionId, safeElId) {
|
|
4988
5416
|
// Handle list-based sectionIds (ucList-*, brList-*, entList-*) → navigate to module + tab
|
|
4989
5417
|
const listMatch = sectionId.match(/^(uc|br|ent)List-(.+)$/);
|
|
4990
5418
|
if (listMatch) {
|
|
4991
5419
|
const [, tabType, moduleCode] = listMatch;
|
|
4992
5420
|
showSection('module-spec-' + moduleCode);
|
|
4993
|
-
setTimeout(()
|
|
5421
|
+
setTimeout(function() {
|
|
4994
5422
|
switchTab(moduleCode, tabType);
|
|
4995
|
-
scrollToCommentThread(sectionId,
|
|
5423
|
+
scrollToCommentThread(sectionId, safeElId);
|
|
4996
5424
|
}, 150);
|
|
4997
5425
|
} else {
|
|
4998
5426
|
showSection(sectionId);
|
|
4999
|
-
scrollToCommentThread(sectionId,
|
|
5427
|
+
scrollToCommentThread(sectionId, safeElId);
|
|
5000
5428
|
}
|
|
5001
5429
|
}
|
|
5002
5430
|
|
|
5003
|
-
function scrollToCommentThread(sectionId,
|
|
5004
|
-
setTimeout(()
|
|
5005
|
-
const thread = document.getElementById('comment-thread-' + sectionId + '-' +
|
|
5431
|
+
function scrollToCommentThread(sectionId, safeElId) {
|
|
5432
|
+
setTimeout(function() {
|
|
5433
|
+
const thread = document.getElementById('comment-thread-' + sectionId + '-' + safeElId);
|
|
5006
5434
|
if (thread && !thread.classList.contains('visible')) {
|
|
5007
5435
|
thread.classList.add('visible');
|
|
5008
5436
|
}
|
|
5009
|
-
const container = document.querySelector(
|
|
5437
|
+
const container = document.querySelector('.comment-btn-container[data-section-id="' + sectionId + '"][data-element-id]');
|
|
5010
5438
|
if (container) {
|
|
5011
5439
|
container.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
5012
5440
|
}
|