@smartnet360/svelte-components 0.0.17 → 0.0.20
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/Charts/ChartCard.svelte +25 -4
- package/dist/core/Charts/editor/ChartLayoutEditor.svelte +289 -0
- package/dist/core/Charts/editor/ChartLayoutEditor.svelte.d.ts +7 -0
- package/dist/core/Charts/editor/GridPreview.svelte +212 -0
- package/dist/core/Charts/editor/GridPreview.svelte.d.ts +3 -0
- package/dist/core/Charts/editor/KPIPicker.svelte +217 -0
- package/dist/core/Charts/editor/KPIPicker.svelte.d.ts +26 -0
- package/dist/core/Charts/editor/LayoutTreeView.svelte +180 -0
- package/dist/core/Charts/editor/LayoutTreeView.svelte.d.ts +3 -0
- package/dist/core/Charts/editor/PropertiesPanel.svelte +286 -0
- package/dist/core/Charts/editor/PropertiesPanel.svelte.d.ts +20 -0
- package/dist/core/Charts/editor/editorState.d.ts +41 -0
- package/dist/core/Charts/editor/editorState.js +320 -0
- package/dist/core/Charts/editor/exampleKPIs.d.ts +38 -0
- package/dist/core/Charts/editor/exampleKPIs.js +66 -0
- package/dist/core/Charts/index.d.ts +4 -0
- package/dist/core/Charts/index.js +4 -0
- package/package.json +1 -1
@@ -0,0 +1,217 @@
|
|
1
|
+
<svelte:options runes={true} />
|
2
|
+
|
3
|
+
<script lang="ts">
|
4
|
+
import { createEventDispatcher } from 'svelte';
|
5
|
+
import type { KPI } from '../charts.model.js';
|
6
|
+
|
7
|
+
interface Props {
|
8
|
+
show: boolean;
|
9
|
+
availableKPIs: KPI[];
|
10
|
+
}
|
11
|
+
|
12
|
+
let { show, availableKPIs }: Props = $props();
|
13
|
+
|
14
|
+
const dispatch = createEventDispatcher();
|
15
|
+
|
16
|
+
let searchQuery = $state('');
|
17
|
+
let selectedKPI = $state<KPI | null>(null);
|
18
|
+
|
19
|
+
// Local search function
|
20
|
+
function searchKPIs(query: string, kpis: KPI[]): KPI[] {
|
21
|
+
const lowerQuery = query.toLowerCase();
|
22
|
+
return kpis.filter(
|
23
|
+
kpi =>
|
24
|
+
kpi.name.toLowerCase().includes(lowerQuery) ||
|
25
|
+
kpi.rawName.toLowerCase().includes(lowerQuery)
|
26
|
+
);
|
27
|
+
}
|
28
|
+
|
29
|
+
let filteredKPIs = $derived(
|
30
|
+
searchQuery ? searchKPIs(searchQuery, availableKPIs) : availableKPIs
|
31
|
+
);
|
32
|
+
|
33
|
+
function handleSelect(kpi: KPI) {
|
34
|
+
selectedKPI = kpi;
|
35
|
+
}
|
36
|
+
|
37
|
+
function handleConfirm() {
|
38
|
+
if (selectedKPI) {
|
39
|
+
dispatch('select', selectedKPI);
|
40
|
+
handleClose();
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
function handleClose() {
|
45
|
+
searchQuery = '';
|
46
|
+
selectedKPI = null;
|
47
|
+
dispatch('close');
|
48
|
+
}
|
49
|
+
|
50
|
+
function handleKeydown(e: KeyboardEvent) {
|
51
|
+
if (e.key === 'Escape') {
|
52
|
+
handleClose();
|
53
|
+
} else if (e.key === 'Enter' && selectedKPI) {
|
54
|
+
handleConfirm();
|
55
|
+
}
|
56
|
+
}
|
57
|
+
</script>
|
58
|
+
|
59
|
+
<svelte:window on:keydown={handleKeydown} />
|
60
|
+
|
61
|
+
{#if show}
|
62
|
+
<!-- Bootstrap Modal -->
|
63
|
+
<div class="modal fade show d-block" tabindex="-1" role="dialog" style="background-color: rgba(0,0,0,0.5);">
|
64
|
+
<div class="modal-dialog modal-lg modal-dialog-scrollable" role="document">
|
65
|
+
<div class="modal-content">
|
66
|
+
<!-- Header -->
|
67
|
+
<div class="modal-header">
|
68
|
+
<h5 class="modal-title">
|
69
|
+
<i class="bi bi-search me-2"></i>
|
70
|
+
Select KPI
|
71
|
+
</h5>
|
72
|
+
<button type="button" class="btn-close" onclick={handleClose}></button>
|
73
|
+
</div>
|
74
|
+
|
75
|
+
<!-- Body -->
|
76
|
+
<div class="modal-body">
|
77
|
+
<!-- Search Input -->
|
78
|
+
<div class="mb-3">
|
79
|
+
<div class="input-group">
|
80
|
+
<span class="input-group-text">
|
81
|
+
<i class="bi bi-search"></i>
|
82
|
+
</span>
|
83
|
+
<input
|
84
|
+
type="text"
|
85
|
+
class="form-control"
|
86
|
+
placeholder="Search KPIs by name or raw name..."
|
87
|
+
bind:value={searchQuery}
|
88
|
+
autofocus
|
89
|
+
/>
|
90
|
+
{#if searchQuery}
|
91
|
+
<button
|
92
|
+
class="btn btn-outline-secondary"
|
93
|
+
onclick={() => (searchQuery = '')}
|
94
|
+
>
|
95
|
+
<i class="bi bi-x"></i>
|
96
|
+
</button>
|
97
|
+
{/if}
|
98
|
+
</div>
|
99
|
+
<small class="text-muted">
|
100
|
+
Showing {filteredKPIs.length} of {availableKPIs.length} KPIs
|
101
|
+
</small>
|
102
|
+
</div>
|
103
|
+
|
104
|
+
<!-- KPI List -->
|
105
|
+
<div class="kpi-list-container">
|
106
|
+
{#if filteredKPIs.length > 0}
|
107
|
+
<div class="list-group">
|
108
|
+
{#each filteredKPIs as kpi}
|
109
|
+
<button
|
110
|
+
class="list-group-item list-group-item-action"
|
111
|
+
class:active={selectedKPI?.rawName === kpi.rawName}
|
112
|
+
onclick={() => handleSelect(kpi)}
|
113
|
+
>
|
114
|
+
<div class="d-flex w-100 justify-content-between align-items-start">
|
115
|
+
<div class="flex-grow-1">
|
116
|
+
<h6 class="mb-1">{kpi.name}</h6>
|
117
|
+
<p class="mb-1 text-muted small">
|
118
|
+
<code>{kpi.rawName}</code>
|
119
|
+
</p>
|
120
|
+
<div class="d-flex gap-2 align-items-center">
|
121
|
+
<span class="badge bg-secondary">{kpi.scale}</span>
|
122
|
+
<span class="badge bg-info">{kpi.unit}</span>
|
123
|
+
{#if kpi.color}
|
124
|
+
<span class="color-preview" style="background-color: {kpi.color}"></span>
|
125
|
+
{/if}
|
126
|
+
</div>
|
127
|
+
</div>
|
128
|
+
{#if selectedKPI?.rawName === kpi.rawName}
|
129
|
+
<i class="bi bi-check-circle-fill text-success fs-4"></i>
|
130
|
+
{/if}
|
131
|
+
</div>
|
132
|
+
</button>
|
133
|
+
{/each}
|
134
|
+
</div>
|
135
|
+
{:else}
|
136
|
+
<div class="alert alert-warning">
|
137
|
+
<i class="bi bi-exclamation-triangle me-2"></i>
|
138
|
+
No KPIs found matching "{searchQuery}"
|
139
|
+
</div>
|
140
|
+
{/if}
|
141
|
+
</div>
|
142
|
+
</div>
|
143
|
+
|
144
|
+
<!-- Footer -->
|
145
|
+
<div class="modal-footer">
|
146
|
+
<button type="button" class="btn btn-secondary" onclick={handleClose}>
|
147
|
+
Cancel
|
148
|
+
</button>
|
149
|
+
<button
|
150
|
+
type="button"
|
151
|
+
class="btn btn-primary"
|
152
|
+
onclick={handleConfirm}
|
153
|
+
disabled={!selectedKPI}
|
154
|
+
>
|
155
|
+
<i class="bi bi-check-lg me-1"></i>
|
156
|
+
Add KPI
|
157
|
+
</button>
|
158
|
+
</div>
|
159
|
+
</div>
|
160
|
+
</div>
|
161
|
+
</div>
|
162
|
+
{/if}
|
163
|
+
|
164
|
+
<style>
|
165
|
+
.modal {
|
166
|
+
overflow-y: auto;
|
167
|
+
}
|
168
|
+
|
169
|
+
.kpi-list-container {
|
170
|
+
max-height: 400px;
|
171
|
+
overflow-y: auto;
|
172
|
+
}
|
173
|
+
|
174
|
+
.list-group-item {
|
175
|
+
cursor: pointer;
|
176
|
+
transition: all 0.15s;
|
177
|
+
}
|
178
|
+
|
179
|
+
.list-group-item:hover:not(.active) {
|
180
|
+
background-color: #f8f9fa;
|
181
|
+
}
|
182
|
+
|
183
|
+
.list-group-item.active {
|
184
|
+
background-color: #0d6efd;
|
185
|
+
border-color: #0d6efd;
|
186
|
+
}
|
187
|
+
|
188
|
+
.list-group-item.active h6,
|
189
|
+
.list-group-item.active p,
|
190
|
+
.list-group-item.active .text-muted {
|
191
|
+
color: white !important;
|
192
|
+
}
|
193
|
+
|
194
|
+
.list-group-item.active code {
|
195
|
+
background-color: rgba(255, 255, 255, 0.2);
|
196
|
+
color: white;
|
197
|
+
}
|
198
|
+
|
199
|
+
.color-preview {
|
200
|
+
display: inline-block;
|
201
|
+
width: 20px;
|
202
|
+
height: 20px;
|
203
|
+
border-radius: 4px;
|
204
|
+
border: 2px solid #dee2e6;
|
205
|
+
}
|
206
|
+
|
207
|
+
code {
|
208
|
+
background-color: #f8f9fa;
|
209
|
+
padding: 0.2rem 0.4rem;
|
210
|
+
border-radius: 3px;
|
211
|
+
font-size: 0.875rem;
|
212
|
+
}
|
213
|
+
|
214
|
+
.badge {
|
215
|
+
font-size: 0.75rem;
|
216
|
+
}
|
217
|
+
</style>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import type { KPI } from '../charts.model.js';
|
2
|
+
interface Props {
|
3
|
+
show: boolean;
|
4
|
+
availableKPIs: KPI[];
|
5
|
+
}
|
6
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
7
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
8
|
+
$$bindings?: Bindings;
|
9
|
+
} & Exports;
|
10
|
+
(internal: unknown, props: Props & {
|
11
|
+
$$events?: Events;
|
12
|
+
$$slots?: Slots;
|
13
|
+
}): Exports & {
|
14
|
+
$set?: any;
|
15
|
+
$on?: any;
|
16
|
+
};
|
17
|
+
z_$$bindings?: Bindings;
|
18
|
+
}
|
19
|
+
declare const KpiPicker: $$__sveltets_2_IsomorphicComponent<Props, {
|
20
|
+
select: CustomEvent<any>;
|
21
|
+
close: CustomEvent<any>;
|
22
|
+
} & {
|
23
|
+
[evt: string]: CustomEvent<any>;
|
24
|
+
}, {}, {}, "">;
|
25
|
+
type KpiPicker = InstanceType<typeof KpiPicker>;
|
26
|
+
export default KpiPicker;
|
@@ -0,0 +1,180 @@
|
|
1
|
+
<svelte:options runes={true} />
|
2
|
+
|
3
|
+
<script lang="ts">
|
4
|
+
import { editorStore, currentLayout, selection } from './editorState.js';
|
5
|
+
|
6
|
+
function handleSelectLayout() {
|
7
|
+
editorStore.select({ type: 'layout' });
|
8
|
+
}
|
9
|
+
|
10
|
+
function handleSelectSection(sectionId: string) {
|
11
|
+
editorStore.select({ type: 'section', sectionId });
|
12
|
+
}
|
13
|
+
|
14
|
+
function handleSelectChart(sectionId: string, chartIndex: number) {
|
15
|
+
editorStore.select({ type: 'chart', sectionId, chartIndex });
|
16
|
+
}
|
17
|
+
|
18
|
+
function handleAddSection() {
|
19
|
+
editorStore.addSection();
|
20
|
+
}
|
21
|
+
|
22
|
+
function handleAddChart(sectionId: string) {
|
23
|
+
editorStore.addChart(sectionId);
|
24
|
+
}
|
25
|
+
|
26
|
+
function isSelected(type: string, sectionId?: string, chartIndex?: number): boolean {
|
27
|
+
if ($selection.type !== type) return false;
|
28
|
+
if (type === 'section') return $selection.sectionId === sectionId;
|
29
|
+
if (type === 'chart') return $selection.sectionId === sectionId && $selection.chartIndex === chartIndex;
|
30
|
+
return type === 'layout';
|
31
|
+
}
|
32
|
+
</script>
|
33
|
+
|
34
|
+
<div class="tree-view h-100 d-flex flex-column">
|
35
|
+
<!-- Header -->
|
36
|
+
<div class="p-3 border-bottom bg-light">
|
37
|
+
<h6 class="mb-0">
|
38
|
+
<i class="bi bi-diagram-3"></i> Structure
|
39
|
+
</h6>
|
40
|
+
</div>
|
41
|
+
|
42
|
+
<!-- Tree Content -->
|
43
|
+
<div class="flex-grow-1 overflow-auto p-2">
|
44
|
+
{#if $currentLayout}
|
45
|
+
<!-- Layout Node -->
|
46
|
+
<div class="list-group mb-2">
|
47
|
+
<div
|
48
|
+
class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"
|
49
|
+
class:active={isSelected('layout')}
|
50
|
+
role="button"
|
51
|
+
tabindex="0"
|
52
|
+
onclick={handleSelectLayout}
|
53
|
+
onkeydown={(e) => e.key === 'Enter' && handleSelectLayout()}
|
54
|
+
>
|
55
|
+
<div>
|
56
|
+
<i class="bi bi-file-earmark-text me-2"></i>
|
57
|
+
<strong>{$currentLayout.layoutName}</strong>
|
58
|
+
</div>
|
59
|
+
<button
|
60
|
+
class="btn btn-sm btn-primary"
|
61
|
+
onclick={(e) => {
|
62
|
+
e.stopPropagation();
|
63
|
+
handleAddSection();
|
64
|
+
}}
|
65
|
+
title="Add Section"
|
66
|
+
>
|
67
|
+
<i class="bi bi-plus-lg me-1"></i>
|
68
|
+
<span>Section</span>
|
69
|
+
</button>
|
70
|
+
</div>
|
71
|
+
</div>
|
72
|
+
|
73
|
+
<!-- Sections -->
|
74
|
+
{#each $currentLayout.sections as section, sIdx}
|
75
|
+
<div class="ms-3 mb-2">
|
76
|
+
<div class="list-group">
|
77
|
+
<!-- Section Node -->
|
78
|
+
<div
|
79
|
+
class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"
|
80
|
+
class:active={isSelected('section', section.id)}
|
81
|
+
role="button"
|
82
|
+
tabindex="0"
|
83
|
+
onclick={() => handleSelectSection(section.id)}
|
84
|
+
onkeydown={(e) => e.key === 'Enter' && handleSelectSection(section.id)}
|
85
|
+
>
|
86
|
+
<div class="flex-grow-1">
|
87
|
+
<i class="bi bi-grid-3x3-gap me-2"></i>
|
88
|
+
<span>{section.title}</span>
|
89
|
+
<span class="badge bg-secondary ms-2">{section.grid || '2x2'}</span>
|
90
|
+
</div>
|
91
|
+
<button
|
92
|
+
class="btn btn-sm btn-success"
|
93
|
+
onclick={(e) => {
|
94
|
+
e.stopPropagation();
|
95
|
+
handleAddChart(section.id);
|
96
|
+
}}
|
97
|
+
title="Add Chart"
|
98
|
+
>
|
99
|
+
<i class="bi bi-plus-lg me-1"></i>
|
100
|
+
<span>Chart</span>
|
101
|
+
</button>
|
102
|
+
</div>
|
103
|
+
|
104
|
+
<!-- Charts in Section -->
|
105
|
+
{#if section.charts && section.charts.length > 0}
|
106
|
+
<div class="ms-3 mt-1">
|
107
|
+
{#each section.charts as chart, cIdx}
|
108
|
+
<button
|
109
|
+
class="list-group-item list-group-item-action mb-1"
|
110
|
+
class:active={isSelected('chart', section.id, cIdx)}
|
111
|
+
onclick={() => handleSelectChart(section.id, cIdx)}
|
112
|
+
>
|
113
|
+
<div class="d-flex align-items-start">
|
114
|
+
<i class="bi bi-bar-chart me-2 mt-1"></i>
|
115
|
+
<div class="flex-grow-1">
|
116
|
+
<div class="fw-bold">{chart.title}</div>
|
117
|
+
<small class="text-muted">
|
118
|
+
{#if chart.pos}
|
119
|
+
<span class="badge bg-info me-1">Pos: {chart.pos}</span>
|
120
|
+
{/if}
|
121
|
+
<span class="badge bg-success me-1">L: {chart.yLeft.length}</span>
|
122
|
+
<span class="badge bg-warning">R: {chart.yRight.length}</span>
|
123
|
+
</small>
|
124
|
+
</div>
|
125
|
+
</div>
|
126
|
+
</button>
|
127
|
+
{/each}
|
128
|
+
</div>
|
129
|
+
{:else}
|
130
|
+
<div class="ms-3 mt-1">
|
131
|
+
<div class="text-muted small p-2">
|
132
|
+
<i class="bi bi-info-circle me-1"></i>
|
133
|
+
No charts yet
|
134
|
+
</div>
|
135
|
+
</div>
|
136
|
+
{/if}
|
137
|
+
</div>
|
138
|
+
</div>
|
139
|
+
{:else}
|
140
|
+
<div class="alert alert-info small m-2">
|
141
|
+
<i class="bi bi-info-circle me-1"></i>
|
142
|
+
No sections yet. Click the blue <strong>"+ Section"</strong> button above.
|
143
|
+
</div>
|
144
|
+
{/each}
|
145
|
+
{/if}
|
146
|
+
</div>
|
147
|
+
</div>
|
148
|
+
|
149
|
+
<style>
|
150
|
+
.tree-view {
|
151
|
+
font-size: 0.9rem;
|
152
|
+
}
|
153
|
+
|
154
|
+
.list-group-item {
|
155
|
+
padding: 0.5rem 0.75rem;
|
156
|
+
border-radius: 0.25rem;
|
157
|
+
margin-bottom: 0.25rem;
|
158
|
+
cursor: pointer;
|
159
|
+
}
|
160
|
+
|
161
|
+
.list-group-item.active {
|
162
|
+
background-color: #0d6efd;
|
163
|
+
border-color: #0d6efd;
|
164
|
+
color: white;
|
165
|
+
}
|
166
|
+
|
167
|
+
.list-group-item:not(.active):hover {
|
168
|
+
background-color: #f8f9fa;
|
169
|
+
}
|
170
|
+
|
171
|
+
.badge {
|
172
|
+
font-size: 0.7rem;
|
173
|
+
padding: 0.25rem 0.5rem;
|
174
|
+
}
|
175
|
+
|
176
|
+
.btn-sm {
|
177
|
+
padding: 0.15rem 0.4rem;
|
178
|
+
font-size: 0.75rem;
|
179
|
+
}
|
180
|
+
</style>
|