@dssp/dkpi 1.0.0-alpha.7 → 1.0.0-alpha.70

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 (232) hide show
  1. package/KPI-STATISTICS-SERVICE.md +233 -0
  2. package/_index.html +0 -5
  3. package/assets/favicon.ico +0 -0
  4. package/assets/images/project-image.png +0 -0
  5. package/assets/manifest/apple-1024.png +0 -0
  6. package/assets/manifest/apple-120.png +0 -0
  7. package/assets/manifest/apple-152.png +0 -0
  8. package/assets/manifest/apple-167.png +0 -0
  9. package/assets/manifest/apple-180.png +0 -0
  10. package/assets/manifest/apple-touch-icon.png +0 -0
  11. package/assets/manifest/badge-128x128.png +0 -0
  12. package/assets/manifest/chrome-splashscreen-icon-384x384.png +0 -0
  13. package/assets/manifest/chrome-touch-icon-192x192.png +0 -0
  14. package/assets/manifest/icon-128x128.png +0 -0
  15. package/assets/manifest/icon-192x192.png +0 -0
  16. package/assets/manifest/icon-512x512.png +0 -0
  17. package/assets/manifest/icon-72x72.png +0 -0
  18. package/assets/manifest/icon-96x96.png +0 -0
  19. package/assets/manifest/image-metaog.png +0 -0
  20. package/assets/manifest/maskable_icon.png +0 -0
  21. package/assets/manifest/ms-icon-144x144.png +0 -0
  22. package/assets/manifest/ms-touch-icon-144x144-precomposed.png +0 -0
  23. package/assets/videos/intro.mp4 +0 -0
  24. package/dist-client/bootstrap.js +64 -4
  25. package/dist-client/bootstrap.js.map +1 -1
  26. package/dist-client/components/kpi-2d-lookup-chart.d.ts +43 -0
  27. package/dist-client/components/kpi-2d-lookup-chart.js +253 -0
  28. package/dist-client/components/kpi-2d-lookup-chart.js.map +1 -0
  29. package/dist-client/components/kpi-boxplot-chart.d.ts +24 -0
  30. package/dist-client/components/kpi-boxplot-chart.js +291 -0
  31. package/dist-client/components/kpi-boxplot-chart.js.map +1 -0
  32. package/dist-client/components/kpi-lookup-chart.d.ts +53 -0
  33. package/dist-client/components/kpi-lookup-chart.js +430 -0
  34. package/dist-client/components/kpi-lookup-chart.js.map +1 -0
  35. package/dist-client/components/kpi-mini-trend-chart.d.ts +14 -0
  36. package/dist-client/components/kpi-mini-trend-chart.js +148 -0
  37. package/dist-client/components/kpi-mini-trend-chart.js.map +1 -0
  38. package/dist-client/components/kpi-radar-chart.d.ts +17 -0
  39. package/dist-client/components/kpi-radar-chart.js +259 -0
  40. package/dist-client/components/kpi-radar-chart.js.map +1 -0
  41. package/dist-client/components/kpi-single-boxplot-chart.d.ts +24 -0
  42. package/dist-client/components/kpi-single-boxplot-chart.js +391 -0
  43. package/dist-client/components/kpi-single-boxplot-chart.js.map +1 -0
  44. package/dist-client/components/kpi-trend-chart.d.ts +25 -0
  45. package/dist-client/components/kpi-trend-chart.js +220 -0
  46. package/dist-client/components/kpi-trend-chart.js.map +1 -0
  47. package/dist-client/components/sv-pagenation-control.d.ts +18 -0
  48. package/dist-client/components/sv-pagenation-control.js +142 -0
  49. package/dist-client/components/sv-pagenation-control.js.map +1 -0
  50. package/dist-client/google-map/common-google-map.d.ts +35 -0
  51. package/dist-client/google-map/common-google-map.js +345 -0
  52. package/dist-client/google-map/common-google-map.js.map +1 -0
  53. package/dist-client/google-map/google-map-loader.d.ts +6 -0
  54. package/dist-client/google-map/google-map-loader.js +23 -0
  55. package/dist-client/google-map/google-map-loader.js.map +1 -0
  56. package/dist-client/icons/menu-icons.d.ts +6 -0
  57. package/dist-client/icons/menu-icons.js +42 -0
  58. package/dist-client/icons/menu-icons.js.map +1 -1
  59. package/dist-client/pages/kpi-admin/dssp-kpi-list-page.d.ts +22 -0
  60. package/dist-client/pages/kpi-admin/dssp-kpi-list-page.js +57 -0
  61. package/dist-client/pages/kpi-admin/dssp-kpi-list-page.js.map +1 -0
  62. package/dist-client/pages/kpi-admin/dssp-kpi-overview.d.ts +46 -0
  63. package/dist-client/pages/kpi-admin/dssp-kpi-overview.js +378 -0
  64. package/dist-client/pages/kpi-admin/dssp-kpi-overview.js.map +1 -0
  65. package/dist-client/pages/kpi-admin/kpi-grade-2d-editor.d.ts +20 -0
  66. package/dist-client/pages/kpi-admin/kpi-grade-2d-editor.js +445 -0
  67. package/dist-client/pages/kpi-admin/kpi-grade-2d-editor.js.map +1 -0
  68. package/dist-client/pages/kpi-admin/kpi-system-guide.d.ts +18 -0
  69. package/dist-client/pages/kpi-admin/kpi-system-guide.js +535 -0
  70. package/dist-client/pages/kpi-admin/kpi-system-guide.js.map +1 -0
  71. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.d.ts +18 -0
  72. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js +259 -0
  73. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js.map +1 -0
  74. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.d.ts +22 -0
  75. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js +346 -0
  76. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js.map +1 -0
  77. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.d.ts +26 -0
  78. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js +433 -0
  79. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js.map +1 -0
  80. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.d.ts +8 -0
  81. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js +78 -0
  82. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js.map +1 -0
  83. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.d.ts +38 -0
  84. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js +851 -0
  85. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js.map +1 -0
  86. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.d.ts +42 -0
  87. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js +316 -0
  88. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js.map +1 -0
  89. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.d.ts +28 -0
  90. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js +497 -0
  91. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js.map +1 -0
  92. package/dist-client/pages/kpi-dashboard/kpi-alert-panel.d.ts +18 -0
  93. package/dist-client/pages/kpi-dashboard/kpi-alert-panel.js +131 -0
  94. package/dist-client/pages/kpi-dashboard/kpi-alert-panel.js.map +1 -0
  95. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.d.ts +52 -0
  96. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +798 -0
  97. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -0
  98. package/dist-client/pages/kpi-dashboard/kpi-dashboard.d.ts +63 -0
  99. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js +1089 -0
  100. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js.map +1 -0
  101. package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.d.ts +12 -0
  102. package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.js +82 -0
  103. package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.js.map +1 -0
  104. package/dist-client/pages/kpi-dashboard/kpi-history-viewer.d.ts +11 -0
  105. package/dist-client/pages/kpi-dashboard/kpi-history-viewer.js +65 -0
  106. package/dist-client/pages/kpi-dashboard/kpi-history-viewer.js.map +1 -0
  107. package/dist-client/pages/kpi-dashboard/kpi-list-summary.d.ts +13 -0
  108. package/dist-client/pages/kpi-dashboard/kpi-list-summary.js +115 -0
  109. package/dist-client/pages/kpi-dashboard/kpi-list-summary.js.map +1 -0
  110. package/dist-client/pages/kpi-dashboard/kpi-performance-summary.d.ts +15 -0
  111. package/dist-client/pages/kpi-dashboard/kpi-performance-summary.js +147 -0
  112. package/dist-client/pages/kpi-dashboard/kpi-performance-summary.js.map +1 -0
  113. package/dist-client/pages/kpi-dashboard/kpi-value-entry.d.ts +7 -0
  114. package/dist-client/pages/kpi-dashboard/kpi-value-entry.js +86 -0
  115. package/dist-client/pages/kpi-dashboard/kpi-value-entry.js.map +1 -0
  116. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.d.ts +57 -0
  117. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js +719 -0
  118. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js.map +1 -0
  119. package/dist-client/pages/kpi-metric-value/kpi-metric-value-importer.d.ts +23 -0
  120. package/dist-client/pages/kpi-metric-value/kpi-metric-value-importer.js +76 -0
  121. package/dist-client/pages/kpi-metric-value/kpi-metric-value-importer.js.map +1 -0
  122. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.d.ts +68 -0
  123. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js +380 -0
  124. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js.map +1 -0
  125. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.d.ts +12 -0
  126. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.js +174 -0
  127. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.js.map +1 -0
  128. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.d.ts +40 -0
  129. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js +190 -0
  130. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js.map +1 -0
  131. package/dist-client/pages/kpi-value/kpi-value-importer.d.ts +23 -0
  132. package/dist-client/pages/kpi-value/kpi-value-importer.js +93 -0
  133. package/dist-client/pages/kpi-value/kpi-value-importer.js.map +1 -0
  134. package/dist-client/pages/kpi-value/kpi-value-list-page.d.ts +71 -0
  135. package/dist-client/pages/kpi-value/kpi-value-list-page.js +464 -0
  136. package/dist-client/pages/kpi-value/kpi-value-list-page.js.map +1 -0
  137. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.d.ts +14 -0
  138. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js +339 -0
  139. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js.map +1 -0
  140. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.d.ts +14 -0
  141. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js +276 -0
  142. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js.map +1 -0
  143. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.d.ts +18 -0
  144. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js +307 -0
  145. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js.map +1 -0
  146. package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.d.ts +18 -0
  147. package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.js +433 -0
  148. package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.js.map +1 -0
  149. package/dist-client/pages/sv-project-complete.d.ts +21 -0
  150. package/dist-client/pages/sv-project-complete.js +213 -0
  151. package/dist-client/pages/sv-project-complete.js.map +1 -0
  152. package/dist-client/pages/sv-project-completed-list.d.ts +27 -0
  153. package/dist-client/pages/sv-project-completed-list.js +416 -0
  154. package/dist-client/pages/sv-project-completed-list.js.map +1 -0
  155. package/dist-client/pages/sv-project-detail.d.ts +46 -0
  156. package/dist-client/pages/sv-project-detail.js +1236 -0
  157. package/dist-client/pages/sv-project-detail.js.map +1 -0
  158. package/dist-client/pages/sv-project-list.d.ts +165 -0
  159. package/dist-client/pages/sv-project-list.js +488 -0
  160. package/dist-client/pages/sv-project-list.js.map +1 -0
  161. package/dist-client/route.d.ts +1 -1
  162. package/dist-client/route.js +35 -1
  163. package/dist-client/route.js.map +1 -1
  164. package/dist-client/shared/complete-api.d.ts +8 -0
  165. package/dist-client/shared/complete-api.js +177 -0
  166. package/dist-client/shared/complete-api.js.map +1 -0
  167. package/dist-client/shared/func.d.ts +2 -0
  168. package/dist-client/shared/func.js +22 -0
  169. package/dist-client/shared/func.js.map +1 -0
  170. package/dist-client/themes/dark.css +24 -24
  171. package/dist-client/themes/light.css +23 -23
  172. package/dist-client/tsconfig.tsbuildinfo +1 -1
  173. package/dist-client/viewparts/menu-tools.d.ts +40 -5
  174. package/dist-client/viewparts/menu-tools.js +289 -34
  175. package/dist-client/viewparts/menu-tools.js.map +1 -1
  176. package/dist-server/index.d.ts +2 -0
  177. package/dist-server/index.js +5 -0
  178. package/dist-server/index.js.map +1 -1
  179. package/dist-server/migrations/index.d.ts +1 -0
  180. package/dist-server/migrations/index.js +12 -0
  181. package/dist-server/migrations/index.js.map +1 -0
  182. package/dist-server/scripts/calculate-kpi-scores.d.ts +10 -0
  183. package/dist-server/scripts/calculate-kpi-scores.js +333 -0
  184. package/dist-server/scripts/calculate-kpi-scores.js.map +1 -0
  185. package/dist-server/scripts/load-grade-data-migration.d.ts +14 -0
  186. package/dist-server/scripts/load-grade-data-migration.js +279 -0
  187. package/dist-server/scripts/load-grade-data-migration.js.map +1 -0
  188. package/dist-server/scripts/propagate-parent-kpi-values.d.ts +14 -0
  189. package/dist-server/scripts/propagate-parent-kpi-values.js +786 -0
  190. package/dist-server/scripts/propagate-parent-kpi-values.js.map +1 -0
  191. package/dist-server/scripts/recalculate-by-project-name.d.ts +2 -0
  192. package/dist-server/scripts/recalculate-by-project-name.js +72 -0
  193. package/dist-server/scripts/recalculate-by-project-name.js.map +1 -0
  194. package/dist-server/service/index.d.ts +4 -0
  195. package/dist-server/service/index.js +20 -0
  196. package/dist-server/service/index.js.map +1 -0
  197. package/dist-server/service/kpi-metric-value/index.d.ts +4 -0
  198. package/dist-server/service/kpi-metric-value/index.js +8 -0
  199. package/dist-server/service/kpi-metric-value/index.js.map +1 -0
  200. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.d.ts +74 -0
  201. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js +687 -0
  202. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js.map +1 -0
  203. package/dist-server/service/kpi-metric-value/kpi-metric-value-query.d.ts +7 -0
  204. package/dist-server/service/kpi-metric-value/kpi-metric-value-query.js +52 -0
  205. package/dist-server/service/kpi-metric-value/kpi-metric-value-query.js.map +1 -0
  206. package/dist-server/service/kpi-stat/index.d.ts +4 -0
  207. package/dist-server/service/kpi-stat/index.js +8 -0
  208. package/dist-server/service/kpi-stat/index.js.map +1 -0
  209. package/dist-server/service/kpi-stat/kpi-stat-query.d.ts +12 -0
  210. package/dist-server/service/kpi-stat/kpi-stat-query.js +662 -0
  211. package/dist-server/service/kpi-stat/kpi-stat-query.js.map +1 -0
  212. package/dist-server/service/kpi-stat/kpi-stat-types.d.ts +32 -0
  213. package/dist-server/service/kpi-stat/kpi-stat-types.js +180 -0
  214. package/dist-server/service/kpi-stat/kpi-stat-types.js.map +1 -0
  215. package/dist-server/service/kpi-value/index.d.ts +3 -0
  216. package/dist-server/service/kpi-value/index.js +7 -0
  217. package/dist-server/service/kpi-value/index.js.map +1 -0
  218. package/dist-server/service/kpi-value/kpi-value-query.d.ts +8 -0
  219. package/dist-server/service/kpi-value/kpi-value-query.js +69 -0
  220. package/dist-server/service/kpi-value/kpi-value-query.js.map +1 -0
  221. package/dist-server/tsconfig.tsbuildinfo +1 -1
  222. package/kpi-module-service-tests.md +1286 -0
  223. package/kpi-module-test-report.md +676 -0
  224. package/kpi-module-unit-test-detailed-report.md +925 -0
  225. package/kpi-module-unit-tests-detailed.md +1452 -0
  226. package/package.json +65 -55
  227. package/recalculate-batch.sh +64 -0
  228. package/recalculate-projects-range.sh +98 -0
  229. package/schema.graphql +2514 -455
  230. package/things-factory.config.js +11 -1
  231. package/views/auth-page.html +0 -1
  232. package/views/public/home.html +0 -1
@@ -0,0 +1,445 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ import '@material/web/button/filled-button.js';
3
+ import '@material/web/icon/icon.js';
4
+ import '@operato/data-grist/ox-grist.js';
5
+ import gql from 'graphql-tag';
6
+ import * as d3 from 'd3';
7
+ import { LitElement, css, html } from 'lit';
8
+ import { customElement, property, state, query } from 'lit/decorators.js';
9
+ import { client } from '@operato/graphql';
10
+ import { notify } from '@operato/layout';
11
+ import { CommonHeaderStyles } from '@operato/styles';
12
+ import { DataGrist } from '@operato/data-grist';
13
+ // 점수 구간 정의 (높은 점수 = 좋음 = 낮은 공기편차)
14
+ const SCORE_ZONES = [
15
+ { score: 5, label: '5점', color: '#1b5e20', fillColor: 'rgba(76,175,80,0.15)' },
16
+ { score: 4, label: '4점', color: '#33691e', fillColor: 'rgba(139,195,74,0.15)' },
17
+ { score: 3, label: '3점', color: '#e65100', fillColor: 'rgba(255,193,7,0.15)' },
18
+ { score: 2, label: '2점', color: '#bf360c', fillColor: 'rgba(255,152,0,0.15)' },
19
+ { score: 1, label: '1점', color: '#b71c1c', fillColor: 'rgba(244,67,54,0.15)' }
20
+ ];
21
+ const BOUNDARIES = [
22
+ { key: 'boundary5to4', label: '5점 기준선', subLabel: '이하 → 5점', color: '#4caf50' },
23
+ { key: 'boundary4to3', label: '4점 기준선', subLabel: '이하 → 4점', color: '#8bc34a' },
24
+ { key: 'boundary3to2', label: '3점 기준선', subLabel: '이하 → 3점', color: '#ff9800' },
25
+ { key: 'boundary2to1', label: '2점 기준선', subLabel: '이하 → 2점', color: '#f44336' }
26
+ ];
27
+ let KpiGrade2dEditor = class KpiGrade2dEditor extends LitElement {
28
+ constructor() {
29
+ super(...arguments);
30
+ this.rows = [];
31
+ this.gristConfig = null;
32
+ }
33
+ firstUpdated() {
34
+ var _a;
35
+ const grades = (_a = this.kpi) === null || _a === void 0 ? void 0 : _a.grades;
36
+ this.rows = (grades === null || grades === void 0 ? void 0 : grades.rows) ? grades.rows.map((r) => (Object.assign({}, r))) : [];
37
+ this.gristConfig = {
38
+ list: { fields: ['progressRate', 'boundary5to4', 'boundary4to3', 'boundary3to2', 'boundary2to1'] },
39
+ columns: [
40
+ { type: 'gutter', gutterName: 'sequence', fixed: true },
41
+ {
42
+ type: 'number',
43
+ name: 'progressRate',
44
+ header: '공정률 (%)',
45
+ record: { editable: false },
46
+ sortable: false,
47
+ width: 90
48
+ },
49
+ {
50
+ type: 'number',
51
+ name: 'boundary5to4',
52
+ header: '5점 기준선',
53
+ record: { editable: true, align: 'right' },
54
+ width: 110
55
+ },
56
+ {
57
+ type: 'number',
58
+ name: 'boundary4to3',
59
+ header: '4점 기준선',
60
+ record: { editable: true, align: 'right' },
61
+ width: 110
62
+ },
63
+ {
64
+ type: 'number',
65
+ name: 'boundary3to2',
66
+ header: '3점 기준선',
67
+ record: { editable: true, align: 'right' },
68
+ width: 110
69
+ },
70
+ {
71
+ type: 'number',
72
+ name: 'boundary2to1',
73
+ header: '2점 기준선',
74
+ record: { editable: true, align: 'right' },
75
+ width: 110
76
+ }
77
+ ],
78
+ rows: { selectable: false, appendable: false },
79
+ pagination: { infinite: true }
80
+ };
81
+ requestAnimationFrame(() => this._drawChart(this.rows));
82
+ }
83
+ render() {
84
+ return html `
85
+ <div class="info-bar">
86
+ <span><strong>X축</strong>: 공정률 (%)</span>
87
+ <span><strong>Y축</strong>: 공기편차 (%)</span>
88
+ <span class="arrow">|</span>
89
+ <span class="rule">
90
+ 공기편차
91
+ <span class="score-badge" style="background:#4caf50">5</span>
92
+ 기준선 미만 → 5점,
93
+ <span class="score-badge" style="background:#8bc34a">4</span>
94
+ 기준선 미만 → 4점,
95
+ <span class="score-badge" style="background:#ff9800">3</span>
96
+ 기준선 미만 → 3점,
97
+ <span class="score-badge" style="background:#f44336">2</span>
98
+ 기준선 미만 → 2점, 초과 → 1점
99
+ </span>
100
+ </div>
101
+
102
+ <div class="chart-wrap">
103
+ <svg height="220"></svg>
104
+ </div>
105
+
106
+ <div class="legend">
107
+ ${BOUNDARIES.map(b => html `
108
+ <div class="legend-item">
109
+ <div class="legend-line" style="background:${b.color}"></div>
110
+ <span>${b.label}</span>
111
+ <span class="legend-sub">(${b.subLabel})</span>
112
+ </div>
113
+ `)}
114
+ </div>
115
+
116
+ <ox-grist
117
+ .mode=${'GRID'}
118
+ .config=${this.gristConfig}
119
+ .fetchHandler=${this._fetchHandler.bind(this)}
120
+ @change=${this._onGristChange}
121
+ ></ox-grist>
122
+
123
+ <div class="footer">
124
+ <md-filled-button @click=${this._save}>
125
+ <md-icon slot="icon">save</md-icon>
126
+ 저장
127
+ </md-filled-button>
128
+ </div>
129
+ `;
130
+ }
131
+ async _fetchHandler({ page, limit }) {
132
+ return { total: this.rows.length, records: this.rows };
133
+ }
134
+ _onGristChange() {
135
+ this._drawChart(this._getCurrentRows());
136
+ }
137
+ _getCurrentRows() {
138
+ var _a, _b;
139
+ const records = (_b = (_a = this.grist) === null || _a === void 0 ? void 0 : _a.dirtyData) === null || _b === void 0 ? void 0 : _b.records;
140
+ if (!records || records.length === 0)
141
+ return this.rows;
142
+ return records.map((r) => ({
143
+ progressRate: r.progressRate,
144
+ boundary5to4: Number(r.boundary5to4),
145
+ boundary4to3: Number(r.boundary4to3),
146
+ boundary3to2: Number(r.boundary3to2),
147
+ boundary2to1: Number(r.boundary2to1)
148
+ }));
149
+ }
150
+ _drawChart(rows) {
151
+ if (!this.svgEl || !rows.length)
152
+ return;
153
+ const W = this.svgEl.clientWidth || 560;
154
+ const H = 220;
155
+ const m = { top: 16, right: 32, bottom: 36, left: 56 };
156
+ const w = W - m.left - m.right;
157
+ const h = H - m.top - m.bottom;
158
+ const svg = d3.select(this.svgEl).attr('height', H);
159
+ svg.selectAll('*').remove();
160
+ const g = svg.append('g').attr('transform', `translate(${m.left},${m.top})`);
161
+ const x = d3.scaleLinear().domain([0, 99]).range([0, w]);
162
+ const allVals = rows.flatMap(r => [r.boundary5to4, r.boundary4to3, r.boundary3to2, r.boundary2to1]);
163
+ const [yMin, yMax] = d3.extent(allVals);
164
+ const pad = (yMax - yMin) * 0.15 || 5;
165
+ const y = d3.scaleLinear().domain([yMin - pad, yMax + pad]).range([h, 0]);
166
+ // 점수 구간 채우기 (5개 존)
167
+ const zoneDefs = [
168
+ { y0: _r => h, y1: r => y(r.boundary5to4), fill: 'rgba(76,175,80,0.12)', label: '5점' },
169
+ { y0: r => y(r.boundary5to4), y1: r => y(r.boundary4to3), fill: 'rgba(139,195,74,0.12)', label: '4점' },
170
+ { y0: r => y(r.boundary4to3), y1: r => y(r.boundary3to2), fill: 'rgba(255,193,7,0.12)', label: '3점' },
171
+ { y0: r => y(r.boundary3to2), y1: r => y(r.boundary2to1), fill: 'rgba(255,152,0,0.12)', label: '2점' },
172
+ { y0: r => y(r.boundary2to1), y1: _r => 0, fill: 'rgba(244,67,54,0.12)', label: '1점' }
173
+ ];
174
+ for (const zone of zoneDefs) {
175
+ const area = d3.area()
176
+ .x(r => x(r.progressRate))
177
+ .y0(zone.y0)
178
+ .y1(zone.y1)
179
+ .curve(d3.curveMonotoneX);
180
+ g.append('path')
181
+ .datum(rows)
182
+ .attr('fill', zone.fill)
183
+ .attr('d', area);
184
+ }
185
+ // 중간 공정률 지점에 점수 레이블
186
+ const midRow = rows[Math.floor(rows.length / 2)];
187
+ if (midRow) {
188
+ const scoreColors = ['#2e7d32', '#558b2f', '#f57f17', '#e65100', '#c62828'];
189
+ const scorePositions = [
190
+ (h + y(midRow.boundary5to4)) / 2,
191
+ (y(midRow.boundary5to4) + y(midRow.boundary4to3)) / 2,
192
+ (y(midRow.boundary4to3) + y(midRow.boundary3to2)) / 2,
193
+ (y(midRow.boundary3to2) + y(midRow.boundary2to1)) / 2,
194
+ y(midRow.boundary2to1) / 2
195
+ ];
196
+ const scoreLabels = ['5점', '4점', '3점', '2점', '1점'];
197
+ scorePositions.forEach((yPos, i) => {
198
+ if (yPos > 4 && yPos < h - 4) {
199
+ g.append('text')
200
+ .attr('x', w - 4)
201
+ .attr('y', yPos)
202
+ .attr('text-anchor', 'end')
203
+ .attr('dominant-baseline', 'middle')
204
+ .attr('font-size', '10px')
205
+ .attr('font-weight', 'bold')
206
+ .attr('fill', scoreColors[i])
207
+ .attr('opacity', 0.8)
208
+ .text(scoreLabels[i]);
209
+ }
210
+ });
211
+ }
212
+ // 격자선
213
+ g.append('g')
214
+ .attr('stroke', '#e0e0e0')
215
+ .attr('stroke-dasharray', '2,3')
216
+ .selectAll('line')
217
+ .data(y.ticks(5))
218
+ .join('line')
219
+ .attr('x1', 0).attr('x2', w)
220
+ .attr('y1', d => y(d)).attr('y2', d => y(d));
221
+ // y=0 기준선 (공기편차 0%)
222
+ const yZero = y(0);
223
+ if (yZero >= 0 && yZero <= h) {
224
+ g.append('line')
225
+ .attr('x1', 0).attr('x2', w)
226
+ .attr('y1', yZero).attr('y2', yZero)
227
+ .attr('stroke', '#90a4ae')
228
+ .attr('stroke-width', 1)
229
+ .attr('stroke-dasharray', '4,2');
230
+ g.append('text')
231
+ .attr('x', -4)
232
+ .attr('y', yZero)
233
+ .attr('text-anchor', 'end')
234
+ .attr('dominant-baseline', 'middle')
235
+ .attr('font-size', '9px')
236
+ .attr('fill', '#90a4ae')
237
+ .text('0%');
238
+ }
239
+ // X축
240
+ g.append('g')
241
+ .attr('transform', `translate(0,${h})`)
242
+ .call(d3.axisBottom(x).ticks(10).tickFormat(d => `${d}%`))
243
+ .selectAll('text')
244
+ .style('font-size', '10px');
245
+ // X축 레이블
246
+ g.append('text')
247
+ .attr('x', w / 2)
248
+ .attr('y', h + 30)
249
+ .attr('text-anchor', 'middle')
250
+ .attr('font-size', '11px')
251
+ .attr('fill', '#666')
252
+ .text('공정률 (%)');
253
+ // Y축
254
+ g.append('g')
255
+ .call(d3.axisLeft(y).ticks(5))
256
+ .selectAll('text')
257
+ .style('font-size', '10px');
258
+ // Y축 레이블
259
+ g.append('text')
260
+ .attr('transform', 'rotate(-90)')
261
+ .attr('x', -h / 2)
262
+ .attr('y', -44)
263
+ .attr('text-anchor', 'middle')
264
+ .attr('font-size', '11px')
265
+ .attr('fill', '#666')
266
+ .text('공기편차 (%)');
267
+ // 4개 경계선
268
+ for (const { key, color } of BOUNDARIES) {
269
+ const line = d3
270
+ .line()
271
+ .x(r => x(r.progressRate))
272
+ .y(r => y(r[key]))
273
+ .curve(d3.curveMonotoneX);
274
+ g.append('path')
275
+ .datum(rows)
276
+ .attr('fill', 'none')
277
+ .attr('stroke', color)
278
+ .attr('stroke-width', 2)
279
+ .attr('d', line);
280
+ }
281
+ }
282
+ _validate(rows) {
283
+ for (const row of rows) {
284
+ const { progressRate: p, boundary5to4: b54, boundary4to3: b43, boundary3to2: b32, boundary2to1: b21 } = row;
285
+ if (!(b54 < b43 && b43 < b32 && b32 < b21)) {
286
+ notify({
287
+ message: `공정률 ${p}%행: 기준선 값이 잘못됐습니다. 5점 < 4점 < 3점 < 2점 기준선 순서여야 합니다.`,
288
+ level: 'error'
289
+ });
290
+ return false;
291
+ }
292
+ }
293
+ return true;
294
+ }
295
+ async _save() {
296
+ const rows = this._getCurrentRows();
297
+ if (!this._validate(rows))
298
+ return;
299
+ const grades = { type: 'PROGRESS_DEVIATION_LOOKUP', rows };
300
+ try {
301
+ await client.mutate({
302
+ mutation: gql `
303
+ mutation ($id: String!, $patch: KpiPatch!) {
304
+ updateKpi(id: $id, patch: $patch) {
305
+ id
306
+ grades
307
+ scoreType
308
+ }
309
+ }
310
+ `,
311
+ variables: {
312
+ id: this.kpi.id,
313
+ patch: { grades, scoreType: 'CUSTOM' }
314
+ }
315
+ });
316
+ this.rows = rows;
317
+ this._drawChart(rows);
318
+ notify({ message: '저장되었습니다.' });
319
+ }
320
+ catch (e) {
321
+ notify({ message: '저장 중 오류가 발생했습니다.', level: 'error' });
322
+ }
323
+ }
324
+ };
325
+ KpiGrade2dEditor.styles = [
326
+ CommonHeaderStyles,
327
+ css `
328
+ :host {
329
+ display: flex;
330
+ flex-direction: column;
331
+ height: 100%;
332
+ background-color: var(--md-sys-color-surface);
333
+ }
334
+
335
+ .info-bar {
336
+ display: flex;
337
+ align-items: center;
338
+ gap: 24px;
339
+ padding: 8px 16px;
340
+ background: var(--md-sys-color-surface-variant, #f5f5f5);
341
+ border-bottom: 1px solid var(--md-sys-color-outline-variant, #ddd);
342
+ font-size: 12px;
343
+ color: var(--md-sys-color-on-surface-variant, #555);
344
+ flex-shrink: 0;
345
+ }
346
+
347
+ .info-bar .rule {
348
+ display: flex;
349
+ align-items: center;
350
+ gap: 6px;
351
+ }
352
+
353
+ .info-bar .arrow {
354
+ color: #888;
355
+ }
356
+
357
+ .score-badge {
358
+ display: inline-flex;
359
+ align-items: center;
360
+ justify-content: center;
361
+ width: 20px;
362
+ height: 20px;
363
+ border-radius: 50%;
364
+ font-size: 11px;
365
+ font-weight: bold;
366
+ color: #fff;
367
+ }
368
+
369
+ .chart-wrap {
370
+ padding: 8px 16px 0;
371
+ flex-shrink: 0;
372
+ position: relative;
373
+ }
374
+
375
+ .chart-wrap svg {
376
+ width: 100%;
377
+ display: block;
378
+ }
379
+
380
+ .legend {
381
+ display: flex;
382
+ gap: 20px;
383
+ padding: 4px 16px 8px;
384
+ font-size: 12px;
385
+ flex-shrink: 0;
386
+ flex-wrap: wrap;
387
+ }
388
+
389
+ .legend-item {
390
+ display: flex;
391
+ align-items: center;
392
+ gap: 6px;
393
+ }
394
+
395
+ .legend-line {
396
+ width: 20px;
397
+ height: 3px;
398
+ border-radius: 2px;
399
+ }
400
+
401
+ .legend-sub {
402
+ color: #888;
403
+ font-size: 11px;
404
+ }
405
+
406
+ ox-grist {
407
+ flex: 1;
408
+ overflow: hidden;
409
+ }
410
+
411
+ .footer {
412
+ display: flex;
413
+ justify-content: flex-end;
414
+ gap: 8px;
415
+ padding: 8px 16px;
416
+ border-top: 1px solid var(--md-sys-color-outline-variant, #ccc);
417
+ flex-shrink: 0;
418
+ }
419
+ `
420
+ ];
421
+ __decorate([
422
+ property({ type: Object }),
423
+ __metadata("design:type", Object)
424
+ ], KpiGrade2dEditor.prototype, "kpi", void 0);
425
+ __decorate([
426
+ state(),
427
+ __metadata("design:type", Array)
428
+ ], KpiGrade2dEditor.prototype, "rows", void 0);
429
+ __decorate([
430
+ state(),
431
+ __metadata("design:type", Object)
432
+ ], KpiGrade2dEditor.prototype, "gristConfig", void 0);
433
+ __decorate([
434
+ query('ox-grist'),
435
+ __metadata("design:type", DataGrist)
436
+ ], KpiGrade2dEditor.prototype, "grist", void 0);
437
+ __decorate([
438
+ query('svg'),
439
+ __metadata("design:type", SVGElement)
440
+ ], KpiGrade2dEditor.prototype, "svgEl", void 0);
441
+ KpiGrade2dEditor = __decorate([
442
+ customElement('kpi-grade-2d-editor')
443
+ ], KpiGrade2dEditor);
444
+ export { KpiGrade2dEditor };
445
+ //# sourceMappingURL=kpi-grade-2d-editor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kpi-grade-2d-editor.js","sourceRoot":"","sources":["../../../client/pages/kpi-admin/kpi-grade-2d-editor.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/button/filled-button.js'\nimport '@material/web/icon/icon.js'\nimport '@operato/data-grist/ox-grist.js'\n\nimport gql from 'graphql-tag'\nimport * as d3 from 'd3'\nimport { LitElement, css, html } from 'lit'\nimport { customElement, property, state, query } from 'lit/decorators.js'\nimport { client } from '@operato/graphql'\nimport { notify } from '@operato/layout'\nimport { CommonHeaderStyles } from '@operato/styles'\nimport { DataGrist, FetchOption } from '@operato/data-grist'\n\ninterface Grade2DRow {\n progressRate: number\n boundary5to4: number\n boundary4to3: number\n boundary3to2: number\n boundary2to1: number\n}\n\n// 점수 구간 정의 (높은 점수 = 좋음 = 낮은 공기편차)\nconst SCORE_ZONES = [\n { score: 5, label: '5점', color: '#1b5e20', fillColor: 'rgba(76,175,80,0.15)' },\n { score: 4, label: '4점', color: '#33691e', fillColor: 'rgba(139,195,74,0.15)' },\n { score: 3, label: '3점', color: '#e65100', fillColor: 'rgba(255,193,7,0.15)' },\n { score: 2, label: '2점', color: '#bf360c', fillColor: 'rgba(255,152,0,0.15)' },\n { score: 1, label: '1점', color: '#b71c1c', fillColor: 'rgba(244,67,54,0.15)' }\n]\n\nconst BOUNDARIES = [\n { key: 'boundary5to4' as const, label: '5점 기준선', subLabel: '이하 → 5점', color: '#4caf50' },\n { key: 'boundary4to3' as const, label: '4점 기준선', subLabel: '이하 → 4점', color: '#8bc34a' },\n { key: 'boundary3to2' as const, label: '3점 기준선', subLabel: '이하 → 3점', color: '#ff9800' },\n { key: 'boundary2to1' as const, label: '2점 기준선', subLabel: '이하 → 2점', color: '#f44336' }\n]\n\n@customElement('kpi-grade-2d-editor')\nexport class KpiGrade2dEditor extends LitElement {\n static styles = [\n CommonHeaderStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n height: 100%;\n background-color: var(--md-sys-color-surface);\n }\n\n .info-bar {\n display: flex;\n align-items: center;\n gap: 24px;\n padding: 8px 16px;\n background: var(--md-sys-color-surface-variant, #f5f5f5);\n border-bottom: 1px solid var(--md-sys-color-outline-variant, #ddd);\n font-size: 12px;\n color: var(--md-sys-color-on-surface-variant, #555);\n flex-shrink: 0;\n }\n\n .info-bar .rule {\n display: flex;\n align-items: center;\n gap: 6px;\n }\n\n .info-bar .arrow {\n color: #888;\n }\n\n .score-badge {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n border-radius: 50%;\n font-size: 11px;\n font-weight: bold;\n color: #fff;\n }\n\n .chart-wrap {\n padding: 8px 16px 0;\n flex-shrink: 0;\n position: relative;\n }\n\n .chart-wrap svg {\n width: 100%;\n display: block;\n }\n\n .legend {\n display: flex;\n gap: 20px;\n padding: 4px 16px 8px;\n font-size: 12px;\n flex-shrink: 0;\n flex-wrap: wrap;\n }\n\n .legend-item {\n display: flex;\n align-items: center;\n gap: 6px;\n }\n\n .legend-line {\n width: 20px;\n height: 3px;\n border-radius: 2px;\n }\n\n .legend-sub {\n color: #888;\n font-size: 11px;\n }\n\n ox-grist {\n flex: 1;\n overflow: hidden;\n }\n\n .footer {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n padding: 8px 16px;\n border-top: 1px solid var(--md-sys-color-outline-variant, #ccc);\n flex-shrink: 0;\n }\n `\n ]\n\n @property({ type: Object }) kpi: any\n @state() private rows: Grade2DRow[] = []\n @state() private gristConfig: any = null\n @query('ox-grist') private grist!: DataGrist\n @query('svg') private svgEl!: SVGElement\n\n firstUpdated() {\n const grades = this.kpi?.grades\n this.rows = grades?.rows ? grades.rows.map((r: any) => ({ ...r })) : []\n\n this.gristConfig = {\n list: { fields: ['progressRate', 'boundary5to4', 'boundary4to3', 'boundary3to2', 'boundary2to1'] },\n columns: [\n { type: 'gutter', gutterName: 'sequence', fixed: true },\n {\n type: 'number',\n name: 'progressRate',\n header: '공정률 (%)',\n record: { editable: false },\n sortable: false,\n width: 90\n },\n {\n type: 'number',\n name: 'boundary5to4',\n header: '5점 기준선',\n record: { editable: true, align: 'right' },\n width: 110\n },\n {\n type: 'number',\n name: 'boundary4to3',\n header: '4점 기준선',\n record: { editable: true, align: 'right' },\n width: 110\n },\n {\n type: 'number',\n name: 'boundary3to2',\n header: '3점 기준선',\n record: { editable: true, align: 'right' },\n width: 110\n },\n {\n type: 'number',\n name: 'boundary2to1',\n header: '2점 기준선',\n record: { editable: true, align: 'right' },\n width: 110\n }\n ],\n rows: { selectable: false, appendable: false },\n pagination: { infinite: true }\n }\n\n requestAnimationFrame(() => this._drawChart(this.rows))\n }\n\n render() {\n return html`\n <div class=\"info-bar\">\n <span><strong>X축</strong>: 공정률 (%)</span>\n <span><strong>Y축</strong>: 공기편차 (%)</span>\n <span class=\"arrow\">|</span>\n <span class=\"rule\">\n 공기편차\n <span class=\"score-badge\" style=\"background:#4caf50\">5</span>\n 기준선 미만 → 5점,\n <span class=\"score-badge\" style=\"background:#8bc34a\">4</span>\n 기준선 미만 → 4점,\n <span class=\"score-badge\" style=\"background:#ff9800\">3</span>\n 기준선 미만 → 3점,\n <span class=\"score-badge\" style=\"background:#f44336\">2</span>\n 기준선 미만 → 2점, 초과 → 1점\n </span>\n </div>\n\n <div class=\"chart-wrap\">\n <svg height=\"220\"></svg>\n </div>\n\n <div class=\"legend\">\n ${BOUNDARIES.map(\n b => html`\n <div class=\"legend-item\">\n <div class=\"legend-line\" style=\"background:${b.color}\"></div>\n <span>${b.label}</span>\n <span class=\"legend-sub\">(${b.subLabel})</span>\n </div>\n `\n )}\n </div>\n\n <ox-grist\n .mode=${'GRID'}\n .config=${this.gristConfig}\n .fetchHandler=${this._fetchHandler.bind(this)}\n @change=${this._onGristChange}\n ></ox-grist>\n\n <div class=\"footer\">\n <md-filled-button @click=${this._save}>\n <md-icon slot=\"icon\">save</md-icon>\n 저장\n </md-filled-button>\n </div>\n `\n }\n\n private async _fetchHandler({ page, limit }: FetchOption) {\n return { total: this.rows.length, records: this.rows }\n }\n\n private _onGristChange() {\n this._drawChart(this._getCurrentRows())\n }\n\n private _getCurrentRows(): Grade2DRow[] {\n const records = this.grist?.dirtyData?.records\n if (!records || records.length === 0) return this.rows\n\n return records.map((r: any) => ({\n progressRate: r.progressRate,\n boundary5to4: Number(r.boundary5to4),\n boundary4to3: Number(r.boundary4to3),\n boundary3to2: Number(r.boundary3to2),\n boundary2to1: Number(r.boundary2to1)\n }))\n }\n\n private _drawChart(rows: Grade2DRow[]) {\n if (!this.svgEl || !rows.length) return\n\n const W = this.svgEl.clientWidth || 560\n const H = 220\n const m = { top: 16, right: 32, bottom: 36, left: 56 }\n const w = W - m.left - m.right\n const h = H - m.top - m.bottom\n\n const svg = d3.select(this.svgEl).attr('height', H)\n svg.selectAll('*').remove()\n\n const g = svg.append('g').attr('transform', `translate(${m.left},${m.top})`)\n\n const x = d3.scaleLinear().domain([0, 99]).range([0, w])\n\n const allVals = rows.flatMap(r => [r.boundary5to4, r.boundary4to3, r.boundary3to2, r.boundary2to1])\n const [yMin, yMax] = d3.extent(allVals) as [number, number]\n const pad = (yMax - yMin) * 0.15 || 5\n const y = d3.scaleLinear().domain([yMin - pad, yMax + pad]).range([h, 0])\n\n // 점수 구간 채우기 (5개 존)\n const zoneDefs: Array<{ y0: (r: Grade2DRow) => number; y1: (r: Grade2DRow) => number; fill: string; label: string }> = [\n { y0: _r => h, y1: r => y(r.boundary5to4), fill: 'rgba(76,175,80,0.12)', label: '5점' },\n { y0: r => y(r.boundary5to4), y1: r => y(r.boundary4to3), fill: 'rgba(139,195,74,0.12)', label: '4점' },\n { y0: r => y(r.boundary4to3), y1: r => y(r.boundary3to2), fill: 'rgba(255,193,7,0.12)', label: '3점' },\n { y0: r => y(r.boundary3to2), y1: r => y(r.boundary2to1), fill: 'rgba(255,152,0,0.12)', label: '2점' },\n { y0: r => y(r.boundary2to1), y1: _r => 0, fill: 'rgba(244,67,54,0.12)', label: '1점' }\n ]\n\n for (const zone of zoneDefs) {\n const area = d3.area<Grade2DRow>()\n .x(r => x(r.progressRate))\n .y0(zone.y0)\n .y1(zone.y1)\n .curve(d3.curveMonotoneX)\n\n g.append('path')\n .datum(rows)\n .attr('fill', zone.fill)\n .attr('d', area)\n }\n\n // 중간 공정률 지점에 점수 레이블\n const midRow = rows[Math.floor(rows.length / 2)]\n if (midRow) {\n const scoreColors = ['#2e7d32', '#558b2f', '#f57f17', '#e65100', '#c62828']\n const scorePositions = [\n (h + y(midRow.boundary5to4)) / 2,\n (y(midRow.boundary5to4) + y(midRow.boundary4to3)) / 2,\n (y(midRow.boundary4to3) + y(midRow.boundary3to2)) / 2,\n (y(midRow.boundary3to2) + y(midRow.boundary2to1)) / 2,\n y(midRow.boundary2to1) / 2\n ]\n const scoreLabels = ['5점', '4점', '3점', '2점', '1점']\n\n scorePositions.forEach((yPos, i) => {\n if (yPos > 4 && yPos < h - 4) {\n g.append('text')\n .attr('x', w - 4)\n .attr('y', yPos)\n .attr('text-anchor', 'end')\n .attr('dominant-baseline', 'middle')\n .attr('font-size', '10px')\n .attr('font-weight', 'bold')\n .attr('fill', scoreColors[i])\n .attr('opacity', 0.8)\n .text(scoreLabels[i])\n }\n })\n }\n\n // 격자선\n g.append('g')\n .attr('stroke', '#e0e0e0')\n .attr('stroke-dasharray', '2,3')\n .selectAll('line')\n .data(y.ticks(5))\n .join('line')\n .attr('x1', 0).attr('x2', w)\n .attr('y1', d => y(d)).attr('y2', d => y(d))\n\n // y=0 기준선 (공기편차 0%)\n const yZero = y(0)\n if (yZero >= 0 && yZero <= h) {\n g.append('line')\n .attr('x1', 0).attr('x2', w)\n .attr('y1', yZero).attr('y2', yZero)\n .attr('stroke', '#90a4ae')\n .attr('stroke-width', 1)\n .attr('stroke-dasharray', '4,2')\n g.append('text')\n .attr('x', -4)\n .attr('y', yZero)\n .attr('text-anchor', 'end')\n .attr('dominant-baseline', 'middle')\n .attr('font-size', '9px')\n .attr('fill', '#90a4ae')\n .text('0%')\n }\n\n // X축\n g.append('g')\n .attr('transform', `translate(0,${h})`)\n .call(d3.axisBottom(x).ticks(10).tickFormat(d => `${d}%`))\n .selectAll('text')\n .style('font-size', '10px')\n\n // X축 레이블\n g.append('text')\n .attr('x', w / 2)\n .attr('y', h + 30)\n .attr('text-anchor', 'middle')\n .attr('font-size', '11px')\n .attr('fill', '#666')\n .text('공정률 (%)')\n\n // Y축\n g.append('g')\n .call(d3.axisLeft(y).ticks(5))\n .selectAll('text')\n .style('font-size', '10px')\n\n // Y축 레이블\n g.append('text')\n .attr('transform', 'rotate(-90)')\n .attr('x', -h / 2)\n .attr('y', -44)\n .attr('text-anchor', 'middle')\n .attr('font-size', '11px')\n .attr('fill', '#666')\n .text('공기편차 (%)')\n\n // 4개 경계선\n for (const { key, color } of BOUNDARIES) {\n const line = d3\n .line<Grade2DRow>()\n .x(r => x(r.progressRate))\n .y(r => y(r[key]))\n .curve(d3.curveMonotoneX)\n\n g.append('path')\n .datum(rows)\n .attr('fill', 'none')\n .attr('stroke', color)\n .attr('stroke-width', 2)\n .attr('d', line)\n }\n }\n\n private _validate(rows: Grade2DRow[]): boolean {\n for (const row of rows) {\n const { progressRate: p, boundary5to4: b54, boundary4to3: b43, boundary3to2: b32, boundary2to1: b21 } = row\n if (!(b54 < b43 && b43 < b32 && b32 < b21)) {\n notify({\n message: `공정률 ${p}%행: 기준선 값이 잘못됐습니다. 5점 < 4점 < 3점 < 2점 기준선 순서여야 합니다.`,\n level: 'error'\n })\n return false\n }\n }\n return true\n }\n\n private async _save() {\n const rows = this._getCurrentRows()\n if (!this._validate(rows)) return\n\n const grades = { type: 'PROGRESS_DEVIATION_LOOKUP', rows }\n\n try {\n await client.mutate({\n mutation: gql`\n mutation ($id: String!, $patch: KpiPatch!) {\n updateKpi(id: $id, patch: $patch) {\n id\n grades\n scoreType\n }\n }\n `,\n variables: {\n id: this.kpi.id,\n patch: { grades, scoreType: 'CUSTOM' }\n }\n })\n\n this.rows = rows\n this._drawChart(rows)\n notify({ message: '저장되었습니다.' })\n } catch (e) {\n notify({ message: '저장 중 오류가 발생했습니다.', level: 'error' })\n }\n }\n}\n"]}
@@ -0,0 +1,18 @@
1
+ import { PageView } from '@operato/shell';
2
+ export declare class KpiSystemGuide extends PageView {
3
+ static styles: import("lit").CSSResult[];
4
+ kpiRoot: any[];
5
+ metrics: any[];
6
+ loading: boolean;
7
+ selectedDetail: any;
8
+ get context(): {
9
+ title: string;
10
+ };
11
+ firstUpdated(): Promise<void>;
12
+ /** 메트릭이 사용되는 KPI 목록 */
13
+ private getMetricUsage;
14
+ private showDetail;
15
+ private closeDetail;
16
+ render(): import("lit-html").TemplateResult<1>;
17
+ private _renderDetailPopup;
18
+ }