@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.
- package/dist/map-v2/core/controls/MapStyleControl.svelte +76 -81
- package/dist/map-v2/core/controls/MapStyleControl.svelte.d.ts +4 -0
- package/dist/map-v2/demo/DemoMap.svelte +75 -7
- package/dist/map-v2/features/cells/controls/CellFilterControl.svelte +14 -5
- package/dist/map-v2/features/cells/controls/CellFilterControl.svelte.d.ts +4 -0
- package/dist/map-v2/features/cells/controls/CellStyleControl.svelte +8 -2
- package/dist/map-v2/features/cells/controls/CellStyleControl.svelte.d.ts +4 -0
- package/dist/map-v2/features/cells/layers/CellsLayer.svelte +2 -1
- package/dist/map-v2/features/cells/stores/cellStoreContext.svelte.d.ts +3 -0
- package/dist/map-v2/features/cells/stores/cellStoreContext.svelte.js +5 -0
- package/dist/map-v2/features/cells/utils/cellGeoJSON.d.ts +2 -6
- package/dist/map-v2/features/cells/utils/cellGeoJSON.js +11 -13
- package/dist/map-v2/features/cells/utils/cellTree.d.ts +5 -2
- package/dist/map-v2/features/cells/utils/cellTree.js +17 -6
- package/dist/map-v2/features/sites/controls/SiteFilterControl.svelte +8 -2
- package/dist/map-v2/features/sites/controls/SiteFilterControl.svelte.d.ts +4 -0
- package/dist/map-v2/features/sites/controls/SiteSelectionControl.svelte +281 -0
- package/dist/map-v2/features/sites/controls/SiteSelectionControl.svelte.d.ts +20 -0
- package/dist/map-v2/features/sites/controls/SiteSizeSlider.svelte +8 -2
- package/dist/map-v2/features/sites/controls/SiteSizeSlider.svelte.d.ts +4 -0
- package/dist/map-v2/features/sites/index.d.ts +1 -0
- package/dist/map-v2/features/sites/index.js +1 -0
- package/dist/map-v2/features/sites/layers/SitesLayer.svelte +11 -1
- package/dist/map-v2/features/sites/stores/siteStoreContext.svelte.d.ts +7 -0
- package/dist/map-v2/features/sites/stores/siteStoreContext.svelte.js +38 -1
- package/dist/map-v2/features/sites/types.d.ts +2 -0
- package/dist/map-v2/index.d.ts +1 -1
- package/dist/map-v2/index.js +1 -1
- package/dist/map-v2/shared/controls/MapControl.svelte +41 -4
- package/dist/map-v2/shared/controls/MapControl.svelte.d.ts +4 -0
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
|
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
|
|
111
|
+
<SiteFilterControl
|
|
112
|
+
store={siteStore}
|
|
113
|
+
position="top-left"
|
|
114
|
+
title="Site Filter"
|
|
115
|
+
icon={controlIcons.siteFilter}
|
|
116
|
+
iconOnlyWhenCollapsed={useIconHeaders}
|
|
117
|
+
/>
|
|
73
118
|
|
|
74
|
-
<!--
|
|
75
|
-
<
|
|
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
|
|
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
|
-
|
|
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
|
-
<!--
|
|
230
|
-
{#if node.metadata?.
|
|
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
|
-
|
|
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
|
}
|
|
@@ -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
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
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>):
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|