@smartnet360/svelte-components 0.0.106 → 0.0.108
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/CellTable/CellTable.svelte +101 -19
- package/dist/core/CellTable/CellTable.svelte.d.ts +6 -0
- package/dist/core/CellTable/CellTablePanel.svelte +85 -2
- package/dist/core/CellTable/CellTableToolbar.svelte +71 -1
- package/dist/core/CellTable/CellTableToolbar.svelte.d.ts +17 -0
- package/dist/core/CellTable/ColumnPicker.svelte +214 -0
- package/dist/core/CellTable/ColumnPicker.svelte.d.ts +26 -0
- package/dist/core/CellTable/column-config.d.ts +35 -0
- package/dist/core/CellTable/column-config.js +112 -0
- package/dist/core/CellTable/index.d.ts +2 -1
- package/dist/core/CellTable/index.js +2 -1
- package/dist/core/CoverageMap/data/SiteStore.js +2 -2
- package/dist/shared/demo/cell-generator.js +11 -7
- package/dist/shared/demo/cell-types.d.ts +11 -11
- package/package.json +1 -1
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
getColumnsForPreset,
|
|
17
17
|
getGroupHeaderFormatter,
|
|
18
18
|
DEFAULT_TECH_COLORS,
|
|
19
|
-
DEFAULT_STATUS_COLORS
|
|
19
|
+
DEFAULT_STATUS_COLORS,
|
|
20
|
+
cellDataSorter
|
|
20
21
|
} from './column-config';
|
|
21
22
|
|
|
22
23
|
interface Props extends CellTableProps {
|
|
@@ -57,13 +58,21 @@
|
|
|
57
58
|
let table: Tabulator | null = null;
|
|
58
59
|
let isInitialized = $state(false);
|
|
59
60
|
|
|
60
|
-
// Reactive column configuration
|
|
61
|
-
let columns = $derived(
|
|
61
|
+
// Reactive column configuration - only changes when preset changes
|
|
62
|
+
let columns = $derived.by(() => {
|
|
63
|
+
// Only depend on columnPreset to avoid unnecessary recalculations
|
|
64
|
+
return getColumnsForPreset(columnPreset, techColors, statusColors, headerFilters);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Pre-sort data using our custom multi-level sorter
|
|
68
|
+
let sortedCells = $derived.by(() => {
|
|
69
|
+
return [...cells].sort(cellDataSorter);
|
|
70
|
+
});
|
|
62
71
|
|
|
63
72
|
// Build Tabulator options
|
|
64
73
|
function buildOptions(): Options {
|
|
65
74
|
const baseOptions: Options = {
|
|
66
|
-
data:
|
|
75
|
+
data: sortedCells,
|
|
67
76
|
columns: columns,
|
|
68
77
|
layout: 'fitDataFill',
|
|
69
78
|
height: height,
|
|
@@ -76,8 +85,12 @@
|
|
|
76
85
|
resizableColumns: resizableColumns,
|
|
77
86
|
movableColumns: movableColumns,
|
|
78
87
|
|
|
79
|
-
//
|
|
80
|
-
initialSort: [
|
|
88
|
+
// No initial sort - data is pre-sorted
|
|
89
|
+
initialSort: [
|
|
90
|
+
{ column: 'tech', dir: 'asc' },
|
|
91
|
+
{ column: 'fband', dir: 'asc' },
|
|
92
|
+
{ column: 'cellName', dir: 'asc' }
|
|
93
|
+
],
|
|
81
94
|
|
|
82
95
|
// Row selection
|
|
83
96
|
selectable: selectable ? (multiSelect ? true : 1) : false,
|
|
@@ -171,13 +184,7 @@
|
|
|
171
184
|
// Mark as initialized after table is ready
|
|
172
185
|
table.on('tableBuilt', () => {
|
|
173
186
|
isInitialized = true;
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Update table data when cells change
|
|
178
|
-
$effect(() => {
|
|
179
|
-
if (isInitialized && table && cells) {
|
|
180
|
-
table.replaceData(cells);
|
|
187
|
+
// Fire initial data change event
|
|
181
188
|
if (ondatachange) {
|
|
182
189
|
ondatachange({
|
|
183
190
|
type: 'load',
|
|
@@ -185,28 +192,52 @@
|
|
|
185
192
|
filteredCount: cells.length
|
|
186
193
|
});
|
|
187
194
|
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Track previous values to avoid unnecessary updates
|
|
199
|
+
let prevCellsLength = 0;
|
|
200
|
+
let prevCellsFirstId: string | null = null;
|
|
201
|
+
let prevGroupBy: string | null = null;
|
|
202
|
+
let prevColumnPreset: string | null = null;
|
|
203
|
+
|
|
204
|
+
// Update table data when cells actually change (not just reference)
|
|
205
|
+
$effect(() => {
|
|
206
|
+
const currentLength = sortedCells?.length ?? 0;
|
|
207
|
+
const currentFirstId = sortedCells?.[0]?.id ?? null;
|
|
208
|
+
|
|
209
|
+
// Only update if length or first item changed (rough equality check)
|
|
210
|
+
if (isInitialized && table &&
|
|
211
|
+
(currentLength !== prevCellsLength || currentFirstId !== prevCellsFirstId)) {
|
|
212
|
+
prevCellsLength = currentLength;
|
|
213
|
+
prevCellsFirstId = currentFirstId;
|
|
214
|
+
table.replaceData(sortedCells);
|
|
215
|
+
ondatachange?.({
|
|
216
|
+
type: 'load',
|
|
217
|
+
rowCount: sortedCells.length,
|
|
218
|
+
filteredCount: sortedCells.length
|
|
219
|
+
});
|
|
188
220
|
}
|
|
189
221
|
});
|
|
190
222
|
|
|
191
223
|
// Update grouping when groupBy changes
|
|
192
224
|
$effect(() => {
|
|
193
|
-
if (isInitialized && table) {
|
|
225
|
+
if (isInitialized && table && groupBy !== prevGroupBy) {
|
|
226
|
+
prevGroupBy = groupBy;
|
|
194
227
|
if (groupBy === 'none') {
|
|
195
228
|
table.setGroupBy(false);
|
|
196
229
|
} else {
|
|
197
230
|
table.setGroupBy(groupBy);
|
|
198
231
|
table.setGroupHeader(getGroupHeaderFormatter(groupBy));
|
|
199
232
|
}
|
|
200
|
-
// Force redraw after grouping change
|
|
201
|
-
table.redraw(true);
|
|
202
233
|
}
|
|
203
234
|
});
|
|
204
235
|
|
|
205
236
|
// Update columns when preset changes
|
|
206
237
|
$effect(() => {
|
|
207
|
-
if (isInitialized && table &&
|
|
238
|
+
if (isInitialized && table && columnPreset !== prevColumnPreset) {
|
|
239
|
+
prevColumnPreset = columnPreset;
|
|
208
240
|
table.setColumns(columns);
|
|
209
|
-
table.redraw(true);
|
|
210
241
|
}
|
|
211
242
|
});
|
|
212
243
|
|
|
@@ -256,12 +287,63 @@
|
|
|
256
287
|
}
|
|
257
288
|
|
|
258
289
|
export function clearFilters(): void {
|
|
259
|
-
table
|
|
290
|
+
if (!table) return;
|
|
291
|
+
// Clear programmatic filters
|
|
292
|
+
table.clearFilter();
|
|
293
|
+
// Clear header filter inputs
|
|
294
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
295
|
+
(table as any).clearHeaderFilter();
|
|
260
296
|
}
|
|
261
297
|
|
|
262
298
|
export function redraw(): void {
|
|
263
299
|
table?.redraw(true);
|
|
264
300
|
}
|
|
301
|
+
|
|
302
|
+
export function collapseAll(): void {
|
|
303
|
+
if (!table) return;
|
|
304
|
+
// Use setGroupStartOpen to collapse all groups, then refresh data to apply
|
|
305
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
306
|
+
(table as any).setGroupStartOpen(false);
|
|
307
|
+
table.setData(table.getData());
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export function expandAll(): void {
|
|
311
|
+
if (!table) return;
|
|
312
|
+
// Use setGroupStartOpen to expand all groups, then refresh data to apply
|
|
313
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
314
|
+
(table as any).setGroupStartOpen(true);
|
|
315
|
+
table.setData(table.getData());
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export function toggleHeaderFilters(visible: boolean): void {
|
|
319
|
+
if (!table) return;
|
|
320
|
+
const headerFiltersElement = tableContainer.querySelector('.tabulator-header-filter');
|
|
321
|
+
if (headerFiltersElement) {
|
|
322
|
+
// Toggle all header filter rows
|
|
323
|
+
const filterRows = tableContainer.querySelectorAll('.tabulator-col .tabulator-header-filter');
|
|
324
|
+
filterRows.forEach(el => {
|
|
325
|
+
(el as HTMLElement).style.display = visible ? '' : 'none';
|
|
326
|
+
});
|
|
327
|
+
table.redraw();
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export function showColumn(field: string): void {
|
|
332
|
+
table?.showColumn(field);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export function hideColumn(field: string): void {
|
|
336
|
+
table?.hideColumn(field);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export function getVisibleColumns(): string[] {
|
|
340
|
+
if (!table) return [];
|
|
341
|
+
const columns = table.getColumns();
|
|
342
|
+
return columns
|
|
343
|
+
.filter(col => col.isVisible())
|
|
344
|
+
.map(col => col.getField())
|
|
345
|
+
.filter((field): field is string => !!field);
|
|
346
|
+
}
|
|
265
347
|
</script>
|
|
266
348
|
|
|
267
349
|
<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
|
-
//
|
|
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
|
-
|
|
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;
|
|
@@ -49,6 +49,25 @@ export declare function azimuthFormatter(cell: CellComponent): string;
|
|
|
49
49
|
* Height formatter with meter unit
|
|
50
50
|
*/
|
|
51
51
|
export declare function heightFormatter(cell: CellComponent): string;
|
|
52
|
+
/**
|
|
53
|
+
* Custom sorter for fband - extracts numeric portion and sorts numerically
|
|
54
|
+
* Examples: LTE700 → 700, GSM900 → 900, LTE1800 → 1800, 5G-3500 → 3500
|
|
55
|
+
*/
|
|
56
|
+
export declare function fbandSorter(a: string, b: string): number;
|
|
57
|
+
/**
|
|
58
|
+
* Custom sorter for cellName - sorts by the 5th character (sector digit)
|
|
59
|
+
* Example: 10001 → '1', 10002 → '2', 10003 → '3'
|
|
60
|
+
*/
|
|
61
|
+
export declare function cellNameSectorSorter(a: string, b: string): number;
|
|
62
|
+
/**
|
|
63
|
+
* Combined multi-level sorter for cell data
|
|
64
|
+
* Sort order: tech (asc) → fband by numeric value (asc) → cellName by 5th digit (asc)
|
|
65
|
+
*/
|
|
66
|
+
export declare function cellDataSorter<T extends {
|
|
67
|
+
tech?: string;
|
|
68
|
+
fband?: string;
|
|
69
|
+
cellName?: string;
|
|
70
|
+
}>(a: T, b: T): number;
|
|
52
71
|
/**
|
|
53
72
|
* Get all column definitions
|
|
54
73
|
*/
|
|
@@ -61,3 +80,19 @@ export declare function getColumnsForPreset(preset: ColumnPreset, techColors?: T
|
|
|
61
80
|
* Get group header formatter for a specific field
|
|
62
81
|
*/
|
|
63
82
|
export declare function getGroupHeaderFormatter(groupField: string): (value: unknown, count: number) => string;
|
|
83
|
+
/**
|
|
84
|
+
* Column metadata for the column picker
|
|
85
|
+
*/
|
|
86
|
+
export interface ColumnMeta {
|
|
87
|
+
field: string;
|
|
88
|
+
title: string;
|
|
89
|
+
group: string;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Get column metadata for the column picker UI
|
|
93
|
+
*/
|
|
94
|
+
export declare function getColumnMetadata(): ColumnMeta[];
|
|
95
|
+
/**
|
|
96
|
+
* Get default visible columns for a preset
|
|
97
|
+
*/
|
|
98
|
+
export declare function getPresetVisibleFields(preset: ColumnPreset): string[];
|
|
@@ -123,6 +123,46 @@ export function heightFormatter(cell) {
|
|
|
123
123
|
return '';
|
|
124
124
|
return `${value}m`;
|
|
125
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Custom sorter for fband - extracts numeric portion and sorts numerically
|
|
128
|
+
* Examples: LTE700 → 700, GSM900 → 900, LTE1800 → 1800, 5G-3500 → 3500
|
|
129
|
+
*/
|
|
130
|
+
export function fbandSorter(a, b) {
|
|
131
|
+
const numA = parseInt(a.replace(/\D/g, ''), 10) || 0;
|
|
132
|
+
const numB = parseInt(b.replace(/\D/g, ''), 10) || 0;
|
|
133
|
+
return numA - numB;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Custom sorter for cellName - sorts by the 5th character (sector digit)
|
|
137
|
+
* Example: 10001 → '1', 10002 → '2', 10003 → '3'
|
|
138
|
+
*/
|
|
139
|
+
export function cellNameSectorSorter(a, b) {
|
|
140
|
+
const charA = a.charAt(4) || '0';
|
|
141
|
+
const charB = b.charAt(4) || '0';
|
|
142
|
+
return charA.localeCompare(charB);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Combined multi-level sorter for cell data
|
|
146
|
+
* Sort order: tech (asc) → fband by numeric value (asc) → cellName by 5th digit (asc)
|
|
147
|
+
*/
|
|
148
|
+
export function cellDataSorter(a, b) {
|
|
149
|
+
// 1. Sort by tech (string comparison)
|
|
150
|
+
const techA = String(a.tech || '');
|
|
151
|
+
const techB = String(b.tech || '');
|
|
152
|
+
const techCompare = techA.localeCompare(techB);
|
|
153
|
+
if (techCompare !== 0)
|
|
154
|
+
return techCompare;
|
|
155
|
+
// 2. Sort by fband (numeric extraction)
|
|
156
|
+
const fbandA = String(a.fband || '');
|
|
157
|
+
const fbandB = String(b.fband || '');
|
|
158
|
+
const fbandCompare = fbandSorter(fbandA, fbandB);
|
|
159
|
+
if (fbandCompare !== 0)
|
|
160
|
+
return fbandCompare;
|
|
161
|
+
// 3. Sort by cellName 5th character (sector)
|
|
162
|
+
const cellNameA = String(a.cellName || '');
|
|
163
|
+
const cellNameB = String(b.cellName || '');
|
|
164
|
+
return cellNameSectorSorter(cellNameA, cellNameB);
|
|
165
|
+
}
|
|
126
166
|
/**
|
|
127
167
|
* Get all column definitions
|
|
128
168
|
*/
|
|
@@ -463,3 +503,75 @@ export function getGroupHeaderFormatter(groupField) {
|
|
|
463
503
|
<span class="text-muted">(${count} cell${count !== 1 ? 's' : ''})</span>`;
|
|
464
504
|
};
|
|
465
505
|
}
|
|
506
|
+
/**
|
|
507
|
+
* Get column metadata for the column picker UI
|
|
508
|
+
*/
|
|
509
|
+
export function getColumnMetadata() {
|
|
510
|
+
return [
|
|
511
|
+
// Core
|
|
512
|
+
{ field: 'id', title: 'ID', group: 'Core' },
|
|
513
|
+
{ field: 'cellName', title: 'Cell Name', group: 'Core' },
|
|
514
|
+
{ field: 'siteId', title: 'Site ID', group: 'Core' },
|
|
515
|
+
{ field: 'tech', title: 'Technology', group: 'Core' },
|
|
516
|
+
{ field: 'fband', title: 'Band', group: 'Core' },
|
|
517
|
+
{ field: 'frq', title: 'Frequency', group: 'Core' },
|
|
518
|
+
{ field: 'status', title: 'Status', group: 'Core' },
|
|
519
|
+
{ field: 'type', title: 'Type', group: 'Core' },
|
|
520
|
+
{ field: 'onAirDate', title: 'On Air Date', group: 'Core' },
|
|
521
|
+
// Physical
|
|
522
|
+
{ field: 'antenna', title: 'Antenna', group: 'Physical' },
|
|
523
|
+
{ field: 'azimuth', title: 'Azimuth', group: 'Physical' },
|
|
524
|
+
{ field: 'height', title: 'Height', group: 'Physical' },
|
|
525
|
+
{ field: 'electricalTilt', title: 'E-Tilt', group: 'Physical' },
|
|
526
|
+
{ field: 'beamwidth', title: 'Beamwidth', group: 'Physical' },
|
|
527
|
+
// Network
|
|
528
|
+
{ field: 'dlEarfn', title: 'DL EARFCN', group: 'Network' },
|
|
529
|
+
{ field: 'bcch', title: 'BCCH', group: 'Network' },
|
|
530
|
+
{ field: 'pci', title: 'PCI', group: 'Network' },
|
|
531
|
+
{ field: 'rru', title: 'RRU', group: 'Network' },
|
|
532
|
+
{ field: 'cellID', title: 'Cell ID', group: 'Network' },
|
|
533
|
+
{ field: 'cellId2G', title: 'Cell ID 2G', group: 'Network' },
|
|
534
|
+
{ field: 'txId', title: 'TX ID', group: 'Network' },
|
|
535
|
+
{ field: 'ctrlid', title: 'Ctrl ID', group: 'Network' },
|
|
536
|
+
{ field: 'nwtET', title: 'NWT ET', group: 'Network' },
|
|
537
|
+
{ field: 'nwtPW', title: 'NWT PW', group: 'Network' },
|
|
538
|
+
{ field: 'nwtRS', title: 'NWT RS', group: 'Network' },
|
|
539
|
+
{ field: 'nwtBW', title: 'NWT BW', group: 'Network' },
|
|
540
|
+
// Atoll
|
|
541
|
+
{ field: 'atollET', title: 'Atoll ET', group: 'Atoll' },
|
|
542
|
+
{ field: 'atollPW', title: 'Atoll PW', group: 'Atoll' },
|
|
543
|
+
{ field: 'atollRS', title: 'Atoll RS', group: 'Atoll' },
|
|
544
|
+
{ field: 'atollBW', title: 'Atoll BW', group: 'Atoll' },
|
|
545
|
+
// Position
|
|
546
|
+
{ field: 'latitude', title: 'Latitude', group: 'Position' },
|
|
547
|
+
{ field: 'longitude', title: 'Longitude', group: 'Position' },
|
|
548
|
+
{ field: 'siteLatitude', title: 'Site Latitude', group: 'Position' },
|
|
549
|
+
{ field: 'siteLongitude', title: 'Site Longitude', group: 'Position' },
|
|
550
|
+
{ field: 'dx', title: 'DX', group: 'Position' },
|
|
551
|
+
{ field: 'dy', title: 'DY', group: 'Position' },
|
|
552
|
+
// Planning
|
|
553
|
+
{ field: 'planner', title: 'Planner', group: 'Planning' },
|
|
554
|
+
{ field: 'comment', title: 'Comment', group: 'Planning' },
|
|
555
|
+
{ field: 'customSubgroup', title: 'Subgroup', group: 'Planning' },
|
|
556
|
+
];
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Get default visible columns for a preset
|
|
560
|
+
*/
|
|
561
|
+
export function getPresetVisibleFields(preset) {
|
|
562
|
+
switch (preset) {
|
|
563
|
+
case 'compact':
|
|
564
|
+
return ['cellName', 'siteId', 'tech', 'fband', 'status'];
|
|
565
|
+
case 'full':
|
|
566
|
+
return getColumnMetadata().map(c => c.field);
|
|
567
|
+
case 'physical':
|
|
568
|
+
return [...COLUMN_GROUPS.core, ...COLUMN_GROUPS.physical];
|
|
569
|
+
case 'network':
|
|
570
|
+
return [...COLUMN_GROUPS.core, ...COLUMN_GROUPS.network];
|
|
571
|
+
case 'planning':
|
|
572
|
+
return [...COLUMN_GROUPS.core, ...COLUMN_GROUPS.planning];
|
|
573
|
+
case 'default':
|
|
574
|
+
default:
|
|
575
|
+
return ['id', 'cellName', 'siteId', 'tech', 'fband', 'frq', 'status', 'azimuth', 'height', 'antenna'];
|
|
576
|
+
}
|
|
577
|
+
}
|
|
@@ -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, fbandSorter, cellNameSectorSorter, cellDataSorter, 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, fbandSorter, cellNameSectorSorter, cellDataSorter, 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 >
|
|
120
|
-
const txPower = cell.atollPW || cell.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
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
|
-
|
|
39
|
-
atollPW
|
|
40
|
-
atollRS
|
|
41
|
-
atollBW
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
nwtRS
|
|
47
|
-
nwtBW
|
|
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
|
}
|