@atlashub/smartstack-cli 4.61.0 → 4.63.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlashub/smartstack-cli",
3
- "version": "4.61.0",
3
+ "version": "4.63.0",
4
4
  "description": "SmartStack Claude Code automation toolkit - GitFlow, EF Core migrations, prompts and more",
5
5
  "author": {
6
6
  "name": "SmartStack",
@@ -1367,15 +1367,6 @@ body {
1367
1367
  }
1368
1368
  .mock-kpi-value { font-size: 1.3rem; font-weight: 700; color: var(--text-bright); }
1369
1369
  .mock-kpi-label { font-size: 0.7rem; color: var(--text-muted); margin-top: 0.2rem; }
1370
- .mock-chart-placeholder {
1371
- height: 150px;
1372
- background: linear-gradient(135deg, rgba(99,102,241,0.05), rgba(6,182,212,0.05));
1373
- border: 1px dashed var(--border);
1374
- border-radius: 8px;
1375
- display: flex; align-items: center; justify-content: center;
1376
- color: var(--text-muted); font-size: 0.85rem;
1377
- }
1378
-
1379
1370
  @media (max-width: 768px) {
1380
1371
  .mock-kpi-grid { grid-template-columns: repeat(2, 1fr); }
1381
1372
  }
@@ -1949,6 +1940,90 @@ body {
1949
1940
  padding: 0.15rem 0.5rem;
1950
1941
  border-radius: 4px;
1951
1942
  }
1943
+
1944
+ /* ---- Mock Charts (SVG previews) ---- */
1945
+ .mock-chart-container {
1946
+ margin: 0.75rem 0;
1947
+ border: 1px solid var(--border);
1948
+ border-radius: 8px;
1949
+ overflow: hidden;
1950
+ }
1951
+
1952
+ .mock-chart-header {
1953
+ padding: 0.5rem 0.75rem;
1954
+ font-size: 0.85rem;
1955
+ font-weight: 500;
1956
+ color: var(--text-bright);
1957
+ background: var(--bg-hover);
1958
+ border-bottom: 1px solid var(--border);
1959
+ }
1960
+
1961
+ .mock-chart-body {
1962
+ padding: 1rem;
1963
+ }
1964
+
1965
+ .mock-pie-svg {
1966
+ width: 120px;
1967
+ height: 120px;
1968
+ flex-shrink: 0;
1969
+ }
1970
+
1971
+ .mock-bar-svg,
1972
+ .mock-line-svg {
1973
+ width: 100%;
1974
+ height: 140px;
1975
+ }
1976
+
1977
+ .mock-chart-legend {
1978
+ font-size: 0.8rem;
1979
+ color: var(--text);
1980
+ display: flex;
1981
+ flex-direction: column;
1982
+ gap: 0.4rem;
1983
+ }
1984
+
1985
+ .mock-legend-dot {
1986
+ display: inline-block;
1987
+ width: 10px;
1988
+ height: 10px;
1989
+ border-radius: 2px;
1990
+ margin-right: 0.4rem;
1991
+ vertical-align: middle;
1992
+ }
1993
+
1994
+ /* ---- Form Tab Switching ---- */
1995
+ .mock-form-tab-bar {
1996
+ display: flex;
1997
+ gap: 0;
1998
+ border-bottom: 1px solid var(--border);
1999
+ margin-bottom: 1.5rem;
2000
+ }
2001
+
2002
+ .mock-form-tab {
2003
+ padding: 0.5rem 1rem;
2004
+ font-size: 0.85rem;
2005
+ cursor: pointer;
2006
+ border-bottom: 2px solid transparent;
2007
+ color: var(--text-muted);
2008
+ transition: all var(--transition-fast);
2009
+ }
2010
+
2011
+ .mock-form-tab:hover {
2012
+ color: var(--text);
2013
+ }
2014
+
2015
+ .mock-form-tab.active {
2016
+ border-bottom-color: var(--primary);
2017
+ color: var(--primary-light);
2018
+ }
2019
+
2020
+ .mock-form-tab-content {
2021
+ display: none;
2022
+ }
2023
+
2024
+ .mock-form-tab-content.active {
2025
+ display: block;
2026
+ }
1952
2027
 
1953
2028
  </style>
1954
2029
  </head>
@@ -4078,16 +4153,12 @@ function renderModuleMockups(code) {
4078
4153
  return typeof s === 'string' ? { code: s, name: s, resources: [] } : s;
4079
4154
  }) : [];
4080
4155
 
4081
- // Priority 1: HTML mockups from screens[] specs
4156
+ // Priority 1: HTML mockups from screens[] specs (wireframes NOT shown when screens exist)
4082
4157
  if (screens.length > 0) {
4083
4158
  var html = '';
4084
4159
  if (typeof renderScreenMockups === 'function') {
4085
4160
  html = renderScreenMockups(code);
4086
4161
  }
4087
- if (wireframes.length > 0) {
4088
- html += '<h3 style="color:var(--text-bright);font-size:1rem;margin:2rem 0 1rem;">Wireframes</h3>';
4089
- html += wireframes.map(function(wf, i) { return renderWireframeMockup(code, wf, i); }).join('');
4090
- }
4091
4162
  return html;
4092
4163
  }
4093
4164
 
@@ -4363,6 +4434,8 @@ function renderE2EFlows() {
4363
4434
  Generates realistic HTML mockups from screens[] specs
4364
4435
  ============================================ */
4365
4436
 
4437
+ var _formTabCounter = 0;
4438
+
4366
4439
  function renderScreenMockups(code) {
4367
4440
  var spec = data.moduleSpecs[code] || {};
4368
4441
  var screens = spec.screens || [];
@@ -4522,6 +4595,7 @@ function renderSmartFormMockup(res) {
4522
4595
  }
4523
4596
  if (tabs.length === 0) return '<div style="padding:2rem;text-align:center;color:var(--text-muted);">Formulaire sans champs définis</div>';
4524
4597
 
4598
+ var mockupId = 'form-tabs-' + (++_formTabCounter);
4525
4599
  var html = '';
4526
4600
 
4527
4601
  // Header
@@ -4534,37 +4608,51 @@ function renderSmartFormMockup(res) {
4534
4608
  });
4535
4609
  html += '</div></div>';
4536
4610
 
4537
- // Tabs
4611
+ // Tabs (clickable when more than 1)
4538
4612
  if (tabs.length > 1) {
4539
- html += '<div style="display:flex;gap:0;border-bottom:1px solid var(--border);margin-bottom:1.5rem;">';
4613
+ html += '<div class="mock-form-tab-bar" data-mockup="' + mockupId + '">';
4540
4614
  tabs.forEach(function(tab, i) {
4541
- 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>';
4615
+ html += '<span class="mock-form-tab' + (i === 0 ? ' active' : '') + '"';
4616
+ html += ' onclick="switchFormTab(\'' + mockupId + '\', ' + i + ')"';
4617
+ html += '>' + escapeHtml(tab.label) + '</span>';
4542
4618
  });
4543
4619
  html += '</div>';
4544
4620
  }
4545
4621
 
4546
- // Fields (first tab only)
4547
- var fields = tabs[0].fields || [];
4548
- var rows = [];
4549
- for (var i = 0; i < fields.length; i += 2) {
4550
- rows.push(fields.slice(i, i + 2));
4551
- }
4622
+ // Tab contents (all rendered, only first visible)
4623
+ tabs.forEach(function(tab, ti) {
4624
+ var isOnly = tabs.length <= 1;
4625
+ html += '<div class="mock-form-tab-content' + (ti === 0 || isOnly ? ' active' : '') + '"';
4626
+ html += ' data-mockup="' + mockupId + '" data-tab="' + ti + '">';
4552
4627
 
4553
- rows.forEach(function(row) {
4554
- if (row.length === 1 && row[0].type === 'subtable') {
4555
- html += renderSubtableMockup(row[0]);
4556
- } else {
4557
- html += '<div class="mock-form-row">';
4558
- row.forEach(function(field) {
4559
- html += '<div class="mock-form-group">';
4560
- html += '<label class="mock-label">' + escapeHtml(field.label || field.field);
4561
- if (field.required) html += ' <span style="color:var(--error);">*</span>';
4562
- html += '</label>';
4563
- html += renderFormFieldMockup(field);
4628
+ var fields = tab.fields || [];
4629
+ var rows = [];
4630
+ for (var i = 0; i < fields.length; i += 2) {
4631
+ rows.push(fields.slice(i, i + 2));
4632
+ }
4633
+
4634
+ rows.forEach(function(row) {
4635
+ if (row.length === 1 && row[0].type === 'subtable') {
4636
+ html += renderSubtableMockup(row[0]);
4637
+ } else {
4638
+ html += '<div class="mock-form-row">';
4639
+ row.forEach(function(field) {
4640
+ html += '<div class="mock-form-group">';
4641
+ html += '<label class="mock-label">' + escapeHtml(field.label || field.field);
4642
+ if (field.required) html += ' <span style="color:var(--error);">*</span>';
4643
+ html += '</label>';
4644
+ html += renderFormFieldMockup(field);
4645
+ html += '</div>';
4646
+ });
4564
4647
  html += '</div>';
4565
- });
4566
- html += '</div>';
4648
+ }
4649
+ });
4650
+
4651
+ if (fields.length === 0) {
4652
+ html += '<div style="padding:2rem;text-align:center;color:var(--text-muted);font-style:italic;">Contenu de l\'onglet &laquo; ' + escapeHtml(tab.label) + ' &raquo;</div>';
4567
4653
  }
4654
+
4655
+ html += '</div>';
4568
4656
  });
4569
4657
 
4570
4658
  return html;
@@ -4668,10 +4756,10 @@ function renderSmartDashboardMockup(res) {
4668
4756
  });
4669
4757
  html += '</div>';
4670
4758
 
4671
- // Chart placeholders
4672
- var charts = res.charts || [{ label: 'Graphique' }];
4759
+ // Charts (SVG mock previews)
4760
+ var charts = res.charts || [{ label: 'Graphique', type: 'bar' }];
4673
4761
  charts.forEach(function(chart) {
4674
- html += '<div class="mock-chart-placeholder">' + escapeHtml(chart.label || chart.type || 'Graphique') + '</div>';
4762
+ html += renderMockChart(chart);
4675
4763
  });
4676
4764
 
4677
4765
  return html;
@@ -4712,6 +4800,96 @@ function renderSmartFilterMockup(res) {
4712
4800
  return html;
4713
4801
  }
4714
4802
 
4803
+ /* ---------- Mock Charts ---------- */
4804
+ function renderMockChart(chart) {
4805
+ var type = (chart.type || '').toLowerCase();
4806
+ var label = chart.label || chart.type || 'Graphique';
4807
+ var html = '<div class="mock-chart-container">';
4808
+ html += '<div class="mock-chart-header">' + escapeHtml(label) + '</div>';
4809
+ html += '<div class="mock-chart-body">';
4810
+ if (type === 'pie' || type === 'donut') {
4811
+ html += renderMockPieChart();
4812
+ } else if (type === 'line' || type === 'area') {
4813
+ html += renderMockLineChart();
4814
+ } else {
4815
+ html += renderMockBarChart();
4816
+ }
4817
+ html += '</div></div>';
4818
+ return html;
4819
+ }
4820
+
4821
+ function renderMockPieChart() {
4822
+ var html = '<div style="display:flex;align-items:center;gap:1.5rem;">';
4823
+ html += '<svg viewBox="0 0 42 42" class="mock-pie-svg">';
4824
+ html += '<circle cx="21" cy="21" r="15.9" fill="transparent" stroke="var(--primary)" stroke-width="8" stroke-dasharray="35 65" stroke-dashoffset="25"/>';
4825
+ html += '<circle cx="21" cy="21" r="15.9" fill="transparent" stroke="var(--success)" stroke-width="8" stroke-dasharray="25 75" stroke-dashoffset="-10"/>';
4826
+ html += '<circle cx="21" cy="21" r="15.9" fill="transparent" stroke="var(--warning)" stroke-width="8" stroke-dasharray="22 78" stroke-dashoffset="-35"/>';
4827
+ html += '<circle cx="21" cy="21" r="15.9" fill="transparent" stroke="var(--error)" stroke-width="8" stroke-dasharray="18 82" stroke-dashoffset="-57"/>';
4828
+ html += '</svg>';
4829
+ html += '<div class="mock-chart-legend">';
4830
+ html += '<div><span class="mock-legend-dot" style="background:var(--primary);"></span> Cat\u00e9gorie A (35%)</div>';
4831
+ html += '<div><span class="mock-legend-dot" style="background:var(--success);"></span> Cat\u00e9gorie B (25%)</div>';
4832
+ html += '<div><span class="mock-legend-dot" style="background:var(--warning);"></span> Cat\u00e9gorie C (22%)</div>';
4833
+ html += '<div><span class="mock-legend-dot" style="background:var(--error);"></span> Cat\u00e9gorie D (18%)</div>';
4834
+ html += '</div></div>';
4835
+ return html;
4836
+ }
4837
+
4838
+ function renderMockBarChart() {
4839
+ var bars = [85, 60, 45, 70, 55, 90];
4840
+ var labels = ['Jan', 'F\u00e9v', 'Mar', 'Avr', 'Mai', 'Jun'];
4841
+ var html = '<svg viewBox="0 0 300 140" class="mock-bar-svg">';
4842
+ html += '<line x1="30" y1="10" x2="30" y2="120" stroke="var(--border)" stroke-width="1"/>';
4843
+ html += '<line x1="30" y1="120" x2="290" y2="120" stroke="var(--border)" stroke-width="1"/>';
4844
+ html += '<line x1="30" y1="40" x2="290" y2="40" stroke="var(--border)" stroke-width="0.5" stroke-dasharray="4"/>';
4845
+ html += '<line x1="30" y1="80" x2="290" y2="80" stroke="var(--border)" stroke-width="0.5" stroke-dasharray="4"/>';
4846
+ var bw = 30, gap = 13;
4847
+ bars.forEach(function(h, i) {
4848
+ var x = 35 + i * (bw + gap);
4849
+ var y = 120 - h * 1.1;
4850
+ html += '<rect x="' + x + '" y="' + y + '" width="' + bw + '" height="' + (h * 1.1) + '" fill="var(--primary)" rx="3" opacity="' + (0.6 + i * 0.08) + '"/>';
4851
+ html += '<text x="' + (x + bw / 2) + '" y="134" text-anchor="middle" fill="var(--text-muted)" font-size="9">' + labels[i] + '</text>';
4852
+ });
4853
+ html += '</svg>';
4854
+ return html;
4855
+ }
4856
+
4857
+ function renderMockLineChart() {
4858
+ var points = '40,90 90,65 140,75 190,40 240,50 280,25';
4859
+ var labels = ['Jan', 'F\u00e9v', 'Mar', 'Avr', 'Mai', 'Jun'];
4860
+ var xs = [40, 90, 140, 190, 240, 280];
4861
+ var html = '<svg viewBox="0 0 300 140" class="mock-line-svg">';
4862
+ html += '<line x1="30" y1="10" x2="30" y2="120" stroke="var(--border)" stroke-width="1"/>';
4863
+ html += '<line x1="30" y1="120" x2="290" y2="120" stroke="var(--border)" stroke-width="1"/>';
4864
+ html += '<line x1="30" y1="40" x2="290" y2="40" stroke="var(--border)" stroke-width="0.5" stroke-dasharray="4"/>';
4865
+ html += '<line x1="30" y1="80" x2="290" y2="80" stroke="var(--border)" stroke-width="0.5" stroke-dasharray="4"/>';
4866
+ html += '<polygon fill="var(--primary)" opacity="0.1" points="' + points + ' 280,120 40,120"/>';
4867
+ html += '<polyline fill="none" stroke="var(--primary)" stroke-width="2.5" points="' + points + '" stroke-linecap="round" stroke-linejoin="round"/>';
4868
+ points.split(' ').forEach(function(p) {
4869
+ var xy = p.split(',');
4870
+ html += '<circle cx="' + xy[0] + '" cy="' + xy[1] + '" r="3" fill="var(--primary)" stroke="var(--bg-card)" stroke-width="1.5"/>';
4871
+ });
4872
+ xs.forEach(function(x, i) {
4873
+ html += '<text x="' + x + '" y="134" text-anchor="middle" fill="var(--text-muted)" font-size="9">' + labels[i] + '</text>';
4874
+ });
4875
+ html += '</svg>';
4876
+ return html;
4877
+ }
4878
+
4879
+ /* ---------- Form Tab Switching ---------- */
4880
+ function switchFormTab(mockupId, tabIndex) {
4881
+ document.querySelectorAll('.mock-form-tab-bar[data-mockup="' + mockupId + '"] .mock-form-tab').forEach(function(t) {
4882
+ t.classList.remove('active');
4883
+ });
4884
+ var allTabs = document.querySelectorAll('.mock-form-tab-bar[data-mockup="' + mockupId + '"] .mock-form-tab');
4885
+ if (allTabs[tabIndex]) allTabs[tabIndex].classList.add('active');
4886
+ document.querySelectorAll('.mock-form-tab-content[data-mockup="' + mockupId + '"]').forEach(function(el) {
4887
+ el.classList.remove('active');
4888
+ });
4889
+ var target = document.querySelector('.mock-form-tab-content[data-mockup="' + mockupId + '"][data-tab="' + tabIndex + '"]');
4890
+ if (target) target.classList.add('active');
4891
+ }
4892
+
4715
4893
  /* ---------- Helpers ---------- */
4716
4894
  function formatActionLabel(action) {
4717
4895
  var labels = {
@@ -721,16 +721,12 @@ function renderModuleMockups(code) {
721
721
  return typeof s === 'string' ? { code: s, name: s, resources: [] } : s;
722
722
  }) : [];
723
723
 
724
- // Priority 1: HTML mockups from screens[] specs
724
+ // Priority 1: HTML mockups from screens[] specs (wireframes NOT shown when screens exist)
725
725
  if (screens.length > 0) {
726
726
  var html = '';
727
727
  if (typeof renderScreenMockups === 'function') {
728
728
  html = renderScreenMockups(code);
729
729
  }
730
- if (wireframes.length > 0) {
731
- html += '<h3 style="color:var(--text-bright);font-size:1rem;margin:2rem 0 1rem;">Wireframes</h3>';
732
- html += wireframes.map(function(wf, i) { return renderWireframeMockup(code, wf, i); }).join('');
733
- }
734
730
  return html;
735
731
  }
736
732
 
@@ -3,6 +3,8 @@
3
3
  Generates realistic HTML mockups from screens[] specs
4
4
  ============================================ */
5
5
 
6
+ var _formTabCounter = 0;
7
+
6
8
  function renderScreenMockups(code) {
7
9
  var spec = data.moduleSpecs[code] || {};
8
10
  var screens = spec.screens || [];
@@ -162,6 +164,7 @@ function renderSmartFormMockup(res) {
162
164
  }
163
165
  if (tabs.length === 0) return '<div style="padding:2rem;text-align:center;color:var(--text-muted);">Formulaire sans champs définis</div>';
164
166
 
167
+ var mockupId = 'form-tabs-' + (++_formTabCounter);
165
168
  var html = '';
166
169
 
167
170
  // Header
@@ -174,37 +177,51 @@ function renderSmartFormMockup(res) {
174
177
  });
175
178
  html += '</div></div>';
176
179
 
177
- // Tabs
180
+ // Tabs (clickable when more than 1)
178
181
  if (tabs.length > 1) {
179
- html += '<div style="display:flex;gap:0;border-bottom:1px solid var(--border);margin-bottom:1.5rem;">';
182
+ html += '<div class="mock-form-tab-bar" data-mockup="' + mockupId + '">';
180
183
  tabs.forEach(function(tab, i) {
181
- 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>';
184
+ html += '<span class="mock-form-tab' + (i === 0 ? ' active' : '') + '"';
185
+ html += ' onclick="switchFormTab(\'' + mockupId + '\', ' + i + ')"';
186
+ html += '>' + escapeHtml(tab.label) + '</span>';
182
187
  });
183
188
  html += '</div>';
184
189
  }
185
190
 
186
- // Fields (first tab only)
187
- var fields = tabs[0].fields || [];
188
- var rows = [];
189
- for (var i = 0; i < fields.length; i += 2) {
190
- rows.push(fields.slice(i, i + 2));
191
- }
191
+ // Tab contents (all rendered, only first visible)
192
+ tabs.forEach(function(tab, ti) {
193
+ var isOnly = tabs.length <= 1;
194
+ html += '<div class="mock-form-tab-content' + (ti === 0 || isOnly ? ' active' : '') + '"';
195
+ html += ' data-mockup="' + mockupId + '" data-tab="' + ti + '">';
196
+
197
+ var fields = tab.fields || [];
198
+ var rows = [];
199
+ for (var i = 0; i < fields.length; i += 2) {
200
+ rows.push(fields.slice(i, i + 2));
201
+ }
192
202
 
193
- rows.forEach(function(row) {
194
- if (row.length === 1 && row[0].type === 'subtable') {
195
- html += renderSubtableMockup(row[0]);
196
- } else {
197
- html += '<div class="mock-form-row">';
198
- row.forEach(function(field) {
199
- html += '<div class="mock-form-group">';
200
- html += '<label class="mock-label">' + escapeHtml(field.label || field.field);
201
- if (field.required) html += ' <span style="color:var(--error);">*</span>';
202
- html += '</label>';
203
- html += renderFormFieldMockup(field);
203
+ rows.forEach(function(row) {
204
+ if (row.length === 1 && row[0].type === 'subtable') {
205
+ html += renderSubtableMockup(row[0]);
206
+ } else {
207
+ html += '<div class="mock-form-row">';
208
+ row.forEach(function(field) {
209
+ html += '<div class="mock-form-group">';
210
+ html += '<label class="mock-label">' + escapeHtml(field.label || field.field);
211
+ if (field.required) html += ' <span style="color:var(--error);">*</span>';
212
+ html += '</label>';
213
+ html += renderFormFieldMockup(field);
214
+ html += '</div>';
215
+ });
204
216
  html += '</div>';
205
- });
206
- html += '</div>';
217
+ }
218
+ });
219
+
220
+ if (fields.length === 0) {
221
+ html += '<div style="padding:2rem;text-align:center;color:var(--text-muted);font-style:italic;">Contenu de l\'onglet &laquo; ' + escapeHtml(tab.label) + ' &raquo;</div>';
207
222
  }
223
+
224
+ html += '</div>';
208
225
  });
209
226
 
210
227
  return html;
@@ -308,10 +325,10 @@ function renderSmartDashboardMockup(res) {
308
325
  });
309
326
  html += '</div>';
310
327
 
311
- // Chart placeholders
312
- var charts = res.charts || [{ label: 'Graphique' }];
328
+ // Charts (SVG mock previews)
329
+ var charts = res.charts || [{ label: 'Graphique', type: 'bar' }];
313
330
  charts.forEach(function(chart) {
314
- html += '<div class="mock-chart-placeholder">' + escapeHtml(chart.label || chart.type || 'Graphique') + '</div>';
331
+ html += renderMockChart(chart);
315
332
  });
316
333
 
317
334
  return html;
@@ -352,6 +369,96 @@ function renderSmartFilterMockup(res) {
352
369
  return html;
353
370
  }
354
371
 
372
+ /* ---------- Mock Charts ---------- */
373
+ function renderMockChart(chart) {
374
+ var type = (chart.type || '').toLowerCase();
375
+ var label = chart.label || chart.type || 'Graphique';
376
+ var html = '<div class="mock-chart-container">';
377
+ html += '<div class="mock-chart-header">' + escapeHtml(label) + '</div>';
378
+ html += '<div class="mock-chart-body">';
379
+ if (type === 'pie' || type === 'donut') {
380
+ html += renderMockPieChart();
381
+ } else if (type === 'line' || type === 'area') {
382
+ html += renderMockLineChart();
383
+ } else {
384
+ html += renderMockBarChart();
385
+ }
386
+ html += '</div></div>';
387
+ return html;
388
+ }
389
+
390
+ function renderMockPieChart() {
391
+ var html = '<div style="display:flex;align-items:center;gap:1.5rem;">';
392
+ html += '<svg viewBox="0 0 42 42" class="mock-pie-svg">';
393
+ html += '<circle cx="21" cy="21" r="15.9" fill="transparent" stroke="var(--primary)" stroke-width="8" stroke-dasharray="35 65" stroke-dashoffset="25"/>';
394
+ html += '<circle cx="21" cy="21" r="15.9" fill="transparent" stroke="var(--success)" stroke-width="8" stroke-dasharray="25 75" stroke-dashoffset="-10"/>';
395
+ html += '<circle cx="21" cy="21" r="15.9" fill="transparent" stroke="var(--warning)" stroke-width="8" stroke-dasharray="22 78" stroke-dashoffset="-35"/>';
396
+ html += '<circle cx="21" cy="21" r="15.9" fill="transparent" stroke="var(--error)" stroke-width="8" stroke-dasharray="18 82" stroke-dashoffset="-57"/>';
397
+ html += '</svg>';
398
+ html += '<div class="mock-chart-legend">';
399
+ html += '<div><span class="mock-legend-dot" style="background:var(--primary);"></span> Cat\u00e9gorie A (35%)</div>';
400
+ html += '<div><span class="mock-legend-dot" style="background:var(--success);"></span> Cat\u00e9gorie B (25%)</div>';
401
+ html += '<div><span class="mock-legend-dot" style="background:var(--warning);"></span> Cat\u00e9gorie C (22%)</div>';
402
+ html += '<div><span class="mock-legend-dot" style="background:var(--error);"></span> Cat\u00e9gorie D (18%)</div>';
403
+ html += '</div></div>';
404
+ return html;
405
+ }
406
+
407
+ function renderMockBarChart() {
408
+ var bars = [85, 60, 45, 70, 55, 90];
409
+ var labels = ['Jan', 'F\u00e9v', 'Mar', 'Avr', 'Mai', 'Jun'];
410
+ var html = '<svg viewBox="0 0 300 140" class="mock-bar-svg">';
411
+ html += '<line x1="30" y1="10" x2="30" y2="120" stroke="var(--border)" stroke-width="1"/>';
412
+ html += '<line x1="30" y1="120" x2="290" y2="120" stroke="var(--border)" stroke-width="1"/>';
413
+ html += '<line x1="30" y1="40" x2="290" y2="40" stroke="var(--border)" stroke-width="0.5" stroke-dasharray="4"/>';
414
+ html += '<line x1="30" y1="80" x2="290" y2="80" stroke="var(--border)" stroke-width="0.5" stroke-dasharray="4"/>';
415
+ var bw = 30, gap = 13;
416
+ bars.forEach(function(h, i) {
417
+ var x = 35 + i * (bw + gap);
418
+ var y = 120 - h * 1.1;
419
+ html += '<rect x="' + x + '" y="' + y + '" width="' + bw + '" height="' + (h * 1.1) + '" fill="var(--primary)" rx="3" opacity="' + (0.6 + i * 0.08) + '"/>';
420
+ html += '<text x="' + (x + bw / 2) + '" y="134" text-anchor="middle" fill="var(--text-muted)" font-size="9">' + labels[i] + '</text>';
421
+ });
422
+ html += '</svg>';
423
+ return html;
424
+ }
425
+
426
+ function renderMockLineChart() {
427
+ var points = '40,90 90,65 140,75 190,40 240,50 280,25';
428
+ var labels = ['Jan', 'F\u00e9v', 'Mar', 'Avr', 'Mai', 'Jun'];
429
+ var xs = [40, 90, 140, 190, 240, 280];
430
+ var html = '<svg viewBox="0 0 300 140" class="mock-line-svg">';
431
+ html += '<line x1="30" y1="10" x2="30" y2="120" stroke="var(--border)" stroke-width="1"/>';
432
+ html += '<line x1="30" y1="120" x2="290" y2="120" stroke="var(--border)" stroke-width="1"/>';
433
+ html += '<line x1="30" y1="40" x2="290" y2="40" stroke="var(--border)" stroke-width="0.5" stroke-dasharray="4"/>';
434
+ html += '<line x1="30" y1="80" x2="290" y2="80" stroke="var(--border)" stroke-width="0.5" stroke-dasharray="4"/>';
435
+ html += '<polygon fill="var(--primary)" opacity="0.1" points="' + points + ' 280,120 40,120"/>';
436
+ html += '<polyline fill="none" stroke="var(--primary)" stroke-width="2.5" points="' + points + '" stroke-linecap="round" stroke-linejoin="round"/>';
437
+ points.split(' ').forEach(function(p) {
438
+ var xy = p.split(',');
439
+ html += '<circle cx="' + xy[0] + '" cy="' + xy[1] + '" r="3" fill="var(--primary)" stroke="var(--bg-card)" stroke-width="1.5"/>';
440
+ });
441
+ xs.forEach(function(x, i) {
442
+ html += '<text x="' + x + '" y="134" text-anchor="middle" fill="var(--text-muted)" font-size="9">' + labels[i] + '</text>';
443
+ });
444
+ html += '</svg>';
445
+ return html;
446
+ }
447
+
448
+ /* ---------- Form Tab Switching ---------- */
449
+ function switchFormTab(mockupId, tabIndex) {
450
+ document.querySelectorAll('.mock-form-tab-bar[data-mockup="' + mockupId + '"] .mock-form-tab').forEach(function(t) {
451
+ t.classList.remove('active');
452
+ });
453
+ var allTabs = document.querySelectorAll('.mock-form-tab-bar[data-mockup="' + mockupId + '"] .mock-form-tab');
454
+ if (allTabs[tabIndex]) allTabs[tabIndex].classList.add('active');
455
+ document.querySelectorAll('.mock-form-tab-content[data-mockup="' + mockupId + '"]').forEach(function(el) {
456
+ el.classList.remove('active');
457
+ });
458
+ var target = document.querySelector('.mock-form-tab-content[data-mockup="' + mockupId + '"][data-tab="' + tabIndex + '"]');
459
+ if (target) target.classList.add('active');
460
+ }
461
+
355
462
  /* ---------- Helpers ---------- */
356
463
  function formatActionLabel(action) {
357
464
  var labels = {
@@ -258,15 +258,6 @@
258
258
  }
259
259
  .mock-kpi-value { font-size: 1.3rem; font-weight: 700; color: var(--text-bright); }
260
260
  .mock-kpi-label { font-size: 0.7rem; color: var(--text-muted); margin-top: 0.2rem; }
261
- .mock-chart-placeholder {
262
- height: 150px;
263
- background: linear-gradient(135deg, rgba(99,102,241,0.05), rgba(6,182,212,0.05));
264
- border: 1px dashed var(--border);
265
- border-radius: 8px;
266
- display: flex; align-items: center; justify-content: center;
267
- color: var(--text-muted); font-size: 0.85rem;
268
- }
269
-
270
261
  @media (max-width: 768px) {
271
262
  .mock-kpi-grid { grid-template-columns: repeat(2, 1fr); }
272
263
  }
@@ -134,3 +134,87 @@
134
134
  padding: 0.15rem 0.5rem;
135
135
  border-radius: 4px;
136
136
  }
137
+
138
+ /* ---- Mock Charts (SVG previews) ---- */
139
+ .mock-chart-container {
140
+ margin: 0.75rem 0;
141
+ border: 1px solid var(--border);
142
+ border-radius: 8px;
143
+ overflow: hidden;
144
+ }
145
+
146
+ .mock-chart-header {
147
+ padding: 0.5rem 0.75rem;
148
+ font-size: 0.85rem;
149
+ font-weight: 500;
150
+ color: var(--text-bright);
151
+ background: var(--bg-hover);
152
+ border-bottom: 1px solid var(--border);
153
+ }
154
+
155
+ .mock-chart-body {
156
+ padding: 1rem;
157
+ }
158
+
159
+ .mock-pie-svg {
160
+ width: 120px;
161
+ height: 120px;
162
+ flex-shrink: 0;
163
+ }
164
+
165
+ .mock-bar-svg,
166
+ .mock-line-svg {
167
+ width: 100%;
168
+ height: 140px;
169
+ }
170
+
171
+ .mock-chart-legend {
172
+ font-size: 0.8rem;
173
+ color: var(--text);
174
+ display: flex;
175
+ flex-direction: column;
176
+ gap: 0.4rem;
177
+ }
178
+
179
+ .mock-legend-dot {
180
+ display: inline-block;
181
+ width: 10px;
182
+ height: 10px;
183
+ border-radius: 2px;
184
+ margin-right: 0.4rem;
185
+ vertical-align: middle;
186
+ }
187
+
188
+ /* ---- Form Tab Switching ---- */
189
+ .mock-form-tab-bar {
190
+ display: flex;
191
+ gap: 0;
192
+ border-bottom: 1px solid var(--border);
193
+ margin-bottom: 1.5rem;
194
+ }
195
+
196
+ .mock-form-tab {
197
+ padding: 0.5rem 1rem;
198
+ font-size: 0.85rem;
199
+ cursor: pointer;
200
+ border-bottom: 2px solid transparent;
201
+ color: var(--text-muted);
202
+ transition: all var(--transition-fast);
203
+ }
204
+
205
+ .mock-form-tab:hover {
206
+ color: var(--text);
207
+ }
208
+
209
+ .mock-form-tab.active {
210
+ border-bottom-color: var(--primary);
211
+ color: var(--primary-light);
212
+ }
213
+
214
+ .mock-form-tab-content {
215
+ display: none;
216
+ }
217
+
218
+ .mock-form-tab-content.active {
219
+ display: block;
220
+ }