@smartnet360/svelte-components 0.0.59 → 0.0.61

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.
@@ -1,12 +1,13 @@
1
1
  /**
2
2
  * Demo Cell Data
3
3
  *
4
- * 1 site with 3 sectors (azimuths: 0°, 120°, 240°)
4
+ * 100 sites across San Francisco Bay Area
5
+ * Each site has 3 sectors (azimuths: 0°, 120°, 240°)
5
6
  * Each sector has 12 cells (all tech-band combinations)
6
- * Total: 36 cells
7
+ * Total: 100 sites × 3 sectors × 12 tech-bands = 3,600 cells
7
8
  */
8
9
  import type { Cell } from '../features/cells/types';
9
10
  /**
10
- * Generate demo cells: 3 sectors × 12 tech-bands = 36 cells
11
+ * Generate demo cells: 100 sites × 3 sectors × 12 tech-bands = 3,600 cells
11
12
  */
12
13
  export declare const demoCells: Cell[];
@@ -1,14 +1,19 @@
1
1
  /**
2
2
  * Demo Cell Data
3
3
  *
4
- * 1 site with 3 sectors (azimuths: 0°, 120°, 240°)
4
+ * 100 sites across San Francisco Bay Area
5
+ * Each site has 3 sectors (azimuths: 0°, 120°, 240°)
5
6
  * Each sector has 12 cells (all tech-band combinations)
6
- * Total: 36 cells
7
+ * Total: 100 sites × 3 sectors × 12 tech-bands = 3,600 cells
7
8
  */
8
- // Site location: San Francisco
9
- const SITE_LAT = 37.7749;
10
- const SITE_LNG = -122.4194;
11
- const SITE_ID = 'DEMO-SITE-001';
9
+ // Base location: San Francisco
10
+ const BASE_LAT = 37.7749;
11
+ const BASE_LNG = -122.4194;
12
+ // Grid parameters for distributing sites
13
+ const NUM_SITES = 1;
14
+ const GRID_SIZE = 10; // 10×10 grid
15
+ const LAT_SPACING = 0.01; // ~1.1 km spacing
16
+ const LNG_SPACING = 0.015; // ~1.1 km spacing (adjusted for longitude)
12
17
  // Standard beamwidth for sectors
13
18
  const BEAMWIDTH = 65;
14
19
  // Cell tech-band definitions with proper fband format
@@ -46,69 +51,82 @@ const STATUSES = [
46
51
  'On_Air'
47
52
  ];
48
53
  /**
49
- * Generate demo cells: 3 sectors × 12 tech-bands = 36 cells
54
+ * Generate demo cells: 100 sites × 3 sectors × 12 tech-bands = 3,600 cells
50
55
  */
51
56
  export const demoCells = [];
52
57
  let cellCounter = 1;
53
- AZIMUTHS.forEach((azimuth, sectorIndex) => {
54
- TECH_BANDS.forEach((techBand, techIndex) => {
55
- const cellId = `CELL-${String(cellCounter).padStart(3, '0')}`;
56
- const status = STATUSES[techIndex];
57
- demoCells.push({
58
- // Core properties
59
- id: cellId,
60
- txId: `TX-${String(cellCounter).padStart(3, '0')}`,
61
- cellID: cellId,
62
- cellID2G: techBand.tech === '2G' ? cellId : '',
63
- cellName: `Demo Cell ${techBand.tech} ${techBand.band} - Sector ${sectorIndex + 1}`,
64
- siteId: SITE_ID,
65
- tech: techBand.tech,
66
- fband: techBand.fband,
67
- frq: techBand.band,
68
- type: 'MACRO',
69
- status: status,
70
- onAirDate: '2024-01-15',
71
- // 2G specific
72
- bcch: techBand.tech === '2G' ? 100 + techIndex : 0,
73
- ctrlid: techBand.tech === '2G' ? `CTRL-${cellId}` : '',
74
- // 4G specific
75
- dlEarfn: techBand.tech === '4G' ? 6200 + techIndex * 100 : 0,
76
- // Physical properties
77
- antenna: 'DEMO-ANTENNA-MODEL',
78
- azimuth: azimuth,
79
- height: 30, // 30 meters antenna height
80
- electricalTilt: '3',
81
- beamwidth: BEAMWIDTH,
82
- latitude: SITE_LAT,
83
- longitude: SITE_LNG,
84
- dx: 0,
85
- dy: 0,
86
- siteLatitude: SITE_LAT,
87
- siteLongitude: SITE_LNG,
88
- // Planning
89
- comment: `Demo ${techBand.tech} ${techBand.band} cell at azimuth ${azimuth}°`,
90
- planner: 'Demo User',
91
- // Atoll properties
92
- atollETP: 43.0,
93
- atollPW: 20.0,
94
- atollRS: 500.0 + (techBand.band === '700' ? 200 : 0), // Lower freq = longer range
95
- atollBW: parseFloat(techBand.band) / 100, // Simplified bandwidth
96
- // Network properties
97
- cellId3: `${cellId}-3G`,
98
- nwtP1: 20,
99
- nwtP2: 40,
100
- pci1: (cellCounter % 504), // Physical Cell ID for LTE
101
- nwtRS: 450.0,
102
- nwtBW: 10.0,
103
- // Other
104
- other: {
105
- demoCell: true,
106
- sector: sectorIndex + 1,
107
- techBandKey: `${techBand.tech}_${techBand.band}`
108
- },
109
- customSubgroup: `Sector-${sectorIndex + 1}`
58
+ // Generate sites in a grid pattern
59
+ for (let siteIndex = 0; siteIndex < NUM_SITES; siteIndex++) {
60
+ const row = Math.floor(siteIndex / GRID_SIZE);
61
+ const col = siteIndex % GRID_SIZE;
62
+ // Calculate site position (centered grid around base location)
63
+ const siteLat = BASE_LAT + (row - GRID_SIZE / 2) * LAT_SPACING;
64
+ const siteLng = BASE_LNG + (col - GRID_SIZE / 2) * LNG_SPACING;
65
+ const siteId = `DEMO-SITE-${String(siteIndex + 1).padStart(3, '0')}`;
66
+ // Generate 3 sectors per site
67
+ AZIMUTHS.forEach((azimuth, sectorIndex) => {
68
+ // Generate 12 tech-bands per sector
69
+ TECH_BANDS.forEach((techBand, techIndex) => {
70
+ const cellId = `CELL-${String(cellCounter).padStart(4, '0')}`;
71
+ const status = STATUSES[techIndex];
72
+ demoCells.push({
73
+ // Core properties
74
+ id: cellId,
75
+ txId: `TX-${String(cellCounter).padStart(4, '0')}`,
76
+ cellID: cellId,
77
+ cellID2G: techBand.tech === '2G' ? cellId : '',
78
+ cellName: `${siteId} ${techBand.tech}${techBand.band} S${sectorIndex + 1}`,
79
+ siteId: siteId,
80
+ tech: techBand.tech,
81
+ fband: techBand.fband,
82
+ frq: techBand.band,
83
+ type: 'MACRO',
84
+ status: status,
85
+ onAirDate: '2024-01-15',
86
+ // 2G specific
87
+ bcch: techBand.tech === '2G' ? 100 + techIndex : 0,
88
+ ctrlid: techBand.tech === '2G' ? `CTRL-${cellId}` : '',
89
+ // 4G specific
90
+ dlEarfn: techBand.tech === '4G' ? 6200 + techIndex * 100 : 0,
91
+ // Physical properties
92
+ antenna: 'DEMO-ANTENNA-MODEL',
93
+ azimuth: azimuth,
94
+ height: 30, // 30 meters antenna height
95
+ electricalTilt: '3',
96
+ beamwidth: BEAMWIDTH,
97
+ latitude: siteLat,
98
+ longitude: siteLng,
99
+ dx: 0,
100
+ dy: 0,
101
+ siteLatitude: siteLat,
102
+ siteLongitude: siteLng,
103
+ // Planning
104
+ comment: `Demo ${techBand.tech} ${techBand.band} cell at azimuth ${azimuth}°`,
105
+ planner: 'Demo User',
106
+ // Atoll properties
107
+ atollETP: 43.0,
108
+ atollPW: 20.0,
109
+ atollRS: 500.0 + (techBand.band === '700' ? 200 : 0), // Lower freq = longer range
110
+ atollBW: parseFloat(techBand.band) / 100, // Simplified bandwidth
111
+ // Network properties
112
+ cellId3: `${cellId}-3G`,
113
+ nwtP1: 20,
114
+ nwtP2: 40,
115
+ pci1: (cellCounter % 504), // Physical Cell ID for LTE
116
+ nwtRS: 450.0,
117
+ nwtBW: 10.0,
118
+ // Other
119
+ other: {
120
+ demoCell: true,
121
+ siteNumber: siteIndex + 1,
122
+ sector: sectorIndex + 1,
123
+ techBandKey: `${techBand.tech}_${techBand.band}`,
124
+ gridPosition: { row, col }
125
+ },
126
+ customSubgroup: `Sector-${sectorIndex + 1}`
127
+ });
128
+ cellCounter++;
110
129
  });
111
- cellCounter++;
112
130
  });
113
- });
114
- console.log(`Generated ${demoCells.length} demo cells across ${AZIMUTHS.length} sectors`);
131
+ }
132
+ console.log(`Generated ${demoCells.length} demo cells across ${NUM_SITES} sites (${AZIMUTHS.length} sectors × ${TECH_BANDS.length} tech-bands per site)`);
@@ -56,10 +56,36 @@
56
56
  let level1 = $state<Exclude<CellGroupingField, 'none'>>(store.groupingConfig.level1);
57
57
  let level2 = $state<CellGroupingField>(store.groupingConfig.level2);
58
58
 
59
+ // Track config for localStorage invalidation
60
+ const STORAGE_CONFIG_KEY = 'cellular-cell-filter:config';
61
+
62
+ /**
63
+ * Check if grouping config has changed and clear localStorage if needed
64
+ */
65
+ function validateStoredConfig() {
66
+ const storedConfigStr = localStorage.getItem(STORAGE_CONFIG_KEY);
67
+ const currentConfig = `${level1}:${level2}`;
68
+
69
+ if (storedConfigStr !== currentConfig) {
70
+ console.log('CellFilterControl: Config changed, clearing tree state', {
71
+ stored: storedConfigStr,
72
+ current: currentConfig
73
+ });
74
+ // Clear tree state from localStorage
75
+ localStorage.removeItem('cellular-cell-filter:checked');
76
+ localStorage.removeItem('cellular-cell-filter:expanded');
77
+ // Save new config
78
+ localStorage.setItem(STORAGE_CONFIG_KEY, currentConfig);
79
+ }
80
+ }
81
+
59
82
  // Rebuild tree when grouping config or cells change
60
83
  function rebuildTree() {
61
84
  if (store.cells.length === 0) return;
62
85
 
86
+ // Validate and clear stale localStorage
87
+ validateStoredConfig();
88
+
63
89
  console.log('CellFilterControl: Building tree with config:', { level1, level2 });
64
90
 
65
91
  const treeNodes = buildCellTree(
@@ -35,6 +35,7 @@
35
35
  let map = $state<MapboxMap | null>(null);
36
36
  let mounted = $state(false);
37
37
  let viewportUpdateTimer: ReturnType<typeof setTimeout> | null = null;
38
+ let viewportVersion = $state(0); // Increment this to force $effect re-run on viewport changes
38
39
 
39
40
  // Viewport change handler (pan/zoom/move) with debouncing
40
41
  function handleMoveEnd() {
@@ -50,8 +51,10 @@
50
51
  if (!map) return;
51
52
  const newZoom = map.getZoom();
52
53
  console.log('CellsLayer: Viewport changed (zoom/pan), updating to zoom:', newZoom);
53
- // This will trigger $effect to re-run with new viewport bounds and zoom
54
+ // Update zoom if changed
54
55
  store.setCurrentZoom(newZoom);
56
+ // Force $effect to re-run by incrementing version (triggers on pan without zoom change)
57
+ viewportVersion++;
55
58
  }, 200);
56
59
  }
57
60
 
@@ -102,13 +105,17 @@
102
105
 
103
106
  // Reactive: Update GeoJSON when cells/zoom/filters/settings change
104
107
  $effect(() => {
108
+ // Track viewportVersion to force re-run on pan (even without zoom change)
109
+ viewportVersion;
110
+
105
111
  console.log('CellsLayer $effect triggered:', {
106
112
  mounted,
107
113
  hasMap: !!map,
108
114
  showCells: store.showCells,
109
115
  filteredCellsCount: store.filteredCells.length,
110
116
  currentZoom: store.currentZoom,
111
- baseRadius: store.baseRadius
117
+ baseRadius: store.baseRadius,
118
+ viewportVersion
112
119
  });
113
120
 
114
121
  if (!mounted || !map || !store.showCells) {
@@ -24,7 +24,7 @@ import * as turf from '@turf/turf';
24
24
  * 64 // 64 points
25
25
  * );
26
26
  */
27
- export function createArcPolygon(center, azimuth, beamwidth, radius, steps = 64) {
27
+ export function createArcPolygon(center, azimuth, beamwidth, radius, steps = 6) {
28
28
  // Convert radius from meters to kilometers for Turf
29
29
  const radiusKm = radius / 1000;
30
30
  // Calculate start and end bearings for the arc
@@ -71,12 +71,22 @@ function generateNodeId(field, value, parentId) {
71
71
  */
72
72
  export function buildCellTree(cells, config, colorMap) {
73
73
  const { level1, level2 } = config;
74
+ console.log('=== buildCellTree START ===');
75
+ console.log('Config:', { level1, level2 });
76
+ console.log('Total cells:', cells.length);
74
77
  // If level2 is 'none', create flat 2-level tree
75
78
  if (level2 === 'none') {
76
- return buildFlatTree(cells, level1, colorMap);
79
+ console.log('Building flat tree (2-level)');
80
+ const tree = buildFlatTree(cells, level1, colorMap);
81
+ console.log('Flat tree built, root children:', tree.children?.length);
82
+ return tree;
77
83
  }
78
84
  // Otherwise, create nested 3-level tree
79
- return buildNestedTree(cells, level1, level2, colorMap);
85
+ console.log('Building nested tree (3-level)');
86
+ const tree = buildNestedTree(cells, level1, level2, colorMap);
87
+ console.log('Nested tree built, root children:', tree.children?.length);
88
+ console.log('=== buildCellTree END ===');
89
+ return tree;
80
90
  }
81
91
  /**
82
92
  * Build flat 2-level tree (Root → Level1 groups)
@@ -97,6 +107,8 @@ function buildFlatTree(cells, level1, colorMap) {
97
107
  const children = sortedGroups.map(([value, groupCells]) => {
98
108
  const nodeId = generateNodeId(level1, value);
99
109
  const color = colorMap?.get(nodeId);
110
+ const cellIds = groupCells.map((c) => c.id);
111
+ console.log(` Flat tree node: id="${nodeId}", cells=${cellIds.length}, cellIds sample:`, cellIds.slice(0, 3));
100
112
  return {
101
113
  id: nodeId,
102
114
  label: getGroupLabel(level1, value, groupCells.length),
@@ -106,7 +118,7 @@ function buildFlatTree(cells, level1, colorMap) {
106
118
  type: 'leafGroup',
107
119
  groupingField: level1,
108
120
  groupingValue: value,
109
- cellIds: groupCells.map((c) => c.id),
121
+ cellIds,
110
122
  color
111
123
  }
112
124
  };
@@ -149,6 +161,8 @@ function buildNestedTree(cells, level1, level2, colorMap) {
149
161
  const level2Children = sortedLevel2.map(([value2, groupCells]) => {
150
162
  const nodeId = generateNodeId(level2, value2, parentId);
151
163
  const color = colorMap?.get(nodeId);
164
+ const cellIds = groupCells.map((c) => c.id);
165
+ console.log(` Nested tree leaf: id="${nodeId}", cells=${cellIds.length}, cellIds sample:`, cellIds.slice(0, 3));
152
166
  return {
153
167
  id: nodeId,
154
168
  label: getGroupLabel(level2, value2, groupCells.length),
@@ -160,13 +174,14 @@ function buildNestedTree(cells, level1, level2, colorMap) {
160
174
  groupingValue: value2,
161
175
  parentGroupingField: level1,
162
176
  parentGroupingValue: value1,
163
- cellIds: groupCells.map((c) => c.id),
177
+ cellIds,
164
178
  color
165
179
  }
166
180
  };
167
181
  });
168
182
  // Calculate total cells in this level1 group
169
183
  const totalCells = Array.from(level2Groups.values()).flat().length;
184
+ console.log(` Nested tree parent: id="${parentId}", totalCells=${totalCells}, children=${level2Children.length}`);
170
185
  return {
171
186
  id: parentId,
172
187
  label: getGroupLabel(level1, value1, totalCells),
@@ -197,30 +212,72 @@ function buildNestedTree(cells, level1, level2, colorMap) {
197
212
  * @returns Filtered array of cells
198
213
  */
199
214
  export function getFilteredCells(checkedPaths, allCells) {
215
+ console.log('=== getFilteredCells START ===');
216
+ console.log('Checked paths:', Array.from(checkedPaths));
217
+ console.log('Total cells:', allCells.length);
200
218
  // If root is checked (or no specific filters), return all cells
201
219
  if (checkedPaths.has('root') && checkedPaths.size === 1) {
220
+ console.log('Root only checked, returning all cells');
202
221
  return allCells;
203
222
  }
223
+ // Sample first 3 cells for detailed logging
224
+ const sampleSize = 3;
225
+ let processedCount = 0;
204
226
  // Filter cells that match any of the checked paths
205
227
  const filtered = allCells.filter((cell) => {
228
+ const shouldLog = processedCount < sampleSize;
229
+ if (shouldLog) {
230
+ console.log(`\n--- Sample Cell ${processedCount + 1}: ${cell.id} ---`);
231
+ console.log(` Properties: tech="${cell.tech}", frq="${cell.frq}", status="${cell.status}"`);
232
+ }
206
233
  // Check if this cell belongs to any checked path
207
- return Array.from(checkedPaths).some((path) => {
234
+ const matched = Array.from(checkedPaths).some((path) => {
208
235
  // Match cell properties against path segments
209
- if (path === 'root')
236
+ if (path === 'root') {
237
+ if (shouldLog)
238
+ console.log(` Path "root": MATCH`);
210
239
  return true;
211
- // Parse path and check if cell matches
212
- const segments = path.split(':');
213
- // Simple string matching against cell properties
214
- return segments.every((segment) => {
215
- return (cell.tech === segment ||
240
+ }
241
+ // Parse path and filter out "root" prefix and deduplicate consecutive segments
242
+ const allSegments = path.split(':');
243
+ // Remove "root" and deduplicate consecutive segments
244
+ const segments = [];
245
+ let prevSegment = '';
246
+ for (const segment of allSegments) {
247
+ if (segment !== 'root' && segment !== prevSegment) {
248
+ segments.push(segment);
249
+ prevSegment = segment;
250
+ }
251
+ }
252
+ if (shouldLog) {
253
+ console.log(` Checking path "${path}" (raw: [${allSegments.join(', ')}], filtered: [${segments.join(', ')}])`);
254
+ }
255
+ // Match filtered segments against cell properties
256
+ const segmentMatches = segments.every((segment) => {
257
+ const matches = (cell.tech === segment ||
216
258
  cell.frq === segment ||
217
259
  cell.status === segment ||
218
260
  cell.siteId === segment ||
219
261
  cell.customSubgroup === segment ||
220
262
  cell.type === segment ||
221
263
  cell.planner === segment);
264
+ if (shouldLog) {
265
+ console.log(` Segment "${segment}": ${matches ? '✓ MATCH' : '✗ NO MATCH'}`);
266
+ }
267
+ return matches;
222
268
  });
269
+ if (shouldLog) {
270
+ console.log(` Path "${path}": ${segmentMatches ? '✓ MATCHED' : '✗ NOT MATCHED'}`);
271
+ }
272
+ return segmentMatches;
223
273
  });
274
+ if (shouldLog) {
275
+ console.log(` Final result: ${matched ? '✓ INCLUDE' : '✗ EXCLUDE'}`);
276
+ }
277
+ processedCount++;
278
+ return matched;
224
279
  });
280
+ console.log(`\nFiltered result: ${filtered.length} of ${allCells.length} cells`);
281
+ console.log('=== getFilteredCells END ===\n');
225
282
  return filtered;
226
283
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartnet360/svelte-components",
3
- "version": "0.0.59",
3
+ "version": "0.0.61",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",