@smartnet360/svelte-components 0.0.109 → 0.0.111
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/dist/core/CellTable/CellTableDemo.svelte +44 -0
- package/dist/core/CellTable/CellTablePanel.svelte +106 -11
- package/dist/core/CellTable/CellTablePanel.svelte.d.ts +6 -0
- package/dist/core/CellTable/CellTableToolbar.svelte +2 -1
- package/dist/core/CellTable/column-config.d.ts +12 -0
- package/dist/core/CellTable/column-config.js +129 -48
- package/dist/core/CellTable/types.d.ts +2 -1
- package/dist/shared/demo/cell-types.d.ts +2 -2
- package/package.json +1 -1
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
let groupBy: CellTableGroupField = $state('none');
|
|
11
11
|
let columnPreset: ColumnPreset = $state('default');
|
|
12
|
+
let searchTerm = $state('');
|
|
12
13
|
|
|
13
14
|
function handleSelectionChange(event: RowSelectionEvent) {
|
|
14
15
|
console.log('Selection changed:', event.ids);
|
|
@@ -17,6 +18,28 @@
|
|
|
17
18
|
function regenerateData() {
|
|
18
19
|
demoCells = generateCellsFromPreset(datasetSize);
|
|
19
20
|
}
|
|
21
|
+
|
|
22
|
+
function handleSearch() {
|
|
23
|
+
const term = searchTerm.trim().toLowerCase();
|
|
24
|
+
if (!term) {
|
|
25
|
+
// Reset to full dataset
|
|
26
|
+
demoCells = generateCellsFromPreset(datasetSize);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
// Filter by siteId (case-insensitive, partial match)
|
|
30
|
+
const allCells = generateCellsFromPreset(datasetSize);
|
|
31
|
+
demoCells = allCells.filter(cell =>
|
|
32
|
+
cell.siteId.toLowerCase().includes(term) ||
|
|
33
|
+
cell.cellName.toLowerCase().includes(term)
|
|
34
|
+
);
|
|
35
|
+
console.log(`Search "${term}": found ${demoCells.length} cells`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function handleSearchKeydown(event: KeyboardEvent) {
|
|
39
|
+
if (event.key === 'Enter') {
|
|
40
|
+
handleSearch();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
20
43
|
</script>
|
|
21
44
|
|
|
22
45
|
<div class="cell-table-page vh-100 d-flex flex-column">
|
|
@@ -55,6 +78,27 @@
|
|
|
55
78
|
title="Cell Data"
|
|
56
79
|
onselectionchange={handleSelectionChange}
|
|
57
80
|
>
|
|
81
|
+
{#snippet headerSearch()}
|
|
82
|
+
<div class="input-group input-group-sm" style="width: 180px;">
|
|
83
|
+
<input
|
|
84
|
+
type="text"
|
|
85
|
+
class="form-control"
|
|
86
|
+
placeholder="Search site or cell..."
|
|
87
|
+
bind:value={searchTerm}
|
|
88
|
+
onkeydown={handleSearchKeydown}
|
|
89
|
+
/>
|
|
90
|
+
<button
|
|
91
|
+
class="btn btn-outline-primary"
|
|
92
|
+
type="button"
|
|
93
|
+
onclick={handleSearch}
|
|
94
|
+
title="Search"
|
|
95
|
+
aria-label="Search"
|
|
96
|
+
>
|
|
97
|
+
<i class="bi bi-search"></i>
|
|
98
|
+
</button>
|
|
99
|
+
</div>
|
|
100
|
+
{/snippet}
|
|
101
|
+
|
|
58
102
|
{#snippet footer({ selectedRows, selectedCount })}
|
|
59
103
|
<div class="d-flex align-items-center justify-content-between">
|
|
60
104
|
<span class="text-muted small">
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import CellTable from './CellTable.svelte';
|
|
4
4
|
import CellTableToolbar from './CellTableToolbar.svelte';
|
|
5
|
-
import { getColumnMetadata, getPresetVisibleFields } from './column-config';
|
|
5
|
+
import { getColumnMetadata, getPresetVisibleFields, DEFAULT_TECH_COLORS, DEFAULT_STATUS_COLORS, FBAND_COLORS } from './column-config';
|
|
6
6
|
import type {
|
|
7
7
|
CellData,
|
|
8
8
|
CellTableGroupField,
|
|
@@ -44,6 +44,10 @@
|
|
|
44
44
|
showDetailsSidebar?: boolean;
|
|
45
45
|
/** Sidebar width in pixels */
|
|
46
46
|
sidebarWidth?: number;
|
|
47
|
+
/** Persist settings (groupBy, visibleColumns) to localStorage */
|
|
48
|
+
persistSettings?: boolean;
|
|
49
|
+
/** Storage key prefix for persisted settings */
|
|
50
|
+
storageKey?: string;
|
|
47
51
|
/** Bindable reference to table methods */
|
|
48
52
|
tableRef?: { redraw: () => void } | null;
|
|
49
53
|
/** Row selection change event */
|
|
@@ -52,6 +56,8 @@
|
|
|
52
56
|
onrowclick?: (event: RowClickEvent) => void;
|
|
53
57
|
/** Row double-click event */
|
|
54
58
|
onrowdblclick?: (event: RowDblClickEvent) => void;
|
|
59
|
+
/** Custom header search slot (appears next to title) */
|
|
60
|
+
headerSearch?: Snippet;
|
|
55
61
|
/** Custom header actions slot */
|
|
56
62
|
headerActions?: Snippet;
|
|
57
63
|
/** Custom footer slot */
|
|
@@ -75,15 +81,81 @@
|
|
|
75
81
|
title = 'Cell Data',
|
|
76
82
|
showDetailsSidebar = false,
|
|
77
83
|
sidebarWidth = 320,
|
|
84
|
+
persistSettings = true,
|
|
85
|
+
storageKey = 'cell-table',
|
|
78
86
|
tableRef = $bindable(null),
|
|
79
87
|
onselectionchange,
|
|
80
88
|
onrowclick,
|
|
81
89
|
onrowdblclick,
|
|
90
|
+
headerSearch,
|
|
82
91
|
headerActions,
|
|
83
92
|
footer,
|
|
84
93
|
detailsContent
|
|
85
94
|
}: Props = $props();
|
|
86
95
|
|
|
96
|
+
// Storage keys
|
|
97
|
+
const STORAGE_KEY_GROUP = `${storageKey}-groupBy`;
|
|
98
|
+
const STORAGE_KEY_COLUMNS = `${storageKey}-visibleColumns`;
|
|
99
|
+
const STORAGE_KEY_FILTERS = `${storageKey}-filtersVisible`;
|
|
100
|
+
|
|
101
|
+
// Load persisted settings
|
|
102
|
+
function loadPersistedSettings() {
|
|
103
|
+
if (!persistSettings || typeof localStorage === 'undefined') return { columns: null, filtersVisible: true };
|
|
104
|
+
|
|
105
|
+
let columns: string[] | null = null;
|
|
106
|
+
let filters = true;
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const savedGroup = localStorage.getItem(STORAGE_KEY_GROUP);
|
|
110
|
+
if (savedGroup) {
|
|
111
|
+
groupBy = savedGroup as CellTableGroupField;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const savedColumns = localStorage.getItem(STORAGE_KEY_COLUMNS);
|
|
115
|
+
if (savedColumns) {
|
|
116
|
+
columns = JSON.parse(savedColumns) as string[];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const savedFilters = localStorage.getItem(STORAGE_KEY_FILTERS);
|
|
120
|
+
if (savedFilters !== null) {
|
|
121
|
+
filters = savedFilters === 'true';
|
|
122
|
+
}
|
|
123
|
+
} catch (e) {
|
|
124
|
+
console.warn('Failed to load CellTable settings:', e);
|
|
125
|
+
}
|
|
126
|
+
return { columns, filtersVisible: filters };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Save group setting
|
|
130
|
+
function saveGroupSetting(group: CellTableGroupField) {
|
|
131
|
+
if (!persistSettings || typeof localStorage === 'undefined') return;
|
|
132
|
+
try {
|
|
133
|
+
localStorage.setItem(STORAGE_KEY_GROUP, group);
|
|
134
|
+
} catch (e) {
|
|
135
|
+
console.warn('Failed to save group setting:', e);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Save column visibility
|
|
140
|
+
function saveColumnVisibility(columns: string[]) {
|
|
141
|
+
if (!persistSettings || typeof localStorage === 'undefined') return;
|
|
142
|
+
try {
|
|
143
|
+
localStorage.setItem(STORAGE_KEY_COLUMNS, JSON.stringify(columns));
|
|
144
|
+
} catch (e) {
|
|
145
|
+
console.warn('Failed to save column visibility:', e);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Save filter visibility
|
|
150
|
+
function saveFilterVisibility(visible: boolean) {
|
|
151
|
+
if (!persistSettings || typeof localStorage === 'undefined') return;
|
|
152
|
+
try {
|
|
153
|
+
localStorage.setItem(STORAGE_KEY_FILTERS, String(visible));
|
|
154
|
+
} catch (e) {
|
|
155
|
+
console.warn('Failed to save filter visibility:', e);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
87
159
|
let cellTable: CellTable;
|
|
88
160
|
let selectedCount = $state(0);
|
|
89
161
|
let selectedRows = $state<CellData[]>([]);
|
|
@@ -91,13 +163,16 @@
|
|
|
91
163
|
let sidebarOpen = $state(false);
|
|
92
164
|
let clickedCell: CellData | null = $state(null);
|
|
93
165
|
let tableRefSet = false;
|
|
94
|
-
let filtersVisible = $state(true);
|
|
95
166
|
|
|
96
167
|
// Column visibility management
|
|
97
168
|
const columnMeta = getColumnMetadata();
|
|
98
|
-
|
|
169
|
+
|
|
170
|
+
// Initialize from storage or defaults
|
|
171
|
+
const persistedSettings = loadPersistedSettings();
|
|
172
|
+
let filtersVisible = $state(persistedSettings.filtersVisible);
|
|
173
|
+
let visibleColumns = $state<string[]>(persistedSettings.columns ?? getPresetVisibleFields(columnPreset));
|
|
99
174
|
|
|
100
|
-
// Update visible columns when preset changes
|
|
175
|
+
// Update visible columns when preset changes (but not from storage load)
|
|
101
176
|
$effect(() => {
|
|
102
177
|
visibleColumns = getPresetVisibleFields(columnPreset);
|
|
103
178
|
});
|
|
@@ -109,6 +184,10 @@
|
|
|
109
184
|
tableRef = {
|
|
110
185
|
redraw: () => cellTable?.redraw()
|
|
111
186
|
};
|
|
187
|
+
// Apply persisted filter visibility after table is ready
|
|
188
|
+
if (!filtersVisible) {
|
|
189
|
+
setTimeout(() => cellTable?.toggleHeaderFilters(false), 100);
|
|
190
|
+
}
|
|
112
191
|
}
|
|
113
192
|
});
|
|
114
193
|
|
|
@@ -133,6 +212,7 @@
|
|
|
133
212
|
|
|
134
213
|
function handleGroupChange(group: CellTableGroupField) {
|
|
135
214
|
groupBy = group;
|
|
215
|
+
saveGroupSetting(group);
|
|
136
216
|
}
|
|
137
217
|
|
|
138
218
|
function handlePresetChange(preset: ColumnPreset) {
|
|
@@ -162,6 +242,7 @@
|
|
|
162
242
|
function handleToggleFilters() {
|
|
163
243
|
filtersVisible = !filtersVisible;
|
|
164
244
|
cellTable?.toggleHeaderFilters(filtersVisible);
|
|
245
|
+
saveFilterVisibility(filtersVisible);
|
|
165
246
|
}
|
|
166
247
|
|
|
167
248
|
function handleColumnVisibilityChange(field: string, visible: boolean) {
|
|
@@ -174,6 +255,7 @@
|
|
|
174
255
|
visibleColumns = visibleColumns.filter(f => f !== field);
|
|
175
256
|
cellTable?.hideColumn(field);
|
|
176
257
|
}
|
|
258
|
+
saveColumnVisibility(visibleColumns);
|
|
177
259
|
}
|
|
178
260
|
|
|
179
261
|
function handleResetColumns() {
|
|
@@ -187,6 +269,14 @@
|
|
|
187
269
|
cellTable?.hideColumn(col.field);
|
|
188
270
|
}
|
|
189
271
|
});
|
|
272
|
+
// Clear persisted column visibility (use preset defaults)
|
|
273
|
+
if (persistSettings && typeof localStorage !== 'undefined') {
|
|
274
|
+
try {
|
|
275
|
+
localStorage.removeItem(STORAGE_KEY_COLUMNS);
|
|
276
|
+
} catch (e) {
|
|
277
|
+
console.warn('Failed to clear column visibility:', e);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
190
280
|
}
|
|
191
281
|
|
|
192
282
|
function toggleSidebar() {
|
|
@@ -225,10 +315,15 @@
|
|
|
225
315
|
<div class="cell-table-panel d-flex flex-column" style:height>
|
|
226
316
|
<!-- Header -->
|
|
227
317
|
<div class="panel-header d-flex align-items-center justify-content-between px-3 py-2 bg-body-secondary border-bottom">
|
|
228
|
-
<
|
|
229
|
-
<
|
|
230
|
-
|
|
231
|
-
|
|
318
|
+
<div class="d-flex align-items-center gap-3">
|
|
319
|
+
<h6 class="mb-0 d-flex align-items-center gap-2">
|
|
320
|
+
<i class="bi bi-table text-primary"></i>
|
|
321
|
+
{title}
|
|
322
|
+
</h6>
|
|
323
|
+
{#if headerSearch}
|
|
324
|
+
{@render headerSearch()}
|
|
325
|
+
{/if}
|
|
326
|
+
</div>
|
|
232
327
|
<div class="d-flex align-items-center gap-2">
|
|
233
328
|
{#if headerActions}
|
|
234
329
|
<div class="header-actions">
|
|
@@ -334,12 +429,12 @@
|
|
|
334
429
|
|
|
335
430
|
<dt class="col-5 text-muted">Technology</dt>
|
|
336
431
|
<dd class="col-7">
|
|
337
|
-
<span class="badge
|
|
432
|
+
<span class="badge" style="background-color: {techColors?.[clickedCell.tech] ?? DEFAULT_TECH_COLORS[clickedCell.tech] ?? '#6c757d'}; color: white;">{clickedCell.tech}</span>
|
|
338
433
|
</dd>
|
|
339
434
|
|
|
340
435
|
<dt class="col-5 text-muted">Band</dt>
|
|
341
436
|
<dd class="col-7">
|
|
342
|
-
<span class="badge
|
|
437
|
+
<span class="badge" style="background-color: {FBAND_COLORS[clickedCell.fband] ?? '#6c757d'}; color: white;">{clickedCell.fband}</span>
|
|
343
438
|
</dd>
|
|
344
439
|
|
|
345
440
|
<dt class="col-5 text-muted">Frequency</dt>
|
|
@@ -347,7 +442,7 @@
|
|
|
347
442
|
|
|
348
443
|
<dt class="col-5 text-muted">Status</dt>
|
|
349
444
|
<dd class="col-7">
|
|
350
|
-
<span class="badge
|
|
445
|
+
<span class="badge" style="background-color: {statusColors?.[clickedCell.status] ?? DEFAULT_STATUS_COLORS[clickedCell.status] ?? '#6c757d'}; color: white;">{clickedCell.status.replace(/_/g, ' ')}</span>
|
|
351
446
|
</dd>
|
|
352
447
|
|
|
353
448
|
<dt class="col-12 text-muted mt-2 mb-1 border-top pt-2">Physical</dt>
|
|
@@ -29,6 +29,10 @@ interface Props {
|
|
|
29
29
|
showDetailsSidebar?: boolean;
|
|
30
30
|
/** Sidebar width in pixels */
|
|
31
31
|
sidebarWidth?: number;
|
|
32
|
+
/** Persist settings (groupBy, visibleColumns) to localStorage */
|
|
33
|
+
persistSettings?: boolean;
|
|
34
|
+
/** Storage key prefix for persisted settings */
|
|
35
|
+
storageKey?: string;
|
|
32
36
|
/** Bindable reference to table methods */
|
|
33
37
|
tableRef?: {
|
|
34
38
|
redraw: () => void;
|
|
@@ -39,6 +43,8 @@ interface Props {
|
|
|
39
43
|
onrowclick?: (event: RowClickEvent) => void;
|
|
40
44
|
/** Row double-click event */
|
|
41
45
|
onrowdblclick?: (event: RowDblClickEvent) => void;
|
|
46
|
+
/** Custom header search slot (appears next to title) */
|
|
47
|
+
headerSearch?: Snippet;
|
|
42
48
|
/** Custom header actions slot */
|
|
43
49
|
headerActions?: Snippet;
|
|
44
50
|
/** Custom footer slot */
|
|
@@ -87,7 +87,8 @@
|
|
|
87
87
|
{ value: 'full', label: 'All Columns' },
|
|
88
88
|
{ value: 'physical', label: 'Physical' },
|
|
89
89
|
{ value: 'network', label: 'Network' },
|
|
90
|
-
{ value: 'planning', label: 'Planning' }
|
|
90
|
+
{ value: 'planning', label: 'Planning' },
|
|
91
|
+
{ value: 'compare', label: 'Compare (Atoll/Nwt)' }
|
|
91
92
|
];
|
|
92
93
|
|
|
93
94
|
function handleGroupChange(e: Event) {
|
|
@@ -7,6 +7,7 @@ import type { ColumnDefinition, CellComponent } from 'tabulator-tables';
|
|
|
7
7
|
import type { CellTableColumn, ColumnPreset, TechColorMap, StatusColorMap, ColumnGroups } from './types';
|
|
8
8
|
/**
|
|
9
9
|
* Default technology colors (Bootstrap-aligned)
|
|
10
|
+
* 2G: Yellow, 4G: Purple, 5G: Green
|
|
10
11
|
*/
|
|
11
12
|
export declare const DEFAULT_TECH_COLORS: TechColorMap;
|
|
12
13
|
/**
|
|
@@ -15,6 +16,9 @@ export declare const DEFAULT_TECH_COLORS: TechColorMap;
|
|
|
15
16
|
export declare const DEFAULT_STATUS_COLORS: StatusColorMap;
|
|
16
17
|
/**
|
|
17
18
|
* Frequency band colors
|
|
19
|
+
* GSM: Yellow shades (darker = higher freq)
|
|
20
|
+
* LTE: Purple shades (darker = higher freq)
|
|
21
|
+
* 5G: Green shades (stronger = higher freq)
|
|
18
22
|
*/
|
|
19
23
|
export declare const FBAND_COLORS: Record<string, string>;
|
|
20
24
|
/**
|
|
@@ -49,6 +53,14 @@ export declare function azimuthFormatter(cell: CellComponent): string;
|
|
|
49
53
|
* Height formatter with meter unit
|
|
50
54
|
*/
|
|
51
55
|
export declare function heightFormatter(cell: CellComponent): string;
|
|
56
|
+
/**
|
|
57
|
+
* Comparison formatter for Atoll vs Network values
|
|
58
|
+
* Shows both values side by side with color coding:
|
|
59
|
+
* - Green: values match exactly
|
|
60
|
+
* - Red: values don't match
|
|
61
|
+
* - Gray: one or both values missing
|
|
62
|
+
*/
|
|
63
|
+
export declare function createCompareFormatter(atollField: string, nwtField: string): (cell: CellComponent) => string;
|
|
52
64
|
/**
|
|
53
65
|
* Custom sorter for fband - extracts numeric portion and sorts numerically
|
|
54
66
|
* Examples: LTE700 → 700, GSM900 → 900, LTE1800 → 1800, 5G-3500 → 3500
|
|
@@ -5,52 +5,61 @@
|
|
|
5
5
|
*/
|
|
6
6
|
/**
|
|
7
7
|
* Default technology colors (Bootstrap-aligned)
|
|
8
|
+
* 2G: Yellow, 4G: Purple, 5G: Green
|
|
8
9
|
*/
|
|
9
10
|
export const DEFAULT_TECH_COLORS = {
|
|
10
|
-
'2G': '#
|
|
11
|
+
'2G': '#ffc107', // Yellow
|
|
11
12
|
'3G': '#0d6efd', // primary blue
|
|
12
|
-
'4G': '#
|
|
13
|
-
'5G': '#
|
|
14
|
-
'LTE': '#
|
|
15
|
-
'NR': '#
|
|
13
|
+
'4G': '#9333ea', // Purple
|
|
14
|
+
'5G': '#22c55e', // Green
|
|
15
|
+
'LTE': '#9333ea', // Purple (same as 4G)
|
|
16
|
+
'NR': '#22c55e', // Green (same as 5G)
|
|
16
17
|
};
|
|
17
18
|
/**
|
|
18
19
|
* Default status colors (Bootstrap-aligned)
|
|
19
20
|
*/
|
|
20
21
|
export const DEFAULT_STATUS_COLORS = {
|
|
21
22
|
'On_Air': '#198754', // success green
|
|
22
|
-
'On_Air_UNDER_CONSTRUCTION': '#
|
|
23
|
-
'On_Air_Locked': '#
|
|
24
|
-
'RF_Plan_Ready': '#
|
|
25
|
-
'Re-Planned_RF_Plan_Ready': '#
|
|
26
|
-
'Tavlati_RF_Plan_Ready': '#
|
|
23
|
+
'On_Air_UNDER_CONSTRUCTION': '#4ce207ff', // warning yellow
|
|
24
|
+
'On_Air_Locked': '#e04001ff', // info cyan
|
|
25
|
+
'RF_Plan_Ready': '#e69603ff', // primary blue
|
|
26
|
+
'Re-Planned_RF_Plan_Ready': '#c1a342ff', // purple
|
|
27
|
+
'Tavlati_RF_Plan_Ready': '#25ddfdff', // orange
|
|
27
28
|
};
|
|
28
29
|
/**
|
|
29
30
|
* Frequency band colors
|
|
31
|
+
* GSM: Yellow shades (darker = higher freq)
|
|
32
|
+
* LTE: Purple shades (darker = higher freq)
|
|
33
|
+
* 5G: Green shades (stronger = higher freq)
|
|
30
34
|
*/
|
|
31
35
|
export const FBAND_COLORS = {
|
|
32
|
-
|
|
33
|
-
'
|
|
34
|
-
'
|
|
35
|
-
|
|
36
|
-
'
|
|
37
|
-
'
|
|
38
|
-
'
|
|
39
|
-
'
|
|
40
|
-
'
|
|
41
|
-
'
|
|
42
|
-
|
|
36
|
+
// GSM - Yellow shades (900 lighter, 1800 darker)
|
|
37
|
+
'GSM900': '#ffc107', // Light yellow
|
|
38
|
+
'GSM1800': '#cc9a06', // Darker yellow/gold
|
|
39
|
+
// LTE - Purple shades (700 lightest → 2600 darkest)
|
|
40
|
+
'LTE700': '#d8b4fe', // Light purple
|
|
41
|
+
'LTE800': '#c084fc', //
|
|
42
|
+
'LTE900': '#a855f7', //
|
|
43
|
+
'LTE1800': '#9333ea', //
|
|
44
|
+
'LTE2100': '#7e22ce', //
|
|
45
|
+
'LTE2600': '#6b21a8', // Dark purple
|
|
46
|
+
// 5G - Green shades (700 lighter → 3500 stronger)
|
|
47
|
+
'5G-700': '#86efac', // Light green
|
|
48
|
+
'5G-2100': '#22c55e', // Medium green
|
|
49
|
+
'5G-3500': '#15803d', // Strong/dark green
|
|
50
|
+
'NR3500': '#15803d', // Same as 5G-3500
|
|
43
51
|
};
|
|
44
52
|
/**
|
|
45
53
|
* Column groups for different presets
|
|
46
54
|
*/
|
|
47
55
|
export const COLUMN_GROUPS = {
|
|
48
|
-
core: ['
|
|
56
|
+
core: ['siteId', 'txId', 'cellName', 'tech', 'fband', 'status'],
|
|
49
57
|
physical: ['antenna', 'azimuth', 'height', 'electricalTilt', 'beamwidth'],
|
|
50
|
-
network: ['dlEarfn', 'bcch', 'pci1', 'cellId3', '
|
|
58
|
+
network: ['dlEarfn', 'bcch', 'pci1', 'cellId3', 'nwtET', 'nwtPW', 'nwtRS', 'nwtBW'],
|
|
51
59
|
planning: ['planner', 'comment', 'onAirDate', 'type'],
|
|
52
|
-
atoll: ['
|
|
60
|
+
atoll: ['atollET', 'atollPW', 'atollRS', 'atollBW'],
|
|
53
61
|
position: ['latitude', 'longitude', 'siteLatitude', 'siteLongitude', 'dx', 'dy'],
|
|
62
|
+
compare: ['compareET', 'comparePW', 'compareRS', 'compareBW'],
|
|
54
63
|
};
|
|
55
64
|
/**
|
|
56
65
|
* Create a technology badge formatter
|
|
@@ -123,6 +132,40 @@ export function heightFormatter(cell) {
|
|
|
123
132
|
return '';
|
|
124
133
|
return `${value}m`;
|
|
125
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Comparison formatter for Atoll vs Network values
|
|
137
|
+
* Shows both values side by side with color coding:
|
|
138
|
+
* - Green: values match exactly
|
|
139
|
+
* - Red: values don't match
|
|
140
|
+
* - Gray: one or both values missing
|
|
141
|
+
*/
|
|
142
|
+
export function createCompareFormatter(atollField, nwtField) {
|
|
143
|
+
return (cell) => {
|
|
144
|
+
const row = cell.getRow().getData();
|
|
145
|
+
const atollVal = row[atollField];
|
|
146
|
+
const nwtVal = row[nwtField];
|
|
147
|
+
const atollStr = atollVal !== null && atollVal !== undefined ? String(atollVal) : '-';
|
|
148
|
+
const nwtStr = nwtVal !== null && nwtVal !== undefined ? String(nwtVal) : '-';
|
|
149
|
+
// Determine match status
|
|
150
|
+
const atollMissing = atollVal === null || atollVal === undefined;
|
|
151
|
+
const nwtMissing = nwtVal === null || nwtVal === undefined;
|
|
152
|
+
let bgColor;
|
|
153
|
+
let textColor = 'white';
|
|
154
|
+
if (atollMissing || nwtMissing) {
|
|
155
|
+
// One or both missing - gray
|
|
156
|
+
bgColor = '#6c757d';
|
|
157
|
+
}
|
|
158
|
+
else if (atollVal === nwtVal) {
|
|
159
|
+
// Exact match - green
|
|
160
|
+
bgColor = '#198754';
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
// Mismatch - red
|
|
164
|
+
bgColor = '#dc3545';
|
|
165
|
+
}
|
|
166
|
+
return `<span class="badge" style="background-color: ${bgColor}; color: ${textColor}; font-size: 0.75rem; font-weight: normal;">${atollStr} | ${nwtStr}</span>`;
|
|
167
|
+
};
|
|
168
|
+
}
|
|
126
169
|
/**
|
|
127
170
|
* Custom sorter for fband - extracts numeric portion and sorts numerically
|
|
128
171
|
* Examples: LTE700 → 700, GSM900 → 900, LTE1800 → 1800, 5G-3500 → 3500
|
|
@@ -170,24 +213,26 @@ export function getAllColumns(techColors = DEFAULT_TECH_COLORS, statusColors = D
|
|
|
170
213
|
const headerFilterParams = headerFilters ? { headerFilter: 'input' } : {};
|
|
171
214
|
const selectHeaderFilter = headerFilters ? { headerFilter: 'list', headerFilterParams: { valuesLookup: true } } : {};
|
|
172
215
|
return [
|
|
173
|
-
// Core columns
|
|
216
|
+
// Core columns - siteId, txId, cellName first (frozen)
|
|
174
217
|
{
|
|
175
|
-
title: 'ID',
|
|
176
|
-
field: '
|
|
218
|
+
title: 'Site ID',
|
|
219
|
+
field: 'siteId',
|
|
177
220
|
width: 120,
|
|
178
221
|
frozen: true,
|
|
179
222
|
...headerFilterParams,
|
|
180
223
|
},
|
|
181
224
|
{
|
|
182
|
-
title: '
|
|
183
|
-
field: '
|
|
184
|
-
width:
|
|
225
|
+
title: 'TX ID',
|
|
226
|
+
field: 'txId',
|
|
227
|
+
width: 100,
|
|
228
|
+
frozen: true,
|
|
185
229
|
...headerFilterParams,
|
|
186
230
|
},
|
|
187
231
|
{
|
|
188
|
-
title: '
|
|
189
|
-
field: '
|
|
190
|
-
width:
|
|
232
|
+
title: 'Cell Name',
|
|
233
|
+
field: 'cellName',
|
|
234
|
+
width: 150,
|
|
235
|
+
frozen: true,
|
|
191
236
|
...headerFilterParams,
|
|
192
237
|
},
|
|
193
238
|
{
|
|
@@ -310,12 +355,6 @@ export function getAllColumns(techColors = DEFAULT_TECH_COLORS, statusColors = D
|
|
|
310
355
|
width: 100,
|
|
311
356
|
...headerFilterParams,
|
|
312
357
|
},
|
|
313
|
-
{
|
|
314
|
-
title: 'TX ID',
|
|
315
|
-
field: 'txId',
|
|
316
|
-
width: 100,
|
|
317
|
-
...headerFilterParams,
|
|
318
|
-
},
|
|
319
358
|
{
|
|
320
359
|
title: 'Ctrl ID',
|
|
321
360
|
field: 'ctrlid',
|
|
@@ -352,8 +391,8 @@ export function getAllColumns(techColors = DEFAULT_TECH_COLORS, statusColors = D
|
|
|
352
391
|
},
|
|
353
392
|
// Atoll columns
|
|
354
393
|
{
|
|
355
|
-
title: 'Atoll
|
|
356
|
-
field: '
|
|
394
|
+
title: 'Atoll ET',
|
|
395
|
+
field: 'atollET',
|
|
357
396
|
width: 90,
|
|
358
397
|
hozAlign: 'right',
|
|
359
398
|
formatter: numberFormatter(1),
|
|
@@ -383,6 +422,39 @@ export function getAllColumns(techColors = DEFAULT_TECH_COLORS, statusColors = D
|
|
|
383
422
|
formatter: numberFormatter(1),
|
|
384
423
|
...headerFilterParams,
|
|
385
424
|
},
|
|
425
|
+
// Compare columns (Atoll vs Network)
|
|
426
|
+
{
|
|
427
|
+
title: 'Δ ET',
|
|
428
|
+
field: 'compareET',
|
|
429
|
+
width: 110,
|
|
430
|
+
hozAlign: 'center',
|
|
431
|
+
formatter: createCompareFormatter('atollET', 'nwtET'),
|
|
432
|
+
headerTooltip: 'Atoll ET | Network ET',
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
title: 'Δ PW',
|
|
436
|
+
field: 'comparePW',
|
|
437
|
+
width: 110,
|
|
438
|
+
hozAlign: 'center',
|
|
439
|
+
formatter: createCompareFormatter('atollPW', 'nwtPW'),
|
|
440
|
+
headerTooltip: 'Atoll PW | Network PW',
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
title: 'Δ RS',
|
|
444
|
+
field: 'compareRS',
|
|
445
|
+
width: 110,
|
|
446
|
+
hozAlign: 'center',
|
|
447
|
+
formatter: createCompareFormatter('atollRS', 'nwtRS'),
|
|
448
|
+
headerTooltip: 'Atoll RS | Network RS',
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
title: 'Δ BW',
|
|
452
|
+
field: 'compareBW',
|
|
453
|
+
width: 110,
|
|
454
|
+
hozAlign: 'center',
|
|
455
|
+
formatter: createCompareFormatter('atollBW', 'nwtBW'),
|
|
456
|
+
headerTooltip: 'Atoll BW | Network BW',
|
|
457
|
+
},
|
|
386
458
|
// Position columns
|
|
387
459
|
{
|
|
388
460
|
title: 'Latitude',
|
|
@@ -461,7 +533,7 @@ export function getColumnsForPreset(preset, techColors = DEFAULT_TECH_COLORS, st
|
|
|
461
533
|
let visibleFields;
|
|
462
534
|
switch (preset) {
|
|
463
535
|
case 'compact':
|
|
464
|
-
visibleFields = ['
|
|
536
|
+
visibleFields = ['siteId', 'txId', 'cellName', 'tech', 'fband', 'status'];
|
|
465
537
|
break;
|
|
466
538
|
case 'full':
|
|
467
539
|
return allColumns;
|
|
@@ -474,10 +546,13 @@ export function getColumnsForPreset(preset, techColors = DEFAULT_TECH_COLORS, st
|
|
|
474
546
|
case 'planning':
|
|
475
547
|
visibleFields = [...COLUMN_GROUPS.core, ...COLUMN_GROUPS.planning];
|
|
476
548
|
break;
|
|
549
|
+
case 'compare':
|
|
550
|
+
visibleFields = [...COLUMN_GROUPS.core, ...COLUMN_GROUPS.compare];
|
|
551
|
+
break;
|
|
477
552
|
case 'default':
|
|
478
553
|
default:
|
|
479
554
|
visibleFields = [
|
|
480
|
-
'
|
|
555
|
+
'siteId', 'txId', 'cellName', 'tech', 'fband', 'frq', 'status',
|
|
481
556
|
'azimuth', 'height', 'antenna'
|
|
482
557
|
];
|
|
483
558
|
}
|
|
@@ -509,9 +584,9 @@ export function getGroupHeaderFormatter(groupField) {
|
|
|
509
584
|
export function getColumnMetadata() {
|
|
510
585
|
return [
|
|
511
586
|
// Core
|
|
512
|
-
{ field: 'id', title: 'ID', group: 'Core' },
|
|
513
|
-
{ field: 'cellName', title: 'Cell Name', group: 'Core' },
|
|
514
587
|
{ field: 'siteId', title: 'Site ID', group: 'Core' },
|
|
588
|
+
{ field: 'txId', title: 'TX ID', group: 'Core' },
|
|
589
|
+
{ field: 'cellName', title: 'Cell Name', group: 'Core' },
|
|
515
590
|
{ field: 'tech', title: 'Technology', group: 'Core' },
|
|
516
591
|
{ field: 'fband', title: 'Band', group: 'Core' },
|
|
517
592
|
{ field: 'frq', title: 'Frequency', group: 'Core' },
|
|
@@ -531,7 +606,6 @@ export function getColumnMetadata() {
|
|
|
531
606
|
{ field: 'rru', title: 'RRU', group: 'Network' },
|
|
532
607
|
{ field: 'cellID', title: 'Cell ID', group: 'Network' },
|
|
533
608
|
{ field: 'cellId2G', title: 'Cell ID 2G', group: 'Network' },
|
|
534
|
-
{ field: 'txId', title: 'TX ID', group: 'Network' },
|
|
535
609
|
{ field: 'ctrlid', title: 'Ctrl ID', group: 'Network' },
|
|
536
610
|
{ field: 'nwtET', title: 'NWT ET', group: 'Network' },
|
|
537
611
|
{ field: 'nwtPW', title: 'NWT PW', group: 'Network' },
|
|
@@ -542,6 +616,11 @@ export function getColumnMetadata() {
|
|
|
542
616
|
{ field: 'atollPW', title: 'Atoll PW', group: 'Atoll' },
|
|
543
617
|
{ field: 'atollRS', title: 'Atoll RS', group: 'Atoll' },
|
|
544
618
|
{ field: 'atollBW', title: 'Atoll BW', group: 'Atoll' },
|
|
619
|
+
// Compare (Atoll vs Network)
|
|
620
|
+
{ field: 'compareET', title: 'Δ ET', group: 'Compare' },
|
|
621
|
+
{ field: 'comparePW', title: 'Δ PW', group: 'Compare' },
|
|
622
|
+
{ field: 'compareRS', title: 'Δ RS', group: 'Compare' },
|
|
623
|
+
{ field: 'compareBW', title: 'Δ BW', group: 'Compare' },
|
|
545
624
|
// Position
|
|
546
625
|
{ field: 'latitude', title: 'Latitude', group: 'Position' },
|
|
547
626
|
{ field: 'longitude', title: 'Longitude', group: 'Position' },
|
|
@@ -561,7 +640,7 @@ export function getColumnMetadata() {
|
|
|
561
640
|
export function getPresetVisibleFields(preset) {
|
|
562
641
|
switch (preset) {
|
|
563
642
|
case 'compact':
|
|
564
|
-
return ['
|
|
643
|
+
return ['siteId', 'txId', 'cellName', 'tech', 'fband', 'status'];
|
|
565
644
|
case 'full':
|
|
566
645
|
return getColumnMetadata().map(c => c.field);
|
|
567
646
|
case 'physical':
|
|
@@ -570,8 +649,10 @@ export function getPresetVisibleFields(preset) {
|
|
|
570
649
|
return [...COLUMN_GROUPS.core, ...COLUMN_GROUPS.network];
|
|
571
650
|
case 'planning':
|
|
572
651
|
return [...COLUMN_GROUPS.core, ...COLUMN_GROUPS.planning];
|
|
652
|
+
case 'compare':
|
|
653
|
+
return [...COLUMN_GROUPS.core, ...COLUMN_GROUPS.compare];
|
|
573
654
|
case 'default':
|
|
574
655
|
default:
|
|
575
|
-
return ['
|
|
656
|
+
return ['siteId', 'txId', 'cellName', 'tech', 'fband', 'frq', 'status', 'azimuth', 'height', 'antenna'];
|
|
576
657
|
}
|
|
577
658
|
}
|
|
@@ -16,7 +16,7 @@ export type CellTableGroupField = CellGroupingField | 'none';
|
|
|
16
16
|
/**
|
|
17
17
|
* Column preset configurations
|
|
18
18
|
*/
|
|
19
|
-
export type ColumnPreset = 'default' | 'compact' | 'full' | 'physical' | 'network' | 'planning';
|
|
19
|
+
export type ColumnPreset = 'default' | 'compact' | 'full' | 'physical' | 'network' | 'planning' | 'compare';
|
|
20
20
|
/**
|
|
21
21
|
* Column visibility configuration
|
|
22
22
|
*/
|
|
@@ -122,4 +122,5 @@ export interface ColumnGroups {
|
|
|
122
122
|
planning: string[];
|
|
123
123
|
atoll: string[];
|
|
124
124
|
position: string[];
|
|
125
|
+
compare: string[];
|
|
125
126
|
}
|
|
@@ -39,12 +39,12 @@ export interface Cell {
|
|
|
39
39
|
atollPW?: number;
|
|
40
40
|
atollRS?: number;
|
|
41
41
|
atollBW?: number;
|
|
42
|
-
rru: string;
|
|
43
42
|
nwtET?: number;
|
|
44
43
|
nwtPW?: number;
|
|
45
|
-
pci?: number;
|
|
46
44
|
nwtRS?: number;
|
|
47
45
|
nwtBW?: number;
|
|
46
|
+
pci?: number;
|
|
47
|
+
rru: string;
|
|
48
48
|
other?: Record<string, unknown>;
|
|
49
49
|
customSubgroup: string;
|
|
50
50
|
}
|