@smartnet360/svelte-components 0.0.70 → 0.0.72

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/demo/DemoMap.svelte +29 -1
  2. package/dist/map-v2/demo/demo-repeaters.d.ts +13 -0
  3. package/dist/map-v2/demo/demo-repeaters.js +74 -0
  4. package/dist/map-v2/features/cells/layers/CellLabelsLayer.svelte +9 -8
  5. package/dist/map-v2/features/repeaters/constants/radiusMultipliers.d.ts +26 -0
  6. package/dist/map-v2/features/repeaters/constants/radiusMultipliers.js +40 -0
  7. package/dist/map-v2/features/repeaters/constants/techBandZOrder.d.ts +25 -0
  8. package/dist/map-v2/features/repeaters/constants/techBandZOrder.js +43 -0
  9. package/dist/map-v2/features/repeaters/constants/zIndex.d.ts +11 -0
  10. package/dist/map-v2/features/repeaters/constants/zIndex.js +11 -0
  11. package/dist/map-v2/features/repeaters/controls/RepeaterFilterControl.svelte +172 -0
  12. package/dist/map-v2/features/repeaters/controls/RepeaterFilterControl.svelte.d.ts +18 -0
  13. package/dist/map-v2/features/repeaters/index.d.ts +13 -0
  14. package/dist/map-v2/features/repeaters/index.js +16 -0
  15. package/dist/map-v2/features/repeaters/layers/RepeaterLabelsLayer.svelte +301 -0
  16. package/dist/map-v2/features/repeaters/layers/RepeaterLabelsLayer.svelte.d.ts +10 -0
  17. package/dist/map-v2/features/repeaters/layers/RepeatersLayer.svelte +259 -0
  18. package/dist/map-v2/features/repeaters/layers/RepeatersLayer.svelte.d.ts +10 -0
  19. package/dist/map-v2/features/repeaters/stores/repeaterStoreContext.svelte.d.ts +69 -0
  20. package/dist/map-v2/features/repeaters/stores/repeaterStoreContext.svelte.js +222 -0
  21. package/dist/map-v2/features/repeaters/types.d.ts +59 -0
  22. package/dist/map-v2/features/repeaters/types.js +4 -0
  23. package/dist/map-v2/features/repeaters/utils/repeaterGeoJSON.d.ts +20 -0
  24. package/dist/map-v2/features/repeaters/utils/repeaterGeoJSON.js +90 -0
  25. package/dist/map-v2/features/repeaters/utils/repeaterTree.d.ts +23 -0
  26. package/dist/map-v2/features/repeaters/utils/repeaterTree.js +111 -0
  27. package/dist/map-v2/shared/controls/FeatureSettingsControl.svelte +27 -21
  28. package/dist/map-v2/shared/controls/FeatureSettingsControl.svelte.d.ts +3 -0
  29. package/dist/map-v2/shared/controls/panels/RepeaterSettingsPanel.svelte +280 -5
  30. package/dist/map-v2/shared/controls/panels/RepeaterSettingsPanel.svelte.d.ts +17 -16
  31. package/package.json +1 -1
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Repeater Store Context - Reactive state for repeater feature
3
+ *
4
+ * Simplified store with fixed Tech → FBand tree structure
5
+ * Supports leaf-level coloring for each tech:fband combination
6
+ */
7
+ const STORAGE_KEY = 'repeaterVisualSettings';
8
+ function loadSettings() {
9
+ if (typeof window === 'undefined')
10
+ return {};
11
+ try {
12
+ const saved = localStorage.getItem(STORAGE_KEY);
13
+ if (saved) {
14
+ return JSON.parse(saved);
15
+ }
16
+ }
17
+ catch (error) {
18
+ console.warn('Failed to load repeater settings from localStorage:', error);
19
+ }
20
+ return {};
21
+ }
22
+ function saveSettings(settings) {
23
+ if (typeof window === 'undefined')
24
+ return;
25
+ try {
26
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
27
+ }
28
+ catch (error) {
29
+ console.warn('Failed to save repeater settings to localStorage:', error);
30
+ }
31
+ }
32
+ /**
33
+ * Default colors for tech:fband combinations
34
+ */
35
+ const DEFAULT_TECH_BAND_COLORS = {
36
+ // 2G
37
+ '2G:900': '#FF6B6B',
38
+ '2G:1800': '#FF8E53',
39
+ // 4G
40
+ '4G:700': '#4ECDC4',
41
+ '4G:800': '#45B7D1',
42
+ '4G:900': '#96CEB4',
43
+ '4G:1800': '#FFEAA7',
44
+ '4G:2100': '#DFE6E9',
45
+ '4G:2600': '#74B9FF',
46
+ // 5G
47
+ '5G:2600': '#A29BFE',
48
+ '5G:3500': '#6C5CE7',
49
+ '5G:26000': '#FD79A8'
50
+ };
51
+ /**
52
+ * Create a repeater store context with reactive state
53
+ */
54
+ export function createRepeaterStoreContext(repeaters) {
55
+ // Load persisted settings
56
+ const persistedSettings = loadSettings();
57
+ // Convert persisted arrays/objects back to Sets/Maps
58
+ const initialVisibleSet = new Set(persistedSettings.visibleTechBands || []);
59
+ const initialColorMap = new Map();
60
+ if (persistedSettings.techBandColors) {
61
+ Object.entries(persistedSettings.techBandColors).forEach(([key, value]) => {
62
+ initialColorMap.set(key, value);
63
+ });
64
+ }
65
+ // If no colors persisted, use defaults
66
+ if (initialColorMap.size === 0) {
67
+ Object.entries(DEFAULT_TECH_BAND_COLORS).forEach(([key, value]) => {
68
+ initialColorMap.set(key, value);
69
+ });
70
+ }
71
+ // Internal reactive state
72
+ let state = $state({
73
+ repeaters,
74
+ visibleTechBands: initialVisibleSet,
75
+ techBandColorMap: initialColorMap,
76
+ showRepeaters: persistedSettings.showRepeaters ?? true,
77
+ baseRadius: persistedSettings.baseRadius ?? 500,
78
+ fillOpacity: persistedSettings.fillOpacity ?? 0.4,
79
+ lineWidth: persistedSettings.lineWidth ?? 2,
80
+ currentZoom: 12, // Default zoom
81
+ // Label settings with defaults
82
+ showLabels: persistedSettings.showLabels ?? true, // Default to true
83
+ primaryLabelField: (persistedSettings.primaryLabelField ?? 'repeaterId'),
84
+ secondaryLabelField: (persistedSettings.secondaryLabelField ?? 'fband'),
85
+ labelSize: persistedSettings.labelSize ?? 12,
86
+ labelColor: persistedSettings.labelColor ?? '#ffffff', // White text
87
+ labelOffset: persistedSettings.labelOffset ?? 300,
88
+ labelHaloColor: persistedSettings.labelHaloColor ?? '#000000', // Black halo
89
+ labelHaloWidth: persistedSettings.labelHaloWidth ?? 2,
90
+ minLabelZoom: persistedSettings.minLabelZoom ?? 10 // Lower default to show labels at more zoom levels
91
+ });
92
+ // Derived: Filter repeaters by visible tech:fband combinations
93
+ let filteredRepeaters = $derived(state.repeaters.filter(r => {
94
+ const key = `${r.tech}:${r.fband}`;
95
+ return state.visibleTechBands.has(key);
96
+ }));
97
+ // Auto-save settings when they change
98
+ $effect(() => {
99
+ // Convert Set to Array and Map to Object for serialization
100
+ const visibleArray = Array.from(state.visibleTechBands);
101
+ const colorsObj = {};
102
+ state.techBandColorMap.forEach((color, key) => {
103
+ colorsObj[key] = color;
104
+ });
105
+ const settings = {
106
+ visibleTechBands: visibleArray,
107
+ techBandColors: colorsObj,
108
+ showRepeaters: state.showRepeaters,
109
+ baseRadius: state.baseRadius,
110
+ fillOpacity: state.fillOpacity,
111
+ lineWidth: state.lineWidth,
112
+ // Label settings
113
+ showLabels: state.showLabels,
114
+ primaryLabelField: state.primaryLabelField,
115
+ secondaryLabelField: state.secondaryLabelField,
116
+ labelSize: state.labelSize,
117
+ labelColor: state.labelColor,
118
+ labelOffset: state.labelOffset,
119
+ labelHaloColor: state.labelHaloColor,
120
+ labelHaloWidth: state.labelHaloWidth,
121
+ minLabelZoom: state.minLabelZoom
122
+ };
123
+ saveSettings(settings);
124
+ });
125
+ // Return store context
126
+ return {
127
+ // Data getters
128
+ get repeaters() { return state.repeaters; },
129
+ get filteredRepeaters() { return filteredRepeaters; },
130
+ get visibleTechBands() { return state.visibleTechBands; },
131
+ get techBandColorMap() { return state.techBandColorMap; },
132
+ // Visual getters
133
+ get showRepeaters() { return state.showRepeaters; },
134
+ get baseRadius() { return state.baseRadius; },
135
+ get fillOpacity() { return state.fillOpacity; },
136
+ get lineWidth() { return state.lineWidth; },
137
+ get currentZoom() { return state.currentZoom; },
138
+ // Label getters
139
+ get showLabels() { return state.showLabels; },
140
+ get primaryLabelField() { return state.primaryLabelField; },
141
+ get secondaryLabelField() { return state.secondaryLabelField; },
142
+ get labelSize() { return state.labelSize; },
143
+ get labelColor() { return state.labelColor; },
144
+ get labelOffset() { return state.labelOffset; },
145
+ get labelHaloColor() { return state.labelHaloColor; },
146
+ get labelHaloWidth() { return state.labelHaloWidth; },
147
+ get minLabelZoom() { return state.minLabelZoom; },
148
+ // Data methods
149
+ setRepeaters(repeaters) {
150
+ state.repeaters = repeaters;
151
+ },
152
+ toggleTechBand(tech, fband) {
153
+ const key = `${tech}:${fband}`;
154
+ if (state.visibleTechBands.has(key)) {
155
+ state.visibleTechBands.delete(key);
156
+ }
157
+ else {
158
+ state.visibleTechBands.add(key);
159
+ }
160
+ // Trigger reactivity by creating new Set
161
+ state.visibleTechBands = new Set(state.visibleTechBands);
162
+ },
163
+ isTechBandVisible(tech, fband) {
164
+ const key = `${tech}:${fband}`;
165
+ return state.visibleTechBands.has(key);
166
+ },
167
+ getTechBandColor(tech, fband) {
168
+ const key = `${tech}:${fband}`;
169
+ return state.techBandColorMap.get(key) || '#888888'; // Fallback gray
170
+ },
171
+ setTechBandColor(tech, fband, color) {
172
+ const key = `${tech}:${fband}`;
173
+ state.techBandColorMap.set(key, color);
174
+ // Trigger reactivity by creating new Map
175
+ state.techBandColorMap = new Map(state.techBandColorMap);
176
+ },
177
+ // Visual setters
178
+ setShowRepeaters(value) {
179
+ state.showRepeaters = value;
180
+ },
181
+ setBaseRadius(value) {
182
+ state.baseRadius = value;
183
+ },
184
+ setFillOpacity(value) {
185
+ state.fillOpacity = value;
186
+ },
187
+ setLineWidth(value) {
188
+ state.lineWidth = value;
189
+ },
190
+ setCurrentZoom(value) {
191
+ state.currentZoom = value;
192
+ },
193
+ // Label setters
194
+ setShowLabels(value) {
195
+ state.showLabels = value;
196
+ },
197
+ setPrimaryLabelField(field) {
198
+ state.primaryLabelField = field;
199
+ },
200
+ setSecondaryLabelField(field) {
201
+ state.secondaryLabelField = field;
202
+ },
203
+ setLabelSize(value) {
204
+ state.labelSize = value;
205
+ },
206
+ setLabelColor(value) {
207
+ state.labelColor = value;
208
+ },
209
+ setLabelOffset(value) {
210
+ state.labelOffset = value;
211
+ },
212
+ setLabelHaloColor(value) {
213
+ state.labelHaloColor = value;
214
+ },
215
+ setLabelHaloWidth(value) {
216
+ state.labelHaloWidth = value;
217
+ },
218
+ setMinLabelZoom(value) {
219
+ state.minLabelZoom = value;
220
+ }
221
+ };
222
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Repeater type definitions
3
+ */
4
+ /**
5
+ * Repeater - Represents a cellular network repeater
6
+ *
7
+ * Repeaters amplify signals from donor cells to extend coverage.
8
+ * They are visualized as arc sectors with a fixed 30° beamwidth.
9
+ */
10
+ export interface Repeater {
11
+ /** Unique repeater identifier */
12
+ repeaterId: string;
13
+ /** ID of the donor cell providing the signal */
14
+ donorCellId: string;
15
+ /** Human-readable name of the donor cell */
16
+ donorCellName: string;
17
+ /** Frequency band (e.g., "1800", "2600") */
18
+ fband: string;
19
+ /** Technology (e.g., "2G", "4G", "5G") */
20
+ tech: string;
21
+ /** Type discriminator */
22
+ type: 'REPEATER';
23
+ /** Direction the repeater faces (0-360°) */
24
+ azimuth: number;
25
+ /** Physical height (meters) */
26
+ height: number;
27
+ /** Geographic coordinates */
28
+ longitude: number;
29
+ latitude: number;
30
+ /** Coverage angle (degrees) - typically 30° for repeaters */
31
+ beamwidth: number;
32
+ /** Factory/hardware number */
33
+ factory_nbr: string;
34
+ /** Optional: Network operator */
35
+ provider?: string;
36
+ /** Optional: Custom grouping */
37
+ featureGroup?: string;
38
+ /** Optional: Repeater name */
39
+ name?: string;
40
+ /** Optional: Status */
41
+ status?: 'active' | 'inactive' | 'planned';
42
+ }
43
+ /**
44
+ * TreeNode - For the fixed Tech → FBand tree structure
45
+ */
46
+ export interface RepeaterTreeNode {
47
+ /** Unique node ID (e.g., "4G", "4G:1800") */
48
+ id: string;
49
+ /** Display name */
50
+ name: string;
51
+ /** Child nodes */
52
+ children: RepeaterTreeNode[];
53
+ /** Color for leaf nodes (tech:fband combinations) */
54
+ color?: string;
55
+ /** Whether this node is visible/selected */
56
+ visible?: boolean;
57
+ /** Repeater count for this node */
58
+ count?: number;
59
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Repeater type definitions
3
+ */
4
+ export {};
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Repeater GeoJSON Conversion
3
+ *
4
+ * Convert repeater data to GeoJSON FeatureCollection with arc geometries
5
+ * Fixed 30° beamwidth for all repeaters
6
+ * Uses inverse zoom scaling like cells (larger arcs when zoomed out)
7
+ * Includes tech-band specific radius multipliers and z-ordering
8
+ */
9
+ import type { FeatureCollection, Polygon } from 'geojson';
10
+ import type { Repeater } from '../types';
11
+ /**
12
+ * Convert repeaters to GeoJSON FeatureCollection with arc geometries
13
+ *
14
+ * @param repeaters - Array of repeaters to convert
15
+ * @param currentZoom - Current map zoom level for radius calculation
16
+ * @param baseRadius - Base radius in meters (zoom-reactive)
17
+ * @param techBandColorMap - Map of tech:fband -> color
18
+ * @returns GeoJSON FeatureCollection with arc polygons
19
+ */
20
+ export declare function repeatersToGeoJSON(repeaters: Repeater[], currentZoom: number, baseRadius: number | undefined, techBandColorMap: Map<string, string>): FeatureCollection<Polygon>;
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Repeater GeoJSON Conversion
3
+ *
4
+ * Convert repeater data to GeoJSON FeatureCollection with arc geometries
5
+ * Fixed 30° beamwidth for all repeaters
6
+ * Uses inverse zoom scaling like cells (larger arcs when zoomed out)
7
+ * Includes tech-band specific radius multipliers and z-ordering
8
+ */
9
+ import { createArcPolygon } from '../../cells/utils/arcGeometry';
10
+ import { getRepeaterRadiusMultiplier } from '../constants/radiusMultipliers';
11
+ import { getRepeaterZOrder } from '../constants/techBandZOrder';
12
+ /** Fixed beamwidth for all repeaters */
13
+ const REPEATER_BEAMWIDTH = 30;
14
+ /** Reference zoom level where base radius is used as-is */
15
+ const REFERENCE_ZOOM = 12;
16
+ /**
17
+ * Get zoom factor for visual size scaling (inverse scaling)
18
+ * Lower zoom = larger radius (to compensate for zoomed out view)
19
+ * Higher zoom = smaller radius (to compensate for zoomed in view)
20
+ *
21
+ * Formula: 2^((referenceZoom - currentZoom) / 2)
22
+ * - At zoom 12: factor = 1.0
23
+ * - At zoom 10: factor = 2.0 (double the radius)
24
+ * - At zoom 14: factor = 0.5 (half the radius)
25
+ */
26
+ function getZoomFactor(currentZoom) {
27
+ return Math.pow(2, (REFERENCE_ZOOM - currentZoom) / 2);
28
+ }
29
+ /**
30
+ * Convert repeaters to GeoJSON FeatureCollection with arc geometries
31
+ *
32
+ * @param repeaters - Array of repeaters to convert
33
+ * @param currentZoom - Current map zoom level for radius calculation
34
+ * @param baseRadius - Base radius in meters (zoom-reactive)
35
+ * @param techBandColorMap - Map of tech:fband -> color
36
+ * @returns GeoJSON FeatureCollection with arc polygons
37
+ */
38
+ export function repeatersToGeoJSON(repeaters, currentZoom, baseRadius = 500, techBandColorMap) {
39
+ const features = repeaters.map((repeater) => {
40
+ // Get color for this tech:fband combination
41
+ const key = `${repeater.tech}:${repeater.fband}`;
42
+ const color = techBandColorMap.get(key) || '#888888'; // Fallback gray
43
+ // Calculate zoom-reactive radius with inverse scaling (like cells)
44
+ const zoomFactor = getZoomFactor(currentZoom);
45
+ // Apply tech-band specific radius multiplier
46
+ const techBandMultiplier = getRepeaterRadiusMultiplier(repeater.tech, repeater.fband);
47
+ const radius = baseRadius * zoomFactor * techBandMultiplier;
48
+ // Get z-order for layering (higher frequency = higher z-order = on top)
49
+ const zOrder = getRepeaterZOrder(repeater.tech, repeater.fband);
50
+ // Create arc polygon geometry with fixed 30° beamwidth
51
+ const center = [repeater.longitude, repeater.latitude];
52
+ const arc = createArcPolygon(center, repeater.azimuth, REPEATER_BEAMWIDTH, radius);
53
+ // Build feature with properties
54
+ return {
55
+ type: 'Feature',
56
+ geometry: arc.geometry,
57
+ properties: {
58
+ // Repeater identification
59
+ repeaterId: repeater.repeaterId,
60
+ factory_nbr: repeater.factory_nbr,
61
+ name: repeater.name || repeater.repeaterId,
62
+ // Donor cell info
63
+ donorCellId: repeater.donorCellId,
64
+ donorCellName: repeater.donorCellName,
65
+ // Technical attributes
66
+ tech: repeater.tech,
67
+ fband: repeater.fband,
68
+ azimuth: repeater.azimuth,
69
+ beamwidth: REPEATER_BEAMWIDTH,
70
+ height: repeater.height,
71
+ // Optional attributes
72
+ provider: repeater.provider || '',
73
+ featureGroup: repeater.featureGroup || '',
74
+ status: repeater.status || 'active',
75
+ // Styling properties for Mapbox
76
+ techBandKey: key,
77
+ techBandColor: color,
78
+ lineColor: color,
79
+ lineOpacity: 0.8,
80
+ // Z-order for layering (higher frequency on top)
81
+ zOrder: zOrder,
82
+ sortKey: zOrder // Used by Mapbox for layer ordering
83
+ }
84
+ };
85
+ });
86
+ return {
87
+ type: 'FeatureCollection',
88
+ features
89
+ };
90
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Repeater Tree Utilities
3
+ *
4
+ * Builds a fixed Tech→FBand two-level tree structure for repeater filtering.
5
+ * Leaf nodes (tech:fband) can have custom colors.
6
+ */
7
+ import type { Repeater } from '../types';
8
+ import type { TreeNode } from '../../../../core/TreeView/tree.model';
9
+ /**
10
+ * Build a two-level tree: Tech → FBand
11
+ *
12
+ * @param repeaters - All repeaters
13
+ * @param colorMap - Map of tech:fband to color
14
+ * @returns Root tree node
15
+ */
16
+ export declare function buildRepeaterTree(repeaters: Repeater[], colorMap: Map<string, string>): TreeNode;
17
+ /**
18
+ * Get set of selected tech:fband keys from checked paths
19
+ *
20
+ * @param checkedPaths - Set of checked node paths from tree
21
+ * @returns Set of "tech:fband" keys that are checked
22
+ */
23
+ export declare function getFilteredRepeaters(checkedPaths: Set<string>): Set<string>;
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Repeater Tree Utilities
3
+ *
4
+ * Builds a fixed Tech→FBand two-level tree structure for repeater filtering.
5
+ * Leaf nodes (tech:fband) can have custom colors.
6
+ */
7
+ /**
8
+ * Build a two-level tree: Tech → FBand
9
+ *
10
+ * @param repeaters - All repeaters
11
+ * @param colorMap - Map of tech:fband to color
12
+ * @returns Root tree node
13
+ */
14
+ export function buildRepeaterTree(repeaters, colorMap) {
15
+ // Group repeaters by tech, then by fband
16
+ const techMap = new Map();
17
+ for (const repeater of repeaters) {
18
+ const { tech, fband } = repeater;
19
+ if (!techMap.has(tech)) {
20
+ techMap.set(tech, new Map());
21
+ }
22
+ const fbandMap = techMap.get(tech);
23
+ if (!fbandMap.has(fband)) {
24
+ fbandMap.set(fband, []);
25
+ }
26
+ fbandMap.get(fband).push(repeater);
27
+ }
28
+ // Build tree structure
29
+ const techNodes = [];
30
+ // Sort technologies: 2G, 4G, 5G
31
+ const sortedTechs = Array.from(techMap.keys()).sort((a, b) => {
32
+ const order = { '2G': 1, '4G': 2, '5G': 3 };
33
+ return (order[a] || 99) - (order[b] || 99);
34
+ });
35
+ for (const tech of sortedTechs) {
36
+ const fbandMap = techMap.get(tech);
37
+ const fbandNodes = [];
38
+ // Sort fbands alphabetically
39
+ const sortedFbands = Array.from(fbandMap.keys()).sort();
40
+ for (const fband of sortedFbands) {
41
+ const repeatersInFband = fbandMap.get(fband);
42
+ const techBandKey = `${tech}:${fband}`;
43
+ const color = colorMap.get(techBandKey) || '#808080';
44
+ fbandNodes.push({
45
+ id: techBandKey,
46
+ label: `${fband} (${repeatersInFband.length})`,
47
+ defaultChecked: true,
48
+ defaultExpanded: false,
49
+ children: [],
50
+ metadata: { color } // Leaf nodes have colors in metadata
51
+ });
52
+ }
53
+ techNodes.push({
54
+ id: tech,
55
+ label: `${tech} (${Array.from(fbandMap.values()).flat().length})`,
56
+ defaultChecked: true,
57
+ defaultExpanded: true,
58
+ children: fbandNodes
59
+ });
60
+ }
61
+ // Root node
62
+ return {
63
+ id: 'root',
64
+ label: `All Repeaters (${repeaters.length})`,
65
+ defaultChecked: true,
66
+ defaultExpanded: true,
67
+ children: techNodes
68
+ };
69
+ }
70
+ /**
71
+ * Get set of selected tech:fband keys from checked paths
72
+ *
73
+ * @param checkedPaths - Set of checked node paths from tree
74
+ * @returns Set of "tech:fband" keys that are checked
75
+ */
76
+ export function getFilteredRepeaters(checkedPaths) {
77
+ const selected = new Set();
78
+ console.log('getFilteredRepeaters - checkedPaths:', Array.from(checkedPaths));
79
+ // If only root is checked, all tech:fbands are visible
80
+ if (checkedPaths.has('root') && checkedPaths.size === 1) {
81
+ console.log('Only root checked - returning empty set for "all visible"');
82
+ return selected;
83
+ }
84
+ // Check each path - leaf nodes have tech:fband in their ID
85
+ for (const path of checkedPaths) {
86
+ // Paths are like "root:2G:2G:GSM900" where the node ID is the last segment
87
+ // The node ID for leaf nodes is "tech:fband" format
88
+ const parts = path.split(':');
89
+ console.log('Processing path:', path, 'parts:', parts);
90
+ // Check if this is a leaf node (contains tech:fband format)
91
+ // Leaf node IDs are like "2G:GSM900", "4G:LTE1800"
92
+ // The path would be "root:2G:2G:GSM900" (root -> tech -> tech:fband)
93
+ if (parts.length === 4 && parts[0] === 'root') {
94
+ // This is a leaf node: root:tech:tech:fband
95
+ const techBandKey = `${parts[2]}:${parts[3]}`;
96
+ console.log('Adding tech:fband key:', techBandKey);
97
+ selected.add(techBandKey);
98
+ }
99
+ else if (parts.length === 3 && parts[0] === 'root') {
100
+ // This might be "root:2G:GSM900" if node ID is "2G:GSM900"
101
+ const nodeId = parts[2];
102
+ if (nodeId && nodeId.includes(':')) {
103
+ // This is already in tech:fband format
104
+ console.log('Adding tech:fband key (alternative format):', nodeId);
105
+ selected.add(nodeId);
106
+ }
107
+ }
108
+ }
109
+ console.log('getFilteredRepeaters - selected tech:bands:', Array.from(selected));
110
+ return selected;
111
+ }
@@ -12,6 +12,7 @@
12
12
  import RepeaterSettingsPanel from './panels/RepeaterSettingsPanel.svelte';
13
13
  import type { SiteStoreContext } from '../../features/sites/stores/siteStoreContext.svelte';
14
14
  import type { CellStoreContext } from '../../features/cells/stores/cellStoreContext.svelte';
15
+ import type { RepeaterStoreContext } from '../../features/repeaters/stores/repeaterStoreContext.svelte';
15
16
  import type { Cell, CellGroupingLabels } from '../../features/cells/types';
16
17
 
17
18
  interface Props {
@@ -19,6 +20,8 @@
19
20
  siteStore: SiteStoreContext;
20
21
  /** Cell store context */
21
22
  cellStore: CellStoreContext;
23
+ /** Repeater store context (optional) */
24
+ repeaterStore?: RepeaterStoreContext;
22
25
  /** Available label field options for 4G/5G cells */
23
26
  labelFieldOptions4G5G: Array<keyof Cell>;
24
27
  /** Available label field options for 2G cells */
@@ -40,6 +43,7 @@
40
43
  let {
41
44
  siteStore,
42
45
  cellStore,
46
+ repeaterStore,
43
47
  labelFieldOptions4G5G,
44
48
  labelFieldOptions2G,
45
49
  fieldLabels,
@@ -111,30 +115,32 @@
111
115
  </div>
112
116
  </div>
113
117
 
114
- <!-- Repeater Settings Accordion Item (Placeholder) -->
115
- <div class="accordion-item">
116
- <h2 class="accordion-header" id="heading-repeaters">
117
- <button
118
- class="accordion-button collapsed"
119
- type="button"
120
- data-bs-toggle="collapse"
121
- data-bs-target="#collapse-repeaters"
122
- aria-expanded="false"
123
- aria-controls="collapse-repeaters"
118
+ <!-- Repeater Settings Accordion Item -->
119
+ {#if repeaterStore}
120
+ <div class="accordion-item">
121
+ <h2 class="accordion-header" id="heading-repeaters">
122
+ <button
123
+ class="accordion-button collapsed"
124
+ type="button"
125
+ data-bs-toggle="collapse"
126
+ data-bs-target="#collapse-repeaters"
127
+ aria-expanded="false"
128
+ aria-controls="collapse-repeaters"
129
+ >
130
+ Repeater Settings
131
+ </button>
132
+ </h2>
133
+ <div
134
+ id="collapse-repeaters"
135
+ class="accordion-collapse collapse"
136
+ aria-labelledby="heading-repeaters"
124
137
  >
125
- Repeater Settings
126
- </button>
127
- </h2>
128
- <div
129
- id="collapse-repeaters"
130
- class="accordion-collapse collapse"
131
- aria-labelledby="heading-repeaters"
132
- >
133
- <div class="accordion-body">
134
- <RepeaterSettingsPanel />
138
+ <div class="accordion-body">
139
+ <RepeaterSettingsPanel store={repeaterStore} />
140
+ </div>
135
141
  </div>
136
142
  </div>
137
- </div>
143
+ {/if}
138
144
  </div>
139
145
  </MapControl>
140
146
 
@@ -1,11 +1,14 @@
1
1
  import type { SiteStoreContext } from '../../features/sites/stores/siteStoreContext.svelte';
2
2
  import type { CellStoreContext } from '../../features/cells/stores/cellStoreContext.svelte';
3
+ import type { RepeaterStoreContext } from '../../features/repeaters/stores/repeaterStoreContext.svelte';
3
4
  import type { Cell, CellGroupingLabels } from '../../features/cells/types';
4
5
  interface Props {
5
6
  /** Site store context */
6
7
  siteStore: SiteStoreContext;
7
8
  /** Cell store context */
8
9
  cellStore: CellStoreContext;
10
+ /** Repeater store context (optional) */
11
+ repeaterStore?: RepeaterStoreContext;
9
12
  /** Available label field options for 4G/5G cells */
10
13
  labelFieldOptions4G5G: Array<keyof Cell>;
11
14
  /** Available label field options for 2G cells */