@things-factory/kpi 10.0.0-beta.4 → 10.0.0-beta.41

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/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,14 +425,7 @@ 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
  },
@@ -487,6 +518,7 @@ export class KpiListPage extends p13n(localize(i18next)(ScopedElementsMixin(Page
487
518
  active
488
519
  formula
489
520
  periodType
521
+ scoreType
490
522
  scoreFormula
491
523
  grades
492
524
  vizType
@@ -654,19 +686,86 @@ export class KpiListPage extends p13n(localize(i18next)(ScopedElementsMixin(Page
654
686
  }
655
687
  }
656
688
 
657
- async _editGrades(kpi: any) { if (!kpi.id) { notify({ message: 'KPI를 먼저 저장한 후에 등급 설정을 할 수 있습니다.'
658
- })
689
+ /**
690
+ * grades 셀 렌더러. 서브클래스에서 override 가능.
691
+ * CUSTOM scoreType은 renderCustomGradesCell()로 위임.
692
+ */
693
+ protected renderGradesCell(kpi: any): any {
694
+ const scoreType = inferScoreType(kpi)
695
+ let label: string
696
+ let hasGrades: boolean
697
+
698
+ switch (scoreType) {
699
+ case KPI_SCORE_TYPE.LOOKUP:
700
+ hasGrades = Array.isArray(kpi.grades) && kpi.grades.length > 0
701
+ label = hasGrades ? `${kpi.grades.length}개 등급 설정됨` : '등급 설정 없음'
702
+ break
703
+ case KPI_SCORE_TYPE.DIRECT:
704
+ // valueType으로 세분화 표시
705
+ if (kpi.valueType === KPI_VALUE_TYPE.ASSESSED) {
706
+ return html`<span style="color:#2196f3">평가형 (1~5)</span>`
707
+ }
708
+ if (kpi.valueType === KPI_VALUE_TYPE.CALCULATED) {
709
+ return html`<span style="color:#888">산식 계산</span>`
710
+ }
711
+ return html`<span style="color:#888">직접 (value=score)</span>`
712
+ case KPI_SCORE_TYPE.FORMULA:
713
+ return html`<span style="color:#888">산식 변환</span>`
714
+ case KPI_SCORE_TYPE.CUSTOM:
715
+ return this.renderCustomGradesCell(kpi)
716
+ default:
717
+ if (!scoreType) {
718
+ return html`<span style="color:#ccc">미설정</span>`
719
+ }
720
+ hasGrades = false
721
+ label = '등급 설정 없음'
722
+ }
723
+
724
+ return html`<span
725
+ style="color:${hasGrades ? '#4caf50' : '#999'};cursor:pointer;"
726
+ @click=${() => this._editGrades(kpi)}
727
+ >${label}</span
728
+ >`
729
+ }
730
+
731
+ /**
732
+ * CUSTOM scoreType의 grades 셀 렌더링. 서브클래스에서 override하여 구현.
733
+ */
734
+ protected renderCustomGradesCell(kpi: any): any {
735
+ return html`<span style="color:#999;cursor:pointer;" @click=${() => this._editGrades(kpi)}>커스텀 설정됨</span>`
736
+ }
737
+
738
+ /**
739
+ * 등급 편집 팝업. CUSTOM 타입은 _editCustomGrades()로 위임. 서브클래스에서 override 가능.
740
+ */
741
+ protected async _editGrades(kpi: any) {
742
+ if (!kpi.id) {
743
+ notify({ message: 'KPI를 먼저 저장한 후에 등급 설정을 할 수 있습니다.' })
744
+ return
745
+ }
746
+
747
+ const scoreType = inferScoreType(kpi)
659
748
 
749
+ if (scoreType === KPI_SCORE_TYPE.CUSTOM) {
750
+ await this._editCustomGrades(kpi)
660
751
  return
661
- }
752
+ }
753
+
754
+ if (scoreType !== KPI_SCORE_TYPE.LOOKUP) return
662
755
 
663
- const popup = await openPopup(html` <kpi-grade-editor .kpi=${kpi}></kpi-grade-editor> `, { title: `${kpi.name} - 등급 설정`,
756
+ const popup = await openPopup(html` <kpi-grade-editor .kpi=${kpi}></kpi-grade-editor> `, {
757
+ title: `${kpi.name} - 등급 설정`,
664
758
  size: 'large'
665
- })
759
+ })
760
+ popup.onclosed = () => { this.grist.fetch() }
761
+ }
666
762
 
667
- popup.onclosed = () => { this.grist.fetch()
668
- }
669
- }
763
+ /**
764
+ * CUSTOM scoreType의 등급 편집 팝업. 서브클래스에서 override하여 구현.
765
+ */
766
+ protected async _editCustomGrades(kpi: any) {
767
+ notify({ message: '커스텀 등급 편집기가 필요합니다.' })
768
+ }
670
769
 
671
770
  async _editViz(kpi: any) { const popup = await openPopup(
672
771
  html`
@@ -733,6 +832,7 @@ export class KpiListPage extends p13n(localize(i18next)(ScopedElementsMixin(Page
733
832
  active
734
833
  formula
735
834
  periodType
835
+ scoreType
736
836
  scoreFormula
737
837
  grades
738
838
  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>
@@ -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,18 +422,7 @@ 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
  },
@@ -486,6 +514,7 @@ let KpiListPage = class KpiListPage extends p13n(localize(i18next)(ScopedElement
486
514
  active
487
515
  formula
488
516
  periodType
517
+ scoreType
489
518
  scoreFormula
490
519
  grades
491
520
  vizType
@@ -648,18 +677,77 @@ let KpiListPage = class KpiListPage extends p13n(localize(i18next)(ScopedElement
648
677
  this.grist.fetch();
649
678
  };
650
679
  }
680
+ /**
681
+ * grades 셀 렌더러. 서브클래스에서 override 가능.
682
+ * CUSTOM scoreType은 renderCustomGradesCell()로 위임.
683
+ */
684
+ renderGradesCell(kpi) {
685
+ const scoreType = inferScoreType(kpi);
686
+ let label;
687
+ let hasGrades;
688
+ switch (scoreType) {
689
+ case KPI_SCORE_TYPE.LOOKUP:
690
+ hasGrades = Array.isArray(kpi.grades) && kpi.grades.length > 0;
691
+ label = hasGrades ? `${kpi.grades.length}개 등급 설정됨` : '등급 설정 없음';
692
+ break;
693
+ case KPI_SCORE_TYPE.DIRECT:
694
+ // valueType으로 세분화 표시
695
+ if (kpi.valueType === KPI_VALUE_TYPE.ASSESSED) {
696
+ return html `<span style="color:#2196f3">평가형 (1~5)</span>`;
697
+ }
698
+ if (kpi.valueType === KPI_VALUE_TYPE.CALCULATED) {
699
+ return html `<span style="color:#888">산식 계산</span>`;
700
+ }
701
+ return html `<span style="color:#888">직접 (value=score)</span>`;
702
+ case KPI_SCORE_TYPE.FORMULA:
703
+ return html `<span style="color:#888">산식 변환</span>`;
704
+ case KPI_SCORE_TYPE.CUSTOM:
705
+ return this.renderCustomGradesCell(kpi);
706
+ default:
707
+ if (!scoreType) {
708
+ return html `<span style="color:#ccc">미설정</span>`;
709
+ }
710
+ hasGrades = false;
711
+ label = '등급 설정 없음';
712
+ }
713
+ return html `<span
714
+ style="color:${hasGrades ? '#4caf50' : '#999'};cursor:pointer;"
715
+ @click=${() => this._editGrades(kpi)}
716
+ >${label}</span
717
+ >`;
718
+ }
719
+ /**
720
+ * CUSTOM scoreType의 grades 셀 렌더링. 서브클래스에서 override하여 구현.
721
+ */
722
+ renderCustomGradesCell(kpi) {
723
+ return html `<span style="color:#999;cursor:pointer;" @click=${() => this._editGrades(kpi)}>커스텀 설정됨</span>`;
724
+ }
725
+ /**
726
+ * 등급 편집 팝업. CUSTOM 타입은 _editCustomGrades()로 위임. 서브클래스에서 override 가능.
727
+ */
651
728
  async _editGrades(kpi) {
652
729
  if (!kpi.id) {
653
- notify({ message: 'KPI를 먼저 저장한 후에 등급 설정을 할 수 있습니다.'
654
- });
730
+ notify({ message: 'KPI를 먼저 저장한 후에 등급 설정을 할 수 있습니다.' });
731
+ return;
732
+ }
733
+ const scoreType = inferScoreType(kpi);
734
+ if (scoreType === KPI_SCORE_TYPE.CUSTOM) {
735
+ await this._editCustomGrades(kpi);
655
736
  return;
656
737
  }
657
- const popup = await openPopup(html ` <kpi-grade-editor .kpi=${kpi}></kpi-grade-editor> `, { title: `${kpi.name} - 등급 설정`,
738
+ if (scoreType !== KPI_SCORE_TYPE.LOOKUP)
739
+ return;
740
+ const popup = await openPopup(html ` <kpi-grade-editor .kpi=${kpi}></kpi-grade-editor> `, {
741
+ title: `${kpi.name} - 등급 설정`,
658
742
  size: 'large'
659
743
  });
660
- popup.onclosed = () => {
661
- this.grist.fetch();
662
- };
744
+ popup.onclosed = () => { this.grist.fetch(); };
745
+ }
746
+ /**
747
+ * CUSTOM scoreType의 등급 편집 팝업. 서브클래스에서 override하여 구현.
748
+ */
749
+ async _editCustomGrades(kpi) {
750
+ notify({ message: '커스텀 등급 편집기가 필요합니다.' });
663
751
  }
664
752
  async _editViz(kpi) {
665
753
  const popup = await openPopup(html `
@@ -731,6 +819,7 @@ let KpiListPage = class KpiListPage extends p13n(localize(i18next)(ScopedElement
731
819
  active
732
820
  formula
733
821
  periodType
822
+ scoreType
734
823
  scoreFormula
735
824
  grades
736
825
  vizType