@dssp/dkpi 1.0.0-y.0 → 1.0.1

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 (213) hide show
  1. package/KPI-STATISTICS-SERVICE.md +233 -0
  2. package/_index.html +4 -9
  3. package/assets/helps/index.md +1 -1
  4. package/assets/images/project-image.png +0 -0
  5. package/assets/manifest.json +2 -2
  6. package/config/config.development.js +2 -1
  7. package/config/config.production.js +2 -1
  8. package/dist-client/bootstrap.d.ts +1 -0
  9. package/dist-client/bootstrap.js +15 -0
  10. package/dist-client/bootstrap.js.map +1 -1
  11. package/dist-client/components/kpi-2d-lookup-chart.d.ts +50 -0
  12. package/dist-client/components/kpi-2d-lookup-chart.js +263 -0
  13. package/dist-client/components/kpi-2d-lookup-chart.js.map +1 -0
  14. package/dist-client/components/kpi-boxplot-chart.d.ts +31 -0
  15. package/dist-client/components/kpi-boxplot-chart.js +306 -0
  16. package/dist-client/components/kpi-boxplot-chart.js.map +1 -0
  17. package/dist-client/components/kpi-lookup-chart.d.ts +58 -0
  18. package/dist-client/components/kpi-lookup-chart.js +464 -0
  19. package/dist-client/components/kpi-lookup-chart.js.map +1 -0
  20. package/dist-client/components/kpi-mini-trend-chart.d.ts +14 -0
  21. package/dist-client/components/kpi-mini-trend-chart.js +180 -0
  22. package/dist-client/components/kpi-mini-trend-chart.js.map +1 -0
  23. package/dist-client/components/kpi-radar-chart.d.ts +17 -0
  24. package/dist-client/components/kpi-radar-chart.js +259 -0
  25. package/dist-client/components/kpi-radar-chart.js.map +1 -0
  26. package/dist-client/components/kpi-single-boxplot-chart.d.ts +14 -2
  27. package/dist-client/components/kpi-single-boxplot-chart.js +237 -137
  28. package/dist-client/components/kpi-single-boxplot-chart.js.map +1 -1
  29. package/dist-client/components/kpi-step-lookup-chart.d.ts +33 -0
  30. package/dist-client/components/kpi-step-lookup-chart.js +181 -0
  31. package/dist-client/components/kpi-step-lookup-chart.js.map +1 -0
  32. package/dist-client/components/kpi-trend-chart.d.ts +25 -0
  33. package/dist-client/components/kpi-trend-chart.js +241 -0
  34. package/dist-client/components/kpi-trend-chart.js.map +1 -0
  35. package/dist-client/entries/auth/checkin.d.ts +38 -0
  36. package/dist-client/entries/auth/checkin.js +551 -0
  37. package/dist-client/entries/auth/checkin.js.map +1 -0
  38. package/dist-client/google-map/common-google-map.d.ts +35 -0
  39. package/dist-client/google-map/common-google-map.js +349 -0
  40. package/dist-client/google-map/common-google-map.js.map +1 -0
  41. package/dist-client/google-map/google-map-loader.d.ts +6 -0
  42. package/dist-client/google-map/google-map-loader.js +23 -0
  43. package/dist-client/google-map/google-map-loader.js.map +1 -0
  44. package/dist-client/pages/component/project-update-header.d.ts +1 -0
  45. package/dist-client/pages/component/project-update-header.js +127 -0
  46. package/dist-client/pages/component/project-update-header.js.map +1 -0
  47. package/dist-client/pages/kpi-admin/dssp-kpi-list-page.d.ts +22 -0
  48. package/dist-client/pages/kpi-admin/dssp-kpi-list-page.js +57 -0
  49. package/dist-client/pages/kpi-admin/dssp-kpi-list-page.js.map +1 -0
  50. package/dist-client/pages/kpi-admin/dssp-kpi-overview.d.ts +47 -0
  51. package/dist-client/pages/kpi-admin/dssp-kpi-overview.js +393 -0
  52. package/dist-client/pages/kpi-admin/dssp-kpi-overview.js.map +1 -0
  53. package/dist-client/pages/kpi-admin/kpi-grade-2d-editor.d.ts +20 -0
  54. package/dist-client/pages/kpi-admin/kpi-grade-2d-editor.js +445 -0
  55. package/dist-client/pages/kpi-admin/kpi-grade-2d-editor.js.map +1 -0
  56. package/dist-client/pages/kpi-admin/kpi-system-guide.d.ts +18 -0
  57. package/dist-client/pages/kpi-admin/kpi-system-guide.js +696 -0
  58. package/dist-client/pages/kpi-admin/kpi-system-guide.js.map +1 -0
  59. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.d.ts +18 -0
  60. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js +259 -0
  61. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js.map +1 -0
  62. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.d.ts +22 -0
  63. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js +346 -0
  64. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js.map +1 -0
  65. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.d.ts +26 -0
  66. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js +433 -0
  67. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js.map +1 -0
  68. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.d.ts +8 -0
  69. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js +78 -0
  70. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js.map +1 -0
  71. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.d.ts +46 -0
  72. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js +945 -0
  73. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js.map +1 -0
  74. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.d.ts +34 -0
  75. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js +238 -0
  76. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js.map +1 -0
  77. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.d.ts +37 -0
  78. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js +689 -0
  79. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js.map +1 -0
  80. package/dist-client/pages/kpi-dashboard/kpi-alert-panel.d.ts +18 -0
  81. package/dist-client/pages/kpi-dashboard/kpi-alert-panel.js +131 -0
  82. package/dist-client/pages/kpi-dashboard/kpi-alert-panel.js.map +1 -0
  83. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.d.ts +57 -0
  84. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +866 -0
  85. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -0
  86. package/dist-client/pages/kpi-dashboard/kpi-dashboard.d.ts +63 -0
  87. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js +1097 -0
  88. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js.map +1 -0
  89. package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.d.ts +12 -0
  90. package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.js +82 -0
  91. package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.js.map +1 -0
  92. package/dist-client/pages/kpi-dashboard/kpi-history-viewer.d.ts +11 -0
  93. package/dist-client/pages/kpi-dashboard/kpi-history-viewer.js +65 -0
  94. package/dist-client/pages/kpi-dashboard/kpi-history-viewer.js.map +1 -0
  95. package/dist-client/pages/kpi-dashboard/kpi-list-summary.d.ts +13 -0
  96. package/dist-client/pages/kpi-dashboard/kpi-list-summary.js +115 -0
  97. package/dist-client/pages/kpi-dashboard/kpi-list-summary.js.map +1 -0
  98. package/dist-client/pages/kpi-dashboard/kpi-performance-summary.d.ts +15 -0
  99. package/dist-client/pages/kpi-dashboard/kpi-performance-summary.js +147 -0
  100. package/dist-client/pages/kpi-dashboard/kpi-performance-summary.js.map +1 -0
  101. package/dist-client/pages/kpi-dashboard/kpi-value-entry.d.ts +7 -0
  102. package/dist-client/pages/kpi-dashboard/kpi-value-entry.js +86 -0
  103. package/dist-client/pages/kpi-dashboard/kpi-value-entry.js.map +1 -0
  104. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.d.ts +1 -2
  105. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js +1 -13
  106. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js.map +1 -1
  107. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.d.ts +1 -2
  108. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js +20 -11
  109. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js.map +1 -1
  110. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.d.ts +1 -2
  111. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js +1 -2
  112. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js.map +1 -1
  113. package/dist-client/pages/kpi-value/kpi-value-list-page.d.ts +1 -2
  114. package/dist-client/pages/kpi-value/kpi-value-list-page.js +22 -33
  115. package/dist-client/pages/kpi-value/kpi-value-list-page.js.map +1 -1
  116. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.d.ts +56 -0
  117. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js +609 -67
  118. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js.map +1 -1
  119. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.d.ts +15 -3
  120. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js +180 -65
  121. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js.map +1 -1
  122. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.d.ts +13 -4
  123. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js +213 -91
  124. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js.map +1 -1
  125. package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.d.ts +51 -0
  126. package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.js +693 -0
  127. package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.js.map +1 -0
  128. package/dist-client/pages/sv-project-complete.d.ts +12 -3
  129. package/dist-client/pages/sv-project-complete.js +175 -29
  130. package/dist-client/pages/sv-project-complete.js.map +1 -1
  131. package/dist-client/pages/sv-project-completed-list.d.ts +3 -0
  132. package/dist-client/pages/sv-project-completed-list.js +81 -22
  133. package/dist-client/pages/sv-project-completed-list.js.map +1 -1
  134. package/dist-client/pages/sv-project-detail.d.ts +60 -0
  135. package/dist-client/pages/sv-project-detail.js +1092 -85
  136. package/dist-client/pages/sv-project-detail.js.map +1 -1
  137. package/dist-client/pages/sv-project-list.d.ts +20 -2
  138. package/dist-client/pages/sv-project-list.js +180 -30
  139. package/dist-client/pages/sv-project-list.js.map +1 -1
  140. package/dist-client/pages/sv-project-update.d.ts +91 -0
  141. package/dist-client/pages/sv-project-update.js +1150 -0
  142. package/dist-client/pages/sv-project-update.js.map +1 -0
  143. package/dist-client/pages/sv-user-management.d.ts +1 -0
  144. package/dist-client/pages/sv-user-management.js +5 -0
  145. package/dist-client/pages/sv-user-management.js.map +1 -1
  146. package/dist-client/route.d.ts +1 -1
  147. package/dist-client/route.js +29 -2
  148. package/dist-client/route.js.map +1 -1
  149. package/dist-client/shared/complete-api.d.ts +45 -5
  150. package/dist-client/shared/complete-api.js +177 -28
  151. package/dist-client/shared/complete-api.js.map +1 -1
  152. package/dist-client/shared/func.d.ts +1 -1
  153. package/dist-client/shared/func.js.map +1 -1
  154. package/dist-client/shared/geo-group-mapping.d.ts +25 -0
  155. package/dist-client/shared/geo-group-mapping.js +189 -0
  156. package/dist-client/shared/geo-group-mapping.js.map +1 -0
  157. package/dist-client/shared/integration-fetch.d.ts +35 -0
  158. package/dist-client/shared/integration-fetch.js +53 -0
  159. package/dist-client/shared/integration-fetch.js.map +1 -0
  160. package/dist-client/shared/kpi-project-sync.d.ts +16 -0
  161. package/dist-client/shared/kpi-project-sync.js +129 -0
  162. package/dist-client/shared/kpi-project-sync.js.map +1 -0
  163. package/dist-client/tsconfig.tsbuildinfo +1 -1
  164. package/dist-client/viewparts/menu-tools.d.ts +11 -3
  165. package/dist-client/viewparts/menu-tools.js +126 -75
  166. package/dist-client/viewparts/menu-tools.js.map +1 -1
  167. package/dist-server/scripts/calculate-kpi-scores.js +65 -3
  168. package/dist-server/scripts/calculate-kpi-scores.js.map +1 -1
  169. package/dist-server/scripts/load-grade-data-migration.d.ts +4 -0
  170. package/dist-server/scripts/load-grade-data-migration.js +95 -10
  171. package/dist-server/scripts/load-grade-data-migration.js.map +1 -1
  172. package/dist-server/scripts/propagate-parent-kpi-values.d.ts +4 -0
  173. package/dist-server/scripts/propagate-parent-kpi-values.js +423 -77
  174. package/dist-server/scripts/propagate-parent-kpi-values.js.map +1 -1
  175. package/dist-server/scripts/recalculate-by-project-name.d.ts +2 -0
  176. package/dist-server/scripts/recalculate-by-project-name.js +72 -0
  177. package/dist-server/scripts/recalculate-by-project-name.js.map +1 -0
  178. package/dist-server/service/index.d.ts +1 -3
  179. package/dist-server/service/index.js +3 -4
  180. package/dist-server/service/index.js.map +1 -1
  181. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.d.ts +192 -1
  182. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js +993 -38
  183. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js.map +1 -1
  184. package/dist-server/service/kpi-metric-value/kpi-metric-value-query.d.ts +9 -1
  185. package/dist-server/service/kpi-metric-value/kpi-metric-value-query.js +33 -8
  186. package/dist-server/service/kpi-metric-value/kpi-metric-value-query.js.map +1 -1
  187. package/dist-server/service/kpi-stat/index.d.ts +4 -0
  188. package/dist-server/service/kpi-stat/index.js +8 -0
  189. package/dist-server/service/kpi-stat/index.js.map +1 -0
  190. package/dist-server/service/kpi-stat/kpi-stat-query.d.ts +13 -0
  191. package/dist-server/service/kpi-stat/kpi-stat-query.js +751 -0
  192. package/dist-server/service/kpi-stat/kpi-stat-query.js.map +1 -0
  193. package/dist-server/service/kpi-stat/kpi-stat-types.d.ts +32 -0
  194. package/dist-server/service/kpi-stat/kpi-stat-types.js +180 -0
  195. package/dist-server/service/kpi-stat/kpi-stat-types.js.map +1 -0
  196. package/dist-server/service/kpi-value/kpi-value-query.d.ts +2 -1
  197. package/dist-server/service/kpi-value/kpi-value-query.js +49 -6
  198. package/dist-server/service/kpi-value/kpi-value-query.js.map +1 -1
  199. package/dist-server/tsconfig.tsbuildinfo +1 -1
  200. package/kpi-module-service-tests.md +1286 -0
  201. package/kpi-module-test-report.md +676 -0
  202. package/kpi-module-unit-test-detailed-report.md +925 -0
  203. package/kpi-module-unit-tests-detailed.md +1452 -0
  204. package/openapi/unstable.yaml +2 -2
  205. package/package.json +58 -54
  206. package/recalculate-batch.sh +64 -0
  207. package/recalculate-projects-range.sh +98 -0
  208. package/schema.graphql +1159 -55
  209. package/things-factory.config.js +6 -1
  210. package/translations/en.json +5 -1
  211. package/translations/ko.json +5 -1
  212. package/views/auth-page.html +4 -5
  213. package/views/public/home.html +5 -6
@@ -1,13 +1,27 @@
1
1
  import { __decorate, __metadata } from "tslib";
2
2
  import { css, html, LitElement } from 'lit';
3
- import { customElement, property } from 'lit/decorators.js';
4
- import { client } from '@operato/graphql';
5
- import { gql } from '@apollo/client';
3
+ import { customElement, property, state } from 'lit/decorators.js';
4
+ import { getProject, updateProjectCompleteStep3, getKpiMetrics, getKpiMetricValues } from '../../shared/complete-api';
5
+ import { notify } from '@operato/layout';
6
+ import { hasPrivilege } from '@things-factory/auth-base/dist-client';
6
7
  let SvProjectCompleteTab3Upload = class SvProjectCompleteTab3Upload extends LitElement {
7
8
  constructor() {
8
9
  super(...arguments);
9
- this.data = [];
10
10
  this.project = {};
11
+ this.attachment = {};
12
+ this.pendingFile = null;
13
+ this.slpaData = [];
14
+ /** kpi:sentiment — Step3 보고서 첨부/저장 권한 */
15
+ this.canSave = false;
16
+ }
17
+ async connectedCallback() {
18
+ super.connectedCallback();
19
+ this.canSave = await hasPrivilege({
20
+ category: 'kpi',
21
+ privilege: 'sentiment',
22
+ domainOwnerGranted: true,
23
+ superUserGranted: true
24
+ });
11
25
  }
12
26
  render() {
13
27
  var _a, _b;
@@ -22,83 +36,125 @@ let SvProjectCompleteTab3Upload = class SvProjectCompleteTab3Upload extends LitE
22
36
  </div>
23
37
 
24
38
  <div class="rows">
25
- <div class="upload-controls">
26
- <ox-input-file accept="application/pdf,.pdf" hide-filelist @change=${this._onFileSelect} multiple="true">
27
- </ox-input-file>
28
- </div>
29
-
30
- <div class="attachment-list">
31
- ${!((_a = this.data) === null || _a === void 0 ? void 0 : _a.length)
32
- ? html `<div class="empty-state">업로드된 파일이 없습니다.</div>`
33
- : (_b = this.data) === null || _b === void 0 ? void 0 : _b.map(file => html `<div class="attachment-row">
34
- <div class="attachment-name" title="${file.name}">${file.name}</div>
35
- <md-icon class="delete-icon" @click=${() => this._deleteAttachment(file.id)}>delete</md-icon>
36
- </div>`)}
37
- </div>
39
+ ${this._getCurrentFile()
40
+ ? html `
41
+ <div class="attachment-list">
42
+ <div class="attachment-row">
43
+ <md-icon class="file-icon">picture_as_pdf</md-icon>
44
+ <div class="attachment-name" title="${(_a = this._getCurrentFile()) === null || _a === void 0 ? void 0 : _a.name}">${(_b = this._getCurrentFile()) === null || _b === void 0 ? void 0 : _b.name}</div>
45
+ <md-icon class="delete-icon" @click=${this._removeFile}>delete</md-icon>
46
+ </div>
47
+ </div>
48
+ `
49
+ : html `
50
+ <div class="upload-controls">
51
+ <ox-input-file accept="application/pdf,.pdf" hide-filelist @change=${this._onFileSelect}> </ox-input-file>
52
+ </div>
53
+ `}
54
+ ${this.slpaData.length > 0
55
+ ? html `
56
+ <div class="slpa-results">
57
+ <div class="slpa-title">AI 분석 결과 (SL-PA 관련 항목)</div>
58
+ ${this.slpaData.map(item => html `
59
+ <div class="slpa-item">
60
+ <div class="slpa-label">${item.name}</div>
61
+ <div class="slpa-value">${item.value.toFixed(2)} ${item.unit || ''}</div>
62
+ </div>
63
+ `)}
64
+ </div>
65
+ `
66
+ : ''}
38
67
 
39
68
  <div class="button-line">
40
69
  <div class="ghost-btn " @click=${this._reset}>초기화</div>
41
- <div class="ghost-btn secondary" @click=${() => this._save()}>저장</div>
70
+ <div
71
+ class="ghost-btn secondary ${this.canSave ? '' : 'disabled'}"
72
+ title=${this.canSave ? '' : 'kpi:sentiment 권한 필요'}
73
+ @click=${() => this.canSave && this._save()}
74
+ >
75
+ 저장
76
+ </div>
42
77
  </div>
43
78
  </div>
44
79
  `;
45
80
  }
46
- _save() {
47
- this.dispatchEvent(new CustomEvent('complete-tab-save', { detail: { data: this.data } }));
81
+ willUpdate(changedProperties) {
82
+ var _a;
83
+ super.willUpdate(changedProperties);
84
+ // project가 변경되고, project.id가 존재하면 데이터 로드
85
+ if (changedProperties.has('project') && ((_a = this.project) === null || _a === void 0 ? void 0 : _a.id)) {
86
+ this._getProjectData();
87
+ }
88
+ }
89
+ _getCurrentFile() {
90
+ var _a;
91
+ return this.pendingFile || (((_a = this.attachment) === null || _a === void 0 ? void 0 : _a.id) ? this.attachment : null);
48
92
  }
49
- async _reset() {
93
+ async _save() {
50
94
  var _a;
51
- await ((_a = this.data) === null || _a === void 0 ? void 0 : _a.forEach(attachment => this._deleteAttachment(attachment.id)));
52
- this.dispatchEvent(new CustomEvent('complete-tab-reset', { detail: { tab: 3 } }));
95
+ // 대기 중인 파일이 있으면 업로드
96
+ if (this.pendingFile) {
97
+ await this._uploadFile(this.pendingFile);
98
+ this.pendingFile = null;
99
+ }
100
+ else if (!this.pendingFile && !((_a = this.attachment) === null || _a === void 0 ? void 0 : _a.id)) {
101
+ notify({ message: '파일은 필수입니다.' });
102
+ }
103
+ }
104
+ _removeFile() {
105
+ this.pendingFile = null;
106
+ this.attachment = {};
107
+ this.slpaData = []; // SL-PA 데이터도 초기화
53
108
  }
54
109
  async _onFileSelect(e) {
55
110
  const files = e.detail;
56
- await this._uploadFiles(files);
57
- }
58
- async _uploadFiles(files) {
59
- const response = await client.mutate({
60
- mutation: gql `
61
- mutation ($attachments: [NewAttachment!]!) {
62
- createAttachments(attachments: $attachments) {
63
- id
64
- name
65
- fullpath
66
- }
111
+ if (files.length > 0) {
112
+ this.pendingFile = files[0];
67
113
  }
68
- `,
69
- variables: {
70
- attachments: files.map(file => {
71
- var _a;
72
- return ({
73
- file,
74
- refBy: (_a = this.project) === null || _a === void 0 ? void 0 : _a.id,
75
- refType: 'ProjectCompleteReport'
76
- });
77
- })
78
- },
79
- context: {
80
- hasUpload: true
81
- }
82
- });
114
+ }
115
+ async _uploadFile(file) {
116
+ var _a;
117
+ const response = await updateProjectCompleteStep3(file, this.project.id, (_a = this.project) === null || _a === void 0 ? void 0 : _a.code);
83
118
  if (!response.errors) {
84
- const uploaded = response.data.createAttachments;
85
- this.data = [...this.data, ...uploaded];
86
- this.dispatchEvent(new CustomEvent('complete-data-change', { detail: { tab: 3, data: this.data } }));
119
+ const uploaded = response.data.updateKpiMetricValuesSentiment;
120
+ this.attachment = uploaded;
121
+ notify({ message: '저장되었습니다.' });
122
+ // PDF 업로드 후 SL-PA 관련 데이터를 가져와서 표시
123
+ await this._loadSlpaData();
87
124
  }
88
125
  }
89
- async _deleteAttachment(attachmentId) {
90
- var _a;
91
- const response = await client.mutate({
92
- mutation: gql `
93
- mutation DeleteAttachment($deleteAttachmentId: String!) {
94
- deleteAttachment(id: $deleteAttachmentId)
126
+ async _loadSlpaData() {
127
+ var _a, _b;
128
+ // KPI 메트릭들 가져오기
129
+ const kpiMetrics = await getKpiMetrics((_a = this.project) === null || _a === void 0 ? void 0 : _a.code);
130
+ // "SL-PA"가 포함된 메트릭들만 필터링
131
+ const slpaMetrics = kpiMetrics.filter(metric => metric.name.includes('SL-PA'));
132
+ if (slpaMetrics.length > 0) {
133
+ // 해당 프로젝트의 KPI 값들 가져오기
134
+ const kpiMetricValues = await getKpiMetricValues(this.project.id, (_b = this.project) === null || _b === void 0 ? void 0 : _b.code);
135
+ // SL-PA 메트릭들과 해당 값들을 매칭하여 표시용 데이터 생성
136
+ this.slpaData = slpaMetrics.map(metric => {
137
+ const metricValue = kpiMetricValues.find((item) => item.metricId === metric.id);
138
+ return {
139
+ name: metric.name,
140
+ value: (metricValue === null || metricValue === void 0 ? void 0 : metricValue.value) || 0,
141
+ unit: (metricValue === null || metricValue === void 0 ? void 0 : metricValue.unit) || metric.unit || ''
142
+ };
143
+ });
95
144
  }
96
- `,
97
- variables: { deleteAttachmentId: attachmentId }
98
- });
99
- if (!response.errors) {
100
- this.data = ((_a = this.data) === null || _a === void 0 ? void 0 : _a.filter(a => a.id !== attachmentId)) || [];
101
- this.dispatchEvent(new CustomEvent('complete-data-change', { detail: { tab: 3, data: this.data } }));
145
+ }
146
+ _reset() {
147
+ this.pendingFile = null;
148
+ this.slpaData = []; // SL-PA 데이터도 초기화
149
+ this._getProjectData();
150
+ }
151
+ async _getProjectData() {
152
+ var _a;
153
+ const project = await getProject(this.project.id);
154
+ this.attachment = project.completeReport;
155
+ // 기존에 PDF가 업로드되어 있다면 SL-PA 데이터도 로드
156
+ if ((_a = this.attachment) === null || _a === void 0 ? void 0 : _a.id) {
157
+ await this._loadSlpaData();
102
158
  }
103
159
  }
104
160
  };
@@ -136,45 +192,58 @@ SvProjectCompleteTab3Upload.styles = [
136
192
  }
137
193
 
138
194
  .attachment-list {
139
- position: relative;
140
- z-index: 1;
141
195
  display: flex;
142
- min-height: 80px;
143
196
  justify-content: center;
144
- flex-direction: column;
145
- gap: 8px;
146
- border: 1px solid #eee;
147
- border-radius: 8px;
148
- padding: 12px;
149
197
  margin-top: 8px;
150
198
  }
151
199
 
152
200
  .attachment-row {
153
- display: grid;
154
- grid-template-columns: 1fr auto;
201
+ display: flex;
202
+ flex-direction: column;
155
203
  align-items: center;
156
- gap: 8px;
157
- padding: 8px 10px;
158
- border: 1px solid #ddd;
159
- border-radius: 6px;
160
- background: #fafafa;
204
+ gap: 16px;
205
+ padding: 24px 20px;
206
+ border: 2px solid #e3f2fd;
207
+ border-radius: 12px;
208
+ background: linear-gradient(135deg, #f8fbff 0%, #e3f2fd 100%);
209
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
210
+ min-width: 300px;
211
+ max-width: 400px;
161
212
  }
162
213
 
163
- .attachment-name {
164
- font-size: 14px;
165
- overflow: hidden;
166
- white-space: nowrap;
167
- text-overflow: ellipsis;
214
+ .file-icon {
215
+ font-size: 40px;
216
+ color: #d32f2f;
217
+ line-height: 1;
218
+ display: flex;
219
+ align-items: center;
220
+ justify-content: center;
221
+ width: auto;
222
+ height: 48px;
168
223
  }
169
224
 
170
- .empty-state {
225
+ .attachment-name {
226
+ font-size: 16px;
227
+ font-weight: 500;
228
+ color: #1976d2;
171
229
  text-align: center;
172
- color: #666;
173
- font-style: italic;
230
+ word-break: break-word;
231
+ line-height: 1.4;
174
232
  }
175
233
 
176
234
  .delete-icon {
177
235
  cursor: pointer;
236
+ color: #757575;
237
+ background: rgba(117, 117, 117, 0.1);
238
+ border-radius: 50%;
239
+ padding: 8px;
240
+ transition: all 0.2s ease;
241
+ }
242
+
243
+ .delete-icon:hover {
244
+ color: #e57373;
245
+ background: rgba(229, 115, 115, 0.15);
246
+ transform: scale(1.1);
178
247
  }
179
248
 
180
249
  .button-line {
@@ -196,16 +265,69 @@ SvProjectCompleteTab3Upload.styles = [
196
265
  .ghost-btn.secondary {
197
266
  background: #24be7b;
198
267
  }
268
+ .ghost-btn.disabled {
269
+ opacity: 0.45;
270
+ cursor: not-allowed;
271
+ }
272
+
273
+ .slpa-results {
274
+ margin-top: 20px;
275
+ padding: 16px;
276
+ border: 1px solid #e0e0e0;
277
+ border-radius: 8px;
278
+ background: #f8f9fa;
279
+ }
280
+
281
+ .slpa-title {
282
+ font-size: 16px;
283
+ font-weight: 600;
284
+ color: #35618e;
285
+ margin-bottom: 12px;
286
+ }
287
+
288
+ .slpa-item {
289
+ display: flex;
290
+ justify-content: space-between;
291
+ align-items: center;
292
+ padding: 8px 0;
293
+ border-bottom: 1px solid #e0e0e0;
294
+ }
295
+
296
+ .slpa-item:last-child {
297
+ border-bottom: none;
298
+ }
299
+
300
+ .slpa-label {
301
+ font-weight: 500;
302
+ color: #333;
303
+ }
304
+
305
+ .slpa-value {
306
+ font-weight: 600;
307
+ color: #1976d2;
308
+ }
199
309
  `
200
310
  ];
201
- __decorate([
202
- property({ type: Array }),
203
- __metadata("design:type", Object)
204
- ], SvProjectCompleteTab3Upload.prototype, "data", void 0);
205
311
  __decorate([
206
312
  property({ type: Object }),
207
313
  __metadata("design:type", Object)
208
314
  ], SvProjectCompleteTab3Upload.prototype, "project", void 0);
315
+ __decorate([
316
+ state(),
317
+ __metadata("design:type", Object)
318
+ ], SvProjectCompleteTab3Upload.prototype, "attachment", void 0);
319
+ __decorate([
320
+ state(),
321
+ __metadata("design:type", Object)
322
+ ], SvProjectCompleteTab3Upload.prototype, "pendingFile", void 0);
323
+ __decorate([
324
+ state(),
325
+ __metadata("design:type", Array)
326
+ ], SvProjectCompleteTab3Upload.prototype, "slpaData", void 0);
327
+ __decorate([
328
+ state(),
329
+ __metadata("design:type", Object)
330
+ ], SvProjectCompleteTab3Upload.prototype, "canSave", void 0);
209
331
  SvProjectCompleteTab3Upload = __decorate([
210
332
  customElement('sv-pc-tab3-upload')
211
333
  ], SvProjectCompleteTab3Upload);
@@ -1 +1 @@
1
- {"version":3,"file":"pc-tab3-upload.js","sourceRoot":"","sources":["../../../client/pages/project-complete-tabs/pc-tab3-upload.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { client } from '@operato/graphql'\nimport { gql } from '@apollo/client'\n\n@customElement('sv-pc-tab3-upload')\nexport class SvProjectCompleteTab3Upload extends LitElement {\n static styles = [\n css`\n :host {\n display: block;\n }\n .title {\n color: #212529;\n font-size: 13px;\n font-weight: 400;\n line-height: 24px;\n text-align: center;\n }\n\n .rows {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 8px 6px;\n }\n\n .upload-controls {\n display: flex;\n gap: 8px;\n justify-content: center;\n margin-bottom: 16px;\n\n ox-input-file {\n flex: 1;\n max-width: 500px;\n height: 210px;\n }\n }\n\n .attachment-list {\n position: relative;\n z-index: 1;\n display: flex;\n min-height: 80px;\n justify-content: center;\n flex-direction: column;\n gap: 8px;\n border: 1px solid #eee;\n border-radius: 8px;\n padding: 12px;\n margin-top: 8px;\n }\n\n .attachment-row {\n display: grid;\n grid-template-columns: 1fr auto;\n align-items: center;\n gap: 8px;\n padding: 8px 10px;\n border: 1px solid #ddd;\n border-radius: 6px;\n background: #fafafa;\n }\n\n .attachment-name {\n font-size: 14px;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n }\n\n .empty-state {\n text-align: center;\n color: #666;\n font-style: italic;\n }\n\n .delete-icon {\n cursor: pointer;\n }\n\n .button-line {\n display: flex;\n justify-content: center;\n gap: 10px;\n margin-top: 16px;\n }\n .ghost-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 10px;\n background: #35618e;\n color: #ffffff;\n border-radius: 5px;\n cursor: pointer;\n }\n .ghost-btn.secondary {\n background: #24be7b;\n }\n `\n ]\n\n @property({ type: Array }) data: any = []\n @property({ type: Object }) project: any = {}\n\n render() {\n return html`\n <div class=\"title\">\n <div>\n 감리 최종 감리보고서의 종합의견서(PDF 형식)를 시스템에 업로드해 주세요.\n <br />\n 보고서에 포함된 텍스트와 평가 내용을 기반으로 기계 판독 및 AI 기반 프로젝트 평가가 자동으로 진행되므로, 반드시 최종\n 확정된 원본 파일을 제출해 주시기 바랍니다.\n </div>\n </div>\n\n <div class=\"rows\">\n <div class=\"upload-controls\">\n <ox-input-file accept=\"application/pdf,.pdf\" hide-filelist @change=${this._onFileSelect} multiple=\"true\">\n </ox-input-file>\n </div>\n\n <div class=\"attachment-list\">\n ${!this.data?.length\n ? html`<div class=\"empty-state\">업로드된 파일이 없습니다.</div>`\n : this.data?.map(\n file =>\n html`<div class=\"attachment-row\">\n <div class=\"attachment-name\" title=\"${file.name}\">${file.name}</div>\n <md-icon class=\"delete-icon\" @click=${() => this._deleteAttachment(file.id)}>delete</md-icon>\n </div>`\n )}\n </div>\n\n <div class=\"button-line\">\n <div class=\"ghost-btn \" @click=${this._reset}>초기화</div>\n <div class=\"ghost-btn secondary\" @click=${() => this._save()}>저장</div>\n </div>\n </div>\n `\n }\n\n private _save() {\n this.dispatchEvent(new CustomEvent('complete-tab-save', { detail: { data: this.data } }))\n }\n\n private async _reset() {\n await this.data?.forEach(attachment => this._deleteAttachment(attachment.id))\n this.dispatchEvent(new CustomEvent('complete-tab-reset', { detail: { tab: 3 } }))\n }\n\n private async _onFileSelect(e: CustomEvent) {\n const files = e.detail as File[]\n await this._uploadFiles(files)\n }\n\n private async _uploadFiles(files: File[]) {\n const response = await client.mutate({\n mutation: gql`\n mutation ($attachments: [NewAttachment!]!) {\n createAttachments(attachments: $attachments) {\n id\n name\n fullpath\n }\n }\n `,\n variables: {\n attachments: files.map(file => ({\n file,\n refBy: this.project?.id,\n refType: 'ProjectCompleteReport'\n }))\n },\n context: {\n hasUpload: true\n }\n })\n\n if (!response.errors) {\n const uploaded = response.data.createAttachments\n this.data = [...this.data, ...uploaded]\n this.dispatchEvent(new CustomEvent('complete-data-change', { detail: { tab: 3, data: this.data } }))\n }\n }\n\n private async _deleteAttachment(attachmentId: string) {\n const response = await client.mutate({\n mutation: gql`\n mutation DeleteAttachment($deleteAttachmentId: String!) {\n deleteAttachment(id: $deleteAttachmentId)\n }\n `,\n variables: { deleteAttachmentId: attachmentId }\n })\n\n if (!response.errors) {\n this.data = this.data?.filter(a => a.id !== attachmentId) || []\n this.dispatchEvent(new CustomEvent('complete-data-change', { detail: { tab: 3, data: this.data } }))\n }\n }\n}\n"]}
1
+ {"version":3,"file":"pc-tab3-upload.js","sourceRoot":"","sources":["../../../client/pages/project-complete-tabs/pc-tab3-upload.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { client } from '@operato/graphql'\nimport { gql } from '@apollo/client'\nimport { getProject, updateProjectCompleteStep3, getKpiMetrics, getKpiMetricValues } from '../../shared/complete-api'\nimport { notify } from '@operato/layout'\nimport { hasPrivilege } from '@things-factory/auth-base/dist-client'\n\n@customElement('sv-pc-tab3-upload')\nexport class SvProjectCompleteTab3Upload extends LitElement {\n static styles = [\n css`\n :host {\n display: block;\n }\n .title {\n color: #212529;\n font-size: 13px;\n font-weight: 400;\n line-height: 24px;\n text-align: center;\n }\n\n .rows {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 8px 6px;\n }\n\n .upload-controls {\n display: flex;\n gap: 8px;\n justify-content: center;\n margin-bottom: 16px;\n\n ox-input-file {\n flex: 1;\n max-width: 500px;\n height: 210px;\n }\n }\n\n .attachment-list {\n display: flex;\n justify-content: center;\n margin-top: 8px;\n }\n\n .attachment-row {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 16px;\n padding: 24px 20px;\n border: 2px solid #e3f2fd;\n border-radius: 12px;\n background: linear-gradient(135deg, #f8fbff 0%, #e3f2fd 100%);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n min-width: 300px;\n max-width: 400px;\n }\n\n .file-icon {\n font-size: 40px;\n color: #d32f2f;\n line-height: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n width: auto;\n height: 48px;\n }\n\n .attachment-name {\n font-size: 16px;\n font-weight: 500;\n color: #1976d2;\n text-align: center;\n word-break: break-word;\n line-height: 1.4;\n }\n\n .delete-icon {\n cursor: pointer;\n color: #757575;\n background: rgba(117, 117, 117, 0.1);\n border-radius: 50%;\n padding: 8px;\n transition: all 0.2s ease;\n }\n\n .delete-icon:hover {\n color: #e57373;\n background: rgba(229, 115, 115, 0.15);\n transform: scale(1.1);\n }\n\n .button-line {\n display: flex;\n justify-content: center;\n gap: 10px;\n margin-top: 16px;\n }\n .ghost-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 10px;\n background: #35618e;\n color: #ffffff;\n border-radius: 5px;\n cursor: pointer;\n }\n .ghost-btn.secondary {\n background: #24be7b;\n }\n .ghost-btn.disabled {\n opacity: 0.45;\n cursor: not-allowed;\n }\n\n .slpa-results {\n margin-top: 20px;\n padding: 16px;\n border: 1px solid #e0e0e0;\n border-radius: 8px;\n background: #f8f9fa;\n }\n\n .slpa-title {\n font-size: 16px;\n font-weight: 600;\n color: #35618e;\n margin-bottom: 12px;\n }\n\n .slpa-item {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 8px 0;\n border-bottom: 1px solid #e0e0e0;\n }\n\n .slpa-item:last-child {\n border-bottom: none;\n }\n\n .slpa-label {\n font-weight: 500;\n color: #333;\n }\n\n .slpa-value {\n font-weight: 600;\n color: #1976d2;\n }\n `\n ]\n\n @property({ type: Object }) project: any = {}\n @state() attachment: any = {}\n @state() private pendingFile: File | null = null\n @state() slpaData: any[] = []\n /** kpi:sentiment — Step3 보고서 첨부/저장 권한 */\n @state() canSave = false\n\n async connectedCallback() {\n super.connectedCallback()\n this.canSave = await hasPrivilege({\n category: 'kpi',\n privilege: 'sentiment',\n domainOwnerGranted: true,\n superUserGranted: true\n })\n }\n\n render() {\n return html`\n <div class=\"title\">\n <div>\n 감리 최종 감리보고서의 종합의견서(PDF 형식)를 시스템에 업로드해 주세요.\n <br />\n 보고서에 포함된 텍스트와 평가 내용을 기반으로 기계 판독 및 AI 기반 프로젝트 평가가 자동으로 진행되므로, 반드시 최종\n 확정된 원본 파일을 제출해 주시기 바랍니다.\n </div>\n </div>\n\n <div class=\"rows\">\n ${this._getCurrentFile()\n ? html`\n <div class=\"attachment-list\">\n <div class=\"attachment-row\">\n <md-icon class=\"file-icon\">picture_as_pdf</md-icon>\n <div class=\"attachment-name\" title=\"${this._getCurrentFile()?.name}\">${this._getCurrentFile()?.name}</div>\n <md-icon class=\"delete-icon\" @click=${this._removeFile}>delete</md-icon>\n </div>\n </div>\n `\n : html`\n <div class=\"upload-controls\">\n <ox-input-file accept=\"application/pdf,.pdf\" hide-filelist @change=${this._onFileSelect}> </ox-input-file>\n </div>\n `}\n ${this.slpaData.length > 0\n ? html`\n <div class=\"slpa-results\">\n <div class=\"slpa-title\">AI 분석 결과 (SL-PA 관련 항목)</div>\n ${this.slpaData.map(\n item => html`\n <div class=\"slpa-item\">\n <div class=\"slpa-label\">${item.name}</div>\n <div class=\"slpa-value\">${item.value.toFixed(2)} ${item.unit || ''}</div>\n </div>\n `\n )}\n </div>\n `\n : ''}\n\n <div class=\"button-line\">\n <div class=\"ghost-btn \" @click=${this._reset}>초기화</div>\n <div\n class=\"ghost-btn secondary ${this.canSave ? '' : 'disabled'}\"\n title=${this.canSave ? '' : 'kpi:sentiment 권한 필요'}\n @click=${() => this.canSave && this._save()}\n >\n 저장\n </div>\n </div>\n </div>\n `\n }\n\n willUpdate(changedProperties: Map<string, any>) {\n super.willUpdate(changedProperties)\n\n // project가 변경되고, project.id가 존재하면 데이터 로드\n if (changedProperties.has('project') && this.project?.id) {\n this._getProjectData()\n }\n }\n\n private _getCurrentFile() {\n return this.pendingFile || (this.attachment?.id ? this.attachment : null)\n }\n\n private async _save() {\n // 대기 중인 파일이 있으면 업로드\n if (this.pendingFile) {\n await this._uploadFile(this.pendingFile)\n this.pendingFile = null\n } else if (!this.pendingFile && !this.attachment?.id) {\n notify({ message: '파일은 필수입니다.' })\n }\n }\n\n private _removeFile() {\n this.pendingFile = null\n this.attachment = {}\n this.slpaData = [] // SL-PA 데이터도 초기화\n }\n\n private async _onFileSelect(e: CustomEvent) {\n const files = e.detail as File[]\n if (files.length > 0) {\n this.pendingFile = files[0]\n }\n }\n\n private async _uploadFile(file: File | null) {\n const response = await updateProjectCompleteStep3(file, this.project.id, this.project?.code)\n if (!response.errors) {\n const uploaded = response.data.updateKpiMetricValuesSentiment\n this.attachment = uploaded\n notify({ message: '저장되었습니다.' })\n\n // PDF 업로드 후 SL-PA 관련 데이터를 가져와서 표시\n await this._loadSlpaData()\n }\n }\n\n private async _loadSlpaData() {\n // KPI 메트릭들 가져오기\n const kpiMetrics = await getKpiMetrics(this.project?.code)\n\n // \"SL-PA\"가 포함된 메트릭들만 필터링\n const slpaMetrics = kpiMetrics.filter(metric => metric.name.includes('SL-PA'))\n\n if (slpaMetrics.length > 0) {\n // 해당 프로젝트의 KPI 값들 가져오기\n const kpiMetricValues = await getKpiMetricValues(this.project.id, this.project?.code)\n\n // SL-PA 메트릭들과 해당 값들을 매칭하여 표시용 데이터 생성\n this.slpaData = slpaMetrics.map(metric => {\n const metricValue = kpiMetricValues.find((item: any) => item.metricId === metric.id)\n return {\n name: metric.name,\n value: metricValue?.value || 0,\n unit: metricValue?.unit || metric.unit || ''\n }\n })\n }\n }\n\n private _reset() {\n this.pendingFile = null\n this.slpaData = [] // SL-PA 데이터도 초기화\n this._getProjectData()\n }\n\n private async _getProjectData() {\n const project = await getProject(this.project.id)\n this.attachment = project.completeReport\n\n // 기존에 PDF가 업로드되어 있다면 SL-PA 데이터도 로드\n if (this.attachment?.id) {\n await this._loadSlpaData()\n }\n }\n}\n"]}
@@ -0,0 +1,51 @@
1
+ import { LitElement } from 'lit';
2
+ import '@things-factory/kpi/dist-client/viewparts/kpi-metric-input';
3
+ export declare class SvProjectCompleteTab4Monthly extends LitElement {
4
+ static styles: import("lit").CSSResult[];
5
+ project: any;
6
+ /** 월별 metric 목록 (periodType=MONTH). KpiMetric admin 에 등록된 것 기준. */
7
+ monthlyMetrics: any[];
8
+ /** monthRows: { workDate:'YYYY-MM', values:{[metricId]:value}, originalValues, dirty }[] */
9
+ monthRows: any[];
10
+ addYear: number;
11
+ addMonth: number;
12
+ /** kpi:input — Step4 월별 데이터 저장 권한 (cumulative 와 같은 권한) */
13
+ canSave: boolean;
14
+ connectedCallback(): Promise<void>;
15
+ render(): import("lit-html").TemplateResult<1>;
16
+ willUpdate(changedProperties: Map<string, any>): void;
17
+ private _getYearRange;
18
+ private _isCurrentMonth;
19
+ /**
20
+ * 월별 metric 정의 + 그 프로젝트의 월별 KpiMetricValue 들을 조회해 그리드 row 구성.
21
+ *
22
+ * 1) KpiMetric where periodType=MONTH → monthlyMetrics
23
+ * 2) KpiMetricValue where org=projectId → 월별로 그룹핑하여 monthRows
24
+ */
25
+ private _loadData;
26
+ /** project.startDate (YYYY-MM) ~ **전월** (YYYY-MM) 까지 매월 문자열 배열.
27
+ * 현재월은 의도적으로 제외 — 운영 원칙상 "이번달 데이터는 아직 입력 대상이 아님". */
28
+ private _generateExpectedMonths;
29
+ /** 셀 미입력 여부 — 값 없음 AND 그 월이 **전월** (직전 한 달) 인 경우만 pending.
30
+ * 과월 전체가 아니라 전월 한 달에 한해서만 강조 — 사용자 입력 흐름(이번 달에 지난달 데이터)과 일치. */
31
+ private _isCellPending;
32
+ /** metric 컬럼이 전 row 통틀어 한 번도 값이 없으면 헤더 강조. */
33
+ private _isMetricNeverEntered;
34
+ /** 종료(COMPLETED) 프로젝트 — 신규 월 추가 금지, 기존 월만 수정 가능. */
35
+ private get isCompleted();
36
+ private _addMonth;
37
+ private _removeMonth;
38
+ /**
39
+ * metric 입력 힌트 결정: DB 선언(meta.input) 우선. 없으면 전환용 파생(DB meta 미도입 동안).
40
+ * 향후 metric.meta.input 이 채워지면 이 파생 분기(및 isRating)는 제거한다.
41
+ */
42
+ private _withInputHint;
43
+ /** <kpi-metric-input> 이 방출하는 (이미 타입된) 값을 그대로 저장. */
44
+ private _setCell;
45
+ /**
46
+ * 저장 — dirty row 들 안의 변경된 cell 들을 KpiMetricValuePatch 로 모아
47
+ * updateKpiMetricValuesCumulative 한 번 호출 (backend upsert 가 unique 조합 처리).
48
+ */
49
+ private _save;
50
+ private _reset;
51
+ }