@smartnet360/svelte-components 0.0.65 → 0.0.67

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.
Files changed (31) hide show
  1. package/dist/map-v2/core/controls/MapStyleControl.svelte +76 -81
  2. package/dist/map-v2/core/controls/MapStyleControl.svelte.d.ts +4 -0
  3. package/dist/map-v2/demo/DemoMap.svelte +75 -7
  4. package/dist/map-v2/features/cells/controls/CellFilterControl.svelte +14 -5
  5. package/dist/map-v2/features/cells/controls/CellFilterControl.svelte.d.ts +4 -0
  6. package/dist/map-v2/features/cells/controls/CellStyleControl.svelte +8 -2
  7. package/dist/map-v2/features/cells/controls/CellStyleControl.svelte.d.ts +4 -0
  8. package/dist/map-v2/features/cells/layers/CellsLayer.svelte +2 -1
  9. package/dist/map-v2/features/cells/stores/cellStoreContext.svelte.d.ts +3 -0
  10. package/dist/map-v2/features/cells/stores/cellStoreContext.svelte.js +5 -0
  11. package/dist/map-v2/features/cells/utils/cellGeoJSON.d.ts +2 -6
  12. package/dist/map-v2/features/cells/utils/cellGeoJSON.js +11 -13
  13. package/dist/map-v2/features/cells/utils/cellTree.d.ts +5 -2
  14. package/dist/map-v2/features/cells/utils/cellTree.js +17 -6
  15. package/dist/map-v2/features/sites/controls/SiteFilterControl.svelte +8 -2
  16. package/dist/map-v2/features/sites/controls/SiteFilterControl.svelte.d.ts +4 -0
  17. package/dist/map-v2/features/sites/controls/SiteSelectionControl.svelte +281 -0
  18. package/dist/map-v2/features/sites/controls/SiteSelectionControl.svelte.d.ts +20 -0
  19. package/dist/map-v2/features/sites/controls/SiteSizeSlider.svelte +8 -2
  20. package/dist/map-v2/features/sites/controls/SiteSizeSlider.svelte.d.ts +4 -0
  21. package/dist/map-v2/features/sites/index.d.ts +1 -0
  22. package/dist/map-v2/features/sites/index.js +1 -0
  23. package/dist/map-v2/features/sites/layers/SitesLayer.svelte +11 -1
  24. package/dist/map-v2/features/sites/stores/siteStoreContext.svelte.d.ts +7 -0
  25. package/dist/map-v2/features/sites/stores/siteStoreContext.svelte.js +38 -1
  26. package/dist/map-v2/features/sites/types.d.ts +2 -0
  27. package/dist/map-v2/index.d.ts +1 -1
  28. package/dist/map-v2/index.js +1 -1
  29. package/dist/map-v2/shared/controls/MapControl.svelte +41 -4
  30. package/dist/map-v2/shared/controls/MapControl.svelte.d.ts +4 -0
  31. package/package.json +1 -1
@@ -85,6 +85,10 @@
85
85
  position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
86
86
  /** Control title */
87
87
  title?: string;
88
+ /** Optional header icon */
89
+ icon?: string;
90
+ /** Show icon when collapsed (default: true) */
91
+ iconOnlyWhenCollapsed?: boolean;
88
92
  /** Initially collapsed? */
89
93
  initiallyCollapsed?: boolean;
90
94
  /** Storage namespace for persistence */
@@ -98,7 +102,9 @@
98
102
  let {
99
103
  position = 'top-right',
100
104
  title = 'Map Style',
101
- initiallyCollapsed = false,
105
+ icon = 'layers',
106
+ iconOnlyWhenCollapsed = true,
107
+ initiallyCollapsed = true,
102
108
  namespace = 'map',
103
109
  availableStyles = DEFAULT_STYLES,
104
110
  defaultStyleId = 'streets'
@@ -179,34 +185,24 @@
179
185
  }
180
186
  </script>
181
187
 
182
- <MapControl {position} {title} collapsible={true} {initiallyCollapsed}>
188
+ <MapControl {position} {title} {icon} {iconOnlyWhenCollapsed} collapsible={true} {initiallyCollapsed}>
183
189
  <div class="map-style-controls">
184
190
  <div class="style-grid">
185
191
  {#each availableStyles as style (style.id)}
186
192
  <button
187
- class="style-option"
193
+ type="button"
194
+ class="btn style-option"
195
+ class:btn-primary={currentStyle.id === style.id}
196
+ class:btn-outline-secondary={currentStyle.id !== style.id}
188
197
  class:active={currentStyle.id === style.id}
189
198
  onclick={() => handleStyleChange(style.id)}
190
199
  title={style.description}
191
200
  >
192
- <i class="bi bi-{style.icon || 'map'}"></i>
193
- <!-- {#if currentStyle.id === style.id}
194
- <div class="style-checkmark">
195
- <i class="bi bi-check-circle-fill"></i>
196
- </div>
197
- {/if} -->
201
+ <i class="bi bi-{style.icon || 'map'} style-icon"></i>
202
+ <span class="style-name">{style.name}</span>
198
203
  </button>
199
204
  {/each}
200
205
  </div>
201
-
202
- {#if currentStyle.description}
203
- <div class="current-style-info">
204
- <small class="text-muted">
205
- <i class="bi bi-info-circle"></i>
206
- {currentStyle.description}
207
- </small>
208
- </div>
209
- {/if}
210
206
  </div>
211
207
  </MapControl>
212
208
 
@@ -215,75 +211,74 @@
215
211
  min-width: 240px;
216
212
  }
217
213
 
218
- .style-grid {
219
- display: grid;
220
- grid-template-columns: repeat(2, 1fr);
221
- gap: 4px;
222
- margin-bottom: 8px;
223
- }
214
+ .style-grid {
215
+ display: grid;
216
+ grid-template-columns: repeat(2, minmax(0, 1fr));
217
+ gap: 0.5rem;
218
+ margin-bottom: 0.75rem;
219
+ }
224
220
 
225
- .style-option {
226
- position: relative;
227
- display: flex;
228
- align-items: center;
229
- justify-content: center;
230
- padding: 8px;
231
- border: 2px solid #ddd;
232
- border-radius: 6px;
233
- background: white;
234
- cursor: pointer;
235
- transition: all 0.2s;
236
- font-size: 24px;
237
- color: #6c757d;
238
- }
221
+ .style-option {
222
+ display: inline-flex;
223
+ flex-direction: column;
224
+ align-items: center;
225
+ justify-content: center;
226
+ gap: 0.4rem;
227
+ text-align: center;
228
+ font-weight: 500;
229
+ border: var(--bs-btn-border-width, 1px) solid var(
230
+ --bs-btn-border-color,
231
+ var(--bs-border-color, #dee2e6)
232
+ );
233
+ /* Normalise Bootstrap spacing so icons remain centred */
234
+ --bs-btn-padding-y: 0.75rem;
235
+ --bs-btn-padding-x: 0.75rem;
236
+ --bs-btn-line-height: 1.25;
237
+ width: 100%;
238
+ height: auto;
239
+ }
239
240
 
240
- .style-option:hover {
241
- border-color: #0d6efd;
242
- box-shadow: 0 2px 8px rgba(13, 110, 253, 0.2);
243
- transform: translateY(-1px);
244
- }
241
+ .style-option .style-icon {
242
+ display: block;
243
+ font-size: 1.2rem;
244
+ line-height: 1;
245
+ margin-top: 0.15rem;
246
+ }
245
247
 
246
- .style-option.active {
247
- border-color: #0d6efd;
248
- background: #e7f1ff;
249
- color: #0d6efd;
250
- }
248
+ .style-option .style-name {
249
+ display: block;
250
+ font-size: 0.5rem;
251
+ text-transform: uppercase;
252
+ letter-spacing: 0.04em;
253
+ }
251
254
 
252
- .style-name {
253
- font-size: 12px;
254
- font-weight: 500;
255
- text-align: center;
256
- color: #333;
257
- }
255
+ /* Ensure Bootstrap button variables win inside Mapbox control */
256
+ .map-style-controls .btn {
257
+ background-color: var(--bs-btn-bg, transparent);
258
+ border-color: var(--bs-btn-border-color, currentColor);
259
+ color: var(--bs-btn-color, inherit);
260
+ transition: var(--bs-btn-transition, all 0.15s ease);
261
+ border-radius: var(--bs-btn-border-radius, var(--bs-border-radius, 0.375rem));
262
+ }
258
263
 
259
- .style-option.active .style-name {
260
- color: #0d6efd;
261
- font-weight: 600;
262
- }
264
+ .map-style-controls .btn:hover,
265
+ .map-style-controls .btn:focus {
266
+ background-color: var(--bs-btn-hover-bg, var(--bs-btn-bg, transparent));
267
+ border-color: var(--bs-btn-hover-border-color, var(--bs-btn-border-color, currentColor));
268
+ color: var(--bs-btn-hover-color, var(--bs-btn-color, inherit));
269
+ }
263
270
 
264
- .style-checkmark {
265
- position: absolute;
266
- top: 4px;
267
- right: 4px;
268
- color: #0d6efd;
269
- font-size: 18px;
270
- background: white;
271
- border-radius: 50%;
272
- line-height: 1;
273
- }
271
+ .map-style-controls .btn:disabled,
272
+ .map-style-controls .btn:disabled:hover {
273
+ background-color: var(--bs-btn-disabled-bg, var(--bs-btn-bg, transparent));
274
+ border-color: var(--bs-btn-disabled-border-color, var(--bs-btn-border-color, currentColor));
275
+ color: var(--bs-btn-disabled-color, var(--bs-btn-color, inherit));
276
+ opacity: var(--bs-btn-disabled-opacity, 0.65);
277
+ }
278
+
279
+ .map-style-controls .btn-primary {
280
+ box-shadow: 0 4px 12px rgba(var(--bs-primary-rgb, 13, 110, 253), 0.18);
281
+ }
274
282
 
275
- .current-style-info {
276
- padding: 8px;
277
- background: #f8f9fa;
278
- border-radius: 4px;
279
- text-align: center;
280
- }
281
283
 
282
- .current-style-info small {
283
- display: flex;
284
- align-items: center;
285
- justify-content: center;
286
- gap: 4px;
287
- font-size: 11px;
288
- }
289
284
  </style>
@@ -10,6 +10,10 @@ interface Props {
10
10
  position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
11
11
  /** Control title */
12
12
  title?: string;
13
+ /** Optional header icon */
14
+ icon?: string;
15
+ /** Show icon when collapsed (default: true) */
16
+ iconOnlyWhenCollapsed?: boolean;
13
17
  /** Initially collapsed? */
14
18
  initiallyCollapsed?: boolean;
15
19
  /** Storage namespace for persistence */
@@ -18,6 +18,7 @@
18
18
  import SitesLayer from '../features/sites/layers/SitesLayer.svelte';
19
19
  import SiteFilterControl from '../features/sites/controls/SiteFilterControl.svelte';
20
20
  import SiteSizeSlider from '../features/sites/controls/SiteSizeSlider.svelte';
21
+ import SiteSelectionControl from '../features/sites/controls/SiteSelectionControl.svelte';
21
22
  import CellsLayer from '../features/cells/layers/CellsLayer.svelte';
22
23
  import CellFilterControl from '../features/cells/controls/CellFilterControl.svelte';
23
24
  import CellStyleControl from '../features/cells/controls/CellStyleControl.svelte';
@@ -51,6 +52,28 @@
51
52
  // Create cell store with demo data
52
53
  const cellStore = createCellStoreContext(demoCells);
53
54
 
55
+ // Toggle whether control headers show icons (when collapsed) or always show text
56
+ const useIconHeaders = true;
57
+
58
+ // Bootstrap icon names for each control header
59
+ const controlIcons = {
60
+ mapStyle: 'layers',
61
+ cellFilter: 'diagram-3',
62
+ siteFilter: 'funnel',
63
+ siteSelection: 'check2-square',
64
+ cellStyle: 'palette',
65
+ siteSize: 'sliders'
66
+ };
67
+
68
+ // Handler for site selection action button
69
+ function handleAnalyzeSites(siteIds: string[]) {
70
+ console.log('Analyze sites:', siteIds);
71
+ // Example: Navigate to SiteCheck page with selected sites
72
+ // window.location.href = `/site-check?sites=${siteIds.join(',')}`;
73
+ // Or use SvelteKit navigate, or open modal, etc.
74
+ alert(`Selected ${siteIds.length} sites:\n${siteIds.join(', ')}`);
75
+ }
76
+
54
77
  </script>
55
78
 
56
79
  <!-- // controls={['navigation', 'scale']} -->
@@ -66,19 +89,64 @@
66
89
  <CellsLayer store={cellStore} namespace="demo-cells" />
67
90
 
68
91
  <!-- Map style control - switch between map styles -->
69
- <MapStyleControl position="top-right" initiallyCollapsed={true} namespace="demo-map" />
92
+ <MapStyleControl
93
+ position="top-right"
94
+ initiallyCollapsed={true}
95
+ namespace="demo-map"
96
+ icon={controlIcons.mapStyle}
97
+ iconOnlyWhenCollapsed={useIconHeaders}
98
+ />
99
+
100
+ <!-- Cell filter control - dynamic hierarchical filtering -->
101
+ <CellFilterControl
102
+ store={cellStore}
103
+ position="top-left"
104
+ title="Cell Filter"
105
+ initiallyCollapsed={true}
106
+ icon={controlIcons.cellFilter}
107
+ iconOnlyWhenCollapsed={useIconHeaders}
108
+ />
70
109
 
71
110
  <!-- Site filter control - updates store.filteredSites -->
72
- <SiteFilterControl store={siteStore} position="top-left" title="Site Filter" />
111
+ <SiteFilterControl
112
+ store={siteStore}
113
+ position="top-left"
114
+ title="Site Filter"
115
+ icon={controlIcons.siteFilter}
116
+ iconOnlyWhenCollapsed={useIconHeaders}
117
+ />
73
118
 
74
- <!-- Cell filter control - dynamic hierarchical filtering -->
75
- <CellFilterControl store={cellStore} position="top-left" title="Cell Filter" initiallyCollapsed={true} />
119
+ <!-- Site selection control - build list and analyze sites -->
120
+ <SiteSelectionControl
121
+ store={siteStore}
122
+ position="top-left"
123
+ title="Site Selection"
124
+ icon={controlIcons.siteSelection}
125
+ iconOnlyWhenCollapsed={useIconHeaders}
126
+ onAction={handleAnalyzeSites}
127
+ actionButtonLabel="Open Cluster KPIs"
128
+ />
129
+
130
+ <!-- Cell style control - visual settings for cells -->
131
+ <CellStyleControl
132
+ store={cellStore}
133
+ position="top-right"
134
+ title="Cell Settings"
135
+ initiallyCollapsed={true}
136
+ icon={controlIcons.cellStyle}
137
+ iconOnlyWhenCollapsed={useIconHeaders}
138
+ />
76
139
 
77
140
  <!-- Site size control - updates store visual properties -->
78
- <SiteSizeSlider store={siteStore} position="bottom-right" title="Site Display" />
141
+ <SiteSizeSlider
142
+ store={siteStore}
143
+ position="top-right"
144
+ title="Site Settings"
145
+ icon={controlIcons.siteSize}
146
+ iconOnlyWhenCollapsed={useIconHeaders}
147
+ />
148
+
79
149
 
80
- <!-- Cell style control - visual settings for cells -->
81
- <CellStyleControl store={cellStore} position="bottom-left" title="Cell Display" initiallyCollapsed={true} />
82
150
  </MapboxProvider>
83
151
  </div>
84
152
 
@@ -27,6 +27,10 @@
27
27
  position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
28
28
  /** Control title */
29
29
  title?: string;
30
+ /** Optional header icon */
31
+ icon?: string;
32
+ /** Show icon when collapsed (default: true) */
33
+ iconOnlyWhenCollapsed?: boolean;
30
34
  /** Initially collapsed? */
31
35
  initiallyCollapsed?: boolean;
32
36
  }
@@ -35,7 +39,9 @@
35
39
  store,
36
40
  position = 'top-left',
37
41
  title = 'Cell Filter',
38
- initiallyCollapsed = false
42
+ icon = 'diagram-3',
43
+ iconOnlyWhenCollapsed = true,
44
+ initiallyCollapsed = true
39
45
  }: Props = $props();
40
46
 
41
47
  // Grouping options (excluding 'none' from level1)
@@ -82,12 +88,15 @@
82
88
  // Validate and clear stale localStorage
83
89
  validateStoredConfig();
84
90
 
85
- const treeNodes = buildCellTree(
91
+ const { tree: treeNodes, cellGroupMap } = buildCellTree(
86
92
  store.cellsFilteredByStatus, // Use filtered cells, not all cells
87
93
  { level1, level2 },
88
94
  store.groupColorMap
89
95
  );
90
96
 
97
+ // Update the cell-to-group lookup map in the store
98
+ store.setCellGroupMap(cellGroupMap);
99
+
91
100
  // Create or recreate tree store
92
101
  treeStore = createTreeStore({
93
102
  nodes: [treeNodes],
@@ -165,7 +174,7 @@
165
174
  };
166
175
  </script>
167
176
 
168
- <MapControl {position} {title} collapsible={true} {initiallyCollapsed}>
177
+ <MapControl {position} {title} {icon} {iconOnlyWhenCollapsed} collapsible={true} {initiallyCollapsed}>
169
178
  <div class="cell-filter-control">
170
179
  <!-- Status Filter Checkbox -->
171
180
  <div class="mb-3">
@@ -226,8 +235,8 @@
226
235
  <div class="cell-filter-tree">
227
236
  <TreeView store={$treeStore} showControls={false}>
228
237
  {#snippet children({ node, state })}
229
- <!-- Custom node rendering with color picker for leaf nodes -->
230
- {#if node.metadata?.isLeafGroup}
238
+ <!-- Color picker for all leaf nodes (nodes with cells) -->
239
+ {#if node.metadata?.type === 'leafGroup'}
231
240
  <input
232
241
  type="color"
233
242
  class="color-picker"
@@ -6,6 +6,10 @@ interface Props {
6
6
  position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
7
7
  /** Control title */
8
8
  title?: string;
9
+ /** Optional header icon */
10
+ icon?: string;
11
+ /** Show icon when collapsed (default: true) */
12
+ iconOnlyWhenCollapsed?: boolean;
9
13
  /** Initially collapsed? */
10
14
  initiallyCollapsed?: boolean;
11
15
  }
@@ -19,6 +19,10 @@
19
19
  position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
20
20
  /** Control title */
21
21
  title?: string;
22
+ /** Optional header icon */
23
+ icon?: string;
24
+ /** Show icon when collapsed (default: true) */
25
+ iconOnlyWhenCollapsed?: boolean;
22
26
  /** Initially collapsed? */
23
27
  initiallyCollapsed?: boolean;
24
28
  }
@@ -27,11 +31,13 @@
27
31
  store,
28
32
  position = 'bottom-left',
29
33
  title = 'Cell Display',
30
- initiallyCollapsed = false
34
+ icon = 'palette',
35
+ iconOnlyWhenCollapsed = true,
36
+ initiallyCollapsed = true
31
37
  }: Props = $props();
32
38
  </script>
33
39
 
34
- <MapControl {position} {title} collapsible={true} {initiallyCollapsed}>
40
+ <MapControl {position} {title} {icon} {iconOnlyWhenCollapsed} collapsible={true} {initiallyCollapsed}>
35
41
  <div class="cell-style-controls">
36
42
  <!-- Show cells toggle -->
37
43
  <div class="control-row">
@@ -6,6 +6,10 @@ interface Props {
6
6
  position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
7
7
  /** Control title */
8
8
  title?: string;
9
+ /** Optional header icon */
10
+ icon?: string;
11
+ /** Show icon when collapsed (default: true) */
12
+ iconOnlyWhenCollapsed?: boolean;
9
13
  /** Initially collapsed? */
10
14
  initiallyCollapsed?: boolean;
11
15
  }
@@ -162,7 +162,8 @@
162
162
  visibleCells,
163
163
  store.currentZoom,
164
164
  store.baseRadius,
165
- store.groupColorMap
165
+ store.groupColorMap,
166
+ store.cellGroupMap // Pass lookup map for O(1) color lookup
166
167
  );
167
168
 
168
169
  console.log('CellsLayer: Generated GeoJSON:', {
@@ -16,6 +16,7 @@ export interface CellStoreValue {
16
16
  statusStyles: Map<CellStatus, CellStatusStyle>;
17
17
  groupingConfig: CellTreeConfig;
18
18
  groupColorMap: Map<string, string>;
19
+ cellGroupMap: Map<string, string>;
19
20
  currentZoom: number;
20
21
  }
21
22
  export interface CellStoreContext {
@@ -30,6 +31,7 @@ export interface CellStoreContext {
30
31
  readonly currentZoom: number;
31
32
  readonly groupingConfig: CellTreeConfig;
32
33
  readonly groupColorMap: Map<string, string>;
34
+ readonly cellGroupMap: Map<string, string>;
33
35
  setFilteredCells(cells: Cell[]): void;
34
36
  setIncludePlannedCells(value: boolean): void;
35
37
  setShowCells(value: boolean): void;
@@ -38,6 +40,7 @@ export interface CellStoreContext {
38
40
  setBaseRadius(value: number): void;
39
41
  setCurrentZoom(value: number): void;
40
42
  setGroupingConfig(config: CellTreeConfig): void;
43
+ setCellGroupMap(map: Map<string, string>): void;
41
44
  getStatusStyle(status: CellStatus): CellStatusStyle;
42
45
  setStatusStyle(status: CellStatus, style: CellStatusStyle): void;
43
46
  getGroupColor(groupKey: string): string | undefined;
@@ -56,6 +56,7 @@ export function createCellStoreContext(cells) {
56
56
  statusStyles: new Map(Object.entries(DEFAULT_STATUS_STYLES)),
57
57
  groupingConfig: persistedSettings.groupingConfig ?? DEFAULT_CELL_TREE_CONFIG,
58
58
  groupColorMap: initialColorMap,
59
+ cellGroupMap: new Map(), // Will be populated when tree is built
59
60
  currentZoom: 12 // Default zoom
60
61
  });
61
62
  // Derived: Filter cells by status based on includePlannedCells flag
@@ -105,6 +106,7 @@ export function createCellStoreContext(cells) {
105
106
  set currentZoom(value) { state.currentZoom = value; },
106
107
  get groupingConfig() { return state.groupingConfig; },
107
108
  get groupColorMap() { return state.groupColorMap; },
109
+ get cellGroupMap() { return state.cellGroupMap; },
108
110
  // Methods
109
111
  setFilteredCells(cells) {
110
112
  state.filteredCells = cells;
@@ -130,6 +132,9 @@ export function createCellStoreContext(cells) {
130
132
  setGroupingConfig(config) {
131
133
  state.groupingConfig = config;
132
134
  },
135
+ setCellGroupMap(map) {
136
+ state.cellGroupMap = map;
137
+ },
133
138
  getStatusStyle(status) {
134
139
  return state.statusStyles.get(status) || DEFAULT_STATUS_STYLES['On_Air'];
135
140
  },
@@ -12,11 +12,7 @@ import type { Cell } from '../types';
12
12
  * @param currentZoom - Current map zoom level for radius calculation
13
13
  * @param baseRadius - Base radius in meters before multipliers
14
14
  * @param groupColorMap - Optional custom colors for cell groups
15
+ * @param cellGroupMap - Optional cell-to-group lookup map (cellId -> groupNodeId)
15
16
  * @returns GeoJSON FeatureCollection with arc polygons
16
17
  */
17
- export declare function cellsToGeoJSON(cells: Cell[], currentZoom: number, baseRadius?: number, groupColorMap?: Map<string, string>): FeatureCollection<Polygon>;
18
- /**
19
- * Helper function to generate group key from cell based on grouping config
20
- * This will be used when implementing dynamic grouping
21
- */
22
- export declare function getCellGroupKey(cell: Cell, level1Field: string, level2Field?: string): string;
18
+ export declare function cellsToGeoJSON(cells: Cell[], currentZoom: number, baseRadius?: number, groupColorMap?: Map<string, string>, cellGroupMap?: Map<string, string>): FeatureCollection<Polygon>;
@@ -14,18 +14,23 @@ import { createArcPolygon } from './arcGeometry';
14
14
  * @param currentZoom - Current map zoom level for radius calculation
15
15
  * @param baseRadius - Base radius in meters before multipliers
16
16
  * @param groupColorMap - Optional custom colors for cell groups
17
+ * @param cellGroupMap - Optional cell-to-group lookup map (cellId -> groupNodeId)
17
18
  * @returns GeoJSON FeatureCollection with arc polygons
18
19
  */
19
- export function cellsToGeoJSON(cells, currentZoom, baseRadius = 500, groupColorMap) {
20
+ export function cellsToGeoJSON(cells, currentZoom, baseRadius = 500, groupColorMap, cellGroupMap) {
20
21
  const features = cells.map((cell) => {
21
22
  // Calculate tech-band key for styling
22
23
  const techBandKey = `${cell.tech}_${cell.frq}`;
23
24
  // Get default tech-band color
24
25
  const defaultColor = TECHNOLOGY_BAND_COLORS[techBandKey] || '#cccccc';
25
- // Get custom group color if available
26
- // Group key depends on grouping config, for now use tech-band
27
- const groupKey = techBandKey;
28
- const groupColor = groupColorMap?.get(groupKey);
26
+ // Get custom group color using lookup map
27
+ let groupColor;
28
+ if (groupColorMap && cellGroupMap) {
29
+ const groupKey = cellGroupMap.get(cell.id);
30
+ if (groupKey) {
31
+ groupColor = groupColorMap.get(groupKey);
32
+ }
33
+ }
29
34
  // Get status style
30
35
  const statusStyle = DEFAULT_STATUS_STYLES[cell.status] || DEFAULT_STATUS_STYLES['On_Air'];
31
36
  // Calculate final radius based on zoom and tech-band
@@ -41,6 +46,7 @@ export function cellsToGeoJSON(cells, currentZoom, baseRadius = 500, groupColorM
41
46
  // Cell identification
42
47
  id: cell.txId,
43
48
  txId: cell.txId,
49
+ cellId: cell.txId,
44
50
  cellID: cell.cellID,
45
51
  cellName: cell.cellName,
46
52
  siteId: cell.siteId,
@@ -72,11 +78,3 @@ export function cellsToGeoJSON(cells, currentZoom, baseRadius = 500, groupColorM
72
78
  features
73
79
  };
74
80
  }
75
- /**
76
- * Helper function to generate group key from cell based on grouping config
77
- * This will be used when implementing dynamic grouping
78
- */
79
- export function getCellGroupKey(cell, level1Field, level2Field) {
80
- // For now, default to tech-band
81
- return `${cell.tech}_${cell.frq}`;
82
- }
@@ -12,9 +12,12 @@ import type { TreeNode } from '../../../../core/TreeView/tree.model';
12
12
  * @param cells - Array of cells to build tree from
13
13
  * @param config - Grouping configuration (level1, level2)
14
14
  * @param colorMap - Optional map of leaf group IDs to custom colors
15
- * @returns Root tree node
15
+ * @returns Object with tree and cell-to-group lookup map
16
16
  */
17
- export declare function buildCellTree(cells: Cell[], config: CellTreeConfig, colorMap?: Map<string, string>): TreeNode;
17
+ export declare function buildCellTree(cells: Cell[], config: CellTreeConfig, colorMap?: Map<string, string>): {
18
+ tree: TreeNode;
19
+ cellGroupMap: Map<string, string>;
20
+ };
18
21
  /**
19
22
  * Get filtered cells based on checked tree paths
20
23
  *
@@ -67,7 +67,7 @@ function generateNodeId(field, value, parentId) {
67
67
  * @param cells - Array of cells to build tree from
68
68
  * @param config - Grouping configuration (level1, level2)
69
69
  * @param colorMap - Optional map of leaf group IDs to custom colors
70
- * @returns Root tree node
70
+ * @returns Object with tree and cell-to-group lookup map
71
71
  */
72
72
  export function buildCellTree(cells, config, colorMap) {
73
73
  const { level1, level2 } = config;
@@ -84,6 +84,7 @@ export function buildCellTree(cells, config, colorMap) {
84
84
  function buildFlatTree(cells, level1, colorMap) {
85
85
  // Group cells by level1 field
86
86
  const groups = new Map();
87
+ const cellGroupMap = new Map();
87
88
  cells.forEach((cell) => {
88
89
  const value = getGroupingValue(cell, level1);
89
90
  if (!groups.has(value)) {
@@ -93,12 +94,15 @@ function buildFlatTree(cells, level1, colorMap) {
93
94
  });
94
95
  // Sort groups alphabetically
95
96
  const sortedGroups = Array.from(groups.entries()).sort(([a], [b]) => a.localeCompare(b));
96
- // Build tree nodes
97
+ // Build tree nodes and populate cellGroupMap
97
98
  const children = sortedGroups.map(([value, groupCells]) => {
98
99
  const nodeId = generateNodeId(level1, value);
99
100
  const color = colorMap?.get(nodeId);
100
101
  const cellIds = groupCells.map((c) => c.id);
101
- console.log(` Flat tree node: id="${nodeId}", cells=${cellIds.length}, cellIds sample:`, cellIds.slice(0, 3));
102
+ // Map each cell to its group
103
+ groupCells.forEach(cell => {
104
+ cellGroupMap.set(cell.id, nodeId);
105
+ });
102
106
  return {
103
107
  id: nodeId,
104
108
  label: getGroupLabel(level1, value, groupCells.length),
@@ -113,7 +117,7 @@ function buildFlatTree(cells, level1, colorMap) {
113
117
  }
114
118
  };
115
119
  });
116
- return {
120
+ const tree = {
117
121
  id: 'root',
118
122
  label: `Cells (${cells.length})`,
119
123
  defaultChecked: true,
@@ -122,6 +126,7 @@ function buildFlatTree(cells, level1, colorMap) {
122
126
  type: 'root'
123
127
  }
124
128
  };
129
+ return { tree, cellGroupMap };
125
130
  }
126
131
  /**
127
132
  * Build nested 3-level tree (Root → Level1 → Level2 groups)
@@ -129,6 +134,7 @@ function buildFlatTree(cells, level1, colorMap) {
129
134
  function buildNestedTree(cells, level1, level2, colorMap) {
130
135
  // Group cells by level1, then by level2
131
136
  const level1Groups = new Map();
137
+ const cellGroupMap = new Map();
132
138
  cells.forEach((cell) => {
133
139
  const value1 = getGroupingValue(cell, level1);
134
140
  const value2 = getGroupingValue(cell, level2);
@@ -143,7 +149,7 @@ function buildNestedTree(cells, level1, level2, colorMap) {
143
149
  });
144
150
  // Sort level1 groups
145
151
  const sortedLevel1 = Array.from(level1Groups.entries()).sort(([a], [b]) => a.localeCompare(b));
146
- // Build tree nodes
152
+ // Build tree nodes and populate cellGroupMap
147
153
  const children = sortedLevel1.map(([value1, level2Groups]) => {
148
154
  const parentId = generateNodeId(level1, value1);
149
155
  // Sort level2 groups
@@ -152,6 +158,10 @@ function buildNestedTree(cells, level1, level2, colorMap) {
152
158
  const nodeId = generateNodeId(level2, value2, parentId);
153
159
  const color = colorMap?.get(nodeId);
154
160
  const cellIds = groupCells.map((c) => c.id);
161
+ // Map each cell to its group
162
+ groupCells.forEach(cell => {
163
+ cellGroupMap.set(cell.id, nodeId);
164
+ });
155
165
  return {
156
166
  id: nodeId,
157
167
  label: getGroupLabel(level2, value2, groupCells.length),
@@ -182,7 +192,7 @@ function buildNestedTree(cells, level1, level2, colorMap) {
182
192
  }
183
193
  };
184
194
  });
185
- return {
195
+ const tree = {
186
196
  id: 'root',
187
197
  label: `Cells (${cells.length})`,
188
198
  defaultChecked: true,
@@ -191,6 +201,7 @@ function buildNestedTree(cells, level1, level2, colorMap) {
191
201
  type: 'root'
192
202
  }
193
203
  };
204
+ return { tree, cellGroupMap };
194
205
  }
195
206
  /**
196
207
  * Get filtered cells based on checked tree paths