@things-factory/kpi 9.0.23 → 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 (76) hide show
  1. package/client/pages/kpi/kpi-viz-editor.ts +1 -1
  2. package/client/pages/kpi-category-value/kpi-category-value-list-page.ts +404 -0
  3. package/client/pages/kpi-metric-value/kpi-metric-value-editor-page.ts +763 -0
  4. package/client/pages/kpi-metric-value/kpi-metric-value-list-page.ts +12 -0
  5. package/client/pages/kpi-value/kpi-value-editor-page.ts +774 -0
  6. package/client/pages/kpi-value/kpi-value-list-page.ts +13 -0
  7. package/client/route.ts +16 -0
  8. package/dist-client/pages/kpi/kpi-viz-editor.d.ts +0 -1
  9. package/dist-client/pages/kpi/kpi-viz-editor.js +1 -1
  10. package/dist-client/pages/kpi/kpi-viz-editor.js.map +1 -1
  11. package/dist-client/pages/kpi-category-value/kpi-category-value-list-page.d.ts +63 -0
  12. package/dist-client/pages/kpi-category-value/kpi-category-value-list-page.js +393 -0
  13. package/dist-client/pages/kpi-category-value/kpi-category-value-list-page.js.map +1 -0
  14. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.d.ts +58 -0
  15. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js +736 -0
  16. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js.map +1 -0
  17. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.d.ts +1 -0
  18. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js +11 -0
  19. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js.map +1 -1
  20. package/dist-client/pages/kpi-value/kpi-value-editor-page.d.ts +55 -0
  21. package/dist-client/pages/kpi-value/kpi-value-editor-page.js +748 -0
  22. package/dist-client/pages/kpi-value/kpi-value-editor-page.js.map +1 -0
  23. package/dist-client/pages/kpi-value/kpi-value-list-page.d.ts +9 -2
  24. package/dist-client/pages/kpi-value/kpi-value-list-page.js +12 -0
  25. package/dist-client/pages/kpi-value/kpi-value-list-page.js.map +1 -1
  26. package/dist-client/route.d.ts +1 -1
  27. package/dist-client/route.js +12 -0
  28. package/dist-client/route.js.map +1 -1
  29. package/dist-client/tsconfig.tsbuildinfo +1 -1
  30. package/dist-server/service/index.d.ts +4 -2
  31. package/dist-server/service/index.js +5 -0
  32. package/dist-server/service/index.js.map +1 -1
  33. package/dist-server/service/kpi-category/kpi-category-mutation.d.ts +2 -3
  34. package/dist-server/service/kpi-category/kpi-category-mutation.js +149 -78
  35. package/dist-server/service/kpi-category/kpi-category-mutation.js.map +1 -1
  36. package/dist-server/service/kpi-category/kpi-category-query.d.ts +1 -9
  37. package/dist-server/service/kpi-category/kpi-category-query.js +3 -165
  38. package/dist-server/service/kpi-category/kpi-category-query.js.map +1 -1
  39. package/dist-server/service/kpi-category-value/index.d.ts +6 -0
  40. package/dist-server/service/kpi-category-value/index.js +10 -0
  41. package/dist-server/service/kpi-category-value/index.js.map +1 -0
  42. package/dist-server/service/kpi-category-value/kpi-category-value-mutation.d.ts +8 -0
  43. package/dist-server/service/kpi-category-value/kpi-category-value-mutation.js +102 -0
  44. package/dist-server/service/kpi-category-value/kpi-category-value-mutation.js.map +1 -0
  45. package/dist-server/service/kpi-category-value/kpi-category-value-query.d.ts +13 -0
  46. package/dist-server/service/kpi-category-value/kpi-category-value-query.js +91 -0
  47. package/dist-server/service/kpi-category-value/kpi-category-value-query.js.map +1 -0
  48. package/dist-server/service/kpi-category-value/kpi-category-value-type.d.ts +19 -0
  49. package/dist-server/service/kpi-category-value/kpi-category-value-type.js +73 -0
  50. package/dist-server/service/kpi-category-value/kpi-category-value-type.js.map +1 -0
  51. package/dist-server/service/kpi-category-value/kpi-category-value.d.ts +19 -0
  52. package/dist-server/service/kpi-category-value/kpi-category-value.js +91 -0
  53. package/dist-server/service/kpi-category-value/kpi-category-value.js.map +1 -0
  54. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js +20 -0
  55. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js.map +1 -1
  56. package/dist-server/service/kpi-value/kpi-value-mutation.d.ts +1 -0
  57. package/dist-server/service/kpi-value/kpi-value-mutation.js +60 -0
  58. package/dist-server/service/kpi-value/kpi-value-mutation.js.map +1 -1
  59. package/dist-server/tsconfig.tsbuildinfo +1 -1
  60. package/package.json +5 -5
  61. package/server/service/index.ts +5 -0
  62. package/server/service/kpi-category/kpi-category-mutation.ts +154 -81
  63. package/server/service/kpi-category/kpi-category-query.ts +1 -155
  64. package/server/service/kpi-category-value/index.ts +7 -0
  65. package/server/service/kpi-category-value/kpi-category-value-mutation.ts +88 -0
  66. package/server/service/kpi-category-value/kpi-category-value-query.ts +62 -0
  67. package/server/service/kpi-category-value/kpi-category-value-type.ts +48 -0
  68. package/server/service/kpi-category-value/kpi-category-value.ts +79 -0
  69. package/server/service/kpi-metric-value/kpi-metric-value-mutation.ts +28 -0
  70. package/server/service/kpi-value/kpi-value-mutation.ts +66 -0
  71. package/things-factory.config.js +3 -0
  72. package/translations/en.json +3 -0
  73. package/translations/ja.json +3 -0
  74. package/translations/ko.json +3 -0
  75. package/translations/ms.json +3 -0
  76. package/translations/zh.json +3 -0
@@ -0,0 +1,736 @@
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 KpiMetricValueEditorPage = class KpiMetricValueEditorPage extends connect(store)(localize(i18next)(ScopedElementsMixin(PageView))) {
16
+ constructor() {
17
+ super(...arguments);
18
+ this.group = '';
19
+ this.startDate = '';
20
+ this.endDate = '';
21
+ this.metrics = [];
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
+ .metric-header {
88
+ position: sticky;
89
+ left: 0;
90
+ top: 0;
91
+ z-index: 3;
92
+ }
93
+
94
+ .metric-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 metric 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
+ <table style="border-collapse: collapse; width: 100%;">
276
+ <thead>
277
+ <tr>
278
+ <th
279
+ style="border: 1px solid #ccc; padding: 8px; background: #f5f5f5; min-width: 200px; left: 0; top: 0;"
280
+ class="metric-header"
281
+ >
282
+ Metric명
283
+ </th>
284
+ ${this.dates.map(date => html `
285
+ <th
286
+ style="border: 1px solid #ccc; padding: 8px; background: #f5f5f5; min-width: 80px; position: sticky; top: 0;"
287
+ >
288
+ ${this._formatDate(date)}
289
+ </th>
290
+ `)}
291
+ </tr>
292
+ </thead>
293
+ <tbody>
294
+ ${this.metrics.map(metric => html `
295
+ <tr>
296
+ <td
297
+ style="border: 1px solid #ccc; padding: 8px; background: #fff; min-width: 200px;"
298
+ class="metric-name"
299
+ >
300
+ ${metric.metricName}
301
+ <span
302
+ style="font-size: 10px; padding: 2px 6px; background: #e0e0e0; border-radius: 4px; margin-left: 8px;"
303
+ >${metric.periodType}</span
304
+ >
305
+ </td>
306
+ ${this.dates.map(date => html `
307
+ <td
308
+ style="border: 1px solid #ccc; padding: 8px; background: #e3f2fd; text-align: center; min-width: 80px; cursor: pointer;"
309
+ @click=${() => this._startEdit(metric.metricId, date)}
310
+ >
311
+ ${this._renderCellContent(metric, date)}
312
+ </td>
313
+ `)}
314
+ </tr>
315
+ `)}
316
+ </tbody>
317
+ </table>
318
+ </div>
319
+
320
+ <div class="legend">
321
+ <div class="legend-item">
322
+ <div class="legend-color" style="background: var(--md-sys-color-primary-container);"></div>
323
+ <span>편집 가능</span>
324
+ </div>
325
+ <div class="legend-item">
326
+ <div class="legend-color" style="background: var(--md-sys-color-secondary-container);"></div>
327
+ <span>하이라이트 (PeriodType에 따라)</span>
328
+ </div>
329
+ <div class="legend-item">
330
+ <div class="legend-color" style="background: var(--md-sys-color-surface-container);"></div>
331
+ <span>편집 불가</span>
332
+ </div>
333
+ </div>
334
+ `;
335
+ }
336
+ _renderCellContent(metric, date) {
337
+ const isEditing = this.editingCell?.metricId === metric.metricId && this.editingCell?.date === date;
338
+ // periodType에 따라 값을 가져오는 방식 결정
339
+ let value = null;
340
+ if (metric.periodType === 'DAY') {
341
+ // DAY인 경우 해당 날짜의 값을 사용
342
+ value = metric.values[date]?.value;
343
+ }
344
+ else if (metric.periodType === 'WEEK') {
345
+ // WEEK인 경우 해당 주의 월요일의 값을 사용
346
+ const weekKey = this._getWeekKey(date);
347
+ value = metric.values[weekKey]?.value;
348
+ }
349
+ else if (metric.periodType === 'MONTH') {
350
+ // MONTH인 경우 해당 월의 값을 사용
351
+ const monthKey = this._getMonthKey(date);
352
+ value = metric.values[monthKey]?.value;
353
+ }
354
+ else if (metric.periodType === 'QUARTER') {
355
+ // QUARTER인 경우 해당 분기의 값을 사용
356
+ const quarterKey = this._getQuarterKey(date);
357
+ value = metric.values[quarterKey]?.value;
358
+ }
359
+ else if (metric.periodType === 'YEAR') {
360
+ // YEAR인 경우 해당 연도의 값을 사용
361
+ const yearKey = this._getYearKey(date);
362
+ value = metric.values[yearKey]?.value;
363
+ }
364
+ else {
365
+ // 기본값: DAY와 동일
366
+ value = metric.values[date]?.value;
367
+ }
368
+ if (isEditing) {
369
+ console.log('Rendering input for editing, value:', value);
370
+ return html `
371
+ <input
372
+ type="number"
373
+ step="0.01"
374
+ .value=${(value || '').toString()}
375
+ @blur=${(e) => this._finishEdit(metric.metricId, date, parseFloat(e.target.value) || 0)}
376
+ @keydown=${(e) => e.key === 'Enter' && e.target.blur()}
377
+ style="width: 100%; text-align: center; border: none; background: transparent;"
378
+ autofocus
379
+ />
380
+ `;
381
+ }
382
+ return html `
383
+ <div style="display: flex; flex-direction: column; gap: 2px;">
384
+ <span style="font-weight: 500; color: ${value !== null && value !== undefined ? 'inherit' : '#999'};">
385
+ ${value !== null && value !== undefined ? value.toLocaleString() : '클릭하여 입력'}
386
+ </span>
387
+ </div>
388
+ `;
389
+ }
390
+ _formatDate(date) {
391
+ const d = new Date(date);
392
+ return d.toLocaleDateString('ko-KR', { month: 'numeric', day: 'numeric' });
393
+ }
394
+ _startEdit(metricId, date) {
395
+ this.editingCell = { metricId, date };
396
+ }
397
+ _finishEdit(metricId, date, value) {
398
+ const metric = this.metrics.find(m => m.metricId === metricId);
399
+ if (metric) {
400
+ // periodType에 따라 저장할 키 결정
401
+ let storageKey = date;
402
+ if (metric.periodType === 'WEEK') {
403
+ storageKey = this._getWeekKey(date);
404
+ }
405
+ else if (metric.periodType === 'MONTH') {
406
+ storageKey = this._getMonthKey(date);
407
+ }
408
+ else if (metric.periodType === 'QUARTER') {
409
+ storageKey = this._getQuarterKey(date);
410
+ }
411
+ else if (metric.periodType === 'YEAR') {
412
+ storageKey = this._getYearKey(date);
413
+ }
414
+ if (!metric.values[storageKey]) {
415
+ metric.values[storageKey] = { value: 0, isDirty: false };
416
+ }
417
+ // 값이 변경되었는지 확인
418
+ const originalValue = this._findExistingValue(metricId, storageKey)?.value;
419
+ const isChanged = originalValue !== value;
420
+ metric.values[storageKey].value = value;
421
+ metric.values[storageKey].isDirty = isChanged;
422
+ }
423
+ this.editingCell = null;
424
+ }
425
+ async _loadData() {
426
+ if (!this.startDate || !this.endDate) {
427
+ this.error = '시작일, 종료일을 모두 입력해주세요.';
428
+ return;
429
+ }
430
+ this.loading = true;
431
+ this.error = '';
432
+ try {
433
+ // KPI Metric 목록 조회
434
+ const metricsResponse = await client.query({
435
+ query: gql `
436
+ query ($filters: [Filter!]) {
437
+ kpiMetrics(filters: $filters) {
438
+ items {
439
+ id
440
+ name
441
+ periodType
442
+ active
443
+ }
444
+ total
445
+ }
446
+ }
447
+ `,
448
+ variables: {
449
+ filters: [{ name: 'active', operator: 'eq', value: true }]
450
+ }
451
+ });
452
+ // KPI Metric Value 데이터 조회
453
+ const valuesResponse = await client.query({
454
+ query: gql `
455
+ query ($filters: [Filter!]) {
456
+ kpiMetricValues(filters: $filters) {
457
+ items {
458
+ id
459
+ metricId
460
+ valueDate
461
+ value
462
+ group
463
+ }
464
+ total
465
+ }
466
+ }
467
+ `,
468
+ variables: {
469
+ filters: [
470
+ ...(this.group ? [{ name: 'group', operator: 'eq', value: this.group }] : []),
471
+ { name: 'valueDate', operator: 'between', value: [this.startDate, this.endDate] }
472
+ ]
473
+ }
474
+ });
475
+ // 날짜 배열 생성
476
+ this.dates = this._generateDateArray(this.startDate, this.endDate);
477
+ // KPI Metric 목록을 기준으로 데이터 구성
478
+ this.metrics = metricsResponse.data.kpiMetrics.items.map((metric) => ({
479
+ metricId: metric.id,
480
+ metricName: metric.name,
481
+ periodType: metric.periodType,
482
+ values: {}
483
+ }));
484
+ // 기존 KPI Metric Value 데이터 저장
485
+ this._existingValues = valuesResponse.data.kpiMetricValues.items;
486
+ // KPI Metric Value 데이터를 해당 Metric에 매핑
487
+ valuesResponse.data.kpiMetricValues.items.forEach((value) => {
488
+ const metric = this.metrics.find(m => m.metricId === value.metricId);
489
+ if (metric) {
490
+ metric.values[value.valueDate] = {
491
+ value: value.value,
492
+ isDirty: false
493
+ };
494
+ }
495
+ });
496
+ // KPI Metric Value가 없는 Metric도 빈 값으로 초기화
497
+ this.metrics.forEach(metric => {
498
+ this.dates.forEach(date => {
499
+ if (!metric.values[date]) {
500
+ metric.values[date] = { value: null, isDirty: false };
501
+ }
502
+ });
503
+ });
504
+ this.requestUpdate();
505
+ }
506
+ catch (error) {
507
+ console.error('데이터 로드 중 오류:', error);
508
+ this.error = '데이터를 불러오는 중 오류가 발생했습니다.';
509
+ }
510
+ finally {
511
+ this.loading = false;
512
+ }
513
+ }
514
+ _generateDateArray(startDate, endDate) {
515
+ const dates = [];
516
+ const start = new Date(startDate);
517
+ const end = new Date(endDate);
518
+ // 일별로 생성 (기본)
519
+ for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
520
+ dates.push(d.toLocaleDateString('sv-SE'));
521
+ }
522
+ return dates;
523
+ }
524
+ async _saveValues() {
525
+ try {
526
+ const patches = [];
527
+ this.metrics.forEach(metric => {
528
+ Object.entries(metric.values).forEach(([date, data]) => {
529
+ // dirty 상태인 데이터만 처리
530
+ if (data.isDirty && data.value !== null && data.value !== undefined) {
531
+ const existingValue = this._findExistingValue(metric.metricId, date);
532
+ if (existingValue) {
533
+ patches.push({
534
+ id: existingValue.id,
535
+ value: data.value,
536
+ group: this.group,
537
+ cuFlag: 'M'
538
+ });
539
+ }
540
+ else {
541
+ console.log('Creating new value for metric:', metric.metricName, 'periodType:', metric.periodType, 'date:', date);
542
+ const normalizedDate = this._normalizeDateByPeriodType(date, metric.periodType);
543
+ console.log('Normalized date:', normalizedDate);
544
+ patches.push({
545
+ metricId: metric.metricId,
546
+ valueDate: normalizedDate,
547
+ value: data.value,
548
+ group: this.group,
549
+ cuFlag: '+'
550
+ });
551
+ }
552
+ }
553
+ });
554
+ });
555
+ if (patches.length === 0) {
556
+ notify({ message: '저장할 데이터가 없습니다.' });
557
+ return;
558
+ }
559
+ // 디버깅: patches 내용 확인
560
+ console.log('Sending patches:', patches);
561
+ // 단일 mutation으로 생성과 업데이트 처리
562
+ const response = await client.mutate({
563
+ mutation: gql `
564
+ mutation ($patches: [KpiMetricValuePatch!]!) {
565
+ updateMultipleKpiMetricValue(patches: $patches) {
566
+ id
567
+ value
568
+ valueDate
569
+ metricId
570
+ }
571
+ }
572
+ `,
573
+ variables: { patches }
574
+ });
575
+ if (!response.errors) {
576
+ // 저장 후 dirty 상태 해제
577
+ response.data.updateMultipleKpiMetricValue.forEach((savedValue) => {
578
+ const metric = this.metrics.find(m => m.metricId === savedValue.metricId);
579
+ if (metric && metric.values[savedValue.valueDate]) {
580
+ metric.values[savedValue.valueDate].isDirty = false;
581
+ }
582
+ });
583
+ notify({ message: 'KPI Metric 값이 성공적으로 저장되었습니다.' });
584
+ this.requestUpdate();
585
+ }
586
+ }
587
+ catch (error) {
588
+ console.error('저장 중 오류:', error);
589
+ notify({ message: '저장 중 오류가 발생했습니다.' });
590
+ }
591
+ }
592
+ _findExistingValue(metricId, date) {
593
+ // 기존 로드된 KPI Metric Value 데이터에서 찾기
594
+ return this._existingValues?.find((v) => v.metricId === metricId && v.valueDate === date);
595
+ }
596
+ _getMonthKey(date) {
597
+ const dateObj = new Date(date);
598
+ return `${dateObj.getFullYear()}-${String(dateObj.getMonth() + 1).padStart(2, '0')}`;
599
+ }
600
+ _getQuarterKey(date) {
601
+ const dateObj = new Date(date);
602
+ const quarter = Math.floor(dateObj.getMonth() / 3) + 1;
603
+ return `${dateObj.getFullYear()}-Q${quarter}`;
604
+ }
605
+ _getYearKey(date) {
606
+ const dateObj = new Date(date);
607
+ return `${dateObj.getFullYear()}`;
608
+ }
609
+ _getWeekKey(date) {
610
+ const dateObj = new Date(date);
611
+ const dayOfWeek = dateObj.getDay();
612
+ const daysToMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
613
+ const monday = new Date(dateObj);
614
+ monday.setDate(dateObj.getDate() - daysToMonday);
615
+ return monday.toLocaleDateString('sv-SE');
616
+ }
617
+ _getMondayOfWeek(date) {
618
+ const dateObj = new Date(date);
619
+ const dayOfWeek = dateObj.getDay();
620
+ const daysToMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
621
+ const monday = new Date(dateObj);
622
+ monday.setDate(dateObj.getDate() - daysToMonday);
623
+ return monday.toLocaleDateString('sv-SE');
624
+ }
625
+ _getFirstDayOfMonth(date) {
626
+ const dateObj = new Date(date);
627
+ return new Date(dateObj.getFullYear(), dateObj.getMonth(), 1).toLocaleDateString('sv-SE');
628
+ }
629
+ _getFirstDayOfQuarter(date) {
630
+ const dateObj = new Date(date);
631
+ const quarter = Math.floor(dateObj.getMonth() / 3);
632
+ const firstMonthOfQuarter = quarter * 3;
633
+ return new Date(dateObj.getFullYear(), firstMonthOfQuarter, 1).toLocaleDateString('sv-SE');
634
+ }
635
+ _getFirstDayOfYear(date) {
636
+ const dateObj = new Date(date);
637
+ return new Date(dateObj.getFullYear(), 0, 1).toLocaleDateString('sv-SE');
638
+ }
639
+ _normalizeDateByPeriodType(date, periodType) {
640
+ console.log('Normalizing date:', date, 'for periodType:', periodType);
641
+ const dateObj = new Date(date);
642
+ switch (periodType) {
643
+ case 'DAY':
644
+ console.log('DAY - returning original date:', date);
645
+ return date; // 그대로 사용
646
+ case 'WEEK':
647
+ // 해당 주의 월요일로 설정
648
+ const dayOfWeek = dateObj.getDay();
649
+ const daysToMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1; // 일요일이면 6일 전, 아니면 dayOfWeek - 1
650
+ const monday = new Date(dateObj);
651
+ monday.setDate(dateObj.getDate() - daysToMonday);
652
+ const weeklyDate = monday.toLocaleDateString('sv-SE');
653
+ console.log('WEEK - returning monday:', weeklyDate);
654
+ return weeklyDate;
655
+ case 'MONTH':
656
+ // 년-월 형식으로 설정 (예: "2025-01")
657
+ const monthlyDate = `${dateObj.getFullYear()}-${String(dateObj.getMonth() + 1).padStart(2, '0')}`;
658
+ console.log('MONTH - returning year-month format:', monthlyDate);
659
+ return monthlyDate;
660
+ case 'QUARTER':
661
+ // 분기 형식으로 설정 (예: "2025-Q1")
662
+ const quarter = Math.floor(dateObj.getMonth() / 3) + 1;
663
+ const quarterlyDate = `${dateObj.getFullYear()}-Q${quarter}`;
664
+ console.log('QUARTER - returning quarter format:', quarterlyDate);
665
+ return quarterlyDate;
666
+ case 'YEAR':
667
+ // 년도 형식으로 설정 (예: "2025")
668
+ const yearlyDate = `${dateObj.getFullYear()}`;
669
+ console.log('YEAR - returning year format:', yearlyDate);
670
+ return yearlyDate;
671
+ default:
672
+ console.log('DEFAULT - returning original date:', date);
673
+ return date; // 기본값
674
+ }
675
+ }
676
+ _cancel() {
677
+ // 편집 취소 로직
678
+ this.editingCell = null;
679
+ this._loadData(); // 원본 데이터로 복원
680
+ }
681
+ async pageInitialized(lifecycle) {
682
+ // 기본값 설정 - 지난 1개월
683
+ if (!this.startDate) {
684
+ const today = new Date();
685
+ const oneMonthAgo = new Date(today.getFullYear(), today.getMonth() - 1, today.getDate());
686
+ this.startDate = oneMonthAgo.toLocaleDateString('sv-SE');
687
+ }
688
+ if (!this.endDate) {
689
+ const today = new Date();
690
+ this.endDate = today.toLocaleDateString('sv-SE');
691
+ }
692
+ // 페이지 초기화 시 자동으로 데이터 로드
693
+ await this._loadData();
694
+ }
695
+ };
696
+ __decorate([
697
+ property({ type: String }),
698
+ __metadata("design:type", String)
699
+ ], KpiMetricValueEditorPage.prototype, "group", void 0);
700
+ __decorate([
701
+ property({ type: String }),
702
+ __metadata("design:type", String)
703
+ ], KpiMetricValueEditorPage.prototype, "startDate", void 0);
704
+ __decorate([
705
+ property({ type: String }),
706
+ __metadata("design:type", String)
707
+ ], KpiMetricValueEditorPage.prototype, "endDate", void 0);
708
+ __decorate([
709
+ state(),
710
+ __metadata("design:type", Array)
711
+ ], KpiMetricValueEditorPage.prototype, "metrics", void 0);
712
+ __decorate([
713
+ state(),
714
+ __metadata("design:type", Array)
715
+ ], KpiMetricValueEditorPage.prototype, "dates", void 0);
716
+ __decorate([
717
+ state(),
718
+ __metadata("design:type", Boolean)
719
+ ], KpiMetricValueEditorPage.prototype, "loading", void 0);
720
+ __decorate([
721
+ state(),
722
+ __metadata("design:type", String)
723
+ ], KpiMetricValueEditorPage.prototype, "error", void 0);
724
+ __decorate([
725
+ state(),
726
+ __metadata("design:type", Object)
727
+ ], KpiMetricValueEditorPage.prototype, "editingCell", void 0);
728
+ __decorate([
729
+ state(),
730
+ __metadata("design:type", Array)
731
+ ], KpiMetricValueEditorPage.prototype, "_existingValues", void 0);
732
+ KpiMetricValueEditorPage = __decorate([
733
+ customElement('kpi-metric-value-editor-page')
734
+ ], KpiMetricValueEditorPage);
735
+ export { KpiMetricValueEditorPage };
736
+ //# sourceMappingURL=kpi-metric-value-editor-page.js.map