@things-factory/kpi 9.0.15 → 9.0.17

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.
Files changed (28) hide show
  1. package/client/pages/kpi/kpi-grade-editor.ts +80 -207
  2. package/client/pages/kpi/kpi-list-page.ts +53 -11
  3. package/client/pages/kpi/kpi-viz-editor.ts +353 -0
  4. package/client/pages/kpi-dashboard/kpi-dashboard.ts +128 -1
  5. package/dist-client/pages/kpi/kpi-grade-editor.d.ts +13 -13
  6. package/dist-client/pages/kpi/kpi-grade-editor.js +84 -197
  7. package/dist-client/pages/kpi/kpi-grade-editor.js.map +1 -1
  8. package/dist-client/pages/kpi/kpi-list-page.d.ts +6 -2
  9. package/dist-client/pages/kpi/kpi-list-page.js +50 -10
  10. package/dist-client/pages/kpi/kpi-list-page.js.map +1 -1
  11. package/dist-client/pages/kpi/kpi-viz-editor.d.ts +20 -0
  12. package/dist-client/pages/kpi/kpi-viz-editor.js +364 -0
  13. package/dist-client/pages/kpi/kpi-viz-editor.js.map +1 -0
  14. package/dist-client/pages/kpi-dashboard/kpi-dashboard.d.ts +2 -0
  15. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js +117 -1
  16. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js.map +1 -1
  17. package/dist-client/tsconfig.tsbuildinfo +1 -1
  18. package/dist-server/service/index.d.ts +2 -2
  19. package/dist-server/service/kpi/kpi-type.d.ts +3 -3
  20. package/dist-server/service/kpi/kpi-type.js +4 -4
  21. package/dist-server/service/kpi/kpi-type.js.map +1 -1
  22. package/dist-server/service/kpi/kpi.d.ts +18 -1
  23. package/dist-server/service/kpi/kpi.js +26 -4
  24. package/dist-server/service/kpi/kpi.js.map +1 -1
  25. package/dist-server/tsconfig.tsbuildinfo +1 -1
  26. package/package.json +2 -2
  27. package/server/service/kpi/kpi-type.ts +7 -7
  28. package/server/service/kpi/kpi.ts +27 -4
@@ -0,0 +1,353 @@
1
+ import '@material/web/button/elevated-button.js'
2
+ import '@material/web/select/outlined-select.js'
3
+ import '@material/web/textfield/outlined-text-field.js'
4
+ import '@material/web/icon/icon.js'
5
+
6
+ import { PageView } from '@operato/shell'
7
+ import { css, html, LitElement } from 'lit'
8
+ import { customElement, property } from 'lit/decorators.js'
9
+ import { client } from '@operato/graphql'
10
+ import { i18next, localize } from '@operato/i18n'
11
+ import { notify } from '@operato/layout'
12
+ import { OxPopup } from '@operato/popup'
13
+ import gql from 'graphql-tag'
14
+ import { CommonHeaderStyles, ScrollbarStyles } from '@operato/styles'
15
+
16
+ const VIZ_TYPES = [
17
+ { value: 'CARD', label: '카드', icon: 'dashboard' },
18
+ { value: 'GAUGE', label: '게이지', icon: 'speed' },
19
+ { value: 'PROGRESS', label: '진행률', icon: 'linear_scale' },
20
+ { value: 'BAR', label: '막대 차트', icon: 'bar_chart' },
21
+ { value: 'LINE', label: '선 차트', icon: 'show_chart' },
22
+ { value: 'PIE', label: '파이 차트', icon: 'pie_chart' },
23
+ { value: 'DONUT', label: '도넛 차트', icon: 'donut_large' },
24
+ { value: 'RADAR', label: '레이더 차트', icon: 'radar' },
25
+ { value: 'BULLET', label: '불릿 차트', icon: 'track_changes' },
26
+ { value: 'THERMOMETER', label: '온도계', icon: 'thermostat' },
27
+ { value: 'SPEEDOMETER', label: '속도계', icon: 'speed' },
28
+ { value: 'ICON', label: '아이콘', icon: 'emoji_events' },
29
+ { value: 'BADGE', label: '배지', icon: 'badge' },
30
+ { value: 'TEXT', label: '텍스트', icon: 'text_fields' },
31
+ { value: 'TABLE', label: '테이블', icon: 'table_chart' }
32
+ ]
33
+
34
+ @customElement('kpi-viz-editor')
35
+ export class KpiVizEditor extends localize(i18next)(LitElement) {
36
+ static styles = [
37
+ CommonHeaderStyles,
38
+ ScrollbarStyles,
39
+ css`
40
+ :host {
41
+ display: flex;
42
+ flex-direction: column;
43
+ background-color: var(--md-sys-color-surface, #f4f6fa);
44
+ }
45
+
46
+ .viz-editor {
47
+ flex: 1;
48
+ display: flex;
49
+ flex-direction: column;
50
+ overflow-y: auto;
51
+
52
+ background: white;
53
+ padding: 24px;
54
+ }
55
+
56
+ .form-group {
57
+ margin-bottom: 20px;
58
+ }
59
+
60
+ .form-group label {
61
+ display: block;
62
+ margin-bottom: 8px;
63
+ font-weight: 500;
64
+ color: #555;
65
+ }
66
+
67
+ .form-options {
68
+ flex: 1;
69
+ display: flex;
70
+ flex-direction: row;
71
+ }
72
+
73
+ .viz-type-grid {
74
+ display: grid;
75
+ grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
76
+ gap: 12px;
77
+ margin-bottom: 20px;
78
+ }
79
+
80
+ .viz-type-option {
81
+ display: flex;
82
+ flex-direction: column;
83
+ align-items: center;
84
+ padding: 16px 12px;
85
+ border: 2px solid #e0e0e0;
86
+ border-radius: 8px;
87
+ cursor: pointer;
88
+ transition: all 0.2s;
89
+ text-align: center;
90
+ }
91
+
92
+ .viz-type-option:hover {
93
+ border-color: #2196f3;
94
+ background: #f5f9ff;
95
+ }
96
+
97
+ .viz-type-option.selected {
98
+ border-color: #2196f3;
99
+ background: #e3f2fd;
100
+ }
101
+
102
+ .viz-type-option md-icon {
103
+ font-size: 24px;
104
+ margin-bottom: 8px;
105
+ color: #666;
106
+ }
107
+
108
+ .viz-type-option.selected md-icon {
109
+ color: #2196f3;
110
+ }
111
+
112
+ .viz-type-option .label {
113
+ font-size: 0.9rem;
114
+ font-weight: 500;
115
+ color: #333;
116
+ }
117
+
118
+ .viz-meta-section {
119
+ margin-top: 24px;
120
+ padding-top: 20px;
121
+ border-top: 1px solid #eee;
122
+ }
123
+
124
+ .color-picker {
125
+ display: flex;
126
+ gap: 12px;
127
+ align-items: center;
128
+ margin-bottom: 16px;
129
+ }
130
+
131
+ .color-input {
132
+ width: 60px;
133
+ height: 40px;
134
+ border: none;
135
+ border-radius: 6px;
136
+ cursor: pointer;
137
+ }
138
+
139
+ .buttons {
140
+ display: flex;
141
+ gap: 12px;
142
+ justify-content: flex-end;
143
+ margin-top: 24px;
144
+ padding-top: 20px;
145
+ border-top: 1px solid #eee;
146
+ }
147
+
148
+ .preview {
149
+ flex: 1;
150
+ margin: 30px 16px 16px 16px;
151
+ padding: 16px;
152
+ background: #f8f9fa;
153
+ border-radius: 8px;
154
+ border: 1px solid #e9ecef;
155
+ }
156
+
157
+ .preview h4 {
158
+ margin: 0 0 12px 0;
159
+ color: #495057;
160
+ font-size: 0.95rem;
161
+ }
162
+
163
+ .footer span {
164
+ font-size: 0.8em;
165
+ color: var(--md-sys-color-on-surface);
166
+ line-height: 1.5;
167
+ padding: 10px;
168
+ }
169
+ `
170
+ ]
171
+
172
+ @property({ type: Object }) kpi: any = null
173
+ @property({ type: String }) selectedVizType: string = 'CARD'
174
+ @property({ type: Object }) vizMeta: any = {}
175
+ @property({ type: Object }) onSave: Function = () => {}
176
+ @property({ type: Object }) onCancel: Function = () => {}
177
+
178
+ connectedCallback() {
179
+ super.connectedCallback()
180
+ if (this.kpi) {
181
+ this.selectedVizType = this.kpi.vizType || 'CARD'
182
+ this.vizMeta = this.kpi.vizMeta || {}
183
+ }
184
+ }
185
+
186
+ _selectVizType(type: string) {
187
+ this.selectedVizType = type
188
+ this.requestUpdate()
189
+ }
190
+
191
+ _updateVizMeta(key: string, value: any) {
192
+ this.vizMeta = { ...this.vizMeta, [key]: value }
193
+ this.requestUpdate()
194
+ }
195
+
196
+ _renderPreview() {
197
+ const kpiValue = this.kpi?.value?.value || 75
198
+ const targetValue = this.kpi?.targetValue || 100
199
+ const unit = this.kpi?.unit || ''
200
+ const color = this.vizMeta.color || '#2196f3'
201
+ const icon = this.vizMeta.icon || 'trending_up'
202
+
203
+ switch (this.selectedVizType) {
204
+ case 'CARD':
205
+ return html`
206
+ <div
207
+ style="display:flex;align-items:center;gap:12px;padding:16px;background:white;border-radius:8px;border:1px solid #e0e0e0;"
208
+ >
209
+ <md-icon style="color:${color};font-size:32px;">${icon}</md-icon>
210
+ <div>
211
+ <div style="font-size:1.5rem;font-weight:bold;color:${color};">${kpiValue}${unit}</div>
212
+ <div style="font-size:0.9rem;color:#666;">목표: ${targetValue}${unit}</div>
213
+ </div>
214
+ </div>
215
+ `
216
+ case 'GAUGE':
217
+ const percentage = Math.min((kpiValue / targetValue) * 100, 100)
218
+ return html`
219
+ <div style="text-align:center;padding:16px;">
220
+ <div
221
+ style="width:120px;height:60px;border-radius:60px 60px 0 0;background:conic-gradient(${color} 0deg ${percentage *
222
+ 3.6}deg, #e0e0e0 ${percentage * 3.6}deg 360deg);margin:0 auto;position:relative;"
223
+ >
224
+ <div
225
+ style="position:absolute;bottom:0;left:50%;transform:translateX(-50%);font-size:1.2rem;font-weight:bold;color:${color};"
226
+ >
227
+ ${kpiValue}${unit}
228
+ </div>
229
+ </div>
230
+ </div>
231
+ `
232
+ case 'PROGRESS':
233
+ const progressPercentage = Math.min((kpiValue / targetValue) * 100, 100)
234
+ return html`
235
+ <div style="padding:16px;">
236
+ <div style="background:#e0e0e0;height:20px;border-radius:10px;overflow:hidden;">
237
+ <div style="background:${color};height:100%;width:${progressPercentage}%;transition:width 0.3s;"></div>
238
+ </div>
239
+ <div style="text-align:center;margin-top:8px;font-weight:bold;color:${color};">
240
+ ${kpiValue}${unit} / ${targetValue}${unit}
241
+ </div>
242
+ </div>
243
+ `
244
+ case 'ICON':
245
+ return html`
246
+ <div style="text-align:center;padding:16px;">
247
+ <md-icon style="color:${color};font-size:48px;">${icon}</md-icon>
248
+ <div style="font-size:1.2rem;font-weight:bold;color:${color};margin-top:8px;">${kpiValue}${unit}</div>
249
+ </div>
250
+ `
251
+ default:
252
+ return html` <div style="padding:16px;text-align:center;color:#666;">${this.selectedVizType} 미리보기</div> `
253
+ }
254
+ }
255
+
256
+ render() {
257
+ return html`
258
+ <div class="viz-editor">
259
+ <div class="form-group">
260
+ <label>시각화 타입 선택</label>
261
+ <div class="viz-type-grid">
262
+ ${VIZ_TYPES.map(
263
+ type => html`
264
+ <div
265
+ class="viz-type-option ${this.selectedVizType === type.value ? 'selected' : ''}"
266
+ @click=${() => this._selectVizType(type.value)}
267
+ >
268
+ <md-icon>${type.icon}</md-icon>
269
+ <div class="label">${type.label}</div>
270
+ </div>
271
+ `
272
+ )}
273
+ </div>
274
+ </div>
275
+
276
+ <div class="form-options">
277
+ <div class="viz-meta-section">
278
+ <label>시각화 옵션</label>
279
+
280
+ <div class="form-group">
281
+ <label>색상</label>
282
+ <div class="color-picker">
283
+ <input
284
+ type="color"
285
+ class="color-input"
286
+ .value=${this.vizMeta.color || '#2196f3'}
287
+ @change=${(e: any) => this._updateVizMeta('color', e.target.value)}
288
+ />
289
+ <md-outlined-text-field
290
+ label="색상 코드"
291
+ .value=${this.vizMeta.color || '#2196f3'}
292
+ @change=${(e: any) => this._updateVizMeta('color', e.target.value)}
293
+ ></md-outlined-text-field>
294
+ </div>
295
+ </div>
296
+
297
+ <div class="form-group">
298
+ <label>아이콘</label>
299
+ <md-outlined-text-field
300
+ label="Material Icons 이름"
301
+ .value=${this.vizMeta.icon || 'trending_up'}
302
+ @change=${(e: any) => this._updateVizMeta('icon', e.target.value)}
303
+ ></md-outlined-text-field>
304
+ </div>
305
+
306
+ <div class="form-group">
307
+ <label>최소값</label>
308
+ <md-outlined-text-field
309
+ type="number"
310
+ label="최소값"
311
+ .value=${this.vizMeta.minValue || 0}
312
+ @change=${(e: any) => this._updateVizMeta('minValue', parseFloat(e.target.value))}
313
+ ></md-outlined-text-field>
314
+ </div>
315
+
316
+ <div class="form-group">
317
+ <label>최대값</label>
318
+ <md-outlined-text-field
319
+ type="number"
320
+ label="최대값"
321
+ .value=${this.vizMeta.maxValue || 100}
322
+ @change=${(e: any) => this._updateVizMeta('maxValue', parseFloat(e.target.value))}
323
+ ></md-outlined-text-field>
324
+ </div>
325
+
326
+ <div class="form-group">
327
+ <label>소수점 자릿수</label>
328
+ <md-outlined-text-field
329
+ type="number"
330
+ label="소수점 자릿수"
331
+ .value=${this.vizMeta.decimalPlaces || 0}
332
+ @change=${(e: any) => this._updateVizMeta('decimalPlaces', parseInt(e.target.value))}
333
+ ></md-outlined-text-field>
334
+ </div>
335
+ </div>
336
+
337
+ <div class="preview">
338
+ <h4>미리보기</h4>
339
+ ${this._renderPreview()}
340
+ </div>
341
+ </div>
342
+ </div>
343
+
344
+ <div class="footer">
345
+ <div filler></div>
346
+ <button type="button" @click=${this.onCancel}><md-icon>cancel</md-icon>취소</button>
347
+ <button type="button" @click=${() => this.onSave(this.selectedVizType, this.vizMeta)} done>
348
+ <md-icon>save</md-icon>저장
349
+ </button>
350
+ </div>
351
+ `
352
+ }
353
+ }
@@ -5,6 +5,7 @@ import { ScrollbarStyles } from '@operato/styles'
5
5
  import { client } from '@operato/graphql'
6
6
  import gql from 'graphql-tag'
7
7
  import { state } from 'lit/decorators.js'
8
+ import '@material/web/icon/icon.js'
8
9
 
9
10
  import './kpi-performance-summary'
10
11
  import './kpi-grade-visualization'
@@ -110,6 +111,8 @@ export class KpiDashboardPage extends PageView {
110
111
  targetValue
111
112
  unit
112
113
  grades
114
+ vizType
115
+ vizMeta
113
116
  histories(limit: 1) {
114
117
  version
115
118
  updatedAt
@@ -174,6 +177,130 @@ export class KpiDashboardPage extends PageView {
174
177
  this.modalKpiName = ''
175
178
  }
176
179
 
180
+ _renderKpiValue(kpi: any) {
181
+ const kpiValue = kpi.value?.value ?? 0
182
+ const targetValue = kpi.targetValue ?? 100
183
+ const unit = kpi.unit ?? ''
184
+ const vizType = kpi.vizType || 'CARD'
185
+ const vizMeta = kpi.vizMeta || {}
186
+ const color = vizMeta.color || '#3a3ad6'
187
+ const icon = vizMeta.icon || 'trending_up'
188
+ const minValue = vizMeta.minValue || 0
189
+ const maxValue = vizMeta.maxValue || 100
190
+ const decimalPlaces = vizMeta.decimalPlaces || 0
191
+
192
+ const formattedValue = typeof kpiValue === 'number' ? kpiValue.toFixed(decimalPlaces) : kpiValue
193
+
194
+ switch (vizType) {
195
+ case 'GAUGE':
196
+ const gaugePercentage = Math.min(((kpiValue - minValue) / (maxValue - minValue)) * 100, 100)
197
+ return html`
198
+ <div style="text-align:center;margin:16px 0;">
199
+ <div
200
+ style="width:120px;height:60px;border-radius:60px 60px 0 0;background:conic-gradient(${color} 0deg ${gaugePercentage *
201
+ 3.6}deg, #e0e0e0 ${gaugePercentage * 3.6}deg 360deg);margin:0 auto;position:relative;"
202
+ >
203
+ <div
204
+ style="position:absolute;bottom:0;left:50%;transform:translateX(-50%);font-size:1.2rem;font-weight:bold;color:${color};"
205
+ >
206
+ ${formattedValue}${unit}
207
+ </div>
208
+ </div>
209
+ </div>
210
+ `
211
+
212
+ case 'PROGRESS':
213
+ const progressPercentage = Math.min(((kpiValue - minValue) / (maxValue - minValue)) * 100, 100)
214
+ return html`
215
+ <div style="margin:16px 0;">
216
+ <div style="background:#e0e0e0;height:20px;border-radius:10px;overflow:hidden;">
217
+ <div style="background:${color};height:100%;width:${progressPercentage}%;transition:width 0.3s;"></div>
218
+ </div>
219
+ <div style="text-align:center;margin-top:8px;font-weight:bold;color:${color};font-size:1.2rem;">
220
+ ${formattedValue}${unit}
221
+ </div>
222
+ </div>
223
+ `
224
+
225
+ case 'ICON':
226
+ return html`
227
+ <div style="text-align:center;margin:16px 0;">
228
+ <md-icon style="color:${color};font-size:48px;">${icon}</md-icon>
229
+ <div style="font-size:1.5rem;font-weight:bold;color:${color};margin-top:8px;">${formattedValue}${unit}</div>
230
+ </div>
231
+ `
232
+
233
+ case 'THERMOMETER':
234
+ const thermoPercentage = Math.min(((kpiValue - minValue) / (maxValue - minValue)) * 100, 100)
235
+ return html`
236
+ <div style="text-align:center;margin:16px 0;">
237
+ <div
238
+ style="width:40px;height:120px;background:#e0e0e0;border-radius:20px;margin:0 auto;position:relative;overflow:hidden;"
239
+ >
240
+ <div
241
+ style="position:absolute;bottom:0;width:100%;height:${thermoPercentage}%;background:${color};border-radius:20px;"
242
+ ></div>
243
+ </div>
244
+ <div style="font-size:1.2rem;font-weight:bold;color:${color};margin-top:8px;">${formattedValue}${unit}</div>
245
+ </div>
246
+ `
247
+
248
+ case 'SPEEDOMETER':
249
+ const speedPercentage = Math.min(((kpiValue - minValue) / (maxValue - minValue)) * 100, 100)
250
+ const angle = (speedPercentage / 100) * 180 - 90 // -90도에서 90도까지
251
+ return html`
252
+ <div style="text-align:center;margin:16px 0;">
253
+ <div
254
+ style="width:120px;height:60px;border-radius:60px 60px 0 0;background:conic-gradient(${color} -90deg ${angle}deg, #e0e0e0 ${angle}deg 90deg);margin:0 auto;position:relative;"
255
+ >
256
+ <div
257
+ style="position:absolute;bottom:0;left:50%;transform:translateX(-50%);font-size:1.2rem;font-weight:bold;color:${color};"
258
+ >
259
+ ${formattedValue}${unit}
260
+ </div>
261
+ </div>
262
+ </div>
263
+ `
264
+
265
+ case 'BULLET':
266
+ const bulletPercentage = Math.min(((kpiValue - minValue) / (maxValue - minValue)) * 100, 100)
267
+ const targetPercentage = Math.min(((targetValue - minValue) / (maxValue - minValue)) * 100, 100)
268
+ return html`
269
+ <div style="margin:16px 0;">
270
+ <div style="background:#e0e0e0;height:30px;border-radius:15px;overflow:hidden;position:relative;">
271
+ <div style="background:${color};height:100%;width:${bulletPercentage}%;transition:width 0.3s;"></div>
272
+ <div
273
+ style="position:absolute;top:0;left:${targetPercentage}%;width:2px;height:100%;background:#333;"
274
+ ></div>
275
+ </div>
276
+ <div style="text-align:center;margin-top:8px;font-weight:bold;color:${color};font-size:1.2rem;">
277
+ ${formattedValue}${unit}
278
+ </div>
279
+ </div>
280
+ `
281
+
282
+ case 'TEXT':
283
+ return html`
284
+ <div style="text-align:center;margin:16px 0;">
285
+ <div style="font-size:1.5rem;font-weight:bold;color:${color};">${formattedValue}${unit}</div>
286
+ </div>
287
+ `
288
+
289
+ case 'BADGE':
290
+ return html`
291
+ <div style="text-align:center;margin:16px 0;">
292
+ <span
293
+ style="display:inline-block;padding:8px 16px;background:${color};color:white;border-radius:20px;font-size:1.2rem;font-weight:bold;"
294
+ >${formattedValue}${unit}</span
295
+ >
296
+ </div>
297
+ `
298
+
299
+ default: // CARD, BAR, LINE, PIE, DONUT, RADAR, TABLE
300
+ return html` <div class="kpi-value">${formattedValue}${unit}</div> `
301
+ }
302
+ }
303
+
177
304
  get context() {
178
305
  return {
179
306
  title: 'KPI 대시보드',
@@ -223,7 +350,7 @@ export class KpiDashboardPage extends PageView {
223
350
  kpi => html`
224
351
  <div class="kpi-card">
225
352
  <div class="kpi-name">${kpi.name}</div>
226
- <div class="kpi-value">${kpi.value?.value ?? '데이터 없음'}${kpi.unit ?? ''}</div>
353
+ ${this._renderKpiValue(kpi)}
227
354
  <div class="kpi-target">목표: ${kpi.targetValue ?? '-'}${kpi.unit ?? ''}</div>
228
355
  <!-- 등급 시각화 -->
229
356
  <div style="margin-top:8px;">
@@ -1,11 +1,11 @@
1
1
  import '@material/web/button/elevated-button.js';
2
2
  import '@material/web/button/filled-button.js';
3
3
  import '@material/web/button/text-button.js';
4
- import '@material/web/textfield/outlined-text-field.js';
5
- import '@material/web/select/outlined-select.js';
6
- import '@material/web/select/select-option.js';
7
4
  import '@material/web/icon/icon.js';
5
+ import '@operato/data-grist/ox-grist.js';
8
6
  import { LitElement } from 'lit';
7
+ import { DataGrist } from '@operato/data-grist/ox-grist.js';
8
+ import { FetchOption } from '@operato/data-grist';
9
9
  interface KpiGrade {
10
10
  name: string;
11
11
  minValue: number;
@@ -15,21 +15,21 @@ interface KpiGrade {
15
15
  description?: string;
16
16
  }
17
17
  type KpiGrades = KpiGrade[];
18
- declare const KpiGradeEditor_base: typeof LitElement & import("@open-wc/dedupe-mixin").Constructor<import("@open-wc/scoped-elements/types/src/types").ScopedElementsHost>;
18
+ declare const KpiGradeEditor_base: (new (...args: any[]) => LitElement) & typeof LitElement;
19
19
  export declare class KpiGradeEditor extends KpiGradeEditor_base {
20
20
  static styles: import("lit").CSSResult[];
21
21
  kpi: any;
22
22
  grades: KpiGrades;
23
- isDirty: boolean;
24
- connectedCallback(): void;
23
+ gristConfig: any;
24
+ grist: DataGrist;
25
+ firstUpdated(): Promise<void>;
25
26
  render(): import("lit-html").TemplateResult<1>;
26
- _renderGradeItem(grade: KpiGrade, index: number): import("lit-html").TemplateResult<1>;
27
- _updateGrade(index: number, field: keyof KpiGrade, value: any): void;
28
- _addGrade(): void;
29
- _removeGrade(index: number): void;
30
- _loadTemplate(template: string): void;
31
- _save(): Promise<void>;
27
+ fetchHandler({ page, limit, sorters }: FetchOption): Promise<{
28
+ total: number;
29
+ records: KpiGrades;
30
+ }>;
31
+ _updateGrades(): Promise<void>;
32
32
  _validateGrades(): boolean;
33
- _cancel(): void;
33
+ _deleteGrades(): Promise<void>;
34
34
  }
35
35
  export {};