@smartnet360/svelte-components 0.0.131 → 0.0.133
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/CellHistoryDemo.svelte +122 -65
- package/dist/core/CellTable/CellTablePanel.svelte +10 -1
- package/dist/core/CellTable/CellTablePanel.svelte.d.ts +5 -1
- package/dist/core/CellTable/CellTableToolbar.svelte +10 -4
- package/dist/core/CellTable/CellTableToolbar.svelte.d.ts +3 -1
- package/dist/core/CellTable/column-config.d.ts +12 -0
- package/dist/core/CellTable/column-config.js +87 -2
- package/dist/core/CellTable/history-api-helper.d.ts +79 -0
- package/dist/core/CellTable/history-api-helper.js +83 -0
- package/dist/core/CellTable/index.d.ts +3 -2
- package/dist/core/CellTable/index.js +3 -1
- package/dist/core/CellTable/types.d.ts +37 -0
- package/package.json +1 -1
|
@@ -1,68 +1,114 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import CellTablePanel from './CellTablePanel.svelte';
|
|
3
|
-
import type { RowSelectionEvent,
|
|
3
|
+
import type { RowSelectionEvent, CellHistoryData } from './types';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (Math.random() > 0.6) {
|
|
29
|
-
currentET = Math.floor(Math.random() * 10);
|
|
30
|
-
}
|
|
31
|
-
if (Math.random() > 0.8) {
|
|
32
|
-
currentMT = Math.floor(Math.random() * 6);
|
|
33
|
-
}
|
|
34
|
-
if (Math.random() > 0.7) {
|
|
35
|
-
currentPW = 15 + Math.floor(Math.random() * 15);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const date = new Date(baseDate);
|
|
39
|
-
date.setDate(date.getDate() - i * 7); // Weekly snapshots going back
|
|
40
|
-
|
|
41
|
-
history.push({
|
|
42
|
-
id: `hist-${i}`,
|
|
43
|
-
cellName: cellName,
|
|
44
|
-
configDate: date.toLocaleDateString('en-GB', {
|
|
45
|
-
day: '2-digit',
|
|
46
|
-
month: '2-digit',
|
|
47
|
-
year: 'numeric',
|
|
48
|
-
hour: '2-digit',
|
|
49
|
-
minute: '2-digit'
|
|
50
|
-
}),
|
|
51
|
-
antenna: currentAntenna,
|
|
52
|
-
atollET: currentET,
|
|
53
|
-
atollMT: currentMT,
|
|
54
|
-
atollPW: currentPW,
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return history;
|
|
5
|
+
/**
|
|
6
|
+
* Table-ready history record after transformation
|
|
7
|
+
*/
|
|
8
|
+
interface HistoryTableRow {
|
|
9
|
+
id: string;
|
|
10
|
+
cellName: string;
|
|
11
|
+
frequency: number;
|
|
12
|
+
configDate: string;
|
|
13
|
+
// Current values
|
|
14
|
+
antenna: string;
|
|
15
|
+
electricalTilt: number;
|
|
16
|
+
mechanicalTilt: number;
|
|
17
|
+
power: number;
|
|
18
|
+
// Previous values for change comparison
|
|
19
|
+
prevAntenna: string;
|
|
20
|
+
prevElectricalTilt: number;
|
|
21
|
+
prevMechanicalTilt: number;
|
|
22
|
+
prevPower: number;
|
|
23
|
+
// Virtual fields for the change columns
|
|
24
|
+
antennaChange: string;
|
|
25
|
+
etiltChange: number;
|
|
26
|
+
mtiltChange: number;
|
|
27
|
+
powerChange: number;
|
|
59
28
|
}
|
|
60
29
|
|
|
61
|
-
|
|
62
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Transform API history data to table-friendly format
|
|
32
|
+
* Maps UPPER_CASE API fields to camelCase and adds virtual fields for change detection
|
|
33
|
+
*/
|
|
34
|
+
function transformHistoryData(apiData: CellHistoryData[]): HistoryTableRow[] {
|
|
35
|
+
return apiData.map((record, index) => ({
|
|
36
|
+
id: `hist-${index}`,
|
|
37
|
+
cellName: record.CELLNAME,
|
|
38
|
+
frequency: record.FREQUENCY,
|
|
39
|
+
configDate: record.CONFIG_DATE,
|
|
40
|
+
// Current values
|
|
41
|
+
antenna: record.ANTENNA_TYPE,
|
|
42
|
+
electricalTilt: record.ELECTRONIC_TILT,
|
|
43
|
+
mechanicalTilt: record.MECHANIC_TILT,
|
|
44
|
+
power: record.ANTOUTPUTPWR,
|
|
45
|
+
// Previous values for change comparison
|
|
46
|
+
prevAntenna: record.PREV_ANTENNA_TYPE,
|
|
47
|
+
prevElectricalTilt: record.PREV_ELECTRONIC_TILT,
|
|
48
|
+
prevMechanicalTilt: record.PREV_MECHANIC_TILT,
|
|
49
|
+
prevPower: record.PREV_ANTOUTPUTPWR,
|
|
50
|
+
// Virtual fields for the change columns (needed for column filtering)
|
|
51
|
+
antennaChange: record.ANTENNA_TYPE,
|
|
52
|
+
etiltChange: record.ELECTRONIC_TILT,
|
|
53
|
+
mtiltChange: record.MECHANIC_TILT,
|
|
54
|
+
powerChange: record.ANTOUTPUTPWR,
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Example API data for cell configuration history
|
|
60
|
+
*/
|
|
61
|
+
const EXAMPLE_HISTORY_DATA: CellHistoryData[] = [
|
|
62
|
+
{
|
|
63
|
+
CELLNAME: '1001141',
|
|
64
|
+
FREQUENCY: 1800,
|
|
65
|
+
CONFIG_DATE: '2025-12-03T23:00:00.000Z',
|
|
66
|
+
ANTENNA_TYPE: 'AQU4521R01v06',
|
|
67
|
+
PREV_ANTENNA_TYPE: 'AQU4521R01v06',
|
|
68
|
+
ANTOUTPUTPWR: 20,
|
|
69
|
+
PREV_ANTOUTPUTPWR: 20,
|
|
70
|
+
MECHANIC_TILT: 1,
|
|
71
|
+
PREV_MECHANIC_TILT: 1,
|
|
72
|
+
ELECTRONIC_TILT: 10,
|
|
73
|
+
PREV_ELECTRONIC_TILT: 9
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
CELLNAME: '1001141',
|
|
77
|
+
FREQUENCY: 1800,
|
|
78
|
+
CONFIG_DATE: '2025-09-11T22:00:00.000Z',
|
|
79
|
+
ANTENNA_TYPE: 'AQU4521R01v06',
|
|
80
|
+
PREV_ANTENNA_TYPE: 'ADU451602v01',
|
|
81
|
+
ANTOUTPUTPWR: 20,
|
|
82
|
+
PREV_ANTOUTPUTPWR: 20,
|
|
83
|
+
MECHANIC_TILT: 1,
|
|
84
|
+
PREV_MECHANIC_TILT: 0,
|
|
85
|
+
ELECTRONIC_TILT: 9,
|
|
86
|
+
PREV_ELECTRONIC_TILT: 10
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
CELLNAME: '1001141',
|
|
90
|
+
FREQUENCY: 1800,
|
|
91
|
+
CONFIG_DATE: '2021-02-15T23:00:00.000Z',
|
|
92
|
+
ANTENNA_TYPE: 'ADU451602v01',
|
|
93
|
+
PREV_ANTENNA_TYPE: 'ADU451602v01',
|
|
94
|
+
ANTOUTPUTPWR: 20,
|
|
95
|
+
PREV_ANTOUTPUTPWR: 20,
|
|
96
|
+
MECHANIC_TILT: 0,
|
|
97
|
+
PREV_MECHANIC_TILT: 2,
|
|
98
|
+
ELECTRONIC_TILT: 10,
|
|
99
|
+
PREV_ELECTRONIC_TILT: 10
|
|
100
|
+
}
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
let cellName = $state('1001141');
|
|
104
|
+
let rawHistoryData = $state<CellHistoryData[]>(EXAMPLE_HISTORY_DATA);
|
|
105
|
+
let historyData = $derived(transformHistoryData(rawHistoryData));
|
|
63
106
|
|
|
64
107
|
function loadHistory() {
|
|
65
|
-
|
|
108
|
+
// TODO: Replace with actual API call
|
|
109
|
+
// For now, just reload the example data
|
|
110
|
+
console.log(`Loading history for cell: ${cellName}`);
|
|
111
|
+
rawHistoryData = EXAMPLE_HISTORY_DATA.filter(r => r.CELLNAME === cellName);
|
|
66
112
|
}
|
|
67
113
|
|
|
68
114
|
function handleSelectionChange(event: RowSelectionEvent) {
|
|
@@ -72,20 +118,24 @@
|
|
|
72
118
|
/**
|
|
73
119
|
* Compare two selected history records
|
|
74
120
|
*/
|
|
75
|
-
function handleCompareTwoRecords(record1:
|
|
121
|
+
function handleCompareTwoRecords(record1: HistoryTableRow, record2: HistoryTableRow) {
|
|
76
122
|
const params = new URLSearchParams();
|
|
77
123
|
|
|
78
124
|
// Use antenna from both records
|
|
79
125
|
if (record1.antenna) params.set('ant1', record1.antenna);
|
|
80
126
|
if (record2.antenna) params.set('ant2', record2.antenna);
|
|
81
127
|
|
|
128
|
+
// Use frequency (same for both records typically)
|
|
129
|
+
params.set('freq1', String(record1.frequency || 1800));
|
|
130
|
+
params.set('freq2', String(record2.frequency || 1800));
|
|
131
|
+
|
|
82
132
|
// Use electrical tilt values
|
|
83
|
-
params.set('etilt1', String(record1.
|
|
84
|
-
params.set('etilt2', String(record2.
|
|
133
|
+
params.set('etilt1', String(record1.electricalTilt || 0));
|
|
134
|
+
params.set('etilt2', String(record2.electricalTilt || 0));
|
|
85
135
|
|
|
86
136
|
// Use mechanical tilt values
|
|
87
|
-
params.set('mtilt1', String(record1.
|
|
88
|
-
params.set('mtilt2', String(record2.
|
|
137
|
+
params.set('mtilt1', String(record1.mechanicalTilt || 0));
|
|
138
|
+
params.set('mtilt2', String(record2.mechanicalTilt || 0));
|
|
89
139
|
|
|
90
140
|
// Add config date as labels
|
|
91
141
|
params.set('label1', `${record1.cellName} (${record1.configDate})`);
|
|
@@ -122,9 +172,16 @@
|
|
|
122
172
|
<!-- CellTablePanel with history preset -->
|
|
123
173
|
<div class="flex-grow-1 overflow-hidden">
|
|
124
174
|
<CellTablePanel
|
|
125
|
-
cells={historyData as CellData[]}
|
|
175
|
+
cells={historyData as unknown as import('./types').CellData[]}
|
|
126
176
|
groupBy="none"
|
|
177
|
+
groupOptions={[
|
|
178
|
+
{ value: 'none', label: 'No Grouping' },
|
|
179
|
+
{ value: 'antenna', label: 'Antenna Type' },
|
|
180
|
+
{ value: 'cellname', label: 'Cell Name' },
|
|
181
|
+
|
|
182
|
+
]}
|
|
127
183
|
columnPreset="history"
|
|
184
|
+
showColumnPresets={false}
|
|
128
185
|
selectable={true}
|
|
129
186
|
multiSelect={true}
|
|
130
187
|
showToolbar={true}
|
|
@@ -150,7 +207,7 @@
|
|
|
150
207
|
class="btn btn-sm btn-outline-success"
|
|
151
208
|
disabled={selectedCount !== 2}
|
|
152
209
|
title={selectedCount === 2 ? 'Compare antenna patterns for selected dates' : 'Select exactly 2 records to compare'}
|
|
153
|
-
onclick={() => handleCompareTwoRecords(selectedRows[0], selectedRows[1])}
|
|
210
|
+
onclick={() => handleCompareTwoRecords(selectedRows[0] as unknown as HistoryTableRow, selectedRows[1] as unknown as HistoryTableRow)}
|
|
154
211
|
>
|
|
155
212
|
<i class="bi bi-broadcast-pin"></i>
|
|
156
213
|
<span class="d-none d-sm-inline ms-1">Compare Antennas</span>
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
RowContextMenuEvent,
|
|
14
14
|
DataChangeEvent,
|
|
15
15
|
TechColorMap,
|
|
16
|
-
StatusColorMap
|
|
16
|
+
StatusColorMap,
|
|
17
|
+
GroupOption
|
|
17
18
|
} from './types';
|
|
18
19
|
|
|
19
20
|
interface Props {
|
|
@@ -21,6 +22,8 @@
|
|
|
21
22
|
cells: CellData[];
|
|
22
23
|
/** Initial grouping field */
|
|
23
24
|
groupBy?: CellTableGroupField;
|
|
25
|
+
/** Custom grouping options (overrides default tech/fband/status options) */
|
|
26
|
+
groupOptions?: GroupOption[];
|
|
24
27
|
/** Initial column preset */
|
|
25
28
|
columnPreset?: ColumnPreset;
|
|
26
29
|
/** Enable row selection */
|
|
@@ -31,6 +34,8 @@
|
|
|
31
34
|
height?: string;
|
|
32
35
|
/** Show toolbar */
|
|
33
36
|
showToolbar?: boolean;
|
|
37
|
+
/** Show column presets dropdown and column picker (set false for simple tables) */
|
|
38
|
+
showColumnPresets?: boolean;
|
|
34
39
|
/** Show export buttons */
|
|
35
40
|
showExport?: boolean;
|
|
36
41
|
/** Show JSON export button (requires showExport=true) */
|
|
@@ -76,11 +81,13 @@
|
|
|
76
81
|
let {
|
|
77
82
|
cells = [],
|
|
78
83
|
groupBy = $bindable('none'),
|
|
84
|
+
groupOptions,
|
|
79
85
|
columnPreset = $bindable('default'),
|
|
80
86
|
selectable = true,
|
|
81
87
|
multiSelect = true,
|
|
82
88
|
height = '100%',
|
|
83
89
|
showToolbar = true,
|
|
90
|
+
showColumnPresets = true,
|
|
84
91
|
showExport = true,
|
|
85
92
|
showJsonExport = false,
|
|
86
93
|
techColors,
|
|
@@ -435,12 +442,14 @@
|
|
|
435
442
|
{#if showToolbar}
|
|
436
443
|
<CellTableToolbar
|
|
437
444
|
{groupBy}
|
|
445
|
+
{groupOptions}
|
|
438
446
|
{columnPreset}
|
|
439
447
|
totalCount={cells.length}
|
|
440
448
|
{filteredCount}
|
|
441
449
|
{selectedCount}
|
|
442
450
|
{showExport}
|
|
443
451
|
{showJsonExport}
|
|
452
|
+
showPresets={showColumnPresets}
|
|
444
453
|
ongroupchange={handleGroupChange}
|
|
445
454
|
onpresetchange={handlePresetChange}
|
|
446
455
|
onexportcsv={handleExportCSV}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
|
-
import type { CellData, CellTableGroupField, ColumnPreset, RowSelectionEvent, RowClickEvent, RowDblClickEvent, TechColorMap, StatusColorMap } from './types';
|
|
2
|
+
import type { CellData, CellTableGroupField, ColumnPreset, RowSelectionEvent, RowClickEvent, RowDblClickEvent, TechColorMap, StatusColorMap, GroupOption } from './types';
|
|
3
3
|
interface Props {
|
|
4
4
|
/** Cell data array to display */
|
|
5
5
|
cells: CellData[];
|
|
6
6
|
/** Initial grouping field */
|
|
7
7
|
groupBy?: CellTableGroupField;
|
|
8
|
+
/** Custom grouping options (overrides default tech/fband/status options) */
|
|
9
|
+
groupOptions?: GroupOption[];
|
|
8
10
|
/** Initial column preset */
|
|
9
11
|
columnPreset?: ColumnPreset;
|
|
10
12
|
/** Enable row selection */
|
|
@@ -15,6 +17,8 @@ interface Props {
|
|
|
15
17
|
height?: string;
|
|
16
18
|
/** Show toolbar */
|
|
17
19
|
showToolbar?: boolean;
|
|
20
|
+
/** Show column presets dropdown and column picker (set false for simple tables) */
|
|
21
|
+
showColumnPresets?: boolean;
|
|
18
22
|
/** Show export buttons */
|
|
19
23
|
showExport?: boolean;
|
|
20
24
|
/** Show JSON export button (requires showExport=true) */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type { CellTableGroupField, ColumnPreset } from './types';
|
|
2
|
+
import type { CellTableGroupField, ColumnPreset, GroupOption } from './types';
|
|
3
3
|
import type { ColumnMeta } from './column-config';
|
|
4
4
|
import ColumnPicker from './ColumnPicker.svelte';
|
|
5
5
|
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
showJsonExport?: boolean;
|
|
21
21
|
/** Show grouping dropdown */
|
|
22
22
|
showGrouping?: boolean;
|
|
23
|
+
/** Custom grouping options (overrides default options) */
|
|
24
|
+
groupOptions?: GroupOption[];
|
|
23
25
|
/** Show preset dropdown */
|
|
24
26
|
showPresets?: boolean;
|
|
25
27
|
/** Group change event */
|
|
@@ -65,6 +67,7 @@
|
|
|
65
67
|
showExport = true,
|
|
66
68
|
showJsonExport = false,
|
|
67
69
|
showGrouping = true,
|
|
70
|
+
groupOptions: customGroupOptions,
|
|
68
71
|
showPresets = true,
|
|
69
72
|
ongroupchange,
|
|
70
73
|
onpresetchange,
|
|
@@ -84,7 +87,7 @@
|
|
|
84
87
|
ontogglescrollspy
|
|
85
88
|
}: Props = $props();
|
|
86
89
|
|
|
87
|
-
const
|
|
90
|
+
const defaultGroupOptions: GroupOption[] = [
|
|
88
91
|
{ value: 'none', label: 'No Grouping' },
|
|
89
92
|
{ value: 'tech', label: 'Technology' },
|
|
90
93
|
{ value: 'fband', label: 'Frequency Band' },
|
|
@@ -93,6 +96,9 @@
|
|
|
93
96
|
{ value: 'siteId', label: 'Site ID' }
|
|
94
97
|
];
|
|
95
98
|
|
|
99
|
+
// Use custom options if provided, otherwise use defaults
|
|
100
|
+
const groupOptions = customGroupOptions ?? defaultGroupOptions;
|
|
101
|
+
|
|
96
102
|
const presetOptions: { value: ColumnPreset; label: string }[] = [
|
|
97
103
|
{ value: 'default', label: 'Default' },
|
|
98
104
|
{ value: 'compact', label: 'Compact' },
|
|
@@ -105,8 +111,8 @@
|
|
|
105
111
|
];
|
|
106
112
|
|
|
107
113
|
function handleGroupChange(e: Event) {
|
|
108
|
-
const value = (e.target as HTMLSelectElement).value
|
|
109
|
-
ongroupchange?.(value);
|
|
114
|
+
const value = (e.target as HTMLSelectElement).value;
|
|
115
|
+
ongroupchange?.(value as CellTableGroupField);
|
|
110
116
|
}
|
|
111
117
|
|
|
112
118
|
function handlePresetChange(e: Event) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CellTableGroupField, ColumnPreset } from './types';
|
|
1
|
+
import type { CellTableGroupField, ColumnPreset, GroupOption } from './types';
|
|
2
2
|
import type { ColumnMeta } from './column-config';
|
|
3
3
|
interface Props {
|
|
4
4
|
/** Current grouping field */
|
|
@@ -17,6 +17,8 @@ interface Props {
|
|
|
17
17
|
showJsonExport?: boolean;
|
|
18
18
|
/** Show grouping dropdown */
|
|
19
19
|
showGrouping?: boolean;
|
|
20
|
+
/** Custom grouping options (overrides default options) */
|
|
21
|
+
groupOptions?: GroupOption[];
|
|
20
22
|
/** Show preset dropdown */
|
|
21
23
|
showPresets?: boolean;
|
|
22
24
|
/** Group change event */
|
|
@@ -61,6 +61,18 @@ export declare function heightFormatter(cell: CellComponent): string;
|
|
|
61
61
|
* - Gray: one or both values missing
|
|
62
62
|
*/
|
|
63
63
|
export declare function createCompareFormatter(atollField: string, nwtField: string): (cell: CellComponent) => string;
|
|
64
|
+
/**
|
|
65
|
+
* Create a history change formatter for showing current vs previous values
|
|
66
|
+
* Highlights changes in yellow/amber, unchanged values in muted style
|
|
67
|
+
*
|
|
68
|
+
* @param currentField - Field name for current value
|
|
69
|
+
* @param prevField - Field name for previous value
|
|
70
|
+
*/
|
|
71
|
+
export declare function createHistoryChangeFormatter(currentField: string, prevField: string): (cell: CellComponent) => string;
|
|
72
|
+
/**
|
|
73
|
+
* Format ISO date string to YYYY.MM.DD format for history display
|
|
74
|
+
*/
|
|
75
|
+
export declare function historyDateFormatter(cell: CellComponent): string;
|
|
64
76
|
/**
|
|
65
77
|
* Creates a sparkline SVG formatter for KPI time-series data
|
|
66
78
|
* Renders a mini line chart showing trend over time
|
|
@@ -61,7 +61,7 @@ export const COLUMN_GROUPS = {
|
|
|
61
61
|
position: ['latitude', 'longitude', 'siteLatitude', 'siteLongitude', 'dx', 'dy'],
|
|
62
62
|
compare: ['compareET', 'comparePW', 'compareRS', 'compareBW'],
|
|
63
63
|
kpi: ['kpiTraffic', 'kpiThroughput', 'kpiAvailability', 'kpiSuccessRate'],
|
|
64
|
-
history: ['configDate', '
|
|
64
|
+
history: ['configDate', 'frequency', 'antennaChange', 'etiltChange', 'mtiltChange', 'powerChange'],
|
|
65
65
|
};
|
|
66
66
|
/**
|
|
67
67
|
* Create a technology badge formatter
|
|
@@ -168,6 +168,49 @@ export function createCompareFormatter(atollField, nwtField) {
|
|
|
168
168
|
return `<span class="badge" style="background-color: ${bgColor}; color: ${textColor}; font-size: 0.75rem; font-weight: normal;">${atollStr} | ${nwtStr}</span>`;
|
|
169
169
|
};
|
|
170
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Create a history change formatter for showing current vs previous values
|
|
173
|
+
* Highlights changes in yellow/amber, unchanged values in muted style
|
|
174
|
+
*
|
|
175
|
+
* @param currentField - Field name for current value
|
|
176
|
+
* @param prevField - Field name for previous value
|
|
177
|
+
*/
|
|
178
|
+
export function createHistoryChangeFormatter(currentField, prevField) {
|
|
179
|
+
return (cell) => {
|
|
180
|
+
const row = cell.getRow().getData();
|
|
181
|
+
const currentVal = row[currentField];
|
|
182
|
+
const prevVal = row[prevField];
|
|
183
|
+
const currentStr = currentVal !== null && currentVal !== undefined ? String(currentVal) : '-';
|
|
184
|
+
const prevStr = prevVal !== null && prevVal !== undefined ? String(prevVal) : '-';
|
|
185
|
+
const hasChanged = currentVal !== prevVal;
|
|
186
|
+
if (hasChanged) {
|
|
187
|
+
// Value changed - highlight with amber/warning color
|
|
188
|
+
return `<span class="badge bg-warning text-dark" style="font-size: 0.75rem; font-weight: 500;" title="Changed from: ${prevStr}">${currentStr}</span>`;
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
// No change - muted style
|
|
192
|
+
return `<span class="text-muted">${currentStr}</span>`;
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Format ISO date string to YYYY.MM.DD format for history display
|
|
198
|
+
*/
|
|
199
|
+
export function historyDateFormatter(cell) {
|
|
200
|
+
const value = cell.getValue();
|
|
201
|
+
if (!value)
|
|
202
|
+
return '-';
|
|
203
|
+
try {
|
|
204
|
+
const date = new Date(value);
|
|
205
|
+
const year = date.getFullYear();
|
|
206
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
207
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
208
|
+
return `${year}-${month}-${day}`;
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
return value;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
171
214
|
export function createSparklineFormatter(options = {}) {
|
|
172
215
|
const { width = 80, height = 24, lineColor = '#0d6efd', fillColor = 'rgba(13,110,253,0.2)', showDots = false, showLastValue = true, unit = '', decimals = 1 } = options;
|
|
173
216
|
return (cell) => {
|
|
@@ -331,9 +374,46 @@ export function getAllColumns(techColors = DEFAULT_TECH_COLORS, statusColors = D
|
|
|
331
374
|
{
|
|
332
375
|
title: 'Config Date',
|
|
333
376
|
field: 'configDate',
|
|
334
|
-
width:
|
|
377
|
+
width: 120,
|
|
378
|
+
formatter: historyDateFormatter,
|
|
379
|
+
...headerFilterParams,
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
title: 'Freq (MHz)',
|
|
383
|
+
field: 'frequency',
|
|
384
|
+
width: 100,
|
|
385
|
+
hozAlign: 'right',
|
|
386
|
+
...headerFilterParams,
|
|
387
|
+
},
|
|
388
|
+
// History change columns (current vs previous, highlights changes)
|
|
389
|
+
{
|
|
390
|
+
title: 'Antenna',
|
|
391
|
+
field: 'antennaChange',
|
|
392
|
+
width: 160,
|
|
393
|
+
formatter: createHistoryChangeFormatter('antenna', 'prevAntenna'),
|
|
335
394
|
...headerFilterParams,
|
|
336
395
|
},
|
|
396
|
+
{
|
|
397
|
+
title: 'E-Tilt',
|
|
398
|
+
field: 'etiltChange',
|
|
399
|
+
width: 80,
|
|
400
|
+
hozAlign: 'center',
|
|
401
|
+
formatter: createHistoryChangeFormatter('electricalTilt', 'prevElectricalTilt'),
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
title: 'M-Tilt',
|
|
405
|
+
field: 'mtiltChange',
|
|
406
|
+
width: 80,
|
|
407
|
+
hozAlign: 'center',
|
|
408
|
+
formatter: createHistoryChangeFormatter('mechanicalTilt', 'prevMechanicalTilt'),
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
title: 'Power',
|
|
412
|
+
field: 'powerChange',
|
|
413
|
+
width: 80,
|
|
414
|
+
hozAlign: 'center',
|
|
415
|
+
formatter: createHistoryChangeFormatter('power', 'prevPower'),
|
|
416
|
+
},
|
|
337
417
|
// Physical columns
|
|
338
418
|
{
|
|
339
419
|
title: 'Antenna',
|
|
@@ -748,6 +828,11 @@ export function getColumnMetadata() {
|
|
|
748
828
|
{ field: 'atollBW', title: 'Atoll BW', group: 'Atoll' },
|
|
749
829
|
// History
|
|
750
830
|
{ field: 'configDate', title: 'Config Date', group: 'History' },
|
|
831
|
+
{ field: 'frequency', title: 'Freq (MHz)', group: 'History' },
|
|
832
|
+
{ field: 'antennaChange', title: 'Antenna (Δ)', group: 'History' },
|
|
833
|
+
{ field: 'etiltChange', title: 'E-Tilt (Δ)', group: 'History' },
|
|
834
|
+
{ field: 'mtiltChange', title: 'M-Tilt (Δ)', group: 'History' },
|
|
835
|
+
{ field: 'powerChange', title: 'Power (Δ)', group: 'History' },
|
|
751
836
|
// Compare (Atoll vs Network)
|
|
752
837
|
{ field: 'compareET', title: 'Δ ET', group: 'Compare' },
|
|
753
838
|
{ field: 'comparePW', title: 'Δ PW', group: 'Compare' },
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cell History API Helper
|
|
3
|
+
*
|
|
4
|
+
* Transform functions for converting database records to frontend-ready format.
|
|
5
|
+
* Use these in your API endpoint before sending the response.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Raw database record format (Oracle/SQL style - UPPER_CASE)
|
|
9
|
+
*/
|
|
10
|
+
export interface CellHistoryDbRecord {
|
|
11
|
+
CELLNAME: string;
|
|
12
|
+
CONFIG_DATE: string | Date;
|
|
13
|
+
ANTENNA_TYPE: string;
|
|
14
|
+
PREV_ANTENNA_TYPE: string;
|
|
15
|
+
ANTOUTPUTPWR: number;
|
|
16
|
+
PREV_ANTOUTPUTPWR: number;
|
|
17
|
+
MECHANIC_TILT: number;
|
|
18
|
+
PREV_MECHANIC_TILT: number;
|
|
19
|
+
ELECTRONIC_TILT: number;
|
|
20
|
+
PREV_ELECTRONIC_TILT: number;
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Frontend-ready API response format (camelCase)
|
|
25
|
+
*/
|
|
26
|
+
export interface CellHistoryApiResponse {
|
|
27
|
+
cellName: string;
|
|
28
|
+
configDate: string;
|
|
29
|
+
antenna: string;
|
|
30
|
+
prevAntenna: string;
|
|
31
|
+
power: number;
|
|
32
|
+
prevPower: number;
|
|
33
|
+
mechanicalTilt: number;
|
|
34
|
+
prevMechanicalTilt: number;
|
|
35
|
+
electricalTilt: number;
|
|
36
|
+
prevElectricalTilt: number;
|
|
37
|
+
hasChanges: boolean;
|
|
38
|
+
changedFields: string[];
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Format a date to YYYY.MM.DD string
|
|
42
|
+
*/
|
|
43
|
+
export declare function formatConfigDate(date: string | Date): string;
|
|
44
|
+
/**
|
|
45
|
+
* Transform a single database record to API response format
|
|
46
|
+
*/
|
|
47
|
+
export declare function transformDbRecord(record: CellHistoryDbRecord): CellHistoryApiResponse;
|
|
48
|
+
/**
|
|
49
|
+
* Transform an array of database records to API response format
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* // In your API endpoint:
|
|
53
|
+
* const dbRecords = await db.query('SELECT * FROM CELL_CONFIG_HISTORY WHERE CELLNAME = ?', [cellName]);
|
|
54
|
+
* const response = transformDbRecords(dbRecords);
|
|
55
|
+
* return json(response);
|
|
56
|
+
*/
|
|
57
|
+
export declare function transformDbRecords(records: CellHistoryDbRecord[]): CellHistoryApiResponse[];
|
|
58
|
+
/**
|
|
59
|
+
* Example usage in a SvelteKit API endpoint:
|
|
60
|
+
*
|
|
61
|
+
* ```typescript
|
|
62
|
+
* // src/routes/api/cell-history/[cellName]/+server.ts
|
|
63
|
+
* import { json } from '@sveltejs/kit';
|
|
64
|
+
* import { transformDbRecords } from './history-api-helper';
|
|
65
|
+
* import { db } from '../../server/database';
|
|
66
|
+
*
|
|
67
|
+
* export async function GET({ params }) {
|
|
68
|
+
* const { cellName } = params;
|
|
69
|
+
*
|
|
70
|
+
* const dbRecords = await db.query(`
|
|
71
|
+
* SELECT * FROM CELL_CONFIG_HISTORY
|
|
72
|
+
* WHERE CELLNAME = :cellName
|
|
73
|
+
* ORDER BY CONFIG_DATE DESC
|
|
74
|
+
* `, { cellName });
|
|
75
|
+
*
|
|
76
|
+
* return json(transformDbRecords(dbRecords));
|
|
77
|
+
* }
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cell History API Helper
|
|
3
|
+
*
|
|
4
|
+
* Transform functions for converting database records to frontend-ready format.
|
|
5
|
+
* Use these in your API endpoint before sending the response.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Format a date to YYYY.MM.DD string
|
|
9
|
+
*/
|
|
10
|
+
export function formatConfigDate(date) {
|
|
11
|
+
const d = typeof date === 'string' ? new Date(date) : date;
|
|
12
|
+
const year = d.getFullYear();
|
|
13
|
+
const month = String(d.getMonth() + 1).padStart(2, '0');
|
|
14
|
+
const day = String(d.getDate()).padStart(2, '0');
|
|
15
|
+
return `${year}.${month}.${day}`;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Transform a single database record to API response format
|
|
19
|
+
*/
|
|
20
|
+
export function transformDbRecord(record) {
|
|
21
|
+
// Detect which fields changed
|
|
22
|
+
const changedFields = [];
|
|
23
|
+
if (record.ANTENNA_TYPE !== record.PREV_ANTENNA_TYPE) {
|
|
24
|
+
changedFields.push('antenna');
|
|
25
|
+
}
|
|
26
|
+
if (record.ANTOUTPUTPWR !== record.PREV_ANTOUTPUTPWR) {
|
|
27
|
+
changedFields.push('power');
|
|
28
|
+
}
|
|
29
|
+
if (record.MECHANIC_TILT !== record.PREV_MECHANIC_TILT) {
|
|
30
|
+
changedFields.push('mechanicalTilt');
|
|
31
|
+
}
|
|
32
|
+
if (record.ELECTRONIC_TILT !== record.PREV_ELECTRONIC_TILT) {
|
|
33
|
+
changedFields.push('electricalTilt');
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
cellName: record.CELLNAME,
|
|
37
|
+
configDate: formatConfigDate(record.CONFIG_DATE),
|
|
38
|
+
antenna: record.ANTENNA_TYPE,
|
|
39
|
+
prevAntenna: record.PREV_ANTENNA_TYPE,
|
|
40
|
+
power: record.ANTOUTPUTPWR,
|
|
41
|
+
prevPower: record.PREV_ANTOUTPUTPWR,
|
|
42
|
+
mechanicalTilt: record.MECHANIC_TILT,
|
|
43
|
+
prevMechanicalTilt: record.PREV_MECHANIC_TILT,
|
|
44
|
+
electricalTilt: record.ELECTRONIC_TILT,
|
|
45
|
+
prevElectricalTilt: record.PREV_ELECTRONIC_TILT,
|
|
46
|
+
hasChanges: changedFields.length > 0,
|
|
47
|
+
changedFields
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Transform an array of database records to API response format
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* // In your API endpoint:
|
|
55
|
+
* const dbRecords = await db.query('SELECT * FROM CELL_CONFIG_HISTORY WHERE CELLNAME = ?', [cellName]);
|
|
56
|
+
* const response = transformDbRecords(dbRecords);
|
|
57
|
+
* return json(response);
|
|
58
|
+
*/
|
|
59
|
+
export function transformDbRecords(records) {
|
|
60
|
+
return records.map(transformDbRecord);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Example usage in a SvelteKit API endpoint:
|
|
64
|
+
*
|
|
65
|
+
* ```typescript
|
|
66
|
+
* // src/routes/api/cell-history/[cellName]/+server.ts
|
|
67
|
+
* import { json } from '@sveltejs/kit';
|
|
68
|
+
* import { transformDbRecords } from './history-api-helper';
|
|
69
|
+
* import { db } from '../../server/database';
|
|
70
|
+
*
|
|
71
|
+
* export async function GET({ params }) {
|
|
72
|
+
* const { cellName } = params;
|
|
73
|
+
*
|
|
74
|
+
* const dbRecords = await db.query(`
|
|
75
|
+
* SELECT * FROM CELL_CONFIG_HISTORY
|
|
76
|
+
* WHERE CELLNAME = :cellName
|
|
77
|
+
* ORDER BY CONFIG_DATE DESC
|
|
78
|
+
* `, { cellName });
|
|
79
|
+
*
|
|
80
|
+
* return json(transformDbRecords(dbRecords));
|
|
81
|
+
* }
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
@@ -9,6 +9,7 @@ export { default as CellTablePanel } from './CellTablePanel.svelte';
|
|
|
9
9
|
export { default as CellTableDemo } from './CellTableDemo.svelte';
|
|
10
10
|
export { default as CellHistoryDemo } from './CellHistoryDemo.svelte';
|
|
11
11
|
export { default as ColumnPicker } from './ColumnPicker.svelte';
|
|
12
|
+
export { transformDbRecord, transformDbRecords, formatConfigDate, type CellHistoryDbRecord, type CellHistoryApiResponse } from './history-api-helper';
|
|
12
13
|
export { generateCells, generateCellsFromPreset, getGeneratorInfo, GENERATOR_PRESETS, type CellGeneratorConfig, type GeneratorPreset } from '../../shared/demo';
|
|
13
|
-
export type { CellData, CellTableGroupField, ColumnPreset, ColumnVisibility, TechColorMap, StatusColorMap, CellTableProps, RowSelectionEvent, RowClickEvent, RowDblClickEvent, RowContextMenuEvent, DataChangeEvent, CellTableColumn, ColumnGroups } from './types';
|
|
14
|
-
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';
|
|
14
|
+
export type { CellData, CellTableGroupField, ColumnPreset, ColumnVisibility, TechColorMap, StatusColorMap, CellTableProps, RowSelectionEvent, RowClickEvent, RowDblClickEvent, RowContextMenuEvent, DataChangeEvent, CellTableColumn, ColumnGroups, CellHistoryData, GroupOption } from './types';
|
|
15
|
+
export { DEFAULT_TECH_COLORS, DEFAULT_STATUS_COLORS, FBAND_COLORS, COLUMN_GROUPS, getAllColumns, getColumnsForPreset, getGroupHeaderFormatter, getColumnMetadata, getPresetVisibleFields, fbandSorter, cellNameSectorSorter, cellDataSorter, createTechFormatter, createStatusFormatter, createFbandFormatter, createHistoryChangeFormatter, historyDateFormatter, numberFormatter, coordinateFormatter, azimuthFormatter, heightFormatter, type ColumnMeta } from './column-config';
|
|
@@ -10,9 +10,11 @@ export { default as CellTablePanel } from './CellTablePanel.svelte';
|
|
|
10
10
|
export { default as CellTableDemo } from './CellTableDemo.svelte';
|
|
11
11
|
export { default as CellHistoryDemo } from './CellHistoryDemo.svelte';
|
|
12
12
|
export { default as ColumnPicker } from './ColumnPicker.svelte';
|
|
13
|
+
// History API helpers (for backend transformation)
|
|
14
|
+
export { transformDbRecord, transformDbRecords, formatConfigDate } from './history-api-helper';
|
|
13
15
|
// Re-export shared demo data utilities for convenience
|
|
14
16
|
// Note: Cell type is NOT re-exported here to avoid conflicts with map-v2
|
|
15
17
|
// Import Cell from '$lib/shared/demo' or '@smartnet360/svelte-components' directly
|
|
16
18
|
export { generateCells, generateCellsFromPreset, getGeneratorInfo, GENERATOR_PRESETS } from '../../shared/demo';
|
|
17
19
|
// Configuration utilities
|
|
18
|
-
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';
|
|
20
|
+
export { DEFAULT_TECH_COLORS, DEFAULT_STATUS_COLORS, FBAND_COLORS, COLUMN_GROUPS, getAllColumns, getColumnsForPreset, getGroupHeaderFormatter, getColumnMetadata, getPresetVisibleFields, fbandSorter, cellNameSectorSorter, cellDataSorter, createTechFormatter, createStatusFormatter, createFbandFormatter, createHistoryChangeFormatter, historyDateFormatter, numberFormatter, coordinateFormatter, azimuthFormatter, heightFormatter } from './column-config';
|
|
@@ -23,6 +23,15 @@ export type ColumnPreset = 'default' | 'compact' | 'full' | 'physical' | 'networ
|
|
|
23
23
|
export interface ColumnVisibility {
|
|
24
24
|
[key: string]: boolean;
|
|
25
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Grouping option for toolbar dropdown
|
|
28
|
+
*/
|
|
29
|
+
export interface GroupOption {
|
|
30
|
+
/** Field name to group by (or 'none') */
|
|
31
|
+
value: string;
|
|
32
|
+
/** Display label in dropdown */
|
|
33
|
+
label: string;
|
|
34
|
+
}
|
|
26
35
|
/**
|
|
27
36
|
* Technology color mapping
|
|
28
37
|
*/
|
|
@@ -133,3 +142,31 @@ export interface ColumnGroups {
|
|
|
133
142
|
kpi: string[];
|
|
134
143
|
history: string[];
|
|
135
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Cell configuration history record
|
|
147
|
+
* Each record represents a point in time where a cell's configuration changed
|
|
148
|
+
*/
|
|
149
|
+
export interface CellHistoryData {
|
|
150
|
+
/** Cell name identifier */
|
|
151
|
+
CELLNAME: string;
|
|
152
|
+
/** Frequency in MHz (e.g., 1800, 2100, 3500) */
|
|
153
|
+
FREQUENCY: number;
|
|
154
|
+
/** Date of configuration change (ISO 8601 format) */
|
|
155
|
+
CONFIG_DATE: string;
|
|
156
|
+
/** Current antenna type at this config date */
|
|
157
|
+
ANTENNA_TYPE: string;
|
|
158
|
+
/** Previous antenna type before this change */
|
|
159
|
+
PREV_ANTENNA_TYPE: string;
|
|
160
|
+
/** Current antenna output power (dBm) */
|
|
161
|
+
ANTOUTPUTPWR: number;
|
|
162
|
+
/** Previous antenna output power */
|
|
163
|
+
PREV_ANTOUTPUTPWR: number;
|
|
164
|
+
/** Current mechanical tilt (degrees) */
|
|
165
|
+
MECHANIC_TILT: number;
|
|
166
|
+
/** Previous mechanical tilt */
|
|
167
|
+
PREV_MECHANIC_TILT: number;
|
|
168
|
+
/** Current electrical tilt (degrees) */
|
|
169
|
+
ELECTRONIC_TILT: number;
|
|
170
|
+
/** Previous electrical tilt */
|
|
171
|
+
PREV_ELECTRONIC_TILT: number;
|
|
172
|
+
}
|