@dssp/dkpi 1.0.27 → 1.0.29
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/pages/kpi-dashboard/cards/kpi-level1-card.d.ts +2 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js +19 -4
- package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js.map +1 -1
- package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.d.ts +2 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js +19 -4
- package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js.map +1 -1
- package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.d.ts +2 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js +19 -4
- package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js.map +1 -1
- package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.d.ts +2 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js +78 -4
- package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js.map +1 -1
- package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.d.ts +2 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js +35 -4
- package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js.map +1 -1
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.d.ts +2 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +29 -1
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -1
- package/dist-client/pages/kpi-dashboard/kpi-dashboard.d.ts +11 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard.js +65 -12
- package/dist-client/pages/kpi-dashboard/kpi-dashboard.js.map +1 -1
- package/dist-client/pages/project-complete-tabs/pc-tab1-plan.d.ts +18 -0
- package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js +138 -64
- package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js.map +1 -1
- package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js +80 -8
- package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js.map +1 -1
- package/dist-client/pages/sv-project-detail.d.ts +33 -0
- package/dist-client/pages/sv-project-detail.js +130 -8
- package/dist-client/pages/sv-project-detail.js.map +1 -1
- package/dist-client/pages/sv-project-update.js +98 -31
- package/dist-client/pages/sv-project-update.js.map +1 -1
- package/dist-client/shared/complete-api.d.ts +7 -0
- package/dist-client/shared/complete-api.js +27 -0
- package/dist-client/shared/complete-api.js.map +1 -1
- package/dist-client/shared/project-axis.d.ts +23 -0
- package/dist-client/shared/project-axis.js +42 -0
- package/dist-client/shared/project-axis.js.map +1 -0
- package/dist-client/shared/sentiment-index.d.ts +47 -0
- package/dist-client/shared/sentiment-index.js +119 -0
- package/dist-client/shared/sentiment-index.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/index.js +14 -0
- package/dist-server/index.js.map +1 -1
- package/dist-server/scripts/load-grade-data-migration.js +17 -3
- package/dist-server/scripts/load-grade-data-migration.js.map +1 -1
- package/dist-server/scripts/propagate-parent-kpi-values.js +13 -2
- package/dist-server/scripts/propagate-parent-kpi-values.js.map +1 -1
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.d.ts +22 -0
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js +93 -1
- 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 +9 -9
- package/dist-server/service/kpi-stat/kpi-stat-query.js +71 -84
- package/dist-server/service/kpi-stat/kpi-stat-query.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/schema.graphql +2011 -67
|
@@ -9,6 +9,8 @@ let KpiLevel1Card = class KpiLevel1Card extends LitElement {
|
|
|
9
9
|
super(...arguments);
|
|
10
10
|
this.sectorType = '';
|
|
11
11
|
this.buildingUsage = '';
|
|
12
|
+
this.projectScale = '';
|
|
13
|
+
this.facilityType = '';
|
|
12
14
|
this.loading = true;
|
|
13
15
|
this.error = '';
|
|
14
16
|
this.totalScore = 0;
|
|
@@ -22,7 +24,10 @@ let KpiLevel1Card = class KpiLevel1Card extends LitElement {
|
|
|
22
24
|
this.fetchTotalScore();
|
|
23
25
|
}
|
|
24
26
|
updated(changedProperties) {
|
|
25
|
-
if (changedProperties.has('sectorType') ||
|
|
27
|
+
if (changedProperties.has('sectorType') ||
|
|
28
|
+
changedProperties.has('buildingUsage') ||
|
|
29
|
+
changedProperties.has('projectScale') ||
|
|
30
|
+
changedProperties.has('facilityType')) {
|
|
26
31
|
this.fetchTotalScore();
|
|
27
32
|
}
|
|
28
33
|
}
|
|
@@ -53,8 +58,8 @@ let KpiLevel1Card = class KpiLevel1Card extends LitElement {
|
|
|
53
58
|
try {
|
|
54
59
|
const response = await client.query({
|
|
55
60
|
query: gql `
|
|
56
|
-
query KpiLevel1Stats($sectorType: String, $buildingUsage: String) {
|
|
57
|
-
kpiProjectStats(kpiNamePattern: "Z", sectorType: $sectorType, buildingUsage: $buildingUsage) {
|
|
61
|
+
query KpiLevel1Stats($sectorType: String, $buildingUsage: String, $projectScale: String, $facilityType: String) {
|
|
62
|
+
kpiProjectStats(kpiNamePattern: "Z", sectorType: $sectorType, buildingUsage: $buildingUsage, projectScale: $projectScale, facilityType: $facilityType) {
|
|
58
63
|
kpiName
|
|
59
64
|
avgVal
|
|
60
65
|
minVal
|
|
@@ -65,7 +70,9 @@ let KpiLevel1Card = class KpiLevel1Card extends LitElement {
|
|
|
65
70
|
`,
|
|
66
71
|
variables: {
|
|
67
72
|
sectorType: this.sectorType || null,
|
|
68
|
-
buildingUsage: this.buildingUsage || null
|
|
73
|
+
buildingUsage: this.buildingUsage || null,
|
|
74
|
+
projectScale: this.projectScale || null,
|
|
75
|
+
facilityType: this.facilityType || null
|
|
69
76
|
}
|
|
70
77
|
});
|
|
71
78
|
const stats = response.data.kpiProjectStats || [];
|
|
@@ -224,6 +231,14 @@ __decorate([
|
|
|
224
231
|
property({ type: String }),
|
|
225
232
|
__metadata("design:type", String)
|
|
226
233
|
], KpiLevel1Card.prototype, "buildingUsage", void 0);
|
|
234
|
+
__decorate([
|
|
235
|
+
property({ type: String }),
|
|
236
|
+
__metadata("design:type", String)
|
|
237
|
+
], KpiLevel1Card.prototype, "projectScale", void 0);
|
|
238
|
+
__decorate([
|
|
239
|
+
property({ type: String }),
|
|
240
|
+
__metadata("design:type", String)
|
|
241
|
+
], KpiLevel1Card.prototype, "facilityType", void 0);
|
|
227
242
|
__decorate([
|
|
228
243
|
state(),
|
|
229
244
|
__metadata("design:type", Object)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kpi-level1-card.js","sourceRoot":"","sources":["../../../../client/pages/kpi-dashboard/cards/kpi-level1-card.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAC/B,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AAChC,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,GAAG,MAAM,aAAa,CAAA;AAGtB,IAAM,aAAa,GAAnB,MAAM,aAAc,SAAQ,UAAU;IAAtC;;QAyFuB,eAAU,GAAW,EAAE,CAAA;QACvB,kBAAa,GAAW,EAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"kpi-level1-card.js","sourceRoot":"","sources":["../../../../client/pages/kpi-dashboard/cards/kpi-level1-card.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, css } from 'lit'\nimport { customElement, state, property } from 'lit/decorators.js'\nimport { LitElement } from 'lit'\nimport { client } from '@operato/graphql'\nimport gql from 'graphql-tag'\n\n@customElement('kpi-level1-card')\nexport class KpiLevel1Card extends LitElement {\n static styles = css`\n :host {\n display: block;\n }\n .score-card {\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n border-radius: 16px;\n padding: 32px;\n color: white;\n box-shadow: 0 8px 32px rgba(102, 126, 234, 0.3);\n text-align: center;\n position: relative;\n overflow: hidden;\n }\n .score-card::before {\n content: '';\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(255, 255, 255, 0.1);\n border-radius: 16px;\n }\n .score-content {\n position: relative;\n z-index: 1;\n }\n .score-title {\n font-size: 1.2rem;\n font-weight: 600;\n margin-bottom: 16px;\n opacity: 0.9;\n }\n .score-value {\n font-size: 4rem;\n font-weight: bold;\n margin-bottom: 8px;\n text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);\n }\n .score-grade {\n font-size: 1.5rem;\n font-weight: 600;\n margin-bottom: 16px;\n opacity: 0.9;\n }\n .score-period {\n font-size: 0.9rem;\n opacity: 0.7;\n margin-bottom: 16px;\n }\n .score-details {\n display: flex;\n justify-content: space-around;\n margin-top: 24px;\n padding-top: 16px;\n border-top: 1px solid rgba(255, 255, 255, 0.2);\n }\n .detail-item {\n text-align: center;\n }\n .detail-value {\n font-size: 1.4rem;\n font-weight: bold;\n margin-bottom: 4px;\n }\n .detail-label {\n font-size: 0.8rem;\n opacity: 0.8;\n }\n .loading {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 200px;\n color: #666;\n font-size: 1.1rem;\n }\n .error {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 200px;\n color: #d32f2f;\n font-size: 1.1rem;\n }\n `\n\n @property({ type: String }) sectorType: string = ''\n @property({ type: String }) buildingUsage: string = ''\n @property({ type: String }) projectScale: string = ''\n @property({ type: String }) facilityType: string = ''\n\n @state() loading = true\n @state() error = ''\n @state() totalScore = 0\n @state() grade = ''\n @state() projectCount = 0\n @state() minScore = 0\n @state() maxScore = 0\n\n connectedCallback() {\n super.connectedCallback()\n this.fetchTotalScore()\n }\n\n override updated(changedProperties: Map<string, unknown>) {\n if (changedProperties.has('sectorType') ||\n changedProperties.has('buildingUsage') ||\n changedProperties.has('projectScale') ||\n changedProperties.has('facilityType')) {\n this.fetchTotalScore()\n }\n }\n\n private calculateGrade(score: number): string {\n if (score >= 90) return 'A+'\n if (score >= 85) return 'A'\n if (score >= 80) return 'A-'\n if (score >= 75) return 'B+'\n if (score >= 70) return 'B'\n if (score >= 65) return 'B-'\n if (score >= 60) return 'C+'\n if (score >= 55) return 'C'\n if (score >= 50) return 'C-'\n return 'D'\n }\n\n async fetchTotalScore() {\n this.loading = true\n this.error = ''\n\n try {\n const response = await client.query({\n query: gql`\n query KpiLevel1Stats($sectorType: String, $buildingUsage: String, $projectScale: String, $facilityType: String) {\n kpiProjectStats(kpiNamePattern: \"Z\", sectorType: $sectorType, buildingUsage: $buildingUsage, projectScale: $projectScale, facilityType: $facilityType) {\n kpiName\n avgVal\n minVal\n maxVal\n projectCount\n }\n }\n `,\n variables: {\n sectorType: this.sectorType || null,\n buildingUsage: this.buildingUsage || null,\n projectScale: this.projectScale || null,\n facilityType: this.facilityType || null\n }\n })\n\n const stats: any[] = response.data.kpiProjectStats || []\n\n if (stats.length === 0) {\n this.error = '데이터가 없습니다.'\n return\n }\n\n const zStat = stats[0]\n this.totalScore = Math.round(zStat.avgVal * 100)\n this.grade = this.calculateGrade(this.totalScore)\n this.projectCount = zStat.projectCount\n this.minScore = Math.round(zStat.minVal * 100)\n this.maxScore = Math.round(zStat.maxVal * 100)\n } catch (e) {\n console.error('총점 데이터를 불러오지 못했습니다:', e)\n this.error = '총점 데이터를 불러오지 못했습니다.'\n } finally {\n this.loading = false\n }\n }\n\n render() {\n if (this.loading) {\n return html`\n <div class=\"score-card\">\n <div class=\"loading\">총점 계산 중...</div>\n </div>\n `\n }\n\n if (this.error) {\n return html`\n <div class=\"score-card\">\n <div class=\"error\">${this.error}</div>\n </div>\n `\n }\n\n return html`\n <div class=\"score-card\">\n <div class=\"score-content\">\n <div class=\"score-title\">그룹 총점</div>\n <div class=\"score-value\">${this.totalScore}</div>\n <div class=\"score-grade\">${this.grade}</div>\n\n <div class=\"score-details\">\n <div class=\"detail-item\">\n <div class=\"detail-value\">${this.projectCount}</div>\n <div class=\"detail-label\">분석 프로젝트</div>\n </div>\n <div class=\"detail-item\">\n <div class=\"detail-value\">${this.minScore}</div>\n <div class=\"detail-label\">최저</div>\n </div>\n <div class=\"detail-item\">\n <div class=\"detail-value\">${this.maxScore}</div>\n <div class=\"detail-label\">최고</div>\n </div>\n </div>\n </div>\n </div>\n `\n }\n}\n"]}
|
|
@@ -11,6 +11,8 @@ let KpiLevel2Comparison = class KpiLevel2Comparison extends LitElement {
|
|
|
11
11
|
super(...arguments);
|
|
12
12
|
this.sectorType = '';
|
|
13
13
|
this.buildingUsage = '';
|
|
14
|
+
this.projectScale = '';
|
|
15
|
+
this.facilityType = '';
|
|
14
16
|
this.loading = true;
|
|
15
17
|
this.error = '';
|
|
16
18
|
this.radarData = [];
|
|
@@ -25,7 +27,10 @@ let KpiLevel2Comparison = class KpiLevel2Comparison extends LitElement {
|
|
|
25
27
|
this.fetchCategoryComparison();
|
|
26
28
|
}
|
|
27
29
|
updated(changedProperties) {
|
|
28
|
-
if (changedProperties.has('sectorType') ||
|
|
30
|
+
if (changedProperties.has('sectorType') ||
|
|
31
|
+
changedProperties.has('buildingUsage') ||
|
|
32
|
+
changedProperties.has('projectScale') ||
|
|
33
|
+
changedProperties.has('facilityType')) {
|
|
29
34
|
this.fetchCategoryComparison();
|
|
30
35
|
}
|
|
31
36
|
}
|
|
@@ -35,8 +40,8 @@ let KpiLevel2Comparison = class KpiLevel2Comparison extends LitElement {
|
|
|
35
40
|
try {
|
|
36
41
|
const response = await client.query({
|
|
37
42
|
query: gql `
|
|
38
|
-
query KpiLevel2Stats($sectorType: String, $buildingUsage: String) {
|
|
39
|
-
kpiProjectStats(kpiNamePattern: "X", sectorType: $sectorType, buildingUsage: $buildingUsage) {
|
|
43
|
+
query KpiLevel2Stats($sectorType: String, $buildingUsage: String, $projectScale: String, $facilityType: String) {
|
|
44
|
+
kpiProjectStats(kpiNamePattern: "X", sectorType: $sectorType, buildingUsage: $buildingUsage, projectScale: $projectScale, facilityType: $facilityType) {
|
|
40
45
|
kpiName
|
|
41
46
|
categoryName
|
|
42
47
|
minVal
|
|
@@ -52,7 +57,9 @@ let KpiLevel2Comparison = class KpiLevel2Comparison extends LitElement {
|
|
|
52
57
|
`,
|
|
53
58
|
variables: {
|
|
54
59
|
sectorType: this.sectorType || null,
|
|
55
|
-
buildingUsage: this.buildingUsage || null
|
|
60
|
+
buildingUsage: this.buildingUsage || null,
|
|
61
|
+
projectScale: this.projectScale || null,
|
|
62
|
+
facilityType: this.facilityType || null
|
|
56
63
|
}
|
|
57
64
|
});
|
|
58
65
|
const stats = response.data.kpiProjectStats || [];
|
|
@@ -307,6 +314,14 @@ __decorate([
|
|
|
307
314
|
property({ type: String }),
|
|
308
315
|
__metadata("design:type", String)
|
|
309
316
|
], KpiLevel2Comparison.prototype, "buildingUsage", void 0);
|
|
317
|
+
__decorate([
|
|
318
|
+
property({ type: String }),
|
|
319
|
+
__metadata("design:type", String)
|
|
320
|
+
], KpiLevel2Comparison.prototype, "projectScale", void 0);
|
|
321
|
+
__decorate([
|
|
322
|
+
property({ type: String }),
|
|
323
|
+
__metadata("design:type", String)
|
|
324
|
+
], KpiLevel2Comparison.prototype, "facilityType", void 0);
|
|
310
325
|
__decorate([
|
|
311
326
|
state(),
|
|
312
327
|
__metadata("design:type", Object)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kpi-level2-comparison.js","sourceRoot":"","sources":["../../../../client/pages/kpi-dashboard/cards/kpi-level2-comparison.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, css } from 'lit'\nimport { customElement, state, property } from 'lit/decorators.js'\nimport { LitElement } from 'lit'\nimport { client } from '@operato/graphql'\nimport gql from 'graphql-tag'\nimport '../../components/kpi-radar-chart'\nimport '../../components/kpi-boxplot-chart'\n\n@customElement('kpi-level2-comparison')\nexport class KpiLevel2Comparison extends LitElement {\n static styles = css`\n :host {\n display: block;\n }\n .comparison-container {\n background: #fff;\n border-radius: 16px;\n padding: 24px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);\n border: 1px solid #e0e0e0;\n width: 100%;\n }\n .comparison-title {\n font-size: 1.3rem;\n font-weight: bold;\n margin-bottom: 20px;\n color: #333;\n text-align: center;\n }\n .charts-section {\n display: flex;\n gap: 24px;\n margin-bottom: 20px;\n }\n .chart-card {\n flex: 1;\n background: #f8f9fa;\n border-radius: 12px;\n padding: 20px;\n border: 1px solid #e9ecef;\n }\n .chart-title {\n font-size: 1.1rem;\n font-weight: 600;\n margin-bottom: 16px;\n color: #495057;\n text-align: center;\n }\n .chart-container {\n height: 300px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .loading {\n color: #666;\n font-size: 1rem;\n }\n .error {\n color: #d32f2f;\n font-size: 1rem;\n }\n .no-data {\n color: #666;\n font-size: 1rem;\n text-align: center;\n }\n .summary-info {\n display: flex;\n justify-content: space-around;\n padding: 16px;\n background: #f8f9fa;\n border-radius: 8px;\n margin-top: 16px;\n }\n .summary-item {\n text-align: center;\n }\n .summary-value {\n font-size: 1.2rem;\n font-weight: bold;\n color: #333;\n margin-bottom: 4px;\n }\n .summary-label {\n font-size: 0.9rem;\n color: #666;\n }\n `\n\n @property({ type: String }) sectorType: string = ''\n @property({ type: String }) buildingUsage: string = ''\n\n @state() loading = true\n @state() error = ''\n @state() radarData: any[] = []\n @state() boxplotData: any[] = []\n @state() categories: string[] = []\n @state() totalCategories = 0\n @state() totalKpis = 0\n @state() averageScore = 0\n\n connectedCallback() {\n super.connectedCallback()\n this.fetchCategoryComparison()\n }\n\n override updated(changedProperties: Map<string, unknown>) {\n if (changedProperties.has('sectorType') || changedProperties.has('buildingUsage')) {\n this.fetchCategoryComparison()\n }\n }\n\n async fetchCategoryComparison() {\n this.loading = true\n this.error = ''\n\n try {\n const response = await client.query({\n query: gql`\n query KpiLevel2Stats($sectorType: String, $buildingUsage: String) {\n kpiProjectStats(kpiNamePattern: \"X\", sectorType: $sectorType, buildingUsage: $buildingUsage) {\n kpiName\n categoryName\n minVal\n q1Val\n medVal\n q3Val\n maxVal\n avgVal\n stddevVal\n projectCount\n }\n }\n `,\n variables: {\n sectorType: this.sectorType || null,\n buildingUsage: this.buildingUsage || null\n }\n })\n\n const stats: any[] = response.data.kpiProjectStats || []\n\n if (stats.length === 0) {\n this.error = '데이터가 없습니다.'\n return\n }\n\n // X-level KPIs를 categoryName(Y-level)으로 그룹화\n const categoryMap = new Map<string, any[]>()\n stats.forEach(stat => {\n const catName = stat.categoryName || '기타'\n if (!categoryMap.has(catName)) categoryMap.set(catName, [])\n categoryMap.get(catName)!.push(stat)\n })\n\n this.categories = Array.from(categoryMap.keys())\n this.totalCategories = this.categories.length\n this.totalKpis = stats.length\n\n this.generateRadarData(categoryMap)\n this.generateBoxplotData(categoryMap)\n\n const allAvg = stats.map(s => s.avgVal * 100)\n this.averageScore = Math.round(allAvg.reduce((a, b) => a + b, 0) / allAvg.length)\n } catch (e) {\n console.error('카테고리 비교 데이터를 불러오지 못했습니다:', e)\n this.error = '카테고리 비교 데이터를 불러오지 못했습니다.'\n } finally {\n this.loading = false\n }\n }\n\n private generateRadarData(categoryMap: Map<string, any[]>) {\n const result: any[] = []\n\n this.categories.forEach(category => {\n const stats = categoryMap.get(category)!\n const avgVals = stats.map(s => s.avgVal).filter(v => v > 0)\n const medVals = stats.map(s => s.medVal).filter(v => v > 0)\n const stddevVals = stats.map(s => s.stddevVal).filter(v => v > 0)\n\n if (avgVals.length > 0) {\n result.push({ group: '평균', category, value: avgVals.reduce((a, b) => a + b, 0) / avgVals.length })\n }\n if (medVals.length > 0) {\n result.push({ group: '중앙값', category, value: medVals.reduce((a, b) => a + b, 0) / medVals.length })\n }\n if (stddevVals.length > 0) {\n result.push({ group: '표준편차', category, value: stddevVals.reduce((a, b) => a + b, 0) / stddevVals.length })\n }\n })\n\n this.radarData = result\n }\n\n private generateBoxplotData(categoryMap: Map<string, any[]>) {\n const result: any[] = []\n\n this.categories.forEach(category => {\n const stats = categoryMap.get(category)!\n const avgVals = stats.map(s => s.avgVal)\n const medVals = stats.map(s => s.medVal)\n const minVals = stats.map(s => s.minVal)\n const maxVals = stats.map(s => s.maxVal)\n const q1Vals = stats.map(s => s.q1Val)\n const q3Vals = stats.map(s => s.q3Val)\n\n const avg = (arr: number[]) => arr.reduce((a, b) => a + b, 0) / arr.length\n\n result.push({\n group: category,\n min: Math.min(...minVals),\n q1: avg(q1Vals),\n median: avg(medVals),\n q3: avg(q3Vals),\n max: Math.max(...maxVals),\n mean: avg(avgVals),\n value: avg(avgVals)\n })\n })\n\n this.boxplotData = result\n }\n\n render() {\n if (this.loading) {\n return html`\n <div class=\"comparison-container\">\n <div class=\"comparison-title\">카테고리 비교 분석</div>\n <div class=\"charts-section\">\n <div class=\"chart-card\">\n <div class=\"chart-title\">레이더 차트</div>\n <div class=\"chart-container\">\n <div class=\"loading\">데이터 로딩 중...</div>\n </div>\n </div>\n <div class=\"chart-card\">\n <div class=\"chart-title\">박스플롯</div>\n <div class=\"chart-container\">\n <div class=\"loading\">데이터 로딩 중...</div>\n </div>\n </div>\n </div>\n </div>\n `\n }\n\n if (this.error) {\n return html`\n <div class=\"comparison-container\">\n <div class=\"comparison-title\">카테고리 비교 분석</div>\n <div class=\"charts-section\">\n <div class=\"chart-card\">\n <div class=\"chart-title\">레이더 차트</div>\n <div class=\"chart-container\">\n <div class=\"error\">${this.error}</div>\n </div>\n </div>\n <div class=\"chart-card\">\n <div class=\"chart-title\">박스플롯</div>\n <div class=\"chart-container\">\n <div class=\"error\">${this.error}</div>\n </div>\n </div>\n </div>\n </div>\n `\n }\n\n return html`\n <div class=\"comparison-container\">\n <div class=\"comparison-title\">카테고리 비교 분석</div>\n\n <div class=\"charts-section\">\n <div class=\"chart-card\">\n <div class=\"chart-title\">레이더 차트</div>\n <div class=\"chart-container\">\n ${this.radarData.length > 0\n ? html`<sv-kpi-radar-chart\n .data=${this.radarData}\n .categories=${this.categories}\n .currentGroup=${'평균'}\n ></sv-kpi-radar-chart>`\n : html`<div class=\"no-data\">데이터가 없습니다.</div>`}\n </div>\n </div>\n\n <div class=\"chart-card\">\n <div class=\"chart-title\">박스플롯</div>\n <div class=\"chart-container\">\n ${this.boxplotData.length > 0\n ? html`<sv-kpi-boxplot-chart\n .data=${this.boxplotData}\n .groups=${this.categories}\n .currentGroup=${'평균'}\n ></sv-kpi-boxplot-chart>`\n : html`<div class=\"no-data\">데이터가 없습니다.</div>`}\n </div>\n </div>\n </div>\n\n <div class=\"summary-info\">\n <div class=\"summary-item\">\n <div class=\"summary-value\">${this.totalCategories}</div>\n <div class=\"summary-label\">카테고리</div>\n </div>\n <div class=\"summary-item\">\n <div class=\"summary-value\">${this.totalKpis}</div>\n <div class=\"summary-label\">KPI</div>\n </div>\n <div class=\"summary-item\">\n <div class=\"summary-value\">${this.averageScore}</div>\n <div class=\"summary-label\">평균 점수</div>\n </div>\n </div>\n </div>\n `\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"kpi-level2-comparison.js","sourceRoot":"","sources":["../../../../client/pages/kpi-dashboard/cards/kpi-level2-comparison.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, css } from 'lit'\nimport { customElement, state, property } from 'lit/decorators.js'\nimport { LitElement } from 'lit'\nimport { client } from '@operato/graphql'\nimport gql from 'graphql-tag'\nimport '../../components/kpi-radar-chart'\nimport '../../components/kpi-boxplot-chart'\n\n@customElement('kpi-level2-comparison')\nexport class KpiLevel2Comparison extends LitElement {\n static styles = css`\n :host {\n display: block;\n }\n .comparison-container {\n background: #fff;\n border-radius: 16px;\n padding: 24px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);\n border: 1px solid #e0e0e0;\n width: 100%;\n }\n .comparison-title {\n font-size: 1.3rem;\n font-weight: bold;\n margin-bottom: 20px;\n color: #333;\n text-align: center;\n }\n .charts-section {\n display: flex;\n gap: 24px;\n margin-bottom: 20px;\n }\n .chart-card {\n flex: 1;\n background: #f8f9fa;\n border-radius: 12px;\n padding: 20px;\n border: 1px solid #e9ecef;\n }\n .chart-title {\n font-size: 1.1rem;\n font-weight: 600;\n margin-bottom: 16px;\n color: #495057;\n text-align: center;\n }\n .chart-container {\n height: 300px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .loading {\n color: #666;\n font-size: 1rem;\n }\n .error {\n color: #d32f2f;\n font-size: 1rem;\n }\n .no-data {\n color: #666;\n font-size: 1rem;\n text-align: center;\n }\n .summary-info {\n display: flex;\n justify-content: space-around;\n padding: 16px;\n background: #f8f9fa;\n border-radius: 8px;\n margin-top: 16px;\n }\n .summary-item {\n text-align: center;\n }\n .summary-value {\n font-size: 1.2rem;\n font-weight: bold;\n color: #333;\n margin-bottom: 4px;\n }\n .summary-label {\n font-size: 0.9rem;\n color: #666;\n }\n `\n\n @property({ type: String }) sectorType: string = ''\n @property({ type: String }) buildingUsage: string = ''\n @property({ type: String }) projectScale: string = ''\n @property({ type: String }) facilityType: string = ''\n\n @state() loading = true\n @state() error = ''\n @state() radarData: any[] = []\n @state() boxplotData: any[] = []\n @state() categories: string[] = []\n @state() totalCategories = 0\n @state() totalKpis = 0\n @state() averageScore = 0\n\n connectedCallback() {\n super.connectedCallback()\n this.fetchCategoryComparison()\n }\n\n override updated(changedProperties: Map<string, unknown>) {\n if (changedProperties.has('sectorType') ||\n changedProperties.has('buildingUsage') ||\n changedProperties.has('projectScale') ||\n changedProperties.has('facilityType')) {\n this.fetchCategoryComparison()\n }\n }\n\n async fetchCategoryComparison() {\n this.loading = true\n this.error = ''\n\n try {\n const response = await client.query({\n query: gql`\n query KpiLevel2Stats($sectorType: String, $buildingUsage: String, $projectScale: String, $facilityType: String) {\n kpiProjectStats(kpiNamePattern: \"X\", sectorType: $sectorType, buildingUsage: $buildingUsage, projectScale: $projectScale, facilityType: $facilityType) {\n kpiName\n categoryName\n minVal\n q1Val\n medVal\n q3Val\n maxVal\n avgVal\n stddevVal\n projectCount\n }\n }\n `,\n variables: {\n sectorType: this.sectorType || null,\n buildingUsage: this.buildingUsage || null,\n projectScale: this.projectScale || null,\n facilityType: this.facilityType || null\n }\n })\n\n const stats: any[] = response.data.kpiProjectStats || []\n\n if (stats.length === 0) {\n this.error = '데이터가 없습니다.'\n return\n }\n\n // X-level KPIs를 categoryName(Y-level)으로 그룹화\n const categoryMap = new Map<string, any[]>()\n stats.forEach(stat => {\n const catName = stat.categoryName || '기타'\n if (!categoryMap.has(catName)) categoryMap.set(catName, [])\n categoryMap.get(catName)!.push(stat)\n })\n\n this.categories = Array.from(categoryMap.keys())\n this.totalCategories = this.categories.length\n this.totalKpis = stats.length\n\n this.generateRadarData(categoryMap)\n this.generateBoxplotData(categoryMap)\n\n const allAvg = stats.map(s => s.avgVal * 100)\n this.averageScore = Math.round(allAvg.reduce((a, b) => a + b, 0) / allAvg.length)\n } catch (e) {\n console.error('카테고리 비교 데이터를 불러오지 못했습니다:', e)\n this.error = '카테고리 비교 데이터를 불러오지 못했습니다.'\n } finally {\n this.loading = false\n }\n }\n\n private generateRadarData(categoryMap: Map<string, any[]>) {\n const result: any[] = []\n\n this.categories.forEach(category => {\n const stats = categoryMap.get(category)!\n const avgVals = stats.map(s => s.avgVal).filter(v => v > 0)\n const medVals = stats.map(s => s.medVal).filter(v => v > 0)\n const stddevVals = stats.map(s => s.stddevVal).filter(v => v > 0)\n\n if (avgVals.length > 0) {\n result.push({ group: '평균', category, value: avgVals.reduce((a, b) => a + b, 0) / avgVals.length })\n }\n if (medVals.length > 0) {\n result.push({ group: '중앙값', category, value: medVals.reduce((a, b) => a + b, 0) / medVals.length })\n }\n if (stddevVals.length > 0) {\n result.push({ group: '표준편차', category, value: stddevVals.reduce((a, b) => a + b, 0) / stddevVals.length })\n }\n })\n\n this.radarData = result\n }\n\n private generateBoxplotData(categoryMap: Map<string, any[]>) {\n const result: any[] = []\n\n this.categories.forEach(category => {\n const stats = categoryMap.get(category)!\n const avgVals = stats.map(s => s.avgVal)\n const medVals = stats.map(s => s.medVal)\n const minVals = stats.map(s => s.minVal)\n const maxVals = stats.map(s => s.maxVal)\n const q1Vals = stats.map(s => s.q1Val)\n const q3Vals = stats.map(s => s.q3Val)\n\n const avg = (arr: number[]) => arr.reduce((a, b) => a + b, 0) / arr.length\n\n result.push({\n group: category,\n min: Math.min(...minVals),\n q1: avg(q1Vals),\n median: avg(medVals),\n q3: avg(q3Vals),\n max: Math.max(...maxVals),\n mean: avg(avgVals),\n value: avg(avgVals)\n })\n })\n\n this.boxplotData = result\n }\n\n render() {\n if (this.loading) {\n return html`\n <div class=\"comparison-container\">\n <div class=\"comparison-title\">카테고리 비교 분석</div>\n <div class=\"charts-section\">\n <div class=\"chart-card\">\n <div class=\"chart-title\">레이더 차트</div>\n <div class=\"chart-container\">\n <div class=\"loading\">데이터 로딩 중...</div>\n </div>\n </div>\n <div class=\"chart-card\">\n <div class=\"chart-title\">박스플롯</div>\n <div class=\"chart-container\">\n <div class=\"loading\">데이터 로딩 중...</div>\n </div>\n </div>\n </div>\n </div>\n `\n }\n\n if (this.error) {\n return html`\n <div class=\"comparison-container\">\n <div class=\"comparison-title\">카테고리 비교 분석</div>\n <div class=\"charts-section\">\n <div class=\"chart-card\">\n <div class=\"chart-title\">레이더 차트</div>\n <div class=\"chart-container\">\n <div class=\"error\">${this.error}</div>\n </div>\n </div>\n <div class=\"chart-card\">\n <div class=\"chart-title\">박스플롯</div>\n <div class=\"chart-container\">\n <div class=\"error\">${this.error}</div>\n </div>\n </div>\n </div>\n </div>\n `\n }\n\n return html`\n <div class=\"comparison-container\">\n <div class=\"comparison-title\">카테고리 비교 분석</div>\n\n <div class=\"charts-section\">\n <div class=\"chart-card\">\n <div class=\"chart-title\">레이더 차트</div>\n <div class=\"chart-container\">\n ${this.radarData.length > 0\n ? html`<sv-kpi-radar-chart\n .data=${this.radarData}\n .categories=${this.categories}\n .currentGroup=${'평균'}\n ></sv-kpi-radar-chart>`\n : html`<div class=\"no-data\">데이터가 없습니다.</div>`}\n </div>\n </div>\n\n <div class=\"chart-card\">\n <div class=\"chart-title\">박스플롯</div>\n <div class=\"chart-container\">\n ${this.boxplotData.length > 0\n ? html`<sv-kpi-boxplot-chart\n .data=${this.boxplotData}\n .groups=${this.categories}\n .currentGroup=${'평균'}\n ></sv-kpi-boxplot-chart>`\n : html`<div class=\"no-data\">데이터가 없습니다.</div>`}\n </div>\n </div>\n </div>\n\n <div class=\"summary-info\">\n <div class=\"summary-item\">\n <div class=\"summary-value\">${this.totalCategories}</div>\n <div class=\"summary-label\">카테고리</div>\n </div>\n <div class=\"summary-item\">\n <div class=\"summary-value\">${this.totalKpis}</div>\n <div class=\"summary-label\">KPI</div>\n </div>\n <div class=\"summary-item\">\n <div class=\"summary-value\">${this.averageScore}</div>\n <div class=\"summary-label\">평균 점수</div>\n </div>\n </div>\n </div>\n `\n }\n}\n"]}
|
|
@@ -11,6 +11,8 @@ let KpiLevel3Comparison = class KpiLevel3Comparison extends LitElement {
|
|
|
11
11
|
super(...arguments);
|
|
12
12
|
this.sectorType = '';
|
|
13
13
|
this.buildingUsage = '';
|
|
14
|
+
this.projectScale = '';
|
|
15
|
+
this.facilityType = '';
|
|
14
16
|
this.loading = true;
|
|
15
17
|
this.error = '';
|
|
16
18
|
this.radarData = [];
|
|
@@ -27,7 +29,10 @@ let KpiLevel3Comparison = class KpiLevel3Comparison extends LitElement {
|
|
|
27
29
|
this.fetchKpiComparison();
|
|
28
30
|
}
|
|
29
31
|
updated(changedProperties) {
|
|
30
|
-
if (changedProperties.has('sectorType') ||
|
|
32
|
+
if (changedProperties.has('sectorType') ||
|
|
33
|
+
changedProperties.has('buildingUsage') ||
|
|
34
|
+
changedProperties.has('projectScale') ||
|
|
35
|
+
changedProperties.has('facilityType')) {
|
|
31
36
|
this.fetchKpiComparison();
|
|
32
37
|
}
|
|
33
38
|
}
|
|
@@ -38,8 +43,8 @@ let KpiLevel3Comparison = class KpiLevel3Comparison extends LitElement {
|
|
|
38
43
|
try {
|
|
39
44
|
const response = await client.query({
|
|
40
45
|
query: gql `
|
|
41
|
-
query KpiLevel3Stats($sectorType: String, $buildingUsage: String) {
|
|
42
|
-
kpiProjectStats(kpiNamePattern: "X", sectorType: $sectorType, buildingUsage: $buildingUsage) {
|
|
46
|
+
query KpiLevel3Stats($sectorType: String, $buildingUsage: String, $projectScale: String, $facilityType: String) {
|
|
47
|
+
kpiProjectStats(kpiNamePattern: "X", sectorType: $sectorType, buildingUsage: $buildingUsage, projectScale: $projectScale, facilityType: $facilityType) {
|
|
43
48
|
kpiName
|
|
44
49
|
categoryName
|
|
45
50
|
minVal
|
|
@@ -55,7 +60,9 @@ let KpiLevel3Comparison = class KpiLevel3Comparison extends LitElement {
|
|
|
55
60
|
`,
|
|
56
61
|
variables: {
|
|
57
62
|
sectorType: this.sectorType || null,
|
|
58
|
-
buildingUsage: this.buildingUsage || null
|
|
63
|
+
buildingUsage: this.buildingUsage || null,
|
|
64
|
+
projectScale: this.projectScale || null,
|
|
65
|
+
facilityType: this.facilityType || null
|
|
59
66
|
}
|
|
60
67
|
});
|
|
61
68
|
const stats = response.data.kpiProjectStats || [];
|
|
@@ -386,6 +393,14 @@ __decorate([
|
|
|
386
393
|
property({ type: String }),
|
|
387
394
|
__metadata("design:type", String)
|
|
388
395
|
], KpiLevel3Comparison.prototype, "buildingUsage", void 0);
|
|
396
|
+
__decorate([
|
|
397
|
+
property({ type: String }),
|
|
398
|
+
__metadata("design:type", String)
|
|
399
|
+
], KpiLevel3Comparison.prototype, "projectScale", void 0);
|
|
400
|
+
__decorate([
|
|
401
|
+
property({ type: String }),
|
|
402
|
+
__metadata("design:type", String)
|
|
403
|
+
], KpiLevel3Comparison.prototype, "facilityType", void 0);
|
|
389
404
|
__decorate([
|
|
390
405
|
state(),
|
|
391
406
|
__metadata("design:type", Object)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kpi-level3-comparison.js","sourceRoot":"","sources":["../../../../client/pages/kpi-dashboard/cards/kpi-level3-comparison.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,KAAK,CAAA;AACxC,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AAChC,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,GAAG,MAAM,aAAa,CAAA;AAC7B,OAAO,kCAAkC,CAAA;AACzC,OAAO,oCAAoC,CAAA;AAGpC,IAAM,mBAAmB,GAAzB,MAAM,mBAAoB,SAAQ,UAAU;IAA5C;;QAmHuB,eAAU,GAAW,EAAE,CAAA;QACvB,kBAAa,GAAW,EAAE,CAAA;QAE7C,YAAO,GAAG,IAAI,CAAA;QACd,UAAK,GAAG,EAAE,CAAA;QACV,cAAS,GAAU,EAAE,CAAA;QACrB,gBAAW,GAAU,EAAE,CAAA;QACvB,SAAI,GAAa,EAAE,CAAA;QACnB,wBAAmB,GAAa,EAAE,CAAA;QAClC,qBAAgB,GAAG,EAAE,CAAA;QACrB,cAAS,GAAG,CAAC,CAAA;QACb,iBAAY,GAAG,CAAC,CAAA;QAChB,kBAAa,GAAG,IAAI,GAAG,EAAiB,CAAA;IAoQnD,CAAC;IAlQC,iBAAiB;QACf,KAAK,CAAC,iBAAiB,EAAE,CAAA;QACzB,IAAI,CAAC,kBAAkB,EAAE,CAAA;IAC3B,CAAC;IAEQ,OAAO,CAAC,iBAAuC;QACtD,IAAI,iBAAiB,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,iBAAiB,CAAC,GAAG,CAAC,eAAe,CAAC,EAAE,CAAC;YAClF,IAAI,CAAC,kBAAkB,EAAE,CAAA;QAC3B,CAAC;IACH,CAAC;IAED,KAAK,CAAC,kBAAkB;;QACtB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAA;QACnB,IAAI,CAAC,KAAK,GAAG,EAAE,CAAA;QAEf,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC;gBAClC,KAAK,EAAE,GAAG,CAAA;;;;;;;;;;;;;;;SAeT;gBACD,SAAS,EAAE;oBACT,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,IAAI;oBACnC,aAAa,EAAE,IAAI,CAAC,aAAa,IAAI,IAAI;iBAC1C;aACF,CAAC,CAAA;YAEF,MAAM,KAAK,GAAU,QAAQ,CAAC,IAAI,CAAC,eAAe,IAAI,EAAE,CAAA;YAExD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACvB,IAAI,CAAC,KAAK,GAAG,YAAY,CAAA;gBACzB,OAAM;YACR,CAAC;YAED,4CAA4C;YAC5C,MAAM,WAAW,GAAG,IAAI,GAAG,EAAiB,CAAA;YAC5C,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;gBACnB,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,IAAI,IAAI,CAAA;gBACzC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC;oBAAE,WAAW,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,CAAA;gBAC3D,WAAW,CAAC,GAAG,CAAC,OAAO,CAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACtC,CAAC,CAAC,CAAA;YAEF,IAAI,CAAC,aAAa,GAAG,WAAW,CAAA;YAChC,IAAI,CAAC,mBAAmB,GAAG,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAA;YAEzD,mBAAmB;YACnB,IAAI,CAAC,IAAI,CAAC,gBAAgB,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC;gBACtE,IAAI,CAAC,gBAAgB,GAAG,MAAA,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,mCAAI,EAAE,CAAA;YAC3D,CAAC;YAED,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBAC1B,IAAI,CAAC,iBAAiB,EAAE,CAAA;YAC1B,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,CAAC,CAAC,CAAA;YAC3C,IAAI,CAAC,KAAK,GAAG,yBAAyB,CAAA;QACxC,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,OAAO,GAAG,KAAK,CAAA;QACtB,CAAC;IACH,CAAC;IAEO,gBAAgB,CAAC,KAAY;QACnC,MAAM,MAAM,GAAG,KAAK,CAAC,MAA2B,CAAA;QAChD,IAAI,CAAC,gBAAgB,GAAG,MAAM,CAAC,KAAK,CAAA;QACpC,IAAI,CAAC,iBAAiB,EAAE,CAAA;IAC1B,CAAC;IAEO,iBAAiB;QACvB,IAAI,CAAC,IAAI,CAAC,gBAAgB,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC;YAC7E,IAAI,CAAC,SAAS,GAAG,EAAE,CAAA;YACnB,IAAI,CAAC,WAAW,GAAG,EAAE,CAAA;YACrB,IAAI,CAAC,IAAI,GAAG,EAAE,CAAA;YACd,IAAI,CAAC,SAAS,GAAG,CAAC,CAAA;YAClB,IAAI,CAAC,YAAY,GAAG,CAAC,CAAA;YACrB,OAAM;QACR,CAAC;QAED,MAAM,YAAY,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,gBAAgB,CAAE,CAAA;QACnE,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAA;QAC5C,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAA;QAEjC,IAAI,CAAC,iBAAiB,CAAC,YAAY,CAAC,CAAA;QACpC,IAAI,CAAC,mBAAmB,CAAC,YAAY,CAAC,CAAA;QAEtC,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,GAAG,CAAC,CAAA;QACpD,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAA;IACnF,CAAC;IAEO,iBAAiB,CAAC,YAAmB;QAC3C,MAAM,MAAM,GAAU,EAAE,CAAA;QAExB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE;YAC1B,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC,CAAA;YAC1D,IAAI,IAAI,EAAE,CAAC;gBACT,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;oBAAE,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAA;gBACxF,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;oBAAE,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAA;gBACzF,IAAI,IAAI,CAAC,SAAS,GAAG,CAAC;oBAAE,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAA;YAClG,CAAC;QACH,CAAC,CAAC,CAAA;QAEF,IAAI,CAAC,SAAS,GAAG,MAAM,CAAA;IACzB,CAAC;IAEO,mBAAmB,CAAC,YAAmB;QAC7C,MAAM,MAAM,GAAU,EAAE,CAAA;QAExB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE;YAC1B,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC,CAAA;YAC1D,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC5B,MAAM,CAAC,IAAI,CAAC;oBACV,KAAK,EAAE,OAAO;oBACd,GAAG,EAAE,IAAI,CAAC,MAAM;oBAChB,EAAE,EAAE,IAAI,CAAC,KAAK;oBACd,MAAM,EAAE,IAAI,CAAC,MAAM;oBACnB,EAAE,EAAE,IAAI,CAAC,KAAK;oBACd,GAAG,EAAE,IAAI,CAAC,MAAM;oBAChB,IAAI,EAAE,IAAI,CAAC,MAAM;oBACjB,KAAK,EAAE,IAAI,CAAC,MAAM;iBACnB,CAAC,CAAA;YACJ,CAAC;QACH,CAAC,CAAC,CAAA;QAEF,IAAI,CAAC,WAAW,GAAG,MAAM,CAAA;IAC3B,CAAC;IAED,MAAM;QACJ,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO,IAAI,CAAA;;;;;;;;;;;;;;;;;;;;;;;;OAwBV,CAAA;QACH,CAAC;QAED,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,OAAO,IAAI,CAAA;;;;;;;;;;;;;qCAaoB,IAAI,CAAC,KAAK;;;;;;qCAMV,IAAI,CAAC,KAAK;;;;;OAKxC,CAAA;QACH,CAAC;QAED,OAAO,IAAI,CAAA;;;;;;mDAMoC,IAAI,CAAC,gBAAgB,YAAY,IAAI,CAAC,gBAAgB;cAC3F,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAA,kBAAkB,QAAQ,KAAK,QAAQ,WAAW,CAAC;;;;;;;;gBAQ9F,IAAI,CAAC,gBAAgB;YACrB,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC;gBACzB,CAAC,CAAC,IAAI,CAAA;8BACM,IAAI,CAAC,SAAS;oCACR,IAAI,CAAC,IAAI;sCACP,IAAI;2CACC;gBACzB,CAAC,CAAC,IAAI,CAAA,iDAAiD;YACzD,CAAC,CAAC,IAAI,CAAA,8CAA8C;;;;;;;gBAOpD,IAAI,CAAC,gBAAgB;YACrB,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC;gBAC3B,CAAC,CAAC,IAAI,CAAA;8BACM,IAAI,CAAC,WAAW;gCACd,IAAI,CAAC,IAAI;sCACH,IAAI;6CACG;gBAC3B,CAAC,CAAC,IAAI,CAAA,iDAAiD;YACzD,CAAC,CAAC,IAAI,CAAA,8CAA8C;;;;;UAK1D,IAAI,CAAC,gBAAgB;YACrB,CAAC,CAAC,IAAI,CAAA;;;+CAG+B,IAAI,CAAC,gBAAgB;;;;+CAIrB,IAAI,CAAC,SAAS;;;;+CAId,IAAI,CAAC,YAAY;;;;aAInD;YACH,CAAC,CAAC,OAAO;;KAEd,CAAA;IACH,CAAC;;AAjYM,0BAAM,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgHlB,AAhHY,CAgHZ;AAE2B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;uDAAwB;AACvB;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;0DAA2B;AAE7C;IAAR,KAAK,EAAE;;oDAAe;AACd;IAAR,KAAK,EAAE;;kDAAW;AACV;IAAR,KAAK,EAAE;;sDAAsB;AACrB;IAAR,KAAK,EAAE;;wDAAwB;AACvB;IAAR,KAAK,EAAE;;iDAAoB;AACnB;IAAR,KAAK,EAAE;;gEAAmC;AAClC;IAAR,KAAK,EAAE;;6DAAsB;AACrB;IAAR,KAAK,EAAE;;sDAAc;AACb;IAAR,KAAK,EAAE;;yDAAiB;AAChB;IAAR,KAAK,EAAE;;0DAAyC;AA/HtC,mBAAmB;IAD/B,aAAa,CAAC,uBAAuB,CAAC;GAC1B,mBAAmB,CAmY/B","sourcesContent":["import { html, css, nothing } from 'lit'\nimport { customElement, state, property } from 'lit/decorators.js'\nimport { LitElement } from 'lit'\nimport { client } from '@operato/graphql'\nimport gql from 'graphql-tag'\nimport '../../components/kpi-radar-chart'\nimport '../../components/kpi-boxplot-chart'\n\n@customElement('kpi-level3-comparison')\nexport class KpiLevel3Comparison extends LitElement {\n static styles = css`\n :host {\n display: block;\n }\n .comparison-container {\n background: #fff;\n border-radius: 16px;\n padding: 24px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);\n border: 1px solid #e0e0e0;\n width: 100%;\n }\n .comparison-title {\n font-size: 1.3rem;\n font-weight: bold;\n margin-bottom: 20px;\n color: #333;\n text-align: center;\n }\n .category-selector {\n display: flex;\n align-items: center;\n gap: 12px;\n margin-bottom: 24px;\n padding: 16px;\n background: #f8f9fa;\n border-radius: 8px;\n border: 1px solid #e9ecef;\n }\n .selector-label {\n font-weight: 600;\n color: #495057;\n min-width: 80px;\n }\n .category-select {\n padding: 8px 12px;\n border: 1px solid #ced4da;\n border-radius: 6px;\n background: white;\n font-size: 1rem;\n min-width: 200px;\n }\n .category-select:focus {\n outline: none;\n border-color: #667eea;\n box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2);\n }\n .charts-section {\n display: flex;\n gap: 24px;\n margin-bottom: 20px;\n }\n .chart-card {\n flex: 1;\n background: #f8f9fa;\n border-radius: 12px;\n padding: 20px;\n border: 1px solid #e9ecef;\n }\n .chart-title {\n font-size: 1.1rem;\n font-weight: 600;\n margin-bottom: 16px;\n color: #495057;\n text-align: center;\n }\n .chart-container {\n height: 300px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .loading {\n color: #666;\n font-size: 1rem;\n }\n .error {\n color: #d32f2f;\n font-size: 1rem;\n }\n .no-data {\n color: #666;\n font-size: 1rem;\n text-align: center;\n }\n .no-category {\n color: #666;\n font-size: 1rem;\n text-align: center;\n font-style: italic;\n }\n .summary-info {\n display: flex;\n justify-content: space-around;\n padding: 16px;\n background: #f8f9fa;\n border-radius: 8px;\n margin-top: 16px;\n }\n .summary-item {\n text-align: center;\n }\n .summary-value {\n font-size: 1.2rem;\n font-weight: bold;\n color: #333;\n margin-bottom: 4px;\n }\n .summary-label {\n font-size: 0.9rem;\n color: #666;\n }\n `\n\n @property({ type: String }) sectorType: string = ''\n @property({ type: String }) buildingUsage: string = ''\n\n @state() loading = true\n @state() error = ''\n @state() radarData: any[] = []\n @state() boxplotData: any[] = []\n @state() kpis: string[] = []\n @state() availableCategories: string[] = []\n @state() selectedCategory = ''\n @state() totalKpis = 0\n @state() averageScore = 0\n @state() categoryStats = new Map<string, any[]>()\n\n connectedCallback() {\n super.connectedCallback()\n this.fetchKpiComparison()\n }\n\n override updated(changedProperties: Map<string, unknown>) {\n if (changedProperties.has('sectorType') || changedProperties.has('buildingUsage')) {\n this.fetchKpiComparison()\n }\n }\n\n async fetchKpiComparison() {\n this.loading = true\n this.error = ''\n\n try {\n const response = await client.query({\n query: gql`\n query KpiLevel3Stats($sectorType: String, $buildingUsage: String) {\n kpiProjectStats(kpiNamePattern: \"X\", sectorType: $sectorType, buildingUsage: $buildingUsage) {\n kpiName\n categoryName\n minVal\n q1Val\n medVal\n q3Val\n maxVal\n avgVal\n stddevVal\n projectCount\n }\n }\n `,\n variables: {\n sectorType: this.sectorType || null,\n buildingUsage: this.buildingUsage || null\n }\n })\n\n const stats: any[] = response.data.kpiProjectStats || []\n\n if (stats.length === 0) {\n this.error = '데이터가 없습니다.'\n return\n }\n\n // X-level KPIs를 categoryName(Y-level)으로 그룹화\n const categoryMap = new Map<string, any[]>()\n stats.forEach(stat => {\n const catName = stat.categoryName || '기타'\n if (!categoryMap.has(catName)) categoryMap.set(catName, [])\n categoryMap.get(catName)!.push(stat)\n })\n\n this.categoryStats = categoryMap\n this.availableCategories = Array.from(categoryMap.keys())\n\n // 카테고리 변경 시 선택 초기화\n if (!this.selectedCategory || !categoryMap.has(this.selectedCategory)) {\n this.selectedCategory = this.availableCategories[0] ?? ''\n }\n\n if (this.selectedCategory) {\n this.generateKpiCharts()\n }\n } catch (e) {\n console.error('KPI 비교 데이터를 불러오지 못했습니다:', e)\n this.error = 'KPI 비교 데이터를 불러오지 못했습니다.'\n } finally {\n this.loading = false\n }\n }\n\n private onCategoryChange(event: Event) {\n const target = event.target as HTMLSelectElement\n this.selectedCategory = target.value\n this.generateKpiCharts()\n }\n\n private generateKpiCharts() {\n if (!this.selectedCategory || !this.categoryStats.has(this.selectedCategory)) {\n this.radarData = []\n this.boxplotData = []\n this.kpis = []\n this.totalKpis = 0\n this.averageScore = 0\n return\n }\n\n const categoryData = this.categoryStats.get(this.selectedCategory)!\n this.kpis = categoryData.map(s => s.kpiName)\n this.totalKpis = this.kpis.length\n\n this.generateRadarData(categoryData)\n this.generateBoxplotData(categoryData)\n\n const scores = categoryData.map(s => s.avgVal * 100)\n this.averageScore = Math.round(scores.reduce((a, b) => a + b, 0) / scores.length)\n }\n\n private generateRadarData(categoryData: any[]) {\n const result: any[] = []\n\n this.kpis.forEach(kpiName => {\n const stat = categoryData.find(s => s.kpiName === kpiName)\n if (stat) {\n if (stat.avgVal > 0) result.push({ group: '평균', category: kpiName, value: stat.avgVal })\n if (stat.medVal > 0) result.push({ group: '중앙값', category: kpiName, value: stat.medVal })\n if (stat.stddevVal > 0) result.push({ group: '표준편차', category: kpiName, value: stat.stddevVal })\n }\n })\n\n this.radarData = result\n }\n\n private generateBoxplotData(categoryData: any[]) {\n const result: any[] = []\n\n this.kpis.forEach(kpiName => {\n const stat = categoryData.find(s => s.kpiName === kpiName)\n if (stat && stat.avgVal > 0) {\n result.push({\n group: kpiName,\n min: stat.minVal,\n q1: stat.q1Val,\n median: stat.medVal,\n q3: stat.q3Val,\n max: stat.maxVal,\n mean: stat.avgVal,\n value: stat.avgVal\n })\n }\n })\n\n this.boxplotData = result\n }\n\n render() {\n if (this.loading) {\n return html`\n <div class=\"comparison-container\">\n <div class=\"comparison-title\">KPI 상세 비교 분석</div>\n <div class=\"category-selector\">\n <div class=\"selector-label\">카테고리:</div>\n <select class=\"category-select\" disabled>\n <option>로딩 중...</option>\n </select>\n </div>\n <div class=\"charts-section\">\n <div class=\"chart-card\">\n <div class=\"chart-title\">레이더 차트</div>\n <div class=\"chart-container\">\n <div class=\"loading\">데이터 로딩 중...</div>\n </div>\n </div>\n <div class=\"chart-card\">\n <div class=\"chart-title\">박스플롯</div>\n <div class=\"chart-container\">\n <div class=\"loading\">데이터 로딩 중...</div>\n </div>\n </div>\n </div>\n </div>\n `\n }\n\n if (this.error) {\n return html`\n <div class=\"comparison-container\">\n <div class=\"comparison-title\">KPI 상세 비교 분석</div>\n <div class=\"category-selector\">\n <div class=\"selector-label\">카테고리:</div>\n <select class=\"category-select\" disabled>\n <option>오류 발생</option>\n </select>\n </div>\n <div class=\"charts-section\">\n <div class=\"chart-card\">\n <div class=\"chart-title\">레이더 차트</div>\n <div class=\"chart-container\">\n <div class=\"error\">${this.error}</div>\n </div>\n </div>\n <div class=\"chart-card\">\n <div class=\"chart-title\">박스플롯</div>\n <div class=\"chart-container\">\n <div class=\"error\">${this.error}</div>\n </div>\n </div>\n </div>\n </div>\n `\n }\n\n return html`\n <div class=\"comparison-container\">\n <div class=\"comparison-title\">KPI 상세 비교 분석</div>\n\n <div class=\"category-selector\">\n <div class=\"selector-label\">카테고리:</div>\n <select class=\"category-select\" .value=${this.selectedCategory} @change=${this.onCategoryChange}>\n ${this.availableCategories.map(category => html`<option value=\"${category}\">${category}</option>`)}\n </select>\n </div>\n\n <div class=\"charts-section\">\n <div class=\"chart-card\">\n <div class=\"chart-title\">레이더 차트</div>\n <div class=\"chart-container\">\n ${this.selectedCategory\n ? this.radarData.length > 0\n ? html`<sv-kpi-radar-chart\n .data=${this.radarData}\n .categories=${this.kpis}\n .currentGroup=${'평균'}\n ></sv-kpi-radar-chart>`\n : html`<div class=\"no-data\">선택된 카테고리에 데이터가 없습니다.</div>`\n : html`<div class=\"no-category\">카테고리를 선택해주세요.</div>`}\n </div>\n </div>\n\n <div class=\"chart-card\">\n <div class=\"chart-title\">박스플롯</div>\n <div class=\"chart-container\">\n ${this.selectedCategory\n ? this.boxplotData.length > 0\n ? html`<sv-kpi-boxplot-chart\n .data=${this.boxplotData}\n .groups=${this.kpis}\n .currentGroup=${'평균'}\n ></sv-kpi-boxplot-chart>`\n : html`<div class=\"no-data\">선택된 카테고리에 데이터가 없습니다.</div>`\n : html`<div class=\"no-category\">카테고리를 선택해주세요.</div>`}\n </div>\n </div>\n </div>\n\n ${this.selectedCategory\n ? html`\n <div class=\"summary-info\">\n <div class=\"summary-item\">\n <div class=\"summary-value\">${this.selectedCategory}</div>\n <div class=\"summary-label\">선택된 카테고리</div>\n </div>\n <div class=\"summary-item\">\n <div class=\"summary-value\">${this.totalKpis}</div>\n <div class=\"summary-label\">KPI</div>\n </div>\n <div class=\"summary-item\">\n <div class=\"summary-value\">${this.averageScore}</div>\n <div class=\"summary-label\">평균 점수</div>\n </div>\n </div>\n `\n : nothing}\n </div>\n `\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"kpi-level3-comparison.js","sourceRoot":"","sources":["../../../../client/pages/kpi-dashboard/cards/kpi-level3-comparison.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, css, nothing } from 'lit'\nimport { customElement, state, property } from 'lit/decorators.js'\nimport { LitElement } from 'lit'\nimport { client } from '@operato/graphql'\nimport gql from 'graphql-tag'\nimport '../../components/kpi-radar-chart'\nimport '../../components/kpi-boxplot-chart'\n\n@customElement('kpi-level3-comparison')\nexport class KpiLevel3Comparison extends LitElement {\n static styles = css`\n :host {\n display: block;\n }\n .comparison-container {\n background: #fff;\n border-radius: 16px;\n padding: 24px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);\n border: 1px solid #e0e0e0;\n width: 100%;\n }\n .comparison-title {\n font-size: 1.3rem;\n font-weight: bold;\n margin-bottom: 20px;\n color: #333;\n text-align: center;\n }\n .category-selector {\n display: flex;\n align-items: center;\n gap: 12px;\n margin-bottom: 24px;\n padding: 16px;\n background: #f8f9fa;\n border-radius: 8px;\n border: 1px solid #e9ecef;\n }\n .selector-label {\n font-weight: 600;\n color: #495057;\n min-width: 80px;\n }\n .category-select {\n padding: 8px 12px;\n border: 1px solid #ced4da;\n border-radius: 6px;\n background: white;\n font-size: 1rem;\n min-width: 200px;\n }\n .category-select:focus {\n outline: none;\n border-color: #667eea;\n box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2);\n }\n .charts-section {\n display: flex;\n gap: 24px;\n margin-bottom: 20px;\n }\n .chart-card {\n flex: 1;\n background: #f8f9fa;\n border-radius: 12px;\n padding: 20px;\n border: 1px solid #e9ecef;\n }\n .chart-title {\n font-size: 1.1rem;\n font-weight: 600;\n margin-bottom: 16px;\n color: #495057;\n text-align: center;\n }\n .chart-container {\n height: 300px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .loading {\n color: #666;\n font-size: 1rem;\n }\n .error {\n color: #d32f2f;\n font-size: 1rem;\n }\n .no-data {\n color: #666;\n font-size: 1rem;\n text-align: center;\n }\n .no-category {\n color: #666;\n font-size: 1rem;\n text-align: center;\n font-style: italic;\n }\n .summary-info {\n display: flex;\n justify-content: space-around;\n padding: 16px;\n background: #f8f9fa;\n border-radius: 8px;\n margin-top: 16px;\n }\n .summary-item {\n text-align: center;\n }\n .summary-value {\n font-size: 1.2rem;\n font-weight: bold;\n color: #333;\n margin-bottom: 4px;\n }\n .summary-label {\n font-size: 0.9rem;\n color: #666;\n }\n `\n\n @property({ type: String }) sectorType: string = ''\n @property({ type: String }) buildingUsage: string = ''\n @property({ type: String }) projectScale: string = ''\n @property({ type: String }) facilityType: string = ''\n\n @state() loading = true\n @state() error = ''\n @state() radarData: any[] = []\n @state() boxplotData: any[] = []\n @state() kpis: string[] = []\n @state() availableCategories: string[] = []\n @state() selectedCategory = ''\n @state() totalKpis = 0\n @state() averageScore = 0\n @state() categoryStats = new Map<string, any[]>()\n\n connectedCallback() {\n super.connectedCallback()\n this.fetchKpiComparison()\n }\n\n override updated(changedProperties: Map<string, unknown>) {\n if (changedProperties.has('sectorType') ||\n changedProperties.has('buildingUsage') ||\n changedProperties.has('projectScale') ||\n changedProperties.has('facilityType')) {\n this.fetchKpiComparison()\n }\n }\n\n async fetchKpiComparison() {\n this.loading = true\n this.error = ''\n\n try {\n const response = await client.query({\n query: gql`\n query KpiLevel3Stats($sectorType: String, $buildingUsage: String, $projectScale: String, $facilityType: String) {\n kpiProjectStats(kpiNamePattern: \"X\", sectorType: $sectorType, buildingUsage: $buildingUsage, projectScale: $projectScale, facilityType: $facilityType) {\n kpiName\n categoryName\n minVal\n q1Val\n medVal\n q3Val\n maxVal\n avgVal\n stddevVal\n projectCount\n }\n }\n `,\n variables: {\n sectorType: this.sectorType || null,\n buildingUsage: this.buildingUsage || null,\n projectScale: this.projectScale || null,\n facilityType: this.facilityType || null\n }\n })\n\n const stats: any[] = response.data.kpiProjectStats || []\n\n if (stats.length === 0) {\n this.error = '데이터가 없습니다.'\n return\n }\n\n // X-level KPIs를 categoryName(Y-level)으로 그룹화\n const categoryMap = new Map<string, any[]>()\n stats.forEach(stat => {\n const catName = stat.categoryName || '기타'\n if (!categoryMap.has(catName)) categoryMap.set(catName, [])\n categoryMap.get(catName)!.push(stat)\n })\n\n this.categoryStats = categoryMap\n this.availableCategories = Array.from(categoryMap.keys())\n\n // 카테고리 변경 시 선택 초기화\n if (!this.selectedCategory || !categoryMap.has(this.selectedCategory)) {\n this.selectedCategory = this.availableCategories[0] ?? ''\n }\n\n if (this.selectedCategory) {\n this.generateKpiCharts()\n }\n } catch (e) {\n console.error('KPI 비교 데이터를 불러오지 못했습니다:', e)\n this.error = 'KPI 비교 데이터를 불러오지 못했습니다.'\n } finally {\n this.loading = false\n }\n }\n\n private onCategoryChange(event: Event) {\n const target = event.target as HTMLSelectElement\n this.selectedCategory = target.value\n this.generateKpiCharts()\n }\n\n private generateKpiCharts() {\n if (!this.selectedCategory || !this.categoryStats.has(this.selectedCategory)) {\n this.radarData = []\n this.boxplotData = []\n this.kpis = []\n this.totalKpis = 0\n this.averageScore = 0\n return\n }\n\n const categoryData = this.categoryStats.get(this.selectedCategory)!\n this.kpis = categoryData.map(s => s.kpiName)\n this.totalKpis = this.kpis.length\n\n this.generateRadarData(categoryData)\n this.generateBoxplotData(categoryData)\n\n const scores = categoryData.map(s => s.avgVal * 100)\n this.averageScore = Math.round(scores.reduce((a, b) => a + b, 0) / scores.length)\n }\n\n private generateRadarData(categoryData: any[]) {\n const result: any[] = []\n\n this.kpis.forEach(kpiName => {\n const stat = categoryData.find(s => s.kpiName === kpiName)\n if (stat) {\n if (stat.avgVal > 0) result.push({ group: '평균', category: kpiName, value: stat.avgVal })\n if (stat.medVal > 0) result.push({ group: '중앙값', category: kpiName, value: stat.medVal })\n if (stat.stddevVal > 0) result.push({ group: '표준편차', category: kpiName, value: stat.stddevVal })\n }\n })\n\n this.radarData = result\n }\n\n private generateBoxplotData(categoryData: any[]) {\n const result: any[] = []\n\n this.kpis.forEach(kpiName => {\n const stat = categoryData.find(s => s.kpiName === kpiName)\n if (stat && stat.avgVal > 0) {\n result.push({\n group: kpiName,\n min: stat.minVal,\n q1: stat.q1Val,\n median: stat.medVal,\n q3: stat.q3Val,\n max: stat.maxVal,\n mean: stat.avgVal,\n value: stat.avgVal\n })\n }\n })\n\n this.boxplotData = result\n }\n\n render() {\n if (this.loading) {\n return html`\n <div class=\"comparison-container\">\n <div class=\"comparison-title\">KPI 상세 비교 분석</div>\n <div class=\"category-selector\">\n <div class=\"selector-label\">카테고리:</div>\n <select class=\"category-select\" disabled>\n <option>로딩 중...</option>\n </select>\n </div>\n <div class=\"charts-section\">\n <div class=\"chart-card\">\n <div class=\"chart-title\">레이더 차트</div>\n <div class=\"chart-container\">\n <div class=\"loading\">데이터 로딩 중...</div>\n </div>\n </div>\n <div class=\"chart-card\">\n <div class=\"chart-title\">박스플롯</div>\n <div class=\"chart-container\">\n <div class=\"loading\">데이터 로딩 중...</div>\n </div>\n </div>\n </div>\n </div>\n `\n }\n\n if (this.error) {\n return html`\n <div class=\"comparison-container\">\n <div class=\"comparison-title\">KPI 상세 비교 분석</div>\n <div class=\"category-selector\">\n <div class=\"selector-label\">카테고리:</div>\n <select class=\"category-select\" disabled>\n <option>오류 발생</option>\n </select>\n </div>\n <div class=\"charts-section\">\n <div class=\"chart-card\">\n <div class=\"chart-title\">레이더 차트</div>\n <div class=\"chart-container\">\n <div class=\"error\">${this.error}</div>\n </div>\n </div>\n <div class=\"chart-card\">\n <div class=\"chart-title\">박스플롯</div>\n <div class=\"chart-container\">\n <div class=\"error\">${this.error}</div>\n </div>\n </div>\n </div>\n </div>\n `\n }\n\n return html`\n <div class=\"comparison-container\">\n <div class=\"comparison-title\">KPI 상세 비교 분석</div>\n\n <div class=\"category-selector\">\n <div class=\"selector-label\">카테고리:</div>\n <select class=\"category-select\" .value=${this.selectedCategory} @change=${this.onCategoryChange}>\n ${this.availableCategories.map(category => html`<option value=\"${category}\">${category}</option>`)}\n </select>\n </div>\n\n <div class=\"charts-section\">\n <div class=\"chart-card\">\n <div class=\"chart-title\">레이더 차트</div>\n <div class=\"chart-container\">\n ${this.selectedCategory\n ? this.radarData.length > 0\n ? html`<sv-kpi-radar-chart\n .data=${this.radarData}\n .categories=${this.kpis}\n .currentGroup=${'평균'}\n ></sv-kpi-radar-chart>`\n : html`<div class=\"no-data\">선택된 카테고리에 데이터가 없습니다.</div>`\n : html`<div class=\"no-category\">카테고리를 선택해주세요.</div>`}\n </div>\n </div>\n\n <div class=\"chart-card\">\n <div class=\"chart-title\">박스플롯</div>\n <div class=\"chart-container\">\n ${this.selectedCategory\n ? this.boxplotData.length > 0\n ? html`<sv-kpi-boxplot-chart\n .data=${this.boxplotData}\n .groups=${this.kpis}\n .currentGroup=${'평균'}\n ></sv-kpi-boxplot-chart>`\n : html`<div class=\"no-data\">선택된 카테고리에 데이터가 없습니다.</div>`\n : html`<div class=\"no-category\">카테고리를 선택해주세요.</div>`}\n </div>\n </div>\n </div>\n\n ${this.selectedCategory\n ? html`\n <div class=\"summary-info\">\n <div class=\"summary-item\">\n <div class=\"summary-value\">${this.selectedCategory}</div>\n <div class=\"summary-label\">선택된 카테고리</div>\n </div>\n <div class=\"summary-item\">\n <div class=\"summary-value\">${this.totalKpis}</div>\n <div class=\"summary-label\">KPI</div>\n </div>\n <div class=\"summary-item\">\n <div class=\"summary-value\">${this.averageScore}</div>\n <div class=\"summary-label\">평균 점수</div>\n </div>\n </div>\n `\n : nothing}\n </div>\n `\n }\n}\n"]}
|
|
@@ -7,6 +7,7 @@ import { client } from '@operato/graphql';
|
|
|
7
7
|
import '../../../components/kpi-radar-chart.js';
|
|
8
8
|
import '../../../components/kpi-boxplot-chart.js';
|
|
9
9
|
import '../../../components/kpi-mini-trend-chart.js';
|
|
10
|
+
import { PROJECT_SCALE_OPTIONS, FACILITY_TYPE_OPTIONS } from '../../../shared/project-axis';
|
|
10
11
|
let KpiLeftPanel = class KpiLeftPanel extends LitElement {
|
|
11
12
|
constructor() {
|
|
12
13
|
super(...arguments);
|
|
@@ -17,6 +18,8 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
|
|
|
17
18
|
this.endYearMonth = ''; // YYYY-MM 형식
|
|
18
19
|
this.sectorType = '';
|
|
19
20
|
this.buildingUsage = '';
|
|
21
|
+
this.projectScale = '';
|
|
22
|
+
this.facilityType = '';
|
|
20
23
|
this.isAllPeriod = false;
|
|
21
24
|
this.chartData = [];
|
|
22
25
|
this.chartCategories = [];
|
|
@@ -66,7 +69,9 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
|
|
|
66
69
|
changedProperties.has('startYearMonth') ||
|
|
67
70
|
changedProperties.has('endYearMonth') ||
|
|
68
71
|
changedProperties.has('sectorType') ||
|
|
69
|
-
changedProperties.has('buildingUsage')
|
|
72
|
+
changedProperties.has('buildingUsage') ||
|
|
73
|
+
changedProperties.has('projectScale') ||
|
|
74
|
+
changedProperties.has('facilityType')) {
|
|
70
75
|
// 기간 정보가 설정되어 있을 때만 조회 (빈 문자열이면 전체 기간)
|
|
71
76
|
if (this.startYearMonth !== undefined && this.endYearMonth !== undefined) {
|
|
72
77
|
this.fetchKpiYComprehensiveStats();
|
|
@@ -84,12 +89,16 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
|
|
|
84
89
|
$endYearMonth: String
|
|
85
90
|
$sectorType: String
|
|
86
91
|
$buildingUsage: String
|
|
92
|
+
$projectScale: String
|
|
93
|
+
$facilityType: String
|
|
87
94
|
) {
|
|
88
95
|
totalKpiYValueComprehensiveStats(
|
|
89
96
|
startYearMonth: $startYearMonth
|
|
90
97
|
endYearMonth: $endYearMonth
|
|
91
98
|
sectorType: $sectorType
|
|
92
99
|
buildingUsage: $buildingUsage
|
|
100
|
+
projectScale: $projectScale
|
|
101
|
+
facilityType: $facilityType
|
|
93
102
|
) {
|
|
94
103
|
kpiName
|
|
95
104
|
minVal
|
|
@@ -105,7 +114,9 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
|
|
|
105
114
|
startYearMonth: this.startYearMonth,
|
|
106
115
|
endYearMonth: this.endYearMonth,
|
|
107
116
|
sectorType: this.sectorType || null,
|
|
108
|
-
buildingUsage: this.buildingUsage || null
|
|
117
|
+
buildingUsage: this.buildingUsage || null,
|
|
118
|
+
projectScale: this.projectScale || null,
|
|
119
|
+
facilityType: this.facilityType || null
|
|
109
120
|
}
|
|
110
121
|
});
|
|
111
122
|
this.kpiYComprehensiveStats = response.data.totalKpiYValueComprehensiveStats || [];
|
|
@@ -144,6 +155,8 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
|
|
|
144
155
|
$endYearMonth: String
|
|
145
156
|
$sectorType: String
|
|
146
157
|
$buildingUsage: String
|
|
158
|
+
$projectScale: String
|
|
159
|
+
$facilityType: String
|
|
147
160
|
) {
|
|
148
161
|
kpiZValueComprehensiveStatsByGeoGroup(
|
|
149
162
|
kpiName: $kpiName
|
|
@@ -151,6 +164,8 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
|
|
|
151
164
|
endYearMonth: $endYearMonth
|
|
152
165
|
sectorType: $sectorType
|
|
153
166
|
buildingUsage: $buildingUsage
|
|
167
|
+
projectScale: $projectScale
|
|
168
|
+
facilityType: $facilityType
|
|
154
169
|
) {
|
|
155
170
|
geoGroup
|
|
156
171
|
avgVal
|
|
@@ -165,7 +180,9 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
|
|
|
165
180
|
startYearMonth: this.startYearMonth,
|
|
166
181
|
endYearMonth: this.endYearMonth,
|
|
167
182
|
sectorType: this.sectorType || null,
|
|
168
|
-
buildingUsage: this.buildingUsage || null
|
|
183
|
+
buildingUsage: this.buildingUsage || null,
|
|
184
|
+
projectScale: this.projectScale || null,
|
|
185
|
+
facilityType: this.facilityType || null
|
|
169
186
|
}
|
|
170
187
|
});
|
|
171
188
|
this.regionalKpiStats = response.data.kpiZValueComprehensiveStatsByGeoGroup || [];
|
|
@@ -202,6 +219,8 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
|
|
|
202
219
|
$endYearMonth: String
|
|
203
220
|
$sectorType: String
|
|
204
221
|
$buildingUsage: String
|
|
222
|
+
$projectScale: String
|
|
223
|
+
$facilityType: String
|
|
205
224
|
) {
|
|
206
225
|
kpiZValueMonthlyTrendByGeoGroup(
|
|
207
226
|
kpiName: $kpiName
|
|
@@ -209,6 +228,8 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
|
|
|
209
228
|
endYearMonth: $endYearMonth
|
|
210
229
|
sectorType: $sectorType
|
|
211
230
|
buildingUsage: $buildingUsage
|
|
231
|
+
projectScale: $projectScale
|
|
232
|
+
facilityType: $facilityType
|
|
212
233
|
) {
|
|
213
234
|
geoGroup
|
|
214
235
|
yearMonth
|
|
@@ -222,7 +243,9 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
|
|
|
222
243
|
startYearMonth: this.startYearMonth,
|
|
223
244
|
endYearMonth: this.endYearMonth,
|
|
224
245
|
sectorType: this.sectorType || null,
|
|
225
|
-
buildingUsage: this.buildingUsage || null
|
|
246
|
+
buildingUsage: this.buildingUsage || null,
|
|
247
|
+
projectScale: this.projectScale || null,
|
|
248
|
+
facilityType: this.facilityType || null
|
|
226
249
|
}
|
|
227
250
|
});
|
|
228
251
|
this.monthlyTrendData = response.data.kpiZValueMonthlyTrendByGeoGroup || [];
|
|
@@ -559,6 +582,45 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
|
|
|
559
582
|
</select>
|
|
560
583
|
</div>
|
|
561
584
|
|
|
585
|
+
<!--
|
|
586
|
+
규모·시설물유형은 줄을 따로 쓴다. 패널 폭이 400px 인데 다섯 쌍을 한 줄에 넣으면
|
|
587
|
+
select 하나가 70px 이 채 안 되고, "공공·교육·문화시설" 같은 항목이 잘린다.
|
|
588
|
+
-->
|
|
589
|
+
<div class="filter-row">
|
|
590
|
+
<span class="filter-label">규모</span>
|
|
591
|
+
<select
|
|
592
|
+
class="filter-select"
|
|
593
|
+
.value=${this.projectScale}
|
|
594
|
+
@change=${(e) => {
|
|
595
|
+
this.projectScale = e.target.value;
|
|
596
|
+
// 부모로 전파 → selectedProjectScale 동기화 → 지역 팝업도 같은 규모 필터 사용
|
|
597
|
+
this.dispatchEvent(new CustomEvent('scale-change', {
|
|
598
|
+
detail: { projectScale: this.projectScale },
|
|
599
|
+
bubbles: true,
|
|
600
|
+
composed: true
|
|
601
|
+
}));
|
|
602
|
+
}}
|
|
603
|
+
>
|
|
604
|
+
${PROJECT_SCALE_OPTIONS.map(option => html `<option value=${option.value}>${option.label}</option>`)}
|
|
605
|
+
</select>
|
|
606
|
+
<span class="filter-label">시설물</span>
|
|
607
|
+
<select
|
|
608
|
+
class="filter-select wide"
|
|
609
|
+
.value=${this.facilityType}
|
|
610
|
+
@change=${(e) => {
|
|
611
|
+
this.facilityType = e.target.value;
|
|
612
|
+
// 부모로 전파 → selectedFacilityType 동기화 → 지역 팝업도 같은 시설물 필터 사용
|
|
613
|
+
this.dispatchEvent(new CustomEvent('facility-change', {
|
|
614
|
+
detail: { facilityType: this.facilityType },
|
|
615
|
+
bubbles: true,
|
|
616
|
+
composed: true
|
|
617
|
+
}));
|
|
618
|
+
}}
|
|
619
|
+
>
|
|
620
|
+
${FACILITY_TYPE_OPTIONS.map(option => html `<option value=${option.value}>${option.label}</option>`)}
|
|
621
|
+
</select>
|
|
622
|
+
</div>
|
|
623
|
+
|
|
562
624
|
<!-- 기간 필터 -->
|
|
563
625
|
<div class="filter-row">
|
|
564
626
|
<span class="filter-label">기간</span>
|
|
@@ -884,6 +946,10 @@ KpiLeftPanel.styles = [
|
|
|
884
946
|
outline: none;
|
|
885
947
|
border-color: #667eea;
|
|
886
948
|
}
|
|
949
|
+
/* 시설물유형은 "공공·교육·문화시설" 처럼 항목 이름이 길어 같은 폭으로는 잘린다. */
|
|
950
|
+
.filter-select.wide {
|
|
951
|
+
flex: 1.7;
|
|
952
|
+
}
|
|
887
953
|
`
|
|
888
954
|
];
|
|
889
955
|
__decorate([
|
|
@@ -914,6 +980,14 @@ __decorate([
|
|
|
914
980
|
property({ type: String }),
|
|
915
981
|
__metadata("design:type", Object)
|
|
916
982
|
], KpiLeftPanel.prototype, "buildingUsage", void 0);
|
|
983
|
+
__decorate([
|
|
984
|
+
property({ type: String }),
|
|
985
|
+
__metadata("design:type", Object)
|
|
986
|
+
], KpiLeftPanel.prototype, "projectScale", void 0);
|
|
987
|
+
__decorate([
|
|
988
|
+
property({ type: String }),
|
|
989
|
+
__metadata("design:type", Object)
|
|
990
|
+
], KpiLeftPanel.prototype, "facilityType", void 0);
|
|
917
991
|
__decorate([
|
|
918
992
|
state(),
|
|
919
993
|
__metadata("design:type", Object)
|