@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.
- package/dist-client/components/kpi-boxplot-chart.d.ts +1 -1
- package/dist-client/components/kpi-boxplot-chart.js +17 -17
- package/dist-client/components/kpi-boxplot-chart.js.map +1 -1
- package/dist-client/components/kpi-lookup-chart.d.ts +29 -0
- package/dist-client/components/kpi-lookup-chart.js +434 -0
- package/dist-client/components/kpi-lookup-chart.js.map +1 -0
- package/dist-client/components/kpi-mini-trend-chart.d.ts +14 -0
- package/dist-client/components/kpi-mini-trend-chart.js +148 -0
- package/dist-client/components/kpi-mini-trend-chart.js.map +1 -0
- package/dist-client/components/kpi-radar-chart.d.ts +1 -1
- package/dist-client/components/kpi-radar-chart.js +73 -55
- package/dist-client/components/kpi-radar-chart.js.map +1 -1
- package/dist-client/components/kpi-trend-chart.d.ts +25 -0
- package/dist-client/components/kpi-trend-chart.js +220 -0
- package/dist-client/components/kpi-trend-chart.js.map +1 -0
- package/dist-client/google-map/common-google-map.d.ts +35 -0
- package/dist-client/google-map/common-google-map.js +343 -0
- package/dist-client/google-map/common-google-map.js.map +1 -0
- package/dist-client/google-map/google-map-loader.d.ts +6 -0
- package/dist-client/google-map/google-map-loader.js +23 -0
- package/dist-client/google-map/google-map-loader.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.d.ts +17 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js +280 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.d.ts +21 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js +389 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.d.ts +25 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js +469 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.d.ts +8 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js +78 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.d.ts +34 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js +642 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.d.ts +38 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js +501 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.d.ts +26 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js +439 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/kpi-alert-panel.d.ts +18 -0
- package/dist-client/pages/kpi-dashboard/kpi-alert-panel.js +131 -0
- package/dist-client/pages/kpi-dashboard/kpi-alert-panel.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.d.ts +36 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +572 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard.d.ts +59 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard.js +1027 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.d.ts +12 -0
- package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.js +82 -0
- package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/kpi-history-viewer.d.ts +11 -0
- package/dist-client/pages/kpi-dashboard/kpi-history-viewer.js +65 -0
- package/dist-client/pages/kpi-dashboard/kpi-history-viewer.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/kpi-list-summary.d.ts +13 -0
- package/dist-client/pages/kpi-dashboard/kpi-list-summary.js +115 -0
- package/dist-client/pages/kpi-dashboard/kpi-list-summary.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/kpi-performance-summary.d.ts +15 -0
- package/dist-client/pages/kpi-dashboard/kpi-performance-summary.js +147 -0
- package/dist-client/pages/kpi-dashboard/kpi-performance-summary.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/kpi-value-entry.d.ts +7 -0
- package/dist-client/pages/kpi-dashboard/kpi-value-entry.js +86 -0
- package/dist-client/pages/kpi-dashboard/kpi-value-entry.js.map +1 -0
- package/dist-client/pages/sv-project-detail.d.ts +6 -0
- package/dist-client/pages/sv-project-detail.js +170 -10
- package/dist-client/pages/sv-project-detail.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/route.js +3 -0
- package/dist-client/route.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.d.ts +16 -0
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js +132 -16
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js.map +1 -1
- package/dist-server/service/kpi-stat/kpi-stat-query.d.ts +6 -4
- package/dist-server/service/kpi-stat/kpi-stat-query.js +232 -22
- package/dist-server/service/kpi-stat/kpi-stat-query.js.map +1 -1
- package/dist-server/service/kpi-stat/kpi-stat-types.d.ts +6 -0
- package/dist-server/service/kpi-stat/kpi-stat-types.js +23 -1
- package/dist-server/service/kpi-stat/kpi-stat-types.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- 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,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
|
-
|
|
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
|
|
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-
|
|
834
|
-
color: #
|
|
835
|
-
font-size:
|
|
836
|
-
|
|
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);
|