@dssp/dkpi 1.0.0-alpha.58 → 1.0.0-alpha.59

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 (84) hide show
  1. package/dist-client/components/kpi-boxplot-chart.d.ts +1 -1
  2. package/dist-client/components/kpi-boxplot-chart.js +17 -17
  3. package/dist-client/components/kpi-boxplot-chart.js.map +1 -1
  4. package/dist-client/components/kpi-lookup-chart.d.ts +29 -0
  5. package/dist-client/components/kpi-lookup-chart.js +434 -0
  6. package/dist-client/components/kpi-lookup-chart.js.map +1 -0
  7. package/dist-client/components/kpi-mini-trend-chart.d.ts +14 -0
  8. package/dist-client/components/kpi-mini-trend-chart.js +148 -0
  9. package/dist-client/components/kpi-mini-trend-chart.js.map +1 -0
  10. package/dist-client/components/kpi-radar-chart.d.ts +1 -1
  11. package/dist-client/components/kpi-radar-chart.js +73 -55
  12. package/dist-client/components/kpi-radar-chart.js.map +1 -1
  13. package/dist-client/components/kpi-trend-chart.d.ts +25 -0
  14. package/dist-client/components/kpi-trend-chart.js +220 -0
  15. package/dist-client/components/kpi-trend-chart.js.map +1 -0
  16. package/dist-client/google-map/common-google-map.d.ts +35 -0
  17. package/dist-client/google-map/common-google-map.js +343 -0
  18. package/dist-client/google-map/common-google-map.js.map +1 -0
  19. package/dist-client/google-map/google-map-loader.d.ts +6 -0
  20. package/dist-client/google-map/google-map-loader.js +23 -0
  21. package/dist-client/google-map/google-map-loader.js.map +1 -0
  22. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.d.ts +17 -0
  23. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js +280 -0
  24. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js.map +1 -0
  25. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.d.ts +21 -0
  26. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js +389 -0
  27. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js.map +1 -0
  28. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.d.ts +25 -0
  29. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js +469 -0
  30. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js.map +1 -0
  31. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.d.ts +8 -0
  32. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js +78 -0
  33. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js.map +1 -0
  34. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.d.ts +34 -0
  35. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js +642 -0
  36. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js.map +1 -0
  37. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.d.ts +38 -0
  38. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js +501 -0
  39. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js.map +1 -0
  40. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.d.ts +26 -0
  41. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js +439 -0
  42. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js.map +1 -0
  43. package/dist-client/pages/kpi-dashboard/kpi-alert-panel.d.ts +18 -0
  44. package/dist-client/pages/kpi-dashboard/kpi-alert-panel.js +131 -0
  45. package/dist-client/pages/kpi-dashboard/kpi-alert-panel.js.map +1 -0
  46. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.d.ts +36 -0
  47. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +572 -0
  48. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -0
  49. package/dist-client/pages/kpi-dashboard/kpi-dashboard.d.ts +59 -0
  50. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js +1027 -0
  51. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js.map +1 -0
  52. package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.d.ts +12 -0
  53. package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.js +82 -0
  54. package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.js.map +1 -0
  55. package/dist-client/pages/kpi-dashboard/kpi-history-viewer.d.ts +11 -0
  56. package/dist-client/pages/kpi-dashboard/kpi-history-viewer.js +65 -0
  57. package/dist-client/pages/kpi-dashboard/kpi-history-viewer.js.map +1 -0
  58. package/dist-client/pages/kpi-dashboard/kpi-list-summary.d.ts +13 -0
  59. package/dist-client/pages/kpi-dashboard/kpi-list-summary.js +115 -0
  60. package/dist-client/pages/kpi-dashboard/kpi-list-summary.js.map +1 -0
  61. package/dist-client/pages/kpi-dashboard/kpi-performance-summary.d.ts +15 -0
  62. package/dist-client/pages/kpi-dashboard/kpi-performance-summary.js +147 -0
  63. package/dist-client/pages/kpi-dashboard/kpi-performance-summary.js.map +1 -0
  64. package/dist-client/pages/kpi-dashboard/kpi-value-entry.d.ts +7 -0
  65. package/dist-client/pages/kpi-dashboard/kpi-value-entry.js +86 -0
  66. package/dist-client/pages/kpi-dashboard/kpi-value-entry.js.map +1 -0
  67. package/dist-client/pages/sv-project-detail.d.ts +6 -0
  68. package/dist-client/pages/sv-project-detail.js +170 -10
  69. package/dist-client/pages/sv-project-detail.js.map +1 -1
  70. package/dist-client/route.d.ts +1 -1
  71. package/dist-client/route.js +3 -0
  72. package/dist-client/route.js.map +1 -1
  73. package/dist-client/tsconfig.tsbuildinfo +1 -1
  74. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.d.ts +16 -0
  75. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js +132 -16
  76. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js.map +1 -1
  77. package/dist-server/service/kpi-stat/kpi-stat-query.d.ts +6 -4
  78. package/dist-server/service/kpi-stat/kpi-stat-query.js +232 -22
  79. package/dist-server/service/kpi-stat/kpi-stat-query.js.map +1 -1
  80. package/dist-server/service/kpi-stat/kpi-stat-types.d.ts +6 -0
  81. package/dist-server/service/kpi-stat/kpi-stat-types.js +23 -1
  82. package/dist-server/service/kpi-stat/kpi-stat-types.js.map +1 -1
  83. package/dist-server/tsconfig.tsbuildinfo +1 -1
  84. package/package.json +3 -3
@@ -0,0 +1,147 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ import { html, css, LitElement } from 'lit';
3
+ import { customElement, state } from 'lit/decorators.js';
4
+ import { client } from '@operato/graphql';
5
+ import gql from 'graphql-tag';
6
+ let KpiPerformanceSummary = class KpiPerformanceSummary extends LitElement {
7
+ constructor() {
8
+ super(...arguments);
9
+ this.kpis = [];
10
+ this.loading = true;
11
+ this.error = '';
12
+ }
13
+ connectedCallback() {
14
+ super.connectedCallback();
15
+ this.fetchKpis();
16
+ }
17
+ async fetchKpis() {
18
+ this.loading = true;
19
+ this.error = '';
20
+ try {
21
+ const response = await client.query({
22
+ query: gql `
23
+ query {
24
+ kpis {
25
+ items {
26
+ id
27
+ name
28
+ description
29
+ value
30
+ targetValue
31
+ unit
32
+ }
33
+ total
34
+ }
35
+ }
36
+ `
37
+ });
38
+ this.kpis = (response.data.kpis.items || []).map(kpi => {
39
+ var _a, _b, _c, _d;
40
+ // value가 JSON 형태로 반환되므로 적절히 처리
41
+ let value = '-';
42
+ if (kpi.value && typeof kpi.value === 'object') {
43
+ value = (_b = (_a = kpi.value.value) !== null && _a !== void 0 ? _a : kpi.value.latestValue) !== null && _b !== void 0 ? _b : '-';
44
+ }
45
+ else if (kpi.value) {
46
+ value = kpi.value;
47
+ }
48
+ return {
49
+ name: kpi.name,
50
+ value: value,
51
+ target: (_c = kpi.targetValue) !== null && _c !== void 0 ? _c : '-',
52
+ unit: (_d = kpi.unit) !== null && _d !== void 0 ? _d : ''
53
+ };
54
+ });
55
+ }
56
+ catch (e) {
57
+ this.error = 'KPI 데이터를 불러오지 못했습니다.';
58
+ }
59
+ finally {
60
+ this.loading = false;
61
+ }
62
+ }
63
+ render() {
64
+ if (this.loading) {
65
+ return html `<div class="summary-container">로딩 중...</div>`;
66
+ }
67
+ if (this.error) {
68
+ return html `<div class="summary-container">${this.error}</div>`;
69
+ }
70
+ return html `
71
+ <div class="summary-container">
72
+ <div class="summary-title">KPI 실적 현황</div>
73
+ <div class="kpi-cards">
74
+ ${this.kpis.map(kpi => html `
75
+ <div class="kpi-card">
76
+ <div class="kpi-name">${kpi.name}</div>
77
+ <div class="kpi-value">${kpi.value}${kpi.unit}</div>
78
+ <div class="kpi-target">목표: ${kpi.target}${kpi.unit}</div>
79
+ </div>
80
+ `)}
81
+ </div>
82
+ </div>
83
+ `;
84
+ }
85
+ };
86
+ KpiPerformanceSummary.styles = css `
87
+ .summary-container {
88
+ background: #fff;
89
+ border-radius: 16px;
90
+ padding: 32px;
91
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
92
+ margin-bottom: 16px;
93
+ }
94
+ .summary-title {
95
+ font-size: 1.5rem;
96
+ font-weight: bold;
97
+ margin-bottom: 16px;
98
+ }
99
+ .kpi-cards {
100
+ display: flex;
101
+ gap: 24px;
102
+ flex-wrap: wrap;
103
+ }
104
+ .kpi-card {
105
+ background: #f7f7fa;
106
+ border-radius: 12px;
107
+ padding: 24px 32px;
108
+ min-width: 220px;
109
+ flex: 1;
110
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.03);
111
+ display: flex;
112
+ flex-direction: column;
113
+ align-items: flex-start;
114
+ }
115
+ .kpi-name {
116
+ font-size: 1.1rem;
117
+ font-weight: 500;
118
+ margin-bottom: 8px;
119
+ }
120
+ .kpi-value {
121
+ font-size: 2.2rem;
122
+ font-weight: bold;
123
+ color: #3a3ad6;
124
+ margin-bottom: 4px;
125
+ }
126
+ .kpi-target {
127
+ font-size: 1rem;
128
+ color: #888;
129
+ }
130
+ `;
131
+ __decorate([
132
+ state(),
133
+ __metadata("design:type", Array)
134
+ ], KpiPerformanceSummary.prototype, "kpis", void 0);
135
+ __decorate([
136
+ state(),
137
+ __metadata("design:type", Object)
138
+ ], KpiPerformanceSummary.prototype, "loading", void 0);
139
+ __decorate([
140
+ state(),
141
+ __metadata("design:type", Object)
142
+ ], KpiPerformanceSummary.prototype, "error", void 0);
143
+ KpiPerformanceSummary = __decorate([
144
+ customElement('kpi-performance-summary')
145
+ ], KpiPerformanceSummary);
146
+ export { KpiPerformanceSummary };
147
+ //# sourceMappingURL=kpi-performance-summary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kpi-performance-summary.js","sourceRoot":"","sources":["../../../client/pages/kpi-dashboard/kpi-performance-summary.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, css, LitElement } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\nimport { client } from '@operato/graphql'\nimport gql from 'graphql-tag'\n\n@customElement('kpi-performance-summary')\nexport class KpiPerformanceSummary extends LitElement {\n static styles = css`\n .summary-container {\n background: #fff;\n border-radius: 16px;\n padding: 32px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);\n margin-bottom: 16px;\n }\n .summary-title {\n font-size: 1.5rem;\n font-weight: bold;\n margin-bottom: 16px;\n }\n .kpi-cards {\n display: flex;\n gap: 24px;\n flex-wrap: wrap;\n }\n .kpi-card {\n background: #f7f7fa;\n border-radius: 12px;\n padding: 24px 32px;\n min-width: 220px;\n flex: 1;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.03);\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n }\n .kpi-name {\n font-size: 1.1rem;\n font-weight: 500;\n margin-bottom: 8px;\n }\n .kpi-value {\n font-size: 2.2rem;\n font-weight: bold;\n color: #3a3ad6;\n margin-bottom: 4px;\n }\n .kpi-target {\n font-size: 1rem;\n color: #888;\n }\n `\n\n @state() kpis: Array<{ name: string; value: string | number; target: string | number; unit: string }> = []\n @state() loading = true\n @state() error = ''\n\n connectedCallback() {\n super.connectedCallback()\n this.fetchKpis()\n }\n\n async fetchKpis() {\n this.loading = true\n this.error = ''\n try {\n const response = await client.query({\n query: gql`\n query {\n kpis {\n items {\n id\n name\n description\n value\n targetValue\n unit\n }\n total\n }\n }\n `\n })\n this.kpis = (response.data.kpis.items || []).map(kpi => {\n // value가 JSON 형태로 반환되므로 적절히 처리\n let value = '-'\n if (kpi.value && typeof kpi.value === 'object') {\n value = kpi.value.value ?? kpi.value.latestValue ?? '-'\n } else if (kpi.value) {\n value = kpi.value\n }\n\n return {\n name: kpi.name,\n value: value,\n target: kpi.targetValue ?? '-',\n unit: kpi.unit ?? ''\n }\n })\n } catch (e) {\n this.error = 'KPI 데이터를 불러오지 못했습니다.'\n } finally {\n this.loading = false\n }\n }\n\n render() {\n if (this.loading) {\n return html`<div class=\"summary-container\">로딩 중...</div>`\n }\n if (this.error) {\n return html`<div class=\"summary-container\">${this.error}</div>`\n }\n return html`\n <div class=\"summary-container\">\n <div class=\"summary-title\">KPI 실적 현황</div>\n <div class=\"kpi-cards\">\n ${this.kpis.map(\n kpi => html`\n <div class=\"kpi-card\">\n <div class=\"kpi-name\">${kpi.name}</div>\n <div class=\"kpi-value\">${kpi.value}${kpi.unit}</div>\n <div class=\"kpi-target\">목표: ${kpi.target}${kpi.unit}</div>\n </div>\n `\n )}\n </div>\n </div>\n `\n }\n}\n"]}
@@ -0,0 +1,7 @@
1
+ import { LitElement } from 'lit';
2
+ export declare class KpiValueEntry extends LitElement {
3
+ static styles: import("lit").CSSResult;
4
+ value: string;
5
+ render(): import("lit-html").TemplateResult<1>;
6
+ onSubmit(e: Event): void;
7
+ }
@@ -0,0 +1,86 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ import { html, css, LitElement } from 'lit';
3
+ import { customElement, state } from 'lit/decorators.js';
4
+ let KpiValueEntry = class KpiValueEntry extends LitElement {
5
+ constructor() {
6
+ super(...arguments);
7
+ this.value = '';
8
+ }
9
+ render() {
10
+ return html `
11
+ <div class="entry-container">
12
+ <div class="entry-title">KPI 실적 수동입력/자동집계</div>
13
+ <form class="entry-form" @submit=${this.onSubmit}>
14
+ <input
15
+ class="entry-input"
16
+ type="text"
17
+ .value=${this.value}
18
+ @input=${e => (this.value = e.target.value)}
19
+ placeholder="실적값 입력"
20
+ />
21
+ <button class="entry-btn" type="submit">저장</button>
22
+ </form>
23
+ <div class="entry-note">자동집계는 스케줄에 따라 별도 처리됩니다.</div>
24
+ </div>
25
+ `;
26
+ }
27
+ onSubmit(e) {
28
+ e.preventDefault();
29
+ // TODO: 저장 로직 구현
30
+ alert(`입력값: ${this.value}`);
31
+ this.value = '';
32
+ }
33
+ };
34
+ KpiValueEntry.styles = css `
35
+ .entry-container {
36
+ background: #fff;
37
+ border-radius: 16px;
38
+ padding: 32px;
39
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
40
+ margin-bottom: 16px;
41
+ }
42
+ .entry-title {
43
+ font-size: 1.5rem;
44
+ font-weight: bold;
45
+ margin-bottom: 16px;
46
+ }
47
+ .entry-form {
48
+ display: flex;
49
+ gap: 12px;
50
+ align-items: center;
51
+ }
52
+ .entry-input {
53
+ padding: 8px 12px;
54
+ border-radius: 8px;
55
+ border: 1px solid #ccc;
56
+ font-size: 1rem;
57
+ width: 120px;
58
+ }
59
+ .entry-btn {
60
+ padding: 8px 20px;
61
+ border-radius: 8px;
62
+ background: #3a3ad6;
63
+ color: #fff;
64
+ border: none;
65
+ font-size: 1rem;
66
+ cursor: pointer;
67
+ transition: background 0.2s;
68
+ }
69
+ .entry-btn:hover {
70
+ background: #2222aa;
71
+ }
72
+ .entry-note {
73
+ font-size: 0.95rem;
74
+ color: #888;
75
+ margin-top: 8px;
76
+ }
77
+ `;
78
+ __decorate([
79
+ state(),
80
+ __metadata("design:type", Object)
81
+ ], KpiValueEntry.prototype, "value", void 0);
82
+ KpiValueEntry = __decorate([
83
+ customElement('kpi-value-entry')
84
+ ], KpiValueEntry);
85
+ export { KpiValueEntry };
86
+ //# sourceMappingURL=kpi-value-entry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kpi-value-entry.js","sourceRoot":"","sources":["../../../client/pages/kpi-dashboard/kpi-value-entry.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAGjD,IAAM,aAAa,GAAnB,MAAM,aAAc,SAAQ,UAAU;IAAtC;;QA8CI,UAAK,GAAG,EAAE,CAAA;IA2BrB,CAAC;IAzBC,MAAM;QACJ,OAAO,IAAI,CAAA;;;2CAG4B,IAAI,CAAC,QAAQ;;;;qBAInC,IAAI,CAAC,KAAK;qBACV,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;;;;;;;KAOlD,CAAA;IACH,CAAC;IAED,QAAQ,CAAC,CAAQ;QACf,CAAC,CAAC,cAAc,EAAE,CAAA;QAClB,iBAAiB;QACjB,KAAK,CAAC,QAAQ,IAAI,CAAC,KAAK,EAAE,CAAC,CAAA;QAC3B,IAAI,CAAC,KAAK,GAAG,EAAE,CAAA;IACjB,CAAC;;AAvEM,oBAAM,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2ClB,AA3CY,CA2CZ;AAEQ;IAAR,KAAK,EAAE;;4CAAW;AA9CR,aAAa;IADzB,aAAa,CAAC,iBAAiB,CAAC;GACpB,aAAa,CAyEzB","sourcesContent":["import { html, css, LitElement } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\n\n@customElement('kpi-value-entry')\nexport class KpiValueEntry extends LitElement {\n static styles = css`\n .entry-container {\n background: #fff;\n border-radius: 16px;\n padding: 32px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);\n margin-bottom: 16px;\n }\n .entry-title {\n font-size: 1.5rem;\n font-weight: bold;\n margin-bottom: 16px;\n }\n .entry-form {\n display: flex;\n gap: 12px;\n align-items: center;\n }\n .entry-input {\n padding: 8px 12px;\n border-radius: 8px;\n border: 1px solid #ccc;\n font-size: 1rem;\n width: 120px;\n }\n .entry-btn {\n padding: 8px 20px;\n border-radius: 8px;\n background: #3a3ad6;\n color: #fff;\n border: none;\n font-size: 1rem;\n cursor: pointer;\n transition: background 0.2s;\n }\n .entry-btn:hover {\n background: #2222aa;\n }\n .entry-note {\n font-size: 0.95rem;\n color: #888;\n margin-top: 8px;\n }\n `\n\n @state() value = ''\n\n render() {\n return html`\n <div class=\"entry-container\">\n <div class=\"entry-title\">KPI 실적 수동입력/자동집계</div>\n <form class=\"entry-form\" @submit=${this.onSubmit}>\n <input\n class=\"entry-input\"\n type=\"text\"\n .value=${this.value}\n @input=${e => (this.value = e.target.value)}\n placeholder=\"실적값 입력\"\n />\n <button class=\"entry-btn\" type=\"submit\">저장</button>\n </form>\n <div class=\"entry-note\">자동집계는 스케줄에 따라 별도 처리됩니다.</div>\n </div>\n `\n }\n\n onSubmit(e: Event) {\n e.preventDefault()\n // TODO: 저장 로직 구현\n alert(`입력값: ${this.value}`)\n this.value = ''\n }\n}\n"]}
@@ -2,6 +2,7 @@ import { PageLifecycle, PageView } from '@operato/shell';
2
2
  import '../components/kpi-boxplot-chart';
3
3
  import '../components/kpi-single-boxplot-chart';
4
4
  import '../components/kpi-radar-chart';
5
+ import '../components/kpi-lookup-chart';
5
6
  declare const SvProjectDetailPage_base: typeof PageView & import("@open-wc/dedupe-mixin").Constructor<import("@open-wc/scoped-elements/types/src/types").ScopedElementsHost>;
6
7
  export declare class SvProjectDetailPage extends SvProjectDetailPage_base {
7
8
  static styles: import("lit").CSSResult[];
@@ -12,12 +13,17 @@ export declare class SvProjectDetailPage extends SvProjectDetailPage_base {
12
13
  private kpiComprehensiveStats;
13
14
  private kpiYComprehensiveStats;
14
15
  private projectXKpiValues;
16
+ private showLookupChart;
17
+ private selectedKpi;
15
18
  render(): import("lit-html").TemplateResult<1>;
16
19
  pageUpdated(changes: any, lifecycle: PageLifecycle): Promise<void>;
17
20
  initProject(projectId?: string): Promise<void>;
18
21
  getKpiComprehensiveStats(): Promise<void>;
19
22
  getKpiYComprehensiveStats(): Promise<void>;
20
23
  getProjectXKpiValues(projectId: string): Promise<void>;
24
+ private _onMetricLabelClick;
25
+ private _fetchKpiWithGradesByPattern;
26
+ private _closePopup;
21
27
  private getMetricValue;
22
28
  private getMetricGroups;
23
29
  private getYKpiBoxplotData;
@@ -9,6 +9,7 @@ import gql from 'graphql-tag';
9
9
  import '../components/kpi-boxplot-chart';
10
10
  import '../components/kpi-single-boxplot-chart';
11
11
  import '../components/kpi-radar-chart';
12
+ import '../components/kpi-lookup-chart';
12
13
  let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(PageView) {
13
14
  constructor() {
14
15
  super(...arguments);
@@ -16,6 +17,8 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
16
17
  this.kpiComprehensiveStats = [];
17
18
  this.kpiYComprehensiveStats = [];
18
19
  this.projectXKpiValues = [];
20
+ this.showLookupChart = false;
21
+ this.selectedKpi = null;
19
22
  }
20
23
  get context() {
21
24
  var _a;
@@ -152,11 +155,11 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
152
155
  </div>
153
156
 
154
157
  <div class="spider-area">
155
- <kpi-radar-chart
158
+ <sv-kpi-radar-chart
156
159
  .data=${this.getRadarChartData().data}
157
160
  .categories=${this.getRadarChartData().categories}
158
161
  .currentGroup=${'프로젝트성과'}
159
- ></kpi-radar-chart>
162
+ ></sv-kpi-radar-chart>
160
163
  </div>
161
164
  </div>
162
165
  </div>
@@ -174,7 +177,7 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
174
177
  이 프로젝트의 값은 진한 오렌지색 원으로 별도 강조되어 표시되며, 중앙값/평균과 다를 수 있습니다.
175
178
  </div>
176
179
  <div class="group-chart">
177
- <kpi-boxplot-chart .data=${this.getYKpiBoxplotData()}></kpi-boxplot-chart>
180
+ <sv-kpi-boxplot-chart .data=${this.getYKpiBoxplotData()}></sv-kpi-boxplot-chart>
178
181
  </div>
179
182
  </div>
180
183
 
@@ -184,7 +187,9 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
184
187
  <span>그룹별 상세 매트릭 데이터</span>
185
188
  <div class="triangle"></div>
186
189
  </div>
187
- <div class="metrics-dropdown">프로젝트의 6개 성과별 상세 주요 매트릭 데이터 ▼</div>
190
+ </div>
191
+ <div class="desc">
192
+ 프로젝트의 6개 성과별 상세 주요 매트릭 데이터 (각 세부 항목을 클릭하시면 평가 기준표를 확인할 수 있습니다)
188
193
  </div>
189
194
  <div class="metrics-content">
190
195
  ${Object.values(this.getMetricGroups()).map(group => html `
@@ -196,7 +201,13 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
196
201
  <div class="metric-items">
197
202
  ${group.metrics.map(metric => html `
198
203
  <div class="metric-item">
199
- <span class="metric-label">${metric.label}</span>
204
+ <span
205
+ class="metric-label"
206
+ @click=${() => this._onMetricLabelClick(metric)}
207
+ title="클릭하시면 평가 기준표를 확인할 수 있습니다"
208
+ >
209
+ ${metric.label}
210
+ </span>
200
211
  <span class="metric-value">${metric.value}</span>
201
212
  </div>
202
213
  `)}
@@ -207,6 +218,27 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
207
218
  </div>
208
219
  </div>
209
220
  </div>
221
+
222
+ ${this.showLookupChart && this.selectedKpi
223
+ ? html `
224
+ <div class="popup-overlay" @click=${this._closePopup}>
225
+ <div class="popup-content" @click=${(e) => e.stopPropagation()}>
226
+ <div class="popup-header">
227
+ <div class="popup-title">${this.selectedKpi.kpiName} - Lookup Table</div>
228
+ <button class="popup-close" @click=${this._closePopup}>×</button>
229
+ </div>
230
+ <div class="popup-chart-container">
231
+ <kpi-lookup-chart
232
+ .grades=${this.selectedKpi.grades || []}
233
+ .value=${this.selectedKpi.value}
234
+ unit=${this.selectedKpi.unit || ''}
235
+ kpiName=${this.selectedKpi.kpiName || ''}
236
+ ></kpi-lookup-chart>
237
+ </div>
238
+ </div>
239
+ </div>
240
+ `
241
+ : ''}
210
242
  `;
211
243
  }
212
244
  async pageUpdated(changes, lifecycle) {
@@ -359,6 +391,66 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
359
391
  this.projectXKpiValues = [];
360
392
  }
361
393
  }
394
+ _onMetricLabelClick(metric) {
395
+ // metric.pattern을 사용하여 X-KPI 조회
396
+ if (!metric.pattern) {
397
+ console.warn('No pattern found for metric:', metric);
398
+ return;
399
+ }
400
+ // X-KPI 이름 패턴 (예: X11, X12, X21, ...)
401
+ const kpiNamePattern = metric.pattern;
402
+ // 해당 KPI 정보 조회
403
+ this._fetchKpiWithGradesByPattern(kpiNamePattern, metric.label);
404
+ }
405
+ async _fetchKpiWithGradesByPattern(kpiNamePattern, displayName) {
406
+ var _a, _b;
407
+ try {
408
+ // X-KPI 이름 패턴으로 전체 이름 찾기 (projectXKpiValues에서)
409
+ const kpiValue = this.projectXKpiValues.find(kv => { var _a; return ((_a = kv.kpi) === null || _a === void 0 ? void 0 : _a.name) && kv.kpi.name.includes(kpiNamePattern); });
410
+ if (!kpiValue || !kpiValue.kpi) {
411
+ console.warn(`No KPI found for pattern: ${kpiNamePattern}`);
412
+ return;
413
+ }
414
+ // GraphQL로 KPI 상세 정보 (grades 포함) 조회
415
+ const response = await client.query({
416
+ query: gql `
417
+ query Kpi($id: String!) {
418
+ kpi(id: $id) {
419
+ id
420
+ name
421
+ grades
422
+ vizMeta
423
+ }
424
+ }
425
+ `,
426
+ variables: { id: kpiValue.kpi.id }
427
+ });
428
+ if (response.errors || !((_a = response.data) === null || _a === void 0 ? void 0 : _a.kpi)) {
429
+ console.error('KPI not found or error:', response.errors);
430
+ return;
431
+ }
432
+ const kpi = response.data.kpi;
433
+ // grades가 없으면 경고
434
+ if (!kpi.grades || kpi.grades.length === 0) {
435
+ console.warn(`No grades found for KPI: ${kpiValue.kpi.name}`);
436
+ // 그래도 팝업은 띄워서 "No grade data available" 메시지 표시
437
+ }
438
+ this.selectedKpi = {
439
+ kpiName: displayName,
440
+ grades: kpi.grades || [],
441
+ value: kpiValue.value || null,
442
+ unit: ((_b = kpi.vizMeta) === null || _b === void 0 ? void 0 : _b.unit) || ''
443
+ };
444
+ this.showLookupChart = true;
445
+ }
446
+ catch (error) {
447
+ console.error('Error fetching KPI:', error);
448
+ }
449
+ }
450
+ _closePopup() {
451
+ this.showLookupChart = false;
452
+ this.selectedKpi = null;
453
+ }
362
454
  getMetricValue(kpiNamePattern) {
363
455
  const kpiValue = this.projectXKpiValues.find(kv => { var _a; return ((_a = kv.kpi) === null || _a === void 0 ? void 0 : _a.name) && kv.kpi.name.includes(kpiNamePattern); });
364
456
  const value = (kpiValue === null || kpiValue === void 0 ? void 0 : kpiValue.value) || 0;
@@ -820,7 +912,6 @@ SvProjectDetailPage.styles = [
820
912
  display: flex;
821
913
  align-items: center;
822
914
  justify-content: space-between;
823
- margin-bottom: 15px;
824
915
  }
825
916
  .metrics-header .title {
826
917
  display: flex;
@@ -830,10 +921,12 @@ SvProjectDetailPage.styles = [
830
921
  font-weight: 700;
831
922
  font-size: 18px;
832
923
  }
833
- .metrics-dropdown {
834
- color: #666;
835
- font-size: 14px;
836
- cursor: pointer;
924
+ .metrics-card .desc {
925
+ color: #35618e;
926
+ font-size: 13px;
927
+ letter-spacing: -0.05em;
928
+ margin-top: 4px;
929
+ margin-bottom: 15px;
837
930
  }
838
931
  .metrics-content {
839
932
  display: flex;
@@ -896,6 +989,65 @@ SvProjectDetailPage.styles = [
896
989
  min-width: 40px;
897
990
  text-align: right;
898
991
  }
992
+
993
+ /* Popup overlay */
994
+ .popup-overlay {
995
+ position: fixed;
996
+ top: 0;
997
+ left: 0;
998
+ right: 0;
999
+ bottom: 0;
1000
+ background: rgba(0, 0, 0, 0.5);
1001
+ display: flex;
1002
+ align-items: center;
1003
+ justify-content: center;
1004
+ z-index: 1000;
1005
+ }
1006
+ .popup-content {
1007
+ background: white;
1008
+ border-radius: 12px;
1009
+ padding: 24px;
1010
+ max-width: 800px;
1011
+ width: 90%;
1012
+ max-height: 80vh;
1013
+ overflow-y: auto;
1014
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
1015
+ }
1016
+ .popup-header {
1017
+ display: flex;
1018
+ justify-content: space-between;
1019
+ align-items: center;
1020
+ margin-bottom: 20px;
1021
+ padding-bottom: 16px;
1022
+ border-bottom: 2px solid #e0e0e0;
1023
+ }
1024
+ .popup-title {
1025
+ font-size: 20px;
1026
+ font-weight: 700;
1027
+ color: #35618e;
1028
+ }
1029
+ .popup-close {
1030
+ cursor: pointer;
1031
+ font-size: 24px;
1032
+ color: #999;
1033
+ background: none;
1034
+ border: none;
1035
+ padding: 4px 8px;
1036
+ }
1037
+ .popup-close:hover {
1038
+ color: #333;
1039
+ }
1040
+ .popup-chart-container {
1041
+ min-height: 400px;
1042
+ height: 500px;
1043
+ }
1044
+ .metric-label {
1045
+ cursor: pointer;
1046
+ }
1047
+ .metric-label:hover {
1048
+ opacity: 0.7;
1049
+ text-decoration: underline;
1050
+ }
899
1051
  `
900
1052
  ];
901
1053
  __decorate([
@@ -914,6 +1066,14 @@ __decorate([
914
1066
  state(),
915
1067
  __metadata("design:type", Array)
916
1068
  ], SvProjectDetailPage.prototype, "projectXKpiValues", void 0);
1069
+ __decorate([
1070
+ state(),
1071
+ __metadata("design:type", Boolean)
1072
+ ], SvProjectDetailPage.prototype, "showLookupChart", void 0);
1073
+ __decorate([
1074
+ state(),
1075
+ __metadata("design:type", Object)
1076
+ ], SvProjectDetailPage.prototype, "selectedKpi", void 0);
917
1077
  SvProjectDetailPage = __decorate([
918
1078
  customElement('sv-project-detail')
919
1079
  ], SvProjectDetailPage);