@atlashub/smartstack-cli 3.6.0 → 3.8.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/package.json +3 -2
- package/templates/skills/business-analyse/SKILL.md +6 -12
- package/templates/skills/business-analyse/_architecture.md +1 -1
- package/templates/skills/business-analyse/html/ba-interactive.html +3058 -2252
- package/templates/skills/business-analyse/html/build-html.js +77 -0
- package/templates/skills/business-analyse/html/src/scripts/01-data-init.js +129 -0
- package/templates/skills/business-analyse/html/src/scripts/02-navigation.js +22 -0
- package/templates/skills/business-analyse/html/src/scripts/03-render-cadrage.js +208 -0
- package/templates/skills/business-analyse/html/src/scripts/04-render-modules.js +211 -0
- package/templates/skills/business-analyse/html/src/scripts/05-render-specs.js +542 -0
- package/templates/skills/business-analyse/html/src/scripts/06-render-consolidation.js +105 -0
- package/templates/skills/business-analyse/html/src/scripts/07-render-handoff.js +90 -0
- package/templates/skills/business-analyse/html/src/scripts/08-editing.js +45 -0
- package/templates/skills/business-analyse/html/src/scripts/09-export.js +65 -0
- package/templates/skills/business-analyse/html/src/scripts/10-comments.js +165 -0
- package/templates/skills/business-analyse/html/src/scripts/11-review-panel.js +139 -0
- package/templates/skills/business-analyse/html/src/styles/01-variables.css +38 -0
- package/templates/skills/business-analyse/html/src/styles/02-layout.css +101 -0
- package/templates/skills/business-analyse/html/src/styles/03-navigation.css +62 -0
- package/templates/skills/business-analyse/html/src/styles/04-cards.css +196 -0
- package/templates/skills/business-analyse/html/src/styles/05-modules.css +325 -0
- package/templates/skills/business-analyse/html/src/styles/06-wireframes.css +230 -0
- package/templates/skills/business-analyse/html/src/styles/07-comments.css +184 -0
- package/templates/skills/business-analyse/html/src/styles/08-review-panel.css +229 -0
- package/templates/skills/business-analyse/html/src/template.html +622 -0
- package/templates/skills/business-analyse/react/components.md +1 -1
- package/templates/skills/business-analyse/react/schema.md +1 -1
- package/templates/skills/business-analyse/references/html-data-mapping.md +2 -2
- package/templates/skills/business-analyse/schemas/feature-schema.json +1 -1
- package/templates/skills/business-analyse/steps/step-00-init.md +8 -1
- package/templates/skills/business-analyse/steps/step-03d-validate.md +1 -1
- package/templates/skills/business-analyse/steps/step-04-consolidation.md +21 -0
- package/templates/skills/business-analyse/steps/step-05a-handoff.md +273 -10
- package/templates/skills/business-analyse/steps/{step-05d-html.md → step-05b-deploy.md} +262 -63
- package/templates/skills/business-analyse/templates/tpl-launch-displays.md +1 -1
- package/templates/skills/business-analyse/templates/tpl-progress.md +1 -1
- package/templates/skills/ralph-loop/SKILL.md +3 -3
- package/templates/skills/ralph-loop/steps/step-00-init.md +77 -1
- package/templates/skills/business-analyse/steps/step-05b-mapping.md +0 -302
- package/templates/skills/business-analyse/steps/step-05c-deploy.md +0 -296
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
MODULE SPECIFICATION (per module)
|
|
3
|
+
============================================ */
|
|
4
|
+
function renderAllModuleSpecs() {
|
|
5
|
+
const container = document.getElementById('moduleSpecContainer');
|
|
6
|
+
container.innerHTML = data.modules.map(m => renderModuleSpecSection(m)).join('');
|
|
7
|
+
// Bind editable fields in module specs
|
|
8
|
+
container.querySelectorAll('.editable[data-module-field]').forEach(el => {
|
|
9
|
+
el.addEventListener('blur', function() {
|
|
10
|
+
const code = this.dataset.moduleCode;
|
|
11
|
+
const field = this.dataset.moduleField;
|
|
12
|
+
if (data.moduleSpecs[code]) {
|
|
13
|
+
data.moduleSpecs[code][field] = this.textContent.trim();
|
|
14
|
+
autoSave();
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
// Restore currently visible section after re-render
|
|
19
|
+
restoreCurrentSection();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function renderModuleSpecSection(mod) {
|
|
23
|
+
const code = mod.code;
|
|
24
|
+
const spec = data.moduleSpecs[code] || { useCases: [], businessRules: [], entities: [], permissions: [], notes: '' };
|
|
25
|
+
|
|
26
|
+
return `
|
|
27
|
+
<div class="section" id="module-spec-${code}" style="display:none;">
|
|
28
|
+
<h2 class="section-title">${mod.name}</h2>
|
|
29
|
+
<p class="section-subtitle">${mod.description || 'Specification detaillee de ce domaine fonctionnel.'}</p>
|
|
30
|
+
|
|
31
|
+
<div class="tab-bar">
|
|
32
|
+
<button class="tab-btn active" onclick="switchTab('${code}', 'uc')">Cas d'utilisation</button>
|
|
33
|
+
<button class="tab-btn" onclick="switchTab('${code}', 'br')">Regles metier</button>
|
|
34
|
+
<button class="tab-btn" onclick="switchTab('${code}', 'ent')">Donnees</button>
|
|
35
|
+
<button class="tab-btn" onclick="switchTab('${code}', 'perm')">Droits d'acces</button>
|
|
36
|
+
<button class="tab-btn" onclick="switchTab('${code}', 'mock')">Maquettes</button>
|
|
37
|
+
<button class="tab-btn" onclick="switchTab('${code}', 'notes')">Notes</button>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<!-- TAB: Cas d'utilisation -->
|
|
41
|
+
<div class="tab-panel active" id="tab-${code}-uc">
|
|
42
|
+
<p style="font-size:0.85rem;color:var(--text-muted);margin-bottom:1rem;">Decrivez ce que chaque type d'utilisateur peut faire dans ce domaine. Un cas d'utilisation = une action concrete.</p>
|
|
43
|
+
<div id="ucList-${code}" class="uc-list">
|
|
44
|
+
${spec.useCases.map((uc, i) => renderUseCase(code, uc, i)).join('')}
|
|
45
|
+
</div>
|
|
46
|
+
<button class="add-btn" onclick="toggleForm('addUcForm-${code}')">+ Ajouter un cas d'utilisation</button>
|
|
47
|
+
<div class="inline-form" id="addUcForm-${code}">
|
|
48
|
+
<div class="inline-form-title">Nouveau cas d'utilisation</div>
|
|
49
|
+
<div class="form-group">
|
|
50
|
+
<label class="form-label">Que fait l'utilisateur ? (exemple : Creer une commande)</label>
|
|
51
|
+
<input type="text" class="form-input" id="uc-name-${code}" placeholder="Action concrete de l'utilisateur">
|
|
52
|
+
</div>
|
|
53
|
+
<div class="form-group">
|
|
54
|
+
<label class="form-label">Qui realise cette action ? (profil utilisateur)</label>
|
|
55
|
+
<input type="text" class="form-input" id="uc-actor-${code}" placeholder="Exemple : Responsable de production">
|
|
56
|
+
</div>
|
|
57
|
+
<div class="form-group">
|
|
58
|
+
<label class="form-label">Deroulement normal, etape par etape (une par ligne)</label>
|
|
59
|
+
<textarea class="form-textarea" id="uc-steps-${code}" placeholder="1. L'utilisateur ouvre la page de creation 2. Il remplit les champs obligatoires 3. Il valide le formulaire 4. Le systeme enregistre et confirme"></textarea>
|
|
60
|
+
</div>
|
|
61
|
+
<div class="form-group">
|
|
62
|
+
<label class="form-label">Que se passe-t-il si quelque chose ne va pas ? (optionnel)</label>
|
|
63
|
+
<textarea class="form-textarea" id="uc-alt-${code}" placeholder="Exemple : Si les donnees sont incorrectes, le systeme affiche un message d'erreur" style="min-height:50px;"></textarea>
|
|
64
|
+
</div>
|
|
65
|
+
<div class="form-actions">
|
|
66
|
+
<button class="btn" onclick="toggleForm('addUcForm-${code}')">Annuler</button>
|
|
67
|
+
<button class="btn btn-primary" onclick="addUseCase('${code}')">Ajouter</button>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<!-- TAB: Regles metier -->
|
|
73
|
+
<div class="tab-panel" id="tab-${code}-br">
|
|
74
|
+
<p style="font-size:0.85rem;color:var(--text-muted);margin-bottom:1rem;">Les regles que le systeme doit respecter. Formulez-les sous forme de conditions : "Si... alors... sinon..."</p>
|
|
75
|
+
<div id="brList-${code}">
|
|
76
|
+
${spec.businessRules.map((br, i) => renderBusinessRule(code, br, i)).join('')}
|
|
77
|
+
</div>
|
|
78
|
+
<button class="add-btn" onclick="toggleForm('addBrForm-${code}')">+ Ajouter une regle metier</button>
|
|
79
|
+
<div class="inline-form" id="addBrForm-${code}">
|
|
80
|
+
<div class="inline-form-title">Nouvelle regle metier</div>
|
|
81
|
+
<div class="form-group">
|
|
82
|
+
<label class="form-label">Nom court de la regle</label>
|
|
83
|
+
<input type="text" class="form-input" id="br-name-${code}" placeholder="Exemple : Verification du budget disponible">
|
|
84
|
+
</div>
|
|
85
|
+
<div class="form-group">
|
|
86
|
+
<label class="form-label">Categorie</label>
|
|
87
|
+
<select class="form-select" id="br-cat-${code}">
|
|
88
|
+
<option value="validation">Verification (le systeme verifie que...)</option>
|
|
89
|
+
<option value="calculation">Calcul (le systeme calcule...)</option>
|
|
90
|
+
<option value="workflow">Processus (quand X se produit, alors...)</option>
|
|
91
|
+
<option value="security">Securite (seul... peut...)</option>
|
|
92
|
+
<option value="data">Donnees (les donnees doivent...)</option>
|
|
93
|
+
</select>
|
|
94
|
+
</div>
|
|
95
|
+
<div class="form-group">
|
|
96
|
+
<label class="form-label">Formulation de la regle (Si... alors... sinon...)</label>
|
|
97
|
+
<textarea class="form-textarea" id="br-statement-${code}" placeholder="Si le montant de la commande depasse le budget du client, alors la commande est refusee et un message d'erreur s'affiche"></textarea>
|
|
98
|
+
</div>
|
|
99
|
+
<div class="form-group">
|
|
100
|
+
<label class="form-label">Exemple concret (optionnel)</label>
|
|
101
|
+
<input type="text" class="form-input" id="br-example-${code}" placeholder="Exemple : Commande de 5000 CHF, budget de 2000 CHF -> refusee">
|
|
102
|
+
</div>
|
|
103
|
+
<div class="form-actions">
|
|
104
|
+
<button class="btn" onclick="toggleForm('addBrForm-${code}')">Annuler</button>
|
|
105
|
+
<button class="btn btn-primary" onclick="addBusinessRule('${code}')">Ajouter</button>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<!-- TAB: Donnees (Entites) -->
|
|
111
|
+
<div class="tab-panel" id="tab-${code}-ent">
|
|
112
|
+
<p style="font-size:0.85rem;color:var(--text-muted);margin-bottom:1rem;">Les types de donnees que ce domaine gere. Decrivez les informations importantes a enregistrer pour chaque type.</p>
|
|
113
|
+
<div id="entList-${code}">
|
|
114
|
+
${spec.entities.map((ent, i) => renderEntity(code, ent, i)).join('')}
|
|
115
|
+
</div>
|
|
116
|
+
<button class="add-btn" onclick="toggleForm('addEntForm-${code}')">+ Ajouter un type de donnees</button>
|
|
117
|
+
<div class="inline-form" id="addEntForm-${code}">
|
|
118
|
+
<div class="inline-form-title">Nouveau type de donnees</div>
|
|
119
|
+
<div class="form-group">
|
|
120
|
+
<label class="form-label">Nom (exemple : Commande, Client, Facture)</label>
|
|
121
|
+
<input type="text" class="form-input" id="ent-name-${code}" placeholder="Nom du type de donnees">
|
|
122
|
+
</div>
|
|
123
|
+
<div class="form-group">
|
|
124
|
+
<label class="form-label">Description : a quoi sert cette donnee ?</label>
|
|
125
|
+
<textarea class="form-textarea" id="ent-desc-${code}" placeholder="En une ou deux phrases, decrivez ce que represente cette donnee dans votre activite" style="min-height:50px;"></textarea>
|
|
126
|
+
</div>
|
|
127
|
+
<div class="form-group">
|
|
128
|
+
<label class="form-label">Informations a enregistrer (une par ligne : Nom - Description)</label>
|
|
129
|
+
<textarea class="form-textarea" id="ent-attrs-${code}" placeholder="Numero - Identifiant unique de la commande Date - Date de creation de la commande Montant total - Somme des lignes Statut - Brouillon, Envoyee, Validee, Refusee"></textarea>
|
|
130
|
+
</div>
|
|
131
|
+
<div class="form-group">
|
|
132
|
+
<label class="form-label">Relations avec d'autres donnees (optionnel, une par ligne)</label>
|
|
133
|
+
<textarea class="form-textarea" id="ent-rels-${code}" placeholder="Client - Chaque commande appartient a un client Ligne de commande - Une commande contient plusieurs lignes" style="min-height:50px;"></textarea>
|
|
134
|
+
</div>
|
|
135
|
+
<div class="form-actions">
|
|
136
|
+
<button class="btn" onclick="toggleForm('addEntForm-${code}')">Annuler</button>
|
|
137
|
+
<button class="btn btn-primary" onclick="addEntity('${code}')">Ajouter</button>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
<!-- TAB: Droits d'acces -->
|
|
143
|
+
<div class="tab-panel" id="tab-${code}-perm">
|
|
144
|
+
<p style="font-size:0.85rem;color:var(--text-muted);margin-bottom:1rem;">Definissez qui peut faire quoi dans ce domaine. Cochez les actions autorisees pour chaque profil.</p>
|
|
145
|
+
<div id="permGrid-${code}">
|
|
146
|
+
${renderPermissionGrid(code)}
|
|
147
|
+
</div>
|
|
148
|
+
<div style="display:flex;gap:0.75rem;margin-top:1rem;flex-wrap:wrap;">
|
|
149
|
+
<button class="add-btn" onclick="toggleForm('addRoleForm-${code}')" style="flex:1;min-width:200px;">+ Ajouter un role</button>
|
|
150
|
+
<button class="add-btn" onclick="toggleForm('addActionForm-${code}')" style="flex:1;min-width:200px;">+ Ajouter une action</button>
|
|
151
|
+
</div>
|
|
152
|
+
<div class="inline-form" id="addRoleForm-${code}">
|
|
153
|
+
<div class="inline-form-title">Nouveau role</div>
|
|
154
|
+
<div class="form-group">
|
|
155
|
+
<label class="form-label">Nom du role</label>
|
|
156
|
+
<input type="text" class="form-input" id="role-name-${code}" placeholder="Exemple : Superviseur, Auditeur...">
|
|
157
|
+
</div>
|
|
158
|
+
<div class="form-actions">
|
|
159
|
+
<button class="btn" onclick="toggleForm('addRoleForm-${code}')">Annuler</button>
|
|
160
|
+
<button class="btn btn-primary" onclick="addCustomRole('${code}')">Ajouter</button>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
<div class="inline-form" id="addActionForm-${code}">
|
|
164
|
+
<div class="inline-form-title">Nouvelle action</div>
|
|
165
|
+
<div class="form-group">
|
|
166
|
+
<label class="form-label">Nom de l'action</label>
|
|
167
|
+
<input type="text" class="form-input" id="action-name-${code}" placeholder="Exemple : Archiver, Imprimer, Approuver...">
|
|
168
|
+
</div>
|
|
169
|
+
<div class="form-actions">
|
|
170
|
+
<button class="btn" onclick="toggleForm('addActionForm-${code}')">Annuler</button>
|
|
171
|
+
<button class="btn btn-primary" onclick="addCustomAction('${code}')">Ajouter</button>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
<!-- TAB: Maquettes -->
|
|
177
|
+
<div class="tab-panel" id="tab-${code}-mock">
|
|
178
|
+
<p style="font-size:0.85rem;color:var(--text-muted);margin-bottom:1rem;">Maquettes validees lors de l'analyse. Ces wireframes montrent la structure exacte des ecrans de ce domaine.</p>
|
|
179
|
+
<div id="mockupContainer-${code}">
|
|
180
|
+
${renderModuleMockups(code)}
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
|
|
184
|
+
<!-- TAB: Notes -->
|
|
185
|
+
<div class="tab-panel" id="tab-${code}-notes">
|
|
186
|
+
<p style="font-size:0.85rem;color:var(--text-muted);margin-bottom:1rem;">Notes libres, questions en suspens, elements a clarifier pour ce domaine.</p>
|
|
187
|
+
<div class="card">
|
|
188
|
+
<div class="editable" contenteditable="true" data-module-code="${code}" data-module-field="notes" data-placeholder="Notez ici tout ce qui concerne ce domaine : questions, precisions, contraintes particulieres...">${spec.notes || ''}</div>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
</div>`;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function renderUseCase(code, uc, index) {
|
|
195
|
+
return `
|
|
196
|
+
<div class="uc-item">
|
|
197
|
+
<div class="uc-header">
|
|
198
|
+
<span class="uc-id">UC-${String(index + 1).padStart(3, '0')}</span>
|
|
199
|
+
<span class="uc-title">${uc.name}</span>
|
|
200
|
+
<div class="uc-actions">
|
|
201
|
+
<button class="btn btn-sm" onclick="removeUseCase('${code}',${index})">Supprimer</button>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
<div class="uc-actors"><div class="uc-actor">${uc.actor}</div></div>
|
|
205
|
+
${uc.steps ? `<div class="uc-detail-label">Deroulement</div><div class="uc-detail">${uc.steps.replace(/\n/g, '<br>')}</div>` : ''}
|
|
206
|
+
${uc.alternative ? `<div class="uc-detail-label">En cas de probleme</div><div class="uc-detail" style="color:var(--warning);">${uc.alternative}</div>` : ''}
|
|
207
|
+
<div style="padding:0.5rem 0.75rem;border-top:1px solid var(--border);background:var(--bg-input);border-radius:0 0 8px 8px;">
|
|
208
|
+
<label style="font-size:0.75rem;color:var(--text-muted);display:block;margin-bottom:0.25rem;">Commentaire / Feedback :</label>
|
|
209
|
+
<textarea class="form-textarea" placeholder="Ajouter un commentaire sur ce cas d'utilisation..."
|
|
210
|
+
onblur="updateSpecComment('${code}','uc',${index},this.value)"
|
|
211
|
+
style="min-height:45px;font-size:0.8rem;resize:vertical;">${getSpecComment(code, 'uc', index)}</textarea>
|
|
212
|
+
</div>
|
|
213
|
+
</div>`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function addUseCase(code) {
|
|
217
|
+
const name = document.getElementById('uc-name-' + code).value.trim();
|
|
218
|
+
if (!name) return;
|
|
219
|
+
if (!data.moduleSpecs[code]) data.moduleSpecs[code] = { useCases: [], businessRules: [], entities: [], permissions: [], notes: '' };
|
|
220
|
+
|
|
221
|
+
data.moduleSpecs[code].useCases.push({
|
|
222
|
+
name: name,
|
|
223
|
+
actor: document.getElementById('uc-actor-' + code).value.trim(),
|
|
224
|
+
steps: document.getElementById('uc-steps-' + code).value.trim(),
|
|
225
|
+
alternative: document.getElementById('uc-alt-' + code).value.trim()
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
renderAllModuleSpecs();
|
|
229
|
+
updateCounts();
|
|
230
|
+
autoSave();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function removeUseCase(code, index) {
|
|
234
|
+
data.moduleSpecs[code].useCases.splice(index, 1);
|
|
235
|
+
renderAllModuleSpecs();
|
|
236
|
+
updateCounts();
|
|
237
|
+
autoSave();
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function renderBusinessRule(code, br, index) {
|
|
241
|
+
const catColors = { validation: 'br-cat-validation', calculation: 'br-cat-calculation', workflow: 'br-cat-workflow', security: 'br-cat-security', data: 'br-cat-data' };
|
|
242
|
+
const catLabels = { validation: 'Verification', calculation: 'Calcul', workflow: 'Processus', security: 'Securite', data: 'Donnees' };
|
|
243
|
+
return `
|
|
244
|
+
<div style="margin-bottom:0.5rem;">
|
|
245
|
+
<div class="br-item" style="margin-bottom:0;border-radius:8px 8px 0 0;">
|
|
246
|
+
<span class="br-category ${catColors[br.category] || 'br-cat-validation'}">${catLabels[br.category] || br.category}</span>
|
|
247
|
+
<div class="br-text" style="flex:1;">
|
|
248
|
+
<div style="font-weight:600;color:var(--text-bright);margin-bottom:0.2rem;">${br.name}</div>
|
|
249
|
+
<div>${br.statement}</div>
|
|
250
|
+
${br.example ? `<div style="font-size:0.8rem;color:var(--text-muted);margin-top:0.25rem;font-style:italic;">Exemple : ${br.example}</div>` : ''}
|
|
251
|
+
</div>
|
|
252
|
+
<button class="btn btn-sm" onclick="removeBusinessRule('${code}',${index})" style="opacity:0.5;flex-shrink:0;">✕</button>
|
|
253
|
+
</div>
|
|
254
|
+
<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;">
|
|
255
|
+
<textarea class="form-textarea" placeholder="Commentaire sur cette regle..."
|
|
256
|
+
onblur="updateSpecComment('${code}','br',${index},this.value)"
|
|
257
|
+
style="min-height:35px;font-size:0.8rem;resize:vertical;">${getSpecComment(code, 'br', index)}</textarea>
|
|
258
|
+
</div>
|
|
259
|
+
</div>`;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function addBusinessRule(code) {
|
|
263
|
+
const name = document.getElementById('br-name-' + code).value.trim();
|
|
264
|
+
if (!name) return;
|
|
265
|
+
if (!data.moduleSpecs[code]) data.moduleSpecs[code] = { useCases: [], businessRules: [], entities: [], permissions: [], notes: '' };
|
|
266
|
+
|
|
267
|
+
data.moduleSpecs[code].businessRules.push({
|
|
268
|
+
name: name,
|
|
269
|
+
category: document.getElementById('br-cat-' + code).value,
|
|
270
|
+
statement: document.getElementById('br-statement-' + code).value.trim(),
|
|
271
|
+
example: document.getElementById('br-example-' + code).value.trim()
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
renderAllModuleSpecs();
|
|
275
|
+
autoSave();
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function removeBusinessRule(code, index) {
|
|
279
|
+
data.moduleSpecs[code].businessRules.splice(index, 1);
|
|
280
|
+
renderAllModuleSpecs();
|
|
281
|
+
autoSave();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function renderEntity(code, ent, index) {
|
|
285
|
+
return `
|
|
286
|
+
<div class="entity-block">
|
|
287
|
+
<div class="entity-header">
|
|
288
|
+
<div>
|
|
289
|
+
<div class="entity-name">${ent.name}</div>
|
|
290
|
+
<div class="entity-desc">${ent.description || ''}</div>
|
|
291
|
+
</div>
|
|
292
|
+
<button class="btn btn-sm" onclick="removeEntity('${code}',${index})" style="opacity:0.5;">Supprimer</button>
|
|
293
|
+
</div>
|
|
294
|
+
${(ent.attributes || []).length > 0 ? `
|
|
295
|
+
<table class="attr-table">
|
|
296
|
+
<thead><tr><th>Information</th><th>Description</th></tr></thead>
|
|
297
|
+
<tbody>
|
|
298
|
+
${ent.attributes.map(a => `<tr><td style="font-weight:500;color:var(--text-bright);">${a.name}</td><td>${a.description || ''}</td></tr>`).join('')}
|
|
299
|
+
</tbody>
|
|
300
|
+
</table>` : ''}
|
|
301
|
+
${(ent.relationships || []).length > 0 ? `
|
|
302
|
+
<div style="padding:0.5rem 0.75rem;font-size:0.8rem;color:var(--text-muted);border-top:1px solid var(--border);">
|
|
303
|
+
Relations : ${ent.relationships.map(r => `<span style="color:var(--accent);">${r}</span>`).join(', ')}
|
|
304
|
+
</div>` : ''}
|
|
305
|
+
<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;">
|
|
306
|
+
<label style="font-size:0.75rem;color:var(--text-muted);display:block;margin-bottom:0.25rem;">Commentaire :</label>
|
|
307
|
+
<textarea class="form-textarea" placeholder="Commentaire sur cette entite..."
|
|
308
|
+
onblur="updateSpecComment('${code}','ent',${index},this.value)"
|
|
309
|
+
style="min-height:35px;font-size:0.8rem;resize:vertical;">${getSpecComment(code, 'ent', index)}</textarea>
|
|
310
|
+
</div>
|
|
311
|
+
</div>`;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function addEntity(code) {
|
|
315
|
+
const name = document.getElementById('ent-name-' + code).value.trim();
|
|
316
|
+
if (!name) return;
|
|
317
|
+
if (!data.moduleSpecs[code]) data.moduleSpecs[code] = { useCases: [], businessRules: [], entities: [], permissions: [], notes: '' };
|
|
318
|
+
|
|
319
|
+
const attrs = document.getElementById('ent-attrs-' + code).value.split('\n').filter(l => l.trim()).map(l => {
|
|
320
|
+
const parts = l.split(' - ');
|
|
321
|
+
return { name: parts[0]?.trim() || l.trim(), description: parts.slice(1).join(' - ').trim() };
|
|
322
|
+
});
|
|
323
|
+
const rels = document.getElementById('ent-rels-' + code).value.split('\n').filter(l => l.trim());
|
|
324
|
+
|
|
325
|
+
data.moduleSpecs[code].entities.push({
|
|
326
|
+
name: name,
|
|
327
|
+
description: document.getElementById('ent-desc-' + code).value.trim(),
|
|
328
|
+
attributes: attrs,
|
|
329
|
+
relationships: rels
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
renderAllModuleSpecs();
|
|
333
|
+
autoSave();
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function removeEntity(code, index) {
|
|
337
|
+
data.moduleSpecs[code].entities.splice(index, 1);
|
|
338
|
+
renderAllModuleSpecs();
|
|
339
|
+
autoSave();
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function getSpecComment(code, type, index) {
|
|
343
|
+
const key = code + '.' + type + '.' + index;
|
|
344
|
+
return data.specComments[key] || '';
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function updateSpecComment(code, type, index, value) {
|
|
348
|
+
const key = code + '.' + type + '.' + index;
|
|
349
|
+
data.specComments[key] = value.trim();
|
|
350
|
+
autoSave();
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function getWireframeComment(code, screen) {
|
|
354
|
+
return (data.wireframeComments[code] || {})[screen] || '';
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function updateWireframeComment(code, screen, value) {
|
|
358
|
+
if (!data.wireframeComments[code]) data.wireframeComments[code] = {};
|
|
359
|
+
data.wireframeComments[code][screen] = value.trim();
|
|
360
|
+
autoSave();
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function renderModuleMockups(code) {
|
|
364
|
+
const wireframes = EMBEDDED_ARTIFACTS?.wireframes?.[code] || [];
|
|
365
|
+
|
|
366
|
+
if (wireframes.length === 0) {
|
|
367
|
+
return `
|
|
368
|
+
<div class="card" style="text-align:center;padding:2rem;color:var(--text-muted);">
|
|
369
|
+
<p>Aucune maquette disponible pour ce module.</p>
|
|
370
|
+
<p style="font-size:0.85rem;margin-top:0.5rem;">Les maquettes seront generees lors de la specification detaillee.</p>
|
|
371
|
+
</div>`;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return wireframes.map((wf, i) => `
|
|
375
|
+
<div class="mockup-frame" style="${i > 0 ? 'margin-top:1.5rem;' : ''}">
|
|
376
|
+
<div class="mockup-toolbar">
|
|
377
|
+
<div class="mockup-dot mockup-dot-red"></div>
|
|
378
|
+
<div class="mockup-dot mockup-dot-yellow"></div>
|
|
379
|
+
<div class="mockup-dot mockup-dot-green"></div>
|
|
380
|
+
<span class="mockup-title">${wf.screen || wf.section}</span>
|
|
381
|
+
</div>
|
|
382
|
+
<div class="mockup-content">
|
|
383
|
+
${wf.format === 'ascii'
|
|
384
|
+
? `<pre class="ascii-wireframe">${wf.content || ''}</pre>`
|
|
385
|
+
: `<div class="svg-wireframe">${wf.content || ''}</div>`}
|
|
386
|
+
</div>
|
|
387
|
+
${wf.description ? `
|
|
388
|
+
<div class="wireframe-description">
|
|
389
|
+
<strong>Description:</strong> ${wf.description}
|
|
390
|
+
</div>` : ''}
|
|
391
|
+
${(wf.elements || []).length > 0 ? `
|
|
392
|
+
<div class="wireframe-metadata">
|
|
393
|
+
<div><strong>Elements:</strong> ${wf.elements.join(', ')}</div>
|
|
394
|
+
</div>` : ''}
|
|
395
|
+
${(wf.componentMapping || []).length > 0 ? `
|
|
396
|
+
<details class="wireframe-details">
|
|
397
|
+
<summary>Mapping composants SmartStack</summary>
|
|
398
|
+
<table class="mapping-table">
|
|
399
|
+
<thead><tr><th>Element maquette</th><th>Composant React</th></tr></thead>
|
|
400
|
+
<tbody>
|
|
401
|
+
${wf.componentMapping.map(m =>
|
|
402
|
+
`<tr><td>${m.wireframeElement}</td><td><code>${m.reactComponent}</code></td></tr>`
|
|
403
|
+
).join('')}
|
|
404
|
+
</tbody>
|
|
405
|
+
</table>
|
|
406
|
+
</details>` : ''}
|
|
407
|
+
<div class="wireframe-comment">
|
|
408
|
+
<label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:0.3rem;">Commentaire / Feedback :</label>
|
|
409
|
+
<textarea class="form-textarea"
|
|
410
|
+
data-module="${code}"
|
|
411
|
+
data-screen="${wf.screen}"
|
|
412
|
+
placeholder="Ajouter un commentaire sur cette maquette (ex: deplacer ce bouton, ajouter une colonne...)"
|
|
413
|
+
onblur="updateWireframeComment('${code}', '${wf.screen}', this.value)"
|
|
414
|
+
style="min-height:60px;font-size:0.85rem;resize:vertical;"
|
|
415
|
+
>${getWireframeComment(code, wf.screen)}</textarea>
|
|
416
|
+
</div>
|
|
417
|
+
</div>
|
|
418
|
+
`).join('');
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function getPermRoles() {
|
|
422
|
+
const baseRoles = data.cadrage.stakeholders.length > 0
|
|
423
|
+
? data.cadrage.stakeholders.map(s => s.role)
|
|
424
|
+
: ['Administrateur', 'Responsable', 'Contributeur', 'Lecteur'];
|
|
425
|
+
return [...baseRoles, ...(data.customRoles || [])];
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function getPermActions() {
|
|
429
|
+
const baseActions = ['Consulter', 'Creer', 'Modifier', 'Supprimer', 'Valider', 'Exporter'];
|
|
430
|
+
return [...baseActions, ...(data.customActions || [])];
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function renderPermissionGrid(code) {
|
|
434
|
+
const roles = getPermRoles();
|
|
435
|
+
const actions = getPermActions();
|
|
436
|
+
const baseRolesCount = data.cadrage.stakeholders.length > 0
|
|
437
|
+
? data.cadrage.stakeholders.length
|
|
438
|
+
: 4;
|
|
439
|
+
const baseActionsCount = 6;
|
|
440
|
+
|
|
441
|
+
const perms = data.moduleSpecs[code]?.permissions || [];
|
|
442
|
+
|
|
443
|
+
return `
|
|
444
|
+
<table class="mock-table" style="background:var(--bg-card);border-radius:8px;overflow:hidden;">
|
|
445
|
+
<thead><tr>
|
|
446
|
+
<th>Profil</th>
|
|
447
|
+
${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('')}
|
|
448
|
+
</tr></thead>
|
|
449
|
+
<tbody>
|
|
450
|
+
${roles.map((role, ri) => `
|
|
451
|
+
<tr>
|
|
452
|
+
<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 role">✕</span>' : ''}</td>
|
|
453
|
+
${actions.map(action => {
|
|
454
|
+
const key = role + '|' + action;
|
|
455
|
+
const checked = perms.includes(key);
|
|
456
|
+
return `<td style="text-align:center;"><input type="checkbox" ${checked ? 'checked' : ''} onchange="togglePermission('${code}','${key}',this.checked)" style="cursor:pointer;width:16px;height:16px;"></td>`;
|
|
457
|
+
}).join('')}
|
|
458
|
+
</tr>
|
|
459
|
+
`).join('')}
|
|
460
|
+
</tbody>
|
|
461
|
+
</table>`;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function togglePermission(code, key, checked) {
|
|
465
|
+
if (!data.moduleSpecs[code]) data.moduleSpecs[code] = { useCases: [], businessRules: [], entities: [], permissions: [], notes: '' };
|
|
466
|
+
if (!data.moduleSpecs[code].permissions) data.moduleSpecs[code].permissions = [];
|
|
467
|
+
if (checked) {
|
|
468
|
+
if (!data.moduleSpecs[code].permissions.includes(key)) data.moduleSpecs[code].permissions.push(key);
|
|
469
|
+
} else {
|
|
470
|
+
data.moduleSpecs[code].permissions = data.moduleSpecs[code].permissions.filter(p => p !== key);
|
|
471
|
+
}
|
|
472
|
+
autoSave();
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function addCustomRole(code) {
|
|
476
|
+
const input = document.getElementById('role-name-' + code);
|
|
477
|
+
const name = input.value.trim();
|
|
478
|
+
if (!name) return;
|
|
479
|
+
if (!data.customRoles) data.customRoles = [];
|
|
480
|
+
if (!data.customRoles.includes(name) && !getPermRoles().includes(name)) {
|
|
481
|
+
data.customRoles.push(name);
|
|
482
|
+
}
|
|
483
|
+
input.value = '';
|
|
484
|
+
toggleForm('addRoleForm-' + code);
|
|
485
|
+
refreshAllPermGrids();
|
|
486
|
+
autoSave();
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function addCustomAction(code) {
|
|
490
|
+
const input = document.getElementById('action-name-' + code);
|
|
491
|
+
const name = input.value.trim();
|
|
492
|
+
if (!name) return;
|
|
493
|
+
if (!data.customActions) data.customActions = [];
|
|
494
|
+
if (!data.customActions.includes(name) && !getPermActions().includes(name)) {
|
|
495
|
+
data.customActions.push(name);
|
|
496
|
+
}
|
|
497
|
+
input.value = '';
|
|
498
|
+
toggleForm('addActionForm-' + code);
|
|
499
|
+
refreshAllPermGrids();
|
|
500
|
+
autoSave();
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function removeCustomRole(code, role) {
|
|
504
|
+
data.customRoles = (data.customRoles || []).filter(r => r !== role);
|
|
505
|
+
data.modules.forEach(m => {
|
|
506
|
+
if (data.moduleSpecs[m.code]?.permissions) {
|
|
507
|
+
data.moduleSpecs[m.code].permissions = data.moduleSpecs[m.code].permissions.filter(p => !p.startsWith(role + '|'));
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
refreshAllPermGrids();
|
|
511
|
+
autoSave();
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function removeCustomAction(code, action) {
|
|
515
|
+
data.customActions = (data.customActions || []).filter(a => a !== action);
|
|
516
|
+
data.modules.forEach(m => {
|
|
517
|
+
if (data.moduleSpecs[m.code]?.permissions) {
|
|
518
|
+
data.moduleSpecs[m.code].permissions = data.moduleSpecs[m.code].permissions.filter(p => !p.endsWith('|' + action));
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
refreshAllPermGrids();
|
|
522
|
+
autoSave();
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function refreshAllPermGrids() {
|
|
526
|
+
data.modules.forEach(m => {
|
|
527
|
+
const container = document.getElementById('permGrid-' + m.code);
|
|
528
|
+
if (container) container.innerHTML = renderPermissionGrid(m.code);
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function switchTab(code, tabId) {
|
|
533
|
+
const section = document.getElementById('module-spec-' + code);
|
|
534
|
+
if (!section) return;
|
|
535
|
+
section.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
|
|
536
|
+
section.querySelectorAll('.tab-panel').forEach(panel => panel.classList.remove('active'));
|
|
537
|
+
const targetPanel = document.getElementById('tab-' + code + '-' + tabId);
|
|
538
|
+
if (targetPanel) targetPanel.classList.add('active');
|
|
539
|
+
const buttons = section.querySelectorAll('.tab-btn');
|
|
540
|
+
const tabIndex = { uc: 0, br: 1, ent: 2, perm: 3, mock: 4, notes: 5 }[tabId];
|
|
541
|
+
if (buttons[tabIndex]) buttons[tabIndex].classList.add('active');
|
|
542
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
CONSOLIDATION
|
|
3
|
+
============================================ */
|
|
4
|
+
function renderConsolidation() {
|
|
5
|
+
renderConsolInteractions();
|
|
6
|
+
renderConsolPermissions();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function renderConsolInteractions() {
|
|
10
|
+
const container = document.getElementById('consolInteractions');
|
|
11
|
+
if (!container || data.dependencies.length === 0) return;
|
|
12
|
+
|
|
13
|
+
container.innerHTML = data.dependencies.map(d => {
|
|
14
|
+
const fromName = data.modules.find(m => m.code === d.from)?.name || d.from;
|
|
15
|
+
const toName = data.modules.find(m => m.code === d.to)?.name || d.to;
|
|
16
|
+
return `
|
|
17
|
+
<div class="interaction-item">
|
|
18
|
+
<span style="font-weight:600;color:var(--text-bright);">${fromName}</span>
|
|
19
|
+
<span class="interaction-arrow">→</span>
|
|
20
|
+
<span style="font-weight:600;color:var(--text-bright);">${toName}</span>
|
|
21
|
+
<span class="interaction-type">Dependance</span>
|
|
22
|
+
<span style="flex:1;font-size:0.8rem;color:var(--text-muted);">${d.description || ''}</span>
|
|
23
|
+
</div>`;
|
|
24
|
+
}).join('');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function renderConsolPermissions() {
|
|
28
|
+
const container = document.getElementById('consolPermissions');
|
|
29
|
+
if (!container || data.modules.length === 0) return;
|
|
30
|
+
|
|
31
|
+
const roles = getPermRoles();
|
|
32
|
+
|
|
33
|
+
let html = '<table class="mock-table" style="background:var(--bg-card);border-radius:8px;overflow:hidden;">';
|
|
34
|
+
html += '<thead><tr><th>Profil</th>';
|
|
35
|
+
data.modules.forEach(m => { html += `<th style="text-align:center;">${m.name || m.code}</th>`; });
|
|
36
|
+
html += '</tr></thead><tbody>';
|
|
37
|
+
|
|
38
|
+
roles.forEach(role => {
|
|
39
|
+
html += `<tr><td style="font-weight:500;color:var(--text-bright);">${role}</td>`;
|
|
40
|
+
data.modules.forEach(m => {
|
|
41
|
+
const perms = (data.moduleSpecs[m.code]?.permissions || []).filter(p => p.startsWith(role + '|'));
|
|
42
|
+
const count = perms.length;
|
|
43
|
+
const color = count > 4 ? 'var(--success)' : count > 2 ? 'var(--warning)' : count > 0 ? 'var(--text-muted)' : 'var(--border)';
|
|
44
|
+
html += `<td style="text-align:center;color:${color};font-weight:600;">${count > 0 ? count + ' droits' : 'Aucun'}</td>`;
|
|
45
|
+
});
|
|
46
|
+
html += '</tr>';
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
html += '</tbody></table>';
|
|
50
|
+
container.innerHTML = html;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* ============================================
|
|
54
|
+
E2E FLOWS
|
|
55
|
+
============================================ */
|
|
56
|
+
function addE2EFlow() {
|
|
57
|
+
const name = document.getElementById('flow-name').value.trim();
|
|
58
|
+
if (!name) return;
|
|
59
|
+
|
|
60
|
+
const steps = document.getElementById('flow-steps').value.split('\n').filter(l => l.trim()).map(l => {
|
|
61
|
+
const parts = l.split(' - ');
|
|
62
|
+
return { module: parts[0]?.trim() || '', action: parts.slice(1).join(' - ').trim() || l.trim() };
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
data.consolidation.e2eFlows.push({
|
|
66
|
+
name: name,
|
|
67
|
+
steps: steps,
|
|
68
|
+
actors: document.getElementById('flow-actors').value.trim()
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
renderE2EFlows();
|
|
72
|
+
toggleForm('addFlowForm');
|
|
73
|
+
clearForm('addFlowForm');
|
|
74
|
+
autoSave();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function removeE2EFlow(index) {
|
|
78
|
+
data.consolidation.e2eFlows.splice(index, 1);
|
|
79
|
+
renderE2EFlows();
|
|
80
|
+
autoSave();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function renderE2EFlows() {
|
|
84
|
+
const container = document.getElementById('e2eFlowsList');
|
|
85
|
+
if (!container) return;
|
|
86
|
+
|
|
87
|
+
container.innerHTML = data.consolidation.e2eFlows.map((flow, fi) => `
|
|
88
|
+
<div class="card" style="margin-bottom:1rem;">
|
|
89
|
+
<div class="card-header">
|
|
90
|
+
<span class="card-title">${flow.name}</span>
|
|
91
|
+
<button class="btn btn-sm" onclick="removeE2EFlow(${fi})" style="opacity:0.5;">Supprimer</button>
|
|
92
|
+
</div>
|
|
93
|
+
${flow.actors ? `<div style="font-size:0.8rem;color:var(--text-muted);margin-bottom:0.5rem;">Intervenants : ${flow.actors}</div>` : ''}
|
|
94
|
+
<div class="e2e-flow">
|
|
95
|
+
${flow.steps.map((s, i) => `
|
|
96
|
+
<div class="e2e-step">
|
|
97
|
+
<div class="e2e-step-module">${s.module}</div>
|
|
98
|
+
<div class="e2e-step-action">${s.action}</div>
|
|
99
|
+
</div>
|
|
100
|
+
${i < flow.steps.length - 1 ? '<div class="process-arrow">→</div>' : ''}
|
|
101
|
+
`).join('')}
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
`).join('');
|
|
105
|
+
}
|