@things-factory/kpi 10.0.0-beta.5 → 10.0.0-beta.52

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 (34) hide show
  1. package/client/index.ts +1 -0
  2. package/client/pages/kpi/kpi-list-page.ts +150 -17
  3. package/client/pages/kpi-dashboard/components/kpi-map-panel.ts +2 -2
  4. package/client/pages/kpi-metric/kpi-metric-list-page.ts +17 -3
  5. package/dist-client/index.d.ts +1 -0
  6. package/dist-client/index.js +1 -1
  7. package/dist-client/index.js.map +1 -1
  8. package/dist-client/pages/kpi/kpi-list-page.d.ts +48 -3
  9. package/dist-client/pages/kpi/kpi-list-page.js +140 -18
  10. package/dist-client/pages/kpi/kpi-list-page.js.map +1 -1
  11. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js +2 -2
  12. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js.map +1 -1
  13. package/dist-client/pages/kpi-metric/kpi-metric-list-page.js +17 -3
  14. package/dist-client/pages/kpi-metric/kpi-metric-list-page.js.map +1 -1
  15. package/dist-client/tsconfig.tsbuildinfo +1 -1
  16. package/dist-server/service/index.d.ts +2 -2
  17. package/dist-server/service/kpi/index.d.ts +1 -1
  18. package/dist-server/service/kpi/kpi-type.d.ts +5 -1
  19. package/dist-server/service/kpi/kpi-type.js +28 -0
  20. package/dist-server/service/kpi/kpi-type.js.map +1 -1
  21. package/dist-server/service/kpi/kpi.d.ts +64 -0
  22. package/dist-server/service/kpi/kpi.js +89 -1
  23. package/dist-server/service/kpi/kpi.js.map +1 -1
  24. package/dist-server/service/kpi-metric/kpi-metric-mutation.js +15 -7
  25. package/dist-server/service/kpi-metric/kpi-metric-mutation.js.map +1 -1
  26. package/dist-server/service/kpi-metric/kpi-metric-query.d.ts +2 -0
  27. package/dist-server/service/kpi-metric/kpi-metric-query.js +13 -1
  28. package/dist-server/service/kpi-metric/kpi-metric-query.js.map +1 -1
  29. package/dist-server/service/kpi-metric/kpi-metric-type.d.ts +3 -2
  30. package/dist-server/service/kpi-metric/kpi-metric-type.js +7 -6
  31. package/dist-server/service/kpi-metric/kpi-metric-type.js.map +1 -1
  32. package/dist-server/service/kpi-value/index.d.ts +1 -1
  33. package/dist-server/tsconfig.tsbuildinfo +1 -1
  34. package/package.json +5 -5
package/client/index.ts CHANGED
@@ -0,0 +1 @@
1
+ export { KpiListPage } from './pages/kpi/kpi-list-page.js'
@@ -21,6 +21,44 @@ import { p13n } from '@operato/p13n'
21
21
 
22
22
  import gql from 'graphql-tag'
23
23
 
24
+ /**
25
+ * KPI scoreType — value → score 변환 방식 (null이면 미설정)
26
+ *
27
+ * DIRECT — value = score, 변환 없음
28
+ * FORMULA — scoreFormula 수식으로 변환
29
+ * LOOKUP — grade table(1D)로 변환
30
+ * CUSTOM — 2D 룩업 등 특수 변환
31
+ */
32
+ export const KPI_SCORE_TYPE = {
33
+ DIRECT: 'DIRECT',
34
+ FORMULA: 'FORMULA',
35
+ LOOKUP: 'LOOKUP',
36
+ CUSTOM: 'CUSTOM'
37
+ } as const
38
+
39
+ /**
40
+ * KPI valueType — value 획득 방식
41
+ *
42
+ * MEASURED — 외부 시스템 수집 측정값
43
+ * ASSESSED — 감리자 직접 평가 (1~5)
44
+ * CALCULATED — formula 자동 계산
45
+ * COMPOSITE — 다차원 복합 입력
46
+ */
47
+ export const KPI_VALUE_TYPE = {
48
+ MEASURED: 'MEASURED',
49
+ ASSESSED: 'ASSESSED',
50
+ CALCULATED: 'CALCULATED',
51
+ COMPOSITE: 'COMPOSITE'
52
+ } as const
53
+
54
+ export function inferScoreType(kpi: any): string {
55
+ if (kpi.scoreType) return kpi.scoreType
56
+ if (kpi.scoreFormula) return KPI_SCORE_TYPE.FORMULA
57
+ if (kpi.grades && typeof kpi.grades === 'object' && !Array.isArray(kpi.grades)) return KPI_SCORE_TYPE.CUSTOM
58
+ if (Array.isArray(kpi.grades) && kpi.grades.length > 0) return KPI_SCORE_TYPE.LOOKUP
59
+ return KPI_SCORE_TYPE.DIRECT
60
+ }
61
+
24
62
  import { KpiImporter } from './kpi-importer'
25
63
  import { KpiGradeEditor } from './kpi-grade-editor'
26
64
  import { KpiVizEditor } from './kpi-viz-editor'
@@ -57,7 +95,7 @@ export class KpiListPage extends p13n(localize(i18next)(ScopedElementsMixin(Page
57
95
  @property({ type: Object }) gristConfig: any
58
96
  @property({ type: String }) mode: 'CARD' | 'GRID' | 'LIST' = isMobileDevice() ? 'CARD' : 'GRID'
59
97
 
60
- @query('ox-grist') private grist!: DataGrist
98
+ @query('ox-grist') protected grist!: DataGrist
61
99
 
62
100
  @state() availableVariables: any[] = []
63
101
  @state() availableVariablesLoaded = false
@@ -387,17 +425,42 @@ export class KpiListPage extends p13n(localize(i18next)(ScopedElementsMixin(Page
387
425
  name: 'grades',
388
426
  header: '성과지수 Lookup',
389
427
  record: { editable: false,
390
- renderer: (v, c, r) => { if (r.grades && r.grades.length > 0) { return html`<span style="color: #4caf50; cursor: pointer;" @click=${() => this._editGrades(r)}>
391
- ${r.grades.length}개 등급 설정됨
392
- </span>`
393
- } else { return html`<span style="color: #999; cursor: pointer;" @click=${() => this._editGrades(r)}>
394
- 등급 설정 없음
395
- </span>`
396
- }
397
- }
428
+ renderer: (v, c, r) => this.renderGradesCell(r)
398
429
  },
399
430
  width: 150
400
431
  },
432
+ {
433
+ type: 'select',
434
+ name: 'scoreType',
435
+ header: 'Score 산정',
436
+ record: {
437
+ editable: true,
438
+ options: [
439
+ { value: '', display: '(미설정)' },
440
+ { value: 'DIRECT', display: 'DIRECT (변환없음)' },
441
+ { value: 'FORMULA', display: 'FORMULA (수식)' },
442
+ { value: 'LOOKUP', display: 'LOOKUP (등급표)' },
443
+ { value: 'CUSTOM', display: 'CUSTOM (특수)' }
444
+ ]
445
+ },
446
+ width: 130
447
+ },
448
+ {
449
+ type: 'select',
450
+ name: 'valueType',
451
+ header: 'Value 획득',
452
+ record: {
453
+ editable: true,
454
+ options: [
455
+ { value: '', display: '(미설정)' },
456
+ { value: 'MEASURED', display: 'MEASURED (외부수집)' },
457
+ { value: 'ASSESSED', display: 'ASSESSED (감리자평가)' },
458
+ { value: 'CALCULATED', display: 'CALCULATED (산식)' },
459
+ { value: 'COMPOSITE', display: 'COMPOSITE (복합)' }
460
+ ]
461
+ },
462
+ width: 140
463
+ },
401
464
  { type: 'number',
402
465
  name: 'weight',
403
466
  header: '가중치',
@@ -487,6 +550,8 @@ export class KpiListPage extends p13n(localize(i18next)(ScopedElementsMixin(Page
487
550
  active
488
551
  formula
489
552
  periodType
553
+ scoreType
554
+ valueType
490
555
  scoreFormula
491
556
  grades
492
557
  vizType
@@ -654,19 +719,86 @@ export class KpiListPage extends p13n(localize(i18next)(ScopedElementsMixin(Page
654
719
  }
655
720
  }
656
721
 
657
- async _editGrades(kpi: any) { if (!kpi.id) { notify({ message: 'KPI를 먼저 저장한 후에 등급 설정을 할 수 있습니다.'
658
- })
722
+ /**
723
+ * grades 셀 렌더러. 서브클래스에서 override 가능.
724
+ * CUSTOM scoreType은 renderCustomGradesCell()로 위임.
725
+ */
726
+ protected renderGradesCell(kpi: any): any {
727
+ const scoreType = inferScoreType(kpi)
728
+ let label: string
729
+ let hasGrades: boolean
730
+
731
+ switch (scoreType) {
732
+ case KPI_SCORE_TYPE.LOOKUP:
733
+ hasGrades = Array.isArray(kpi.grades) && kpi.grades.length > 0
734
+ label = hasGrades ? `${kpi.grades.length}개 등급 설정됨` : '등급 설정 없음'
735
+ break
736
+ case KPI_SCORE_TYPE.DIRECT:
737
+ // valueType으로 세분화 표시
738
+ if (kpi.valueType === KPI_VALUE_TYPE.ASSESSED) {
739
+ return html`<span style="color:#2196f3">평가형 (1~5)</span>`
740
+ }
741
+ if (kpi.valueType === KPI_VALUE_TYPE.CALCULATED) {
742
+ return html`<span style="color:#888">산식 계산</span>`
743
+ }
744
+ return html`<span style="color:#888">직접 (value=score)</span>`
745
+ case KPI_SCORE_TYPE.FORMULA:
746
+ return html`<span style="color:#888">산식 변환</span>`
747
+ case KPI_SCORE_TYPE.CUSTOM:
748
+ return this.renderCustomGradesCell(kpi)
749
+ default:
750
+ if (!scoreType) {
751
+ return html`<span style="color:#ccc">미설정</span>`
752
+ }
753
+ hasGrades = false
754
+ label = '등급 설정 없음'
755
+ }
756
+
757
+ return html`<span
758
+ style="color:${hasGrades ? '#4caf50' : '#999'};cursor:pointer;"
759
+ @click=${() => this._editGrades(kpi)}
760
+ >${label}</span
761
+ >`
762
+ }
763
+
764
+ /**
765
+ * CUSTOM scoreType의 grades 셀 렌더링. 서브클래스에서 override하여 구현.
766
+ */
767
+ protected renderCustomGradesCell(kpi: any): any {
768
+ return html`<span style="color:#999;cursor:pointer;" @click=${() => this._editGrades(kpi)}>커스텀 설정됨</span>`
769
+ }
770
+
771
+ /**
772
+ * 등급 편집 팝업. CUSTOM 타입은 _editCustomGrades()로 위임. 서브클래스에서 override 가능.
773
+ */
774
+ protected async _editGrades(kpi: any) {
775
+ if (!kpi.id) {
776
+ notify({ message: 'KPI를 먼저 저장한 후에 등급 설정을 할 수 있습니다.' })
777
+ return
778
+ }
779
+
780
+ const scoreType = inferScoreType(kpi)
659
781
 
782
+ if (scoreType === KPI_SCORE_TYPE.CUSTOM) {
783
+ await this._editCustomGrades(kpi)
660
784
  return
661
- }
785
+ }
786
+
787
+ if (scoreType !== KPI_SCORE_TYPE.LOOKUP) return
662
788
 
663
- const popup = await openPopup(html` <kpi-grade-editor .kpi=${kpi}></kpi-grade-editor> `, { title: `${kpi.name} - 등급 설정`,
789
+ const popup = await openPopup(html` <kpi-grade-editor .kpi=${kpi}></kpi-grade-editor> `, {
790
+ title: `${kpi.name} - 등급 설정`,
664
791
  size: 'large'
665
- })
792
+ })
793
+ popup.onclosed = () => { this.grist.fetch() }
794
+ }
666
795
 
667
- popup.onclosed = () => { this.grist.fetch()
668
- }
669
- }
796
+ /**
797
+ * CUSTOM scoreType의 등급 편집 팝업. 서브클래스에서 override하여 구현.
798
+ */
799
+ protected async _editCustomGrades(kpi: any) {
800
+ notify({ message: '커스텀 등급 편집기가 필요합니다.' })
801
+ }
670
802
 
671
803
  async _editViz(kpi: any) { const popup = await openPopup(
672
804
  html`
@@ -733,6 +865,7 @@ export class KpiListPage extends p13n(localize(i18next)(ScopedElementsMixin(Page
733
865
  active
734
866
  formula
735
867
  periodType
868
+ scoreType
736
869
  scoreFormula
737
870
  grades
738
871
  vizType
@@ -329,7 +329,7 @@ export class KpiMapPanel extends LitElement {
329
329
  this.onPeriodChange()
330
330
  }}
331
331
  >
332
- <option value="전체">전체</option>
332
+ <option value="2026">2026년</option>
333
333
  <option value="2025">2025년</option>
334
334
  <option value="2024">2024년</option>
335
335
  <option value="2023">2023년</option>
@@ -366,7 +366,7 @@ export class KpiMapPanel extends LitElement {
366
366
  this.onPeriodChange()
367
367
  }}
368
368
  >
369
- <option value="전체">전체</option>
369
+ <option value="2026">2026년</option>
370
370
  <option value="2025">2025년</option>
371
371
  <option value="2024">2024년</option>
372
372
  <option value="2023">2023년</option>
@@ -161,11 +161,25 @@ export class KpiMetricListPage extends p13n(localize(i18next)(ScopedElementsMixi
161
161
  { type: 'string', name: 'description', header: '설명', record: { editable: true }, width: 200 },
162
162
  { type: 'string', name: 'unit', header: '단위', record: { editable: true }, width: 80 },
163
163
  { type: 'string', name: 'source', header: '데이터출처', record: { editable: true }, width: 120 },
164
- { type: 'string',
164
+ { type: 'resource-object',
165
165
  name: 'dataSet',
166
166
  header: '데이터셋',
167
- record: { editable: false, renderer: (v, c, r) => r.dataSet?.name },
168
- width: 120
167
+ record: {
168
+ editable: true,
169
+ options: {
170
+ title: '데이터셋 선택',
171
+ queryName: 'dataSets',
172
+ columns: [
173
+ { name: 'id', hidden: true },
174
+ { name: 'name', header: '이름', filter: 'search' },
175
+ { name: 'description', header: '설명' }
176
+ ],
177
+ list: { fields: ['name', 'description'] },
178
+ nameField: 'name',
179
+ descriptionField: ''
180
+ }
181
+ },
182
+ width: 150
169
183
  },
170
184
  { type: 'string', name: 'fieldName', header: '필드명', record: { editable: true }, width: 120 },
171
185
  { type: 'select',
@@ -0,0 +1 @@
1
+ export { KpiListPage } from './pages/kpi/kpi-list-page.js';
@@ -1,2 +1,2 @@
1
- "use strict";
1
+ export { KpiListPage } from './pages/kpi/kpi-list-page.js';
2
2
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../client/index.ts"],"names":[],"mappings":"","sourcesContent":[""]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA","sourcesContent":["export { KpiListPage } from './pages/kpi/kpi-list-page.js'\n"]}
@@ -6,7 +6,36 @@ import '@operato/data-grist/ox-record-creator.js';
6
6
  import './kpi-viz-editor.js';
7
7
  import './kpi-grade-editor.js';
8
8
  import { PageView } from '@operato/shell';
9
- import { FetchOption } from '@operato/data-grist';
9
+ import { DataGrist, FetchOption } from '@operato/data-grist';
10
+ /**
11
+ * KPI scoreType — value → score 변환 방식 (null이면 미설정)
12
+ *
13
+ * DIRECT — value = score, 변환 없음
14
+ * FORMULA — scoreFormula 수식으로 변환
15
+ * LOOKUP — grade table(1D)로 변환
16
+ * CUSTOM — 2D 룩업 등 특수 변환
17
+ */
18
+ export declare const KPI_SCORE_TYPE: {
19
+ readonly DIRECT: "DIRECT";
20
+ readonly FORMULA: "FORMULA";
21
+ readonly LOOKUP: "LOOKUP";
22
+ readonly CUSTOM: "CUSTOM";
23
+ };
24
+ /**
25
+ * KPI valueType — value 획득 방식
26
+ *
27
+ * MEASURED — 외부 시스템 수집 측정값
28
+ * ASSESSED — 감리자 직접 평가 (1~5)
29
+ * CALCULATED — formula 자동 계산
30
+ * COMPOSITE — 다차원 복합 입력
31
+ */
32
+ export declare const KPI_VALUE_TYPE: {
33
+ readonly MEASURED: "MEASURED";
34
+ readonly ASSESSED: "ASSESSED";
35
+ readonly CALCULATED: "CALCULATED";
36
+ readonly COMPOSITE: "COMPOSITE";
37
+ };
38
+ export declare function inferScoreType(kpi: any): string;
10
39
  import { KpiImporter } from './kpi-importer';
11
40
  import { KpiGradeEditor } from './kpi-grade-editor';
12
41
  import { KpiVizEditor } from './kpi-viz-editor';
@@ -25,7 +54,7 @@ export declare class KpiListPage extends KpiListPage_base {
25
54
  };
26
55
  gristConfig: any;
27
56
  mode: 'CARD' | 'GRID' | 'LIST';
28
- private grist;
57
+ protected grist: DataGrist;
29
58
  availableVariables: any[];
30
59
  availableVariablesLoaded: boolean;
31
60
  hierarchicalView: boolean;
@@ -77,7 +106,23 @@ export declare class KpiListPage extends KpiListPage_base {
77
106
  creationCallback(kpi: any): Promise<boolean>;
78
107
  exportHandler(): Promise<{}[]>;
79
108
  importHandler(records: any): Promise<void>;
80
- _editGrades(kpi: any): Promise<void>;
109
+ /**
110
+ * grades 셀 렌더러. 서브클래스에서 override 가능.
111
+ * CUSTOM scoreType은 renderCustomGradesCell()로 위임.
112
+ */
113
+ protected renderGradesCell(kpi: any): any;
114
+ /**
115
+ * CUSTOM scoreType의 grades 셀 렌더링. 서브클래스에서 override하여 구현.
116
+ */
117
+ protected renderCustomGradesCell(kpi: any): any;
118
+ /**
119
+ * 등급 편집 팝업. CUSTOM 타입은 _editCustomGrades()로 위임. 서브클래스에서 override 가능.
120
+ */
121
+ protected _editGrades(kpi: any): Promise<void>;
122
+ /**
123
+ * CUSTOM scoreType의 등급 편집 팝업. 서브클래스에서 override하여 구현.
124
+ */
125
+ protected _editCustomGrades(kpi: any): Promise<void>;
81
126
  _editViz(kpi: any): Promise<void>;
82
127
  _onVizUpdated(kpiId: string, vizType: string, vizMeta: any): Promise<void>;
83
128
  _calculateKpiValue(kpi: any): Promise<void>;
@@ -19,6 +19,45 @@ import { OxPrompt } from '@operato/popup';
19
19
  import { isMobileDevice } from '@operato/utils';
20
20
  import { p13n } from '@operato/p13n';
21
21
  import gql from 'graphql-tag';
22
+ /**
23
+ * KPI scoreType — value → score 변환 방식 (null이면 미설정)
24
+ *
25
+ * DIRECT — value = score, 변환 없음
26
+ * FORMULA — scoreFormula 수식으로 변환
27
+ * LOOKUP — grade table(1D)로 변환
28
+ * CUSTOM — 2D 룩업 등 특수 변환
29
+ */
30
+ export const KPI_SCORE_TYPE = {
31
+ DIRECT: 'DIRECT',
32
+ FORMULA: 'FORMULA',
33
+ LOOKUP: 'LOOKUP',
34
+ CUSTOM: 'CUSTOM'
35
+ };
36
+ /**
37
+ * KPI valueType — value 획득 방식
38
+ *
39
+ * MEASURED — 외부 시스템 수집 측정값
40
+ * ASSESSED — 감리자 직접 평가 (1~5)
41
+ * CALCULATED — formula 자동 계산
42
+ * COMPOSITE — 다차원 복합 입력
43
+ */
44
+ export const KPI_VALUE_TYPE = {
45
+ MEASURED: 'MEASURED',
46
+ ASSESSED: 'ASSESSED',
47
+ CALCULATED: 'CALCULATED',
48
+ COMPOSITE: 'COMPOSITE'
49
+ };
50
+ export function inferScoreType(kpi) {
51
+ if (kpi.scoreType)
52
+ return kpi.scoreType;
53
+ if (kpi.scoreFormula)
54
+ return KPI_SCORE_TYPE.FORMULA;
55
+ if (kpi.grades && typeof kpi.grades === 'object' && !Array.isArray(kpi.grades))
56
+ return KPI_SCORE_TYPE.CUSTOM;
57
+ if (Array.isArray(kpi.grades) && kpi.grades.length > 0)
58
+ return KPI_SCORE_TYPE.LOOKUP;
59
+ return KPI_SCORE_TYPE.DIRECT;
60
+ }
22
61
  import { KpiImporter } from './kpi-importer';
23
62
  import { KpiGradeEditor } from './kpi-grade-editor';
24
63
  import { KpiVizEditor } from './kpi-viz-editor';
@@ -383,21 +422,42 @@ let KpiListPage = class KpiListPage extends p13n(localize(i18next)(ScopedElement
383
422
  name: 'grades',
384
423
  header: '성과지수 Lookup',
385
424
  record: { editable: false,
386
- renderer: (v, c, r) => {
387
- if (r.grades && r.grades.length > 0) {
388
- return html `<span style="color: #4caf50; cursor: pointer;" @click=${() => this._editGrades(r)}>
389
- ${r.grades.length}개 등급 설정됨
390
- </span>`;
391
- }
392
- else {
393
- return html `<span style="color: #999; cursor: pointer;" @click=${() => this._editGrades(r)}>
394
- 등급 설정 없음
395
- </span>`;
396
- }
397
- }
425
+ renderer: (v, c, r) => this.renderGradesCell(r)
398
426
  },
399
427
  width: 150
400
428
  },
429
+ {
430
+ type: 'select',
431
+ name: 'scoreType',
432
+ header: 'Score 산정',
433
+ record: {
434
+ editable: true,
435
+ options: [
436
+ { value: '', display: '(미설정)' },
437
+ { value: 'DIRECT', display: 'DIRECT (변환없음)' },
438
+ { value: 'FORMULA', display: 'FORMULA (수식)' },
439
+ { value: 'LOOKUP', display: 'LOOKUP (등급표)' },
440
+ { value: 'CUSTOM', display: 'CUSTOM (특수)' }
441
+ ]
442
+ },
443
+ width: 130
444
+ },
445
+ {
446
+ type: 'select',
447
+ name: 'valueType',
448
+ header: 'Value 획득',
449
+ record: {
450
+ editable: true,
451
+ options: [
452
+ { value: '', display: '(미설정)' },
453
+ { value: 'MEASURED', display: 'MEASURED (외부수집)' },
454
+ { value: 'ASSESSED', display: 'ASSESSED (감리자평가)' },
455
+ { value: 'CALCULATED', display: 'CALCULATED (산식)' },
456
+ { value: 'COMPOSITE', display: 'COMPOSITE (복합)' }
457
+ ]
458
+ },
459
+ width: 140
460
+ },
401
461
  { type: 'number',
402
462
  name: 'weight',
403
463
  header: '가중치',
@@ -486,6 +546,8 @@ let KpiListPage = class KpiListPage extends p13n(localize(i18next)(ScopedElement
486
546
  active
487
547
  formula
488
548
  periodType
549
+ scoreType
550
+ valueType
489
551
  scoreFormula
490
552
  grades
491
553
  vizType
@@ -648,18 +710,77 @@ let KpiListPage = class KpiListPage extends p13n(localize(i18next)(ScopedElement
648
710
  this.grist.fetch();
649
711
  };
650
712
  }
713
+ /**
714
+ * grades 셀 렌더러. 서브클래스에서 override 가능.
715
+ * CUSTOM scoreType은 renderCustomGradesCell()로 위임.
716
+ */
717
+ renderGradesCell(kpi) {
718
+ const scoreType = inferScoreType(kpi);
719
+ let label;
720
+ let hasGrades;
721
+ switch (scoreType) {
722
+ case KPI_SCORE_TYPE.LOOKUP:
723
+ hasGrades = Array.isArray(kpi.grades) && kpi.grades.length > 0;
724
+ label = hasGrades ? `${kpi.grades.length}개 등급 설정됨` : '등급 설정 없음';
725
+ break;
726
+ case KPI_SCORE_TYPE.DIRECT:
727
+ // valueType으로 세분화 표시
728
+ if (kpi.valueType === KPI_VALUE_TYPE.ASSESSED) {
729
+ return html `<span style="color:#2196f3">평가형 (1~5)</span>`;
730
+ }
731
+ if (kpi.valueType === KPI_VALUE_TYPE.CALCULATED) {
732
+ return html `<span style="color:#888">산식 계산</span>`;
733
+ }
734
+ return html `<span style="color:#888">직접 (value=score)</span>`;
735
+ case KPI_SCORE_TYPE.FORMULA:
736
+ return html `<span style="color:#888">산식 변환</span>`;
737
+ case KPI_SCORE_TYPE.CUSTOM:
738
+ return this.renderCustomGradesCell(kpi);
739
+ default:
740
+ if (!scoreType) {
741
+ return html `<span style="color:#ccc">미설정</span>`;
742
+ }
743
+ hasGrades = false;
744
+ label = '등급 설정 없음';
745
+ }
746
+ return html `<span
747
+ style="color:${hasGrades ? '#4caf50' : '#999'};cursor:pointer;"
748
+ @click=${() => this._editGrades(kpi)}
749
+ >${label}</span
750
+ >`;
751
+ }
752
+ /**
753
+ * CUSTOM scoreType의 grades 셀 렌더링. 서브클래스에서 override하여 구현.
754
+ */
755
+ renderCustomGradesCell(kpi) {
756
+ return html `<span style="color:#999;cursor:pointer;" @click=${() => this._editGrades(kpi)}>커스텀 설정됨</span>`;
757
+ }
758
+ /**
759
+ * 등급 편집 팝업. CUSTOM 타입은 _editCustomGrades()로 위임. 서브클래스에서 override 가능.
760
+ */
651
761
  async _editGrades(kpi) {
652
762
  if (!kpi.id) {
653
- notify({ message: 'KPI를 먼저 저장한 후에 등급 설정을 할 수 있습니다.'
654
- });
763
+ notify({ message: 'KPI를 먼저 저장한 후에 등급 설정을 할 수 있습니다.' });
655
764
  return;
656
765
  }
657
- const popup = await openPopup(html ` <kpi-grade-editor .kpi=${kpi}></kpi-grade-editor> `, { title: `${kpi.name} - 등급 설정`,
766
+ const scoreType = inferScoreType(kpi);
767
+ if (scoreType === KPI_SCORE_TYPE.CUSTOM) {
768
+ await this._editCustomGrades(kpi);
769
+ return;
770
+ }
771
+ if (scoreType !== KPI_SCORE_TYPE.LOOKUP)
772
+ return;
773
+ const popup = await openPopup(html ` <kpi-grade-editor .kpi=${kpi}></kpi-grade-editor> `, {
774
+ title: `${kpi.name} - 등급 설정`,
658
775
  size: 'large'
659
776
  });
660
- popup.onclosed = () => {
661
- this.grist.fetch();
662
- };
777
+ popup.onclosed = () => { this.grist.fetch(); };
778
+ }
779
+ /**
780
+ * CUSTOM scoreType의 등급 편집 팝업. 서브클래스에서 override하여 구현.
781
+ */
782
+ async _editCustomGrades(kpi) {
783
+ notify({ message: '커스텀 등급 편집기가 필요합니다.' });
663
784
  }
664
785
  async _editViz(kpi) {
665
786
  const popup = await openPopup(html `
@@ -731,6 +852,7 @@ let KpiListPage = class KpiListPage extends p13n(localize(i18next)(ScopedElement
731
852
  active
732
853
  formula
733
854
  periodType
855
+ scoreType
734
856
  scoreFormula
735
857
  grades
736
858
  vizType