@smartnet360/svelte-components 0.0.106 → 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.
@@ -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;
@@ -2,6 +2,7 @@
2
2
  import type { Snippet } from 'svelte';
3
3
  import CellTable from './CellTable.svelte';
4
4
  import CellTableToolbar from './CellTableToolbar.svelte';
5
+ import { getColumnMetadata, getPresetVisibleFields } from './column-config';
5
6
  import type {
6
7
  CellData,
7
8
  CellTableGroupField,
@@ -89,10 +90,22 @@
89
90
  let filteredCount = $state(cells.length);
90
91
  let sidebarOpen = $state(false);
91
92
  let clickedCell: CellData | null = $state(null);
93
+ let tableRefSet = false;
94
+ let filtersVisible = $state(true);
92
95
 
93
- // Expose table methods via tableRef
96
+ // Column visibility management
97
+ const columnMeta = getColumnMetadata();
98
+ let visibleColumns = $state<string[]>(getPresetVisibleFields(columnPreset));
99
+
100
+ // Update visible columns when preset changes
94
101
  $effect(() => {
95
- if (cellTable) {
102
+ visibleColumns = getPresetVisibleFields(columnPreset);
103
+ });
104
+
105
+ // Expose table methods via tableRef - only set once
106
+ $effect(() => {
107
+ if (cellTable && !tableRefSet) {
108
+ tableRefSet = true;
96
109
  tableRef = {
97
110
  redraw: () => cellTable?.redraw()
98
111
  };
@@ -138,6 +151,44 @@
138
151
  cellTable?.clearFilters();
139
152
  }
140
153
 
154
+ function handleCollapseAll() {
155
+ cellTable?.collapseAll();
156
+ }
157
+
158
+ function handleExpandAll() {
159
+ cellTable?.expandAll();
160
+ }
161
+
162
+ function handleToggleFilters() {
163
+ filtersVisible = !filtersVisible;
164
+ cellTable?.toggleHeaderFilters(filtersVisible);
165
+ }
166
+
167
+ function handleColumnVisibilityChange(field: string, visible: boolean) {
168
+ if (visible) {
169
+ if (!visibleColumns.includes(field)) {
170
+ visibleColumns = [...visibleColumns, field];
171
+ }
172
+ cellTable?.showColumn(field);
173
+ } else {
174
+ visibleColumns = visibleColumns.filter(f => f !== field);
175
+ cellTable?.hideColumn(field);
176
+ }
177
+ }
178
+
179
+ function handleResetColumns() {
180
+ const defaultFields = getPresetVisibleFields(columnPreset);
181
+ visibleColumns = defaultFields;
182
+ // Show/hide columns to match preset
183
+ columnMeta.forEach(col => {
184
+ if (defaultFields.includes(col.field)) {
185
+ cellTable?.showColumn(col.field);
186
+ } else {
187
+ cellTable?.hideColumn(col.field);
188
+ }
189
+ });
190
+ }
191
+
141
192
  function toggleSidebar() {
142
193
  sidebarOpen = !sidebarOpen;
143
194
  setTimeout(() => cellTable?.redraw(), 320);
@@ -212,6 +263,14 @@
212
263
  onexportcsv={handleExportCSV}
213
264
  onexportjson={handleExportJSON}
214
265
  onclearfilters={handleClearFilters}
266
+ oncollapseall={handleCollapseAll}
267
+ onexpandall={handleExpandAll}
268
+ {filtersVisible}
269
+ ontogglefilters={handleToggleFilters}
270
+ {columnMeta}
271
+ {visibleColumns}
272
+ oncolumnvisibilitychange={handleColumnVisibilityChange}
273
+ onresetcolumns={handleResetColumns}
215
274
  />
216
275
  {/if}
217
276
 
@@ -330,6 +389,30 @@
330
389
  <dt class="col-5 text-muted">Comment</dt>
331
390
  <dd class="col-7 fst-italic">{clickedCell.comment}</dd>
332
391
  {/if}
392
+
393
+ <!-- Dynamic Other Properties -->
394
+ {#if clickedCell.other && Object.keys(clickedCell.other).length > 0}
395
+ <dt class="col-12 text-muted mt-2 mb-1 border-top pt-2">Other</dt>
396
+
397
+ {#each Object.entries(clickedCell.other) as [key, value]}
398
+ <dt class="col-5 text-muted text-capitalize">{key.replace(/_/g, ' ')}</dt>
399
+ <dd class="col-7">
400
+ {#if value === null || value === undefined}
401
+ <span class="text-muted fst-italic">—</span>
402
+ {:else if typeof value === 'boolean'}
403
+ <span class="badge" class:bg-success={value} class:bg-secondary={!value}>
404
+ {value ? 'Yes' : 'No'}
405
+ </span>
406
+ {:else if typeof value === 'number'}
407
+ <code>{value}</code>
408
+ {:else if typeof value === 'object'}
409
+ <code class="small text-break">{JSON.stringify(value)}</code>
410
+ {:else}
411
+ {String(value)}
412
+ {/if}
413
+ </dd>
414
+ {/each}
415
+ {/if}
333
416
  </dl>
334
417
  {:else}
335
418
  <div class="text-center text-muted py-5">
@@ -1,5 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { CellTableGroupField, ColumnPreset } from './types';
3
+ import type { ColumnMeta } from './column-config';
4
+ import ColumnPicker from './ColumnPicker.svelte';
3
5
 
4
6
  interface Props {
5
7
  /** Current grouping field */
@@ -28,6 +30,22 @@
28
30
  onexportjson?: () => void;
29
31
  /** Clear filters event */
30
32
  onclearfilters?: () => void;
33
+ /** Collapse all groups event */
34
+ oncollapseall?: () => void;
35
+ /** Expand all groups event */
36
+ onexpandall?: () => void;
37
+ /** Whether header filters are visible */
38
+ filtersVisible?: boolean;
39
+ /** Toggle filters event */
40
+ ontogglefilters?: () => void;
41
+ /** All available columns metadata */
42
+ columnMeta?: ColumnMeta[];
43
+ /** Currently visible column fields */
44
+ visibleColumns?: string[];
45
+ /** Column visibility change event */
46
+ oncolumnvisibilitychange?: (field: string, visible: boolean) => void;
47
+ /** Reset columns to preset default */
48
+ onresetcolumns?: () => void;
31
49
  }
32
50
 
33
51
  let {
@@ -43,7 +61,15 @@
43
61
  onpresetchange,
44
62
  onexportcsv,
45
63
  onexportjson,
46
- onclearfilters
64
+ onclearfilters,
65
+ oncollapseall,
66
+ onexpandall,
67
+ filtersVisible = true,
68
+ ontogglefilters,
69
+ columnMeta = [],
70
+ visibleColumns = [],
71
+ oncolumnvisibilitychange,
72
+ onresetcolumns
47
73
  }: Props = $props();
48
74
 
49
75
  const groupOptions: { value: CellTableGroupField; label: string }[] = [
@@ -107,6 +133,28 @@
107
133
  <option value={option.value}>{option.label}</option>
108
134
  {/each}
109
135
  </select>
136
+ {#if groupBy !== 'none'}
137
+ <div class="btn-group ms-2">
138
+ <button
139
+ type="button"
140
+ class="btn btn-sm btn-outline-secondary"
141
+ onclick={oncollapseall}
142
+ title="Collapse all groups"
143
+ aria-label="Collapse all groups"
144
+ >
145
+ <i class="bi bi-chevron-contract"></i>
146
+ </button>
147
+ <button
148
+ type="button"
149
+ class="btn btn-sm btn-outline-secondary"
150
+ onclick={onexpandall}
151
+ title="Expand all groups"
152
+ aria-label="Expand all groups"
153
+ >
154
+ <i class="bi bi-chevron-expand"></i>
155
+ </button>
156
+ </div>
157
+ {/if}
110
158
  </div>
111
159
  {/if}
112
160
 
@@ -127,6 +175,15 @@
127
175
  <option value={option.value}>{option.label}</option>
128
176
  {/each}
129
177
  </select>
178
+ {#if columnMeta.length > 0}
179
+ <ColumnPicker
180
+ columns={columnMeta}
181
+ {visibleColumns}
182
+ presetName={presetOptions.find(p => p.value === columnPreset)?.label ?? 'Default'}
183
+ onchange={oncolumnvisibilitychange}
184
+ onreset={onresetcolumns}
185
+ />
186
+ {/if}
130
187
  </div>
131
188
  {/if}
132
189
 
@@ -134,6 +191,19 @@
134
191
 
135
192
  <!-- Actions -->
136
193
  <div class="toolbar-actions d-flex align-items-center gap-2">
194
+ {#if ontogglefilters}
195
+ <button
196
+ type="button"
197
+ class="btn btn-sm"
198
+ class:btn-outline-secondary={!filtersVisible}
199
+ class:btn-secondary={filtersVisible}
200
+ onclick={ontogglefilters}
201
+ title={filtersVisible ? 'Hide filters' : 'Show filters'}
202
+ aria-label={filtersVisible ? 'Hide filters' : 'Show filters'}
203
+ >
204
+ <i class="bi bi-funnel"></i>
205
+ </button>
206
+ {/if}
137
207
  {#if onclearfilters}
138
208
  <button
139
209
  type="button"
@@ -1,4 +1,5 @@
1
1
  import type { CellTableGroupField, ColumnPreset } from './types';
2
+ import type { ColumnMeta } from './column-config';
2
3
  interface Props {
3
4
  /** Current grouping field */
4
5
  groupBy?: CellTableGroupField;
@@ -26,6 +27,22 @@ interface Props {
26
27
  onexportjson?: () => void;
27
28
  /** Clear filters event */
28
29
  onclearfilters?: () => void;
30
+ /** Collapse all groups event */
31
+ oncollapseall?: () => void;
32
+ /** Expand all groups event */
33
+ onexpandall?: () => void;
34
+ /** Whether header filters are visible */
35
+ filtersVisible?: boolean;
36
+ /** Toggle filters event */
37
+ ontogglefilters?: () => void;
38
+ /** All available columns metadata */
39
+ columnMeta?: ColumnMeta[];
40
+ /** Currently visible column fields */
41
+ visibleColumns?: string[];
42
+ /** Column visibility change event */
43
+ oncolumnvisibilitychange?: (field: string, visible: boolean) => void;
44
+ /** Reset columns to preset default */
45
+ onresetcolumns?: () => void;
29
46
  }
30
47
  declare const CellTableToolbar: import("svelte").Component<Props, {}, "">;
31
48
  type CellTableToolbar = ReturnType<typeof CellTableToolbar>;
@@ -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 { default as ColumnPicker } from './ColumnPicker.svelte';
10
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,9 +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
+ export { default as ColumnPicker } from './ColumnPicker.svelte';
11
12
  // Re-export shared demo data utilities for convenience
12
13
  // Note: Cell type is NOT re-exported here to avoid conflicts with map-v2
13
14
  // Import Cell from '$lib/shared/demo' or '@smartnet360/svelte-components' directly
14
15
  export { generateCells, generateCellsFromPreset, getGeneratorInfo, GENERATOR_PRESETS } from '../../shared/demo';
15
16
  // Configuration utilities
16
- 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';
@@ -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)
@@ -176,7 +176,7 @@ export function generateCells(config) {
176
176
  id: cellName,
177
177
  txId: cellName,
178
178
  cellID: cellName,
179
- cellID2G: techBand.tech === '2G' ? cellName : '',
179
+ cellId2G: techBand.tech === '2G' ? cellName : '',
180
180
  cellName: cellName,
181
181
  siteId: siteId,
182
182
  tech: techBand.tech,
@@ -201,22 +201,26 @@ export function generateCells(config) {
201
201
  siteLongitude: siteLng,
202
202
  comment: `Demo ${techBand.tech} ${techBand.band} cell at azimuth ${sector.azimuth}°`,
203
203
  planner: 'Demo User',
204
- atollETP: 43.0,
204
+ atollET: 43.0,
205
205
  atollPW: 20.0,
206
206
  atollRS: 500.0 + (techBand.band === '700' ? 200 : 0),
207
207
  atollBW: parseFloat(techBand.band) / 100,
208
- cellId3: `${cellName}-3G`,
209
- nwtP1: 20,
210
- nwtP2: 40,
211
- pci1: cellCounter % 504,
208
+ rru: `RRU-${siteId}-${sector.sectorNum}`,
209
+ nwtET: 40.0,
210
+ nwtPW: 20,
211
+ pci: cellCounter % 504,
212
212
  nwtRS: 450.0,
213
213
  nwtBW: 10.0,
214
214
  other: {
215
+ city: ['Tehran', 'Shiraz', 'Isfahan', 'Mashhad', 'Tabriz'][siteNum % 5],
216
+ bcc: bandIndex % 8,
217
+ ncc: Math.floor(bandIndex / 8) % 8,
218
+ mall: random() < 0.1 ? 'Shopping Center' : undefined,
219
+ hsn: techBand.tech === '2G' ? Math.floor(random() * 64) : undefined,
215
220
  demoCell: true,
216
221
  siteNumber: actualSiteIndex,
217
222
  sector: sector.sectorNum,
218
223
  techBandKey: `${techBand.tech}_${techBand.band}`,
219
- radius: normalizedRadius,
220
224
  densityZone: zone.name
221
225
  },
222
226
  customSubgroup: `Sector-${sector.sectorNum}`
@@ -10,7 +10,7 @@ export interface Cell {
10
10
  id: string;
11
11
  txId: string;
12
12
  cellID: string;
13
- cellID2G: string;
13
+ cellId2G: string;
14
14
  cellName: string;
15
15
  siteId: string;
16
16
  tech: string;
@@ -35,16 +35,16 @@ export interface Cell {
35
35
  siteLongitude: number;
36
36
  comment: string;
37
37
  planner: string;
38
- atollETP: number;
39
- atollPW: number;
40
- atollRS: number;
41
- atollBW: number;
42
- cellId3: string;
43
- nwtP1: number;
44
- nwtP2: number;
45
- pci1: number;
46
- nwtRS: number;
47
- nwtBW: number;
38
+ atollET?: number;
39
+ atollPW?: number;
40
+ atollRS?: number;
41
+ atollBW?: number;
42
+ rru: string;
43
+ nwtET?: number;
44
+ nwtPW?: number;
45
+ pci?: number;
46
+ nwtRS?: number;
47
+ nwtBW?: number;
48
48
  other?: Record<string, unknown>;
49
49
  customSubgroup: string;
50
50
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartnet360/svelte-components",
3
- "version": "0.0.106",
3
+ "version": "0.0.107",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",