@dssp/dkpi 1.0.0-alpha.39 → 1.0.0-alpha.41

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