@smartnet360/svelte-components 0.0.135 → 0.0.136
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/dist/core/index.d.ts +1 -2
- package/dist/core/index.js +2 -5
- package/package.json +1 -1
- package/dist/core/CellTable/CellHistoryDemo.svelte +0 -240
- package/dist/core/CellTable/CellHistoryDemo.svelte.d.ts +0 -3
- package/dist/core/CellTable/CellTable.svelte +0 -597
- package/dist/core/CellTable/CellTable.svelte.d.ts +0 -40
- package/dist/core/CellTable/CellTableDemo.svelte +0 -336
- package/dist/core/CellTable/CellTableDemo.svelte.d.ts +0 -3
- package/dist/core/CellTable/CellTablePanel.svelte +0 -833
- package/dist/core/CellTable/CellTablePanel.svelte.d.ts +0 -84
- package/dist/core/CellTable/CellTableToolbar.svelte +0 -323
- package/dist/core/CellTable/CellTableToolbar.svelte.d.ts +0 -59
- package/dist/core/CellTable/ColumnPicker.svelte +0 -214
- package/dist/core/CellTable/ColumnPicker.svelte.d.ts +0 -26
- package/dist/core/CellTable/column-config.d.ts +0 -139
- package/dist/core/CellTable/column-config.js +0 -872
- package/dist/core/CellTable/history-api-helper.d.ts +0 -79
- package/dist/core/CellTable/history-api-helper.js +0 -83
- package/dist/core/CellTable/index.d.ts +0 -15
- package/dist/core/CellTable/index.js +0 -20
- package/dist/core/CellTable/types.d.ts +0 -172
- package/dist/core/CellTable/types.js +0 -6
|
@@ -1,833 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { Snippet } from 'svelte';
|
|
3
|
-
import CellTable from './CellTable.svelte';
|
|
4
|
-
import CellTableToolbar from './CellTableToolbar.svelte';
|
|
5
|
-
import { getColumnMetadata, getPresetVisibleFields, DEFAULT_TECH_COLORS, DEFAULT_STATUS_COLORS, FBAND_COLORS } from './column-config';
|
|
6
|
-
import type {
|
|
7
|
-
CellData,
|
|
8
|
-
CellTableGroupField,
|
|
9
|
-
ColumnPreset,
|
|
10
|
-
RowSelectionEvent,
|
|
11
|
-
RowClickEvent,
|
|
12
|
-
RowDblClickEvent,
|
|
13
|
-
RowContextMenuEvent,
|
|
14
|
-
DataChangeEvent,
|
|
15
|
-
TechColorMap,
|
|
16
|
-
StatusColorMap,
|
|
17
|
-
GroupOption
|
|
18
|
-
} from './types';
|
|
19
|
-
|
|
20
|
-
interface Props {
|
|
21
|
-
/** Cell data array to display */
|
|
22
|
-
cells: CellData[];
|
|
23
|
-
/** Initial grouping field */
|
|
24
|
-
groupBy?: CellTableGroupField;
|
|
25
|
-
/** Custom grouping options (overrides default tech/fband/status options) */
|
|
26
|
-
groupOptions?: GroupOption[];
|
|
27
|
-
/** Initial column preset */
|
|
28
|
-
columnPreset?: ColumnPreset;
|
|
29
|
-
/** Enable row selection */
|
|
30
|
-
selectable?: boolean;
|
|
31
|
-
/** Enable multi-row selection */
|
|
32
|
-
multiSelect?: boolean;
|
|
33
|
-
/** Panel height (CSS value) */
|
|
34
|
-
height?: string;
|
|
35
|
-
/** Show toolbar */
|
|
36
|
-
showToolbar?: boolean;
|
|
37
|
-
/** Show column presets dropdown and column picker (set false for simple tables) */
|
|
38
|
-
showColumnPresets?: boolean;
|
|
39
|
-
/** Show export buttons */
|
|
40
|
-
showExport?: boolean;
|
|
41
|
-
/** Show JSON export button (requires showExport=true) */
|
|
42
|
-
showJsonExport?: boolean;
|
|
43
|
-
/** Technology color mapping */
|
|
44
|
-
techColors?: TechColorMap;
|
|
45
|
-
/** Status color mapping */
|
|
46
|
-
statusColors?: StatusColorMap;
|
|
47
|
-
/** Enable header filters */
|
|
48
|
-
headerFilters?: boolean;
|
|
49
|
-
/** Panel title */
|
|
50
|
-
title?: string;
|
|
51
|
-
/** Show details sidebar */
|
|
52
|
-
showDetailsSidebar?: boolean;
|
|
53
|
-
/** Sidebar width in pixels */
|
|
54
|
-
sidebarWidth?: number;
|
|
55
|
-
/** Persist settings (groupBy, visibleColumns) to localStorage */
|
|
56
|
-
persistSettings?: boolean;
|
|
57
|
-
/** Storage key prefix for persisted settings */
|
|
58
|
-
storageKey?: string;
|
|
59
|
-
/** Enable Tabulator persistence for column widths, order, visibility */
|
|
60
|
-
persistLayout?: boolean;
|
|
61
|
-
/** Show scrollspy navigation bar for quick group navigation */
|
|
62
|
-
showScrollSpy?: boolean;
|
|
63
|
-
/** Bindable reference to table methods */
|
|
64
|
-
tableRef?: { redraw: () => void } | null;
|
|
65
|
-
/** Row selection change event */
|
|
66
|
-
onselectionchange?: (event: RowSelectionEvent) => void;
|
|
67
|
-
/** Row click event */
|
|
68
|
-
onrowclick?: (event: RowClickEvent) => void;
|
|
69
|
-
/** Row double-click event */
|
|
70
|
-
onrowdblclick?: (event: RowDblClickEvent) => void;
|
|
71
|
-
/** Custom header search slot (appears next to title) */
|
|
72
|
-
headerSearch?: Snippet;
|
|
73
|
-
/** Custom header actions slot */
|
|
74
|
-
headerActions?: Snippet;
|
|
75
|
-
/** Custom footer slot */
|
|
76
|
-
footer?: Snippet<[{ selectedRows: CellData[]; selectedCount: number }]>;
|
|
77
|
-
/** Custom details sidebar content */
|
|
78
|
-
detailsContent?: Snippet<[{ cell: CellData | null; closeSidebar: () => void }]>;
|
|
79
|
-
/** Custom context menu content (right-click on row) */
|
|
80
|
-
contextMenu?: Snippet<[{ row: CellData; closeMenu: () => void }]>;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
let {
|
|
84
|
-
cells = [],
|
|
85
|
-
groupBy = $bindable('none'),
|
|
86
|
-
groupOptions,
|
|
87
|
-
columnPreset = $bindable('default'),
|
|
88
|
-
selectable = true,
|
|
89
|
-
multiSelect = true,
|
|
90
|
-
height = '100%',
|
|
91
|
-
showToolbar = true,
|
|
92
|
-
showColumnPresets = true,
|
|
93
|
-
showExport = true,
|
|
94
|
-
showJsonExport = false,
|
|
95
|
-
techColors,
|
|
96
|
-
statusColors,
|
|
97
|
-
headerFilters = true,
|
|
98
|
-
title = 'Cell Data',
|
|
99
|
-
showDetailsSidebar = false,
|
|
100
|
-
sidebarWidth = 320,
|
|
101
|
-
persistSettings = true,
|
|
102
|
-
storageKey = 'cell-table',
|
|
103
|
-
persistLayout = true,
|
|
104
|
-
showScrollSpy = false,
|
|
105
|
-
tableRef = $bindable(null),
|
|
106
|
-
onselectionchange,
|
|
107
|
-
onrowclick,
|
|
108
|
-
onrowdblclick,
|
|
109
|
-
headerSearch,
|
|
110
|
-
headerActions,
|
|
111
|
-
footer,
|
|
112
|
-
detailsContent,
|
|
113
|
-
contextMenu
|
|
114
|
-
}: Props = $props();
|
|
115
|
-
|
|
116
|
-
// Context menu state
|
|
117
|
-
let contextMenuVisible = $state(false);
|
|
118
|
-
let contextMenuRow: CellData | null = $state(null);
|
|
119
|
-
let contextMenuPosition = $state({ x: 0, y: 0 });
|
|
120
|
-
|
|
121
|
-
// Storage keys
|
|
122
|
-
const STORAGE_KEY_GROUP = `${storageKey}-groupBy`;
|
|
123
|
-
const STORAGE_KEY_FILTERS = `${storageKey}-filtersVisible`;
|
|
124
|
-
const STORAGE_KEY_SCROLLSPY = `${storageKey}-scrollSpyEnabled`;
|
|
125
|
-
const STORAGE_KEY_PRESET = `${storageKey}-columnPreset`;
|
|
126
|
-
|
|
127
|
-
// Per-preset column visibility storage key
|
|
128
|
-
function getColumnsStorageKey(preset: ColumnPreset): string {
|
|
129
|
-
return `${storageKey}-visibleColumns-${preset}`;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Load persisted settings
|
|
133
|
-
function loadPersistedSettings() {
|
|
134
|
-
if (!persistSettings || typeof localStorage === 'undefined') return { filtersVisible: true, scrollSpyEnabled: showScrollSpy, preset: null };
|
|
135
|
-
|
|
136
|
-
let filters = true;
|
|
137
|
-
let scrollSpy = showScrollSpy;
|
|
138
|
-
let preset: ColumnPreset | null = null;
|
|
139
|
-
|
|
140
|
-
try {
|
|
141
|
-
const savedGroup = localStorage.getItem(STORAGE_KEY_GROUP);
|
|
142
|
-
if (savedGroup) {
|
|
143
|
-
groupBy = savedGroup as CellTableGroupField;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const savedPreset = localStorage.getItem(STORAGE_KEY_PRESET);
|
|
147
|
-
if (savedPreset) {
|
|
148
|
-
preset = savedPreset as ColumnPreset;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const savedFilters = localStorage.getItem(STORAGE_KEY_FILTERS);
|
|
152
|
-
if (savedFilters !== null) {
|
|
153
|
-
filters = savedFilters === 'true';
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const savedScrollSpy = localStorage.getItem(STORAGE_KEY_SCROLLSPY);
|
|
157
|
-
if (savedScrollSpy !== null) {
|
|
158
|
-
scrollSpy = savedScrollSpy === 'true';
|
|
159
|
-
}
|
|
160
|
-
} catch (e) {
|
|
161
|
-
console.warn('Failed to load CellTable settings:', e);
|
|
162
|
-
}
|
|
163
|
-
return { filtersVisible: filters, scrollSpyEnabled: scrollSpy, preset };
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Load columns for a specific preset
|
|
167
|
-
function loadColumnsForPreset(preset: ColumnPreset): string[] {
|
|
168
|
-
if (!persistSettings || typeof localStorage === 'undefined') {
|
|
169
|
-
return getPresetVisibleFields(preset);
|
|
170
|
-
}
|
|
171
|
-
try {
|
|
172
|
-
const saved = localStorage.getItem(getColumnsStorageKey(preset));
|
|
173
|
-
if (saved) {
|
|
174
|
-
return JSON.parse(saved) as string[];
|
|
175
|
-
}
|
|
176
|
-
} catch (e) {
|
|
177
|
-
console.warn('Failed to load columns for preset:', e);
|
|
178
|
-
}
|
|
179
|
-
return getPresetVisibleFields(preset);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Save group setting
|
|
183
|
-
function saveGroupSetting(group: CellTableGroupField) {
|
|
184
|
-
if (!persistSettings || typeof localStorage === 'undefined') return;
|
|
185
|
-
try {
|
|
186
|
-
localStorage.setItem(STORAGE_KEY_GROUP, group);
|
|
187
|
-
} catch (e) {
|
|
188
|
-
console.warn('Failed to save group setting:', e);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Save column visibility for current preset
|
|
193
|
-
function saveColumnVisibility(columns: string[]) {
|
|
194
|
-
if (!persistSettings || typeof localStorage === 'undefined') return;
|
|
195
|
-
try {
|
|
196
|
-
localStorage.setItem(getColumnsStorageKey(columnPreset), JSON.stringify(columns));
|
|
197
|
-
} catch (e) {
|
|
198
|
-
console.warn('Failed to save column visibility:', e);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Save filter visibility
|
|
203
|
-
function saveFilterVisibility(visible: boolean) {
|
|
204
|
-
if (!persistSettings || typeof localStorage === 'undefined') return;
|
|
205
|
-
try {
|
|
206
|
-
localStorage.setItem(STORAGE_KEY_FILTERS, String(visible));
|
|
207
|
-
} catch (e) {
|
|
208
|
-
console.warn('Failed to save filter visibility:', e);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Save scrollspy state
|
|
213
|
-
function saveScrollSpyState(enabled: boolean) {
|
|
214
|
-
if (!persistSettings || typeof localStorage === 'undefined') return;
|
|
215
|
-
try {
|
|
216
|
-
localStorage.setItem(STORAGE_KEY_SCROLLSPY, String(enabled));
|
|
217
|
-
} catch (e) {
|
|
218
|
-
console.warn('Failed to save scrollspy state:', e);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Save column preset
|
|
223
|
-
function savePreset(preset: ColumnPreset) {
|
|
224
|
-
if (!persistSettings || typeof localStorage === 'undefined') return;
|
|
225
|
-
try {
|
|
226
|
-
localStorage.setItem(STORAGE_KEY_PRESET, preset);
|
|
227
|
-
} catch (e) {
|
|
228
|
-
console.warn('Failed to save preset:', e);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
let cellTable: CellTable;
|
|
233
|
-
let selectedCount = $state(0);
|
|
234
|
-
let selectedRows = $state<CellData[]>([]);
|
|
235
|
-
let filteredCount = $state(cells.length);
|
|
236
|
-
let sidebarOpen = $state(false);
|
|
237
|
-
let clickedCell: CellData | null = $state(null);
|
|
238
|
-
let tableRefSet = false;
|
|
239
|
-
|
|
240
|
-
// Column visibility management
|
|
241
|
-
const columnMeta = getColumnMetadata();
|
|
242
|
-
|
|
243
|
-
// Initialize from storage or defaults
|
|
244
|
-
const persistedSettings = loadPersistedSettings();
|
|
245
|
-
let filtersVisible = $state(persistedSettings.filtersVisible);
|
|
246
|
-
|
|
247
|
-
// Apply persisted preset if available
|
|
248
|
-
if (persistedSettings.preset) {
|
|
249
|
-
columnPreset = persistedSettings.preset;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// Load columns for the current preset (with any saved customizations)
|
|
253
|
-
let visibleColumns = $state<string[]>(loadColumnsForPreset(columnPreset));
|
|
254
|
-
|
|
255
|
-
// ScrollSpy state - initialize from persisted settings
|
|
256
|
-
let scrollSpyGroups = $state<{ key: string; count: number }[]>([]);
|
|
257
|
-
let scrollSpyEnabled = $state(persistedSettings.scrollSpyEnabled);
|
|
258
|
-
|
|
259
|
-
// Track preset changes to load per-preset columns
|
|
260
|
-
let prevPreset: ColumnPreset | null = columnPreset;
|
|
261
|
-
|
|
262
|
-
// Apply column visibility to Tabulator
|
|
263
|
-
function applyColumnVisibility(columns: string[]) {
|
|
264
|
-
if (!cellTable) return;
|
|
265
|
-
columnMeta.forEach(col => {
|
|
266
|
-
if (columns.includes(col.field)) {
|
|
267
|
-
cellTable.showColumn(col.field);
|
|
268
|
-
} else {
|
|
269
|
-
cellTable.hideColumn(col.field);
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Update visible columns when preset changes - load saved customizations for that preset
|
|
275
|
-
$effect(() => {
|
|
276
|
-
if (columnPreset !== prevPreset) {
|
|
277
|
-
prevPreset = columnPreset;
|
|
278
|
-
// Load saved columns for this preset (or preset defaults if none saved)
|
|
279
|
-
const newColumns = loadColumnsForPreset(columnPreset);
|
|
280
|
-
visibleColumns = newColumns;
|
|
281
|
-
// Apply to Tabulator
|
|
282
|
-
applyColumnVisibility(newColumns);
|
|
283
|
-
}
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
// Expose table methods via tableRef - only set once
|
|
287
|
-
$effect(() => {
|
|
288
|
-
if (cellTable && !tableRefSet) {
|
|
289
|
-
tableRefSet = true;
|
|
290
|
-
tableRef = {
|
|
291
|
-
redraw: () => cellTable?.redraw()
|
|
292
|
-
};
|
|
293
|
-
// Apply persisted column visibility after table is ready
|
|
294
|
-
setTimeout(() => applyColumnVisibility(visibleColumns), 100);
|
|
295
|
-
// Apply persisted filter visibility after table is ready
|
|
296
|
-
if (!filtersVisible) {
|
|
297
|
-
setTimeout(() => cellTable?.toggleHeaderFilters(false), 100);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
function handleSelectionChange(event: RowSelectionEvent) {
|
|
303
|
-
selectedCount = event.rows.length;
|
|
304
|
-
selectedRows = event.rows;
|
|
305
|
-
onselectionchange?.(event);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
function handleRowClick(event: RowClickEvent) {
|
|
309
|
-
if (showDetailsSidebar) {
|
|
310
|
-
clickedCell = event.row;
|
|
311
|
-
sidebarOpen = true;
|
|
312
|
-
setTimeout(() => cellTable?.redraw(), 320);
|
|
313
|
-
}
|
|
314
|
-
onrowclick?.(event);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
function handleDataChange(event: DataChangeEvent) {
|
|
318
|
-
filteredCount = event.filteredCount;
|
|
319
|
-
// Update scrollspy groups when data changes
|
|
320
|
-
updateScrollSpyGroups();
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
function handleRowContextMenu(event: RowContextMenuEvent) {
|
|
324
|
-
if (contextMenu) {
|
|
325
|
-
contextMenuRow = event.row;
|
|
326
|
-
contextMenuPosition = { x: event.event.clientX, y: event.event.clientY };
|
|
327
|
-
contextMenuVisible = true;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
function closeContextMenu() {
|
|
332
|
-
contextMenuVisible = false;
|
|
333
|
-
contextMenuRow = null;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
function handleClickOutside(event: MouseEvent) {
|
|
337
|
-
const target = event.target as HTMLElement;
|
|
338
|
-
if (!target.closest('.context-menu-container')) {
|
|
339
|
-
closeContextMenu();
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
function updateScrollSpyGroups() {
|
|
344
|
-
if (scrollSpyEnabled && groupBy !== 'none') {
|
|
345
|
-
// Small delay to ensure table has updated
|
|
346
|
-
setTimeout(() => {
|
|
347
|
-
scrollSpyGroups = cellTable?.getGroups() ?? [];
|
|
348
|
-
}, 50);
|
|
349
|
-
} else {
|
|
350
|
-
scrollSpyGroups = [];
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
function handleScrollToGroup(key: string) {
|
|
355
|
-
cellTable?.scrollToGroup(key);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
function handleToggleScrollSpy() {
|
|
359
|
-
scrollSpyEnabled = !scrollSpyEnabled;
|
|
360
|
-
saveScrollSpyState(scrollSpyEnabled);
|
|
361
|
-
if (scrollSpyEnabled && groupBy !== 'none') {
|
|
362
|
-
updateScrollSpyGroups();
|
|
363
|
-
} else {
|
|
364
|
-
scrollSpyGroups = [];
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
function handleGroupChange(group: CellTableGroupField) {
|
|
369
|
-
groupBy = group;
|
|
370
|
-
saveGroupSetting(group);
|
|
371
|
-
// Update scrollspy groups after grouping changes
|
|
372
|
-
if (scrollSpyEnabled) {
|
|
373
|
-
setTimeout(() => updateScrollSpyGroups(), 100);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
function handlePresetChange(preset: ColumnPreset) {
|
|
378
|
-
columnPreset = preset;
|
|
379
|
-
savePreset(preset);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
function handleExportCSV() {
|
|
383
|
-
cellTable?.downloadCSV(`cells-${new Date().toISOString().slice(0, 10)}.csv`);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
function handleExportJSON() {
|
|
387
|
-
cellTable?.downloadJSON(`cells-${new Date().toISOString().slice(0, 10)}.json`);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
function handleClearFilters() {
|
|
391
|
-
cellTable?.clearFilters();
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
function handleCollapseAll() {
|
|
395
|
-
cellTable?.collapseAll();
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
function handleExpandAll() {
|
|
399
|
-
cellTable?.expandAll();
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
function handleToggleFilters() {
|
|
403
|
-
filtersVisible = !filtersVisible;
|
|
404
|
-
cellTable?.toggleHeaderFilters(filtersVisible);
|
|
405
|
-
saveFilterVisibility(filtersVisible);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
function handleColumnVisibilityChange(field: string, visible: boolean) {
|
|
409
|
-
if (visible) {
|
|
410
|
-
if (!visibleColumns.includes(field)) {
|
|
411
|
-
visibleColumns = [...visibleColumns, field];
|
|
412
|
-
}
|
|
413
|
-
cellTable?.showColumn(field);
|
|
414
|
-
} else {
|
|
415
|
-
visibleColumns = visibleColumns.filter(f => f !== field);
|
|
416
|
-
cellTable?.hideColumn(field);
|
|
417
|
-
}
|
|
418
|
-
saveColumnVisibility(visibleColumns);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
function handleResetColumns() {
|
|
422
|
-
const defaultFields = getPresetVisibleFields(columnPreset);
|
|
423
|
-
visibleColumns = defaultFields;
|
|
424
|
-
// Show/hide columns to match preset
|
|
425
|
-
columnMeta.forEach(col => {
|
|
426
|
-
if (defaultFields.includes(col.field)) {
|
|
427
|
-
cellTable?.showColumn(col.field);
|
|
428
|
-
} else {
|
|
429
|
-
cellTable?.hideColumn(col.field);
|
|
430
|
-
}
|
|
431
|
-
});
|
|
432
|
-
// Clear persisted column visibility for this preset (use preset defaults)
|
|
433
|
-
if (persistSettings && typeof localStorage !== 'undefined') {
|
|
434
|
-
try {
|
|
435
|
-
localStorage.removeItem(getColumnsStorageKey(columnPreset));
|
|
436
|
-
} catch (e) {
|
|
437
|
-
console.warn('Failed to clear column visibility:', e);
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
function toggleSidebar() {
|
|
443
|
-
sidebarOpen = !sidebarOpen;
|
|
444
|
-
setTimeout(() => cellTable?.redraw(), 320);
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
function closeSidebar() {
|
|
448
|
-
sidebarOpen = false;
|
|
449
|
-
setTimeout(() => cellTable?.redraw(), 320);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
// Expose table methods
|
|
453
|
-
export function getSelectedRows(): CellData[] {
|
|
454
|
-
return cellTable?.getSelectedRows() ?? [];
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
export function clearSelection(): void {
|
|
458
|
-
cellTable?.clearSelection();
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
export function scrollToRow(id: string): void {
|
|
462
|
-
cellTable?.scrollToRow(id);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
export function redraw(): void {
|
|
466
|
-
cellTable?.redraw();
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
export function openSidebar(): void {
|
|
470
|
-
sidebarOpen = true;
|
|
471
|
-
setTimeout(() => cellTable?.redraw(), 320);
|
|
472
|
-
}
|
|
473
|
-
</script>
|
|
474
|
-
|
|
475
|
-
<div class="cell-table-panel d-flex flex-column" style:height>
|
|
476
|
-
<!-- Header -->
|
|
477
|
-
<div class="panel-header d-flex align-items-center justify-content-between px-3 py-2 bg-body-secondary border-bottom">
|
|
478
|
-
<div class="d-flex align-items-center gap-3">
|
|
479
|
-
<h6 class="mb-0 d-flex align-items-center gap-2">
|
|
480
|
-
<i class="bi bi-table text-primary"></i>
|
|
481
|
-
{title}
|
|
482
|
-
</h6>
|
|
483
|
-
{#if headerSearch}
|
|
484
|
-
{@render headerSearch()}
|
|
485
|
-
{/if}
|
|
486
|
-
</div>
|
|
487
|
-
<div class="d-flex align-items-center gap-2">
|
|
488
|
-
{#if headerActions}
|
|
489
|
-
<div class="header-actions">
|
|
490
|
-
{@render headerActions()}
|
|
491
|
-
</div>
|
|
492
|
-
{/if}
|
|
493
|
-
{#if showDetailsSidebar}
|
|
494
|
-
<button
|
|
495
|
-
class="btn btn-sm"
|
|
496
|
-
class:btn-outline-secondary={!sidebarOpen}
|
|
497
|
-
class:btn-secondary={sidebarOpen}
|
|
498
|
-
onclick={toggleSidebar}
|
|
499
|
-
title={sidebarOpen ? 'Hide details' : 'Show details'}
|
|
500
|
-
>
|
|
501
|
-
<i class="bi" class:bi-layout-sidebar-reverse={!sidebarOpen} class:bi-x-lg={sidebarOpen}></i>
|
|
502
|
-
</button>
|
|
503
|
-
{/if}
|
|
504
|
-
</div>
|
|
505
|
-
</div>
|
|
506
|
-
|
|
507
|
-
<!-- Toolbar -->
|
|
508
|
-
{#if showToolbar}
|
|
509
|
-
<CellTableToolbar
|
|
510
|
-
{groupBy}
|
|
511
|
-
{groupOptions}
|
|
512
|
-
{columnPreset}
|
|
513
|
-
totalCount={cells.length}
|
|
514
|
-
{filteredCount}
|
|
515
|
-
{selectedCount}
|
|
516
|
-
{showExport}
|
|
517
|
-
{showJsonExport}
|
|
518
|
-
showPresets={showColumnPresets}
|
|
519
|
-
ongroupchange={handleGroupChange}
|
|
520
|
-
onpresetchange={handlePresetChange}
|
|
521
|
-
onexportcsv={handleExportCSV}
|
|
522
|
-
onexportjson={handleExportJSON}
|
|
523
|
-
onclearfilters={handleClearFilters}
|
|
524
|
-
oncollapseall={handleCollapseAll}
|
|
525
|
-
onexpandall={handleExpandAll}
|
|
526
|
-
{filtersVisible}
|
|
527
|
-
ontogglefilters={handleToggleFilters}
|
|
528
|
-
{columnMeta}
|
|
529
|
-
{visibleColumns}
|
|
530
|
-
oncolumnvisibilitychange={handleColumnVisibilityChange}
|
|
531
|
-
onresetcolumns={handleResetColumns}
|
|
532
|
-
{scrollSpyEnabled}
|
|
533
|
-
showScrollSpyToggle={showScrollSpy}
|
|
534
|
-
ontogglescrollspy={handleToggleScrollSpy}
|
|
535
|
-
/>
|
|
536
|
-
{/if}
|
|
537
|
-
|
|
538
|
-
<!-- ScrollSpy Navigation Bar -->
|
|
539
|
-
{#if scrollSpyEnabled && groupBy !== 'none' && scrollSpyGroups.length > 0}
|
|
540
|
-
<div class="scrollspy-bar d-flex align-items-center gap-2 px-3 py-2 bg-body-tertiary border-bottom overflow-auto">
|
|
541
|
-
<span class="text-muted small me-1">
|
|
542
|
-
<i class="bi bi-signpost-split"></i> Jump to:
|
|
543
|
-
</span>
|
|
544
|
-
{#each scrollSpyGroups as group (group.key)}
|
|
545
|
-
{@const bgColor = groupBy === 'tech'
|
|
546
|
-
? (techColors?.[group.key] ?? DEFAULT_TECH_COLORS[group.key] ?? '#6c757d')
|
|
547
|
-
: groupBy === 'fband'
|
|
548
|
-
? (FBAND_COLORS[group.key] ?? '#6c757d')
|
|
549
|
-
: groupBy === 'status'
|
|
550
|
-
? (statusColors?.[group.key] ?? DEFAULT_STATUS_COLORS[group.key] ?? '#6c757d')
|
|
551
|
-
: '#6c757d'}
|
|
552
|
-
<button
|
|
553
|
-
type="button"
|
|
554
|
-
class="btn btn-sm scrollspy-badge"
|
|
555
|
-
style="background-color: {bgColor}; border-color: {bgColor}; color: white;"
|
|
556
|
-
onclick={() => handleScrollToGroup(group.key)}
|
|
557
|
-
title="Scroll to {group.key} ({group.count} cells)"
|
|
558
|
-
>
|
|
559
|
-
<span class="badge rounded-pill bg-light text-dark me-1">{group.count}</span>
|
|
560
|
-
{group.key}
|
|
561
|
-
</button>
|
|
562
|
-
{/each}
|
|
563
|
-
</div>
|
|
564
|
-
{/if}
|
|
565
|
-
|
|
566
|
-
<!-- Main content with optional sidebar -->
|
|
567
|
-
<div class="content-area d-flex flex-grow-1 overflow-hidden">
|
|
568
|
-
<!-- Table -->
|
|
569
|
-
<div class="table-wrapper flex-grow-1 overflow-hidden">
|
|
570
|
-
<CellTable
|
|
571
|
-
bind:this={cellTable}
|
|
572
|
-
{cells}
|
|
573
|
-
{groupBy}
|
|
574
|
-
{columnPreset}
|
|
575
|
-
{selectable}
|
|
576
|
-
{multiSelect}
|
|
577
|
-
height="100%"
|
|
578
|
-
{techColors}
|
|
579
|
-
{statusColors}
|
|
580
|
-
{headerFilters}
|
|
581
|
-
{persistLayout}
|
|
582
|
-
storageKey="{storageKey}-tabulator"
|
|
583
|
-
onselectionchange={handleSelectionChange}
|
|
584
|
-
ondatachange={handleDataChange}
|
|
585
|
-
onrowclick={handleRowClick}
|
|
586
|
-
onrowcontextmenu={handleRowContextMenu}
|
|
587
|
-
{onrowdblclick}
|
|
588
|
-
/>
|
|
589
|
-
</div>
|
|
590
|
-
|
|
591
|
-
<!-- Details Sidebar -->
|
|
592
|
-
{#if showDetailsSidebar}
|
|
593
|
-
<aside
|
|
594
|
-
class="details-sidebar border-start bg-white overflow-auto"
|
|
595
|
-
class:open={sidebarOpen}
|
|
596
|
-
style:--sidebar-width="{sidebarWidth}px"
|
|
597
|
-
>
|
|
598
|
-
<div class="sidebar-content" style:width="{sidebarWidth}px">
|
|
599
|
-
<div class="d-flex align-items-center justify-content-between p-3 border-bottom bg-light sticky-top">
|
|
600
|
-
<h6 class="mb-0">
|
|
601
|
-
<i class="bi bi-info-circle text-primary"></i> Details
|
|
602
|
-
</h6>
|
|
603
|
-
<button
|
|
604
|
-
class="btn btn-sm btn-outline-secondary"
|
|
605
|
-
onclick={closeSidebar}
|
|
606
|
-
title="Close"
|
|
607
|
-
>
|
|
608
|
-
<i class="bi bi-x-lg"></i>
|
|
609
|
-
</button>
|
|
610
|
-
</div>
|
|
611
|
-
|
|
612
|
-
<div class="p-3">
|
|
613
|
-
{#if detailsContent}
|
|
614
|
-
{@render detailsContent({ cell: clickedCell, closeSidebar })}
|
|
615
|
-
{:else if clickedCell}
|
|
616
|
-
<!-- Default details view -->
|
|
617
|
-
<dl class="row mb-0 small">
|
|
618
|
-
<dt class="col-5 text-muted">ID</dt>
|
|
619
|
-
<dd class="col-7"><code class="text-primary">{clickedCell.id}</code></dd>
|
|
620
|
-
|
|
621
|
-
<dt class="col-5 text-muted">Cell Name</dt>
|
|
622
|
-
<dd class="col-7 fw-medium">{clickedCell.cellName}</dd>
|
|
623
|
-
|
|
624
|
-
<dt class="col-5 text-muted">Site</dt>
|
|
625
|
-
<dd class="col-7">{clickedCell.siteId}</dd>
|
|
626
|
-
|
|
627
|
-
<dt class="col-5 text-muted">Technology</dt>
|
|
628
|
-
<dd class="col-7">
|
|
629
|
-
<span class="badge" style="background-color: {techColors?.[clickedCell.tech] ?? DEFAULT_TECH_COLORS[clickedCell.tech] ?? '#6c757d'}; color: white;">{clickedCell.tech}</span>
|
|
630
|
-
</dd>
|
|
631
|
-
|
|
632
|
-
<dt class="col-5 text-muted">Band</dt>
|
|
633
|
-
<dd class="col-7">
|
|
634
|
-
<span class="badge" style="background-color: {FBAND_COLORS[clickedCell.fband] ?? '#6c757d'}; color: white;">{clickedCell.fband}</span>
|
|
635
|
-
</dd>
|
|
636
|
-
|
|
637
|
-
<dt class="col-5 text-muted">Frequency</dt>
|
|
638
|
-
<dd class="col-7">{clickedCell.frq} MHz</dd>
|
|
639
|
-
|
|
640
|
-
<dt class="col-5 text-muted">Status</dt>
|
|
641
|
-
<dd class="col-7">
|
|
642
|
-
<span class="badge" style="background-color: {statusColors?.[clickedCell.status] ?? DEFAULT_STATUS_COLORS[clickedCell.status] ?? '#6c757d'}; color: white;">{clickedCell.status.replace(/_/g, ' ')}</span>
|
|
643
|
-
</dd>
|
|
644
|
-
|
|
645
|
-
<dt class="col-12 text-muted mt-2 mb-1 border-top pt-2">Physical</dt>
|
|
646
|
-
|
|
647
|
-
<dt class="col-5 text-muted">Azimuth</dt>
|
|
648
|
-
<dd class="col-7">{clickedCell.azimuth}°</dd>
|
|
649
|
-
|
|
650
|
-
<dt class="col-5 text-muted">Height</dt>
|
|
651
|
-
<dd class="col-7">{clickedCell.height}m</dd>
|
|
652
|
-
|
|
653
|
-
<dt class="col-5 text-muted">Beamwidth</dt>
|
|
654
|
-
<dd class="col-7">{clickedCell.beamwidth}°</dd>
|
|
655
|
-
|
|
656
|
-
<dt class="col-5 text-muted">E-Tilt</dt>
|
|
657
|
-
<dd class="col-7">{clickedCell.electricalTilt}°</dd>
|
|
658
|
-
|
|
659
|
-
<dt class="col-5 text-muted">Antenna</dt>
|
|
660
|
-
<dd class="col-7 text-truncate" title={clickedCell.antenna}>
|
|
661
|
-
{clickedCell.antenna}
|
|
662
|
-
</dd>
|
|
663
|
-
|
|
664
|
-
<dt class="col-12 text-muted mt-2 mb-1 border-top pt-2">Location</dt>
|
|
665
|
-
|
|
666
|
-
<dt class="col-5 text-muted">Latitude</dt>
|
|
667
|
-
<dd class="col-7">{clickedCell.latitude.toFixed(6)}</dd>
|
|
668
|
-
|
|
669
|
-
<dt class="col-5 text-muted">Longitude</dt>
|
|
670
|
-
<dd class="col-7">{clickedCell.longitude.toFixed(6)}</dd>
|
|
671
|
-
|
|
672
|
-
<dt class="col-12 text-muted mt-2 mb-1 border-top pt-2">Planning</dt>
|
|
673
|
-
|
|
674
|
-
<dt class="col-5 text-muted">Planner</dt>
|
|
675
|
-
<dd class="col-7">{clickedCell.planner}</dd>
|
|
676
|
-
|
|
677
|
-
<dt class="col-5 text-muted">On Air</dt>
|
|
678
|
-
<dd class="col-7">{clickedCell.onAirDate}</dd>
|
|
679
|
-
|
|
680
|
-
{#if clickedCell.comment}
|
|
681
|
-
<dt class="col-5 text-muted">Comment</dt>
|
|
682
|
-
<dd class="col-7 fst-italic">{clickedCell.comment}</dd>
|
|
683
|
-
{/if}
|
|
684
|
-
|
|
685
|
-
<!-- Dynamic Other Properties -->
|
|
686
|
-
{#if clickedCell.other && Object.keys(clickedCell.other).length > 0}
|
|
687
|
-
<dt class="col-12 text-muted mt-2 mb-1 border-top pt-2">Other</dt>
|
|
688
|
-
|
|
689
|
-
{#each Object.entries(clickedCell.other) as [key, value]}
|
|
690
|
-
<dt class="col-5 text-muted text-capitalize">{key.replace(/_/g, ' ')}</dt>
|
|
691
|
-
<dd class="col-7">
|
|
692
|
-
{#if value === null || value === undefined}
|
|
693
|
-
<span class="text-muted fst-italic">—</span>
|
|
694
|
-
{:else if typeof value === 'boolean'}
|
|
695
|
-
<span class="badge" class:bg-success={value} class:bg-secondary={!value}>
|
|
696
|
-
{value ? 'Yes' : 'No'}
|
|
697
|
-
</span>
|
|
698
|
-
{:else if typeof value === 'number'}
|
|
699
|
-
<code>{value}</code>
|
|
700
|
-
{:else if typeof value === 'object'}
|
|
701
|
-
<code class="small text-break">{JSON.stringify(value)}</code>
|
|
702
|
-
{:else}
|
|
703
|
-
{String(value)}
|
|
704
|
-
{/if}
|
|
705
|
-
</dd>
|
|
706
|
-
{/each}
|
|
707
|
-
{/if}
|
|
708
|
-
</dl>
|
|
709
|
-
{:else}
|
|
710
|
-
<div class="text-center text-muted py-5">
|
|
711
|
-
<i class="bi bi-hand-index fs-1 opacity-50"></i>
|
|
712
|
-
<p class="mt-2 mb-0">Click a row to see details</p>
|
|
713
|
-
</div>
|
|
714
|
-
{/if}
|
|
715
|
-
</div>
|
|
716
|
-
</div>
|
|
717
|
-
</aside>
|
|
718
|
-
{/if}
|
|
719
|
-
</div>
|
|
720
|
-
|
|
721
|
-
<!-- Footer -->
|
|
722
|
-
{#if footer}
|
|
723
|
-
<div class="panel-footer border-top p-2 bg-body-tertiary">
|
|
724
|
-
{@render footer({ selectedRows, selectedCount })}
|
|
725
|
-
</div>
|
|
726
|
-
{/if}
|
|
727
|
-
|
|
728
|
-
<!-- Context Menu (portal-like, fixed position) -->
|
|
729
|
-
{#if contextMenuVisible && contextMenuRow && contextMenu}
|
|
730
|
-
<div
|
|
731
|
-
class="context-menu-container"
|
|
732
|
-
style="position: fixed; left: {contextMenuPosition.x}px; top: {contextMenuPosition.y}px; z-index: 1050;"
|
|
733
|
-
>
|
|
734
|
-
{@render contextMenu({ row: contextMenuRow, closeMenu: closeContextMenu })}
|
|
735
|
-
</div>
|
|
736
|
-
{/if}
|
|
737
|
-
</div>
|
|
738
|
-
|
|
739
|
-
<!-- Click outside handler -->
|
|
740
|
-
<svelte:window onclick={contextMenuVisible ? handleClickOutside : undefined} />
|
|
741
|
-
|
|
742
|
-
<style>
|
|
743
|
-
.cell-table-panel {
|
|
744
|
-
background-color: var(--bs-body-bg, #fff);
|
|
745
|
-
border: 1px solid var(--bs-border-color, #dee2e6);
|
|
746
|
-
border-radius: var(--bs-border-radius, 0.375rem);
|
|
747
|
-
overflow: hidden;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
.panel-header {
|
|
751
|
-
min-height: 48px;
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
.panel-header h6 {
|
|
755
|
-
font-weight: 600;
|
|
756
|
-
color: var(--bs-body-color, #212529);
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
.content-area {
|
|
760
|
-
min-height: 0;
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
.table-wrapper {
|
|
764
|
-
min-height: 0;
|
|
765
|
-
transition: all 0.3s ease;
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
.details-sidebar {
|
|
769
|
-
width: 0;
|
|
770
|
-
min-width: 0;
|
|
771
|
-
opacity: 0;
|
|
772
|
-
transition: width 0.3s ease, opacity 0.3s ease, min-width 0.3s ease;
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
.details-sidebar.open {
|
|
776
|
-
width: var(--sidebar-width, 320px);
|
|
777
|
-
min-width: var(--sidebar-width, 320px);
|
|
778
|
-
opacity: 1;
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
.panel-footer {
|
|
782
|
-
min-height: 48px;
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
/* ScrollSpy bar styling */
|
|
786
|
-
.scrollspy-bar {
|
|
787
|
-
min-height: 40px;
|
|
788
|
-
flex-wrap: nowrap;
|
|
789
|
-
scrollbar-width: thin;
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
.scrollspy-bar::-webkit-scrollbar {
|
|
793
|
-
height: 4px;
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
.scrollspy-bar::-webkit-scrollbar-thumb {
|
|
797
|
-
background: var(--bs-secondary-color, #6c757d);
|
|
798
|
-
border-radius: 2px;
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
.scrollspy-badge {
|
|
802
|
-
white-space: nowrap;
|
|
803
|
-
font-size: 0.75rem;
|
|
804
|
-
padding: 0.25rem 0.5rem;
|
|
805
|
-
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
.scrollspy-badge:hover {
|
|
809
|
-
transform: translateY(-1px);
|
|
810
|
-
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
|
811
|
-
filter: brightness(1.1);
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
.scrollspy-badge:active {
|
|
815
|
-
transform: translateY(0);
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
/* Context menu styling */
|
|
819
|
-
.context-menu-container {
|
|
820
|
-
animation: contextMenuFadeIn 0.1s ease-out;
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
@keyframes contextMenuFadeIn {
|
|
824
|
-
from {
|
|
825
|
-
opacity: 0;
|
|
826
|
-
transform: scale(0.95);
|
|
827
|
-
}
|
|
828
|
-
to {
|
|
829
|
-
opacity: 1;
|
|
830
|
-
transform: scale(1);
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
</style>
|