@visns-studio/visns-components 6.1.7 → 6.2.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.
@@ -0,0 +1,99 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+ import CustomFetch from '../Fetch';
3
+ import debugLog from '../utils/debugLog';
4
+ import {
5
+ DEFAULT_SEMANTIC_MODEL_URL,
6
+ normalizeSemanticModel,
7
+ } from './reportSemantics';
8
+
9
+ /**
10
+ * Probe the semantic model endpoint once on mount.
11
+ *
12
+ * This is a feature detection, not a hard dependency: a 404, a network error,
13
+ * or any payload without entities leaves `status` at 'unavailable' and the
14
+ * report builder stays in legacy table/column mode. The probe never surfaces a
15
+ * toast — an app that has not shipped the endpoint yet must not see an error.
16
+ *
17
+ * @param {string} [url] Endpoint to probe. Defaults to the contract default.
18
+ * @param {boolean} [enabled=true] Set false to skip the probe entirely.
19
+ * @returns {{
20
+ * model: Object|null,
21
+ * status: 'loading'|'ready'|'unavailable'|'disabled',
22
+ * isSemantic: boolean,
23
+ * isProbing: boolean,
24
+ * reload: Function,
25
+ * }}
26
+ */
27
+ const useSemanticModel = (url = DEFAULT_SEMANTIC_MODEL_URL, enabled = true) => {
28
+ const [model, setModel] = useState(null);
29
+ const [status, setStatus] = useState(enabled ? 'loading' : 'disabled');
30
+ const mountedRef = useRef(true);
31
+
32
+ useEffect(() => {
33
+ mountedRef.current = true;
34
+ return () => {
35
+ mountedRef.current = false;
36
+ };
37
+ }, []);
38
+
39
+ const probe = useCallback(async () => {
40
+ if (!enabled || !url) {
41
+ setModel(null);
42
+ setStatus('disabled');
43
+ return null;
44
+ }
45
+
46
+ setStatus('loading');
47
+
48
+ try {
49
+ // The empty errorCallback swallows CustomFetch's default toast so a
50
+ // missing endpoint stays silent for the user.
51
+ const result = await CustomFetch(
52
+ url,
53
+ 'POST',
54
+ {},
55
+ null,
56
+ () => {},
57
+ { cache: false }
58
+ );
59
+
60
+ const normalized = normalizeSemanticModel(result);
61
+
62
+ if (!mountedRef.current) return normalized;
63
+
64
+ if (normalized) {
65
+ setModel(normalized);
66
+ setStatus('ready');
67
+ } else {
68
+ debugLog(
69
+ 'Semantic model endpoint returned no entities; staying in legacy mode.'
70
+ );
71
+ setModel(null);
72
+ setStatus('unavailable');
73
+ }
74
+
75
+ return normalized;
76
+ } catch (error) {
77
+ debugLog('Semantic model unavailable, using legacy mode:', error);
78
+ if (mountedRef.current) {
79
+ setModel(null);
80
+ setStatus('unavailable');
81
+ }
82
+ return null;
83
+ }
84
+ }, [url, enabled]);
85
+
86
+ useEffect(() => {
87
+ probe();
88
+ }, [probe]);
89
+
90
+ return {
91
+ model,
92
+ status,
93
+ isSemantic: status === 'ready' && !!model,
94
+ isProbing: status === 'loading',
95
+ reload: probe,
96
+ };
97
+ };
98
+
99
+ export default useSemanticModel;
@@ -0,0 +1,444 @@
1
+ /**
2
+ * Styles for the semantic-mode wizard steps.
3
+ *
4
+ * Kept separate from GenericReport.module.scss so the semantic steps can be
5
+ * read and changed without touching the 4,000-line legacy stylesheet. Uses the
6
+ * same CSS custom properties as the rest of the report builder.
7
+ */
8
+
9
+ .section {
10
+ display: flex;
11
+ flex-direction: column;
12
+ gap: 16px;
13
+ }
14
+
15
+ .sectionIntro {
16
+ h3 {
17
+ margin: 0 0 4px 0;
18
+ font-size: 1rem;
19
+ font-weight: 600;
20
+ color: var(--header-color, #212930);
21
+ }
22
+
23
+ p {
24
+ margin: 0;
25
+ font-size: 0.875rem;
26
+ color: var(--paragraph-color, #221e33);
27
+ }
28
+ }
29
+
30
+ .loading,
31
+ .empty {
32
+ padding: 32px 16px;
33
+ text-align: center;
34
+ font-size: 0.875rem;
35
+ color: var(--paragraph-color, #221e33);
36
+ }
37
+
38
+ /* -- Entity + relation cards ---------------------------------------------- */
39
+
40
+ .cardGrid {
41
+ display: grid;
42
+ grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
43
+ gap: 12px;
44
+ }
45
+
46
+ .card {
47
+ display: flex;
48
+ flex-direction: column;
49
+ gap: 6px;
50
+ padding: 16px;
51
+ text-align: left;
52
+ background-color: var(--tertiary-color, #fff);
53
+ border: 1px solid var(--border-color, #e1e1e1);
54
+ border-radius: var(--radius, var(--br, 5px));
55
+ cursor: pointer;
56
+ transition: all var(--speed, 0.2s) var(--ease, ease);
57
+ font-family: var(--font-family, inherit);
58
+
59
+ &:hover {
60
+ border-color: var(--primary-color, #2c5282);
61
+ box-shadow: var(--box-shadow-small, 0 1px 2px rgba(0, 0, 0, 0.04));
62
+ }
63
+
64
+ &.selected {
65
+ border-color: var(--primary-color, #2c5282);
66
+ background-color: rgba(var(--primary-rgb, 44, 82, 130), 0.06);
67
+ }
68
+ }
69
+
70
+ .cardTitle {
71
+ display: flex;
72
+ align-items: center;
73
+ justify-content: space-between;
74
+ gap: 8px;
75
+ font-weight: 600;
76
+ font-size: 0.9375rem;
77
+ color: var(--header-color, #212930);
78
+ }
79
+
80
+ .cardDescription {
81
+ font-size: 0.8125rem;
82
+ color: var(--paragraph-color, #221e33);
83
+ }
84
+
85
+ .cardMeta {
86
+ font-size: 0.75rem;
87
+ color: var(--paragraph-color, #221e33);
88
+ opacity: 0.75;
89
+ }
90
+
91
+ .badge {
92
+ display: inline-block;
93
+ padding: 2px 8px;
94
+ border-radius: 999px;
95
+ font-size: 0.6875rem;
96
+ font-weight: 600;
97
+ background-color: var(--bg-color, #f7f7f8);
98
+ color: var(--paragraph-color, #221e33);
99
+ border: 1px solid var(--border-color, #e1e1e1);
100
+ }
101
+
102
+ .badgeMany {
103
+ background-color: rgba(var(--color-alert, 255, 228, 0), 0.25);
104
+ }
105
+
106
+ /* -- Field picker ---------------------------------------------------------- */
107
+
108
+ .sourceGroup {
109
+ border: 1px solid var(--border-color, #e1e1e1);
110
+ border-radius: var(--radius, var(--br, 5px));
111
+ overflow: hidden;
112
+ }
113
+
114
+ .sourceHeader {
115
+ display: flex;
116
+ align-items: center;
117
+ justify-content: space-between;
118
+ gap: 8px;
119
+ width: 100%;
120
+ padding: 10px 14px;
121
+ background-color: var(--bg-color, #f7f7f8);
122
+ border: 0;
123
+ border-bottom: 1px solid var(--border-color, #e1e1e1);
124
+ font-weight: 600;
125
+ font-size: 0.875rem;
126
+ color: var(--header-color, #212930);
127
+ cursor: pointer;
128
+ text-align: left;
129
+ font-family: var(--font-family, inherit);
130
+ }
131
+
132
+ .categoryGroup {
133
+ padding: 8px 14px 12px;
134
+
135
+ h5 {
136
+ margin: 8px 0 6px;
137
+ font-size: 0.75rem;
138
+ font-weight: 600;
139
+ text-transform: uppercase;
140
+ letter-spacing: 0.04em;
141
+ color: var(--paragraph-color, #221e33);
142
+ opacity: 0.7;
143
+ }
144
+ }
145
+
146
+ .fieldRow {
147
+ display: flex;
148
+ align-items: center;
149
+ gap: 10px;
150
+ padding: 6px 0;
151
+ border-bottom: 1px solid rgba(var(--paragraph-rgb, 34, 30, 51), 0.06);
152
+
153
+ &:last-child {
154
+ border-bottom: 0;
155
+ }
156
+ }
157
+
158
+ .fieldCheckbox {
159
+ display: flex;
160
+ align-items: center;
161
+ gap: 8px;
162
+ flex: 1;
163
+ min-width: 0;
164
+ cursor: pointer;
165
+ font-size: 0.875rem;
166
+ color: var(--header-color, #212930);
167
+ }
168
+
169
+ .fieldTypeTag {
170
+ font-size: 0.6875rem;
171
+ color: var(--paragraph-color, #221e33);
172
+ opacity: 0.7;
173
+ white-space: nowrap;
174
+ }
175
+
176
+ .aggPicker {
177
+ display: flex;
178
+ align-items: center;
179
+ gap: 6px;
180
+ font-size: 0.75rem;
181
+ color: var(--paragraph-color, #221e33);
182
+ }
183
+
184
+ .select,
185
+ .input {
186
+ padding: 6px 8px;
187
+ font-size: 0.8125rem;
188
+ font-family: var(--font-family, inherit);
189
+ color: var(--header-color, #212930);
190
+ background-color: var(--tertiary-color, #fff);
191
+ border: 1px solid var(--border-color, #e1e1e1);
192
+ border-radius: var(--radius-sm, var(--radius, var(--br, 5px)));
193
+ max-width: 100%;
194
+ }
195
+
196
+ .input {
197
+ min-width: 120px;
198
+ }
199
+
200
+ /* -- Selected columns summary --------------------------------------------- */
201
+
202
+ .selectedList {
203
+ display: flex;
204
+ flex-direction: column;
205
+ gap: 6px;
206
+ }
207
+
208
+ .selectedItem {
209
+ display: flex;
210
+ align-items: center;
211
+ gap: 8px;
212
+ padding: 6px 10px;
213
+ background-color: var(--bg-color, #f7f7f8);
214
+ border: 1px solid var(--border-color, #e1e1e1);
215
+ border-radius: var(--radius-sm, var(--radius, var(--br, 5px)));
216
+ font-size: 0.8125rem;
217
+ }
218
+
219
+ .selectedLabel {
220
+ flex: 1;
221
+ min-width: 0;
222
+ overflow: hidden;
223
+ text-overflow: ellipsis;
224
+ white-space: nowrap;
225
+ }
226
+
227
+ /* -- Filter tree ----------------------------------------------------------- */
228
+
229
+ .filterGroup {
230
+ border: 1px solid var(--border-color, #e1e1e1);
231
+ border-left: 3px solid var(--primary-color, #2c5282);
232
+ border-radius: var(--radius, var(--br, 5px));
233
+ padding: 12px;
234
+ background-color: var(--tertiary-color, #fff);
235
+ display: flex;
236
+ flex-direction: column;
237
+ gap: 10px;
238
+ }
239
+
240
+ .filterGroupNested {
241
+ background-color: var(--bg-color, #f7f7f8);
242
+ }
243
+
244
+ .filterGroupHeader {
245
+ display: flex;
246
+ align-items: center;
247
+ justify-content: space-between;
248
+ gap: 12px;
249
+ flex-wrap: wrap;
250
+ }
251
+
252
+ .matchToggle {
253
+ display: inline-flex;
254
+ align-items: center;
255
+ gap: 6px;
256
+ font-size: 0.8125rem;
257
+ color: var(--paragraph-color, #221e33);
258
+ }
259
+
260
+ .toggleButton {
261
+ padding: 4px 10px;
262
+ font-size: 0.75rem;
263
+ font-weight: 600;
264
+ border: 1px solid var(--border-color, #e1e1e1);
265
+ background-color: var(--tertiary-color, #fff);
266
+ color: var(--paragraph-color, #221e33);
267
+ border-radius: var(--radius-sm, var(--radius, var(--br, 5px)));
268
+ cursor: pointer;
269
+ font-family: var(--font-family, inherit);
270
+
271
+ &.toggleActive {
272
+ background-color: var(--primary-color, #2c5282);
273
+ border-color: var(--primary-color, #2c5282);
274
+ color: var(--item-color, #fff);
275
+ }
276
+ }
277
+
278
+ .filterItems {
279
+ display: flex;
280
+ flex-direction: column;
281
+ gap: 10px;
282
+ }
283
+
284
+ .condition {
285
+ display: flex;
286
+ flex-direction: column;
287
+ gap: 8px;
288
+ padding: 10px;
289
+ border: 1px solid var(--border-color, #e1e1e1);
290
+ border-radius: var(--radius-sm, var(--radius, var(--br, 5px)));
291
+ background-color: var(--tertiary-color, #fff);
292
+ }
293
+
294
+ .conditionRow {
295
+ display: flex;
296
+ align-items: center;
297
+ gap: 8px;
298
+ flex-wrap: wrap;
299
+ }
300
+
301
+ .conditionValues {
302
+ display: flex;
303
+ align-items: center;
304
+ gap: 8px;
305
+ flex-wrap: wrap;
306
+ }
307
+
308
+ .askEachTime {
309
+ display: flex;
310
+ align-items: center;
311
+ gap: 8px;
312
+ font-size: 0.75rem;
313
+ color: var(--paragraph-color, #221e33);
314
+ flex-wrap: wrap;
315
+ }
316
+
317
+ .filterActions {
318
+ display: flex;
319
+ gap: 8px;
320
+ flex-wrap: wrap;
321
+ }
322
+
323
+ .linkButton {
324
+ background: none;
325
+ border: 0;
326
+ padding: 4px 0;
327
+ font-size: 0.8125rem;
328
+ font-weight: 600;
329
+ color: var(--primary-color, #2c5282);
330
+ cursor: pointer;
331
+ font-family: var(--font-family, inherit);
332
+
333
+ &:hover {
334
+ text-decoration: underline;
335
+ }
336
+ }
337
+
338
+ .removeButton {
339
+ background: none;
340
+ border: 0;
341
+ padding: 2px;
342
+ line-height: 0;
343
+ color: var(--approval-color, #c53030);
344
+ cursor: pointer;
345
+ }
346
+
347
+ /* -- Parameters ------------------------------------------------------------ */
348
+
349
+ .parameterList {
350
+ display: flex;
351
+ flex-direction: column;
352
+ gap: 10px;
353
+ }
354
+
355
+ .parameterRow {
356
+ display: flex;
357
+ align-items: center;
358
+ gap: 10px;
359
+ flex-wrap: wrap;
360
+ padding: 10px;
361
+ border: 1px solid var(--border-color, #e1e1e1);
362
+ border-radius: var(--radius-sm, var(--radius, var(--br, 5px)));
363
+ background-color: var(--bg-color, #f7f7f8);
364
+ }
365
+
366
+ .parameterLabel {
367
+ min-width: 160px;
368
+ font-size: 0.8125rem;
369
+ font-weight: 600;
370
+ color: var(--header-color, #212930);
371
+ }
372
+
373
+ /* -- Preview --------------------------------------------------------------- */
374
+
375
+ .previewActions {
376
+ display: flex;
377
+ align-items: center;
378
+ gap: 10px;
379
+ flex-wrap: wrap;
380
+ }
381
+
382
+ .tableWrapper {
383
+ overflow-x: auto;
384
+ border: 1px solid var(--border-color, #e1e1e1);
385
+ border-radius: var(--radius, var(--br, 5px));
386
+ }
387
+
388
+ .previewTable {
389
+ width: 100%;
390
+ border-collapse: collapse;
391
+ font-size: 0.8125rem;
392
+
393
+ th {
394
+ text-align: left;
395
+ padding: 10px 12px;
396
+ background-color: var(--bg-color, #f7f7f8);
397
+ border-bottom: 1px solid var(--border-color, #e1e1e1);
398
+ font-weight: 600;
399
+ color: var(--header-color, #212930);
400
+ white-space: nowrap;
401
+ }
402
+
403
+ td {
404
+ padding: 8px 12px;
405
+ border-bottom: 1px solid rgba(var(--paragraph-rgb, 34, 30, 51), 0.08);
406
+ color: var(--paragraph-color, #221e33);
407
+ }
408
+
409
+ tbody tr:last-child td {
410
+ border-bottom: 0;
411
+ }
412
+ }
413
+
414
+ .numericCell {
415
+ text-align: right;
416
+ font-variant-numeric: tabular-nums;
417
+ }
418
+
419
+ .emptyCell {
420
+ color: var(--disabled-color, #9ca3af);
421
+ font-style: italic;
422
+ }
423
+
424
+ .summaryBar {
425
+ display: flex;
426
+ align-items: center;
427
+ justify-content: space-between;
428
+ gap: 12px;
429
+ flex-wrap: wrap;
430
+ font-size: 0.8125rem;
431
+ color: var(--paragraph-color, #221e33);
432
+ }
433
+
434
+ .errorPanel {
435
+ display: flex;
436
+ align-items: flex-start;
437
+ gap: 10px;
438
+ padding: 12px 14px;
439
+ border: 1px solid rgba(var(--color-invalid, 220, 53, 69), 0.4);
440
+ background-color: rgba(var(--color-invalid, 220, 53, 69), 0.06);
441
+ border-radius: var(--radius, var(--br, 5px));
442
+ font-size: 0.8125rem;
443
+ color: var(--approval-color, #c53030);
444
+ }
package/src/index.js CHANGED
@@ -72,8 +72,10 @@ import GenericMain from './components/generic/GenericMain';
72
72
  import GenericQuote from './components/generic/GenericQuote';
73
73
  import GenericReport from './components/generic/GenericReport';
74
74
  import GenericSort from './components/generic/GenericSort';
75
+ import GroupedReportRenderer from './components/generic/GroupedReportRenderer';
75
76
  import NotificationList from './components/generic/NotificationList';
76
77
  import OcrTemplateEnhanced from './components/generic/OcrTemplateEnhanced';
78
+ import SectionGroupedReport from './components/generic/SectionGroupedReport';
77
79
  import StagePopupModal from './components/generic/StagePopupModal';
78
80
  import StandardModal from './components/generic/StandardModal';
79
81
 
@@ -138,6 +140,7 @@ export {
138
140
  GenericQuote,
139
141
  GenericReport,
140
142
  GenericSort,
143
+ GroupedReportRenderer,
141
144
  ImportWizardModal,
142
145
  Loader,
143
146
  Login,
@@ -158,6 +161,7 @@ export {
158
161
  ProposalTemplatePreview,
159
162
  Reset,
160
163
  SectionEditor,
164
+ SectionGroupedReport,
161
165
  SectionTypeSelector,
162
166
  SelectList,
163
167
  Select,