@things-factory/kpi 9.1.19 → 10.0.0-beta.2

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