@things-factory/kpi 9.0.17 → 9.0.19
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/client/bootstrap.ts +8 -0
- package/client/pages/kpi/kpi-list-page.ts +99 -11
- package/client/pages/kpi/kpi-viz-editor.ts +214 -14
- package/client/pages/kpi-category/kpi-category-list-page.ts +80 -8
- package/client/pages/kpi-history/kpi-history-list-page.ts +1 -1
- package/client/pages/kpi-metric/kpi-metric-list-page.ts +31 -7
- package/client/pages/kpi-metric-value/kpi-metric-value-importer.ts +65 -0
- package/client/pages/kpi-metric-value/kpi-metric-value-list-page.ts +299 -0
- package/client/pages/{kpi-value/kpi-value-manual-entry-form.ts → kpi-metric-value/kpi-metric-value-manual-entry-form.ts} +18 -44
- package/client/pages/{kpi-value/kpi-value-manual-entry-page.ts → kpi-metric-value/kpi-metric-value-manual-entry-page.ts} +21 -21
- package/client/pages/kpi-value/kpi-value-list-page.ts +4 -6
- package/client/route.ts +6 -2
- package/dist-client/bootstrap.d.ts +2 -0
- package/dist-client/bootstrap.js +7 -0
- package/dist-client/bootstrap.js.map +1 -0
- package/dist-client/pages/kpi/kpi-list-page.d.ts +6 -0
- package/dist-client/pages/kpi/kpi-list-page.js +100 -11
- package/dist-client/pages/kpi/kpi-list-page.js.map +1 -1
- package/dist-client/pages/kpi/kpi-viz-editor.js +208 -14
- package/dist-client/pages/kpi/kpi-viz-editor.js.map +1 -1
- package/dist-client/pages/kpi-category/kpi-category-list-page.d.ts +5 -0
- package/dist-client/pages/kpi-category/kpi-category-list-page.js +83 -8
- package/dist-client/pages/kpi-category/kpi-category-list-page.js.map +1 -1
- package/dist-client/pages/kpi-history/kpi-history-list-page.js +1 -1
- package/dist-client/pages/kpi-history/kpi-history-list-page.js.map +1 -1
- package/dist-client/pages/kpi-metric/kpi-metric-list-page.js +29 -5
- package/dist-client/pages/kpi-metric/kpi-metric-list-page.js.map +1 -1
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-importer.d.ts +23 -0
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-importer.js +75 -0
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-importer.js.map +1 -0
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.d.ts +61 -0
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js +301 -0
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js.map +1 -0
- package/dist-client/pages/{kpi-value/kpi-value-manual-entry-form.d.ts → kpi-metric-value/kpi-metric-value-manual-entry-form.d.ts} +3 -5
- package/dist-client/pages/{kpi-value/kpi-value-manual-entry-form.js → kpi-metric-value/kpi-metric-value-manual-entry-form.js} +27 -56
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.js.map +1 -0
- package/dist-client/pages/{kpi-value/kpi-value-manual-entry-page.d.ts → kpi-metric-value/kpi-metric-value-manual-entry-page.d.ts} +5 -5
- package/dist-client/pages/{kpi-value/kpi-value-manual-entry-page.js → kpi-metric-value/kpi-metric-value-manual-entry-page.js} +28 -28
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js.map +1 -0
- package/dist-client/pages/kpi-value/kpi-value-list-page.js +4 -6
- package/dist-client/pages/kpi-value/kpi-value-list-page.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/route.js +5 -2
- package/dist-client/route.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/service/index.d.ts +4 -2
- package/dist-server/service/index.js +6 -1
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/kpi/aggregate-kpi.js +5 -7
- package/dist-server/service/kpi/aggregate-kpi.js.map +1 -1
- package/dist-server/service/kpi/kpi-history.d.ts +3 -1
- package/dist-server/service/kpi/kpi-history.js +10 -0
- package/dist-server/service/kpi/kpi-history.js.map +1 -1
- package/dist-server/service/kpi/kpi-mutation.js +1 -1
- package/dist-server/service/kpi/kpi-mutation.js.map +1 -1
- package/dist-server/service/kpi/kpi-type.d.ts +2 -0
- package/dist-server/service/kpi/kpi-type.js +8 -0
- package/dist-server/service/kpi/kpi-type.js.map +1 -1
- package/dist-server/service/kpi/kpi.d.ts +9 -0
- package/dist-server/service/kpi/kpi.js +23 -1
- package/dist-server/service/kpi/kpi.js.map +1 -1
- package/dist-server/service/kpi-category/kpi-category-mutation.js +0 -8
- package/dist-server/service/kpi-category/kpi-category-mutation.js.map +1 -1
- package/dist-server/service/kpi-category/kpi-category-type.d.ts +4 -2
- package/dist-server/service/kpi-category/kpi-category-type.js +16 -8
- package/dist-server/service/kpi-category/kpi-category-type.js.map +1 -1
- package/dist-server/service/kpi-category/kpi-category.d.ts +2 -2
- package/dist-server/service/kpi-category/kpi-category.js +8 -8
- package/dist-server/service/kpi-category/kpi-category.js.map +1 -1
- package/dist-server/service/kpi-metric/aggregate-kpi-metric.js +31 -74
- package/dist-server/service/kpi-metric/aggregate-kpi-metric.js.map +1 -1
- package/dist-server/service/kpi-metric/kpi-metric-mutation.d.ts +1 -1
- package/dist-server/service/kpi-metric/kpi-metric-mutation.js +15 -28
- package/dist-server/service/kpi-metric/kpi-metric-mutation.js.map +1 -1
- package/dist-server/service/kpi-metric/kpi-metric-type.d.ts +6 -4
- package/dist-server/service/kpi-metric/kpi-metric-type.js +20 -12
- package/dist-server/service/kpi-metric/kpi-metric-type.js.map +1 -1
- package/dist-server/service/kpi-metric/kpi-metric.d.ts +15 -2
- package/dist-server/service/kpi-metric/kpi-metric.js +34 -14
- package/dist-server/service/kpi-metric/kpi-metric.js.map +1 -1
- package/dist-server/service/kpi-metric-value/index.d.ts +6 -0
- package/dist-server/service/kpi-metric-value/index.js +10 -0
- package/dist-server/service/kpi-metric-value/index.js.map +1 -0
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.d.ts +11 -0
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js +229 -0
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js.map +1 -0
- package/dist-server/service/kpi-metric-value/kpi-metric-value-query.d.ts +13 -0
- package/dist-server/service/kpi-metric-value/kpi-metric-value-query.js +95 -0
- package/dist-server/service/kpi-metric-value/kpi-metric-value-query.js.map +1 -0
- package/dist-server/service/kpi-metric-value/kpi-metric-value-type.d.ts +26 -0
- package/dist-server/service/kpi-metric-value/kpi-metric-value-type.js +112 -0
- package/dist-server/service/kpi-metric-value/kpi-metric-value-type.js.map +1 -0
- package/dist-server/service/kpi-metric-value/kpi-metric-value.d.ts +23 -0
- package/dist-server/service/kpi-metric-value/kpi-metric-value.js +106 -0
- package/dist-server/service/kpi-metric-value/kpi-metric-value.js.map +1 -0
- package/dist-server/service/kpi-value/kpi-value-mutation.js +1 -2
- package/dist-server/service/kpi-value/kpi-value-mutation.js.map +1 -1
- package/dist-server/service/kpi-value/kpi-value-query.js +1 -1
- package/dist-server/service/kpi-value/kpi-value-query.js.map +1 -1
- package/dist-server/service/kpi-value/kpi-value-type.d.ts +2 -4
- package/dist-server/service/kpi-value/kpi-value-type.js +4 -18
- package/dist-server/service/kpi-value/kpi-value-type.js.map +1 -1
- package/dist-server/service/kpi-value/kpi-value.d.ts +3 -3
- package/dist-server/service/kpi-value/kpi-value.js +13 -14
- package/dist-server/service/kpi-value/kpi-value.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/server/service/index.ts +6 -1
- package/server/service/kpi/aggregate-kpi.ts +5 -8
- package/server/service/kpi/kpi-history.ts +9 -1
- package/server/service/kpi/kpi-mutation.ts +1 -1
- package/server/service/kpi/kpi-type.ts +6 -0
- package/server/service/kpi/kpi.ts +21 -0
- package/server/service/kpi-category/kpi-category-mutation.ts +0 -10
- package/server/service/kpi-category/kpi-category-type.ts +12 -6
- package/server/service/kpi-category/kpi-category.ts +6 -6
- package/server/service/kpi-metric/aggregate-kpi-metric.ts +29 -69
- package/server/service/kpi-metric/kpi-metric-mutation.ts +15 -26
- package/server/service/kpi-metric/kpi-metric-type.ts +17 -12
- package/server/service/kpi-metric/kpi-metric.ts +32 -11
- package/server/service/kpi-metric-value/index.ts +7 -0
- package/server/service/kpi-metric-value/kpi-metric-value-mutation.ts +215 -0
- package/server/service/kpi-metric-value/kpi-metric-value-query.ts +60 -0
- package/server/service/kpi-metric-value/kpi-metric-value-type.ts +82 -0
- package/server/service/kpi-metric-value/kpi-metric-value.ts +91 -0
- package/server/service/kpi-value/kpi-value-mutation.ts +1 -2
- package/server/service/kpi-value/kpi-value-query.ts +1 -1
- package/server/service/kpi-value/kpi-value-type.ts +4 -16
- package/server/service/kpi-value/kpi-value.ts +14 -14
- package/things-factory.config.js +5 -3
- package/translations/en.json +8 -1
- package/translations/ja.json +8 -1
- package/translations/ko.json +9 -2
- package/translations/ms.json +9 -2
- package/translations/zh.json +8 -1
- package/dist-client/pages/kpi-value/kpi-value-manual-entry-form.js.map +0 -1
- package/dist-client/pages/kpi-value/kpi-value-manual-entry-page.js.map +0 -1
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import '@operato/app/filter-renderer.js' /* register resource-object filter renderer */
|
|
2
|
+
|
|
3
|
+
import { registerEditor as registerGristEditor } from '@operato/data-grist'
|
|
4
|
+
import { OxGristEditorFormula } from '@operato/grist-editor/ox-grist-editor-formula.js'
|
|
5
|
+
|
|
6
|
+
export default function bootstrap() {
|
|
7
|
+
registerGristEditor('formula', OxGristEditorFormula)
|
|
8
|
+
}
|
|
@@ -4,11 +4,12 @@ import '@operato/data-grist/ox-grist.js'
|
|
|
4
4
|
import '@operato/data-grist/ox-filters-form.js'
|
|
5
5
|
import '@operato/data-grist/ox-record-creator.js'
|
|
6
6
|
import './kpi-viz-editor.js'
|
|
7
|
+
import './kpi-grade-editor.js'
|
|
7
8
|
|
|
8
9
|
import { CommonButtonStyles, CommonHeaderStyles, CommonGristStyles, ScrollbarStyles } from '@operato/styles'
|
|
9
10
|
import { PageView, store } from '@operato/shell'
|
|
10
11
|
import { css, html } from 'lit'
|
|
11
|
-
import { customElement, property, query } from 'lit/decorators.js'
|
|
12
|
+
import { customElement, property, query, state } from 'lit/decorators.js'
|
|
12
13
|
import { ScopedElementsMixin } from '@open-wc/scoped-elements'
|
|
13
14
|
import { ColumnConfig, DataGrist, FetchOption } from '@operato/data-grist'
|
|
14
15
|
import { client } from '@operato/graphql'
|
|
@@ -64,6 +65,36 @@ export class KpiListPage extends connect(store)(localize(i18next)(ScopedElements
|
|
|
64
65
|
|
|
65
66
|
@query('ox-grist') private grist!: DataGrist
|
|
66
67
|
|
|
68
|
+
@state() availableVariables: any[] = []
|
|
69
|
+
@state() availableVariablesLoaded = false
|
|
70
|
+
|
|
71
|
+
async getAvailableVariables() {
|
|
72
|
+
if (this.availableVariablesLoaded) {
|
|
73
|
+
return this.availableVariables
|
|
74
|
+
}
|
|
75
|
+
const response = await client.query({
|
|
76
|
+
query: gql`
|
|
77
|
+
query {
|
|
78
|
+
kpiMetrics {
|
|
79
|
+
items {
|
|
80
|
+
name
|
|
81
|
+
description
|
|
82
|
+
unit
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
`
|
|
87
|
+
})
|
|
88
|
+
this.availableVariables = (response.data.kpiMetrics.items || []).map(metric => ({
|
|
89
|
+
name: metric.name,
|
|
90
|
+
description: metric.description,
|
|
91
|
+
type: 'kpi-metric',
|
|
92
|
+
unit: metric.unit
|
|
93
|
+
}))
|
|
94
|
+
this.availableVariablesLoaded = true
|
|
95
|
+
return this.availableVariables
|
|
96
|
+
}
|
|
97
|
+
|
|
67
98
|
get context() {
|
|
68
99
|
return {
|
|
69
100
|
title: i18next.t('title.kpi list'),
|
|
@@ -127,6 +158,11 @@ export class KpiListPage extends connect(store)(localize(i18next)(ScopedElements
|
|
|
127
158
|
`
|
|
128
159
|
}
|
|
129
160
|
|
|
161
|
+
connectedCallback() {
|
|
162
|
+
super.connectedCallback()
|
|
163
|
+
this.fetchKpiMetrics()
|
|
164
|
+
}
|
|
165
|
+
|
|
130
166
|
async pageInitialized(lifecycle: any) {
|
|
131
167
|
this.gristConfig = {
|
|
132
168
|
list: {
|
|
@@ -187,13 +223,6 @@ export class KpiListPage extends connect(store)(localize(i18next)(ScopedElements
|
|
|
187
223
|
filter: 'search',
|
|
188
224
|
width: 200
|
|
189
225
|
},
|
|
190
|
-
// {
|
|
191
|
-
// type: 'string',
|
|
192
|
-
// name: 'category',
|
|
193
|
-
// header: '카테고리',
|
|
194
|
-
// record: { editable: false, renderer: (v, c, r) => r.category?.name },
|
|
195
|
-
// width: 120
|
|
196
|
-
// },
|
|
197
226
|
{
|
|
198
227
|
type: 'resource-object',
|
|
199
228
|
name: 'category',
|
|
@@ -208,7 +237,18 @@ export class KpiListPage extends connect(store)(localize(i18next)(ScopedElements
|
|
|
208
237
|
},
|
|
209
238
|
width: 200
|
|
210
239
|
},
|
|
211
|
-
{
|
|
240
|
+
{
|
|
241
|
+
type: 'formula',
|
|
242
|
+
name: 'formula',
|
|
243
|
+
header: '산식',
|
|
244
|
+
record: {
|
|
245
|
+
editable: true,
|
|
246
|
+
availableVariables: async () => {
|
|
247
|
+
return await this.getAvailableVariables()
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
width: 320
|
|
251
|
+
},
|
|
212
252
|
{
|
|
213
253
|
type: 'string',
|
|
214
254
|
name: 'grades',
|
|
@@ -229,6 +269,15 @@ export class KpiListPage extends connect(store)(localize(i18next)(ScopedElements
|
|
|
229
269
|
},
|
|
230
270
|
width: 150
|
|
231
271
|
},
|
|
272
|
+
{
|
|
273
|
+
type: 'number',
|
|
274
|
+
name: 'weight',
|
|
275
|
+
header: '가중치',
|
|
276
|
+
record: { editable: true },
|
|
277
|
+
filter: true,
|
|
278
|
+
sortable: true,
|
|
279
|
+
width: 80
|
|
280
|
+
},
|
|
232
281
|
{
|
|
233
282
|
type: 'checkbox',
|
|
234
283
|
name: 'active',
|
|
@@ -264,9 +313,9 @@ export class KpiListPage extends connect(store)(localize(i18next)(ScopedElements
|
|
|
264
313
|
},
|
|
265
314
|
width: 150
|
|
266
315
|
},
|
|
267
|
-
{ type: '
|
|
316
|
+
{ type: 'crontab', name: 'schedule', header: '스케줄', record: { editable: true }, width: 120 },
|
|
268
317
|
{ type: 'string', name: 'scheduleId', header: '스케줄ID', record: { editable: false }, width: 120 },
|
|
269
|
-
{ type: '
|
|
318
|
+
{ type: 'timezone', name: 'timezone', header: '타임존', record: { editable: true }, width: 120 },
|
|
270
319
|
{ type: 'number', name: 'version', header: '버전', record: { editable: false }, width: 80 },
|
|
271
320
|
{ type: 'datetime', name: 'createdAt', header: '생성일', record: { editable: false }, width: 180 },
|
|
272
321
|
{
|
|
@@ -323,6 +372,12 @@ export class KpiListPage extends connect(store)(localize(i18next)(ScopedElements
|
|
|
323
372
|
grades
|
|
324
373
|
vizType
|
|
325
374
|
vizMeta
|
|
375
|
+
weight
|
|
376
|
+
formula
|
|
377
|
+
schedule
|
|
378
|
+
scheduleId
|
|
379
|
+
timezone
|
|
380
|
+
version
|
|
326
381
|
category {
|
|
327
382
|
id
|
|
328
383
|
name
|
|
@@ -356,6 +411,31 @@ export class KpiListPage extends connect(store)(localize(i18next)(ScopedElements
|
|
|
356
411
|
}
|
|
357
412
|
}
|
|
358
413
|
|
|
414
|
+
async fetchKpiMetrics() {
|
|
415
|
+
const response = await client.query({
|
|
416
|
+
query: gql`
|
|
417
|
+
query {
|
|
418
|
+
kpiMetrics {
|
|
419
|
+
items {
|
|
420
|
+
name
|
|
421
|
+
description
|
|
422
|
+
unit
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
`
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
if (!response.errors) {
|
|
430
|
+
this.availableVariables = (response.data.kpiMetrics.items || []).map(metric => ({
|
|
431
|
+
name: metric.name,
|
|
432
|
+
description: metric.description,
|
|
433
|
+
type: 'kpi-metric',
|
|
434
|
+
unit: metric.unit
|
|
435
|
+
}))
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
359
439
|
async _deleteKpi() {
|
|
360
440
|
if (
|
|
361
441
|
await OxPrompt.open({
|
|
@@ -503,6 +583,14 @@ export class KpiListPage extends connect(store)(localize(i18next)(ScopedElements
|
|
|
503
583
|
}
|
|
504
584
|
|
|
505
585
|
async _editGrades(kpi: any) {
|
|
586
|
+
if (!kpi.id) {
|
|
587
|
+
notify({
|
|
588
|
+
message: 'KPI를 먼저 저장한 후에 등급 설정을 할 수 있습니다.'
|
|
589
|
+
})
|
|
590
|
+
|
|
591
|
+
return
|
|
592
|
+
}
|
|
593
|
+
|
|
506
594
|
const popup = await openPopup(html` <kpi-grade-editor .kpi=${kpi}></kpi-grade-editor> `, {
|
|
507
595
|
title: `${kpi.name} - 등급 설정`,
|
|
508
596
|
size: 'large'
|
|
@@ -194,11 +194,13 @@ export class KpiVizEditor extends localize(i18next)(LitElement) {
|
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
_renderPreview() {
|
|
197
|
-
const kpiValue = this.kpi?.value?.value
|
|
198
|
-
const targetValue = this.kpi?.targetValue
|
|
199
|
-
const unit = this.kpi?.unit
|
|
197
|
+
const kpiValue = this.kpi?.value?.value ?? 75
|
|
198
|
+
const targetValue = this.kpi?.targetValue ?? 100
|
|
199
|
+
const unit = this.kpi?.unit ?? ''
|
|
200
200
|
const color = this.vizMeta.color || '#2196f3'
|
|
201
201
|
const icon = this.vizMeta.icon || 'trending_up'
|
|
202
|
+
const min = this.vizMeta.minValue ?? 0
|
|
203
|
+
const max = this.vizMeta.maxValue ?? 100
|
|
202
204
|
|
|
203
205
|
switch (this.selectedVizType) {
|
|
204
206
|
case 'CARD':
|
|
@@ -213,22 +215,161 @@ export class KpiVizEditor extends localize(i18next)(LitElement) {
|
|
|
213
215
|
</div>
|
|
214
216
|
</div>
|
|
215
217
|
`
|
|
216
|
-
case 'GAUGE':
|
|
217
|
-
const
|
|
218
|
+
case 'GAUGE': {
|
|
219
|
+
const value = Math.max(min, Math.min(kpiValue, max))
|
|
220
|
+
const percent = max - min > 0 ? (value - min) / (max - min) : 0
|
|
221
|
+
const r = 60
|
|
222
|
+
const cx = 90
|
|
223
|
+
const cy = 90
|
|
224
|
+
const startX = cx - r
|
|
225
|
+
const startY = cy
|
|
226
|
+
const endX = cx + r * Math.cos(Math.PI * (1 - percent))
|
|
227
|
+
const endY = cy - r * Math.sin(Math.PI * (1 - percent))
|
|
228
|
+
const needleAngle = Math.PI - Math.PI * percent
|
|
229
|
+
const needleX = cx + r * Math.cos(needleAngle)
|
|
230
|
+
const needleY = cy - r * Math.sin(needleAngle)
|
|
218
231
|
return html`
|
|
219
232
|
<div style="text-align:center;padding:16px;">
|
|
220
|
-
<
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
233
|
+
<svg width="180" height="110" viewBox="0 0 180 110">
|
|
234
|
+
<!-- 배경 arc -->
|
|
235
|
+
<path
|
|
236
|
+
d="M${startX},${startY} A${r},${r} 0 0,1 ${cx + r},${cy}"
|
|
237
|
+
fill="none"
|
|
238
|
+
stroke="#e0e0e0"
|
|
239
|
+
stroke-width="16"
|
|
240
|
+
/>
|
|
241
|
+
<!-- 값 arc -->
|
|
242
|
+
<path
|
|
243
|
+
d="M${startX},${startY} A${r},${r} 0 0,1 ${endX},${endY}"
|
|
244
|
+
fill="none"
|
|
245
|
+
stroke="${color}"
|
|
246
|
+
stroke-width="16"
|
|
247
|
+
/>
|
|
248
|
+
<!-- 바늘 -->
|
|
249
|
+
<line x1="${cx}" y1="${cy}" x2="${needleX}" y2="${needleY}" stroke="#333" stroke-width="4" />
|
|
250
|
+
<!-- 중심 원 -->
|
|
251
|
+
<circle cx="${cx}" cy="${cy}" r="7" fill="#333" />
|
|
252
|
+
<!-- 중앙값 -->
|
|
253
|
+
<text x="${cx}" y="${cy - 25}" text-anchor="middle" font-size="22" fill="${color}" font-weight="bold">
|
|
254
|
+
${value}${unit}
|
|
255
|
+
</text>
|
|
256
|
+
<!-- min/max -->
|
|
257
|
+
<text x="${cx - r}" y="${cy + 20}" text-anchor="middle" font-size="12" fill="#888">${min}</text>
|
|
258
|
+
<text x="${cx + r}" y="${cy + 20}" text-anchor="middle" font-size="12" fill="#888">${max}</text>
|
|
259
|
+
</svg>
|
|
260
|
+
</div>
|
|
261
|
+
`
|
|
262
|
+
}
|
|
263
|
+
case 'SPEEDOMETER': {
|
|
264
|
+
const value = Math.max(min, Math.min(kpiValue, max))
|
|
265
|
+
const percent = max - min > 0 ? (value - min) / (max - min) : 0
|
|
266
|
+
const r = 60
|
|
267
|
+
const cx = 90
|
|
268
|
+
const cy = 90
|
|
269
|
+
const startX = cx - r
|
|
270
|
+
const startY = cy
|
|
271
|
+
const endX = cx + r * Math.cos(Math.PI * (1 - percent))
|
|
272
|
+
const endY = cy - r * Math.sin(Math.PI * (1 - percent))
|
|
273
|
+
const needleAngle = Math.PI - Math.PI * percent
|
|
274
|
+
const needleX = cx + r * Math.cos(needleAngle)
|
|
275
|
+
const needleY = cy - r * Math.sin(needleAngle)
|
|
276
|
+
// 중간 눈금 (5개)
|
|
277
|
+
const ticks = Array.from({ length: 6 }, (_, i) => {
|
|
278
|
+
const tickAngle = Math.PI - (Math.PI * i) / 5
|
|
279
|
+
const tx1 = cx + (r - 8) * Math.cos(tickAngle)
|
|
280
|
+
const ty1 = cy - (r - 8) * Math.sin(tickAngle)
|
|
281
|
+
const tx2 = cx + (r + 8) * Math.cos(tickAngle)
|
|
282
|
+
const ty2 = cy - (r + 8) * Math.sin(tickAngle)
|
|
283
|
+
const label = Math.round(min + (max - min) * (i / 5))
|
|
284
|
+
const lx = cx + (r + 22) * Math.cos(tickAngle)
|
|
285
|
+
const ly = cy - (r + 22) * Math.sin(tickAngle) + 6
|
|
286
|
+
return { tx1, ty1, tx2, ty2, label, lx, ly }
|
|
287
|
+
})
|
|
288
|
+
return html`
|
|
289
|
+
<div style="text-align:center;padding:16px;">
|
|
290
|
+
<svg width="200" height="120" viewBox="0 0 200 120">
|
|
291
|
+
<!-- 배경 arc (더 두껍게) -->
|
|
292
|
+
<path
|
|
293
|
+
d="M${startX + 10},${startY} A${r},${r} 0 0,1 ${cx + r + 10},${cy}"
|
|
294
|
+
fill="none"
|
|
295
|
+
stroke="#e0e0e0"
|
|
296
|
+
stroke-width="28"
|
|
297
|
+
/>
|
|
298
|
+
<!-- 값 arc -->
|
|
299
|
+
<path
|
|
300
|
+
d="M${startX + 10},${startY} A${r},${r} 0 0,1 ${endX + 10},${endY}"
|
|
301
|
+
fill="none"
|
|
302
|
+
stroke="${color}"
|
|
303
|
+
stroke-width="28"
|
|
304
|
+
/>
|
|
305
|
+
<!-- 눈금 -->
|
|
306
|
+
${ticks.map(
|
|
307
|
+
t =>
|
|
308
|
+
html`<line
|
|
309
|
+
x1="${t.tx1 + 10}"
|
|
310
|
+
y1="${t.ty1}"
|
|
311
|
+
x2="${t.tx2 + 10}"
|
|
312
|
+
y2="${t.ty2}"
|
|
313
|
+
stroke="#888"
|
|
314
|
+
stroke-width="2"
|
|
315
|
+
/>`
|
|
316
|
+
)}
|
|
317
|
+
<!-- 눈금 숫자 -->
|
|
318
|
+
${ticks.map(
|
|
319
|
+
t =>
|
|
320
|
+
html`<text
|
|
321
|
+
x="${t.lx + 10}"
|
|
322
|
+
y="${t.ly}"
|
|
323
|
+
text-anchor="middle"
|
|
324
|
+
font-size="14"
|
|
325
|
+
fill="#333"
|
|
326
|
+
font-weight="bold"
|
|
327
|
+
>${t.label}</text
|
|
328
|
+
>`
|
|
329
|
+
)}
|
|
330
|
+
<!-- 바늘 (빨간색) -->
|
|
331
|
+
<line x1="${cx + 10}" y1="${cy}" x2="${needleX + 10}" y2="${needleY}" stroke="#d32f2f" stroke-width="6" />
|
|
332
|
+
<!-- 중심 원 -->
|
|
333
|
+
<circle cx="${cx + 10}" cy="${cy}" r="13" fill="#333" />
|
|
334
|
+
<!-- 중앙값 -->
|
|
335
|
+
<text
|
|
336
|
+
x="${cx + 10}"
|
|
337
|
+
y="${cy - 32}"
|
|
338
|
+
text-anchor="middle"
|
|
339
|
+
font-size="26"
|
|
340
|
+
fill="${color}"
|
|
341
|
+
font-weight="bold"
|
|
226
342
|
>
|
|
227
|
-
${
|
|
228
|
-
</
|
|
229
|
-
|
|
343
|
+
${value}${unit}
|
|
344
|
+
</text>
|
|
345
|
+
<!-- min/max 포인트 -->
|
|
346
|
+
<circle cx="${startX + 10}" cy="${startY}" r="7" fill="#fff" stroke="#888" stroke-width="2" />
|
|
347
|
+
<circle cx="${cx + r + 10}" cy="${cy}" r="7" fill="#fff" stroke="#888" stroke-width="2" />
|
|
348
|
+
<!-- min/max 숫자 크게 -->
|
|
349
|
+
<text
|
|
350
|
+
x="${startX + 10}"
|
|
351
|
+
y="${startY + 32}"
|
|
352
|
+
text-anchor="middle"
|
|
353
|
+
font-size="16"
|
|
354
|
+
fill="#333"
|
|
355
|
+
font-weight="bold"
|
|
356
|
+
>
|
|
357
|
+
${min}
|
|
358
|
+
</text>
|
|
359
|
+
<text
|
|
360
|
+
x="${cx + r + 10}"
|
|
361
|
+
y="${cy + 32}"
|
|
362
|
+
text-anchor="middle"
|
|
363
|
+
font-size="16"
|
|
364
|
+
fill="#333"
|
|
365
|
+
font-weight="bold"
|
|
366
|
+
>
|
|
367
|
+
${max}
|
|
368
|
+
</text>
|
|
369
|
+
</svg>
|
|
230
370
|
</div>
|
|
231
371
|
`
|
|
372
|
+
}
|
|
232
373
|
case 'PROGRESS':
|
|
233
374
|
const progressPercentage = Math.min((kpiValue / targetValue) * 100, 100)
|
|
234
375
|
return html`
|
|
@@ -241,6 +382,65 @@ export class KpiVizEditor extends localize(i18next)(LitElement) {
|
|
|
241
382
|
</div>
|
|
242
383
|
</div>
|
|
243
384
|
`
|
|
385
|
+
case 'THERMOMETER': {
|
|
386
|
+
const value = Math.max(min, Math.min(kpiValue, max))
|
|
387
|
+
const percent = max - min > 0 ? (value - min) / (max - min) : 0
|
|
388
|
+
const barHeight = 120
|
|
389
|
+
const barWidth = 24
|
|
390
|
+
const x = 100
|
|
391
|
+
const yTop = 30
|
|
392
|
+
const yBottom = yTop + barHeight
|
|
393
|
+
const fillY = yBottom - percent * barHeight
|
|
394
|
+
return html`
|
|
395
|
+
<div style="text-align:center;padding:16px;">
|
|
396
|
+
<svg width="200" height="180" viewBox="0 0 200 180">
|
|
397
|
+
<!-- 바깥 테두리 -->
|
|
398
|
+
<rect
|
|
399
|
+
x="${x - barWidth / 2 - 4}"
|
|
400
|
+
y="${yTop - 4}"
|
|
401
|
+
width="${barWidth + 8}"
|
|
402
|
+
height="${barHeight + 8}"
|
|
403
|
+
rx="16"
|
|
404
|
+
fill="#f5f5f5"
|
|
405
|
+
stroke="#bbb"
|
|
406
|
+
stroke-width="2"
|
|
407
|
+
/>
|
|
408
|
+
<!-- 빈 막대 -->
|
|
409
|
+
<rect
|
|
410
|
+
x="${x - barWidth / 2}"
|
|
411
|
+
y="${yTop}"
|
|
412
|
+
width="${barWidth}"
|
|
413
|
+
height="${barHeight}"
|
|
414
|
+
rx="12"
|
|
415
|
+
fill="#e0e0e0"
|
|
416
|
+
/>
|
|
417
|
+
<!-- 채워진 부분 -->
|
|
418
|
+
<rect
|
|
419
|
+
x="${x - barWidth / 2}"
|
|
420
|
+
y="${fillY}"
|
|
421
|
+
width="${barWidth}"
|
|
422
|
+
height="${yBottom - fillY}"
|
|
423
|
+
rx="12"
|
|
424
|
+
fill="${color}"
|
|
425
|
+
/>
|
|
426
|
+
<!-- 하단 구슬 -->
|
|
427
|
+
<circle cx="${x}" cy="${yBottom + 18}" r="22" fill="#e0e0e0" stroke="#bbb" stroke-width="2" />
|
|
428
|
+
<circle cx="${x}" cy="${yBottom + 18}" r="18" fill="${color}" />
|
|
429
|
+
<!-- 현재값 -->
|
|
430
|
+
<text x="${x}" y="${fillY - 12}" text-anchor="middle" font-size="22" fill="${color}" font-weight="bold">
|
|
431
|
+
${value}${unit}
|
|
432
|
+
</text>
|
|
433
|
+
<!-- min/max -->
|
|
434
|
+
<text x="${x}" y="${yBottom + 52}" text-anchor="middle" font-size="16" fill="#333" font-weight="bold">
|
|
435
|
+
${min}
|
|
436
|
+
</text>
|
|
437
|
+
<text x="${x}" y="${yTop - 12}" text-anchor="middle" font-size="16" fill="#333" font-weight="bold">
|
|
438
|
+
${max}
|
|
439
|
+
</text>
|
|
440
|
+
</svg>
|
|
441
|
+
</div>
|
|
442
|
+
`
|
|
443
|
+
}
|
|
244
444
|
case 'ICON':
|
|
245
445
|
return html`
|
|
246
446
|
<div style="text-align:center;padding:16px;">
|
|
@@ -7,7 +7,7 @@ import '@operato/data-grist/ox-record-creator.js'
|
|
|
7
7
|
import { CommonButtonStyles, CommonHeaderStyles, CommonGristStyles, ScrollbarStyles } from '@operato/styles'
|
|
8
8
|
import { PageView, store } from '@operato/shell'
|
|
9
9
|
import { css, html } from 'lit'
|
|
10
|
-
import { customElement, property, query } from 'lit/decorators.js'
|
|
10
|
+
import { customElement, property, query, state } from 'lit/decorators.js'
|
|
11
11
|
import { ScopedElementsMixin } from '@open-wc/scoped-elements'
|
|
12
12
|
import { ColumnConfig, DataGrist, FetchOption } from '@operato/data-grist'
|
|
13
13
|
import { client } from '@operato/graphql'
|
|
@@ -59,6 +59,35 @@ export class KpiCategoryListPage extends connect(store)(localize(i18next)(Scoped
|
|
|
59
59
|
|
|
60
60
|
@query('ox-grist') private grist!: DataGrist
|
|
61
61
|
|
|
62
|
+
@state() availableVariables: any[] = []
|
|
63
|
+
@state() availableVariablesLoaded = false
|
|
64
|
+
|
|
65
|
+
async getAvailableVariables() {
|
|
66
|
+
if (this.availableVariablesLoaded) {
|
|
67
|
+
return this.availableVariables
|
|
68
|
+
}
|
|
69
|
+
const response = await client.query({
|
|
70
|
+
query: gql`
|
|
71
|
+
query {
|
|
72
|
+
kpis {
|
|
73
|
+
items {
|
|
74
|
+
name
|
|
75
|
+
description
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
`
|
|
80
|
+
})
|
|
81
|
+
this.availableVariables = (response.data.kpis.items || []).map(kpi => ({
|
|
82
|
+
name: kpi.name,
|
|
83
|
+
description: kpi.description,
|
|
84
|
+
type: 'kpi',
|
|
85
|
+
unit: ''
|
|
86
|
+
}))
|
|
87
|
+
this.availableVariablesLoaded = true
|
|
88
|
+
return this.availableVariables
|
|
89
|
+
}
|
|
90
|
+
|
|
62
91
|
get context() {
|
|
63
92
|
return {
|
|
64
93
|
title: i18next.t('title.kpi category list'),
|
|
@@ -122,11 +151,16 @@ export class KpiCategoryListPage extends connect(store)(localize(i18next)(Scoped
|
|
|
122
151
|
`
|
|
123
152
|
}
|
|
124
153
|
|
|
154
|
+
connectedCallback() {
|
|
155
|
+
super.connectedCallback()
|
|
156
|
+
this.fetchKpis()
|
|
157
|
+
}
|
|
158
|
+
|
|
125
159
|
async pageInitialized(lifecycle: any) {
|
|
126
160
|
this.gristConfig = {
|
|
127
161
|
list: {
|
|
128
|
-
fields: ['name', 'description', 'active', '
|
|
129
|
-
details: ['name', 'description', 'active', '
|
|
162
|
+
fields: ['name', 'description', 'active', 'formula', 'weight', 'createdAt', 'updatedAt', 'creator', 'updater'],
|
|
163
|
+
details: ['name', 'description', 'active', 'formula', 'weight', 'createdAt', 'updatedAt', 'creator', 'updater']
|
|
130
164
|
},
|
|
131
165
|
columns: [
|
|
132
166
|
{ type: 'gutter', gutterName: 'sequence' },
|
|
@@ -149,11 +183,23 @@ export class KpiCategoryListPage extends connect(store)(localize(i18next)(Scoped
|
|
|
149
183
|
width: 200
|
|
150
184
|
},
|
|
151
185
|
{
|
|
152
|
-
type: '
|
|
153
|
-
name: '
|
|
154
|
-
header: '
|
|
155
|
-
record: {
|
|
156
|
-
|
|
186
|
+
type: 'formula',
|
|
187
|
+
name: 'formula',
|
|
188
|
+
header: '산식',
|
|
189
|
+
record: {
|
|
190
|
+
editable: true,
|
|
191
|
+
availableVariables: async () => {
|
|
192
|
+
return await this.getAvailableVariables()
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
width: 320
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
type: 'number',
|
|
199
|
+
name: 'weight',
|
|
200
|
+
header: '가중치',
|
|
201
|
+
record: { editable: true },
|
|
202
|
+
width: 80
|
|
157
203
|
},
|
|
158
204
|
{
|
|
159
205
|
type: 'checkbox',
|
|
@@ -216,6 +262,8 @@ export class KpiCategoryListPage extends connect(store)(localize(i18next)(Scoped
|
|
|
216
262
|
name
|
|
217
263
|
description
|
|
218
264
|
active
|
|
265
|
+
formula
|
|
266
|
+
weight
|
|
219
267
|
updater {
|
|
220
268
|
id
|
|
221
269
|
name
|
|
@@ -244,6 +292,30 @@ export class KpiCategoryListPage extends connect(store)(localize(i18next)(Scoped
|
|
|
244
292
|
}
|
|
245
293
|
}
|
|
246
294
|
|
|
295
|
+
async fetchKpis() {
|
|
296
|
+
const response = await client.query({
|
|
297
|
+
query: gql`
|
|
298
|
+
query {
|
|
299
|
+
kpis {
|
|
300
|
+
items {
|
|
301
|
+
name
|
|
302
|
+
description
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
`
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
if (!response.errors) {
|
|
310
|
+
this.availableVariables = (response.data.kpis.items || []).map(kpi => ({
|
|
311
|
+
name: kpi.name,
|
|
312
|
+
description: kpi.description,
|
|
313
|
+
type: 'kpi',
|
|
314
|
+
unit: ''
|
|
315
|
+
}))
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
247
319
|
async _deleteKpiCategory() {
|
|
248
320
|
if (
|
|
249
321
|
await OxPrompt.open({
|
|
@@ -105,7 +105,7 @@ export class KpiHistoryListPage extends localize(i18next)(PageView) {
|
|
|
105
105
|
record: { editable: false, renderer: (v, c, r) => r.category?.name },
|
|
106
106
|
width: 120
|
|
107
107
|
},
|
|
108
|
-
{ type: '
|
|
108
|
+
{ type: 'formula', name: 'formula', header: '산식', record: { editable: false }, width: 200 },
|
|
109
109
|
{ type: 'checkbox', name: 'active', label: true, header: '활성', record: { editable: false }, width: 60 },
|
|
110
110
|
{ type: 'string', name: 'state', header: '상태', record: { editable: false }, width: 100 },
|
|
111
111
|
{ type: 'datetime', name: 'createdAt', header: '생성일', record: { editable: false }, width: 180 },
|
|
@@ -6,8 +6,8 @@ import '@operato/data-grist/ox-record-creator.js'
|
|
|
6
6
|
|
|
7
7
|
import { CommonButtonStyles, CommonHeaderStyles, CommonGristStyles, ScrollbarStyles } from '@operato/styles'
|
|
8
8
|
import { PageView, store } from '@operato/shell'
|
|
9
|
-
import { css, html } from 'lit'
|
|
10
|
-
import { customElement, property, query } from 'lit/decorators.js'
|
|
9
|
+
import { css, html, PropertyValues } from 'lit'
|
|
10
|
+
import { customElement, property, query, state } from 'lit/decorators.js'
|
|
11
11
|
import { ScopedElementsMixin } from '@open-wc/scoped-elements'
|
|
12
12
|
import { ColumnConfig, DataGrist, FetchOption } from '@operato/data-grist'
|
|
13
13
|
import { client } from '@operato/graphql'
|
|
@@ -132,7 +132,7 @@ export class KpiMetricListPage extends connect(store)(localize(i18next)(ScopedEl
|
|
|
132
132
|
'source',
|
|
133
133
|
'dataSet',
|
|
134
134
|
'fieldName',
|
|
135
|
-
'
|
|
135
|
+
'periodType',
|
|
136
136
|
'active',
|
|
137
137
|
'schedule',
|
|
138
138
|
'scheduleId',
|
|
@@ -149,7 +149,7 @@ export class KpiMetricListPage extends connect(store)(localize(i18next)(ScopedEl
|
|
|
149
149
|
'source',
|
|
150
150
|
'dataSet',
|
|
151
151
|
'fieldName',
|
|
152
|
-
'
|
|
152
|
+
'periodType',
|
|
153
153
|
'active',
|
|
154
154
|
'schedule',
|
|
155
155
|
'scheduleId',
|
|
@@ -183,11 +183,24 @@ export class KpiMetricListPage extends connect(store)(localize(i18next)(ScopedEl
|
|
|
183
183
|
width: 120
|
|
184
184
|
},
|
|
185
185
|
{ type: 'string', name: 'fieldName', header: '필드명', record: { editable: true }, width: 120 },
|
|
186
|
-
{
|
|
186
|
+
{
|
|
187
|
+
type: 'select',
|
|
188
|
+
name: 'collectType',
|
|
189
|
+
header: '수집방법',
|
|
190
|
+
record: { editable: true, options: ['', 'AUTO', 'MANUAL', 'IMPORT', 'EXTERNAL'] },
|
|
191
|
+
width: 120
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
type: 'select',
|
|
195
|
+
name: 'periodType',
|
|
196
|
+
header: '주기',
|
|
197
|
+
record: { editable: true, options: ['', 'DAY', 'WEEK', 'MONTH', 'QUARTER', 'YEAR', 'RANGE'] },
|
|
198
|
+
width: 80
|
|
199
|
+
},
|
|
187
200
|
{ type: 'checkbox', name: 'active', label: true, header: '활성', record: { editable: true }, width: 60 },
|
|
188
|
-
{ type: '
|
|
201
|
+
{ type: 'crontab', name: 'schedule', header: '스케줄', record: { editable: true }, width: 120 },
|
|
189
202
|
{ type: 'string', name: 'scheduleId', header: '스케줄ID', record: { editable: false }, width: 120 },
|
|
190
|
-
{ type: '
|
|
203
|
+
{ type: 'timezone', name: 'timezone', header: '타임존', record: { editable: true }, width: 120 },
|
|
191
204
|
{ type: 'datetime', name: 'createdAt', header: '생성일', record: { editable: false }, width: 180 },
|
|
192
205
|
{ type: 'datetime', name: 'updatedAt', header: '수정일', record: { editable: false }, width: 180 },
|
|
193
206
|
{
|
|
@@ -230,7 +243,18 @@ export class KpiMetricListPage extends connect(store)(localize(i18next)(ScopedEl
|
|
|
230
243
|
id
|
|
231
244
|
name
|
|
232
245
|
description
|
|
246
|
+
periodType
|
|
233
247
|
active
|
|
248
|
+
unit
|
|
249
|
+
source
|
|
250
|
+
collectType
|
|
251
|
+
dataSet {
|
|
252
|
+
id
|
|
253
|
+
name
|
|
254
|
+
}
|
|
255
|
+
fieldName
|
|
256
|
+
schedule
|
|
257
|
+
timezone
|
|
234
258
|
updater {
|
|
235
259
|
id
|
|
236
260
|
name
|