@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.
@@ -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>
@@ -0,0 +1,3 @@
1
+ declare const LayoutTreeView: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type LayoutTreeView = ReturnType<typeof LayoutTreeView>;
3
+ export default LayoutTreeView;