@smartnet360/svelte-components 0.0.116 → 0.0.117

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.
@@ -11,18 +11,52 @@
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
19
- let ant1ElectricalTilt = $state(0);
20
- let ant2ElectricalTilt = $state(0);
21
- let ant1MechanicalTilt = $state(0);
22
- let ant2MechanicalTilt = $state(0);
53
+ let ant1ElectricalTilt = $state(initialEtilt1);
54
+ let ant2ElectricalTilt = $state(initialEtilt2);
55
+ let ant1MechanicalTilt = $state(initialMtilt1);
56
+ let ant2MechanicalTilt = $state(initialMtilt2);
23
57
 
24
58
  // Viewing mode and pattern visibility
25
- let viewMode = $state<'single' | 'compare'>('single');
59
+ let viewMode = $state<'single' | 'compare'>(initialViewMode);
26
60
  let patternType = $state<'horizontal' | 'vertical'>('vertical');
27
61
 
28
62
  // Chart engine selection
@@ -117,14 +151,28 @@
117
151
  antennas = await loadAntennas();
118
152
 
119
153
  if (antennas.length > 0) {
120
- // Set default antenna selection for antenna 1
121
- selectedAntenna = antennas[0];
154
+ // Check if initial antenna names were provided (for external initialization)
155
+ if (initialAntenna1Name) {
156
+ // Find antenna by name (case-insensitive partial match)
157
+ const found = antennas.find(a =>
158
+ a.name.toLowerCase().includes(initialAntenna1Name.toLowerCase())
159
+ );
160
+ selectedAntenna = found || antennas[0];
161
+ } else {
162
+ selectedAntenna = antennas[0];
163
+ }
164
+
122
165
  if (selectedAntenna.tilt) {
123
166
  availableElectricalTilts = selectedAntenna.tilt.split(',').map(t => t.trim());
124
167
  }
125
168
 
126
- // Set default antenna selection for antenna 2 (for compare mode)
127
- if (antennas.length > 1) {
169
+ // Set antenna 2 selection (for compare mode)
170
+ if (initialAntenna2Name) {
171
+ const found = antennas.find(a =>
172
+ a.name.toLowerCase().includes(initialAntenna2Name.toLowerCase())
173
+ );
174
+ selectedAntenna2 = found || (antennas.length > 1 ? antennas[1] : antennas[0]);
175
+ } else if (antennas.length > 1) {
128
176
  selectedAntenna2 = antennas[1];
129
177
  } else {
130
178
  selectedAntenna2 = antennas[0]; // Use same antenna if only one available
@@ -138,9 +186,13 @@
138
186
  // Generate chart title based on current selection
139
187
  function generateChartTitle(): string {
140
188
  if (viewMode === 'compare' && selectedAntenna && selectedAntenna2) {
141
- return `${selectedAntenna.name} vs ${selectedAntenna2.name}`;
189
+ // Use custom labels if provided, otherwise use antenna names
190
+ const label1 = antenna1Label || selectedAntenna.name;
191
+ const label2 = antenna2Label || selectedAntenna2.name;
192
+ return `${label1} vs ${label2}`;
142
193
  } else if (selectedAntenna) {
143
- return `${selectedAntenna.name} - Pattern Analysis`;
194
+ const label = antenna1Label || selectedAntenna.name;
195
+ return `${label} - Pattern Analysis`;
144
196
  }
145
197
  return 'Antenna Pattern Analysis';
146
198
  }
@@ -1,3 +1,23 @@
1
- declare const AntennaDiagrams: import("svelte").Component<Record<string, never>, {}, "">;
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>
@@ -0,0 +1,3 @@
1
+ declare const CellHistoryDemo: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type CellHistoryDemo = ReturnType<typeof CellHistoryDemo>;
3
+ export default CellHistoryDemo;
@@ -71,8 +71,25 @@
71
71
 
72
72
  // Reactive column configuration - only changes when preset changes
73
73
  let columns = $derived.by(() => {
74
- // Only depend on columnPreset to avoid unnecessary recalculations
75
- return getColumnsForPreset(columnPreset, techColors, statusColors, headerFilters);
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';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartnet360/svelte-components",
3
- "version": "0.0.116",
3
+ "version": "0.0.117",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",