@dssp/dkpi 1.0.0-alpha.55 → 1.0.0-alpha.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/assets/images/project-image.png +0 -0
  2. package/dist-client/components/kpi-boxplot-chart.js +48 -76
  3. package/dist-client/components/kpi-boxplot-chart.js.map +1 -1
  4. package/dist-client/components/kpi-radar-chart.d.ts +1 -0
  5. package/dist-client/components/kpi-radar-chart.js +85 -7
  6. package/dist-client/components/kpi-radar-chart.js.map +1 -1
  7. package/dist-client/components/kpi-single-boxplot-chart.js +24 -6
  8. package/dist-client/components/kpi-single-boxplot-chart.js.map +1 -1
  9. package/dist-client/pages/sv-project-complete.js +3 -1
  10. package/dist-client/pages/sv-project-complete.js.map +1 -1
  11. package/dist-client/pages/sv-project-completed-list.d.ts +2 -2
  12. package/dist-client/pages/sv-project-completed-list.js +14 -14
  13. package/dist-client/pages/sv-project-completed-list.js.map +1 -1
  14. package/dist-client/pages/sv-project-detail.d.ts +8 -0
  15. package/dist-client/pages/sv-project-detail.js +203 -58
  16. package/dist-client/pages/sv-project-detail.js.map +1 -1
  17. package/dist-client/pages/sv-project-list.d.ts +7 -2
  18. package/dist-client/pages/sv-project-list.js +13 -13
  19. package/dist-client/pages/sv-project-list.js.map +1 -1
  20. package/dist-client/tsconfig.tsbuildinfo +1 -1
  21. package/dist-server/service/kpi-stat/kpi-stat-query.d.ts +5 -6
  22. package/dist-server/service/kpi-stat/kpi-stat-query.js +108 -100
  23. package/dist-server/service/kpi-stat/kpi-stat-query.js.map +1 -1
  24. package/dist-server/service/kpi-stat/kpi-stat-types.d.ts +5 -12
  25. package/dist-server/service/kpi-stat/kpi-stat-types.js +72 -42
  26. package/dist-server/service/kpi-stat/kpi-stat-types.js.map +1 -1
  27. package/dist-server/tsconfig.tsbuildinfo +1 -1
  28. package/package.json +3 -3
  29. package/schema.graphql +41 -0
@@ -13,10 +13,13 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
13
13
  constructor() {
14
14
  super(...arguments);
15
15
  this.project = {};
16
+ this.kpiComprehensiveStats = [];
17
+ this.kpiYComprehensiveStats = [];
16
18
  }
17
19
  get context() {
20
+ var _a;
18
21
  return {
19
- title: '프로젝트 상세 정보'
22
+ title: this.project ? `프로젝트 상세 정보 - ${(_a = this.project) === null || _a === void 0 ? void 0 : _a.name}` : '프로젝트 상세 정보'
20
23
  };
21
24
  }
22
25
  render() {
@@ -31,9 +34,12 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
31
34
  </div>
32
35
 
33
36
  <div class="top-info" style="margin-top: 10px;">
34
- <img pic src=${((_a = this.project.mainPhoto) === null || _a === void 0 ? void 0 : _a.fullpath) || '/assets/images/no-image.png'} alt="project" />
37
+ <img pic src=${((_a = this.project.mainPhoto) === null || _a === void 0 ? void 0 : _a.fullpath) || '/assets/images/project-image.png'} alt="project" />
35
38
  <div class="info">
36
- <div class="address">• ${(_b = this.project.buildingComplex) === null || _b === void 0 ? void 0 : _b.address}</div>
39
+ <div class="row">
40
+ <div class="label">• 주소</div>
41
+ <div class="value">${(_b = this.project.buildingComplex) === null || _b === void 0 ? void 0 : _b.address}</div>
42
+ </div>
37
43
  <div class="row">
38
44
  <div class="label">• 공사기간</div>
39
45
  <div class="value">${this.project.startDate} ~ ${this.project.endDate}</div>
@@ -44,11 +50,11 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
44
50
  </div>
45
51
  <div class="row">
46
52
  <div class="label">• 요청상세</div>
47
- <div class="value bold">???</div>
53
+ <div class="value bold">000</div>
48
54
  </div>
49
55
  <div class="row">
50
56
  <div class="label">• 알람</div>
51
- <div class="value">???</div>
57
+ <div class="value">000</div>
52
58
  </div>
53
59
  </div>
54
60
  </div>
@@ -64,11 +70,11 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
64
70
  </div>
65
71
  <div class="grid-item row">
66
72
  <div class="label">• 용적률</div>
67
- <div class="value">??? %</div>
73
+ <div class="value">000 %</div>
68
74
  </div>
69
75
  <div class="grid-item row">
70
76
  <div class="label">• 투입인력</div>
71
- <div class="value">??? 명</div>
77
+ <div class="value">000 명</div>
72
78
  </div>
73
79
  </div>
74
80
  </div>
@@ -135,17 +141,7 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
135
141
  <div class="kpi-summary">
136
142
  <div class="mini-boxplot" title="boxplot placeholder">
137
143
  <div class="boxplot-area">
138
- <kpi-single-boxplot-chart
139
- .data=${{
140
- min: 0,
141
- max: 100,
142
- mean: 50,
143
- median: 60,
144
- q1: 25,
145
- q3: 75,
146
- value: this.project.kpi
147
- }}
148
- ></kpi-single-boxplot-chart>
144
+ <kpi-single-boxplot-chart .data=${this.getBoxPlotDataForProject(this.project)}></kpi-single-boxplot-chart>
149
145
  </div>
150
146
 
151
147
  <div class="boxplot-point">
@@ -156,40 +152,9 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
156
152
 
157
153
  <div class="spider-area">
158
154
  <kpi-radar-chart
159
- .data=${[
160
- {
161
- group: '평균',
162
- category: '생산성 성과',
163
- value: 41
164
- },
165
- {
166
- group: '평균',
167
- category: '일정 성과',
168
- value: 26
169
- },
170
- {
171
- group: '평균',
172
- category: '비용 성과',
173
- value: 30
174
- },
175
- {
176
- group: '평균',
177
- category: '품질 성과',
178
- value: 21
179
- },
180
- {
181
- group: '평균',
182
- category: '안전 성과',
183
- value: 33
184
- },
185
- {
186
- group: '평균',
187
- category: '환경 성과',
188
- value: 15
189
- }
190
- ]}
191
- .categories=${['생산성 성과', '일정 성과', '비용 성과', '품질 성과', '안전 성과', '환경 성과']}
192
- .currentGroup=${'평균'}
155
+ .data=${this.getRadarChartData().data}
156
+ .categories=${this.getRadarChartData().categories}
157
+ .currentGroup=${'프로젝트성과'}
193
158
  ></kpi-radar-chart>
194
159
  </div>
195
160
  </div>
@@ -197,15 +162,19 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
197
162
 
198
163
  <div class="card group-box">
199
164
  <div class="kpi-header">
200
- <div>그룹별 분포</div>
201
- <div class="triangle"></div>
165
+ <div>
166
+ <div>그룹별 분포</div>
167
+ <div class="triangle"></div>
168
+ </div>
202
169
  </div>
203
170
  <div class="desc">
204
171
  Boxplot(박스플롯)은 각 카테고리별로 값의 분포(최소, 1사분위, 중앙값, 3사분위, 최대, 평균, 이상치 등)를 보여줍니다.
205
172
  박스는 중앙 50% 구간, 수염은 전체 범위, 굵은 검정색 가로선은 중앙값(메디안), 초록색 원은 평균값(Mean)을 의미합니다.
206
173
  이 프로젝트의 값은 진한 오렌지색 원으로 별도 강조되어 표시되며, 중앙값/평균과 다를 수 있습니다.
207
174
  </div>
208
- <div class="group-chart"></div>
175
+ <div class="group-chart">
176
+ <kpi-boxplot-chart .data=${this.getYKpiBoxplotData()}></kpi-boxplot-chart>
177
+ </div>
209
178
  </div>
210
179
  </div>
211
180
  </div>
@@ -213,7 +182,9 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
213
182
  }
214
183
  async pageUpdated(changes, lifecycle) {
215
184
  if (this.active) {
216
- await this.initProject(lifecycle.resourceId);
185
+ this.initProject(lifecycle.resourceId);
186
+ this.getKpiComprehensiveStats();
187
+ this.getKpiYComprehensiveStats();
217
188
  }
218
189
  }
219
190
  async initProject(projectId = '') {
@@ -234,6 +205,10 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
234
205
  totalProgress
235
206
  weeklyProgress
236
207
  kpi
208
+ kpiValues {
209
+ kpiName
210
+ value
211
+ }
237
212
  inspPassRate
238
213
  robotProgressRate
239
214
  structuralSafetyRate
@@ -274,6 +249,167 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
274
249
  if (response.errors)
275
250
  return;
276
251
  this.project = ((_a = response.data) === null || _a === void 0 ? void 0 : _a.project) || {};
252
+ this.updateContext();
253
+ }
254
+ async getKpiComprehensiveStats() {
255
+ try {
256
+ const response = await client.query({
257
+ query: gql `
258
+ query GetKpiZValueComprehensiveStats {
259
+ totalKpiZValueComprehensiveStats {
260
+ minVal
261
+ q1Val
262
+ medVal
263
+ q3Val
264
+ maxVal
265
+ }
266
+ }
267
+ `
268
+ });
269
+ this.kpiComprehensiveStats = response.data.totalKpiZValueComprehensiveStats || [];
270
+ console.log('KPI Z-Value Comprehensive Stats:', this.kpiComprehensiveStats);
271
+ }
272
+ catch (error) {
273
+ console.error('Failed to fetch KPI comprehensive stats:', error);
274
+ this.kpiComprehensiveStats = [];
275
+ }
276
+ }
277
+ async getKpiYComprehensiveStats() {
278
+ try {
279
+ const response = await client.query({
280
+ query: gql `
281
+ query GetKpiYValueComprehensiveStats {
282
+ totalKpiYValueComprehensiveStats {
283
+ kpiName
284
+ minVal
285
+ q1Val
286
+ medVal
287
+ q3Val
288
+ maxVal
289
+ avgVal
290
+ projectCount
291
+ }
292
+ }
293
+ `
294
+ });
295
+ this.kpiYComprehensiveStats = response.data.totalKpiYValueComprehensiveStats || [];
296
+ console.log('KPI Y-Value Comprehensive Stats:', this.kpiYComprehensiveStats);
297
+ }
298
+ catch (error) {
299
+ console.error('Failed to fetch KPI Y comprehensive stats:', error);
300
+ this.kpiYComprehensiveStats = [];
301
+ }
302
+ }
303
+ getYKpiBoxplotData() {
304
+ // KPI 카테고리 매핑 (Y-category KPI names to display categories)
305
+ const categoryMapping = {
306
+ Y01_productivity: '생산성과',
307
+ Y02_schedule: '일정성과',
308
+ Y03_cost: '비용성과',
309
+ Y04_quality: '품질성과',
310
+ Y05_safety: '안전성과',
311
+ Y06_environment: '환경성과'
312
+ };
313
+ const data = [];
314
+ // Y-KPI 카테고리별로 통합된 통계 생성
315
+ this.kpiYComprehensiveStats.forEach(stat => {
316
+ const category = categoryMapping[stat.kpiName] || stat.kpiName;
317
+ data.push({
318
+ org: category, // org 필드 사용 (boxplot 차트가 기대하는 필드명)
319
+ min: stat.minVal * 20,
320
+ max: stat.maxVal * 20,
321
+ q1: stat.q1Val * 20,
322
+ q3: stat.q3Val * 20,
323
+ median: stat.medVal * 20,
324
+ mean: stat.avgVal * 20,
325
+ value: this.getProjectYKpiValue(stat.kpiName) // 현재 프로젝트의 실제 Y-KPI 값
326
+ });
327
+ });
328
+ return data.sort((a, b) => a.org.localeCompare(b.org));
329
+ }
330
+ getProjectYKpiValue(kpiName) {
331
+ var _a, _b;
332
+ // 현재 프로젝트의 kpiValues 배열에서 해당 KPI 값을 찾습니다
333
+ const kpiValue = (_b = (_a = this.project) === null || _a === void 0 ? void 0 : _a.kpiValues) === null || _b === void 0 ? void 0 : _b.find(kv => kv.kpiName === kpiName);
334
+ return (kpiValue === null || kpiValue === void 0 ? void 0 : kpiValue.value) || 0;
335
+ }
336
+ getRadarChartData() {
337
+ // KPI 카테고리 매핑 (Y-category KPI names to display categories)
338
+ const categoryMapping = {
339
+ Y01_productivity: '생산성',
340
+ Y02_schedule: '일정',
341
+ Y03_cost: '비용',
342
+ Y04_quality: '품질',
343
+ Y05_safety: '안전',
344
+ Y06_environment: '환경'
345
+ };
346
+ const data = [];
347
+ const categories = [];
348
+ // Create baseline data from comprehensive stats (using avgVal as baseline)
349
+ this.kpiYComprehensiveStats.forEach(stat => {
350
+ const category = categoryMapping[stat.kpiName] || stat.kpiName;
351
+ if (!categories.includes(category)) {
352
+ categories.push(category);
353
+ }
354
+ // Add baseline average
355
+ data.push({
356
+ org: '기준평균',
357
+ category,
358
+ value: Math.round(stat.avgVal * 20) // Scale to match expected range
359
+ });
360
+ // Add current project performance (using project's individual values)
361
+ // For now, using avgVal + some variation as placeholder
362
+ data.push({
363
+ org: '프로젝트성과',
364
+ category,
365
+ value: this.getProjectYKpiValue(stat.kpiName)
366
+ });
367
+ });
368
+ return {
369
+ data,
370
+ categories: categories.length > 0
371
+ ? categories
372
+ : ['Y1. 일정성과', 'Y2. 비용성과', 'Y3. 품질성과', 'Y4. 안전성과', 'Y5. 환경성과', 'Y6. 생산성성과']
373
+ };
374
+ }
375
+ getBoxPlotDataForProject(project) {
376
+ var _a;
377
+ // 프로젝트의 지역(geo_group)에 해당하는 통계 찾기
378
+ const stats = (_a = this.kpiComprehensiveStats) === null || _a === void 0 ? void 0 : _a[0];
379
+ if (!stats) {
380
+ // 전체 통계의 평균값 사용 또는 기본값 반환
381
+ const defaultStats = this.kpiComprehensiveStats.length > 0 ? this.kpiComprehensiveStats[0] : null;
382
+ if (defaultStats) {
383
+ return {
384
+ min: defaultStats.minVal,
385
+ max: defaultStats.maxVal,
386
+ mean: (defaultStats.q1Val + defaultStats.q3Val) / 2,
387
+ median: defaultStats.medVal,
388
+ q1: defaultStats.q1Val,
389
+ q3: defaultStats.q3Val,
390
+ value: project.kpi || 0
391
+ };
392
+ }
393
+ // 완전한 기본값
394
+ return {
395
+ min: 0,
396
+ max: 100,
397
+ mean: 50,
398
+ median: 50,
399
+ q1: 25,
400
+ q3: 75,
401
+ value: project.kpi || 0
402
+ };
403
+ }
404
+ return {
405
+ min: stats.minVal * 20,
406
+ max: stats.maxVal * 20,
407
+ mean: ((stats.q1Val + stats.q3Val) / 2) * 20, // 대략적 평균값
408
+ median: stats.medVal * 20,
409
+ q1: stats.q1Val * 20,
410
+ q3: stats.q3Val * 20,
411
+ value: project.kpi || 0
412
+ };
277
413
  }
278
414
  };
279
415
  SvProjectDetailPage.styles = [
@@ -363,7 +499,7 @@ SvProjectDetailPage.styles = [
363
499
  letter-spacing: -0.05em;
364
500
  }
365
501
  .value {
366
- color: #212529;
502
+ color: #333;
367
503
  font-size: 16px;
368
504
  }
369
505
  .status-value {
@@ -483,6 +619,7 @@ SvProjectDetailPage.styles = [
483
619
  align-items: center;
484
620
  padding: 0 30px;
485
621
  max-height: 360px;
622
+ gap: 20px;
486
623
  }
487
624
  .mini-boxplot {
488
625
  flex: 1;
@@ -538,7 +675,7 @@ SvProjectDetailPage.styles = [
538
675
  margin-top: 4px;
539
676
  }
540
677
  .group-chart {
541
- height: 205px;
678
+ height: 300px;
542
679
  margin-top: 25px;
543
680
  }
544
681
 
@@ -551,6 +688,14 @@ __decorate([
551
688
  state(),
552
689
  __metadata("design:type", Object)
553
690
  ], SvProjectDetailPage.prototype, "project", void 0);
691
+ __decorate([
692
+ state(),
693
+ __metadata("design:type", Array)
694
+ ], SvProjectDetailPage.prototype, "kpiComprehensiveStats", void 0);
695
+ __decorate([
696
+ state(),
697
+ __metadata("design:type", Array)
698
+ ], SvProjectDetailPage.prototype, "kpiYComprehensiveStats", void 0);
554
699
  SvProjectDetailPage = __decorate([
555
700
  customElement('sv-project-detail')
556
701
  ], SvProjectDetailPage);
@@ -1 +1 @@
1
- {"version":3,"file":"sv-project-detail.js","sourceRoot":"","sources":["../../client/pages/sv-project-detail.ts"],"names":[],"mappings":"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","sourcesContent":["import { navigate, PageLifecycle, PageView } from '@operato/shell'\nimport { css, html } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\nimport { ScopedElementsMixin } from '@open-wc/scoped-elements'\nimport { Project, PROJECT_STATE, ProjectState } from './sv-project-list'\nimport { client } from '@operato/graphql'\nimport gql from 'graphql-tag'\nimport '../components/kpi-boxplot-chart'\nimport '../components/kpi-single-boxplot-chart'\nimport '../components/kpi-radar-chart'\n\n@customElement('sv-project-detail')\nexport class SvProjectDetailPage extends ScopedElementsMixin(PageView) {\n static styles = [\n css`\n :host {\n display: flex;\n flex-direction: column;\n overflow-y: auto;\n\n width: 100%;\n height: 100%;\n background-color: var(--md-sys-color-background, #f6f6f6);\n }\n\n /* content layout */\n div[content] {\n display: flex;\n gap: 35px;\n padding: 25px;\n }\n div[left] {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 20px;\n }\n div[right] {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 20px;\n padding: 0 1px;\n }\n\n /* card */\n .card {\n background: #ffffff;\n border-radius: 10px;\n padding: 15px;\n box-shadow: 3px 3px 3px 0 rgba(0, 0, 0, 0.1);\n }\n .card-title {\n display: flex;\n align-items: center;\n gap: 5px;\n color: #35618e;\n font-weight: 700;\n }\n .triangle {\n width: 0;\n height: 0;\n border-left: 6px solid transparent;\n border-right: 6px solid transparent;\n border-top: 8px solid #35618e;\n }\n\n /* left top info box */\n .bold {\n font-weight: bold;\n }\n .top-info {\n display: flex;\n gap: 10px;\n }\n .top-info img[pic] {\n width: 284px;\n height: 160px;\n object-fit: fill;\n background: #e5e7eb;\n }\n .top-info .info {\n display: flex;\n flex-direction: column;\n gap: 15px;\n padding: 10px 0;\n flex: 1;\n }\n .row {\n display: flex;\n align-items: center;\n gap: 10px;\n }\n .label {\n min-width: fit-content;\n color: #35618e;\n font-size: 16px;\n letter-spacing: -0.05em;\n }\n .value {\n color: #212529;\n font-size: 16px;\n }\n .status-value {\n color: #4cbb49;\n font-weight: 700;\n }\n\n .address {\n color: #35618e;\n font-size: 16px;\n letter-spacing: -0.05em;\n }\n\n .detail-grid {\n display: grid;\n grid-template-columns: 284px 1fr;\n gap: 15px 10px;\n padding: 5px 0;\n }\n .detail-grid .grid-item .label {\n width: auto;\n }\n\n /* left second card (requests) */\n .sub-desc {\n color: #35618e;\n font-size: 14px;\n letter-spacing: -0.05em;\n margin-bottom: 8px;\n }\n .list-block {\n display: flex;\n flex-direction: column;\n gap: 5px;\n padding: 5px 25px;\n }\n .list-title {\n color: #212529;\n font-weight: 700;\n font-size: 16px;\n letter-spacing: -0.05em;\n }\n .list-text {\n color: #35618e;\n font-size: 16px;\n letter-spacing: -0.05em;\n margin-left: 8px;\n }\n .list-text .warn {\n margin-left: 10px;\n color: #e13232;\n\n md-icon {\n font-size: 17px;\n width: 13px;\n height: 13px;\n }\n }\n\n /* right buttons */\n .button-line {\n display: flex;\n justify-content: flex-end;\n gap: 10px;\n }\n .ghost-btn {\n display: flex;\n align-items: center;\n gap: 3px;\n padding: 5px 10px;\n background: #35618e;\n color: #ffffff;\n border-radius: 5px;\n box-shadow: 2px 2px 2px 0 rgba(0, 0, 0, 0.1);\n cursor: pointer;\n\n /* Material Symbols Outlined (stroke style) */\n md-icon {\n font-variation-settings:\n 'FILL' 0,\n 'wght' 300,\n 'GRAD' 0,\n 'opsz' 24;\n }\n }\n\n /* right KPI box */\n .kpi-box {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 15px;\n max-height: 420px;\n min-width: 560px;\n }\n .kpi-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n color: #35618e;\n font-weight: 700;\n font-size: 20px;\n letter-spacing: -0.05em;\n }\n .kpi-header div {\n display: flex;\n align-items: center;\n gap: 5px;\n }\n .kpi-desc {\n color: #35618e;\n font-size: 13px;\n letter-spacing: -0.05em;\n }\n .kpi-summary {\n display: flex;\n align-items: center;\n padding: 0 30px;\n max-height: 360px;\n }\n .mini-boxplot {\n flex: 1;\n display: flex;\n flex-direction: column;\n height: 100%;\n position: relative;\n min-width: 120px;\n }\n .boxplot-area {\n flex: 1;\n width: 100%;\n display: flex;\n flex-direction: column;\n align-items: center;\n background: #ffffff;\n }\n\n .boxplot-point {\n display: flex;\n flex-direction: column;\n align-items: center;\n color: #35618e;\n }\n .boxplot-point .value {\n font-size: 42px;\n font-weight: 800;\n color: #35618e;\n border-radius: 6px;\n }\n .boxplot-point .label {\n font-size: 14px;\n font-weight: 300;\n width: auto;\n }\n\n /* spider chart placeholder */\n .spider-area {\n flex: 5;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 10px;\n height: 100%;\n }\n\n /* group distribution */\n .group-box .desc {\n color: #35618e;\n font-size: 13px;\n letter-spacing: -0.05em;\n margin-top: 4px;\n }\n .group-chart {\n height: 205px;\n margin-top: 25px;\n }\n\n kpi-single-boxplot-chart {\n flex: 1;\n }\n `\n ]\n\n get context() {\n return {\n title: '프로젝트 상세 정보'\n }\n }\n\n @state() private project: Project = {} as Project\n\n render() {\n return html`\n <div content>\n <div left>\n <div class=\"card\">\n <div class=\"card-title\">\n <div>${this.project.name}</div>\n <div class=\"triangle\"></div>\n </div>\n\n <div class=\"top-info\" style=\"margin-top: 10px;\">\n <img pic src=${this.project.mainPhoto?.fullpath || '/assets/images/no-image.png'} alt=\"project\" />\n <div class=\"info\">\n <div class=\"address\">• ${this.project.buildingComplex?.address}</div>\n <div class=\"row\">\n <div class=\"label\">• 공사기간</div>\n <div class=\"value\">${this.project.startDate} ~ ${this.project.endDate}</div>\n </div>\n <div class=\"row\">\n <div class=\"label\">• 현장상태</div>\n <div class=\"value status-value\">${PROJECT_STATE[this.project.state]}</div>\n </div>\n <div class=\"row\">\n <div class=\"label\">• 요청상세</div>\n <div class=\"value bold\">???</div>\n </div>\n <div class=\"row\">\n <div class=\"label\">• 알람</div>\n <div class=\"value\">???</div>\n </div>\n </div>\n </div>\n\n <div class=\"detail-grid\">\n <div class=\"grid-item row\">\n <div class=\"label\">• 공사비</div>\n <div class=\"value\">${this.project.buildingComplex?.constructionCost?.toLocaleString() || '-'} 원</div>\n </div>\n <div class=\"grid-item row\">\n <div class=\"label\">• 연면적</div>\n <div class=\"value\">${this.project.buildingComplex?.area?.toLocaleString() || '-'} ㎡</div>\n </div>\n <div class=\"grid-item row\">\n <div class=\"label\">• 용적률</div>\n <div class=\"value\">??? %</div>\n </div>\n <div class=\"grid-item row\">\n <div class=\"label\">• 투입인력</div>\n <div class=\"value\">??? 명</div>\n </div>\n </div>\n </div>\n\n <div class=\"card\">\n <div class=\"card-title\">\n <div>입력 대기 중</div>\n <div class=\"triangle\"></div>\n </div>\n <div class=\"sub-desc\">\n 현재 입력 기한이 도래했거나, 입력이 지연되고 있는 KPI 항목입니다. 해당 항목에 대한 데이터를 입력 해 주시기 바랍니다.\n </div>\n\n <div class=\"list-block\">\n <div class=\"list-title\">• 입력 대기 중 2건</div>\n <div class=\"list-text\"># 2033312-Y5.1 : 검수자재 불합격률</div>\n <div class=\"list-text\"># 2033312-Y5.1 : 품질시험 불합격 건수</div>\n </div>\n <div class=\"list-block\">\n <div class=\"list-title\">• 입력 기한 초과 1건</div>\n <div class=\"list-text\">\n # 2033312-Y5.2 : 검측 불합격률 <span class=\"warn\">2025.2.12 <md-icon>report</md-icon></span>\n </div>\n </div>\n <div class=\"list-block\">\n <div class=\"list-title\">• 입력 예정 4건</div>\n <div class=\"list-text\"># 2033320-Y4.32 : 연면적 대비 공기입력</div>\n <div class=\"list-text\"># 2012345-Y4.32 : 책임감리원의 일정성과 수준 평가</div>\n <div class=\"list-text\"># 2035641-Y5.22 : 설계 변경에 따른 공기 증감률</div>\n <div class=\"list-text\"># 2096412-Y5.31 : 현장별 일정 이탈 수준</div>\n </div>\n <div class=\"list-block\">\n <div class=\"list-title\">• 입력 기한 없음 3건</div>\n <div class=\"list-text\"># 2033320-Y4.32 : 연면적 대비 공기입력</div>\n <div class=\"list-text\"># 2012345-Y4.32 : 책임감리원의 일정성과 수준 평가</div>\n <div class=\"list-text\"># 2035641-Y5.22 : 설계 변경에 따른 공기 증감률</div>\n </div>\n </div>\n </div>\n\n <div right>\n ${html`\n <div class=\"button-line\">\n <div class=\"ghost-btn\" @click=${() => navigate(`project-update/${this.project.id}`)}>\n <md-icon slot=\"\">assignment</md-icon>\n <div>프로젝트 수정</div>\n </div>\n <div class=\"ghost-btn\" @click=${() => navigate(`project-complete/${this.project.id}`)}>\n <md-icon slot=\"\">assignment_turned_in</md-icon>\n <div>프로젝트 완공 처리</div>\n </div>\n </div>\n `}\n\n <div class=\"card kpi-box\">\n <div class=\"kpi-header\">\n <div>\n <div>종합 KPI</div>\n <div class=\"triangle\"></div>\n </div>\n <div class=\"kpi-desc\">프로젝트의 6개 성과를 종합한 종합 KPI포인트와 각 지표별 분포</div>\n </div>\n\n <div class=\"kpi-summary\">\n <div class=\"mini-boxplot\" title=\"boxplot placeholder\">\n <div class=\"boxplot-area\">\n <kpi-single-boxplot-chart\n .data=${{\n min: 0,\n max: 100,\n mean: 50,\n median: 60,\n q1: 25,\n q3: 75,\n value: this.project.kpi\n }}\n ></kpi-single-boxplot-chart>\n </div>\n\n <div class=\"boxplot-point\">\n <div class=\"value\">${this.project.kpi?.toFixed(1) ?? '-'}</div>\n <div class=\"label\">Point</div>\n </div>\n </div>\n\n <div class=\"spider-area\">\n <kpi-radar-chart\n .data=${[\n {\n group: '평균',\n category: '생산성 성과',\n value: 41\n },\n {\n group: '평균',\n category: '일정 성과',\n value: 26\n },\n {\n group: '평균',\n category: '비용 성과',\n value: 30\n },\n {\n group: '평균',\n category: '품질 성과',\n value: 21\n },\n {\n group: '평균',\n category: '안전 성과',\n value: 33\n },\n {\n group: '평균',\n category: '환경 성과',\n value: 15\n }\n ]}\n .categories=${['생산성 성과', '일정 성과', '비용 성과', '품질 성과', '안전 성과', '환경 성과']}\n .currentGroup=${'평균'}\n ></kpi-radar-chart>\n </div>\n </div>\n </div>\n\n <div class=\"card group-box\">\n <div class=\"kpi-header\">\n <div>그룹별 분포</div>\n <div class=\"triangle\"></div>\n </div>\n <div class=\"desc\">\n Boxplot(박스플롯)은 각 카테고리별로 값의 분포(최소, 1사분위, 중앙값, 3사분위, 최대, 평균, 이상치 등)를 보여줍니다.\n 박스는 중앙 50% 구간, 수염은 전체 범위, 굵은 검정색 가로선은 중앙값(메디안), 초록색 원은 평균값(Mean)을 의미합니다.\n 이 프로젝트의 값은 진한 오렌지색 원으로 별도 강조되어 표시되며, 중앙값/평균과 다를 수 있습니다.\n </div>\n <div class=\"group-chart\"></div>\n </div>\n </div>\n </div>\n `\n }\n\n async pageUpdated(changes: any, lifecycle: PageLifecycle) {\n if (this.active) {\n await this.initProject(lifecycle.resourceId)\n }\n }\n\n async initProject(projectId: string = '') {\n const response = await client.query({\n query: gql`\n query Project($id: String!) {\n project(id: $id) {\n id\n name\n state\n startDate\n endDate\n projectType\n mainPhoto {\n fullpath\n }\n totalProgress\n weeklyProgress\n kpi\n inspPassRate\n robotProgressRate\n structuralSafetyRate\n robotCount\n buildingComplex {\n id\n address\n latitude\n longitude\n area\n clientCompany\n constructionCompany\n supervisoryCompany\n designCompany\n drawing {\n id\n name\n fullpath\n }\n constructionType\n constructionCost\n etc\n notice\n householdCount\n buildingCount\n virtualTourLink\n buildings {\n id\n name\n floorCount\n }\n }\n }\n }\n `,\n variables: { id: projectId }\n })\n\n if (response.errors) return\n\n this.project = response.data?.project || ({} as Project)\n }\n}\n"]}
1
+ {"version":3,"file":"sv-project-detail.js","sourceRoot":"","sources":["../../client/pages/sv-project-detail.ts"],"names":[],"mappings":"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","sourcesContent":["import { navigate, PageLifecycle, PageView } from '@operato/shell'\nimport { css, html } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\nimport { ScopedElementsMixin } from '@open-wc/scoped-elements'\nimport { Project, PROJECT_STATE, ProjectState } from './sv-project-list'\nimport { client } from '@operato/graphql'\nimport gql from 'graphql-tag'\nimport '../components/kpi-boxplot-chart'\nimport '../components/kpi-single-boxplot-chart'\nimport '../components/kpi-radar-chart'\n\n@customElement('sv-project-detail')\nexport class SvProjectDetailPage extends ScopedElementsMixin(PageView) {\n static styles = [\n css`\n :host {\n display: flex;\n flex-direction: column;\n overflow-y: auto;\n\n width: 100%;\n height: 100%;\n background-color: var(--md-sys-color-background, #f6f6f6);\n }\n\n /* content layout */\n div[content] {\n display: flex;\n gap: 35px;\n padding: 25px;\n }\n div[left] {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 20px;\n }\n div[right] {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 20px;\n padding: 0 1px;\n }\n\n /* card */\n .card {\n background: #ffffff;\n border-radius: 10px;\n padding: 15px;\n box-shadow: 3px 3px 3px 0 rgba(0, 0, 0, 0.1);\n }\n .card-title {\n display: flex;\n align-items: center;\n gap: 5px;\n color: #35618e;\n font-weight: 700;\n }\n .triangle {\n width: 0;\n height: 0;\n border-left: 6px solid transparent;\n border-right: 6px solid transparent;\n border-top: 8px solid #35618e;\n }\n\n /* left top info box */\n .bold {\n font-weight: bold;\n }\n .top-info {\n display: flex;\n gap: 10px;\n }\n .top-info img[pic] {\n width: 284px;\n height: 160px;\n object-fit: fill;\n background: #e5e7eb;\n }\n .top-info .info {\n display: flex;\n flex-direction: column;\n gap: 15px;\n padding: 10px 0;\n flex: 1;\n }\n .row {\n display: flex;\n align-items: center;\n gap: 10px;\n }\n .label {\n min-width: fit-content;\n color: #35618e;\n font-size: 16px;\n letter-spacing: -0.05em;\n }\n .value {\n color: #333;\n font-size: 16px;\n }\n .status-value {\n color: #4cbb49;\n font-weight: 700;\n }\n\n .address {\n color: #35618e;\n font-size: 16px;\n letter-spacing: -0.05em;\n }\n\n .detail-grid {\n display: grid;\n grid-template-columns: 284px 1fr;\n gap: 15px 10px;\n padding: 5px 0;\n }\n .detail-grid .grid-item .label {\n width: auto;\n }\n\n /* left second card (requests) */\n .sub-desc {\n color: #35618e;\n font-size: 14px;\n letter-spacing: -0.05em;\n margin-bottom: 8px;\n }\n .list-block {\n display: flex;\n flex-direction: column;\n gap: 5px;\n padding: 5px 25px;\n }\n .list-title {\n color: #212529;\n font-weight: 700;\n font-size: 16px;\n letter-spacing: -0.05em;\n }\n .list-text {\n color: #35618e;\n font-size: 16px;\n letter-spacing: -0.05em;\n margin-left: 8px;\n }\n .list-text .warn {\n margin-left: 10px;\n color: #e13232;\n\n md-icon {\n font-size: 17px;\n width: 13px;\n height: 13px;\n }\n }\n\n /* right buttons */\n .button-line {\n display: flex;\n justify-content: flex-end;\n gap: 10px;\n }\n .ghost-btn {\n display: flex;\n align-items: center;\n gap: 3px;\n padding: 5px 10px;\n background: #35618e;\n color: #ffffff;\n border-radius: 5px;\n box-shadow: 2px 2px 2px 0 rgba(0, 0, 0, 0.1);\n cursor: pointer;\n\n /* Material Symbols Outlined (stroke style) */\n md-icon {\n font-variation-settings:\n 'FILL' 0,\n 'wght' 300,\n 'GRAD' 0,\n 'opsz' 24;\n }\n }\n\n /* right KPI box */\n .kpi-box {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 15px;\n max-height: 420px;\n min-width: 560px;\n }\n .kpi-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n color: #35618e;\n font-weight: 700;\n font-size: 20px;\n letter-spacing: -0.05em;\n }\n .kpi-header div {\n display: flex;\n align-items: center;\n gap: 5px;\n }\n .kpi-desc {\n color: #35618e;\n font-size: 13px;\n letter-spacing: -0.05em;\n }\n .kpi-summary {\n display: flex;\n align-items: center;\n padding: 0 30px;\n max-height: 360px;\n gap: 20px;\n }\n .mini-boxplot {\n flex: 1;\n display: flex;\n flex-direction: column;\n height: 100%;\n position: relative;\n min-width: 120px;\n }\n .boxplot-area {\n flex: 1;\n width: 100%;\n display: flex;\n flex-direction: column;\n align-items: center;\n background: #ffffff;\n }\n\n .boxplot-point {\n display: flex;\n flex-direction: column;\n align-items: center;\n color: #35618e;\n }\n .boxplot-point .value {\n font-size: 42px;\n font-weight: 800;\n color: #35618e;\n border-radius: 6px;\n }\n .boxplot-point .label {\n font-size: 14px;\n font-weight: 300;\n width: auto;\n }\n\n /* spider chart placeholder */\n .spider-area {\n flex: 5;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 10px;\n height: 100%;\n }\n\n /* group distribution */\n .group-box .desc {\n color: #35618e;\n font-size: 13px;\n letter-spacing: -0.05em;\n margin-top: 4px;\n }\n .group-chart {\n height: 300px;\n margin-top: 25px;\n }\n\n kpi-single-boxplot-chart {\n flex: 1;\n }\n `\n ]\n\n get context() {\n return {\n title: this.project ? `프로젝트 상세 정보 - ${this.project?.name}` : '프로젝트 상세 정보'\n }\n }\n\n @state() private project: Project = {} as Project\n @state() private kpiComprehensiveStats: any[] = []\n @state() private kpiYComprehensiveStats: any[] = []\n\n render() {\n return html`\n <div content>\n <div left>\n <div class=\"card\">\n <div class=\"card-title\">\n <div>${this.project.name}</div>\n <div class=\"triangle\"></div>\n </div>\n\n <div class=\"top-info\" style=\"margin-top: 10px;\">\n <img pic src=${this.project.mainPhoto?.fullpath || '/assets/images/project-image.png'} alt=\"project\" />\n <div class=\"info\">\n <div class=\"row\">\n <div class=\"label\">• 주소</div>\n <div class=\"value\">${this.project.buildingComplex?.address}</div>\n </div>\n <div class=\"row\">\n <div class=\"label\">• 공사기간</div>\n <div class=\"value\">${this.project.startDate} ~ ${this.project.endDate}</div>\n </div>\n <div class=\"row\">\n <div class=\"label\">• 현장상태</div>\n <div class=\"value status-value\">${PROJECT_STATE[this.project.state]}</div>\n </div>\n <div class=\"row\">\n <div class=\"label\">• 요청상세</div>\n <div class=\"value bold\">000</div>\n </div>\n <div class=\"row\">\n <div class=\"label\">• 알람</div>\n <div class=\"value\">000</div>\n </div>\n </div>\n </div>\n\n <div class=\"detail-grid\">\n <div class=\"grid-item row\">\n <div class=\"label\">• 공사비</div>\n <div class=\"value\">${this.project.buildingComplex?.constructionCost?.toLocaleString() || '-'} 원</div>\n </div>\n <div class=\"grid-item row\">\n <div class=\"label\">• 연면적</div>\n <div class=\"value\">${this.project.buildingComplex?.area?.toLocaleString() || '-'} ㎡</div>\n </div>\n <div class=\"grid-item row\">\n <div class=\"label\">• 용적률</div>\n <div class=\"value\">000 %</div>\n </div>\n <div class=\"grid-item row\">\n <div class=\"label\">• 투입인력</div>\n <div class=\"value\">000 명</div>\n </div>\n </div>\n </div>\n\n <div class=\"card\">\n <div class=\"card-title\">\n <div>입력 대기 중</div>\n <div class=\"triangle\"></div>\n </div>\n <div class=\"sub-desc\">\n 현재 입력 기한이 도래했거나, 입력이 지연되고 있는 KPI 항목입니다. 해당 항목에 대한 데이터를 입력 해 주시기 바랍니다.\n </div>\n\n <div class=\"list-block\">\n <div class=\"list-title\">• 입력 대기 중 2건</div>\n <div class=\"list-text\"># 2033312-Y5.1 : 검수자재 불합격률</div>\n <div class=\"list-text\"># 2033312-Y5.1 : 품질시험 불합격 건수</div>\n </div>\n <div class=\"list-block\">\n <div class=\"list-title\">• 입력 기한 초과 1건</div>\n <div class=\"list-text\">\n # 2033312-Y5.2 : 검측 불합격률 <span class=\"warn\">2025.2.12 <md-icon>report</md-icon></span>\n </div>\n </div>\n <div class=\"list-block\">\n <div class=\"list-title\">• 입력 예정 4건</div>\n <div class=\"list-text\"># 2033320-Y4.32 : 연면적 대비 공기입력</div>\n <div class=\"list-text\"># 2012345-Y4.32 : 책임감리원의 일정성과 수준 평가</div>\n <div class=\"list-text\"># 2035641-Y5.22 : 설계 변경에 따른 공기 증감률</div>\n <div class=\"list-text\"># 2096412-Y5.31 : 현장별 일정 이탈 수준</div>\n </div>\n <div class=\"list-block\">\n <div class=\"list-title\">• 입력 기한 없음 3건</div>\n <div class=\"list-text\"># 2033320-Y4.32 : 연면적 대비 공기입력</div>\n <div class=\"list-text\"># 2012345-Y4.32 : 책임감리원의 일정성과 수준 평가</div>\n <div class=\"list-text\"># 2035641-Y5.22 : 설계 변경에 따른 공기 증감률</div>\n </div>\n </div>\n </div>\n\n <div right>\n ${html`\n <div class=\"button-line\">\n <div class=\"ghost-btn\" @click=${() => navigate(`project-update/${this.project.id}`)}>\n <md-icon slot=\"\">assignment</md-icon>\n <div>프로젝트 수정</div>\n </div>\n <div class=\"ghost-btn\" @click=${() => navigate(`project-complete/${this.project.id}`)}>\n <md-icon slot=\"\">assignment_turned_in</md-icon>\n <div>프로젝트 완공 처리</div>\n </div>\n </div>\n `}\n\n <div class=\"card kpi-box\">\n <div class=\"kpi-header\">\n <div>\n <div>종합 KPI</div>\n <div class=\"triangle\"></div>\n </div>\n <div class=\"kpi-desc\">프로젝트의 6개 성과를 종합한 종합 KPI포인트와 각 지표별 분포</div>\n </div>\n\n <div class=\"kpi-summary\">\n <div class=\"mini-boxplot\" title=\"boxplot placeholder\">\n <div class=\"boxplot-area\">\n <kpi-single-boxplot-chart .data=${this.getBoxPlotDataForProject(this.project)}></kpi-single-boxplot-chart>\n </div>\n\n <div class=\"boxplot-point\">\n <div class=\"value\">${this.project.kpi?.toFixed(1) ?? '-'}</div>\n <div class=\"label\">Point</div>\n </div>\n </div>\n\n <div class=\"spider-area\">\n <kpi-radar-chart\n .data=${this.getRadarChartData().data}\n .categories=${this.getRadarChartData().categories}\n .currentGroup=${'프로젝트성과'}\n ></kpi-radar-chart>\n </div>\n </div>\n </div>\n\n <div class=\"card group-box\">\n <div class=\"kpi-header\">\n <div>\n <div>그룹별 분포</div>\n <div class=\"triangle\"></div>\n </div>\n </div>\n <div class=\"desc\">\n Boxplot(박스플롯)은 각 카테고리별로 값의 분포(최소, 1사분위, 중앙값, 3사분위, 최대, 평균, 이상치 등)를 보여줍니다.\n 박스는 중앙 50% 구간, 수염은 전체 범위, 굵은 검정색 가로선은 중앙값(메디안), 초록색 원은 평균값(Mean)을 의미합니다.\n 이 프로젝트의 값은 진한 오렌지색 원으로 별도 강조되어 표시되며, 중앙값/평균과 다를 수 있습니다.\n </div>\n <div class=\"group-chart\">\n <kpi-boxplot-chart .data=${this.getYKpiBoxplotData()}></kpi-boxplot-chart>\n </div>\n </div>\n </div>\n </div>\n `\n }\n\n async pageUpdated(changes: any, lifecycle: PageLifecycle) {\n if (this.active) {\n this.initProject(lifecycle.resourceId)\n this.getKpiComprehensiveStats()\n this.getKpiYComprehensiveStats()\n }\n }\n\n async initProject(projectId: string = '') {\n const response = await client.query({\n query: gql`\n query Project($id: String!) {\n project(id: $id) {\n id\n name\n state\n startDate\n endDate\n projectType\n mainPhoto {\n fullpath\n }\n totalProgress\n weeklyProgress\n kpi\n kpiValues {\n kpiName\n value\n }\n inspPassRate\n robotProgressRate\n structuralSafetyRate\n robotCount\n buildingComplex {\n id\n address\n latitude\n longitude\n area\n clientCompany\n constructionCompany\n supervisoryCompany\n designCompany\n drawing {\n id\n name\n fullpath\n }\n constructionType\n constructionCost\n etc\n notice\n householdCount\n buildingCount\n virtualTourLink\n buildings {\n id\n name\n floorCount\n }\n }\n }\n }\n `,\n variables: { id: projectId }\n })\n\n if (response.errors) return\n\n this.project = response.data?.project || ({} as Project)\n\n this.updateContext()\n }\n\n async getKpiComprehensiveStats() {\n try {\n const response = await client.query({\n query: gql`\n query GetKpiZValueComprehensiveStats {\n totalKpiZValueComprehensiveStats {\n minVal\n q1Val\n medVal\n q3Val\n maxVal\n }\n }\n `\n })\n\n this.kpiComprehensiveStats = response.data.totalKpiZValueComprehensiveStats || []\n console.log('KPI Z-Value Comprehensive Stats:', this.kpiComprehensiveStats)\n } catch (error) {\n console.error('Failed to fetch KPI comprehensive stats:', error)\n this.kpiComprehensiveStats = []\n }\n }\n\n async getKpiYComprehensiveStats() {\n try {\n const response = await client.query({\n query: gql`\n query GetKpiYValueComprehensiveStats {\n totalKpiYValueComprehensiveStats {\n kpiName\n minVal\n q1Val\n medVal\n q3Val\n maxVal\n avgVal\n projectCount\n }\n }\n `\n })\n\n this.kpiYComprehensiveStats = response.data.totalKpiYValueComprehensiveStats || []\n console.log('KPI Y-Value Comprehensive Stats:', this.kpiYComprehensiveStats)\n } catch (error) {\n console.error('Failed to fetch KPI Y comprehensive stats:', error)\n this.kpiYComprehensiveStats = []\n }\n }\n\n private getYKpiBoxplotData() {\n // KPI 카테고리 매핑 (Y-category KPI names to display categories)\n const categoryMapping: Record<string, string> = {\n Y01_productivity: '생산성과',\n Y02_schedule: '일정성과',\n Y03_cost: '비용성과',\n Y04_quality: '품질성과',\n Y05_safety: '안전성과',\n Y06_environment: '환경성과'\n }\n\n const data: Array<{\n org: string\n min: number\n max: number\n q1: number\n q3: number\n median: number\n mean: number\n value: number\n }> = []\n\n // Y-KPI 카테고리별로 통합된 통계 생성\n this.kpiYComprehensiveStats.forEach(stat => {\n const category = categoryMapping[stat.kpiName] || stat.kpiName\n\n data.push({\n org: category, // org 필드 사용 (boxplot 차트가 기대하는 필드명)\n min: stat.minVal * 20,\n max: stat.maxVal * 20,\n q1: stat.q1Val * 20,\n q3: stat.q3Val * 20,\n median: stat.medVal * 20,\n mean: stat.avgVal * 20,\n value: this.getProjectYKpiValue(stat.kpiName) // 현재 프로젝트의 실제 Y-KPI 값\n })\n })\n\n return data.sort((a, b) => a.org.localeCompare(b.org))\n }\n\n private getProjectYKpiValue(kpiName: string): number {\n // 현재 프로젝트의 kpiValues 배열에서 해당 KPI 값을 찾습니다\n const kpiValue = this.project?.kpiValues?.find(kv => kv.kpiName === kpiName)\n return kpiValue?.value || 0\n }\n\n private getRadarChartData() {\n // KPI 카테고리 매핑 (Y-category KPI names to display categories)\n const categoryMapping: Record<string, string> = {\n Y01_productivity: '생산성',\n Y02_schedule: '일정',\n Y03_cost: '비용',\n Y04_quality: '품질',\n Y05_safety: '안전',\n Y06_environment: '환경'\n }\n\n const data: Array<{ org: string; category: string; value: number }> = []\n const categories: string[] = []\n\n // Create baseline data from comprehensive stats (using avgVal as baseline)\n this.kpiYComprehensiveStats.forEach(stat => {\n const category = categoryMapping[stat.kpiName] || stat.kpiName\n if (!categories.includes(category)) {\n categories.push(category)\n }\n\n // Add baseline average\n data.push({\n org: '기준평균',\n category,\n value: Math.round(stat.avgVal * 20) // Scale to match expected range\n })\n\n // Add current project performance (using project's individual values)\n // For now, using avgVal + some variation as placeholder\n data.push({\n org: '프로젝트성과',\n category,\n value: this.getProjectYKpiValue(stat.kpiName)\n })\n })\n\n return {\n data,\n categories:\n categories.length > 0\n ? categories\n : ['Y1. 일정성과', 'Y2. 비용성과', 'Y3. 품질성과', 'Y4. 안전성과', 'Y5. 환경성과', 'Y6. 생산성성과']\n }\n }\n\n private getBoxPlotDataForProject(project: Project) {\n // 프로젝트의 지역(geo_group)에 해당하는 통계 찾기\n const stats = this.kpiComprehensiveStats?.[0]\n\n if (!stats) {\n // 전체 통계의 평균값 사용 또는 기본값 반환\n const defaultStats = this.kpiComprehensiveStats.length > 0 ? this.kpiComprehensiveStats[0] : null\n if (defaultStats) {\n return {\n min: defaultStats.minVal,\n max: defaultStats.maxVal,\n mean: (defaultStats.q1Val + defaultStats.q3Val) / 2,\n median: defaultStats.medVal,\n q1: defaultStats.q1Val,\n q3: defaultStats.q3Val,\n value: project.kpi || 0\n }\n }\n\n // 완전한 기본값\n return {\n min: 0,\n max: 100,\n mean: 50,\n median: 50,\n q1: 25,\n q3: 75,\n value: project.kpi || 0\n }\n }\n\n return {\n min: stats.minVal * 20,\n max: stats.maxVal * 20,\n mean: ((stats.q1Val + stats.q3Val) / 2) * 20, // 대략적 평균값\n median: stats.medVal * 20,\n q1: stats.q1Val * 20,\n q3: stats.q3Val * 20,\n value: project.kpi || 0\n }\n }\n}\n"]}
@@ -34,6 +34,10 @@ export declare enum ProjectType {
34
34
  DCSP = "DCSP",
35
35
  DKPI = "DKPI"
36
36
  }
37
+ export interface KpiValuesObject {
38
+ kpiName: string;
39
+ value: number;
40
+ }
37
41
  export interface Project {
38
42
  id?: string;
39
43
  name: string;
@@ -45,6 +49,7 @@ export interface Project {
45
49
  totalProgress?: number;
46
50
  weeklyProgress?: number;
47
51
  kpi?: number;
52
+ kpiValues?: KpiValuesObject[];
48
53
  inspPassRate?: number;
49
54
  robotProgressRate?: number;
50
55
  structuralSafetyRate?: number;
@@ -144,7 +149,7 @@ export declare class SvProjectListPage extends SvProjectListPage_base {
144
149
  private projectList;
145
150
  private projectCount;
146
151
  private currentPage;
147
- private kpiBoxPlotStats;
152
+ private kpiComprehensiveStats;
148
153
  private readonly pageLimit;
149
154
  get totalPages(): number;
150
155
  render(): import("lit-html").TemplateResult<1>;
@@ -153,7 +158,7 @@ export declare class SvProjectListPage extends SvProjectListPage_base {
153
158
  private _onInputChange;
154
159
  private _onKeypress;
155
160
  private _changePage;
156
- getKpiBoxPlotStats(): Promise<void>;
161
+ getKpiComprehensiveStats(): Promise<void>;
157
162
  private _openCreateProjectPopup;
158
163
  private getBoxPlotDataForProject;
159
164
  }
@@ -49,7 +49,7 @@ let SvProjectListPage = class SvProjectListPage extends ScopedElementsMixin(Page
49
49
  this.projectList = [];
50
50
  this.projectCount = 0;
51
51
  this.currentPage = 1;
52
- this.kpiBoxPlotStats = [];
52
+ this.kpiComprehensiveStats = [];
53
53
  this.pageLimit = 20;
54
54
  }
55
55
  get context() {
@@ -103,7 +103,7 @@ let SvProjectListPage = class SvProjectListPage extends ScopedElementsMixin(Page
103
103
  return html `
104
104
  <div tr @click=${() => navigate(`project-detail/${project.id}`)}>
105
105
  <div td-pics>
106
- <img pic src=${((_g = project.mainPhoto) === null || _g === void 0 ? void 0 : _g.fullpath) || '/assets/images/no-image.png'} alt="${project.name}" />
106
+ <img pic src=${((_g = project.mainPhoto) === null || _g === void 0 ? void 0 : _g.fullpath) || '/assets/images/project-image.png'} alt="${project.name}" />
107
107
  </div>
108
108
  <div td-status>${PROJECT_STATE[project.state]}</div>
109
109
  <div td-info>
@@ -133,7 +133,7 @@ let SvProjectListPage = class SvProjectListPage extends ScopedElementsMixin(Page
133
133
  async pageUpdated(changes, lifecycle) {
134
134
  if (this.active) {
135
135
  this.getProjectList();
136
- this.getKpiBoxPlotStats();
136
+ this.getKpiComprehensiveStats();
137
137
  }
138
138
  }
139
139
  async getProjectList() {
@@ -203,12 +203,12 @@ let SvProjectListPage = class SvProjectListPage extends ScopedElementsMixin(Page
203
203
  this.getProjectList();
204
204
  }
205
205
  }
206
- async getKpiBoxPlotStats() {
206
+ async getKpiComprehensiveStats() {
207
207
  try {
208
208
  const response = await client.query({
209
209
  query: gql `
210
- query GetKpiZValueBoxPlotStats {
211
- totalKpiZValueBoxPlotStats {
210
+ query GetKpiZValueComprehensiveStats {
211
+ totalKpiZValueComprehensiveStats {
212
212
  minVal
213
213
  q1Val
214
214
  medVal
@@ -218,12 +218,12 @@ let SvProjectListPage = class SvProjectListPage extends ScopedElementsMixin(Page
218
218
  }
219
219
  `
220
220
  });
221
- this.kpiBoxPlotStats = response.data.totalKpiZValueBoxPlotStats || [];
222
- console.log('KPI Z-Value Box Plot Stats:', this.kpiBoxPlotStats);
221
+ this.kpiComprehensiveStats = response.data.totalKpiZValueComprehensiveStats || [];
222
+ console.log('KPI Z-Value Comprehensive Stats:', this.kpiComprehensiveStats);
223
223
  }
224
224
  catch (error) {
225
- console.error('Failed to fetch KPI box plot stats:', error);
226
- this.kpiBoxPlotStats = [];
225
+ console.error('Failed to fetch KPI comprehensive stats:', error);
226
+ this.kpiComprehensiveStats = [];
227
227
  }
228
228
  }
229
229
  _openCreateProjectPopup() {
@@ -236,10 +236,10 @@ let SvProjectListPage = class SvProjectListPage extends ScopedElementsMixin(Page
236
236
  getBoxPlotDataForProject(project) {
237
237
  var _a;
238
238
  // 프로젝트의 지역(geo_group)에 해당하는 통계 찾기
239
- const stats = (_a = this.kpiBoxPlotStats) === null || _a === void 0 ? void 0 : _a[0];
239
+ const stats = (_a = this.kpiComprehensiveStats) === null || _a === void 0 ? void 0 : _a[0];
240
240
  if (!stats) {
241
241
  // 전체 통계의 평균값 사용 또는 기본값 반환
242
- const defaultStats = this.kpiBoxPlotStats.length > 0 ? this.kpiBoxPlotStats[0] : null;
242
+ const defaultStats = this.kpiComprehensiveStats.length > 0 ? this.kpiComprehensiveStats[0] : null;
243
243
  if (defaultStats) {
244
244
  return {
245
245
  min: defaultStats.minVal,
@@ -481,7 +481,7 @@ __decorate([
481
481
  __decorate([
482
482
  state(),
483
483
  __metadata("design:type", Array)
484
- ], SvProjectListPage.prototype, "kpiBoxPlotStats", void 0);
484
+ ], SvProjectListPage.prototype, "kpiComprehensiveStats", void 0);
485
485
  SvProjectListPage = __decorate([
486
486
  customElement('sv-project-list')
487
487
  ], SvProjectListPage);