@dssp/dkpi 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/dist-client/bootstrap.d.ts +9 -0
  2. package/dist-client/bootstrap.js +272 -0
  3. package/dist-client/bootstrap.js.map +1 -0
  4. package/dist-client/components/kpi-2d-lookup-chart.d.ts +50 -0
  5. package/dist-client/components/kpi-2d-lookup-chart.js +263 -0
  6. package/dist-client/components/kpi-2d-lookup-chart.js.map +1 -0
  7. package/dist-client/components/kpi-boxplot-chart.d.ts +31 -0
  8. package/dist-client/components/kpi-boxplot-chart.js +306 -0
  9. package/dist-client/components/kpi-boxplot-chart.js.map +1 -0
  10. package/dist-client/components/kpi-lookup-chart.d.ts +58 -0
  11. package/dist-client/components/kpi-lookup-chart.js +464 -0
  12. package/dist-client/components/kpi-lookup-chart.js.map +1 -0
  13. package/dist-client/components/kpi-mini-trend-chart.d.ts +14 -0
  14. package/dist-client/components/kpi-mini-trend-chart.js +180 -0
  15. package/dist-client/components/kpi-mini-trend-chart.js.map +1 -0
  16. package/dist-client/components/kpi-radar-chart.d.ts +17 -0
  17. package/dist-client/components/kpi-radar-chart.js +259 -0
  18. package/dist-client/components/kpi-radar-chart.js.map +1 -0
  19. package/dist-client/components/kpi-single-boxplot-chart.d.ts +36 -0
  20. package/dist-client/components/kpi-single-boxplot-chart.js +417 -0
  21. package/dist-client/components/kpi-single-boxplot-chart.js.map +1 -0
  22. package/dist-client/components/kpi-step-lookup-chart.d.ts +33 -0
  23. package/dist-client/components/kpi-step-lookup-chart.js +181 -0
  24. package/dist-client/components/kpi-step-lookup-chart.js.map +1 -0
  25. package/dist-client/components/kpi-trend-chart.d.ts +25 -0
  26. package/dist-client/components/kpi-trend-chart.js +241 -0
  27. package/dist-client/components/kpi-trend-chart.js.map +1 -0
  28. package/dist-client/components/sv-pagenation-control.d.ts +18 -0
  29. package/dist-client/components/sv-pagenation-control.js +142 -0
  30. package/dist-client/components/sv-pagenation-control.js.map +1 -0
  31. package/dist-client/entries/auth/checkin.d.ts +38 -0
  32. package/dist-client/entries/auth/checkin.js +551 -0
  33. package/dist-client/entries/auth/checkin.js.map +1 -0
  34. package/dist-client/entries/public/home.d.ts +8 -0
  35. package/dist-client/entries/public/home.js +97 -0
  36. package/dist-client/entries/public/home.js.map +1 -0
  37. package/dist-client/google-map/common-google-map.d.ts +35 -0
  38. package/dist-client/google-map/common-google-map.js +349 -0
  39. package/dist-client/google-map/common-google-map.js.map +1 -0
  40. package/dist-client/google-map/google-map-loader.d.ts +6 -0
  41. package/dist-client/google-map/google-map-loader.js +23 -0
  42. package/dist-client/google-map/google-map-loader.js.map +1 -0
  43. package/dist-client/icons/menu-icons.d.ts +11 -0
  44. package/dist-client/icons/menu-icons.js +126 -0
  45. package/dist-client/icons/menu-icons.js.map +1 -0
  46. package/dist-client/index.d.ts +0 -0
  47. package/dist-client/index.js +2 -0
  48. package/dist-client/index.js.map +1 -0
  49. package/dist-client/menu.d.ts +23 -0
  50. package/dist-client/menu.js +59 -0
  51. package/dist-client/menu.js.map +1 -0
  52. package/dist-client/pages/component/project-update-header.d.ts +1 -0
  53. package/dist-client/pages/component/project-update-header.js +127 -0
  54. package/dist-client/pages/component/project-update-header.js.map +1 -0
  55. package/dist-client/pages/kpi-admin/dssp-kpi-list-page.d.ts +22 -0
  56. package/dist-client/pages/kpi-admin/dssp-kpi-list-page.js +57 -0
  57. package/dist-client/pages/kpi-admin/dssp-kpi-list-page.js.map +1 -0
  58. package/dist-client/pages/kpi-admin/dssp-kpi-overview.d.ts +47 -0
  59. package/dist-client/pages/kpi-admin/dssp-kpi-overview.js +393 -0
  60. package/dist-client/pages/kpi-admin/dssp-kpi-overview.js.map +1 -0
  61. package/dist-client/pages/kpi-admin/kpi-grade-2d-editor.d.ts +20 -0
  62. package/dist-client/pages/kpi-admin/kpi-grade-2d-editor.js +445 -0
  63. package/dist-client/pages/kpi-admin/kpi-grade-2d-editor.js.map +1 -0
  64. package/dist-client/pages/kpi-admin/kpi-system-guide.d.ts +18 -0
  65. package/dist-client/pages/kpi-admin/kpi-system-guide.js +696 -0
  66. package/dist-client/pages/kpi-admin/kpi-system-guide.js.map +1 -0
  67. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.d.ts +18 -0
  68. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js +259 -0
  69. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js.map +1 -0
  70. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.d.ts +22 -0
  71. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js +346 -0
  72. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js.map +1 -0
  73. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.d.ts +26 -0
  74. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js +433 -0
  75. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js.map +1 -0
  76. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.d.ts +8 -0
  77. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js +78 -0
  78. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js.map +1 -0
  79. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.d.ts +46 -0
  80. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js +945 -0
  81. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js.map +1 -0
  82. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.d.ts +34 -0
  83. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js +238 -0
  84. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js.map +1 -0
  85. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.d.ts +37 -0
  86. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js +689 -0
  87. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js.map +1 -0
  88. package/dist-client/pages/kpi-dashboard/kpi-alert-panel.d.ts +18 -0
  89. package/dist-client/pages/kpi-dashboard/kpi-alert-panel.js +131 -0
  90. package/dist-client/pages/kpi-dashboard/kpi-alert-panel.js.map +1 -0
  91. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.d.ts +57 -0
  92. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +866 -0
  93. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -0
  94. package/dist-client/pages/kpi-dashboard/kpi-dashboard.d.ts +63 -0
  95. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js +1097 -0
  96. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js.map +1 -0
  97. package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.d.ts +12 -0
  98. package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.js +82 -0
  99. package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.js.map +1 -0
  100. package/dist-client/pages/kpi-dashboard/kpi-history-viewer.d.ts +11 -0
  101. package/dist-client/pages/kpi-dashboard/kpi-history-viewer.js +65 -0
  102. package/dist-client/pages/kpi-dashboard/kpi-history-viewer.js.map +1 -0
  103. package/dist-client/pages/kpi-dashboard/kpi-list-summary.d.ts +13 -0
  104. package/dist-client/pages/kpi-dashboard/kpi-list-summary.js +115 -0
  105. package/dist-client/pages/kpi-dashboard/kpi-list-summary.js.map +1 -0
  106. package/dist-client/pages/kpi-dashboard/kpi-performance-summary.d.ts +15 -0
  107. package/dist-client/pages/kpi-dashboard/kpi-performance-summary.js +147 -0
  108. package/dist-client/pages/kpi-dashboard/kpi-performance-summary.js.map +1 -0
  109. package/dist-client/pages/kpi-dashboard/kpi-value-entry.d.ts +7 -0
  110. package/dist-client/pages/kpi-dashboard/kpi-value-entry.js +86 -0
  111. package/dist-client/pages/kpi-dashboard/kpi-value-entry.js.map +1 -0
  112. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.d.ts +57 -0
  113. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js +719 -0
  114. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js.map +1 -0
  115. package/dist-client/pages/kpi-metric-value/kpi-metric-value-importer.d.ts +23 -0
  116. package/dist-client/pages/kpi-metric-value/kpi-metric-value-importer.js +76 -0
  117. package/dist-client/pages/kpi-metric-value/kpi-metric-value-importer.js.map +1 -0
  118. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.d.ts +68 -0
  119. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js +394 -0
  120. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js.map +1 -0
  121. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.d.ts +12 -0
  122. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.js +174 -0
  123. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.js.map +1 -0
  124. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.d.ts +40 -0
  125. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js +190 -0
  126. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js.map +1 -0
  127. package/dist-client/pages/kpi-value/kpi-value-importer.d.ts +23 -0
  128. package/dist-client/pages/kpi-value/kpi-value-importer.js +93 -0
  129. package/dist-client/pages/kpi-value/kpi-value-importer.js.map +1 -0
  130. package/dist-client/pages/kpi-value/kpi-value-list-page.d.ts +71 -0
  131. package/dist-client/pages/kpi-value/kpi-value-list-page.js +454 -0
  132. package/dist-client/pages/kpi-value/kpi-value-list-page.js.map +1 -0
  133. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.d.ts +69 -0
  134. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js +790 -0
  135. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js.map +1 -0
  136. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.d.ts +22 -0
  137. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js +344 -0
  138. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js.map +1 -0
  139. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.d.ts +21 -0
  140. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js +335 -0
  141. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js.map +1 -0
  142. package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.d.ts +51 -0
  143. package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.js +693 -0
  144. package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.js.map +1 -0
  145. package/dist-client/pages/sv-project-complete.d.ts +31 -0
  146. package/dist-client/pages/sv-project-complete.js +359 -0
  147. package/dist-client/pages/sv-project-complete.js.map +1 -0
  148. package/dist-client/pages/sv-project-completed-list.d.ts +27 -0
  149. package/dist-client/pages/sv-project-completed-list.js +416 -0
  150. package/dist-client/pages/sv-project-completed-list.js.map +1 -0
  151. package/dist-client/pages/sv-project-detail.d.ts +73 -0
  152. package/dist-client/pages/sv-project-detail.js +1501 -0
  153. package/dist-client/pages/sv-project-detail.js.map +1 -0
  154. package/dist-client/pages/sv-project-list.d.ts +175 -0
  155. package/dist-client/pages/sv-project-list.js +581 -0
  156. package/dist-client/pages/sv-project-list.js.map +1 -0
  157. package/dist-client/pages/sv-project-update.d.ts +97 -0
  158. package/dist-client/pages/sv-project-update.js +1243 -0
  159. package/dist-client/pages/sv-project-update.js.map +1 -0
  160. package/dist-client/pages/sv-user-management.d.ts +6 -0
  161. package/dist-client/pages/sv-user-management.js +224 -0
  162. package/dist-client/pages/sv-user-management.js.map +1 -0
  163. package/dist-client/route.d.ts +1 -0
  164. package/dist-client/route.js +58 -0
  165. package/dist-client/route.js.map +1 -0
  166. package/dist-client/shared/complete-api.d.ts +45 -0
  167. package/dist-client/shared/complete-api.js +274 -0
  168. package/dist-client/shared/complete-api.js.map +1 -0
  169. package/dist-client/shared/func.d.ts +2 -0
  170. package/dist-client/shared/func.js +22 -0
  171. package/dist-client/shared/func.js.map +1 -0
  172. package/dist-client/shared/geo-group-mapping.d.ts +25 -0
  173. package/dist-client/shared/geo-group-mapping.js +189 -0
  174. package/dist-client/shared/geo-group-mapping.js.map +1 -0
  175. package/dist-client/shared/integration-fetch.d.ts +35 -0
  176. package/dist-client/shared/integration-fetch.js +53 -0
  177. package/dist-client/shared/integration-fetch.js.map +1 -0
  178. package/dist-client/shared/kpi-project-sync.d.ts +16 -0
  179. package/dist-client/shared/kpi-project-sync.js +129 -0
  180. package/dist-client/shared/kpi-project-sync.js.map +1 -0
  181. package/dist-client/themes/dark.css +51 -0
  182. package/dist-client/themes/light.css +51 -0
  183. package/dist-client/tsconfig.tsbuildinfo +1 -0
  184. package/dist-client/viewparts/menu-tools.d.ts +62 -0
  185. package/dist-client/viewparts/menu-tools.js +554 -0
  186. package/dist-client/viewparts/menu-tools.js.map +1 -0
  187. package/dist-client/viewparts/user-circle.d.ts +5 -0
  188. package/dist-client/viewparts/user-circle.js +26 -0
  189. package/dist-client/viewparts/user-circle.js.map +1 -0
  190. package/dist-server/index.d.ts +2 -0
  191. package/dist-server/index.js +6 -0
  192. package/dist-server/index.js.map +1 -0
  193. package/dist-server/migrations/index.d.ts +1 -0
  194. package/dist-server/migrations/index.js +12 -0
  195. package/dist-server/migrations/index.js.map +1 -0
  196. package/dist-server/scripts/calculate-kpi-scores.d.ts +10 -0
  197. package/dist-server/scripts/calculate-kpi-scores.js +333 -0
  198. package/dist-server/scripts/calculate-kpi-scores.js.map +1 -0
  199. package/dist-server/scripts/load-grade-data-migration.d.ts +14 -0
  200. package/dist-server/scripts/load-grade-data-migration.js +279 -0
  201. package/dist-server/scripts/load-grade-data-migration.js.map +1 -0
  202. package/dist-server/scripts/propagate-parent-kpi-values.d.ts +14 -0
  203. package/dist-server/scripts/propagate-parent-kpi-values.js +786 -0
  204. package/dist-server/scripts/propagate-parent-kpi-values.js.map +1 -0
  205. package/dist-server/scripts/recalculate-by-project-name.d.ts +2 -0
  206. package/dist-server/scripts/recalculate-by-project-name.js +72 -0
  207. package/dist-server/scripts/recalculate-by-project-name.js.map +1 -0
  208. package/dist-server/service/index.d.ts +4 -0
  209. package/dist-server/service/index.js +20 -0
  210. package/dist-server/service/index.js.map +1 -0
  211. package/dist-server/service/kpi-metric-value/index.d.ts +4 -0
  212. package/dist-server/service/kpi-metric-value/index.js +8 -0
  213. package/dist-server/service/kpi-metric-value/index.js.map +1 -0
  214. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.d.ts +198 -0
  215. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js +1067 -0
  216. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js.map +1 -0
  217. package/dist-server/service/kpi-metric-value/kpi-metric-value-query.d.ts +15 -0
  218. package/dist-server/service/kpi-metric-value/kpi-metric-value-query.js +72 -0
  219. package/dist-server/service/kpi-metric-value/kpi-metric-value-query.js.map +1 -0
  220. package/dist-server/service/kpi-stat/index.d.ts +4 -0
  221. package/dist-server/service/kpi-stat/index.js +8 -0
  222. package/dist-server/service/kpi-stat/index.js.map +1 -0
  223. package/dist-server/service/kpi-stat/kpi-stat-query.d.ts +13 -0
  224. package/dist-server/service/kpi-stat/kpi-stat-query.js +751 -0
  225. package/dist-server/service/kpi-stat/kpi-stat-query.js.map +1 -0
  226. package/dist-server/service/kpi-stat/kpi-stat-types.d.ts +32 -0
  227. package/dist-server/service/kpi-stat/kpi-stat-types.js +180 -0
  228. package/dist-server/service/kpi-stat/kpi-stat-types.js.map +1 -0
  229. package/dist-server/service/kpi-value/index.d.ts +3 -0
  230. package/dist-server/service/kpi-value/index.js +7 -0
  231. package/dist-server/service/kpi-value/index.js.map +1 -0
  232. package/dist-server/service/kpi-value/kpi-value-query.d.ts +8 -0
  233. package/dist-server/service/kpi-value/kpi-value-query.js +90 -0
  234. package/dist-server/service/kpi-value/kpi-value-query.js.map +1 -0
  235. package/dist-server/tsconfig.tsbuildinfo +1 -0
  236. package/package.json +5 -5
@@ -0,0 +1,1243 @@
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 { PageView, navigate } from '@operato/shell';
6
+ import { css, html } from 'lit';
7
+ import { customElement, state } from 'lit/decorators.js';
8
+ import { ScopedElementsMixin } from '@open-wc/scoped-elements';
9
+ import { client } from '@operato/graphql';
10
+ import { notify } from '@operato/layout';
11
+ import { OxPrompt } from '@operato/popup';
12
+ import { hasPrivilege } from '@things-factory/auth-base/dist-client';
13
+ import gql from 'graphql-tag';
14
+ import { SiteType } from '@dssp/project/dist-client/pages/project/project-list';
15
+ import '@dssp/project/dist-client/pages/lib/select2-component';
16
+ import './component/project-update-header';
17
+ import { isProjectTypeDomain, tenantHeaders } from '@dssp/project/dist-client/shared/domain-context';
18
+ import { inferGeoGroupFromPostalCode } from '../shared/geo-group-mapping';
19
+ import { syncProjectInfoToKpi } from '../shared/kpi-project-sync';
20
+ let SvProjectUpdate = class SvProjectUpdate extends ScopedElementsMixin(PageView) {
21
+ constructor() {
22
+ super(...arguments);
23
+ this.defaultProject = {
24
+ name: '',
25
+ documentNaming: '',
26
+ buildingComplex: {
27
+ address: '',
28
+ area: 0,
29
+ constructionCompany: '',
30
+ clientCompany: '',
31
+ supervisoryCompany: '',
32
+ designCompany: '',
33
+ constructionType: '',
34
+ buildings: []
35
+ }
36
+ };
37
+ this.projectId = '';
38
+ this.project = Object.assign({}, this.defaultProject);
39
+ this.selectedValues = [];
40
+ this.overallConstructorList = [];
41
+ this.taskConstructorList = [];
42
+ this.taskSupervisoryList = [];
43
+ this.overallSupervisoryList = [];
44
+ this.collectingBasicInfo = false;
45
+ this._isSuperUser = false;
46
+ /**
47
+ * 완료(COMPLETED) 상태 프로젝트를 진행중(ONGOING) 으로 되돌림.
48
+ * ProjectPatch 의 state 필드만 갱신 — 다른 폼 필드는 건드리지 않음.
49
+ */
50
+ this._onUnfinalize = async () => {
51
+ var _a, _b;
52
+ if (!((_a = this.project) === null || _a === void 0 ? void 0 : _a.id))
53
+ return;
54
+ const ok = await OxPrompt.open({
55
+ title: '완료 상태 해제',
56
+ confirmButton: { text: '확인' },
57
+ cancelButton: { text: '취소' }
58
+ });
59
+ if (!ok)
60
+ return;
61
+ const response = await client.mutate({
62
+ mutation: gql `
63
+ mutation UpdateProject($project: ProjectPatch!) {
64
+ updateProject(project: $project) {
65
+ id
66
+ state
67
+ }
68
+ }
69
+ `,
70
+ variables: { project: { id: this.project.id, state: 'ONGOING' } },
71
+ context: { headers: tenantHeaders((_b = this.project) === null || _b === void 0 ? void 0 : _b.code) }
72
+ });
73
+ if (!response.errors) {
74
+ notify({ message: '진행중 상태로 변경되었습니다.' });
75
+ await this.initProject(this.project.id);
76
+ }
77
+ };
78
+ /**
79
+ * 프로젝트 삭제 — 수퍼유저 전용(섹션 자체가 수퍼유저에게만 노출). 비밀번호 재확인 후 삭제.
80
+ * 활성 테넌트 상태에서는 삭제 불가(버튼 비활성 + 서버 재검증). 완료 시 리스트 페이지로 이동.
81
+ */
82
+ this._deleteProject = async () => {
83
+ var _a;
84
+ if (!((_a = this.project) === null || _a === void 0 ? void 0 : _a.id))
85
+ return;
86
+ if (this.project.tenantDomain) {
87
+ notify({ message: '활성 테넌트 상태에서는 삭제할 수 없습니다. 먼저 테넌트를 강등하세요.' });
88
+ return;
89
+ }
90
+ const password = await OxPrompt.prompt({
91
+ title: '프로젝트 삭제',
92
+ text: `"${this.project.name || ''}" 프로젝트를 영구 삭제합니다. 되돌릴 수 없습니다.\n계속하려면 비밀번호를 입력하세요.`,
93
+ type: 'warning',
94
+ confirmButton: { text: '삭제' },
95
+ cancelButton: { text: '취소' }
96
+ });
97
+ if (!password)
98
+ return;
99
+ try {
100
+ const response = await client.mutate({
101
+ mutation: gql `
102
+ mutation DeleteProjectWithPassword($projectId: String!, $password: String!) {
103
+ deleteProjectWithPassword(projectId: $projectId, password: $password)
104
+ }
105
+ `,
106
+ variables: { projectId: this.project.id, password }
107
+ });
108
+ if (response.errors) {
109
+ throw new Error(response.errors.map((e) => e.message).join('\n'));
110
+ }
111
+ notify({ message: '프로젝트가 삭제되었습니다.' });
112
+ navigate('project-list');
113
+ }
114
+ catch (err) {
115
+ notify({ message: err instanceof Error ? err.message : '삭제에 실패했습니다.' });
116
+ }
117
+ };
118
+ }
119
+ get context() {
120
+ var _a, _b;
121
+ return {
122
+ title: !isProjectTypeDomain() && ((_a = this.project) === null || _a === void 0 ? void 0 : _a.name) ? `프로젝트 정보 관리 - ${(_b = this.project) === null || _b === void 0 ? void 0 : _b.name}` : '프로젝트 정보 관리'
123
+ };
124
+ }
125
+ render() {
126
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z;
127
+ return html `
128
+ <dkpi-project-update-header
129
+ .projectId=${this.project.id || ''}
130
+ .projectState=${this.project.state || ''}
131
+ title="프로젝트 정보 관리"
132
+ @custom-click=${this._saveProject}
133
+ @unfinalize=${this._onUnfinalize}
134
+ ></dkpi-project-update-header>
135
+
136
+ <div body>
137
+ <div project-info>
138
+ <h3>기본 정보</h3>
139
+ <div row>
140
+ <span>프로젝트명</span>
141
+ <span
142
+ ><md-outlined-text-field
143
+ type="text"
144
+ name="name"
145
+ project
146
+ .value=${this.project.name || ''}
147
+ @input=${this._onInputChange}
148
+ ></md-outlined-text-field>
149
+ </span>
150
+ </div>
151
+ <div row>
152
+ <span>프로젝트 주소</span>
153
+ <span class="address-row">
154
+ <md-outlined-text-field
155
+ type="text"
156
+ name="address"
157
+ building-complex
158
+ .value=${((_b = (_a = this.project) === null || _a === void 0 ? void 0 : _a.buildingComplex) === null || _b === void 0 ? void 0 : _b.address) || ''}
159
+ @input=${this._onInputChange}
160
+ ></md-outlined-text-field>
161
+ <md-elevated-button
162
+ class="info-link-btn"
163
+ ?disabled=${this.collectingBasicInfo}
164
+ @click=${() => this._collectBasicInfo()}
165
+ title="입력된 주소로 세움터 건축물대장 정보를 가져와 폼을 채웁니다"
166
+ >
167
+ ${this.collectingBasicInfo ? '연동 중…' : '🔗 정보 연동'}
168
+ </md-elevated-button>
169
+ </span>
170
+ </div>
171
+ <div row>
172
+ <span>우편번호</span>
173
+ <span>
174
+ <md-outlined-text-field
175
+ type="text"
176
+ name="postalCode"
177
+ project
178
+ .value=${this.project.postalCode || ''}
179
+ @input=${this._onPostalCodeChange}
180
+ ></md-outlined-text-field>
181
+ </span>
182
+ </div>
183
+ <div row>
184
+ <span>연면적</span>
185
+ <span align-end
186
+ ><md-outlined-text-field
187
+ type="text"
188
+ name="area"
189
+ numeric
190
+ building-complex
191
+ .value=${((_e = (_d = (_c = this.project) === null || _c === void 0 ? void 0 : _c.buildingComplex) === null || _d === void 0 ? void 0 : _d.area) === null || _e === void 0 ? void 0 : _e.toString()) || ''}
192
+ @input=${this._onInputChange}
193
+ suffix-text="㎡"
194
+ ></md-outlined-text-field>
195
+ </span>
196
+ </div>
197
+
198
+ <div row>
199
+ <span>용적률</span>
200
+ <span align-end>
201
+ <md-outlined-text-field
202
+ type="text"
203
+ name="floorAreaRatio"
204
+ numeric
205
+ building-complex
206
+ .value=${((_h = (_g = (_f = this.project) === null || _f === void 0 ? void 0 : _f.buildingComplex) === null || _g === void 0 ? void 0 : _g.floorAreaRatio) === null || _h === void 0 ? void 0 : _h.toString()) || ''}
207
+ @input=${this._onInputChange}
208
+ suffix-text="%"
209
+ ></md-outlined-text-field>
210
+ </span>
211
+ </div>
212
+
213
+ <div row>
214
+ <span>착공일정 ~ 준공일정(계획)</span>
215
+ <span
216
+ ><input
217
+ type="date"
218
+ name="startDate"
219
+ project
220
+ .value=${this.project.startDate || ''}
221
+ @input=${this._onInputChange}
222
+ max="9999-12-31"
223
+ />
224
+ ~
225
+ <input
226
+ type="date"
227
+ name="endDate"
228
+ project
229
+ .value=${this.project.endDate || ''}
230
+ @input=${this._onInputChange}
231
+ max="9999-12-31"
232
+ />
233
+ </span>
234
+ </div>
235
+
236
+ <div row>
237
+ <span>유형</span>
238
+ <span
239
+ ><md-filled-select name="sectorType" project @change=${this._onInputChange}>
240
+ <md-select-option ?selected=${!this.project.sectorType} value="">
241
+ <div slot="headline">선택</div>
242
+ </md-select-option>
243
+ <md-select-option ?selected=${this.project.sectorType === 'PUBLIC'} value="PUBLIC">
244
+ <div slot="headline">공공</div>
245
+ </md-select-option>
246
+ <md-select-option ?selected=${this.project.sectorType === 'PRIVATE'} value="PRIVATE">
247
+ <div slot="headline">민간</div>
248
+ </md-select-option>
249
+ </md-filled-select>
250
+ </span>
251
+ </div>
252
+ <div row>
253
+ <span>용도</span>
254
+ <span
255
+ ><md-filled-select name="buildingUsage" project @change=${this._onInputChange}>
256
+ <md-select-option ?selected=${!this.project.buildingUsage} value="">
257
+ <div slot="headline">선택</div>
258
+ </md-select-option>
259
+ <md-select-option ?selected=${this.project.buildingUsage === 'RESIDENTIAL'} value="RESIDENTIAL">
260
+ <div slot="headline">주거</div>
261
+ </md-select-option>
262
+ <md-select-option ?selected=${this.project.buildingUsage === 'NON_RESIDENTIAL'} value="NON_RESIDENTIAL">
263
+ <div slot="headline">비주거</div>
264
+ </md-select-option>
265
+ </md-filled-select>
266
+ </span>
267
+ </div>
268
+
269
+ <div row>
270
+ <span>발주처</span>
271
+ <span
272
+ ><md-outlined-text-field
273
+ type="text"
274
+ name="clientCompany"
275
+ building-complex
276
+ .value=${((_k = (_j = this.project) === null || _j === void 0 ? void 0 : _j.buildingComplex) === null || _k === void 0 ? void 0 : _k.clientCompany) || ''}
277
+ @input=${this._onInputChange}
278
+ ></md-outlined-text-field>
279
+ </span>
280
+ </div>
281
+ <div row>
282
+ <span>건설사</span>
283
+ <span
284
+ ><md-outlined-text-field
285
+ type="text"
286
+ name="constructionCompany"
287
+ building-complex
288
+ .value=${((_m = (_l = this.project) === null || _l === void 0 ? void 0 : _l.buildingComplex) === null || _m === void 0 ? void 0 : _m.constructionCompany) || ''}
289
+ @input=${this._onInputChange}
290
+ ></md-outlined-text-field>
291
+ </span>
292
+ </div>
293
+ <div row>
294
+ <span>설계사</span>
295
+ <span
296
+ ><md-outlined-text-field
297
+ type="text"
298
+ name="designCompany"
299
+ building-complex
300
+ .value=${((_p = (_o = this.project) === null || _o === void 0 ? void 0 : _o.buildingComplex) === null || _p === void 0 ? void 0 : _p.designCompany) || ''}
301
+ @input=${this._onInputChange}
302
+ ></md-outlined-text-field>
303
+ </span>
304
+ </div>
305
+ <div row>
306
+ <span>감리사</span>
307
+ <span
308
+ ><md-outlined-text-field
309
+ type="text"
310
+ name="supervisoryCompany"
311
+ building-complex
312
+ .value=${((_r = (_q = this.project) === null || _q === void 0 ? void 0 : _q.buildingComplex) === null || _r === void 0 ? void 0 : _r.supervisoryCompany) || ''}
313
+ @input=${this._onInputChange}
314
+ ></md-outlined-text-field>
315
+ </span>
316
+ </div>
317
+
318
+ <div row>
319
+ <span>대표사진 업로드</span>
320
+ <span>
321
+ <ox-input-image
322
+ name="mainPhoto"
323
+ value=${((_t = (_s = this.project) === null || _s === void 0 ? void 0 : _s.mainPhoto) === null || _t === void 0 ? void 0 : _t.fullpath) || ''}
324
+ @change=${this.onCreateAttachment.bind(this)}
325
+ ></ox-input-image>
326
+ </span>
327
+ </div>
328
+ <div row>
329
+ <span>계획공사비</span>
330
+ <span
331
+ ><md-outlined-text-field
332
+ type="text"
333
+ name="constructionCost"
334
+ numeric
335
+ building-complex
336
+ .value=${((_w = (_v = (_u = this.project) === null || _u === void 0 ? void 0 : _u.buildingComplex) === null || _v === void 0 ? void 0 : _v.constructionCost) === null || _w === void 0 ? void 0 : _w.toString()) || ''}
337
+ @input=${this._onInputChange}
338
+ suffix-text="억원"
339
+ ></md-outlined-text-field>
340
+ </span>
341
+ </div>
342
+ <div row>
343
+ <span>총 투입인력(M/M)</span>
344
+ <span
345
+ ><md-outlined-text-field
346
+ type="text"
347
+ name="workerCount"
348
+ numeric
349
+ building-complex
350
+ .value=${((_z = (_y = (_x = this.project) === null || _x === void 0 ? void 0 : _x.buildingComplex) === null || _y === void 0 ? void 0 : _y.workerCount) === null || _z === void 0 ? void 0 : _z.toString()) || ''}
351
+ @input=${this._onInputChange}
352
+ suffix-text="(M/M)"
353
+ ></md-outlined-text-field>
354
+ </span>
355
+ </div>
356
+ </div>
357
+
358
+ <div detail-info>
359
+ <div>
360
+ <h3>테넌트 관리</h3>
361
+ <div row>
362
+ <span>관리번호</span>
363
+ <span>
364
+ ${this.project.tenantDomain
365
+ ? html `<a href=${`/project/${this.project.code}/`} target="_blank" title="테넌트로 이동"
366
+ >${this.project.code} <md-icon style="font-size:14px;vertical-align:middle">open_in_new</md-icon></a
367
+ >`
368
+ : this.project.code
369
+ ? html `${this.project.code}`
370
+ : html `<span style="color:#999">미발번 (승격 시 자동 부여)</span>`}
371
+ </span>
372
+ </div>
373
+ <div row>
374
+ <span>현재 상태</span>
375
+ <span>
376
+ ${this.project.tenantDomain
377
+ ? html `<b style="color:#42b382">활성 테넌트</b>`
378
+ : this.project.code
379
+ ? html `<b style="color:#aa6633">강등됨</b> (재승격 시 ${this.project.code} 재사용)`
380
+ : html `<b style="color:#999">미승격</b>`}
381
+ </span>
382
+ </div>
383
+ <div row>
384
+ <span></span>
385
+ <span tenant-actions>
386
+ ${this.project.tenantDomain
387
+ ? html `<md-elevated-button @click=${this._demoteTenant} ?disabled=${!this.project.id}>
388
+ <md-icon slot="icon">link_off</md-icon>테넌트 강등
389
+ </md-elevated-button>`
390
+ : html `<md-elevated-button green @click=${this._promoteTenant} ?disabled=${!this.project.id}>
391
+ <md-icon slot="icon">verified</md-icon>테넌트 승격
392
+ </md-elevated-button>`}
393
+ </span>
394
+ </div>
395
+ </div>
396
+ ${
397
+ // 프로젝트 삭제 — 수퍼유저에게만 노출. 테넌트 관리와 **같은 detail-info 컬럼 안**에
398
+ // 두 번째 카드로 세로로 쌓아 테넌트 섹션 폭이 줄지 않게 한다.
399
+ this._isSuperUser
400
+ ? html `
401
+ <div danger-card>
402
+ <h3>프로젝트 삭제</h3>
403
+ <div row>
404
+ <span>주의</span>
405
+ <span>
406
+ 이 프로젝트와 관련 데이터가 <b style="color:#c62828">영구 삭제</b>됩니다. 되돌릴 수 없습니다.
407
+ ${this.project.tenantDomain
408
+ ? html `<br /><b style="color:#aa6633">활성 테넌트</b>는 삭제할 수 없습니다 — 먼저 테넌트를 강등하세요.`
409
+ : ''}
410
+ </span>
411
+ </div>
412
+ <div row>
413
+ <span></span>
414
+ <span tenant-actions>
415
+ <md-elevated-button
416
+ danger
417
+ @click=${this._deleteProject}
418
+ ?disabled=${!this.project.id || !!this.project.tenantDomain}
419
+ >
420
+ <md-icon slot="icon">delete_forever</md-icon>프로젝트 삭제
421
+ </md-elevated-button>
422
+ </span>
423
+ </div>
424
+ </div>
425
+ `
426
+ : ''}
427
+ </div>
428
+ </div>
429
+ `;
430
+ }
431
+ async pageInitialized(lifecycle) { }
432
+ async pageUpdated(changes, lifecycle) {
433
+ if (!this.active)
434
+ return;
435
+ this.projectId = await this._resolveProjectId(lifecycle.resourceId || '');
436
+ await this.initProject(this.projectId);
437
+ }
438
+ /**
439
+ * resourceId 를 정상 Project.id (UUID) 로 변환한다.
440
+ * - UUID 형식이면 그대로 사용
441
+ * - 그 외 (code 또는 비어있음) 에는 현재 테넌트 컨텍스트의 프로젝트 조회
442
+ */
443
+ async _resolveProjectId(resourceId) {
444
+ var _a, _b;
445
+ if (resourceId && resourceId.length === 36)
446
+ return resourceId;
447
+ const response = await client.query({
448
+ query: gql `
449
+ query CurrentProject {
450
+ currentProject {
451
+ id
452
+ }
453
+ }
454
+ `
455
+ });
456
+ return ((_b = (_a = response.data) === null || _a === void 0 ? void 0 : _a.currentProject) === null || _b === void 0 ? void 0 : _b.id) || '';
457
+ }
458
+ async initProject(projectId = '') {
459
+ var _a, _b, _c, _d;
460
+ // 프로젝트 삭제 섹션은 수퍼유저에게만 노출 (privilege/category 없이 superUserGranted 만
461
+ // 주면 hasPrivilege 는 순수 수퍼유저 여부를 반환).
462
+ this._isSuperUser = !!(await hasPrivilege({ superUserGranted: true }));
463
+ const response = await client.query({
464
+ query: gql `
465
+ query Project($id: String!, $filters: [Filter!]) {
466
+ project(id: $id) {
467
+ id
468
+ code
469
+ state
470
+ tenantDomain {
471
+ id
472
+ subdomain
473
+ name
474
+ }
475
+ name
476
+ documentNaming
477
+ sectorType
478
+ buildingUsage
479
+ postalCode
480
+ geoGroup
481
+ startDate
482
+ endDate
483
+ mainPhoto {
484
+ fullpath
485
+ }
486
+ totalProgress
487
+ weeklyProgress
488
+ kpi
489
+ inspPassRate
490
+ robotProgressRate
491
+ structuralSafetyRate
492
+ robotCount
493
+ buildingComplex {
494
+ id
495
+ address
496
+ siteType
497
+ latitude
498
+ longitude
499
+ area
500
+ coverageRatio
501
+ floorAreaRatio
502
+ structureType
503
+ workerCount
504
+ designChangeCount
505
+ plannedProgress
506
+ actualProgress
507
+ clientCompany
508
+ constructionCompany
509
+ supervisoryCompany
510
+ designCompany
511
+ drawing {
512
+ id
513
+ name
514
+ }
515
+ constructionType
516
+ constructionCost
517
+ etc
518
+ notice
519
+ householdCount
520
+ buildingCount
521
+ overallConstructorEmails
522
+ taskConstructorEmails
523
+ overallSupervisoryEmails
524
+ taskSupervisoryEmails
525
+ virtualTourLink
526
+ buildings {
527
+ id
528
+ name
529
+ floorCount
530
+ hasBasement
531
+ basementFloorCount
532
+ }
533
+ }
534
+ }
535
+
536
+ employees(filters: $filters) {
537
+ items {
538
+ id
539
+ name
540
+ jobResponsibility
541
+ active
542
+ user {
543
+ id
544
+ name
545
+ email
546
+ }
547
+ }
548
+ }
549
+ }
550
+ `,
551
+ variables: {
552
+ id: projectId,
553
+ filters: [
554
+ {
555
+ name: 'active',
556
+ operator: 'eq',
557
+ value: true
558
+ },
559
+ {
560
+ name: 'userId',
561
+ operator: 'is_not_null',
562
+ value: ''
563
+ }
564
+ ]
565
+ },
566
+ context: { headers: tenantHeaders((_a = this.project) === null || _a === void 0 ? void 0 : _a.code) }
567
+ });
568
+ this.project = ((_b = response.data) === null || _b === void 0 ? void 0 : _b.project) || Object.assign({}, this.defaultProject);
569
+ const items = ((_d = (_c = response.data) === null || _c === void 0 ? void 0 : _c.employees) === null || _d === void 0 ? void 0 : _d.items) || [];
570
+ this.overallConstructorList = this._filterUserByPermission(items, 'OVERALL_CONSTRUCTOR');
571
+ this.taskConstructorList = this._filterUserByPermission(items, 'TASK_CONSTRUCTOR');
572
+ this.overallSupervisoryList = this._filterUserByPermission(items, 'OVERALL_SUPERVISORY');
573
+ this.taskSupervisoryList = this._filterUserByPermission(items, 'TASK_SUPERVISORY');
574
+ this.updateContext();
575
+ }
576
+ _filterUserByPermission(userList, permission) {
577
+ return userList
578
+ .filter(v => v.jobResponsibility == permission || v.jobResponsibility == 'ADMIN')
579
+ .map(v => ({ name: v.name, value: v.user.email }));
580
+ }
581
+ _stripTypename(obj) {
582
+ if (Array.isArray(obj)) {
583
+ return obj.map(item => this._stripTypename(item));
584
+ }
585
+ if (obj && typeof obj === 'object' && !(obj instanceof File)) {
586
+ const cleaned = {};
587
+ for (const [key, value] of Object.entries(obj)) {
588
+ if (key === '__typename')
589
+ continue;
590
+ cleaned[key] = this._stripTypename(value);
591
+ }
592
+ return cleaned;
593
+ }
594
+ return obj;
595
+ }
596
+ async _saveProject() {
597
+ var _a;
598
+ // 첨부 파일 필드 제거 (첨부 파일은 {filename}Upload 로 전송)
599
+ delete this.project.mainPhoto;
600
+ delete this.project.buildingComplex.drawing;
601
+ // ProjectPatch 입력 타입에 없는 표시 전용 필드 제거 (테넌트 승격 UI 표시용)
602
+ const projectCode = this.project.code; // 도메인 헤더 용으로 미리 보존
603
+ delete this.project.code;
604
+ delete this.project.tenantDomain;
605
+ // state 는 raw 값('10'/'20')으로 로드되지만 ProjectPatch 는 enum 키 기대 — 일반
606
+ // save 에서는 state 변경 의도 없으므로 patch 에서 제외. (state 변경은 _onUnfinalize 별도)
607
+ delete this.project.state;
608
+ // buildingComplex.siteType 가 SiteType enum 외 값 ('BUILDING' 같은 옛 잔재) 이면
609
+ // mutation validation 실패. enum 에 없으면 null 로 정정.
610
+ const validSiteTypes = new Set(Object.values(SiteType));
611
+ if (((_a = this.project.buildingComplex) === null || _a === void 0 ? void 0 : _a.siteType) && !validSiteTypes.has(this.project.buildingComplex.siteType)) {
612
+ this.project.buildingComplex.siteType = null;
613
+ }
614
+ const response = await client.mutate({
615
+ mutation: gql `
616
+ mutation UpdateProject($project: ProjectPatch!) {
617
+ updateProject(project: $project) {
618
+ id
619
+ }
620
+ }
621
+ `,
622
+ variables: {
623
+ project: this._stripTypename(this.project)
624
+ },
625
+ context: {
626
+ hasUpload: true,
627
+ headers: tenantHeaders(projectCode)
628
+ }
629
+ });
630
+ if (!response.errors) {
631
+ // 양방향 동기 — 연면적/용적률/투입인력/설계변경건을 KPI 기준정보(KpiMetricValue)에 반영하고,
632
+ // 공사일정(startDate/endDate)은 KPI "계획기간"(constructionPeriod)으로 단방향 반영.
633
+ // 보조 동기 실패가 저장 성공 UX 를 막지 않도록 warn 만.
634
+ try {
635
+ await syncProjectInfoToKpi(this.project, projectCode);
636
+ }
637
+ catch (e) {
638
+ console.warn('[프로젝트정보→KPI] KpiMetricValue 동기 실패', e);
639
+ }
640
+ notify({ message: '저장에 성공하였습니다.' });
641
+ }
642
+ }
643
+ /**
644
+ * 주소 옆 "정보 연동" 버튼 handler.
645
+ * - 현재 폼의 주소를 backend 로 보냄 → backend 가 EnvVar 갱신 + 세움터 시나리오 호출
646
+ * - 결과 (한글 raw + 숫자) 를 받아 BuildingComplex 폼 필드들에 매핑
647
+ * · siteType (한글) → SiteType enum 변환 (BC.siteType, enum 컬럼)
648
+ * · siteType (한글) → BC.constructionType 그대로 (자유 텍스트 컬럼)
649
+ * · area / floorAreaRatio / coverageRatio / householdCount / buildingCount /
650
+ * structureType → BC 동명 컬럼에 직접 대입
651
+ * · upperFloorCount / lowerFloorCount / bldNm / useAprDay / pmsDay → BC 컬럼
652
+ * 없음, 매핑 skip (참고용 콘솔 로그만)
653
+ */
654
+ async _collectBasicInfo() {
655
+ var _a, _b, _c, _d;
656
+ const address = (((_b = (_a = this.project) === null || _a === void 0 ? void 0 : _a.buildingComplex) === null || _b === void 0 ? void 0 : _b.address) || '').trim();
657
+ if (!address) {
658
+ notify({ message: '먼저 프로젝트 주소를 입력해 주세요.' });
659
+ return;
660
+ }
661
+ this.collectingBasicInfo = true;
662
+ try {
663
+ const response = await client.mutate({
664
+ mutation: gql `
665
+ mutation CollectProjectBasicInfo($projectId: String!, $address: String) {
666
+ collectProjectBasicInfo(projectId: $projectId, address: $address) {
667
+ source
668
+ label
669
+ ok
670
+ message
671
+ data
672
+ }
673
+ }
674
+ `,
675
+ variables: { projectId: this.project.id, address },
676
+ context: { headers: tenantHeaders((_c = this.project) === null || _c === void 0 ? void 0 : _c.code) }
677
+ });
678
+ if (response.errors) {
679
+ throw new Error(response.errors.map((e) => e.message).join('\n'));
680
+ }
681
+ const result = (_d = response.data) === null || _d === void 0 ? void 0 : _d.collectProjectBasicInfo;
682
+ if (!(result === null || result === void 0 ? void 0 : result.ok)) {
683
+ notify({ message: (result === null || result === void 0 ? void 0 : result.message) || '업데이트할 정보가 없습니다.' });
684
+ return;
685
+ }
686
+ const data = result.data || {};
687
+ this._applyBasicInfoToForm(data);
688
+ this.requestUpdate();
689
+ notify({ message: '외부 시스템에서 기본 정보를 가져왔습니다. 검토 후 [저장]을 눌러주세요.' });
690
+ }
691
+ catch (err) {
692
+ console.warn('[정보 연동] 실패', err);
693
+ notify({ message: '외부 시스템 연동이 일시적으로 안 됩니다.' });
694
+ }
695
+ finally {
696
+ this.collectingBasicInfo = false;
697
+ }
698
+ }
699
+ /**
700
+ * 한글 주용도명 → SiteType enum.
701
+ * - NFC normalize + 공백 제거로 보이지 않는 문자/표기 차이 대응
702
+ * - 매핑 미존재시 null
703
+ */
704
+ _siteTypeFromKorean(korean) {
705
+ if (!korean)
706
+ return null;
707
+ const KEYS = {
708
+ 공동주택: 'APARTMENT_COMPLEX',
709
+ 단독주택: 'DETACHED_HOUSE',
710
+ 제1종근린생활시설: 'NEIGHBORHOOD_FACILITY',
711
+ 제2종근린생활시설: 'NEIGHBORHOOD_FACILITY',
712
+ 근린생활시설: 'NEIGHBORHOOD_FACILITY',
713
+ 업무시설: 'OFFICE',
714
+ 판매시설: 'COMMERCIAL',
715
+ 교육연구시설: 'EDUCATION',
716
+ 의료시설: 'MEDICAL',
717
+ 문화및집회시설: 'CULTURAL',
718
+ 종교시설: 'RELIGIOUS',
719
+ 공장: 'INDUSTRIAL',
720
+ 창고시설: 'INDUSTRIAL',
721
+ 운수시설: 'TRANSPORT'
722
+ };
723
+ const key = korean.normalize('NFC').replace(/\s+/g, '').trim();
724
+ return KEYS[key] || null;
725
+ }
726
+ /**
727
+ * 시나리오 result data 를 폼에 매핑 — BuildingComplex 와 Project 양쪽.
728
+ * 빈 값(null/undefined/'')은 기존 값 유지 — 자동 수집이 사용자 입력을 무리하게 덮지 않도록.
729
+ */
730
+ _applyBasicInfoToForm(data) {
731
+ const bc = Object.assign({}, this.project.buildingComplex);
732
+ const setBC = (key, value) => {
733
+ if (value === null || value === undefined || value === '')
734
+ return;
735
+ bc[key] = value;
736
+ };
737
+ setBC('area', this._toNum(data.area));
738
+ setBC('floorAreaRatio', this._toNum(data.floorAreaRatio));
739
+ setBC('coverageRatio', this._toNum(data.coverageRatio));
740
+ setBC('householdCount', this._toNum(data.householdCount));
741
+ setBC('buildingCount', this._toNum(data.buildingCount));
742
+ setBC('structureType', data.structureType);
743
+ // siteType: 한글 → SiteType enum 매핑. 매핑 안 되면 BC.siteType 빈 채로.
744
+ if (data.siteType) {
745
+ const enumVal = this._siteTypeFromKorean(data.siteType);
746
+ if (enumVal)
747
+ bc.siteType = enumVal;
748
+ }
749
+ // Project 엔티티 컬럼 — 착공일/준공일.
750
+ // 세움터 useAprDay (사용승인일) = 준공으로 매핑. stcnsDay = 착공.
751
+ const project = Object.assign(Object.assign({}, this.project), { buildingComplex: bc });
752
+ if (data.startDate)
753
+ project.startDate = data.startDate;
754
+ if (data.endDate)
755
+ project.endDate = data.endDate;
756
+ this.project = project;
757
+ // 참고 메타 — BC/Project 직접 컬럼 없음, 콘솔로만 (permitDate=건축허가일, bldNm 등)
758
+ console.log('[정보 연동] meta:', JSON.stringify({
759
+ upperFloorCount: data.upperFloorCount,
760
+ lowerFloorCount: data.lowerFloorCount,
761
+ bldNm: data.bldNm,
762
+ permitDate: data.permitDate
763
+ }));
764
+ }
765
+ _toNum(v) {
766
+ if (v === null || v === undefined || v === '')
767
+ return null;
768
+ const n = Number(v);
769
+ return Number.isFinite(n) ? n : null;
770
+ }
771
+ async _promoteTenant() {
772
+ if (!this.project.id)
773
+ return;
774
+ if (!confirm('이 프로젝트를 테넌트로 승격하시겠습니까?\n승격 시 관리번호(YYYY-NNNNN)가 자동 발번되며 변경할 수 없습니다.')) {
775
+ return;
776
+ }
777
+ const response = await client.mutate({
778
+ mutation: gql `
779
+ mutation PromoteProjectToTenant($projectId: String!) {
780
+ promoteProjectToTenant(projectId: $projectId) {
781
+ id
782
+ code
783
+ }
784
+ }
785
+ `,
786
+ variables: { projectId: this.project.id }
787
+ });
788
+ if (!response.errors) {
789
+ notify({ message: `테넌트로 승격되었습니다. 관리번호: ${response.data.promoteProjectToTenant.code}` });
790
+ await this.initProject(this.projectId);
791
+ }
792
+ }
793
+ async _demoteTenant() {
794
+ if (!this.project.id)
795
+ return;
796
+ if (!confirm('이 프로젝트의 테넌트를 강등하시겠습니까?\n관리번호는 보존되며 재승격 시 같은 번호가 재사용됩니다.')) {
797
+ return;
798
+ }
799
+ const response = await client.mutate({
800
+ mutation: gql `
801
+ mutation DemoteProjectTenant($projectId: String!) {
802
+ demoteProjectTenant(projectId: $projectId)
803
+ }
804
+ `,
805
+ variables: { projectId: this.project.id }
806
+ });
807
+ if (!response.errors) {
808
+ notify({ message: '테넌트가 강등되었습니다.' });
809
+ await this.initProject(this.projectId);
810
+ }
811
+ }
812
+ // 동 적용 버튼을 누르면 입력한 수 만큼 해당 단지에 동 데이터 생성
813
+ _setBuilding() {
814
+ var _a, _b, _c, _d, _e;
815
+ const buildingCount = ((_b = (_a = this.project) === null || _a === void 0 ? void 0 : _a.buildingComplex) === null || _b === void 0 ? void 0 : _b.buildingCount) || 0;
816
+ const buildingInitData = { name: undefined, floorCount: undefined };
817
+ // 빌딩 데이터가 없으면 빈 배열 넣어줌
818
+ if (!((_e = (_d = (_c = this.project) === null || _c === void 0 ? void 0 : _c.buildingComplex) === null || _d === void 0 ? void 0 : _d.buildings) === null || _e === void 0 ? void 0 : _e.length)) {
819
+ this.project.buildingComplex.buildings = [];
820
+ }
821
+ if (this.project.buildingComplex.buildings.length >= buildingCount) {
822
+ // 동 수가 더 작게 들어오면 기존 배열을 필요한 크기만큼 잘라내기
823
+ this.project.buildingComplex.buildings = [...this.project.buildingComplex.buildings.slice(0, buildingCount)];
824
+ }
825
+ else {
826
+ // 동수가 더 크게 들어오면 기존 배열 + 빈 값을 채움
827
+ const additionalCount = buildingCount - this.project.buildingComplex.buildings.length;
828
+ const additionalBuildings = Array.from({ length: additionalCount }, () => (Object.assign({}, buildingInitData)));
829
+ this.project.buildingComplex.buildings = [...this.project.buildingComplex.buildings, ...additionalBuildings];
830
+ }
831
+ // 리렌더링
832
+ this.project = Object.assign({}, this.project);
833
+ }
834
+ // Input 요소의 값이 변경될 때 호출되는 콜백 함수
835
+ /**
836
+ * 우편번호 입력 핸들러 — postalCode 갱신과 동시에 앞 2자리 prefix 로 geoGroup
837
+ * best-guess 자동 매핑. 매핑 안 되면 기존 값 유지.
838
+ */
839
+ _onPostalCodeChange(event) {
840
+ const target = event.target;
841
+ const value = target.value;
842
+ this.project.postalCode = value;
843
+ const inferred = inferGeoGroupFromPostalCode(value);
844
+ if (inferred)
845
+ this.project.geoGroup = inferred;
846
+ }
847
+ _onInputChange(event, idx) {
848
+ const target = event.target;
849
+ let inputVal = target.value;
850
+ // 숫자 타입은 다른 문자 입력 제거
851
+ if (target.hasAttribute('numeric')) {
852
+ inputVal = Number(inputVal.replace(/[^\d.]/g, ''));
853
+ }
854
+ if (target.hasAttribute('project')) {
855
+ this.project[target.name] = inputVal;
856
+ // documentNaming 필드가 변경되면 실시간으로 미리보기 업데이트
857
+ if (target.name === 'documentNaming') {
858
+ this.requestUpdate();
859
+ }
860
+ }
861
+ else if (target.hasAttribute('building-complex')) {
862
+ this.project.buildingComplex[target.name] = inputVal;
863
+ }
864
+ else if (target.hasAttribute('building')) {
865
+ this.project.buildingComplex.buildings[idx][target.name] = inputVal;
866
+ }
867
+ }
868
+ // 이미지 업로드
869
+ async onCreateAttachment(e) {
870
+ const target = e.target;
871
+ const file = (target.name === 'mainPhoto' ? e.detail : e.detail[0]) || null;
872
+ if (target.name === 'mainPhoto') {
873
+ this.project.mainPhotoUpload = file;
874
+ }
875
+ else {
876
+ this.project.buildingComplex.drawingUpload = file;
877
+ }
878
+ }
879
+ _handleSelectionChange(e) {
880
+ const name = e.target.getAttribute('name');
881
+ const selectedValues = e.detail.selectedValues;
882
+ this.project.buildingComplex[name] = selectedValues;
883
+ }
884
+ _toggleBasement(idx) {
885
+ const building = this.project.buildingComplex.buildings[idx];
886
+ building.hasBasement = !building.hasBasement;
887
+ // 층 수가 0이거나 체크 해제시 지하층수 초기화 (플레이스 홀더 나오게 하기 위함)
888
+ if (building.basementFloorCount == 0 || !building.hasBasement) {
889
+ building.basementFloorCount = undefined;
890
+ }
891
+ this.project.buildingComplex.buildings[idx]['hasBasement'] = building.hasBasement;
892
+ this.requestUpdate();
893
+ }
894
+ };
895
+ SvProjectUpdate.styles = [
896
+ css `
897
+ :host {
898
+ display: grid;
899
+ grid-template-rows: 55px auto;
900
+ color: #4e5055;
901
+
902
+ width: 100%;
903
+ background-color: var(--md-sys-color-background, #f6f6f6);
904
+ overflow-y: auto;
905
+
906
+ --grid-record-emphasized-background-color: red;
907
+ --grid-record-emphasized-color: yellow;
908
+ }
909
+
910
+ md-outlined-text-field {
911
+ width: 100%;
912
+
913
+ --md-outlined-text-field-container-shape: 5px;
914
+ --md-outlined-text-field-outline-color: rgba(51, 51, 51, 0.2);
915
+ --md-outlined-text-field-focus-outline-color: #1f7fd9;
916
+ --md-outlined-text-field-focus-outline-width: 1px;
917
+ --md-sys-color-primary: #586878;
918
+ --md-outlined-text-field-input-text-size: 14px;
919
+ --md-outlined-field-bottom-space: 3px;
920
+ --md-outlined-field-top-space: 3px;
921
+ --md-outlined-field-leading-space: var(--spacing-medium, 8px);
922
+ --md-outlined-field-trailing-space: var(--spacing-medium, 8px);
923
+ }
924
+ select2-component {
925
+ width: 100%;
926
+ }
927
+ md-filled-select {
928
+ width: 100%;
929
+ --md-filled-select-text-field-container-color: transparent;
930
+ --md-filled-select-text-field-active-indicator-color: #999;
931
+ --md-filled-select-text-field-input-text-size: 14px;
932
+ --md-filled-select-text-field-input-text-line-height: 6px;
933
+ }
934
+ input[type='date'] {
935
+ border: 1px solid rgba(51, 51, 51, 0.2);
936
+ padding: var(--spacing-small, 4px) var(--spacing-medium, 8px);
937
+ border-radius: 5px;
938
+ }
939
+ md-outlined-text-field[type='textarea'] {
940
+ width: 100%;
941
+ height: 120px;
942
+ resize: none;
943
+ }
944
+
945
+ ox-input-image {
946
+ width: 120px;
947
+ height: 90px;
948
+ }
949
+ ox-input-file {
950
+ width: 120px;
951
+ height: 90px;
952
+ padding: 0;
953
+ }
954
+
955
+ div[body] {
956
+ display: flex;
957
+ margin: var(--spacing-large, 12px);
958
+ margin-top: 0;
959
+ gap: var(--spacing-medium, 8px);
960
+
961
+ & > div {
962
+ display: flex;
963
+ height: fit-content;
964
+ flex-direction: column;
965
+ padding: var(--spacing-large, 12px);
966
+ background-color: var(--md-sys-color-on-primary);
967
+ border: 1px solid #cccccc80;
968
+ border-radius: 5px;
969
+ gap: var(--spacing-medium, 8px);
970
+
971
+ h3 {
972
+ color: #2e79be;
973
+ font-size: 16px;
974
+ margin: 0px;
975
+ }
976
+
977
+ div[row] {
978
+ display: flex;
979
+ align-items: center;
980
+
981
+ span:first-child {
982
+ flex: 0.3;
983
+ text-align: right;
984
+ font-size: 14px;
985
+ }
986
+
987
+ span:last-child {
988
+ flex: 0.7;
989
+ display: flex;
990
+ gap: var(--spacing-medium, 8px);
991
+ margin-left: var(--spacing-medium, 8px);
992
+ align-items: center;
993
+
994
+ &.address-row {
995
+ md-outlined-text-field {
996
+ flex: 1;
997
+ }
998
+ .info-link-btn {
999
+ flex: 0 0 auto;
1000
+ --md-elevated-button-container-color: #2e79be;
1001
+ --md-elevated-button-label-text-color: #fff;
1002
+ --md-elevated-button-container-height: 36px;
1003
+ }
1004
+ }
1005
+
1006
+ &[align-end] {
1007
+ align-items: end;
1008
+ }
1009
+ }
1010
+ }
1011
+ }
1012
+
1013
+ div[project-info] {
1014
+ flex: 0.55;
1015
+ }
1016
+
1017
+ div[document-preview] {
1018
+ width: 100%;
1019
+ font-size: 12px;
1020
+ color: #2e79be;
1021
+ font-weight: 500;
1022
+ background-color: #f8f9fa;
1023
+ padding: 8px 12px;
1024
+ border-radius: 4px;
1025
+ border-left: 3px solid #2e79be;
1026
+ margin-top: 4px;
1027
+ }
1028
+
1029
+ div[detail-info] {
1030
+ flex: 0.45;
1031
+ padding: 0px;
1032
+ gap: 10px;
1033
+ background-color: transparent;
1034
+ border: none;
1035
+
1036
+ & > div {
1037
+ display: flex;
1038
+ flex-direction: column;
1039
+ gap: var(--spacing-medium, 8px);
1040
+ border: 1px solid #cccccc80;
1041
+ border-radius: 5px;
1042
+ padding: var(--spacing-large, 12px);
1043
+ background-color: var(--md-sys-color-on-primary);
1044
+
1045
+ md-outlined-text-field[type='text'] {
1046
+ width: 60%;
1047
+ }
1048
+
1049
+ md-elevated-button {
1050
+ --md-elevated-button-container-height: 30px;
1051
+ --md-elevated-button-container-color: var(--md-sys-color-on-primary);
1052
+ --md-elevated-button-label-text-size: 16px;
1053
+ }
1054
+ md-elevated-button[danger] {
1055
+ --md-elevated-button-container-color: #c62828;
1056
+ --md-elevated-button-label-text-color: #fff;
1057
+ --md-elevated-button-icon-color: #fff;
1058
+ }
1059
+ hr {
1060
+ border: 1px rgba(51, 51, 51, 0.1) dashed;
1061
+ width: 100%;
1062
+ margin-bottom: var(--spacing-tiny, 2px);
1063
+ }
1064
+ div[warn] {
1065
+ font-size: 12px;
1066
+ color: red;
1067
+ margin-left: var(--spacing-small, 4px);
1068
+ margin-bottom: var(--spacing-small, 4px);
1069
+ }
1070
+
1071
+ md-outlined-button {
1072
+ min-height: 30px;
1073
+ padding: 0 var(--spacing-large, 12px);
1074
+ box-shadow: 1px 1px 1px #0000001a;
1075
+ --md-outlined-button-label-text-color: #586878;
1076
+ --md-outlined-button-label-text-weight: bold;
1077
+ --md-sys-color-outline: rgba(51, 51, 51, 0.2);
1078
+ }
1079
+
1080
+ div[row] {
1081
+ span:first-child {
1082
+ flex: 0.2;
1083
+ }
1084
+ span:last-child {
1085
+ flex: 0.8;
1086
+ }
1087
+ }
1088
+
1089
+ div[separate-container] {
1090
+ display: grid;
1091
+ grid-template-columns: repeat(2, 1fr);
1092
+ gap: 8px 30px;
1093
+ margin-left: 15px;
1094
+
1095
+ md-outlined-text-field {
1096
+ width: 80px;
1097
+ }
1098
+
1099
+ & > div {
1100
+ display: flex;
1101
+ gap: 15px;
1102
+
1103
+ span[floor-row] {
1104
+ display: inline-flex;
1105
+ align-items: center;
1106
+ margin-left: 5px;
1107
+ }
1108
+
1109
+ span[floor-title] {
1110
+ min-width: 33px;
1111
+ margin-right: var(--spacing-small, 4px);
1112
+ }
1113
+ span:first-child {
1114
+ flex: 0.4;
1115
+ justify-content: flex-start;
1116
+ display: flex;
1117
+ }
1118
+ span:last-child {
1119
+ flex: 0.6;
1120
+ display: flex;
1121
+ align-items: center;
1122
+ padding-left: var(--spacing-medium, 8px);
1123
+ }
1124
+ }
1125
+
1126
+ div[basement-container] {
1127
+ display: flex;
1128
+ align-items: center;
1129
+ gap: 4px;
1130
+
1131
+ div[basement-checkbox] {
1132
+ display: flex;
1133
+ align-items: center;
1134
+ cursor: pointer;
1135
+
1136
+ md-icon {
1137
+ --md-icon-size: 25px;
1138
+ color: #808080;
1139
+ transition: opacity 0.2s ease;
1140
+ }
1141
+
1142
+ &[disabled] {
1143
+ cursor: not-allowed;
1144
+ opacity: 0.4;
1145
+
1146
+ md-icon {
1147
+ color: #cccccc;
1148
+ }
1149
+ }
1150
+
1151
+ &[checked] md-icon {
1152
+ background-color: #2e79be;
1153
+ color: white;
1154
+ border-radius: 3px;
1155
+ }
1156
+ }
1157
+
1158
+ md-outlined-text-field[basement-floor] {
1159
+ width: 70px;
1160
+ margin-left: var(--spacing-small, 4px);
1161
+ opacity: 1;
1162
+ transition: opacity 0.2s ease;
1163
+
1164
+ &[disabled] {
1165
+ opacity: 0.4;
1166
+ pointer-events: none;
1167
+ }
1168
+ }
1169
+ }
1170
+ }
1171
+
1172
+ &[project] {
1173
+ div[separate-container] {
1174
+ margin-left: 15px;
1175
+
1176
+ & > div {
1177
+ align-items: center;
1178
+
1179
+ span:first-child {
1180
+ flex: 0.4;
1181
+ min-width: 100px;
1182
+ }
1183
+ span:last-child {
1184
+ flex: 0.4;
1185
+ margin-left: 0;
1186
+ }
1187
+ }
1188
+ }
1189
+ }
1190
+ }
1191
+
1192
+ & > div[danger-card] {
1193
+ border-color: #c6282833;
1194
+
1195
+ h3 {
1196
+ color: #c62828;
1197
+ }
1198
+ }
1199
+ }
1200
+ }
1201
+ `
1202
+ ];
1203
+ __decorate([
1204
+ state(),
1205
+ __metadata("design:type", String)
1206
+ ], SvProjectUpdate.prototype, "projectId", void 0);
1207
+ __decorate([
1208
+ state(),
1209
+ __metadata("design:type", Object)
1210
+ ], SvProjectUpdate.prototype, "project", void 0);
1211
+ __decorate([
1212
+ state(),
1213
+ __metadata("design:type", Array)
1214
+ ], SvProjectUpdate.prototype, "selectedValues", void 0);
1215
+ __decorate([
1216
+ state(),
1217
+ __metadata("design:type", Array)
1218
+ ], SvProjectUpdate.prototype, "overallConstructorList", void 0);
1219
+ __decorate([
1220
+ state(),
1221
+ __metadata("design:type", Array)
1222
+ ], SvProjectUpdate.prototype, "taskConstructorList", void 0);
1223
+ __decorate([
1224
+ state(),
1225
+ __metadata("design:type", Array)
1226
+ ], SvProjectUpdate.prototype, "taskSupervisoryList", void 0);
1227
+ __decorate([
1228
+ state(),
1229
+ __metadata("design:type", Array)
1230
+ ], SvProjectUpdate.prototype, "overallSupervisoryList", void 0);
1231
+ __decorate([
1232
+ state(),
1233
+ __metadata("design:type", Boolean)
1234
+ ], SvProjectUpdate.prototype, "collectingBasicInfo", void 0);
1235
+ __decorate([
1236
+ state(),
1237
+ __metadata("design:type", Boolean)
1238
+ ], SvProjectUpdate.prototype, "_isSuperUser", void 0);
1239
+ SvProjectUpdate = __decorate([
1240
+ customElement('sv-project-update')
1241
+ ], SvProjectUpdate);
1242
+ export { SvProjectUpdate };
1243
+ //# sourceMappingURL=sv-project-update.js.map