@things-factory/kpi 9.0.22 → 9.0.24

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 (189) hide show
  1. package/client/pages/kpi/kpi-list-page.ts +180 -22
  2. package/client/pages/kpi/kpi-viz-editor.ts +1 -1
  3. package/client/pages/kpi-category/kpi-category-list-page.ts +76 -3
  4. package/client/pages/kpi-category/kpi-category-value-calculator.ts +233 -0
  5. package/client/pages/kpi-category-value/kpi-category-value-list-page.ts +404 -0
  6. package/client/pages/kpi-metric/kpi-metric-list-page.ts +13 -1
  7. package/client/pages/kpi-metric-value/kpi-metric-value-editor-page.ts +763 -0
  8. package/client/pages/kpi-metric-value/kpi-metric-value-list-page.ts +55 -1
  9. package/client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.ts +3 -13
  10. package/client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.ts +13 -1
  11. package/client/pages/kpi-value/kpi-value-editor-page.ts +774 -0
  12. package/client/pages/kpi-value/kpi-value-list-page.ts +58 -1
  13. package/client/route.ts +16 -0
  14. package/dist-client/pages/kpi/kpi-list-page.d.ts +2 -1
  15. package/dist-client/pages/kpi/kpi-list-page.js +180 -22
  16. package/dist-client/pages/kpi/kpi-list-page.js.map +1 -1
  17. package/dist-client/pages/kpi/kpi-viz-editor.d.ts +0 -1
  18. package/dist-client/pages/kpi/kpi-viz-editor.js +1 -1
  19. package/dist-client/pages/kpi/kpi-viz-editor.js.map +1 -1
  20. package/dist-client/pages/kpi-category/kpi-category-list-page.d.ts +3 -0
  21. package/dist-client/pages/kpi-category/kpi-category-list-page.js +71 -3
  22. package/dist-client/pages/kpi-category/kpi-category-list-page.js.map +1 -1
  23. package/dist-client/pages/kpi-category/kpi-category-value-calculator.d.ts +13 -0
  24. package/dist-client/pages/kpi-category/kpi-category-value-calculator.js +256 -0
  25. package/dist-client/pages/kpi-category/kpi-category-value-calculator.js.map +1 -0
  26. package/dist-client/pages/kpi-category-value/kpi-category-value-list-page.d.ts +63 -0
  27. package/dist-client/pages/kpi-category-value/kpi-category-value-list-page.js +393 -0
  28. package/dist-client/pages/kpi-category-value/kpi-category-value-list-page.js.map +1 -0
  29. package/dist-client/pages/kpi-metric/kpi-metric-list-page.js +13 -1
  30. package/dist-client/pages/kpi-metric/kpi-metric-list-page.js.map +1 -1
  31. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.d.ts +58 -0
  32. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js +736 -0
  33. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js.map +1 -0
  34. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.d.ts +5 -1
  35. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js +50 -2
  36. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js.map +1 -1
  37. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.js +3 -13
  38. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.js.map +1 -1
  39. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js +13 -1
  40. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js.map +1 -1
  41. package/dist-client/pages/kpi-value/kpi-value-editor-page.d.ts +55 -0
  42. package/dist-client/pages/kpi-value/kpi-value-editor-page.js +748 -0
  43. package/dist-client/pages/kpi-value/kpi-value-editor-page.js.map +1 -0
  44. package/dist-client/pages/kpi-value/kpi-value-list-page.d.ts +10 -2
  45. package/dist-client/pages/kpi-value/kpi-value-list-page.js +57 -1
  46. package/dist-client/pages/kpi-value/kpi-value-list-page.js.map +1 -1
  47. package/dist-client/route.d.ts +1 -1
  48. package/dist-client/route.js +12 -0
  49. package/dist-client/route.js.map +1 -1
  50. package/dist-client/tsconfig.tsbuildinfo +1 -1
  51. package/dist-server/calculator/evaluator.d.ts +8 -0
  52. package/dist-server/calculator/evaluator.js +42 -0
  53. package/dist-server/calculator/evaluator.js.map +1 -0
  54. package/dist-server/calculator/functions.d.ts +3 -0
  55. package/dist-server/calculator/functions.js +62 -0
  56. package/dist-server/calculator/functions.js.map +1 -0
  57. package/dist-server/calculator/index.d.ts +4 -0
  58. package/dist-server/calculator/index.js +8 -0
  59. package/dist-server/calculator/index.js.map +1 -0
  60. package/dist-server/calculator/parser.d.ts +21 -0
  61. package/dist-server/calculator/parser.js +121 -0
  62. package/dist-server/calculator/parser.js.map +1 -0
  63. package/dist-server/calculator/provider.d.ts +8 -0
  64. package/dist-server/calculator/provider.js +13 -0
  65. package/dist-server/calculator/provider.js.map +1 -0
  66. package/dist-server/controllers/kpi-metric-value-provider.d.ts +11 -0
  67. package/dist-server/controllers/kpi-metric-value-provider.js +63 -0
  68. package/dist-server/controllers/kpi-metric-value-provider.js.map +1 -0
  69. package/dist-server/controllers/kpi-value-provider.d.ts +11 -0
  70. package/dist-server/controllers/kpi-value-provider.js +46 -0
  71. package/dist-server/controllers/kpi-value-provider.js.map +1 -0
  72. package/dist-server/service/index.d.ts +4 -2
  73. package/dist-server/service/index.js +5 -0
  74. package/dist-server/service/index.js.map +1 -1
  75. package/dist-server/service/kpi/aggregate-kpi.js +4 -4
  76. package/dist-server/service/kpi/aggregate-kpi.js.map +1 -1
  77. package/dist-server/service/kpi/kpi-grade.types.d.ts +11 -10
  78. package/dist-server/service/kpi/kpi-grade.types.js.map +1 -1
  79. package/dist-server/service/kpi/kpi-history.d.ts +2 -2
  80. package/dist-server/service/kpi/kpi-history.js.map +1 -1
  81. package/dist-server/service/kpi/kpi-mutation.d.ts +2 -0
  82. package/dist-server/service/kpi/kpi-mutation.js +126 -4
  83. package/dist-server/service/kpi/kpi-mutation.js.map +1 -1
  84. package/dist-server/service/kpi/kpi-type.d.ts +8 -5
  85. package/dist-server/service/kpi/kpi-type.js +22 -8
  86. package/dist-server/service/kpi/kpi-type.js.map +1 -1
  87. package/dist-server/service/kpi/kpi.d.ts +6 -3
  88. package/dist-server/service/kpi/kpi.js +29 -9
  89. package/dist-server/service/kpi/kpi.js.map +1 -1
  90. package/dist-server/service/kpi-category/kpi-category-mutation.d.ts +3 -4
  91. package/dist-server/service/kpi-category/kpi-category-mutation.js +151 -80
  92. package/dist-server/service/kpi-category/kpi-category-mutation.js.map +1 -1
  93. package/dist-server/service/kpi-category/kpi-category-query.d.ts +5 -0
  94. package/dist-server/service/kpi-category/kpi-category-query.js +19 -1
  95. package/dist-server/service/kpi-category/kpi-category-query.js.map +1 -1
  96. package/dist-server/service/kpi-category/kpi-category-type.d.ts +3 -0
  97. package/dist-server/service/kpi-category/kpi-category-type.js +16 -1
  98. package/dist-server/service/kpi-category/kpi-category-type.js.map +1 -1
  99. package/dist-server/service/kpi-category/kpi-category.d.ts +2 -0
  100. package/dist-server/service/kpi-category/kpi-category.js +10 -1
  101. package/dist-server/service/kpi-category/kpi-category.js.map +1 -1
  102. package/dist-server/service/kpi-category-value/index.d.ts +6 -0
  103. package/dist-server/service/kpi-category-value/index.js +10 -0
  104. package/dist-server/service/kpi-category-value/index.js.map +1 -0
  105. package/dist-server/service/kpi-category-value/kpi-category-value-mutation.d.ts +8 -0
  106. package/dist-server/service/kpi-category-value/kpi-category-value-mutation.js +102 -0
  107. package/dist-server/service/kpi-category-value/kpi-category-value-mutation.js.map +1 -0
  108. package/dist-server/service/kpi-category-value/kpi-category-value-query.d.ts +13 -0
  109. package/dist-server/service/kpi-category-value/kpi-category-value-query.js +91 -0
  110. package/dist-server/service/kpi-category-value/kpi-category-value-query.js.map +1 -0
  111. package/dist-server/service/kpi-category-value/kpi-category-value-type.d.ts +19 -0
  112. package/dist-server/service/kpi-category-value/kpi-category-value-type.js +73 -0
  113. package/dist-server/service/kpi-category-value/kpi-category-value-type.js.map +1 -0
  114. package/dist-server/service/kpi-category-value/kpi-category-value.d.ts +19 -0
  115. package/dist-server/service/kpi-category-value/kpi-category-value.js +91 -0
  116. package/dist-server/service/kpi-category-value/kpi-category-value.js.map +1 -0
  117. package/dist-server/service/kpi-metric/kpi-metric-type.d.ts +5 -3
  118. package/dist-server/service/kpi-metric/kpi-metric-type.js +5 -3
  119. package/dist-server/service/kpi-metric/kpi-metric-type.js.map +1 -1
  120. package/dist-server/service/kpi-metric/kpi-metric.d.ts +2 -8
  121. package/dist-server/service/kpi-metric/kpi-metric.js +3 -14
  122. package/dist-server/service/kpi-metric/kpi-metric.js.map +1 -1
  123. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js +87 -45
  124. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js.map +1 -1
  125. package/dist-server/service/kpi-metric-value/kpi-metric-value.js +3 -2
  126. package/dist-server/service/kpi-metric-value/kpi-metric-value.js.map +1 -1
  127. package/dist-server/service/kpi-value/kpi-value-mutation.d.ts +3 -1
  128. package/dist-server/service/kpi-value/kpi-value-mutation.js +174 -6
  129. package/dist-server/service/kpi-value/kpi-value-mutation.js.map +1 -1
  130. package/dist-server/service/kpi-value/kpi-value-query.d.ts +0 -2
  131. package/dist-server/service/kpi-value/kpi-value-query.js +0 -12
  132. package/dist-server/service/kpi-value/kpi-value-query.js.map +1 -1
  133. package/dist-server/service/kpi-value/kpi-value-score.service.d.ts +26 -0
  134. package/dist-server/service/kpi-value/kpi-value-score.service.js +97 -0
  135. package/dist-server/service/kpi-value/kpi-value-score.service.js.map +1 -0
  136. package/dist-server/service/kpi-value/kpi-value-type.d.ts +2 -0
  137. package/dist-server/service/kpi-value/kpi-value-type.js +14 -0
  138. package/dist-server/service/kpi-value/kpi-value-type.js.map +1 -1
  139. package/dist-server/service/kpi-value/kpi-value.d.ts +1 -0
  140. package/dist-server/service/kpi-value/kpi-value.js +9 -1
  141. package/dist-server/service/kpi-value/kpi-value.js.map +1 -1
  142. package/dist-server/service/utils/value-date-util.d.ts +3 -0
  143. package/dist-server/service/utils/value-date-util.js +76 -0
  144. package/dist-server/service/utils/value-date-util.js.map +1 -0
  145. package/dist-server/tsconfig.tsbuildinfo +1 -1
  146. package/package.json +5 -5
  147. package/server/calculator/evaluator.ts +45 -0
  148. package/server/calculator/functions.ts +67 -0
  149. package/server/calculator/index.ts +4 -0
  150. package/server/calculator/parser.ts +128 -0
  151. package/server/calculator/provider.ts +10 -0
  152. package/server/controllers/kpi-metric-value-provider.ts +66 -0
  153. package/server/controllers/kpi-value-provider.ts +51 -0
  154. package/server/service/index.ts +5 -0
  155. package/server/service/kpi/aggregate-kpi.ts +4 -4
  156. package/server/service/kpi/kpi-grade.types.ts +11 -10
  157. package/server/service/kpi/kpi-history.ts +2 -2
  158. package/server/service/kpi/kpi-mutation.ts +128 -4
  159. package/server/service/kpi/kpi-type.ts +21 -9
  160. package/server/service/kpi/kpi.ts +32 -10
  161. package/server/service/kpi-category/kpi-category-mutation.ts +155 -82
  162. package/server/service/kpi-category/kpi-category-query.ts +21 -1
  163. package/server/service/kpi-category/kpi-category-type.ts +17 -6
  164. package/server/service/kpi-category/kpi-category.ts +10 -1
  165. package/server/service/kpi-category-value/index.ts +7 -0
  166. package/server/service/kpi-category-value/kpi-category-value-mutation.ts +88 -0
  167. package/server/service/kpi-category-value/kpi-category-value-query.ts +62 -0
  168. package/server/service/kpi-category-value/kpi-category-value-type.ts +48 -0
  169. package/server/service/kpi-category-value/kpi-category-value.ts +79 -0
  170. package/server/service/kpi-metric/kpi-metric-type.ts +7 -5
  171. package/server/service/kpi-metric/kpi-metric.ts +3 -15
  172. package/server/service/kpi-metric-value/kpi-metric-value-mutation.ts +95 -47
  173. package/server/service/kpi-metric-value/kpi-metric-value.ts +4 -2
  174. package/server/service/kpi-value/kpi-value-mutation.ts +176 -6
  175. package/server/service/kpi-value/kpi-value-query.ts +2 -8
  176. package/server/service/kpi-value/kpi-value-score.service.ts +112 -0
  177. package/server/service/kpi-value/kpi-value-type.ts +12 -0
  178. package/server/service/kpi-value/kpi-value.ts +8 -1
  179. package/server/service/utils/value-date-util.ts +72 -0
  180. package/things-factory.config.js +3 -0
  181. package/translations/en.json +3 -0
  182. package/translations/ja.json +3 -0
  183. package/translations/ko.json +3 -0
  184. package/translations/ms.json +3 -0
  185. package/translations/zh.json +3 -0
  186. package/dist-server/service/kpi-value/kpi-value-grade.service.d.ts +0 -34
  187. package/dist-server/service/kpi-value/kpi-value-grade.service.js +0 -117
  188. package/dist-server/service/kpi-value/kpi-value-grade.service.js.map +0 -1
  189. package/server/service/kpi-value/kpi-value-grade.service.ts +0 -127
@@ -0,0 +1,748 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ import '@material/web/icon/icon.js';
3
+ import '@material/web/button/elevated-button.js';
4
+ import '@material/web/textfield/outlined-text-field.js';
5
+ import { CommonButtonStyles, CommonHeaderStyles, ScrollbarStyles } from '@operato/styles';
6
+ import { PageView, store } from '@operato/shell';
7
+ import { css, html } from 'lit';
8
+ import { customElement, property, state } from 'lit/decorators.js';
9
+ import { ScopedElementsMixin } from '@open-wc/scoped-elements';
10
+ import { client } from '@operato/graphql';
11
+ import { i18next, localize } from '@operato/i18n';
12
+ import { notify } from '@operato/layout';
13
+ import { connect } from 'pwa-helpers/connect-mixin';
14
+ import gql from 'graphql-tag';
15
+ let KpiValueEditorPage = class KpiValueEditorPage extends connect(store)(localize(i18next)(ScopedElementsMixin(PageView))) {
16
+ constructor() {
17
+ super(...arguments);
18
+ this.group = '';
19
+ this.startDate = '';
20
+ this.endDate = '';
21
+ this.kpis = [];
22
+ this.dates = [];
23
+ this.loading = false;
24
+ this.error = '';
25
+ this.editingCell = null;
26
+ this._existingValues = [];
27
+ }
28
+ static { this.styles = [
29
+ CommonHeaderStyles,
30
+ ScrollbarStyles,
31
+ css `
32
+ :host {
33
+ display: flex;
34
+ flex-direction: column;
35
+ padding: 20px;
36
+ overflow-x: auto;
37
+ }
38
+
39
+ .header {
40
+ display: flex;
41
+ gap: 16px;
42
+ align-items: center;
43
+ margin-bottom: 20px;
44
+ padding: 16px;
45
+ background: var(--md-sys-color-surface-container);
46
+ border-radius: 8px;
47
+ }
48
+
49
+ .controls {
50
+ display: flex;
51
+ gap: 12px;
52
+ align-items: center;
53
+ }
54
+
55
+ .table-container {
56
+ flex: 1;
57
+ overflow: auto;
58
+ border: 1px solid var(--md-sys-color-outline);
59
+ border-radius: 8px;
60
+ }
61
+
62
+ table {
63
+ width: 100%;
64
+ border-collapse: collapse;
65
+ min-width: max-content;
66
+ }
67
+
68
+ th {
69
+ background: var(--md-sys-color-surface-container-low);
70
+ font-weight: 500;
71
+ padding: 8px 12px;
72
+ border: 1px solid var(--md-sys-color-outline-variant);
73
+ min-width: 80px;
74
+ height: 120px;
75
+ vertical-align: middle;
76
+ }
77
+
78
+ td {
79
+ padding: 8px 12px;
80
+ border: 1px solid var(--md-sys-color-outline-variant);
81
+ min-width: 80px;
82
+ height: 60px;
83
+ text-align: right;
84
+ vertical-align: middle;
85
+ }
86
+
87
+ .kpi-header {
88
+ position: sticky;
89
+ left: 0;
90
+ top: 0;
91
+ z-index: 3;
92
+ }
93
+
94
+ .kpi-name {
95
+ position: sticky;
96
+ left: 0;
97
+ background: var(--md-sys-color-surface);
98
+ font-weight: 500;
99
+ min-width: 200px;
100
+ text-align: left;
101
+ z-index: 2;
102
+ }
103
+
104
+ tr:hover {
105
+ background: var(--md-sys-color-surface-container-high);
106
+ }
107
+
108
+ th.date-header {
109
+ position: sticky;
110
+ top: 0;
111
+ z-index: 2;
112
+ text-align: center;
113
+ font-size: 11px;
114
+ color: var(--md-sys-color-on-surface-variant);
115
+ writing-mode: vertical-rl;
116
+ text-orientation: mixed;
117
+ min-height: 120px;
118
+ display: flex;
119
+ align-items: center;
120
+ justify-content: center;
121
+ }
122
+
123
+ td.editable-cell {
124
+ cursor: pointer;
125
+ background: var(--md-sys-color-primary-container);
126
+ color: var(--md-sys-color-on-primary-container);
127
+ }
128
+
129
+ td.editable-cell:hover {
130
+ background: var(--md-sys-color-primary-container-high);
131
+ }
132
+
133
+ td.highlighted-cell {
134
+ background: var(--md-sys-color-secondary-container);
135
+ color: var(--md-sys-color-on-secondary-container);
136
+ font-weight: 500;
137
+ }
138
+
139
+ td.highlighted-cell:hover {
140
+ background: var(--md-sys-color-secondary-container-high);
141
+ }
142
+
143
+ td.disabled-cell {
144
+ background: var(--md-sys-color-surface-container);
145
+ color: var(--md-sys-color-on-surface-variant);
146
+ cursor: not-allowed;
147
+ }
148
+
149
+ .value-input {
150
+ width: 100%;
151
+ text-align: center;
152
+ border: none;
153
+ background: transparent;
154
+ color: inherit;
155
+ font-size: 12px;
156
+ padding: 2px;
157
+ }
158
+
159
+ .value-input:focus {
160
+ outline: 2px solid var(--md-sys-color-primary);
161
+ border-radius: 4px;
162
+ }
163
+
164
+ .period-type-badge {
165
+ font-size: 10px;
166
+ padding: 2px 6px;
167
+ border-radius: 4px;
168
+ background: var(--md-sys-color-tertiary-container);
169
+ color: var(--md-sys-color-on-tertiary-container);
170
+ margin-left: 8px;
171
+ }
172
+
173
+ .loading {
174
+ display: flex;
175
+ justify-content: center;
176
+ align-items: center;
177
+ height: 200px;
178
+ color: var(--md-sys-color-on-surface-variant);
179
+ }
180
+
181
+ .error {
182
+ color: var(--md-sys-color-error);
183
+ padding: 16px;
184
+ text-align: center;
185
+ }
186
+
187
+ .legend {
188
+ display: flex;
189
+ gap: 16px;
190
+ margin-top: 16px;
191
+ font-size: 12px;
192
+ }
193
+
194
+ .legend-item {
195
+ display: flex;
196
+ align-items: center;
197
+ gap: 4px;
198
+ }
199
+
200
+ .legend-color {
201
+ width: 16px;
202
+ height: 16px;
203
+ border-radius: 2px;
204
+ }
205
+ `
206
+ ]; }
207
+ get context() {
208
+ return {
209
+ title: i18next.t('title.kpi value editor'),
210
+ actions: [
211
+ {
212
+ title: i18next.t('button.save'),
213
+ action: this._saveValues.bind(this),
214
+ ...CommonButtonStyles.save
215
+ },
216
+ {
217
+ title: i18next.t('button.cancel'),
218
+ action: this._cancel.bind(this),
219
+ ...CommonButtonStyles.cancel
220
+ }
221
+ ]
222
+ };
223
+ }
224
+ render() {
225
+ if (this.loading) {
226
+ return html `
227
+ <div class="loading">
228
+ <md-icon>hourglass_empty</md-icon>
229
+ <span>데이터를 불러오는 중...</span>
230
+ </div>
231
+ `;
232
+ }
233
+ if (this.error) {
234
+ return html `
235
+ <div class="error">
236
+ <md-icon>error</md-icon>
237
+ <span>${this.error}</span>
238
+ </div>
239
+ `;
240
+ }
241
+ return html `
242
+ <div class="header">
243
+ <div class="controls">
244
+ <md-outlined-text-field
245
+ label="그룹"
246
+ .value=${this.group}
247
+ @input=${(e) => (this.group = e.target.value)}
248
+ style="width: 150px;"
249
+ ></md-outlined-text-field>
250
+
251
+ <md-outlined-text-field
252
+ label="시작일"
253
+ type="date"
254
+ .value=${this.startDate}
255
+ @input=${(e) => (this.startDate = e.target.value)}
256
+ style="width: 150px;"
257
+ ></md-outlined-text-field>
258
+
259
+ <md-outlined-text-field
260
+ label="종료일"
261
+ type="date"
262
+ .value=${this.endDate}
263
+ @input=${(e) => (this.endDate = e.target.value)}
264
+ style="width: 150px;"
265
+ ></md-outlined-text-field>
266
+
267
+ <md-elevated-button @click=${this._loadData}>
268
+ <md-icon slot="icon">refresh</md-icon>
269
+ 새로고침
270
+ </md-elevated-button>
271
+ </div>
272
+ </div>
273
+
274
+ <div class="table-container">
275
+ <!-- 디버깅 정보 -->
276
+ <table style="border-collapse: collapse; width: 100%;">
277
+ <thead>
278
+ <tr>
279
+ <th
280
+ style="border: 1px solid #ccc; padding: 8px; background: #f5f5f5; min-width: 200px; left: 0; top: 0;"
281
+ class="kpi-header"
282
+ >
283
+ KPI명
284
+ </th>
285
+ ${this.dates.map(date => html `
286
+ <th
287
+ style="border: 1px solid #ccc; padding: 8px; background: #f5f5f5; min-width: 80px; position: sticky; top: 0;"
288
+ >
289
+ ${this._formatDate(date)}
290
+ </th>
291
+ `)}
292
+ </tr>
293
+ </thead>
294
+ <tbody>
295
+ ${this.kpis.map(kpi => html `
296
+ <tr>
297
+ <td
298
+ style="border: 1px solid #ccc; padding: 8px; background: #fff; min-width: 200px;"
299
+ class="kpi-name"
300
+ >
301
+ ${kpi.kpiName}
302
+ <span
303
+ style="font-size: 10px; padding: 2px 6px; background: #e0e0e0; border-radius: 4px; margin-left: 8px;"
304
+ >${kpi.periodType}</span
305
+ >
306
+ </td>
307
+ ${this.dates.map(date => html `
308
+ <td
309
+ style="border: 1px solid #ccc; padding: 8px; background: #e3f2fd; text-align: center; min-width: 80px; cursor: pointer;"
310
+ @click=${() => this._startEdit(kpi.kpiId, date)}
311
+ >
312
+ ${this._renderCellContent(kpi, date)}
313
+ </td>
314
+ `)}
315
+ </tr>
316
+ `)}
317
+ </tbody>
318
+ </table>
319
+ </div>
320
+
321
+ <div class="legend">
322
+ <div class="legend-item">
323
+ <div class="legend-color" style="background: var(--md-sys-color-primary-container);"></div>
324
+ <span>편집 가능</span>
325
+ </div>
326
+ <div class="legend-item">
327
+ <div class="legend-color" style="background: var(--md-sys-color-secondary-container);"></div>
328
+ <span>하이라이트 (PeriodType에 따라)</span>
329
+ </div>
330
+ <div class="legend-item">
331
+ <div class="legend-color" style="background: var(--md-sys-color-surface-container);"></div>
332
+ <span>편집 불가</span>
333
+ </div>
334
+ </div>
335
+ `;
336
+ }
337
+ _renderKpiCells(kpi) {
338
+ const cells = this._getCellsForKpi(kpi);
339
+ return cells.map(cell => {
340
+ const isEditing = this.editingCell?.kpiId === kpi.kpiId && this.editingCell?.date === cell.date;
341
+ const cellClass = this._getCellClass(cell);
342
+ return html `
343
+ <div class="cell ${cellClass}" @click=${() => this._startEdit(kpi.kpiId, cell.date)}>
344
+ ${isEditing
345
+ ? html `
346
+ <input
347
+ class="value-input"
348
+ type="number"
349
+ step="0.01"
350
+ .value=${(cell.value || '').toString()}
351
+ @blur=${(e) => this._finishEdit(kpi.kpiId, cell.date, parseFloat(e.target.value) || 0)}
352
+ @keydown=${(e) => e.key === 'Enter' && e.target.blur()}
353
+ autofocus
354
+ />
355
+ `
356
+ : html ` <span>${cell.value !== null ? cell.value.toLocaleString() : '-'}</span> `}
357
+ </div>
358
+ `;
359
+ });
360
+ }
361
+ _getCellsForKpi(kpi) {
362
+ const cells = [];
363
+ const periodType = kpi.periodType;
364
+ this.dates.forEach(date => {
365
+ const isEditable = this._isDateEditableForPeriodType(date, periodType);
366
+ const isHighlighted = this._isDateHighlightedForPeriodType(date, periodType);
367
+ cells.push({
368
+ date,
369
+ value: kpi.values[date]?.value || null,
370
+ score: kpi.values[date]?.score,
371
+ isEditable,
372
+ isHighlighted
373
+ });
374
+ });
375
+ return cells;
376
+ }
377
+ _isDateEditableForPeriodType(date, periodType) {
378
+ const targetDate = new Date(date);
379
+ switch (periodType) {
380
+ case 'DAY':
381
+ return true; // 모든 날짜 편집 가능
382
+ case 'WEEK':
383
+ // 해당 주의 첫 번째 날짜만 편집 가능
384
+ const weekStart = new Date(targetDate);
385
+ weekStart.setDate(targetDate.getDate() - targetDate.getDay());
386
+ return date === weekStart.toISOString().split('T')[0];
387
+ case 'MONTH':
388
+ // 해당 월의 첫 번째 날짜만 편집 가능
389
+ return targetDate.getDate() === 1;
390
+ case 'QUARTER':
391
+ // 해당 분기의 첫 번째 날짜만 편집 가능
392
+ const quarterStart = new Date(targetDate.getFullYear(), Math.floor(targetDate.getMonth() / 3) * 3, 1);
393
+ return date === quarterStart.toISOString().split('T')[0];
394
+ case 'YEAR':
395
+ // 해당 연도의 첫 번째 날짜만 편집 가능
396
+ const yearStart = new Date(targetDate.getFullYear(), 0, 1);
397
+ return date === yearStart.toISOString().split('T')[0];
398
+ default:
399
+ return true;
400
+ }
401
+ }
402
+ _isDateHighlightedForPeriodType(date, periodType) {
403
+ const targetDate = new Date(date);
404
+ switch (periodType) {
405
+ case 'DAY':
406
+ return false; // 하이라이트 없음
407
+ case 'WEEK':
408
+ // 해당 주의 모든 날짜 하이라이트
409
+ const weekStart = new Date(targetDate);
410
+ weekStart.setDate(targetDate.getDate() - targetDate.getDay());
411
+ const weekEnd = new Date(weekStart);
412
+ weekEnd.setDate(weekStart.getDate() + 6);
413
+ return date >= weekStart.toISOString().split('T')[0] && date <= weekEnd.toISOString().split('T')[0];
414
+ case 'MONTH':
415
+ // 해당 월의 모든 날짜 하이라이트
416
+ const monthStart = new Date(targetDate.getFullYear(), targetDate.getMonth(), 1);
417
+ const monthEnd = new Date(targetDate.getFullYear(), targetDate.getMonth() + 1, 0);
418
+ return date >= monthStart.toISOString().split('T')[0] && date <= monthEnd.toISOString().split('T')[0];
419
+ case 'QUARTER':
420
+ // 해당 분기의 모든 날짜 하이라이트
421
+ const quarter = Math.floor(targetDate.getMonth() / 3);
422
+ const quarterStart = new Date(targetDate.getFullYear(), quarter * 3, 1);
423
+ const quarterEnd = new Date(targetDate.getFullYear(), (quarter + 1) * 3, 0);
424
+ return date >= quarterStart.toISOString().split('T')[0] && date <= quarterEnd.toISOString().split('T')[0];
425
+ case 'YEAR':
426
+ // 해당 연도의 모든 날짜 하이라이트
427
+ const yearStart = new Date(targetDate.getFullYear(), 0, 1);
428
+ const yearEnd = new Date(targetDate.getFullYear(), 11, 31);
429
+ return date >= yearStart.toISOString().split('T')[0] && date <= yearEnd.toISOString().split('T')[0];
430
+ default:
431
+ return false;
432
+ }
433
+ }
434
+ _getCellClass(cell) {
435
+ if (!cell.isEditable) {
436
+ return 'disabled-cell';
437
+ }
438
+ if (cell.isHighlighted) {
439
+ return 'highlighted-cell';
440
+ }
441
+ if (cell.isEditable) {
442
+ return 'editable-cell';
443
+ }
444
+ return '';
445
+ }
446
+ _formatDate(date) {
447
+ const d = new Date(date);
448
+ return d.toLocaleDateString('ko-KR', { month: 'numeric', day: 'numeric' });
449
+ }
450
+ _startEdit(kpiId, date) {
451
+ this.editingCell = { kpiId, date };
452
+ }
453
+ _finishEdit(kpiId, date, value) {
454
+ const kpi = this.kpis.find(k => k.kpiId === kpiId);
455
+ if (kpi) {
456
+ if (!kpi.values[date]) {
457
+ kpi.values[date] = { value: 0, isDirty: false };
458
+ }
459
+ // 값이 변경되었는지 확인
460
+ const originalValue = this._findExistingValue(kpiId, date)?.value;
461
+ const isChanged = originalValue !== value;
462
+ kpi.values[date].value = value;
463
+ kpi.values[date].isDirty = isChanged;
464
+ // score는 저장 시 서버에서 계산되므로 여기서는 제거
465
+ delete kpi.values[date].score;
466
+ }
467
+ this.editingCell = null;
468
+ }
469
+ _renderCellContent(kpi, date) {
470
+ const isEditing = this.editingCell?.kpiId === kpi.kpiId && this.editingCell?.date === date;
471
+ const value = kpi.values[date]?.value;
472
+ const score = kpi.values[date]?.score;
473
+ if (isEditing) {
474
+ return html `
475
+ <input
476
+ type="number"
477
+ step="0.01"
478
+ .value=${(value || '').toString()}
479
+ @blur=${(e) => this._finishEdit(kpi.kpiId, date, parseFloat(e.target.value) || 0)}
480
+ @keydown=${(e) => e.key === 'Enter' && e.target.blur()}
481
+ style="width: 100%; text-align: center; border: none; background: transparent;"
482
+ autofocus
483
+ />
484
+ `;
485
+ }
486
+ return html `
487
+ <div style="display: flex; flex-direction: column; gap: 2px;">
488
+ <span style="font-weight: 500; color: ${value !== null && value !== undefined ? 'inherit' : '#999'};">
489
+ ${value !== null && value !== undefined ? value.toLocaleString() : '클릭하여 입력'}
490
+ </span>
491
+ ${score !== null && score !== undefined
492
+ ? html `<span style="font-size: 10px; color: #666;">Score: ${score.toFixed(3)}</span>`
493
+ : ''}
494
+ </div>
495
+ `;
496
+ }
497
+ _calculateScore(value, periodType) {
498
+ // 간단한 score 계산 로직 (0-1 범위)
499
+ // 실제로는 KPI의 formula나 기준값을 사용해야 함
500
+ if (value <= 0)
501
+ return 0;
502
+ if (value >= 1000)
503
+ return 1;
504
+ return Math.min(value / 1000, 1);
505
+ }
506
+ async _loadData() {
507
+ if (!this.startDate || !this.endDate) {
508
+ this.error = '시작일, 종료일을 모두 입력해주세요.';
509
+ return;
510
+ }
511
+ this.loading = true;
512
+ this.error = '';
513
+ try {
514
+ // KPI 목록 조회
515
+ const kpisResponse = await client.query({
516
+ query: gql `
517
+ query ($filters: [Filter!]) {
518
+ kpis(filters: $filters) {
519
+ items {
520
+ id
521
+ name
522
+ periodType
523
+ active
524
+ }
525
+ total
526
+ }
527
+ }
528
+ `,
529
+ variables: {
530
+ filters: [{ name: 'active', operator: 'eq', value: true }]
531
+ }
532
+ });
533
+ // KPI Value 데이터 조회
534
+ const valuesResponse = await client.query({
535
+ query: gql `
536
+ query ($filters: [Filter!]) {
537
+ kpiValues(filters: $filters) {
538
+ items {
539
+ id
540
+ kpiId
541
+ valueDate
542
+ value
543
+ score
544
+ group
545
+ }
546
+ total
547
+ }
548
+ }
549
+ `,
550
+ variables: {
551
+ filters: [
552
+ ...(this.group ? [{ name: 'group', operator: 'eq', value: this.group }] : []),
553
+ { name: 'valueDate', operator: 'between', value: [this.startDate, this.endDate] }
554
+ ]
555
+ }
556
+ });
557
+ // 날짜 배열 생성
558
+ this.dates = this._generateDateArray(this.startDate, this.endDate);
559
+ console.log('시작일:', this.startDate);
560
+ console.log('종료일:', this.endDate);
561
+ console.log('생성된 날짜 배열:', this.dates);
562
+ // KPI 목록을 기준으로 데이터 구성
563
+ this.kpis = kpisResponse.data.kpis.items.map((kpi) => ({
564
+ kpiId: kpi.id,
565
+ kpiName: kpi.name,
566
+ periodType: kpi.periodType,
567
+ values: {}
568
+ }));
569
+ // 기존 KPI Value 데이터 저장
570
+ this._existingValues = valuesResponse.data.kpiValues.items;
571
+ // KPI Value 데이터를 해당 KPI에 매핑
572
+ valuesResponse.data.kpiValues.items.forEach((value) => {
573
+ const kpi = this.kpis.find(k => k.kpiId === value.kpiId);
574
+ if (kpi) {
575
+ kpi.values[value.valueDate] = {
576
+ value: value.value,
577
+ score: value.score,
578
+ isDirty: false
579
+ };
580
+ }
581
+ });
582
+ // KPI Value가 없는 KPI도 빈 값으로 초기화
583
+ this.kpis.forEach(kpi => {
584
+ this.dates.forEach(date => {
585
+ if (!kpi.values[date]) {
586
+ kpi.values[date] = { value: null, score: undefined, isDirty: false };
587
+ }
588
+ });
589
+ });
590
+ // 디버깅 정보 출력
591
+ console.log('KPI 총 개수:', kpisResponse.data.kpis.total);
592
+ console.log('KPI 목록:', kpisResponse.data.kpis.items);
593
+ console.log('KPI Value 총 개수:', valuesResponse.data.kpiValues.total);
594
+ console.log('KPI Value 목록:', valuesResponse.data.kpiValues.items);
595
+ console.log('구성된 KPI 데이터:', this.kpis);
596
+ console.log('날짜 배열:', this.dates);
597
+ // 강제 업데이트
598
+ this.requestUpdate();
599
+ }
600
+ catch (error) {
601
+ console.error('데이터 로드 중 오류:', error);
602
+ this.error = '데이터를 불러오는 중 오류가 발생했습니다.';
603
+ }
604
+ finally {
605
+ this.loading = false;
606
+ }
607
+ }
608
+ _generateDateArray(startDate, endDate) {
609
+ const dates = [];
610
+ const start = new Date(startDate);
611
+ const end = new Date(endDate);
612
+ for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
613
+ dates.push(d.toLocaleDateString('sv-SE'));
614
+ }
615
+ return dates;
616
+ }
617
+ async _saveValues() {
618
+ try {
619
+ const patches = [];
620
+ this.kpis.forEach(kpi => {
621
+ Object.entries(kpi.values).forEach(([date, data]) => {
622
+ // dirty 상태인 데이터만 처리
623
+ if (data.isDirty && data.value !== null && data.value !== undefined) {
624
+ const existingValue = this._findExistingValue(kpi.kpiId, date);
625
+ if (existingValue) {
626
+ // 기존 데이터 업데이트
627
+ patches.push({
628
+ id: existingValue.id,
629
+ value: data.value,
630
+ group: this.group,
631
+ cuFlag: 'M'
632
+ });
633
+ }
634
+ else {
635
+ // 새로운 데이터 생성
636
+ patches.push({
637
+ kpiId: kpi.kpiId,
638
+ valueDate: date,
639
+ value: data.value,
640
+ group: this.group,
641
+ cuFlag: '+'
642
+ });
643
+ }
644
+ }
645
+ });
646
+ });
647
+ if (patches.length === 0) {
648
+ notify({ message: '저장할 데이터가 없습니다.' });
649
+ return;
650
+ }
651
+ // 단일 mutation으로 생성과 업데이트 처리
652
+ const response = await client.mutate({
653
+ mutation: gql `
654
+ mutation ($patches: [KpiValuePatch!]!) {
655
+ updateMultipleKpiValue(patches: $patches) {
656
+ id
657
+ value
658
+ score
659
+ valueDate
660
+ kpiId
661
+ }
662
+ }
663
+ `,
664
+ variables: { patches }
665
+ });
666
+ if (!response.errors) {
667
+ // 서버에서 계산된 score로 UI 업데이트
668
+ response.data.updateMultipleKpiValue.forEach((savedValue) => {
669
+ const kpi = this.kpis.find(k => k.kpiId === savedValue.kpiId);
670
+ if (kpi && kpi.values[savedValue.valueDate]) {
671
+ kpi.values[savedValue.valueDate].score = savedValue.score;
672
+ kpi.values[savedValue.valueDate].isDirty = false; // 저장 후 dirty 상태 해제
673
+ }
674
+ });
675
+ notify({ message: 'KPI 값이 성공적으로 저장되었습니다.' });
676
+ this.requestUpdate(); // UI 강제 업데이트
677
+ }
678
+ }
679
+ catch (error) {
680
+ console.error('저장 중 오류:', error);
681
+ notify({ message: '저장 중 오류가 발생했습니다.' });
682
+ }
683
+ }
684
+ _findExistingValue(kpiId, date) {
685
+ // 기존 로드된 KPI Value 데이터에서 찾기
686
+ return this._existingValues?.find((v) => v.kpiId === kpiId && v.valueDate === date);
687
+ }
688
+ _cancel() {
689
+ // 편집 취소 로직
690
+ this.editingCell = null;
691
+ this._loadData(); // 원본 데이터로 복원
692
+ }
693
+ async pageInitialized(lifecycle) {
694
+ // 기본값 설정 - 지난 1개월
695
+ if (!this.startDate) {
696
+ const today = new Date();
697
+ const oneMonthAgo = new Date(today.getFullYear(), today.getMonth() - 1, today.getDate());
698
+ this.startDate = oneMonthAgo.toLocaleDateString('sv-SE');
699
+ }
700
+ if (!this.endDate) {
701
+ const today = new Date();
702
+ this.endDate = today.toLocaleDateString('sv-SE');
703
+ }
704
+ // 페이지 초기화 시 자동으로 데이터 로드
705
+ await this._loadData();
706
+ }
707
+ };
708
+ __decorate([
709
+ property({ type: String }),
710
+ __metadata("design:type", String)
711
+ ], KpiValueEditorPage.prototype, "group", void 0);
712
+ __decorate([
713
+ property({ type: String }),
714
+ __metadata("design:type", String)
715
+ ], KpiValueEditorPage.prototype, "startDate", void 0);
716
+ __decorate([
717
+ property({ type: String }),
718
+ __metadata("design:type", String)
719
+ ], KpiValueEditorPage.prototype, "endDate", void 0);
720
+ __decorate([
721
+ state(),
722
+ __metadata("design:type", Array)
723
+ ], KpiValueEditorPage.prototype, "kpis", void 0);
724
+ __decorate([
725
+ state(),
726
+ __metadata("design:type", Array)
727
+ ], KpiValueEditorPage.prototype, "dates", void 0);
728
+ __decorate([
729
+ state(),
730
+ __metadata("design:type", Boolean)
731
+ ], KpiValueEditorPage.prototype, "loading", void 0);
732
+ __decorate([
733
+ state(),
734
+ __metadata("design:type", String)
735
+ ], KpiValueEditorPage.prototype, "error", void 0);
736
+ __decorate([
737
+ state(),
738
+ __metadata("design:type", Object)
739
+ ], KpiValueEditorPage.prototype, "editingCell", void 0);
740
+ __decorate([
741
+ state(),
742
+ __metadata("design:type", Array)
743
+ ], KpiValueEditorPage.prototype, "_existingValues", void 0);
744
+ KpiValueEditorPage = __decorate([
745
+ customElement('kpi-value-editor-page')
746
+ ], KpiValueEditorPage);
747
+ export { KpiValueEditorPage };
748
+ //# sourceMappingURL=kpi-value-editor-page.js.map