@smartnet360/svelte-components 0.0.105 → 0.0.107

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/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # RF Tool Components
2
+
3
+ Private SvelteKit component library for RF planning and network visualization tools.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @smartnet360/svelte-components
9
+ ```
10
+
11
+ ## Components
12
+
13
+ ### Core Components (`@smartnet360/svelte-components/core`)
14
+
15
+ | Component | Description |
16
+ |-----------|-------------|
17
+ | **Desktop** | Grid orchestrator for multi-panel layouts with component assignment |
18
+ | **Charts** | Plotly-based chart system with tabs/scrollspy modes |
19
+ | **CellTable** | Tabulator-based cell data table with grouping and filtering |
20
+ | **TreeView** | Hierarchical tree component for data navigation |
21
+ | **Settings** | Configuration panels and settings management |
22
+ | **CoverageMap** | Coverage visualization component |
23
+
24
+ ### Map Components
25
+
26
+ | Package | Description |
27
+ |---------|-------------|
28
+ | **map-v2** | Legacy map implementation |
29
+ | **map-v3** | Current map implementation with cell/site features |
30
+
31
+ ### Shared Utilities (`@smartnet360/svelte-components/shared`)
32
+
33
+ | Module | Description |
34
+ |--------|-------------|
35
+ | **demo** | Cell data generator with configurable presets (small/medium/large/xlarge) |
36
+ | **ResizableSplitPanel** | Draggable split panel component |
37
+
38
+ ## Development
39
+
40
+ ```bash
41
+ # Install dependencies
42
+ npm install
43
+
44
+ # Run dev server
45
+ npm run dev
46
+
47
+ # Type checking
48
+ npm run check
49
+
50
+ # Build library
51
+ npm run build
52
+
53
+ # Release (patch/minor/major)
54
+ npm run release:patch
55
+ ```
56
+
57
+ ## Demo Routes
58
+
59
+ Demo pages available at `/apps/demo/`:
60
+ - `/apps/demo/cell-table` - CellTable component
61
+ - `/apps/demo/charts` - Charts component
62
+ - `/apps/demo/desktop` - Desktop grid system
63
+
64
+ ## Tech Stack
65
+
66
+ - **Svelte 5** with runes (`$state`, `$derived`, `$effect`)
67
+ - **SvelteKit** library mode
68
+ - **TypeScript** first
69
+ - **Bootstrap 5** for styling
70
+ - **Plotly.js** for charts
71
+ - **Tabulator** for data tables
72
+
73
+ ## License
74
+
75
+ Private - SmartNet360
@@ -57,8 +57,11 @@
57
57
  let table: Tabulator | null = null;
58
58
  let isInitialized = $state(false);
59
59
 
60
- // Reactive column configuration
61
- let columns = $derived(getColumnsForPreset(columnPreset, techColors, statusColors, headerFilters));
60
+ // Reactive column configuration - only changes when preset changes
61
+ let columns = $derived.by(() => {
62
+ // Only depend on columnPreset to avoid unnecessary recalculations
63
+ return getColumnsForPreset(columnPreset, techColors, statusColors, headerFilters);
64
+ });
62
65
 
63
66
  // Build Tabulator options
64
67
  function buildOptions(): Options {
@@ -171,13 +174,7 @@
171
174
  // Mark as initialized after table is ready
172
175
  table.on('tableBuilt', () => {
173
176
  isInitialized = true;
174
- });
175
- }
176
-
177
- // Update table data when cells change
178
- $effect(() => {
179
- if (isInitialized && table && cells) {
180
- table.replaceData(cells);
177
+ // Fire initial data change event
181
178
  if (ondatachange) {
182
179
  ondatachange({
183
180
  type: 'load',
@@ -185,28 +182,52 @@
185
182
  filteredCount: cells.length
186
183
  });
187
184
  }
185
+ });
186
+ }
187
+
188
+ // Track previous values to avoid unnecessary updates
189
+ let prevCellsLength = 0;
190
+ let prevCellsFirstId: string | null = null;
191
+ let prevGroupBy: string | null = null;
192
+ let prevColumnPreset: string | null = null;
193
+
194
+ // Update table data when cells actually change (not just reference)
195
+ $effect(() => {
196
+ const currentLength = cells?.length ?? 0;
197
+ const currentFirstId = cells?.[0]?.id ?? null;
198
+
199
+ // Only update if length or first item changed (rough equality check)
200
+ if (isInitialized && table &&
201
+ (currentLength !== prevCellsLength || currentFirstId !== prevCellsFirstId)) {
202
+ prevCellsLength = currentLength;
203
+ prevCellsFirstId = currentFirstId;
204
+ table.replaceData(cells);
205
+ ondatachange?.({
206
+ type: 'load',
207
+ rowCount: cells.length,
208
+ filteredCount: cells.length
209
+ });
188
210
  }
189
211
  });
190
212
 
191
213
  // Update grouping when groupBy changes
192
214
  $effect(() => {
193
- if (isInitialized && table) {
215
+ if (isInitialized && table && groupBy !== prevGroupBy) {
216
+ prevGroupBy = groupBy;
194
217
  if (groupBy === 'none') {
195
218
  table.setGroupBy(false);
196
219
  } else {
197
220
  table.setGroupBy(groupBy);
198
221
  table.setGroupHeader(getGroupHeaderFormatter(groupBy));
199
222
  }
200
- // Force redraw after grouping change
201
- table.redraw(true);
202
223
  }
203
224
  });
204
225
 
205
226
  // Update columns when preset changes
206
227
  $effect(() => {
207
- if (isInitialized && table && columns) {
228
+ if (isInitialized && table && columnPreset !== prevColumnPreset) {
229
+ prevColumnPreset = columnPreset;
208
230
  table.setColumns(columns);
209
- table.redraw(true);
210
231
  }
211
232
  });
212
233
 
@@ -256,12 +277,63 @@
256
277
  }
257
278
 
258
279
  export function clearFilters(): void {
259
- table?.clearFilter();
280
+ if (!table) return;
281
+ // Clear programmatic filters
282
+ table.clearFilter();
283
+ // Clear header filter inputs
284
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
285
+ (table as any).clearHeaderFilter();
260
286
  }
261
287
 
262
288
  export function redraw(): void {
263
289
  table?.redraw(true);
264
290
  }
291
+
292
+ export function collapseAll(): void {
293
+ if (!table) return;
294
+ // Use setGroupStartOpen to collapse all groups, then refresh data to apply
295
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
296
+ (table as any).setGroupStartOpen(false);
297
+ table.setData(table.getData());
298
+ }
299
+
300
+ export function expandAll(): void {
301
+ if (!table) return;
302
+ // Use setGroupStartOpen to expand all groups, then refresh data to apply
303
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
304
+ (table as any).setGroupStartOpen(true);
305
+ table.setData(table.getData());
306
+ }
307
+
308
+ export function toggleHeaderFilters(visible: boolean): void {
309
+ if (!table) return;
310
+ const headerFiltersElement = tableContainer.querySelector('.tabulator-header-filter');
311
+ if (headerFiltersElement) {
312
+ // Toggle all header filter rows
313
+ const filterRows = tableContainer.querySelectorAll('.tabulator-col .tabulator-header-filter');
314
+ filterRows.forEach(el => {
315
+ (el as HTMLElement).style.display = visible ? '' : 'none';
316
+ });
317
+ table.redraw();
318
+ }
319
+ }
320
+
321
+ export function showColumn(field: string): void {
322
+ table?.showColumn(field);
323
+ }
324
+
325
+ export function hideColumn(field: string): void {
326
+ table?.hideColumn(field);
327
+ }
328
+
329
+ export function getVisibleColumns(): string[] {
330
+ if (!table) return [];
331
+ const columns = table.getColumns();
332
+ return columns
333
+ .filter(col => col.isVisible())
334
+ .map(col => col.getField())
335
+ .filter((field): field is string => !!field);
336
+ }
265
337
  </script>
266
338
 
267
339
  <div class="cell-table-container">
@@ -22,6 +22,12 @@ declare const CellTable: import("svelte").Component<Props, {
22
22
  setFilter: (field: string, type: string, value: unknown) => void;
23
23
  clearFilters: () => void;
24
24
  redraw: () => void;
25
+ collapseAll: () => void;
26
+ expandAll: () => void;
27
+ toggleHeaderFilters: (visible: boolean) => void;
28
+ showColumn: (field: string) => void;
29
+ hideColumn: (field: string) => void;
30
+ getVisibleColumns: () => string[];
25
31
  }, "">;
26
32
  type CellTable = ReturnType<typeof CellTable>;
27
33
  export default CellTable;
@@ -1,145 +1,101 @@
1
1
  <script lang="ts">
2
2
  import CellTablePanel from './CellTablePanel.svelte';
3
- import type { CellTableGroupField, ColumnPreset, RowClickEvent, RowSelectionEvent, CellData } from './types';
4
- import { demoCells } from './demo-data';
3
+ import type { CellTableGroupField, ColumnPreset, RowSelectionEvent } from './types';
4
+ import { generateCellsFromPreset, getGeneratorInfo, type GeneratorPreset } from '../../shared/demo';
5
5
 
6
+ let datasetSize: GeneratorPreset = $state('small');
7
+ let demoCells = $state(generateCellsFromPreset('small'));
8
+ let dataInfo = $derived(getGeneratorInfo(demoCells));
9
+
6
10
  let groupBy: CellTableGroupField = $state('none');
7
11
  let columnPreset: ColumnPreset = $state('default');
8
- let lastClickedCell: CellData | null = $state(null);
9
- let selectedIds: string[] = $state([]);
10
-
11
- function handleRowClick(event: RowClickEvent) {
12
- lastClickedCell = event.row;
13
- console.log('Row clicked:', event.row);
14
- }
15
12
 
16
13
  function handleSelectionChange(event: RowSelectionEvent) {
17
- selectedIds = event.ids;
18
14
  console.log('Selection changed:', event.ids);
19
15
  }
16
+
17
+ function regenerateData() {
18
+ demoCells = generateCellsFromPreset(datasetSize);
19
+ }
20
20
  </script>
21
21
 
22
- <div class="cell-table-demo container-fluid py-3">
23
- <div class="row mb-3">
24
- <div class="col">
25
- <h2 class="d-flex align-items-center gap-2">
26
- <i class="bi bi-table text-primary"></i>
27
- CellTable Component Demo
28
- </h2>
29
- <p class="text-muted">
30
- A Bootstrap-themed Tabulator wrapper for displaying cell data with grouping, filtering, and export capabilities.
31
- </p>
22
+ <div class="cell-table-page vh-100 d-flex flex-column">
23
+ <!-- Simple header for demo controls only -->
24
+ <div class="demo-controls bg-light border-bottom px-3 py-2 d-flex align-items-center gap-3 flex-wrap">
25
+ <div class="input-group input-group-sm" style="width: auto;">
26
+ <span class="input-group-text">Dataset</span>
27
+ <select class="form-select" bind:value={datasetSize} onchange={regenerateData}>
28
+ <option value="small">Small (~300)</option>
29
+ <option value="medium">Medium (~3K)</option>
30
+ <option value="large">Large (~15K)</option>
31
+ <option value="xlarge">XLarge (~60K)</option>
32
+ </select>
32
33
  </div>
34
+ <span class="badge bg-info">
35
+ {dataInfo.totalCells.toLocaleString()} cells | {dataInfo.totalSites} sites
36
+ </span>
37
+ <span class="badge bg-secondary d-none d-md-inline">
38
+ {Object.entries(dataInfo.techBreakdown).map(([k, v]) => `${k}: ${v}`).join(' | ')}
39
+ </span>
33
40
  </div>
34
41
 
35
- <div class="row" style="height: calc(100vh - 200px);">
36
- <div class="col-12 col-lg-9 h-100">
37
- <CellTablePanel
38
- cells={demoCells}
39
- bind:groupBy
40
- bind:columnPreset
41
- selectable={true}
42
- multiSelect={true}
43
- title="Cell Data Table"
44
- showToolbar={true}
45
- showExport={true}
46
- headerFilters={true}
47
- onrowclick={handleRowClick}
48
- onselectionchange={handleSelectionChange}
49
- >
50
- {#snippet footer({ selectedRows, selectedCount })}
51
- <div class="d-flex align-items-center justify-content-between">
52
- <span class="text-muted small">
53
- {#if selectedCount > 0}
54
- {selectedCount} cell(s) selected
55
- {:else}
56
- Click rows to select
57
- {/if}
58
- </span>
59
- <div class="btn-group">
60
- <button
61
- type="button"
62
- class="btn btn-sm btn-outline-primary"
63
- disabled={selectedCount === 0}
64
- onclick={() => console.log('Process:', selectedRows.map(r => r.id))}
65
- >
66
- <i class="bi bi-gear"></i> Process Selected
67
- </button>
68
- <button
69
- type="button"
70
- class="btn btn-sm btn-outline-secondary"
71
- disabled={selectedCount === 0}
72
- onclick={() => console.log('View:', selectedRows.map(r => r.id))}
73
- >
74
- <i class="bi bi-eye"></i> View Details
75
- </button>
76
- </div>
42
+ <!-- CellTablePanel with integrated sidebar -->
43
+ <div class="flex-grow-1 overflow-hidden">
44
+ <CellTablePanel
45
+ cells={demoCells}
46
+ bind:groupBy
47
+ bind:columnPreset
48
+ selectable={true}
49
+ multiSelect={true}
50
+ showToolbar={true}
51
+ showExport={true}
52
+ headerFilters={true}
53
+ showDetailsSidebar={true}
54
+ sidebarWidth={320}
55
+ title="Cell Data"
56
+ onselectionchange={handleSelectionChange}
57
+ >
58
+ {#snippet footer({ selectedRows, selectedCount })}
59
+ <div class="d-flex align-items-center justify-content-between">
60
+ <span class="text-muted small">
61
+ {#if selectedCount > 0}
62
+ {selectedCount} cell(s) selected
63
+ {:else}
64
+ Click a row to view details
65
+ {/if}
66
+ </span>
67
+ <div class="btn-group">
68
+ <button
69
+ type="button"
70
+ class="btn btn-sm btn-outline-primary"
71
+ disabled={selectedCount === 0}
72
+ onclick={() => console.log('Process:', selectedRows.map(r => r.id))}
73
+ >
74
+ <i class="bi bi-gear"></i>
75
+ <span class="d-none d-sm-inline ms-1">Process</span>
76
+ </button>
77
+ <button
78
+ type="button"
79
+ class="btn btn-sm btn-outline-secondary"
80
+ disabled={selectedCount === 0}
81
+ onclick={() => console.log('Export:', selectedRows.map(r => r.id))}
82
+ >
83
+ <i class="bi bi-download"></i>
84
+ <span class="d-none d-sm-inline ms-1">Export</span>
85
+ </button>
77
86
  </div>
78
- {/snippet}
79
- </CellTablePanel>
80
- </div>
81
-
82
- <div class="col-12 col-lg-3 h-100 overflow-auto">
83
- <div class="card h-100">
84
- <div class="card-header">
85
- <h6 class="mb-0">
86
- <i class="bi bi-info-circle"></i> Cell Details
87
- </h6>
88
87
  </div>
89
- <div class="card-body">
90
- {#if lastClickedCell}
91
- <dl class="row mb-0 small">
92
- <dt class="col-5">ID</dt>
93
- <dd class="col-7"><code>{lastClickedCell.id}</code></dd>
94
-
95
- <dt class="col-5">Cell Name</dt>
96
- <dd class="col-7">{lastClickedCell.cellName}</dd>
97
-
98
- <dt class="col-5">Site</dt>
99
- <dd class="col-7">{lastClickedCell.siteId}</dd>
100
-
101
- <dt class="col-5">Technology</dt>
102
- <dd class="col-7">
103
- <span class="badge bg-secondary">{lastClickedCell.tech}</span>
104
- </dd>
105
-
106
- <dt class="col-5">Band</dt>
107
- <dd class="col-7">
108
- <span class="badge bg-info">{lastClickedCell.fband}</span>
109
- </dd>
110
-
111
- <dt class="col-5">Status</dt>
112
- <dd class="col-7">
113
- <span class="badge bg-success">{lastClickedCell.status.replace(/_/g, ' ')}</span>
114
- </dd>
115
-
116
- <dt class="col-5">Azimuth</dt>
117
- <dd class="col-7">{lastClickedCell.azimuth}°</dd>
118
-
119
- <dt class="col-5">Height</dt>
120
- <dd class="col-7">{lastClickedCell.height}m</dd>
121
-
122
- <dt class="col-5">Antenna</dt>
123
- <dd class="col-7 text-truncate" title={lastClickedCell.antenna}>
124
- {lastClickedCell.antenna}
125
- </dd>
126
-
127
- <dt class="col-5">Planner</dt>
128
- <dd class="col-7">{lastClickedCell.planner}</dd>
129
-
130
- {#if lastClickedCell.comment}
131
- <dt class="col-5">Comment</dt>
132
- <dd class="col-7 text-muted">{lastClickedCell.comment}</dd>
133
- {/if}
134
- </dl>
135
- {:else}
136
- <p class="text-muted text-center">
137
- <i class="bi bi-hand-index"></i><br>
138
- Click a row to see details
139
- </p>
140
- {/if}
141
- </div>
142
- </div>
143
- </div>
88
+ {/snippet}
89
+ </CellTablePanel>
144
90
  </div>
145
91
  </div>
92
+
93
+ <style>
94
+ .cell-table-page {
95
+ background: var(--bs-body-bg, #f8f9fa);
96
+ }
97
+
98
+ .demo-controls {
99
+ flex-shrink: 0;
100
+ }
101
+ </style>