@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.
@@ -0,0 +1,214 @@
1
+ <script lang="ts">
2
+ /**
3
+ * ColumnPicker - Column visibility customization dropdown
4
+ *
5
+ * Shows a dropdown panel with checkboxes to toggle individual column visibility.
6
+ * Works alongside presets - user can customize which columns are visible.
7
+ */
8
+
9
+ interface ColumnInfo {
10
+ field: string;
11
+ title: string;
12
+ group: string;
13
+ }
14
+
15
+ interface Props {
16
+ /** All available columns with their metadata */
17
+ columns: ColumnInfo[];
18
+ /** Currently visible column fields */
19
+ visibleColumns: string[];
20
+ /** Callback when column visibility changes */
21
+ onchange?: (field: string, visible: boolean) => void;
22
+ /** Callback to reset to preset defaults */
23
+ onreset?: () => void;
24
+ /** Current preset name for display */
25
+ presetName?: string;
26
+ }
27
+
28
+ let {
29
+ columns = [],
30
+ visibleColumns = [],
31
+ onchange,
32
+ onreset,
33
+ presetName = 'Default'
34
+ }: Props = $props();
35
+
36
+ let isOpen = $state(false);
37
+ let dropdownRef: HTMLDivElement;
38
+
39
+ // Group columns by category
40
+ const groupedColumns = $derived.by(() => {
41
+ const groups: Record<string, ColumnInfo[]> = {};
42
+ for (const col of columns) {
43
+ if (!groups[col.group]) {
44
+ groups[col.group] = [];
45
+ }
46
+ groups[col.group].push(col);
47
+ }
48
+ return groups;
49
+ });
50
+
51
+ const groupOrder = ['Core', 'Physical', 'Network', 'Atoll', 'Position', 'Planning'];
52
+
53
+ function toggleDropdown() {
54
+ isOpen = !isOpen;
55
+ }
56
+
57
+ function handleCheckboxChange(field: string, event: Event) {
58
+ const checked = (event.target as HTMLInputElement).checked;
59
+ onchange?.(field, checked);
60
+ }
61
+
62
+ function handleReset() {
63
+ onreset?.();
64
+ }
65
+
66
+ function handleSelectAll() {
67
+ columns.forEach(col => onchange?.(col.field, true));
68
+ }
69
+
70
+ function handleDeselectAll() {
71
+ // Keep at least core columns visible
72
+ columns.forEach(col => {
73
+ const isCore = col.group === 'Core';
74
+ onchange?.(col.field, isCore);
75
+ });
76
+ }
77
+
78
+ // Close dropdown when clicking outside
79
+ function handleClickOutside(event: MouseEvent) {
80
+ if (dropdownRef && !dropdownRef.contains(event.target as Node)) {
81
+ isOpen = false;
82
+ }
83
+ }
84
+
85
+ $effect(() => {
86
+ if (isOpen) {
87
+ document.addEventListener('click', handleClickOutside);
88
+ return () => document.removeEventListener('click', handleClickOutside);
89
+ }
90
+ });
91
+ </script>
92
+
93
+ <div class="column-picker position-relative" bind:this={dropdownRef}>
94
+ <button
95
+ type="button"
96
+ class="btn btn-sm btn-outline-secondary"
97
+ onclick={toggleDropdown}
98
+ title="Customize columns"
99
+ aria-label="Customize columns"
100
+ aria-expanded={isOpen}
101
+ >
102
+ <i class="bi bi-pencil"></i>
103
+ </button>
104
+
105
+ {#if isOpen}
106
+ <div class="column-picker-dropdown position-absolute end-0 mt-1 bg-white border rounded shadow-sm"
107
+ style="z-index: 1050; width: 280px; max-height: 400px;">
108
+
109
+ <!-- Header -->
110
+ <div class="d-flex align-items-center justify-content-between p-2 border-bottom bg-light">
111
+ <span class="small fw-medium">
112
+ <i class="bi bi-layout-three-columns text-primary"></i>
113
+ Customize "{presetName}"
114
+ </span>
115
+ <button
116
+ type="button"
117
+ class="btn-close btn-close-sm"
118
+ onclick={() => isOpen = false}
119
+ aria-label="Close"
120
+ ></button>
121
+ </div>
122
+
123
+ <!-- Quick actions -->
124
+ <div class="d-flex gap-2 p-2 border-bottom">
125
+ <button
126
+ type="button"
127
+ class="btn btn-sm btn-outline-secondary flex-grow-1"
128
+ onclick={handleSelectAll}
129
+ >
130
+ Select All
131
+ </button>
132
+ <button
133
+ type="button"
134
+ class="btn btn-sm btn-outline-secondary flex-grow-1"
135
+ onclick={handleDeselectAll}
136
+ >
137
+ Core Only
138
+ </button>
139
+ </div>
140
+
141
+ <!-- Scrollable column list -->
142
+ <div class="column-list overflow-auto" style="max-height: 280px;">
143
+ {#each groupOrder as groupName}
144
+ {#if groupedColumns[groupName]}
145
+ <div class="column-group">
146
+ <div class="px-2 py-1 bg-body-tertiary small fw-medium text-muted border-bottom">
147
+ {groupName}
148
+ </div>
149
+ {#each groupedColumns[groupName] as col}
150
+ <label class="d-flex align-items-center px-2 py-1 column-item">
151
+ <input
152
+ type="checkbox"
153
+ class="form-check-input me-2 mt-0"
154
+ checked={visibleColumns.includes(col.field)}
155
+ onchange={(e) => handleCheckboxChange(col.field, e)}
156
+ />
157
+ <span class="small">{col.title}</span>
158
+ <code class="ms-auto small text-muted">{col.field}</code>
159
+ </label>
160
+ {/each}
161
+ </div>
162
+ {/if}
163
+ {/each}
164
+ </div>
165
+
166
+ <!-- Footer with reset -->
167
+ <div class="p-2 border-top bg-light">
168
+ <button
169
+ type="button"
170
+ class="btn btn-sm btn-outline-primary w-100"
171
+ onclick={handleReset}
172
+ >
173
+ <i class="bi bi-arrow-counterclockwise"></i>
174
+ Reset to "{presetName}" Default
175
+ </button>
176
+ </div>
177
+ </div>
178
+ {/if}
179
+ </div>
180
+
181
+ <style>
182
+ .column-picker-dropdown {
183
+ animation: fadeIn 0.15s ease-out;
184
+ }
185
+
186
+ @keyframes fadeIn {
187
+ from {
188
+ opacity: 0;
189
+ transform: translateY(-4px);
190
+ }
191
+ to {
192
+ opacity: 1;
193
+ transform: translateY(0);
194
+ }
195
+ }
196
+
197
+ .column-item {
198
+ cursor: pointer;
199
+ transition: background-color 0.1s;
200
+ }
201
+
202
+ .column-item:hover {
203
+ background-color: var(--bs-tertiary-bg, #f8f9fa);
204
+ }
205
+
206
+ .form-check-input {
207
+ cursor: pointer;
208
+ }
209
+
210
+ .btn-close-sm {
211
+ font-size: 0.65rem;
212
+ padding: 0.25rem;
213
+ }
214
+ </style>
@@ -0,0 +1,26 @@
1
+ /**
2
+ * ColumnPicker - Column visibility customization dropdown
3
+ *
4
+ * Shows a dropdown panel with checkboxes to toggle individual column visibility.
5
+ * Works alongside presets - user can customize which columns are visible.
6
+ */
7
+ interface ColumnInfo {
8
+ field: string;
9
+ title: string;
10
+ group: string;
11
+ }
12
+ interface Props {
13
+ /** All available columns with their metadata */
14
+ columns: ColumnInfo[];
15
+ /** Currently visible column fields */
16
+ visibleColumns: string[];
17
+ /** Callback when column visibility changes */
18
+ onchange?: (field: string, visible: boolean) => void;
19
+ /** Callback to reset to preset defaults */
20
+ onreset?: () => void;
21
+ /** Current preset name for display */
22
+ presetName?: string;
23
+ }
24
+ declare const ColumnPicker: import("svelte").Component<Props, {}, "">;
25
+ type ColumnPicker = ReturnType<typeof ColumnPicker>;
26
+ export default ColumnPicker;
@@ -61,3 +61,19 @@ export declare function getColumnsForPreset(preset: ColumnPreset, techColors?: T
61
61
  * Get group header formatter for a specific field
62
62
  */
63
63
  export declare function getGroupHeaderFormatter(groupField: string): (value: unknown, count: number) => string;
64
+ /**
65
+ * Column metadata for the column picker
66
+ */
67
+ export interface ColumnMeta {
68
+ field: string;
69
+ title: string;
70
+ group: string;
71
+ }
72
+ /**
73
+ * Get column metadata for the column picker UI
74
+ */
75
+ export declare function getColumnMetadata(): ColumnMeta[];
76
+ /**
77
+ * Get default visible columns for a preset
78
+ */
79
+ export declare function getPresetVisibleFields(preset: ColumnPreset): string[];
@@ -463,3 +463,75 @@ export function getGroupHeaderFormatter(groupField) {
463
463
  <span class="text-muted">(${count} cell${count !== 1 ? 's' : ''})</span>`;
464
464
  };
465
465
  }
466
+ /**
467
+ * Get column metadata for the column picker UI
468
+ */
469
+ export function getColumnMetadata() {
470
+ return [
471
+ // Core
472
+ { field: 'id', title: 'ID', group: 'Core' },
473
+ { field: 'cellName', title: 'Cell Name', group: 'Core' },
474
+ { field: 'siteId', title: 'Site ID', group: 'Core' },
475
+ { field: 'tech', title: 'Technology', group: 'Core' },
476
+ { field: 'fband', title: 'Band', group: 'Core' },
477
+ { field: 'frq', title: 'Frequency', group: 'Core' },
478
+ { field: 'status', title: 'Status', group: 'Core' },
479
+ { field: 'type', title: 'Type', group: 'Core' },
480
+ { field: 'onAirDate', title: 'On Air Date', group: 'Core' },
481
+ // Physical
482
+ { field: 'antenna', title: 'Antenna', group: 'Physical' },
483
+ { field: 'azimuth', title: 'Azimuth', group: 'Physical' },
484
+ { field: 'height', title: 'Height', group: 'Physical' },
485
+ { field: 'electricalTilt', title: 'E-Tilt', group: 'Physical' },
486
+ { field: 'beamwidth', title: 'Beamwidth', group: 'Physical' },
487
+ // Network
488
+ { field: 'dlEarfn', title: 'DL EARFCN', group: 'Network' },
489
+ { field: 'bcch', title: 'BCCH', group: 'Network' },
490
+ { field: 'pci', title: 'PCI', group: 'Network' },
491
+ { field: 'rru', title: 'RRU', group: 'Network' },
492
+ { field: 'cellID', title: 'Cell ID', group: 'Network' },
493
+ { field: 'cellId2G', title: 'Cell ID 2G', group: 'Network' },
494
+ { field: 'txId', title: 'TX ID', group: 'Network' },
495
+ { field: 'ctrlid', title: 'Ctrl ID', group: 'Network' },
496
+ { field: 'nwtET', title: 'NWT ET', group: 'Network' },
497
+ { field: 'nwtPW', title: 'NWT PW', group: 'Network' },
498
+ { field: 'nwtRS', title: 'NWT RS', group: 'Network' },
499
+ { field: 'nwtBW', title: 'NWT BW', group: 'Network' },
500
+ // Atoll
501
+ { field: 'atollET', title: 'Atoll ET', group: 'Atoll' },
502
+ { field: 'atollPW', title: 'Atoll PW', group: 'Atoll' },
503
+ { field: 'atollRS', title: 'Atoll RS', group: 'Atoll' },
504
+ { field: 'atollBW', title: 'Atoll BW', group: 'Atoll' },
505
+ // Position
506
+ { field: 'latitude', title: 'Latitude', group: 'Position' },
507
+ { field: 'longitude', title: 'Longitude', group: 'Position' },
508
+ { field: 'siteLatitude', title: 'Site Latitude', group: 'Position' },
509
+ { field: 'siteLongitude', title: 'Site Longitude', group: 'Position' },
510
+ { field: 'dx', title: 'DX', group: 'Position' },
511
+ { field: 'dy', title: 'DY', group: 'Position' },
512
+ // Planning
513
+ { field: 'planner', title: 'Planner', group: 'Planning' },
514
+ { field: 'comment', title: 'Comment', group: 'Planning' },
515
+ { field: 'customSubgroup', title: 'Subgroup', group: 'Planning' },
516
+ ];
517
+ }
518
+ /**
519
+ * Get default visible columns for a preset
520
+ */
521
+ export function getPresetVisibleFields(preset) {
522
+ switch (preset) {
523
+ case 'compact':
524
+ return ['cellName', 'siteId', 'tech', 'fband', 'status'];
525
+ case 'full':
526
+ return getColumnMetadata().map(c => c.field);
527
+ case 'physical':
528
+ return [...COLUMN_GROUPS.core, ...COLUMN_GROUPS.physical];
529
+ case 'network':
530
+ return [...COLUMN_GROUPS.core, ...COLUMN_GROUPS.network];
531
+ case 'planning':
532
+ return [...COLUMN_GROUPS.core, ...COLUMN_GROUPS.planning];
533
+ case 'default':
534
+ default:
535
+ return ['id', 'cellName', 'siteId', 'tech', 'fband', 'frq', 'status', 'azimuth', 'height', 'antenna'];
536
+ }
537
+ }
@@ -7,6 +7,7 @@ export { default as CellTable } from './CellTable.svelte';
7
7
  export { default as CellTableToolbar } from './CellTableToolbar.svelte';
8
8
  export { default as CellTablePanel } from './CellTablePanel.svelte';
9
9
  export { default as CellTableDemo } from './CellTableDemo.svelte';
10
- export { demoCells } from './demo-data';
10
+ export { default as ColumnPicker } from './ColumnPicker.svelte';
11
+ export { generateCells, generateCellsFromPreset, getGeneratorInfo, GENERATOR_PRESETS, type CellGeneratorConfig, type GeneratorPreset } from '../../shared/demo';
11
12
  export type { CellData, CellTableGroupField, ColumnPreset, ColumnVisibility, TechColorMap, StatusColorMap, CellTableProps, RowSelectionEvent, RowClickEvent, RowDblClickEvent, DataChangeEvent, CellTableColumn, ColumnGroups } from './types';
12
- export { DEFAULT_TECH_COLORS, DEFAULT_STATUS_COLORS, FBAND_COLORS, COLUMN_GROUPS, getAllColumns, getColumnsForPreset, getGroupHeaderFormatter, createTechFormatter, createStatusFormatter, createFbandFormatter, numberFormatter, coordinateFormatter, azimuthFormatter, heightFormatter } from './column-config';
13
+ export { DEFAULT_TECH_COLORS, DEFAULT_STATUS_COLORS, FBAND_COLORS, COLUMN_GROUPS, getAllColumns, getColumnsForPreset, getGroupHeaderFormatter, getColumnMetadata, getPresetVisibleFields, createTechFormatter, createStatusFormatter, createFbandFormatter, numberFormatter, coordinateFormatter, azimuthFormatter, heightFormatter, type ColumnMeta } from './column-config';
@@ -8,7 +8,10 @@ export { default as CellTable } from './CellTable.svelte';
8
8
  export { default as CellTableToolbar } from './CellTableToolbar.svelte';
9
9
  export { default as CellTablePanel } from './CellTablePanel.svelte';
10
10
  export { default as CellTableDemo } from './CellTableDemo.svelte';
11
- // Demo data
12
- export { demoCells } from './demo-data';
11
+ export { default as ColumnPicker } from './ColumnPicker.svelte';
12
+ // Re-export shared demo data utilities for convenience
13
+ // Note: Cell type is NOT re-exported here to avoid conflicts with map-v2
14
+ // Import Cell from '$lib/shared/demo' or '@smartnet360/svelte-components' directly
15
+ export { generateCells, generateCellsFromPreset, getGeneratorInfo, GENERATOR_PRESETS } from '../../shared/demo';
13
16
  // Configuration utilities
14
- export { DEFAULT_TECH_COLORS, DEFAULT_STATUS_COLORS, FBAND_COLORS, COLUMN_GROUPS, getAllColumns, getColumnsForPreset, getGroupHeaderFormatter, createTechFormatter, createStatusFormatter, createFbandFormatter, numberFormatter, coordinateFormatter, azimuthFormatter, heightFormatter } from './column-config';
17
+ export { DEFAULT_TECH_COLORS, DEFAULT_STATUS_COLORS, FBAND_COLORS, COLUMN_GROUPS, getAllColumns, getColumnsForPreset, getGroupHeaderFormatter, getColumnMetadata, getPresetVisibleFields, createTechFormatter, createStatusFormatter, createFbandFormatter, numberFormatter, coordinateFormatter, azimuthFormatter, heightFormatter } from './column-config';
@@ -4,56 +4,15 @@
4
4
  * Types for the Tabulator-based cell data table component
5
5
  */
6
6
  import type { ColumnDefinition, Options } from 'tabulator-tables';
7
+ import type { Cell, CellGroupingField } from '../../shared/demo';
7
8
  /**
8
- * Cell data model - represents a radio cell/sector
9
- * Imported from map-v3 cells feature for consistency
9
+ * Cell data model - re-exported from shared for convenience
10
10
  */
11
- export interface CellData {
12
- id: string;
13
- txId: string;
14
- cellID: string;
15
- cellID2G: string;
16
- cellName: string;
17
- siteId: string;
18
- tech: string;
19
- fband: string;
20
- frq: string;
21
- type: string;
22
- status: string;
23
- onAirDate: string;
24
- bcch: number;
25
- ctrlid: string;
26
- dlEarfn: number;
27
- antenna: string;
28
- azimuth: number;
29
- height: number;
30
- electricalTilt: string;
31
- beamwidth: number;
32
- latitude: number;
33
- longitude: number;
34
- dx: number;
35
- dy: number;
36
- siteLatitude: number;
37
- siteLongitude: number;
38
- comment: string;
39
- planner: string;
40
- atollETP: number;
41
- atollPW: number;
42
- atollRS: number;
43
- atollBW: number;
44
- cellId3: string;
45
- nwtP1: number;
46
- nwtP2: number;
47
- pci1: number;
48
- nwtRS: number;
49
- nwtBW: number;
50
- other?: Record<string, unknown>;
51
- customSubgroup: string;
52
- }
11
+ export type CellData = Cell;
53
12
  /**
54
- * Available grouping fields for the table
13
+ * Available grouping fields for the table - extends shared type with 'none' option
55
14
  */
56
- export type CellTableGroupField = 'tech' | 'fband' | 'frq' | 'status' | 'siteId' | 'none';
15
+ export type CellTableGroupField = CellGroupingField | 'none';
57
16
  /**
58
17
  * Column preset configurations
59
18
  */
@@ -116,8 +116,8 @@ export class SiteStore {
116
116
  availableTilts: ['0']
117
117
  };
118
118
  }
119
- // Determine TX power (priority: atollPW > nwtP1 > default)
120
- const txPower = cell.atollPW || cell.nwtP1 || 43; // Default to 43 dBm (20W)
119
+ // Determine TX power (priority: atollPW > nwtPW > default)
120
+ const txPower = cell.atollPW || cell.nwtPW || 43; // Default to 43 dBm (20W)
121
121
  // Determine frequency from band
122
122
  const frequency = antennaPattern.frequency || this.parseFrequency(cell.fband);
123
123
  // Get mechanical tilt (might need to parse from string)
@@ -1,14 +1,18 @@
1
1
  /**
2
- * Demo Cell Data
2
+ * Demo Cell Data - Map V3
3
3
  *
4
- * 100 sites across San Francisco Bay Area
5
- * Each site has 3 sectors (azimuths: 0°, 120°, 240°)
6
- * Each sector has 12 cells (all tech-band combinations)
7
- * Total: 100 sites × 3 sectors × 12 tech-bands = 3,600 cells
8
- */
9
- import type { Cell } from '../features/cells/types';
10
- /**
11
- * Generate demo cells with varied density patterns in circular distribution
12
- * Creates density zones radiating from center with random placement
4
+ * Uses the shared cell generator to create demo data.
5
+ * Default: 2000 sites, ~60k cells across Budapest area.
6
+ *
7
+ * Structure:
8
+ * - CellName format: 7-digit numeric (e.g., "1000141")
9
+ * - Format: SSSS (site) + S (sector 1-3) + BB (band 41-51)
10
+ * - Site ID range: 1000-2999
11
+ * - Example Site 1000:
12
+ * - Sector 1 (0°): 1000141-1000151 (11 bands)
13
+ * - Sector 2 (120°): 1000241-1000251 (11 bands)
14
+ * - Sector 3 (240°): 1000341-1000351 (11 bands)
13
15
  */
16
+ import { type Cell } from '../../shared/demo';
14
17
  export declare const demoCells: Cell[];
18
+ export type { Cell } from '../../shared/demo';