@smartnet360/svelte-components 0.0.83 → 0.0.84
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.
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<script lang="ts">
|
|
4
4
|
import { TreeView, createTreeStore } from '../../core/TreeView';
|
|
5
5
|
import { ChartComponent, type Layout, type CellStylingConfig } from '../../core/Charts';
|
|
6
|
-
import { buildTreeNodes, filterChartData, transformChartData, type CellTrafficRecord, defaultCellStyling, type TreeGroupingConfig, defaultTreeGrouping } from './index';
|
|
6
|
+
import { buildTreeNodes, filterChartData, transformChartData, type CellTrafficRecord, defaultCellStyling, type TreeGroupingConfig, type TreeGroupField, defaultTreeGrouping } from './index';
|
|
7
7
|
import { expandLayoutForCells } from './helper';
|
|
8
8
|
import { log } from '../../core/logger';
|
|
9
9
|
import type {ChartMarker, Mode } from '../../index.js';
|
|
@@ -58,19 +58,56 @@
|
|
|
58
58
|
// Internal state for current grouping
|
|
59
59
|
let treeGrouping = $state<TreeGroupingConfig>(initialGrouping);
|
|
60
60
|
|
|
61
|
-
// Available grouping
|
|
62
|
-
const
|
|
63
|
-
{
|
|
64
|
-
{
|
|
65
|
-
{
|
|
66
|
-
{
|
|
67
|
-
{ label: 'Azimuth → Site → Cell', value: { level0: 'azimuth', level1: 'site', level2: 'cell' } as TreeGroupingConfig },
|
|
68
|
-
{ label: 'Band → Cell', value: { level0: 'band', level1: null, level2: 'cell' } as TreeGroupingConfig },
|
|
69
|
-
{ label: 'Site → Cell (2-level)', value: { level0: 'site', level1: null, level2: 'cell' } as TreeGroupingConfig },
|
|
70
|
-
{ label: 'Azimuth → Cell (2-level)', value: { level0: 'azimuth', level1: null, level2: 'cell' } as TreeGroupingConfig },
|
|
61
|
+
// Available field options for grouping levels
|
|
62
|
+
const fieldOptions: { value: TreeGroupField; label: string }[] = [
|
|
63
|
+
{ value: 'site', label: 'Site' },
|
|
64
|
+
{ value: 'band', label: 'Band' },
|
|
65
|
+
{ value: 'azimuth', label: 'Azimuth' },
|
|
66
|
+
{ value: 'cell', label: 'Cell' }
|
|
71
67
|
];
|
|
72
68
|
|
|
73
|
-
|
|
69
|
+
// Handlers for level changes
|
|
70
|
+
function handleLevel0Change(value: TreeGroupField) {
|
|
71
|
+
// Clear level1 if it conflicts with new level0
|
|
72
|
+
const newLevel1 = treeGrouping.level1 === value ? null : treeGrouping.level1;
|
|
73
|
+
// Clear level2 if it conflicts with new level0
|
|
74
|
+
let newLevel2 = treeGrouping.level2;
|
|
75
|
+
if (newLevel2 !== null && newLevel2 !== 'cell' && newLevel2 === value) {
|
|
76
|
+
newLevel2 = null;
|
|
77
|
+
}
|
|
78
|
+
treeGrouping = {
|
|
79
|
+
level0: value,
|
|
80
|
+
level1: newLevel1,
|
|
81
|
+
level2: newLevel2
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function handleLevel1Change(value: TreeGroupField | 'none') {
|
|
86
|
+
const newLevel1 = value === 'none' ? null : value;
|
|
87
|
+
// Clear level2 if it conflicts with new level1
|
|
88
|
+
let newLevel2 = treeGrouping.level2;
|
|
89
|
+
if (newLevel2 !== null && newLevel2 !== 'cell' && newLevel2 === newLevel1) {
|
|
90
|
+
newLevel2 = null;
|
|
91
|
+
}
|
|
92
|
+
treeGrouping = {
|
|
93
|
+
level0: treeGrouping.level0,
|
|
94
|
+
level1: newLevel1,
|
|
95
|
+
level2: newLevel2
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Get available options for level1 (exclude level0)
|
|
100
|
+
let availableLevel1Options = $derived.by(() => {
|
|
101
|
+
return fieldOptions.filter(opt => opt.value !== treeGrouping.level0);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Get available options for level2 (exclude level0 and level1)
|
|
105
|
+
let availableLevel2Options = $derived.by(() => {
|
|
106
|
+
return fieldOptions.filter(opt =>
|
|
107
|
+
opt.value !== treeGrouping.level0 &&
|
|
108
|
+
opt.value !== treeGrouping.level1
|
|
109
|
+
);
|
|
110
|
+
}); let treeStore = $state<ReturnType<typeof createTreeStore> | null>(null);
|
|
74
111
|
|
|
75
112
|
// Rebuild tree whenever treeGrouping changes
|
|
76
113
|
$effect(() => {
|
|
@@ -280,34 +317,71 @@
|
|
|
280
317
|
<i class="bi bi-search"></i>
|
|
281
318
|
</button>
|
|
282
319
|
</div>
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
<!-- Grouping Selector -->
|
|
287
|
-
{#if showGroupingSelector}
|
|
288
|
-
<div class="p-3 border-bottom flex-shrink-0">
|
|
289
|
-
<label for="groupingSelect" class="form-label small fw-semibold mb-2">
|
|
290
|
-
Tree Grouping
|
|
291
|
-
</label>
|
|
292
|
-
<select
|
|
293
|
-
id="groupingSelect"
|
|
294
|
-
class="form-select form-select-sm"
|
|
295
|
-
onchange={(e) => {
|
|
296
|
-
const index = parseInt(e.currentTarget.value);
|
|
297
|
-
treeGrouping = groupingPresets[index].value;
|
|
298
|
-
}}
|
|
299
|
-
>
|
|
300
|
-
{#each groupingPresets as preset, i}
|
|
301
|
-
<option value={i} selected={JSON.stringify(preset.value) === JSON.stringify(treeGrouping)}>
|
|
302
|
-
{preset.label}
|
|
303
|
-
</option>
|
|
304
|
-
{/each}
|
|
305
|
-
</select>
|
|
306
|
-
</div>
|
|
307
|
-
{/if}
|
|
308
|
-
{/if}
|
|
320
|
+
</div>
|
|
321
|
+
{/if}
|
|
309
322
|
|
|
310
|
-
|
|
323
|
+
<!-- Grouping Selector -->
|
|
324
|
+
{#if showGroupingSelector}
|
|
325
|
+
<div class="p-3 border-bottom flex-shrink-0">
|
|
326
|
+
<div class="small fw-semibold mb-2">Tree Grouping</div>
|
|
327
|
+
|
|
328
|
+
<!-- Level 0 (Mandatory) -->
|
|
329
|
+
<div class="mb-2">
|
|
330
|
+
<label for="level0Select" class="form-label small mb-1">Level 0 (Root)</label>
|
|
331
|
+
<select
|
|
332
|
+
id="level0Select"
|
|
333
|
+
class="form-select form-select-sm"
|
|
334
|
+
value={treeGrouping.level0}
|
|
335
|
+
onchange={(e) => handleLevel0Change(e.currentTarget.value as TreeGroupField)}
|
|
336
|
+
>
|
|
337
|
+
{#each fieldOptions as option}
|
|
338
|
+
<option value={option.value}>{option.label}</option>
|
|
339
|
+
{/each}
|
|
340
|
+
</select>
|
|
341
|
+
</div>
|
|
342
|
+
|
|
343
|
+
<!-- Level 1 (Optional) -->
|
|
344
|
+
<div class="mb-2">
|
|
345
|
+
<label for="level1Select" class="form-label small mb-1">Level 1 (Optional)</label>
|
|
346
|
+
<select
|
|
347
|
+
id="level1Select"
|
|
348
|
+
class="form-select form-select-sm"
|
|
349
|
+
value={treeGrouping.level1 ?? 'none'}
|
|
350
|
+
onchange={(e) => handleLevel1Change(e.currentTarget.value as TreeGroupField | 'none')}
|
|
351
|
+
>
|
|
352
|
+
<option value="none">None</option>
|
|
353
|
+
{#each availableLevel1Options as option}
|
|
354
|
+
<option value={option.value}>{option.label}</option>
|
|
355
|
+
{/each}
|
|
356
|
+
</select>
|
|
357
|
+
</div>
|
|
358
|
+
|
|
359
|
+
<!-- Level 2 (Optional) -->
|
|
360
|
+
<div class="mb-0">
|
|
361
|
+
<label for="level2Select" class="form-label small mb-1">Level 2 (Optional)</label>
|
|
362
|
+
<select
|
|
363
|
+
id="level2Select"
|
|
364
|
+
class="form-select form-select-sm"
|
|
365
|
+
value={treeGrouping.level2 ?? 'none'}
|
|
366
|
+
onchange={(e) => {
|
|
367
|
+
const value = e.currentTarget.value;
|
|
368
|
+
treeGrouping = {
|
|
369
|
+
level0: treeGrouping.level0,
|
|
370
|
+
level1: treeGrouping.level1,
|
|
371
|
+
level2: value === 'none' ? null : (value as 'cell')
|
|
372
|
+
};
|
|
373
|
+
}}
|
|
374
|
+
>
|
|
375
|
+
<option value="none">None</option>
|
|
376
|
+
<option value="cell">Cell</option>
|
|
377
|
+
{#each availableLevel2Options as option}
|
|
378
|
+
<option value={option.value}>{option.label}</option>
|
|
379
|
+
{/each}
|
|
380
|
+
</select>
|
|
381
|
+
</div>
|
|
382
|
+
</div>
|
|
383
|
+
{/if}
|
|
384
|
+
{/if} <!-- Tree View -->
|
|
311
385
|
<div class="flex-grow-1" style="min-height: 0; overflow: hidden;">
|
|
312
386
|
{#if treeStore}
|
|
313
387
|
<TreeView store={$treeStore!} showControls={true} showIndeterminate={true} height="100%" />
|
|
@@ -14,17 +14,18 @@ export interface CellTrafficRecord {
|
|
|
14
14
|
/**
|
|
15
15
|
* Tree grouping field types
|
|
16
16
|
*/
|
|
17
|
-
export type TreeGroupField = 'site' | 'azimuth' | 'band' | '
|
|
17
|
+
export type TreeGroupField = 'site' | 'azimuth' | 'band' | 'cell';
|
|
18
18
|
/**
|
|
19
19
|
* Configuration for tree hierarchy grouping
|
|
20
20
|
* Defines which fields appear at each level of the tree
|
|
21
|
-
* - For 3-level tree: level0 → level1 →
|
|
22
|
-
* - For 2-level tree: level0 →
|
|
21
|
+
* - For 3-level tree: level0 → level1 → level2
|
|
22
|
+
* - For 2-level tree: level0 → level1 (set level2 to null)
|
|
23
|
+
* - For 1-level tree: level0 only (set level1 and level2 to null)
|
|
23
24
|
*/
|
|
24
25
|
export interface TreeGroupingConfig {
|
|
25
26
|
level0: TreeGroupField;
|
|
26
27
|
level1: TreeGroupField | null;
|
|
27
|
-
level2: 'cell';
|
|
28
|
+
level2: TreeGroupField | 'cell' | null;
|
|
28
29
|
}
|
|
29
30
|
/**
|
|
30
31
|
* Default tree grouping: Site → Azimuth → Cell (3-level)
|
|
@@ -483,14 +483,18 @@ export function createStyledKPI(metricName, cellRecord, unit, grouping, stylingC
|
|
|
483
483
|
/**
|
|
484
484
|
* Generate adaptive label based on tree grouping configuration
|
|
485
485
|
* Label exactly matches the complete tree path shown in the tree view
|
|
486
|
+
* Only includes levels that are not null in the grouping config
|
|
486
487
|
* @param record - Cell traffic record with all metadata
|
|
487
488
|
* @param grouping - Current tree grouping configuration
|
|
488
|
-
* @returns Formatted label string matching complete tree hierarchy (
|
|
489
|
+
* @returns Formatted label string matching complete tree hierarchy (only non-null levels)
|
|
489
490
|
*/
|
|
490
491
|
function generateAdaptiveLabel(record, grouping) {
|
|
491
|
-
const { level0, level1 } = grouping;
|
|
492
|
+
const { level0, level1, level2 } = grouping;
|
|
492
493
|
// Helper to format field values exactly as shown in tree
|
|
493
494
|
const formatField = (field, value) => {
|
|
495
|
+
if (field === 'cell') {
|
|
496
|
+
return value; // Cell name as-is
|
|
497
|
+
}
|
|
494
498
|
switch (field) {
|
|
495
499
|
case 'azimuth':
|
|
496
500
|
return `${value}°`;
|
|
@@ -506,6 +510,9 @@ function generateAdaptiveLabel(record, grouping) {
|
|
|
506
510
|
};
|
|
507
511
|
// Get field values from record
|
|
508
512
|
const getFieldValue = (field) => {
|
|
513
|
+
if (field === 'cell') {
|
|
514
|
+
return record.cellName;
|
|
515
|
+
}
|
|
509
516
|
switch (field) {
|
|
510
517
|
case 'site':
|
|
511
518
|
return record.siteName;
|
|
@@ -517,19 +524,18 @@ function generateAdaptiveLabel(record, grouping) {
|
|
|
517
524
|
return record.sector;
|
|
518
525
|
}
|
|
519
526
|
};
|
|
520
|
-
// Build label
|
|
521
|
-
const
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
+
// Build label parts only for non-null levels
|
|
528
|
+
const parts = [];
|
|
529
|
+
// Level 0 is always present
|
|
530
|
+
parts.push(formatField(level0, getFieldValue(level0)));
|
|
531
|
+
// Level 1 (optional)
|
|
532
|
+
if (level1 !== null) {
|
|
533
|
+
parts.push(formatField(level1, getFieldValue(level1)));
|
|
527
534
|
}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
const level0Value = formatField(level0, getFieldValue(level0));
|
|
532
|
-
const level1Value = formatField(level1, getFieldValue(level1));
|
|
533
|
-
return `${level0Value}_${level1Value}_${cellLabel}`;
|
|
535
|
+
// Level 2 (optional)
|
|
536
|
+
if (level2 !== null) {
|
|
537
|
+
parts.push(formatField(level2, getFieldValue(level2)));
|
|
534
538
|
}
|
|
539
|
+
// Join with arrow separator
|
|
540
|
+
return parts.join('_');
|
|
535
541
|
}
|