@smartnet360/svelte-components 0.0.103 β†’ 0.0.105

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.
@@ -7,7 +7,6 @@
7
7
  import { createTimeSeriesTraceWithMA, getYAxisTitle, createDefaultPlotlyLayout } from './data-utils.js';
8
8
  import { adaptPlotlyLayout, addMarkersToLayout, type ContainerSize } from './adapt.js';
9
9
  import { getKPIValues, type ProcessedChartData } from './data-processor.js';
10
- import { log } from '../logger';
11
10
  import { checkHealth, getMessage } from '../FeatureRegistry';
12
11
 
13
12
  interface Props {
@@ -244,22 +243,9 @@
244
243
  // Use Plotly.react() for updates (preserves zoom/pan) or newPlot for initial render
245
244
  if (chartInitialized) {
246
245
  // Update existing chart - much faster, preserves user interactions
247
- log('πŸ”„ Updating chart with Plotly.react', {
248
- chartTitle: chart.title,
249
- hoverMode: effectiveHoverMode,
250
- layoutHoverMode: finalLayout.hovermode
251
- });
252
246
  Plotly.react(chartDiv, traces, finalLayout, config);
253
247
  } else {
254
248
  // Initial chart creation
255
- log('πŸ“Š Creating new chart with Plotly.newPlot', {
256
- chartTitle: chart.title,
257
- traces: traces.length,
258
- leftKPIs: chart.yLeft.length,
259
- rightKPIs: chart.yRight.length,
260
- hoverMode: effectiveHoverMode,
261
- layoutHoverMode: finalLayout.hovermode
262
- });
263
249
  Plotly.newPlot(chartDiv, traces, finalLayout, config);
264
250
  chartInitialized = true;
265
251
  }
@@ -273,14 +259,6 @@
273
259
  }
274
260
 
275
261
  onMount(() => {
276
- log('πŸ“ˆ ChartCard mounted', {
277
- chartTitle: chart.title,
278
- leftKPIs: chart.yLeft.length,
279
- rightKPIs: chart.yRight.length,
280
- renderDelay,
281
- lazyRender
282
- });
283
-
284
262
  // Initial container size measurement
285
263
  if (chartDiv) {
286
264
  const rect = chartDiv.getBoundingClientRect();
@@ -312,7 +290,6 @@
312
290
  (entries) => {
313
291
  const [entry] = entries;
314
292
  if (entry.isIntersecting && !chartInitialized) {
315
- log('πŸ‘οΈ Chart entering viewport', { chartTitle: chart.title });
316
293
  isVisible = true;
317
294
  doRender();
318
295
  // Disconnect after first render - chart stays rendered
@@ -6,7 +6,6 @@
6
6
  import ChartCard from './ChartCard.svelte';
7
7
  import GlobalControls from './GlobalControls.svelte';
8
8
  import { getPreprocessedData, type ProcessedChartData } from './data-processor.js';
9
- import { log } from '../logger';
10
9
  import { checkHealth, getMessage } from '../FeatureRegistry';
11
10
 
12
11
  interface Props {
@@ -58,18 +57,6 @@
58
57
 
59
58
  let { layout, data, mode, markers, plotlyLayout, enableAdaptation = true, showGlobalControls = true, persistSettings = false }: Props = $props();
60
59
  let isHealthy = $state(checkHealth('charts'));
61
- // Log component initialization
62
- $effect(() => {
63
- log('πŸ“Š ChartComponent initialized', {
64
- mode,
65
- dataRows: data.length,
66
- sections: layout.sections.length,
67
- totalCharts: layout.sections.reduce((sum, s) => sum + s.charts.length, 0),
68
- enableAdaptation,
69
- showGlobalControls,
70
- layoutHoverMode: layout.hoverMode
71
- });
72
- });
73
60
 
74
61
  // Preprocess raw data once - automatically memoized by Svelte's $derived
75
62
  // This extracts all KPI values and timestamps, cached until data or layout changes
@@ -98,13 +85,6 @@
98
85
 
99
86
  // Handler for global controls updates
100
87
  function handleControlsUpdate(updatedControls: GlobalChartControls) {
101
- log('πŸŽ›οΈ Global controls updated', {
102
- movingAverageEnabled: updatedControls.movingAverage?.enabled,
103
- windowOverride: updatedControls.movingAverage?.windowOverride,
104
- markersEnabled: updatedControls.markers?.enabled,
105
- legendEnabled: updatedControls.legend?.enabled,
106
- hoverMode: updatedControls.hoverMode?.mode
107
- });
108
88
  globalControls = updatedControls;
109
89
  }
110
90
 
@@ -195,17 +175,12 @@
195
175
 
196
176
  function zoomSelectedChart() {
197
177
  if (contextMenu.chart && contextMenu.section) {
198
- log('πŸ” Zooming chart', {
199
- chartTitle: contextMenu.chart.title,
200
- sectionId: contextMenu.section.id
201
- });
202
178
  zoomedChart = { chart: contextMenu.chart, section: contextMenu.section };
203
179
  }
204
180
  closeContextMenu();
205
181
  }
206
182
 
207
183
  function exitZoom() {
208
- log('πŸ” Exiting zoom mode');
209
184
  zoomedChart = null;
210
185
  closeContextMenu();
211
186
  }
@@ -1,4 +1,3 @@
1
- import { log } from '../logger';
2
1
  /**
3
2
  * Extract all unique KPI rawNames from a layout configuration
4
3
  * This determines which columns we need to extract from raw data
@@ -17,10 +16,6 @@ export function extractKPINames(layout) {
17
16
  }
18
17
  }
19
18
  }
20
- log('πŸ“‹ KPI names extracted', {
21
- totalKPIs: kpiNames.size,
22
- kpiNames: Array.from(kpiNames)
23
- });
24
19
  return kpiNames;
25
20
  }
26
21
  /**
@@ -33,11 +28,6 @@ export function extractKPINames(layout) {
33
28
  * @returns Preprocessed data ready for chart rendering
34
29
  */
35
30
  export function preprocessChartData(data, layout, timestampField = 'TIMESTAMP') {
36
- log('πŸ”„ Preprocessing chart data', {
37
- rawDataRows: data.length,
38
- sections: layout.sections.length,
39
- timestampField
40
- });
41
31
  // Extract all unique KPI names we need to process
42
32
  const kpiNames = extractKPINames(layout);
43
33
  // Initialize the result map
@@ -53,17 +43,11 @@ export function preprocessChartData(data, layout, timestampField = 'TIMESTAMP')
53
43
  .filter(val => !isNaN(val)); // Remove invalid values
54
44
  kpiValues.set(kpiName, values);
55
45
  if (values.length === 0) {
56
- log('⚠️ No valid values found for KPI', { kpiName });
46
+ console.warn(`[Charts] No valid values found for KPI: ${kpiName}`);
57
47
  }
58
48
  }
59
49
  // Extract timestamps once
60
50
  const timestamps = data.map(row => row[timestampField]);
61
- log('βœ… Data preprocessing complete', {
62
- processedKPIs: kpiValues.size,
63
- timestampCount: timestamps.length,
64
- sampleKPI: kpiValues.keys().next().value,
65
- sampleValues: kpiValues.values().next().value?.slice(0, 3)
66
- });
67
51
  return {
68
52
  kpiValues,
69
53
  timestamps,
@@ -91,11 +75,9 @@ export function getPreprocessedData(data, layout, timestampField = 'TIMESTAMP')
91
75
  if (cached) {
92
76
  // Verify cache is still valid (data reference matches)
93
77
  if (cached._rawDataRef === data) {
94
- log('πŸ’Ύ Using cached preprocessed data');
95
78
  return cached;
96
79
  }
97
80
  }
98
- log('πŸ”„ Cache miss - preprocessing data');
99
81
  // Cache miss or invalid - compute and cache
100
82
  const processed = preprocessChartData(data, layout, timestampField);
101
83
  preprocessCache.set(data, processed);
@@ -3,4 +3,5 @@ export * from './Charts/index.js';
3
3
  export * from './TreeView/index.js';
4
4
  export * from './Settings/index.js';
5
5
  export * from './logger/index.js';
6
+ export * from './CellTable/index.js';
6
7
  export * from './FeatureRegistry/index.js';
@@ -10,6 +10,8 @@ export * from './TreeView/index.js';
10
10
  export * from './Settings/index.js';
11
11
  // Logger utility for debugging and monitoring
12
12
  export * from './logger/index.js';
13
+ // CellTable - Tabulator-based cell data table with Bootstrap theming
14
+ export * from './CellTable/index.js';
13
15
  // Map component - Mapbox GL + Deck.GL integration
14
16
  // TODO: Moved to top-level src/lib/map module
15
17
  // export * from './Map/index.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartnet360/svelte-components",
3
- "version": "0.0.103",
3
+ "version": "0.0.105",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",
@@ -39,7 +39,8 @@
39
39
  "dexie": "^4.0.11",
40
40
  "mapbox-gl": "^3.0.0",
41
41
  "plotly.js-dist-min": "^3.1.0",
42
- "svelte": "^5.0.0"
42
+ "svelte": "^5.0.0",
43
+ "tabulator-tables": "^6.3.1"
43
44
  },
44
45
  "devDependencies": {
45
46
  "@eslint/compat": "^1.2.5",
@@ -1,56 +0,0 @@
1
- /**
2
- * Data Transforms for Site Check Component
3
- * Converts raw CSV data to TreeView nodes and Chart configurations
4
- */
5
- import type { TreeNode } from '../../core/TreeView';
6
- import type { KPI, CellStylingConfig } from '../../core/Charts';
7
- import type { CellTrafficRecord } from './data-loader';
8
- /**
9
- * Extract band from cell name using regex pattern matching
10
- * @param cellName - Cell name like "LTE700_1", "NR3500_2", etc.
11
- * @returns Band string like "LTE700", "NR3500" or null if not found
12
- * @deprecated Use the band field from CellTrafficRecord instead
13
- */
14
- export declare function extractBandFromCell(cellName: string): string | null;
15
- /**
16
- * Get frequency order for a band (for sorting)
17
- * @param band - Band string like "LTE700", "NR3500"
18
- * @returns Frequency number or high value for unknown bands
19
- */
20
- export declare function getBandFrequency(band: string | null): number;
21
- /**
22
- * Sort items by band frequency using actual band data from records
23
- * @param items - Array of [cellName, record] tuples
24
- * @returns Sorted array (ascending frequency order)
25
- */
26
- export declare function sortCellsByBandFrequency(items: [string, CellTrafficRecord][]): [string, CellTrafficRecord][];
27
- /**
28
- * Build hierarchical tree structure: Site β†’ Sector (Azimuth) β†’ Cell (Band)
29
- */
30
- export declare function buildTreeNodes(data: CellTrafficRecord[]): TreeNode[];
31
- /**
32
- * Filter chart data based on selected tree paths
33
- * Only include cells that are checked in the tree
34
- */
35
- export declare function filterChartData(data: CellTrafficRecord[], checkedPaths: Set<string>): CellTrafficRecord[];
36
- /**
37
- * Transform data for chart component consumption
38
- * Pivots data so each cell becomes its own KPI column
39
- * Transforms from long format (many rows per cell) to wide format (one column per cell)
40
- *
41
- * @param data - Filtered cell traffic records
42
- * @param baseMetrics - Array of metric names to pivot (e.g., ['dlGBytes', 'ulGBytes'])
43
- */
44
- export declare function transformChartData(data: CellTrafficRecord[], baseMetrics: string[]): any[];
45
- /**
46
- * Apply cell styling based on band and sector
47
- * Modifies KPI objects to include color (from band) and lineStyle (from sector)
48
- * Updates KPI name to format: Band_AzimuthΒ°
49
- *
50
- * @param metricName - Base metric name (e.g., 'dlGBytes')
51
- * @param cellRecord - Cell traffic record with band, sector, azimuth metadata
52
- * @param unit - Unit string for the metric
53
- * @param stylingConfig - Optional cell styling configuration (band colors, sector line styles)
54
- * @returns Styled KPI object
55
- */
56
- export declare function createStyledKPI(metricName: string, cellRecord: CellTrafficRecord, unit: string, stylingConfig?: CellStylingConfig): KPI;
@@ -1,273 +0,0 @@
1
- /**
2
- * Data Transforms for Site Check Component
3
- * Converts raw CSV data to TreeView nodes and Chart configurations
4
- */
5
- import { log } from '../../core/logger';
6
- /**
7
- * Band frequency mapping for consistent ordering
8
- * Maps band strings to their actual frequencies in MHz
9
- */
10
- const BAND_FREQUENCY_ORDER = {
11
- // LTE Bands (by frequency)
12
- 'LTE700': 700,
13
- 'LTE800': 800,
14
- 'LTE900': 900,
15
- 'LTE1800': 1800,
16
- 'LTE2100': 2100,
17
- 'LTE2600': 2600,
18
- // NR/5G Bands (by frequency)
19
- 'NR700': 700.1, // Slightly higher to sort after LTE700
20
- 'NR2100': 2100.1,
21
- 'NR3500': 3500,
22
- 'NR26000': 26000 // mmWave
23
- };
24
- /**
25
- * Extract band from cell name using regex pattern matching
26
- * @param cellName - Cell name like "LTE700_1", "NR3500_2", etc.
27
- * @returns Band string like "LTE700", "NR3500" or null if not found
28
- * @deprecated Use the band field from CellTrafficRecord instead
29
- */
30
- export function extractBandFromCell(cellName) {
31
- // Match patterns like "LTE700", "NR3500", etc.
32
- const match = cellName.match(/(LTE|NR)(\d+)/i);
33
- return match ? `${match[1].toUpperCase()}${match[2]}` : null;
34
- }
35
- /**
36
- * Get frequency order for a band (for sorting)
37
- * @param band - Band string like "LTE700", "NR3500"
38
- * @returns Frequency number or high value for unknown bands
39
- */
40
- export function getBandFrequency(band) {
41
- if (!band)
42
- return 999999; // Unknown bands go to end
43
- return BAND_FREQUENCY_ORDER[band] || 999999;
44
- }
45
- /**
46
- * Sort items by band frequency using actual band data from records
47
- * @param items - Array of [cellName, record] tuples
48
- * @returns Sorted array (ascending frequency order)
49
- */
50
- export function sortCellsByBandFrequency(items) {
51
- return items.sort((a, b) => {
52
- const [cellNameA, recordA] = a;
53
- const [cellNameB, recordB] = b;
54
- const freqA = getBandFrequency(recordA.band);
55
- const freqB = getBandFrequency(recordB.band);
56
- // Primary sort: by frequency
57
- if (freqA !== freqB) {
58
- return freqA - freqB;
59
- }
60
- // Secondary sort: by cell name for same frequency
61
- return cellNameA.localeCompare(cellNameB);
62
- });
63
- }
64
- /**
65
- * Build hierarchical tree structure: Site β†’ Sector (Azimuth) β†’ Cell (Band)
66
- */
67
- export function buildTreeNodes(data) {
68
- log('πŸ”„ Building tree nodes', { recordCount: data.length });
69
- // Group by site β†’ azimuth β†’ cell
70
- const siteMap = new Map();
71
- data.forEach((record) => {
72
- if (!siteMap.has(record.siteName)) {
73
- siteMap.set(record.siteName, new Map());
74
- }
75
- const azimuthMap = siteMap.get(record.siteName);
76
- if (!azimuthMap.has(record.azimuth)) {
77
- azimuthMap.set(record.azimuth, new Map());
78
- }
79
- const cellMap = azimuthMap.get(record.azimuth);
80
- // Store one record per cell (we just need metadata, not all time series)
81
- if (!cellMap.has(record.cellName)) {
82
- cellMap.set(record.cellName, record);
83
- }
84
- });
85
- // Build tree structure
86
- const treeNodes = [];
87
- Array.from(siteMap.entries())
88
- .sort(([a], [b]) => a.localeCompare(b))
89
- .forEach(([siteName, azimuthMap]) => {
90
- const siteNode = {
91
- id: siteName, // Simple ID
92
- label: `Site ${siteName}`,
93
- // icon: 'πŸ“‘',
94
- metadata: { type: 'site', siteName },
95
- defaultExpanded: false,
96
- defaultChecked: false, // Don't check parent nodes
97
- children: []
98
- };
99
- Array.from(azimuthMap.entries())
100
- .sort(([a], [b]) => a - b)
101
- .forEach(([azimuth, cellMap]) => {
102
- const sectorNode = {
103
- id: `${azimuth}`, // Simple ID (just azimuth)
104
- label: `${azimuth}Β° Sector`,
105
- // icon: 'πŸ“',
106
- metadata: { type: 'sector', azimuth, siteName },
107
- defaultExpanded: false,
108
- defaultChecked: false, // Don't check parent nodes
109
- children: []
110
- };
111
- // Sort cells by band frequency (LTE700, LTE800, etc.)
112
- const sortedCells = sortCellsByBandFrequency(Array.from(cellMap.entries()));
113
- sortedCells.forEach(([cellName, record]) => {
114
- const cellNode = {
115
- id: cellName, // Simple ID (just cell name)
116
- label: `${cellName} (${record.band})`,
117
- icon: getBandIcon(record.band),
118
- metadata: {
119
- type: 'cell',
120
- cellName,
121
- band: record.band,
122
- siteName: record.siteName,
123
- sector: record.sector,
124
- azimuth: record.azimuth
125
- },
126
- defaultChecked: true
127
- };
128
- sectorNode.children.push(cellNode);
129
- });
130
- siteNode.children.push(sectorNode);
131
- });
132
- treeNodes.push(siteNode);
133
- });
134
- log('βœ… Tree nodes built', {
135
- totalNodes: treeNodes.length,
136
- totalSites: siteMap.size,
137
- sampleSite: treeNodes[0]?.label
138
- });
139
- return treeNodes;
140
- }
141
- /**
142
- * Get icon emoji based on band technology
143
- */
144
- function getBandIcon(band) {
145
- return '';
146
- if (band.startsWith('NR'))
147
- return 'πŸ“Ά'; // 5G
148
- if (band.startsWith('LTE'))
149
- return 'πŸ“±'; // 4G
150
- return 'πŸ“‘'; // Fallback
151
- }
152
- /**
153
- * Filter chart data based on selected tree paths
154
- * Only include cells that are checked in the tree
155
- */
156
- export function filterChartData(data, checkedPaths) {
157
- log('πŸ”„ Filtering chart data', {
158
- totalRecords: data.length,
159
- checkedPathsCount: checkedPaths.size,
160
- paths: Array.from(checkedPaths)
161
- });
162
- // Extract cell names from checked leaf paths (format: "site:azimuth:cellName")
163
- const selectedCells = new Set();
164
- checkedPaths.forEach((path) => {
165
- const parts = path.split(':');
166
- if (parts.length === 3) {
167
- // This is a cell-level path (site:azimuth:cellName)
168
- selectedCells.add(parts[2]);
169
- }
170
- });
171
- // Filter data to only include selected cells
172
- const filtered = data.filter((record) => selectedCells.has(record.cellName));
173
- log('βœ… Data filtered', {
174
- selectedCells: Array.from(selectedCells),
175
- filteredRecords: filtered.length,
176
- uniqueCells: new Set(filtered.map(r => r.cellName)).size
177
- });
178
- return filtered;
179
- }
180
- /**
181
- * Transform data for chart component consumption
182
- * Pivots data so each cell becomes its own KPI column
183
- * Transforms from long format (many rows per cell) to wide format (one column per cell)
184
- *
185
- * @param data - Filtered cell traffic records
186
- * @param baseMetrics - Array of metric names to pivot (e.g., ['dlGBytes', 'ulGBytes'])
187
- */
188
- export function transformChartData(data, baseMetrics) {
189
- log('πŸ”„ Transforming chart data', {
190
- inputRecords: data.length,
191
- baseMetrics,
192
- uniqueCells: new Set(data.map(r => r.cellName)).size
193
- });
194
- // Group data by date
195
- const dateMap = new Map();
196
- data.forEach((record) => {
197
- if (!dateMap.has(record.date)) {
198
- dateMap.set(record.date, new Map());
199
- }
200
- dateMap.get(record.date).set(record.cellName, record);
201
- });
202
- // Build pivoted data: one row per date, one column per cell per metric
203
- const pivotedData = [];
204
- dateMap.forEach((cellsOnDate, date) => {
205
- const row = {
206
- TIMESTAMP: date
207
- };
208
- cellsOnDate.forEach((record, cellName) => {
209
- // Pivot each base metric into cell-specific columns
210
- baseMetrics.forEach((metricName) => {
211
- const value = record.metrics[metricName];
212
- if (value !== undefined) {
213
- row[`${metricName}_${cellName}`] = value;
214
- }
215
- });
216
- // Store metadata for reference (band, azimuth, etc.)
217
- row[`BAND_${cellName}`] = record.band;
218
- row[`AZIMUTH_${cellName}`] = record.azimuth;
219
- });
220
- pivotedData.push(row);
221
- });
222
- // Sort by date
223
- pivotedData.sort((a, b) => a.TIMESTAMP.localeCompare(b.TIMESTAMP));
224
- log('βœ… Data transformed', {
225
- outputRows: pivotedData.length,
226
- dateRange: pivotedData.length > 0 ?
227
- `${pivotedData[0].TIMESTAMP} to ${pivotedData[pivotedData.length - 1].TIMESTAMP}` :
228
- 'none',
229
- columnsPerRow: pivotedData[0] ? Object.keys(pivotedData[0]).length : 0,
230
- sampleRow: pivotedData[0]
231
- });
232
- return pivotedData;
233
- }
234
- /**
235
- * Apply cell styling based on band and sector
236
- * Modifies KPI objects to include color (from band) and lineStyle (from sector)
237
- * Updates KPI name to format: Band_AzimuthΒ°
238
- *
239
- * @param metricName - Base metric name (e.g., 'dlGBytes')
240
- * @param cellRecord - Cell traffic record with band, sector, azimuth metadata
241
- * @param unit - Unit string for the metric
242
- * @param stylingConfig - Optional cell styling configuration (band colors, sector line styles)
243
- * @returns Styled KPI object
244
- */
245
- export function createStyledKPI(metricName, cellRecord, unit, stylingConfig) {
246
- const { band, sector, azimuth, cellName } = cellRecord;
247
- // Get color from band (if config provided)
248
- const color = stylingConfig?.bandColors?.[band];
249
- // Get line style from sector (if config provided)
250
- const lineStyle = stylingConfig?.sectorLineStyles?.[sector.toString()];
251
- // Format name as: Band_AzimuthΒ°
252
- const displayName = `${band}_${azimuth}Β°`;
253
- // Build KPI with cell-specific styling
254
- const kpi = {
255
- rawName: `${metricName}_${cellName}`, // Column name in pivoted data
256
- name: displayName,
257
- scale: 'absolute',
258
- unit,
259
- ...(color && { color }),
260
- ...(lineStyle && { lineStyle })
261
- };
262
- log('🎨 Styled KPI created', {
263
- metricName,
264
- cellName,
265
- displayName,
266
- band,
267
- sector,
268
- azimuth,
269
- color,
270
- lineStyle
271
- });
272
- return kpi;
273
- }