@smartnet360/svelte-components 0.0.116 → 0.0.118
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/apps/antenna-pattern/components/AntennaDiagrams.svelte +124 -18
- package/dist/apps/antenna-pattern/components/AntennaDiagrams.svelte.d.ts +21 -1
- package/dist/core/CellTable/CellHistoryDemo.svelte +182 -0
- package/dist/core/CellTable/CellHistoryDemo.svelte.d.ts +3 -0
- package/dist/core/CellTable/CellTable.svelte +19 -2
- package/dist/core/CellTable/CellTableDemo.svelte +34 -0
- package/dist/core/CellTable/column-config.js +24 -0
- package/dist/core/CellTable/index.d.ts +1 -0
- package/dist/core/CellTable/index.js +1 -0
- package/dist/core/CellTable/types.d.ts +2 -1
- package/dist/shared/demo/cell-types.d.ts +15 -0
- package/dist/shared/demo/index.d.ts +1 -1
- package/package.json +1 -1
|
@@ -11,18 +11,55 @@
|
|
|
11
11
|
// Chart engine types
|
|
12
12
|
type ChartEngineType = 'polar-line' | 'polar-bar' | 'polar-area';
|
|
13
13
|
|
|
14
|
+
// Optional props for external initialization (non-breaking - all have defaults)
|
|
15
|
+
interface Props {
|
|
16
|
+
/** Initial antenna 1 name to pre-select */
|
|
17
|
+
initialAntenna1Name?: string;
|
|
18
|
+
/** Initial antenna 2 name to pre-select */
|
|
19
|
+
initialAntenna2Name?: string;
|
|
20
|
+
/** Initial electrical tilt for antenna 1 */
|
|
21
|
+
initialEtilt1?: number;
|
|
22
|
+
/** Initial electrical tilt for antenna 2 */
|
|
23
|
+
initialEtilt2?: number;
|
|
24
|
+
/** Initial mechanical tilt for antenna 1 */
|
|
25
|
+
initialMtilt1?: number;
|
|
26
|
+
/** Initial mechanical tilt for antenna 2 */
|
|
27
|
+
initialMtilt2?: number;
|
|
28
|
+
/** Initial view mode */
|
|
29
|
+
initialViewMode?: 'single' | 'compare';
|
|
30
|
+
/** Label for antenna 1 (e.g., cell name) */
|
|
31
|
+
antenna1Label?: string;
|
|
32
|
+
/** Label for antenna 2 (e.g., cell name) */
|
|
33
|
+
antenna2Label?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let {
|
|
37
|
+
initialAntenna1Name = undefined,
|
|
38
|
+
initialAntenna2Name = undefined,
|
|
39
|
+
initialEtilt1 = 0,
|
|
40
|
+
initialEtilt2 = 0,
|
|
41
|
+
initialMtilt1 = 0,
|
|
42
|
+
initialMtilt2 = 0,
|
|
43
|
+
initialViewMode = 'single',
|
|
44
|
+
antenna1Label = undefined,
|
|
45
|
+
antenna2Label = undefined,
|
|
46
|
+
}: Props = $props();
|
|
47
|
+
|
|
14
48
|
let antennas = $state<Antenna[]>([]);
|
|
15
49
|
let selectedAntenna = $state<Antenna | null>(null);
|
|
16
50
|
let selectedAntenna2 = $state<Antenna | null>(null);
|
|
17
51
|
|
|
18
52
|
// External Bootstrap slider values
|
|
53
|
+
// Note: Electrical tilt is an INDEX into availableElectricalTilts array
|
|
54
|
+
// Mechanical tilt is a direct VALUE in degrees
|
|
55
|
+
// We initialize to 0 and set correct values in onMount after loading antenna data
|
|
19
56
|
let ant1ElectricalTilt = $state(0);
|
|
20
57
|
let ant2ElectricalTilt = $state(0);
|
|
21
|
-
let ant1MechanicalTilt = $state(
|
|
22
|
-
let ant2MechanicalTilt = $state(
|
|
58
|
+
let ant1MechanicalTilt = $state(initialMtilt1);
|
|
59
|
+
let ant2MechanicalTilt = $state(initialMtilt2);
|
|
23
60
|
|
|
24
61
|
// Viewing mode and pattern visibility
|
|
25
|
-
let viewMode = $state<'single' | 'compare'>(
|
|
62
|
+
let viewMode = $state<'single' | 'compare'>(initialViewMode);
|
|
26
63
|
let patternType = $state<'horizontal' | 'vertical'>('vertical');
|
|
27
64
|
|
|
28
65
|
// Chart engine selection
|
|
@@ -38,6 +75,7 @@
|
|
|
38
75
|
let availableElectricalTilts = $state<string[]>(['0', '2', '4', '6', '8']);
|
|
39
76
|
|
|
40
77
|
// Handle antenna selection from controls
|
|
78
|
+
// Note: We no longer reset tilts here - tilts should persist unless explicitly changed
|
|
41
79
|
function handleAntenna1Change(antenna: Antenna | null) {
|
|
42
80
|
selectedAntenna = antenna;
|
|
43
81
|
if (antenna) {
|
|
@@ -45,18 +83,11 @@
|
|
|
45
83
|
availableElectricalTilts = antenna.tilt ?
|
|
46
84
|
antenna.tilt.split(',').map(t => t.trim()) :
|
|
47
85
|
['0', '2', '4', '6', '8'];
|
|
48
|
-
|
|
49
|
-
ant1ElectricalTilt = 0;
|
|
50
|
-
ant1MechanicalTilt = 0;
|
|
51
86
|
}
|
|
52
87
|
}
|
|
53
88
|
|
|
54
89
|
function handleAntenna2Change(antenna: Antenna | null) {
|
|
55
90
|
selectedAntenna2 = antenna;
|
|
56
|
-
if (antenna) {
|
|
57
|
-
ant2ElectricalTilt = 0;
|
|
58
|
-
ant2MechanicalTilt = 0;
|
|
59
|
-
}
|
|
60
91
|
}
|
|
61
92
|
|
|
62
93
|
// Handle electrical tilt changes from controls
|
|
@@ -111,24 +142,95 @@
|
|
|
111
142
|
}
|
|
112
143
|
}
|
|
113
144
|
|
|
145
|
+
// Helper to find tilt index from actual tilt value
|
|
146
|
+
function findTiltIndex(tilts: string[], tiltValue: number): number {
|
|
147
|
+
const index = tilts.findIndex(t => parseInt(t, 10) === tiltValue);
|
|
148
|
+
return index >= 0 ? index : 0; // Default to 0 if not found
|
|
149
|
+
}
|
|
150
|
+
|
|
114
151
|
onMount(async () => {
|
|
115
152
|
try {
|
|
116
153
|
// Load antenna data
|
|
117
154
|
antennas = await loadAntennas();
|
|
118
155
|
|
|
119
156
|
if (antennas.length > 0) {
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
157
|
+
// Check if initial antenna names were provided (for external initialization)
|
|
158
|
+
if (initialAntenna1Name) {
|
|
159
|
+
// Try exact match first, then case-insensitive, then partial match
|
|
160
|
+
let found = antennas.find(a => a.name === initialAntenna1Name);
|
|
161
|
+
if (!found) {
|
|
162
|
+
found = antennas.find(a => a.name.toLowerCase() === initialAntenna1Name.toLowerCase());
|
|
163
|
+
}
|
|
164
|
+
if (!found) {
|
|
165
|
+
found = antennas.find(a => a.name.toLowerCase().includes(initialAntenna1Name.toLowerCase()));
|
|
166
|
+
}
|
|
167
|
+
if (!found) {
|
|
168
|
+
console.warn(`[AntennaDiagrams] Antenna not found: "${initialAntenna1Name}", using first available`);
|
|
169
|
+
}
|
|
170
|
+
selectedAntenna = found || antennas[0];
|
|
171
|
+
} else {
|
|
172
|
+
selectedAntenna = antennas[0];
|
|
124
173
|
}
|
|
125
174
|
|
|
126
|
-
//
|
|
127
|
-
if (
|
|
175
|
+
// Collect ALL available tilts from all antennas with the same name
|
|
176
|
+
if (selectedAntenna) {
|
|
177
|
+
const sameName = antennas.filter(a => a.name === selectedAntenna!.name);
|
|
178
|
+
const allTilts = new Set<string>();
|
|
179
|
+
sameName.forEach(a => {
|
|
180
|
+
if (a.tilt) {
|
|
181
|
+
const tiltStr = a.tilt.toString();
|
|
182
|
+
if (tiltStr.includes(',')) {
|
|
183
|
+
tiltStr.split(',').forEach(t => allTilts.add(t.trim()));
|
|
184
|
+
} else {
|
|
185
|
+
allTilts.add(tiltStr.trim());
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
availableElectricalTilts = Array.from(allTilts).sort((a, b) => parseFloat(a) - parseFloat(b));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Set initial electrical tilt for antenna 1 (convert value to index)
|
|
193
|
+
ant1ElectricalTilt = findTiltIndex(availableElectricalTilts, initialEtilt1);
|
|
194
|
+
|
|
195
|
+
// Set antenna 2 selection (for compare mode)
|
|
196
|
+
if (initialAntenna2Name) {
|
|
197
|
+
// Try exact match first, then case-insensitive, then partial match
|
|
198
|
+
let found = antennas.find(a => a.name === initialAntenna2Name);
|
|
199
|
+
if (!found) {
|
|
200
|
+
found = antennas.find(a => a.name.toLowerCase() === initialAntenna2Name.toLowerCase());
|
|
201
|
+
}
|
|
202
|
+
if (!found) {
|
|
203
|
+
found = antennas.find(a => a.name.toLowerCase().includes(initialAntenna2Name.toLowerCase()));
|
|
204
|
+
}
|
|
205
|
+
if (!found) {
|
|
206
|
+
console.warn(`[AntennaDiagrams] Antenna 2 not found: "${initialAntenna2Name}", using fallback`);
|
|
207
|
+
}
|
|
208
|
+
selectedAntenna2 = found || (antennas.length > 1 ? antennas[1] : antennas[0]);
|
|
209
|
+
} else if (antennas.length > 1) {
|
|
128
210
|
selectedAntenna2 = antennas[1];
|
|
129
211
|
} else {
|
|
130
212
|
selectedAntenna2 = antennas[0]; // Use same antenna if only one available
|
|
131
213
|
}
|
|
214
|
+
|
|
215
|
+
// Set initial electrical tilt for antenna 2 (convert value to index)
|
|
216
|
+
if (selectedAntenna2) {
|
|
217
|
+
// Collect ALL available tilts from all antennas with the same name
|
|
218
|
+
const sameName2 = antennas.filter(a => a.name === selectedAntenna2!.name);
|
|
219
|
+
const allTilts2 = new Set<string>();
|
|
220
|
+
sameName2.forEach(a => {
|
|
221
|
+
if (a.tilt) {
|
|
222
|
+
const tiltStr = a.tilt.toString();
|
|
223
|
+
if (tiltStr.includes(',')) {
|
|
224
|
+
tiltStr.split(',').forEach(t => allTilts2.add(t.trim()));
|
|
225
|
+
} else {
|
|
226
|
+
allTilts2.add(tiltStr.trim());
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
const ant2Tilts = Array.from(allTilts2).sort((a, b) => parseFloat(a) - parseFloat(b));
|
|
231
|
+
ant2ElectricalTilt = findTiltIndex(ant2Tilts, initialEtilt2);
|
|
232
|
+
}
|
|
233
|
+
// Mechanical tilt is a direct value, already set from prop
|
|
132
234
|
}
|
|
133
235
|
} catch (error) {
|
|
134
236
|
console.error('Failed to initialize:', error);
|
|
@@ -138,9 +240,13 @@
|
|
|
138
240
|
// Generate chart title based on current selection
|
|
139
241
|
function generateChartTitle(): string {
|
|
140
242
|
if (viewMode === 'compare' && selectedAntenna && selectedAntenna2) {
|
|
141
|
-
|
|
243
|
+
// Use custom labels if provided, otherwise use antenna names
|
|
244
|
+
const label1 = antenna1Label || selectedAntenna.name;
|
|
245
|
+
const label2 = antenna2Label || selectedAntenna2.name;
|
|
246
|
+
return `${label1} vs ${label2}`;
|
|
142
247
|
} else if (selectedAntenna) {
|
|
143
|
-
|
|
248
|
+
const label = antenna1Label || selectedAntenna.name;
|
|
249
|
+
return `${label} - Pattern Analysis`;
|
|
144
250
|
}
|
|
145
251
|
return 'Antenna Pattern Analysis';
|
|
146
252
|
}
|
|
@@ -1,3 +1,23 @@
|
|
|
1
|
-
|
|
1
|
+
interface Props {
|
|
2
|
+
/** Initial antenna 1 name to pre-select */
|
|
3
|
+
initialAntenna1Name?: string;
|
|
4
|
+
/** Initial antenna 2 name to pre-select */
|
|
5
|
+
initialAntenna2Name?: string;
|
|
6
|
+
/** Initial electrical tilt for antenna 1 */
|
|
7
|
+
initialEtilt1?: number;
|
|
8
|
+
/** Initial electrical tilt for antenna 2 */
|
|
9
|
+
initialEtilt2?: number;
|
|
10
|
+
/** Initial mechanical tilt for antenna 1 */
|
|
11
|
+
initialMtilt1?: number;
|
|
12
|
+
/** Initial mechanical tilt for antenna 2 */
|
|
13
|
+
initialMtilt2?: number;
|
|
14
|
+
/** Initial view mode */
|
|
15
|
+
initialViewMode?: 'single' | 'compare';
|
|
16
|
+
/** Label for antenna 1 (e.g., cell name) */
|
|
17
|
+
antenna1Label?: string;
|
|
18
|
+
/** Label for antenna 2 (e.g., cell name) */
|
|
19
|
+
antenna2Label?: string;
|
|
20
|
+
}
|
|
21
|
+
declare const AntennaDiagrams: import("svelte").Component<Props, {}, "">;
|
|
2
22
|
type AntennaDiagrams = ReturnType<typeof AntennaDiagrams>;
|
|
3
23
|
export default AntennaDiagrams;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import CellTablePanel from './CellTablePanel.svelte';
|
|
3
|
+
import type { RowSelectionEvent, CellData } from './types';
|
|
4
|
+
|
|
5
|
+
// Generate mock history data for a single cell
|
|
6
|
+
function generateMockHistory(cellName: string, count: number = 20): Partial<CellData>[] {
|
|
7
|
+
const antennas = [
|
|
8
|
+
'ADU451R76V06',
|
|
9
|
+
'ADU451R79V06',
|
|
10
|
+
'AAHF4518R3V06',
|
|
11
|
+
'AAHF4516R1V06',
|
|
12
|
+
'742215'
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
const history: Partial<CellData>[] = [];
|
|
16
|
+
const baseDate = new Date('2025-10-20');
|
|
17
|
+
|
|
18
|
+
let currentAntenna = antennas[0];
|
|
19
|
+
let currentET = 4;
|
|
20
|
+
let currentMT = 2;
|
|
21
|
+
let currentPW = 20;
|
|
22
|
+
|
|
23
|
+
for (let i = 0; i < count; i++) {
|
|
24
|
+
// Random chance to change values
|
|
25
|
+
if (Math.random() > 0.7) {
|
|
26
|
+
currentAntenna = antennas[Math.floor(Math.random() * antennas.length)];
|
|
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;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let cellName = $state('2918141');
|
|
62
|
+
let historyData = $state(generateMockHistory('2918141'));
|
|
63
|
+
|
|
64
|
+
function loadHistory() {
|
|
65
|
+
historyData = generateMockHistory(cellName);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function handleSelectionChange(event: RowSelectionEvent) {
|
|
69
|
+
console.log('Selection changed:', event.ids);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Compare two selected history records
|
|
74
|
+
*/
|
|
75
|
+
function handleCompareTwoRecords(record1: CellData, record2: CellData) {
|
|
76
|
+
const params = new URLSearchParams();
|
|
77
|
+
|
|
78
|
+
// Use antenna from both records
|
|
79
|
+
if (record1.antenna) params.set('ant1', record1.antenna);
|
|
80
|
+
if (record2.antenna) params.set('ant2', record2.antenna);
|
|
81
|
+
|
|
82
|
+
// Use electrical tilt values
|
|
83
|
+
params.set('etilt1', String(record1.atollET || 0));
|
|
84
|
+
params.set('etilt2', String(record2.atollET || 0));
|
|
85
|
+
|
|
86
|
+
// Use mechanical tilt values
|
|
87
|
+
params.set('mtilt1', String(record1.atollMT || 0));
|
|
88
|
+
params.set('mtilt2', String(record2.atollMT || 0));
|
|
89
|
+
|
|
90
|
+
// Add config date as labels
|
|
91
|
+
params.set('label1', `${record1.cellName} (${record1.configDate})`);
|
|
92
|
+
params.set('label2', `${record2.cellName} (${record2.configDate})`);
|
|
93
|
+
|
|
94
|
+
const url = `/apps/antenna-compare?${params.toString()}`;
|
|
95
|
+
window.open(url, '_blank');
|
|
96
|
+
}
|
|
97
|
+
</script>
|
|
98
|
+
|
|
99
|
+
<div class="cell-history-page vh-100 d-flex flex-column">
|
|
100
|
+
<!-- Header -->
|
|
101
|
+
<div class="demo-controls bg-light border-bottom px-3 py-2 d-flex align-items-center gap-3 flex-wrap">
|
|
102
|
+
<div class="input-group input-group-sm" style="width: auto;">
|
|
103
|
+
<span class="input-group-text">Cell Name</span>
|
|
104
|
+
<input
|
|
105
|
+
type="text"
|
|
106
|
+
class="form-control"
|
|
107
|
+
bind:value={cellName}
|
|
108
|
+
style="width: 120px;"
|
|
109
|
+
/>
|
|
110
|
+
<button class="btn btn-outline-primary" onclick={loadHistory}>
|
|
111
|
+
<i class="bi bi-arrow-clockwise"></i> Load History
|
|
112
|
+
</button>
|
|
113
|
+
</div>
|
|
114
|
+
<span class="badge bg-info">
|
|
115
|
+
{historyData.length} records
|
|
116
|
+
</span>
|
|
117
|
+
<span class="text-muted small">
|
|
118
|
+
Select 2 rows to compare antenna configurations
|
|
119
|
+
</span>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<!-- CellTablePanel with history preset -->
|
|
123
|
+
<div class="flex-grow-1 overflow-hidden">
|
|
124
|
+
<CellTablePanel
|
|
125
|
+
cells={historyData as CellData[]}
|
|
126
|
+
groupBy="none"
|
|
127
|
+
columnPreset="history"
|
|
128
|
+
selectable={true}
|
|
129
|
+
multiSelect={true}
|
|
130
|
+
showToolbar={true}
|
|
131
|
+
showExport={true}
|
|
132
|
+
headerFilters={true}
|
|
133
|
+
showDetailsSidebar={false}
|
|
134
|
+
showScrollSpy={false}
|
|
135
|
+
title="Cell Configuration History"
|
|
136
|
+
onselectionchange={handleSelectionChange}
|
|
137
|
+
>
|
|
138
|
+
{#snippet footer({ selectedRows, selectedCount })}
|
|
139
|
+
<div class="d-flex align-items-center justify-content-between">
|
|
140
|
+
<span class="text-muted small">
|
|
141
|
+
{#if selectedCount > 0}
|
|
142
|
+
{selectedCount} record(s) selected
|
|
143
|
+
{:else}
|
|
144
|
+
Select rows to compare configurations
|
|
145
|
+
{/if}
|
|
146
|
+
</span>
|
|
147
|
+
<div class="btn-group">
|
|
148
|
+
<button
|
|
149
|
+
type="button"
|
|
150
|
+
class="btn btn-sm btn-outline-success"
|
|
151
|
+
disabled={selectedCount !== 2}
|
|
152
|
+
title={selectedCount === 2 ? 'Compare antenna patterns for selected dates' : 'Select exactly 2 records to compare'}
|
|
153
|
+
onclick={() => handleCompareTwoRecords(selectedRows[0], selectedRows[1])}
|
|
154
|
+
>
|
|
155
|
+
<i class="bi bi-broadcast-pin"></i>
|
|
156
|
+
<span class="d-none d-sm-inline ms-1">Compare Antennas</span>
|
|
157
|
+
</button>
|
|
158
|
+
<button
|
|
159
|
+
type="button"
|
|
160
|
+
class="btn btn-sm btn-outline-secondary"
|
|
161
|
+
disabled={selectedCount === 0}
|
|
162
|
+
onclick={() => console.log('Export:', selectedRows.map(r => r.configDate))}
|
|
163
|
+
>
|
|
164
|
+
<i class="bi bi-download"></i>
|
|
165
|
+
<span class="d-none d-sm-inline ms-1">Export</span>
|
|
166
|
+
</button>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
{/snippet}
|
|
170
|
+
</CellTablePanel>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
<style>
|
|
175
|
+
.cell-history-page {
|
|
176
|
+
background: var(--bs-body-bg, #f8f9fa);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.demo-controls {
|
|
180
|
+
flex-shrink: 0;
|
|
181
|
+
}
|
|
182
|
+
</style>
|
|
@@ -71,8 +71,25 @@
|
|
|
71
71
|
|
|
72
72
|
// Reactive column configuration - only changes when preset changes
|
|
73
73
|
let columns = $derived.by(() => {
|
|
74
|
-
//
|
|
75
|
-
|
|
74
|
+
// Get base columns from preset
|
|
75
|
+
const baseColumns = getColumnsForPreset(columnPreset, techColors, statusColors, headerFilters);
|
|
76
|
+
|
|
77
|
+
// Add row selection checkbox column if selectable
|
|
78
|
+
if (selectable) {
|
|
79
|
+
const selectColumn = {
|
|
80
|
+
title: '',
|
|
81
|
+
formatter: 'rowSelection',
|
|
82
|
+
titleFormatter: multiSelect ? 'rowSelection' : undefined,
|
|
83
|
+
hozAlign: 'center' as const,
|
|
84
|
+
headerSort: false,
|
|
85
|
+
width: 40,
|
|
86
|
+
frozen: true,
|
|
87
|
+
cssClass: 'cell-table-select-column',
|
|
88
|
+
};
|
|
89
|
+
return [selectColumn, ...baseColumns];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return baseColumns;
|
|
76
93
|
});
|
|
77
94
|
|
|
78
95
|
// Pre-sort data using our custom multi-level sorter
|
|
@@ -65,6 +65,30 @@
|
|
|
65
65
|
alert(`Show on map: ${cell.cellName}\nLat: ${cell.latitude}\nLon: ${cell.longitude}`);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Compare two selected cells' antenna configurations
|
|
70
|
+
*/
|
|
71
|
+
function handleCompareTwoCells(cell1: CellData, cell2: CellData) {
|
|
72
|
+
const params = new URLSearchParams();
|
|
73
|
+
|
|
74
|
+
// Use antenna from both cells
|
|
75
|
+
if (cell1.antenna) params.set('ant1', cell1.antenna);
|
|
76
|
+
if (cell2.antenna) params.set('ant2', cell2.antenna);
|
|
77
|
+
|
|
78
|
+
// Use electrical tilt values (parse first value if comma-separated)
|
|
79
|
+
const etilt1 = cell1.electricalTilt ? parseInt(cell1.electricalTilt.split(',')[0], 10) : 0;
|
|
80
|
+
const etilt2 = cell2.electricalTilt ? parseInt(cell2.electricalTilt.split(',')[0], 10) : 0;
|
|
81
|
+
params.set('etilt1', String(etilt1 || 0));
|
|
82
|
+
params.set('etilt2', String(etilt2 || 0));
|
|
83
|
+
|
|
84
|
+
// Add cell name + status as labels
|
|
85
|
+
params.set('label1', `${cell1.cellName} (${cell1.status})`);
|
|
86
|
+
params.set('label2', `${cell2.cellName} (${cell2.status})`);
|
|
87
|
+
|
|
88
|
+
const url = `/apps/antenna-compare?${params.toString()}`;
|
|
89
|
+
window.open(url, '_blank');
|
|
90
|
+
}
|
|
91
|
+
|
|
68
92
|
function regenerateData() {
|
|
69
93
|
demoCells = generateCellsFromPreset(datasetSize);
|
|
70
94
|
}
|
|
@@ -244,6 +268,16 @@
|
|
|
244
268
|
{/if}
|
|
245
269
|
</span>
|
|
246
270
|
<div class="btn-group">
|
|
271
|
+
<button
|
|
272
|
+
type="button"
|
|
273
|
+
class="btn btn-sm btn-outline-success"
|
|
274
|
+
disabled={selectedCount !== 2}
|
|
275
|
+
title={selectedCount === 2 ? 'Compare Atoll antennas of selected cells' : 'Select exactly 2 cells to compare'}
|
|
276
|
+
onclick={() => handleCompareTwoCells(selectedRows[0], selectedRows[1])}
|
|
277
|
+
>
|
|
278
|
+
<i class="bi bi-broadcast-pin"></i>
|
|
279
|
+
<span class="d-none d-sm-inline ms-1">Compare Antennas</span>
|
|
280
|
+
</button>
|
|
247
281
|
<button
|
|
248
282
|
type="button"
|
|
249
283
|
class="btn btn-sm btn-outline-primary"
|
|
@@ -61,6 +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', 'antenna', 'atollET', 'atollMT', 'atollPW'],
|
|
64
65
|
};
|
|
65
66
|
/**
|
|
66
67
|
* Create a technology badge formatter
|
|
@@ -327,6 +328,12 @@ export function getAllColumns(techColors = DEFAULT_TECH_COLORS, statusColors = D
|
|
|
327
328
|
width: 120,
|
|
328
329
|
...headerFilterParams,
|
|
329
330
|
},
|
|
331
|
+
{
|
|
332
|
+
title: 'Config Date',
|
|
333
|
+
field: 'configDate',
|
|
334
|
+
width: 150,
|
|
335
|
+
...headerFilterParams,
|
|
336
|
+
},
|
|
330
337
|
// Physical columns
|
|
331
338
|
{
|
|
332
339
|
title: 'Antenna',
|
|
@@ -448,6 +455,14 @@ export function getAllColumns(techColors = DEFAULT_TECH_COLORS, statusColors = D
|
|
|
448
455
|
formatter: numberFormatter(1),
|
|
449
456
|
...headerFilterParams,
|
|
450
457
|
},
|
|
458
|
+
{
|
|
459
|
+
title: 'Atoll MT',
|
|
460
|
+
field: 'atollMT',
|
|
461
|
+
width: 90,
|
|
462
|
+
hozAlign: 'right',
|
|
463
|
+
formatter: numberFormatter(1),
|
|
464
|
+
...headerFilterParams,
|
|
465
|
+
},
|
|
451
466
|
{
|
|
452
467
|
title: 'Atoll PW',
|
|
453
468
|
field: 'atollPW',
|
|
@@ -659,6 +674,10 @@ export function getColumnsForPreset(preset, techColors = DEFAULT_TECH_COLORS, st
|
|
|
659
674
|
case 'kpi':
|
|
660
675
|
visibleFields = [...COLUMN_GROUPS.core, ...COLUMN_GROUPS.kpi];
|
|
661
676
|
break;
|
|
677
|
+
case 'history':
|
|
678
|
+
// Simplified view for config history - just cellName, date, and config fields
|
|
679
|
+
visibleFields = ['cellName', ...COLUMN_GROUPS.history];
|
|
680
|
+
break;
|
|
662
681
|
case 'default':
|
|
663
682
|
default:
|
|
664
683
|
visibleFields = [
|
|
@@ -723,9 +742,12 @@ export function getColumnMetadata() {
|
|
|
723
742
|
{ field: 'nwBW', title: 'NW BW', group: 'Network' },
|
|
724
743
|
// Atoll
|
|
725
744
|
{ field: 'atollET', title: 'Atoll ET', group: 'Atoll' },
|
|
745
|
+
{ field: 'atollMT', title: 'Atoll MT', group: 'Atoll' },
|
|
726
746
|
{ field: 'atollPW', title: 'Atoll PW', group: 'Atoll' },
|
|
727
747
|
{ field: 'atollRS', title: 'Atoll RS', group: 'Atoll' },
|
|
728
748
|
{ field: 'atollBW', title: 'Atoll BW', group: 'Atoll' },
|
|
749
|
+
// History
|
|
750
|
+
{ field: 'configDate', title: 'Config Date', group: 'History' },
|
|
729
751
|
// Compare (Atoll vs Network)
|
|
730
752
|
{ field: 'compareET', title: 'Δ ET', group: 'Compare' },
|
|
731
753
|
{ field: 'comparePW', title: 'Δ PW', group: 'Compare' },
|
|
@@ -768,6 +790,8 @@ export function getPresetVisibleFields(preset) {
|
|
|
768
790
|
return [...COLUMN_GROUPS.core, ...COLUMN_GROUPS.compare];
|
|
769
791
|
case 'kpi':
|
|
770
792
|
return [...COLUMN_GROUPS.core, ...COLUMN_GROUPS.kpi];
|
|
793
|
+
case 'history':
|
|
794
|
+
return ['cellName', ...COLUMN_GROUPS.history];
|
|
771
795
|
case 'default':
|
|
772
796
|
default:
|
|
773
797
|
return ['siteId', 'txId', 'cellName', 'tech', 'fband', 'frq', 'status', 'azimuth', 'height', 'antenna'];
|
|
@@ -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 CellHistoryDemo } from './CellHistoryDemo.svelte';
|
|
10
11
|
export { default as ColumnPicker } from './ColumnPicker.svelte';
|
|
11
12
|
export { generateCells, generateCellsFromPreset, getGeneratorInfo, GENERATOR_PRESETS, type CellGeneratorConfig, type GeneratorPreset } from '../../shared/demo';
|
|
12
13
|
export type { CellData, CellTableGroupField, ColumnPreset, ColumnVisibility, TechColorMap, StatusColorMap, CellTableProps, RowSelectionEvent, RowClickEvent, RowDblClickEvent, RowContextMenuEvent, DataChangeEvent, CellTableColumn, ColumnGroups } from './types';
|
|
@@ -8,6 +8,7 @@ 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 CellHistoryDemo } from './CellHistoryDemo.svelte';
|
|
11
12
|
export { default as ColumnPicker } from './ColumnPicker.svelte';
|
|
12
13
|
// Re-export shared demo data utilities for convenience
|
|
13
14
|
// Note: Cell type is NOT re-exported here to avoid conflicts with map-v2
|
|
@@ -16,7 +16,7 @@ export type CellTableGroupField = CellGroupingField | 'none';
|
|
|
16
16
|
/**
|
|
17
17
|
* Column preset configurations
|
|
18
18
|
*/
|
|
19
|
-
export type ColumnPreset = 'default' | 'compact' | 'full' | 'physical' | 'network' | 'planning' | 'compare' | 'kpi';
|
|
19
|
+
export type ColumnPreset = 'default' | 'compact' | 'full' | 'physical' | 'network' | 'planning' | 'compare' | 'kpi' | 'history';
|
|
20
20
|
/**
|
|
21
21
|
* Column visibility configuration
|
|
22
22
|
*/
|
|
@@ -131,4 +131,5 @@ export interface ColumnGroups {
|
|
|
131
131
|
position: string[];
|
|
132
132
|
compare: string[];
|
|
133
133
|
kpi: string[];
|
|
134
|
+
history: string[];
|
|
134
135
|
}
|
|
@@ -35,7 +35,9 @@ export interface Cell {
|
|
|
35
35
|
siteLongitude: number;
|
|
36
36
|
comment: string;
|
|
37
37
|
planner: string;
|
|
38
|
+
configDate?: string;
|
|
38
39
|
atollET?: number;
|
|
40
|
+
atollMT?: number;
|
|
39
41
|
atollPW?: number;
|
|
40
42
|
atollRS?: number;
|
|
41
43
|
atollBW?: number;
|
|
@@ -64,3 +66,16 @@ export type TechnologyBandKey = string;
|
|
|
64
66
|
* Grouping fields for views
|
|
65
67
|
*/
|
|
66
68
|
export type CellGroupingField = 'tech' | 'fband' | 'frq' | 'status' | 'siteId' | 'none';
|
|
69
|
+
/**
|
|
70
|
+
* Cell configuration history record
|
|
71
|
+
* Represents a snapshot of cell configuration at a point in time
|
|
72
|
+
*/
|
|
73
|
+
export interface CellConfigHistory {
|
|
74
|
+
id: string;
|
|
75
|
+
cellName: string;
|
|
76
|
+
configDate: string;
|
|
77
|
+
antenna: string;
|
|
78
|
+
antennaPower: number;
|
|
79
|
+
mechanicalTilt: number;
|
|
80
|
+
electricalTilt: number;
|
|
81
|
+
}
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Provides common demo data generators and types for use across components.
|
|
5
5
|
*/
|
|
6
|
-
export type { Cell, CellStatus, CellGroupingField } from './cell-types';
|
|
6
|
+
export type { Cell, CellStatus, CellGroupingField, CellConfigHistory } from './cell-types';
|
|
7
7
|
export { generateCells, generateCellsFromPreset, getGeneratorInfo, GENERATOR_PRESETS, type CellGeneratorConfig, type GeneratorPreset } from './cell-generator';
|