@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.
@@ -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
- let visibleColumns = $state<string[]>(getPresetVisibleFields(columnPreset));
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
- <h6 class="mb-0 d-flex align-items-center gap-2">
229
- <i class="bi bi-table text-primary"></i>
230
- {title}
231
- </h6>
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 bg-secondary">{clickedCell.tech}</span>
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 bg-info">{clickedCell.fband}</span>
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 bg-success">{clickedCell.status.replace(/_/g, ' ')}</span>
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': '#6c757d', // secondary gray
11
+ '2G': '#ffc107', // Yellow
11
12
  '3G': '#0d6efd', // primary blue
12
- '4G': '#198754', // success green
13
- '5G': '#6f42c1', // purple
14
- 'LTE': '#198754', // success green
15
- 'NR': '#6f42c1', // purple
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': '#ffc107', // warning yellow
23
- 'On_Air_Locked': '#0dcaf0', // info cyan
24
- 'RF_Plan_Ready': '#0d6efd', // primary blue
25
- 'Re-Planned_RF_Plan_Ready': '#6f42c1', // purple
26
- 'Tavlati_RF_Plan_Ready': '#fd7e14', // orange
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
- 'GSM900': '#6c757d',
33
- 'GSM1800': '#495057',
34
- 'LTE700': '#dc3545',
35
- 'LTE800': '#e35d6a',
36
- 'LTE900': '#fd7e14',
37
- 'LTE1800': '#198754',
38
- 'LTE2100': '#20c997',
39
- 'LTE2600': '#0dcaf0',
40
- '5G-700': '#6f42c1',
41
- '5G-3500': '#d63384',
42
- 'NR3500': '#d63384',
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: ['id', 'cellName', 'siteId', 'tech', 'fband', 'status'],
56
+ core: ['siteId', 'txId', 'cellName', 'tech', 'fband', 'status'],
49
57
  physical: ['antenna', 'azimuth', 'height', 'electricalTilt', 'beamwidth'],
50
- network: ['dlEarfn', 'bcch', 'pci1', 'cellId3', 'nwtP1', 'nwtP2', 'nwtRS', 'nwtBW'],
58
+ network: ['dlEarfn', 'bcch', 'pci1', 'cellId3', 'nwtET', 'nwtPW', 'nwtRS', 'nwtBW'],
51
59
  planning: ['planner', 'comment', 'onAirDate', 'type'],
52
- atoll: ['atollETP', 'atollPW', 'atollRS', 'atollBW'],
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: 'id',
218
+ title: 'Site ID',
219
+ field: 'siteId',
177
220
  width: 120,
178
221
  frozen: true,
179
222
  ...headerFilterParams,
180
223
  },
181
224
  {
182
- title: 'Cell Name',
183
- field: 'cellName',
184
- width: 150,
225
+ title: 'TX ID',
226
+ field: 'txId',
227
+ width: 100,
228
+ frozen: true,
185
229
  ...headerFilterParams,
186
230
  },
187
231
  {
188
- title: 'Site ID',
189
- field: 'siteId',
190
- width: 120,
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 ETP',
356
- field: 'atollETP',
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 = ['cellName', 'siteId', 'tech', 'fband', 'status'];
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
- 'id', 'cellName', 'siteId', 'tech', 'fband', 'frq', 'status',
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 ['cellName', 'siteId', 'tech', 'fband', 'status'];
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 ['id', 'cellName', 'siteId', 'tech', 'fband', 'frq', 'status', 'azimuth', 'height', 'antenna'];
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartnet360/svelte-components",
3
- "version": "0.0.109",
3
+ "version": "0.0.111",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",