@dssp/supervision 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 (265) hide show
  1. package/dist-client/graphql/building-inspection.js +3 -1
  2. package/dist-client/graphql/building-inspection.js.map +1 -1
  3. package/dist-client/pages/action-plan/action-plan-list-page.d.ts +1 -2
  4. package/dist-client/pages/action-plan/action-plan-list-page.js +1 -2
  5. package/dist-client/pages/action-plan/action-plan-list-page.js.map +1 -1
  6. package/dist-client/pages/building-inspection/building-inspection-detail-ai-defect.js +2 -0
  7. package/dist-client/pages/building-inspection/building-inspection-detail-ai-defect.js.map +1 -1
  8. package/dist-client/pages/building-inspection/building-inspection-detail-ai-measurement.d.ts +53 -39
  9. package/dist-client/pages/building-inspection/building-inspection-detail-ai-measurement.js +1108 -526
  10. package/dist-client/pages/building-inspection/building-inspection-detail-ai-measurement.js.map +1 -1
  11. package/dist-client/pages/building-inspection/building-inspection-detail-ai-measurement_old_ai.d.ts +59 -0
  12. package/dist-client/pages/building-inspection/building-inspection-detail-ai-measurement_old_ai.js +868 -0
  13. package/dist-client/pages/building-inspection/building-inspection-detail-ai-measurement_old_ai.js.map +1 -0
  14. package/dist-client/pages/building-inspection/building-inspection-detail-camera.js +1 -0
  15. package/dist-client/pages/building-inspection/building-inspection-detail-camera.js.map +1 -1
  16. package/dist-client/pages/building-inspection/building-inspection-detail-checklist.d.ts +2 -0
  17. package/dist-client/pages/building-inspection/building-inspection-detail-checklist.js +17 -0
  18. package/dist-client/pages/building-inspection/building-inspection-detail-checklist.js.map +1 -1
  19. package/dist-client/pages/building-inspection/building-inspection-detail-drawing.js +1 -0
  20. package/dist-client/pages/building-inspection/building-inspection-detail-drawing.js.map +1 -1
  21. package/dist-client/pages/building-inspection/building-inspection-list.d.ts +8 -0
  22. package/dist-client/pages/building-inspection/building-inspection-list.js +17 -7
  23. package/dist-client/pages/building-inspection/building-inspection-list.js.map +1 -1
  24. package/dist-client/pages/building-inspection/building-inspection-management.d.ts +82 -0
  25. package/dist-client/pages/building-inspection/building-inspection-management.js +1527 -0
  26. package/dist-client/pages/building-inspection/building-inspection-management.js.map +1 -0
  27. package/dist-client/pages/building-inspection/component/building-inspection-detail-header.d.ts +0 -3
  28. package/dist-client/pages/building-inspection/component/building-inspection-detail-header.js +20 -75
  29. package/dist-client/pages/building-inspection/component/building-inspection-detail-header.js.map +1 -1
  30. package/dist-client/pages/building-inspection/component/field-document/field-document-constants.d.ts +18 -0
  31. package/dist-client/pages/building-inspection/component/field-document/field-document-constants.js +630 -0
  32. package/dist-client/pages/building-inspection/component/field-document/field-document-constants.js.map +1 -0
  33. package/dist-client/pages/building-inspection/component/field-document/field-document-detail-popup.d.ts +10 -0
  34. package/dist-client/pages/building-inspection/component/field-document/field-document-detail-popup.js +219 -0
  35. package/dist-client/pages/building-inspection/component/field-document/field-document-detail-popup.js.map +1 -0
  36. package/dist-client/pages/building-inspection/component/field-document/field-document-list-panel.d.ts +25 -0
  37. package/dist-client/pages/building-inspection/component/field-document/field-document-list-panel.js +546 -0
  38. package/dist-client/pages/building-inspection/component/field-document/field-document-list-panel.js.map +1 -0
  39. package/dist-client/pages/building-inspection/component/field-document/field-document-popup.d.ts +42 -0
  40. package/dist-client/pages/building-inspection/component/field-document/field-document-popup.js +738 -0
  41. package/dist-client/pages/building-inspection/component/field-document/field-document-popup.js.map +1 -0
  42. package/dist-client/pages/building-inspection/component/image-preview-popup.js +0 -3
  43. package/dist-client/pages/building-inspection/component/image-preview-popup.js.map +1 -1
  44. package/dist-client/pages/building-inspection/component/inspection-document/inspection-request-document.d.ts +5 -0
  45. package/dist-client/pages/building-inspection/component/inspection-document/inspection-request-document.js +62 -3
  46. package/dist-client/pages/building-inspection/component/inspection-document/inspection-request-document.js.map +1 -1
  47. package/dist-client/pages/building-inspection/component/inspection-document/inspection-result-notification.d.ts +1 -0
  48. package/dist-client/pages/building-inspection/component/inspection-document/inspection-result-notification.js +8 -2
  49. package/dist-client/pages/building-inspection/component/inspection-document/inspection-result-notification.js.map +1 -1
  50. package/dist-client/pages/building-inspection/component/inspection-event-provider.d.ts +3 -2
  51. package/dist-client/pages/building-inspection/component/inspection-event-provider.js +131 -34
  52. package/dist-client/pages/building-inspection/component/inspection-event-provider.js.map +1 -1
  53. package/dist-client/pages/building-inspection/inspection-create-popup.js +5 -4
  54. package/dist-client/pages/building-inspection/inspection-create-popup.js.map +1 -1
  55. package/dist-client/pages/building-inspection/inspection-with-checklist-create-popup.d.ts +4 -0
  56. package/dist-client/pages/building-inspection/inspection-with-checklist-create-popup.js +844 -0
  57. package/dist-client/pages/building-inspection/inspection-with-checklist-create-popup.js.map +1 -0
  58. package/dist-client/pages/building-inspection-grid/building-inspection-grid-detail.js +8 -5
  59. package/dist-client/pages/building-inspection-grid/building-inspection-grid-detail.js.map +1 -1
  60. package/dist-client/pages/building-inspection-grid/component/grid-inspection-create-popup.js +1 -1
  61. package/dist-client/pages/building-inspection-grid/component/grid-inspection-create-popup.js.map +1 -1
  62. package/dist-client/pages/building-inspection-grid/component/grid-inspection-list-layer.js +2 -2
  63. package/dist-client/pages/building-inspection-grid/component/grid-inspection-list-layer.js.map +1 -1
  64. package/dist-client/pages/checklist/attachment-list-popup.js +1 -2
  65. package/dist-client/pages/checklist/attachment-list-popup.js.map +1 -1
  66. package/dist-client/pages/checklist/checklist-view.d.ts +1 -0
  67. package/dist-client/pages/checklist/checklist-view.js +219 -31
  68. package/dist-client/pages/checklist/checklist-view.js.map +1 -1
  69. package/dist-client/pages/checklist/comment-list-popup.js +1 -2
  70. package/dist-client/pages/checklist/comment-list-popup.js.map +1 -1
  71. package/dist-client/pages/checklist/inspection-info-popup.d.ts +1 -2
  72. package/dist-client/pages/checklist/inspection-info-popup.js +1 -2
  73. package/dist-client/pages/checklist/inspection-info-popup.js.map +1 -1
  74. package/dist-client/pages/checklist/inspection-tab-popup.js +1 -1
  75. package/dist-client/pages/checklist/inspection-tab-popup.js.map +1 -1
  76. package/dist-client/pages/checklist/kcs-criteria-popup.d.ts +16 -0
  77. package/dist-client/pages/checklist/kcs-criteria-popup.js +179 -0
  78. package/dist-client/pages/checklist/kcs-criteria-popup.js.map +1 -0
  79. package/dist-client/pages/checklist-database/checklist-database.d.ts +35 -0
  80. package/dist-client/pages/checklist-database/checklist-database.js +540 -0
  81. package/dist-client/pages/checklist-database/checklist-database.js.map +1 -0
  82. package/dist-client/pages/checklist-template/checklist-template-item.js +25 -0
  83. package/dist-client/pages/checklist-template/checklist-template-item.js.map +1 -1
  84. package/dist-client/pages/checklist-template/checklist-template-list.js +2 -1
  85. package/dist-client/pages/checklist-template/checklist-template-list.js.map +1 -1
  86. package/dist-client/pages/checklist-template/construction-checklist-template-item-popup.d.ts +2 -0
  87. package/dist-client/pages/checklist-template/construction-checklist-template-item-popup.js +223 -0
  88. package/dist-client/pages/checklist-template/construction-checklist-template-item-popup.js.map +1 -0
  89. package/dist-client/pages/checklist-template/construction-checklist-template-list.d.ts +55 -0
  90. package/dist-client/pages/checklist-template/construction-checklist-template-list.js +404 -0
  91. package/dist-client/pages/checklist-template/construction-checklist-template-list.js.map +1 -0
  92. package/dist-client/pages/issue/issue-list-page.d.ts +1 -2
  93. package/dist-client/pages/issue/issue-list-page.js +1 -2
  94. package/dist-client/pages/issue/issue-list-page.js.map +1 -1
  95. package/dist-client/pages/project-checklist/project-checklist-management.d.ts +62 -0
  96. package/dist-client/pages/project-checklist/project-checklist-management.js +1145 -0
  97. package/dist-client/pages/project-checklist/project-checklist-management.js.map +1 -0
  98. package/dist-client/pages/project-report/project-report-list-page.d.ts +1 -2
  99. package/dist-client/pages/project-report/project-report-list-page.js +1 -2
  100. package/dist-client/pages/project-report/project-report-list-page.js.map +1 -1
  101. package/dist-client/pages/supervisor/supervisor-list-page.d.ts +1 -2
  102. package/dist-client/pages/supervisor/supervisor-list-page.js +1 -2
  103. package/dist-client/pages/supervisor/supervisor-list-page.js.map +1 -1
  104. package/dist-client/route.d.ts +1 -1
  105. package/dist-client/route.js +12 -0
  106. package/dist-client/route.js.map +1 -1
  107. package/dist-client/stt/speech-to-text.d.ts +13 -8
  108. package/dist-client/stt/speech-to-text.js +282 -40
  109. package/dist-client/stt/speech-to-text.js.map +1 -1
  110. package/dist-client/tsconfig.tsbuildinfo +1 -1
  111. package/dist-server/service/action-plan/action-plan-mutation.js +5 -6
  112. package/dist-server/service/action-plan/action-plan-mutation.js.map +1 -1
  113. package/dist-server/service/action-plan/action-plan-query.d.ts +2 -2
  114. package/dist-server/service/action-plan/action-plan-query.js +5 -11
  115. package/dist-server/service/action-plan/action-plan-query.js.map +1 -1
  116. package/dist-server/service/building-inspection/building-inspection-mutation.js +41 -7
  117. package/dist-server/service/building-inspection/building-inspection-mutation.js.map +1 -1
  118. package/dist-server/service/building-inspection/building-inspection-query.d.ts +7 -3
  119. package/dist-server/service/building-inspection/building-inspection-query.js +154 -25
  120. package/dist-server/service/building-inspection/building-inspection-query.js.map +1 -1
  121. package/dist-server/service/building-inspection/building-inspection-type.d.ts +10 -1
  122. package/dist-server/service/building-inspection/building-inspection-type.js +35 -1
  123. package/dist-server/service/building-inspection/building-inspection-type.js.map +1 -1
  124. package/dist-server/service/building-inspection/building-inspection.d.ts +1 -0
  125. package/dist-server/service/building-inspection/building-inspection.js +20 -2
  126. package/dist-server/service/building-inspection/building-inspection.js.map +1 -1
  127. package/dist-server/service/building-inspection-daily-worklog/building-inspection-daily-worklog-mutation.js +10 -0
  128. package/dist-server/service/building-inspection-daily-worklog/building-inspection-daily-worklog-mutation.js.map +1 -1
  129. package/dist-server/service/building-inspection-history/building-inspection-history.d.ts +1 -0
  130. package/dist-server/service/building-inspection-history/building-inspection-history.js +6 -1
  131. package/dist-server/service/building-inspection-history/building-inspection-history.js.map +1 -1
  132. package/dist-server/service/checklist-item/checklist-item-mutation.d.ts +2 -0
  133. package/dist-server/service/checklist-item/checklist-item-mutation.js +26 -0
  134. package/dist-server/service/checklist-item/checklist-item-mutation.js.map +1 -1
  135. package/dist-server/service/checklist-item/checklist-item-type.d.ts +5 -1
  136. package/dist-server/service/checklist-item/checklist-item-type.js +15 -1
  137. package/dist-server/service/checklist-item/checklist-item-type.js.map +1 -1
  138. package/dist-server/service/checklist-item/checklist-item.d.ts +2 -0
  139. package/dist-server/service/checklist-item/checklist-item.js +11 -1
  140. package/dist-server/service/checklist-item/checklist-item.js.map +1 -1
  141. package/dist-server/service/checklist-item/index.d.ts +2 -1
  142. package/dist-server/service/checklist-item-history/checklist-item-history.d.ts +2 -0
  143. package/dist-server/service/checklist-item-history/checklist-item-history.js +11 -1
  144. package/dist-server/service/checklist-item-history/checklist-item-history.js.map +1 -1
  145. package/dist-server/service/checklist-template/checklist-template-query.d.ts +1 -1
  146. package/dist-server/service/checklist-template/checklist-template-query.js +2 -5
  147. package/dist-server/service/checklist-template/checklist-template-query.js.map +1 -1
  148. package/dist-server/service/checklist-template-item/checklist-template-item-mutation.js +11 -2
  149. package/dist-server/service/checklist-template-item/checklist-template-item-mutation.js.map +1 -1
  150. package/dist-server/service/checklist-template-item/checklist-template-item-type.d.ts +1 -0
  151. package/dist-server/service/checklist-template-item/checklist-template-item-type.js +4 -0
  152. package/dist-server/service/checklist-template-item/checklist-template-item-type.js.map +1 -1
  153. package/dist-server/service/checklist-template-item/checklist-template-item.d.ts +1 -0
  154. package/dist-server/service/checklist-template-item/checklist-template-item.js +6 -1
  155. package/dist-server/service/checklist-template-item/checklist-template-item.js.map +1 -1
  156. package/dist-server/service/checklist-template-item/spec-matching-service.js +1 -0
  157. package/dist-server/service/checklist-template-item/spec-matching-service.js.map +1 -1
  158. package/dist-server/service/checklist-type/checklist-type-query.d.ts +1 -1
  159. package/dist-server/service/checklist-type/checklist-type-query.js +2 -5
  160. package/dist-server/service/checklist-type/checklist-type-query.js.map +1 -1
  161. package/dist-server/service/construction-checklist-template/construction-checklist-template-mutation.d.ts +7 -0
  162. package/dist-server/service/construction-checklist-template/construction-checklist-template-mutation.js +107 -0
  163. package/dist-server/service/construction-checklist-template/construction-checklist-template-mutation.js.map +1 -0
  164. package/dist-server/service/construction-checklist-template/construction-checklist-template-query.d.ts +10 -0
  165. package/dist-server/service/construction-checklist-template/construction-checklist-template-query.js +62 -0
  166. package/dist-server/service/construction-checklist-template/construction-checklist-template-query.js.map +1 -0
  167. package/dist-server/service/construction-checklist-template/construction-checklist-template-type.d.ts +11 -0
  168. package/dist-server/service/construction-checklist-template/construction-checklist-template-type.js +43 -0
  169. package/dist-server/service/construction-checklist-template/construction-checklist-template-type.js.map +1 -0
  170. package/dist-server/service/construction-checklist-template/construction-checklist-template.d.ts +18 -0
  171. package/dist-server/service/construction-checklist-template/construction-checklist-template.js +80 -0
  172. package/dist-server/service/construction-checklist-template/construction-checklist-template.js.map +1 -0
  173. package/dist-server/service/construction-checklist-template/index.d.ts +5 -0
  174. package/dist-server/service/construction-checklist-template/index.js +9 -0
  175. package/dist-server/service/construction-checklist-template/index.js.map +1 -0
  176. package/dist-server/service/construction-checklist-template-item/construction-checklist-template-item-mutation.d.ts +6 -0
  177. package/dist-server/service/construction-checklist-template-item/construction-checklist-template-item-mutation.js +56 -0
  178. package/dist-server/service/construction-checklist-template-item/construction-checklist-template-item-mutation.js.map +1 -0
  179. package/dist-server/service/construction-checklist-template-item/construction-checklist-template-item-query.d.ts +9 -0
  180. package/dist-server/service/construction-checklist-template-item/construction-checklist-template-item-query.js +53 -0
  181. package/dist-server/service/construction-checklist-template-item/construction-checklist-template-item-query.js.map +1 -0
  182. package/dist-server/service/construction-checklist-template-item/construction-checklist-template-item-type.d.ts +17 -0
  183. package/dist-server/service/construction-checklist-template-item/construction-checklist-template-item-type.js +67 -0
  184. package/dist-server/service/construction-checklist-template-item/construction-checklist-template-item-type.js.map +1 -0
  185. package/dist-server/service/construction-checklist-template-item/construction-checklist-template-item.d.ts +24 -0
  186. package/dist-server/service/construction-checklist-template-item/construction-checklist-template-item.js +113 -0
  187. package/dist-server/service/construction-checklist-template-item/construction-checklist-template-item.js.map +1 -0
  188. package/dist-server/service/construction-checklist-template-item/index.d.ts +5 -0
  189. package/dist-server/service/construction-checklist-template-item/index.js +9 -0
  190. package/dist-server/service/construction-checklist-template-item/index.js.map +1 -0
  191. package/dist-server/service/field-document/field-document-auto-file.d.ts +21 -0
  192. package/dist-server/service/field-document/field-document-auto-file.js +47 -0
  193. package/dist-server/service/field-document/field-document-auto-file.js.map +1 -0
  194. package/dist-server/service/field-document/field-document-mutation.d.ts +9 -0
  195. package/dist-server/service/field-document/field-document-mutation.js +139 -0
  196. package/dist-server/service/field-document/field-document-mutation.js.map +1 -0
  197. package/dist-server/service/field-document/field-document-query.d.ts +14 -0
  198. package/dist-server/service/field-document/field-document-query.js +120 -0
  199. package/dist-server/service/field-document/field-document-query.js.map +1 -0
  200. package/dist-server/service/field-document/field-document-summary-service.d.ts +10 -0
  201. package/dist-server/service/field-document/field-document-summary-service.js +79 -0
  202. package/dist-server/service/field-document/field-document-summary-service.js.map +1 -0
  203. package/dist-server/service/field-document/field-document-type.d.ts +24 -0
  204. package/dist-server/service/field-document/field-document-type.js +88 -0
  205. package/dist-server/service/field-document/field-document-type.js.map +1 -0
  206. package/dist-server/service/field-document/field-document.d.ts +29 -0
  207. package/dist-server/service/field-document/field-document.js +127 -0
  208. package/dist-server/service/field-document/field-document.js.map +1 -0
  209. package/dist-server/service/field-document/index.d.ts +5 -0
  210. package/dist-server/service/field-document/index.js +9 -0
  211. package/dist-server/service/field-document/index.js.map +1 -0
  212. package/dist-server/service/index.d.ts +7 -2
  213. package/dist-server/service/index.js +20 -0
  214. package/dist-server/service/index.js.map +1 -1
  215. package/dist-server/service/issue/issue-mutation.js +5 -6
  216. package/dist-server/service/issue/issue-mutation.js.map +1 -1
  217. package/dist-server/service/issue/issue-query.d.ts +2 -2
  218. package/dist-server/service/issue/issue-query.js +5 -11
  219. package/dist-server/service/issue/issue-query.js.map +1 -1
  220. package/dist-server/service/project-checklist/index.d.ts +5 -0
  221. package/dist-server/service/project-checklist/index.js +9 -0
  222. package/dist-server/service/project-checklist/index.js.map +1 -0
  223. package/dist-server/service/project-checklist/project-checklist-mutation.d.ts +7 -0
  224. package/dist-server/service/project-checklist/project-checklist-mutation.js +118 -0
  225. package/dist-server/service/project-checklist/project-checklist-mutation.js.map +1 -0
  226. package/dist-server/service/project-checklist/project-checklist-query.d.ts +12 -0
  227. package/dist-server/service/project-checklist/project-checklist-query.js +76 -0
  228. package/dist-server/service/project-checklist/project-checklist-query.js.map +1 -0
  229. package/dist-server/service/project-checklist/project-checklist-type.d.ts +18 -0
  230. package/dist-server/service/project-checklist/project-checklist-type.js +69 -0
  231. package/dist-server/service/project-checklist/project-checklist-type.js.map +1 -0
  232. package/dist-server/service/project-checklist/project-checklist.d.ts +20 -0
  233. package/dist-server/service/project-checklist/project-checklist.js +90 -0
  234. package/dist-server/service/project-checklist/project-checklist.js.map +1 -0
  235. package/dist-server/service/project-checklist/spec-matching-service.d.ts +17 -0
  236. package/dist-server/service/project-checklist/spec-matching-service.js +140 -0
  237. package/dist-server/service/project-checklist/spec-matching-service.js.map +1 -0
  238. package/dist-server/service/project-checklist-item/index.d.ts +4 -0
  239. package/dist-server/service/project-checklist-item/index.js +9 -0
  240. package/dist-server/service/project-checklist-item/index.js.map +1 -0
  241. package/dist-server/service/project-checklist-item/project-checklist-item-mutation.d.ts +2 -0
  242. package/dist-server/service/project-checklist-item/project-checklist-item-mutation.js +13 -0
  243. package/dist-server/service/project-checklist-item/project-checklist-item-mutation.js.map +1 -0
  244. package/dist-server/service/project-checklist-item/project-checklist-item-query.d.ts +5 -0
  245. package/dist-server/service/project-checklist-item/project-checklist-item-query.js +34 -0
  246. package/dist-server/service/project-checklist-item/project-checklist-item-query.js.map +1 -0
  247. package/dist-server/service/project-checklist-item/project-checklist-item-type.d.ts +5 -0
  248. package/dist-server/service/project-checklist-item/project-checklist-item-type.js +21 -0
  249. package/dist-server/service/project-checklist-item/project-checklist-item-type.js.map +1 -0
  250. package/dist-server/service/project-checklist-item/project-checklist-item.d.ts +19 -0
  251. package/dist-server/service/project-checklist-item/project-checklist-item.js +92 -0
  252. package/dist-server/service/project-checklist-item/project-checklist-item.js.map +1 -0
  253. package/dist-server/service/project-report/project-report-mutation.js +5 -6
  254. package/dist-server/service/project-report/project-report-mutation.js.map +1 -1
  255. package/dist-server/service/project-report/project-report-query.d.ts +2 -2
  256. package/dist-server/service/project-report/project-report-query.js +5 -11
  257. package/dist-server/service/project-report/project-report-query.js.map +1 -1
  258. package/dist-server/service/supervisor/supervisor-mutation.js +5 -6
  259. package/dist-server/service/supervisor/supervisor-mutation.js.map +1 -1
  260. package/dist-server/service/supervisor/supervisor-query.d.ts +2 -2
  261. package/dist-server/service/supervisor/supervisor-query.js +5 -11
  262. package/dist-server/service/supervisor/supervisor-query.js.map +1 -1
  263. package/dist-server/tsconfig.tsbuildinfo +1 -1
  264. package/package.json +14 -12
  265. package/things-factory.config.js +7 -1
@@ -1,100 +1,92 @@
1
1
  import { __decorate, __metadata } from "tslib";
2
+ /**
3
+ * 검측 관리 상세 - AI 자동 검측
4
+ *
5
+ * 검측 체크리스트 항목을 AI 엔진(inspection-ai)에 보내:
6
+ * 1) 촬영 가이드(plan-shots)를 받아 단계별 촬영 슬롯을 표시하고
7
+ * 2) 촬영된 사진으로 자동 판정(inspect)을 실행한 뒤
8
+ * 3) 항목별 상세 / 추가 촬영(재검사) / 종합 리포트를 거쳐
9
+ * 4) 검사 등록 시 감리자 적합/부적합 자동 반영 + AI판단 근거를 첨부한다.
10
+ */
2
11
  import '@material/web/icon/icon.js';
3
- import '@operato/input/ox-input-switch.js';
4
- import '@operato/mini-map/ox-zoomable-image.js';
5
12
  import '@material/web/progress/circular-progress.js';
6
13
  import gql from 'graphql-tag';
7
14
  import { css, html } from 'lit';
8
15
  import { customElement, state, query } from 'lit/decorators.js';
9
- import html2canvas from 'html2canvas';
10
- import { PageView } from '@operato/shell';
11
- import { CommonGristStyles, ScrollbarStyles } from '@operato/styles';
16
+ import { PageView, navigate } from '@operato/shell';
17
+ import { ScrollbarStyles } from '@operato/styles';
12
18
  import { client } from '@operato/graphql';
13
19
  import { notify } from '@operato/layout';
14
20
  import './component/building-inspection-detail-header';
21
+ const ENGINE_BASE_URL = 'https://hatiolab-korea-uni.ettisoft.com';
22
+ const ENGINE_RUN_URL = `${ENGINE_BASE_URL}/api/run/inspection-ai`;
23
+ const ENGINE_AUTH = `Basic ${btoa('admin:admin1234')}`;
24
+ const MAX_IMAGES = 8;
25
+ /* 엔진 verdict(기계값) → 화면 라벨. pass/fail만 합/불로 자동 체크, 나머지(보류/미지원/오류)는 감리자 판단 */
26
+ const VERDICT_LABEL = {
27
+ pass: '적합',
28
+ fail: '부적합',
29
+ hold: '보류',
30
+ pending: '미지원',
31
+ error: '오류'
32
+ };
33
+ /* 엔진 routing(기계값) → 화면 라벨 */
34
+ const ROUTING_LABEL = {
35
+ vlm: 'AI 사진판독',
36
+ cv: '자동 측정',
37
+ cv_missing: '자동 측정 누락'
38
+ };
15
39
  let BuildingInspectionAiMeasurement = class BuildingInspectionAiMeasurement extends PageView {
16
40
  constructor() {
17
41
  super(...arguments);
18
- this.KEYPOINT_RULER_API_BASE_URL = 'https://hatiolab-korea-uni.ettisoft.com';
19
42
  this.project = {};
20
43
  this.buildingInspection = {};
21
44
  this.buildingInspectionId = '';
22
- this.capturedVideoUrl = null;
23
- this.videoFile = null;
24
- this.frame = null;
25
- this.analyzing = false;
26
- this.selectedPoints = {
27
- x1: null,
28
- y1: null,
29
- x2: null,
30
- y2: null
45
+ this.guide = null; // plan-shots 응답 {shots, total_shots, markdown}
46
+ this.photos = [];
47
+ this.result = null; // inspect 응답
48
+ this.mainPhotoIndex = 0;
49
+ this.panel = 'none';
50
+ this.selectedItem = null;
51
+ this.recaptureBase = 0; // 재촬영 패널 진입 시점의 사진 수
52
+ this.expandedReportId = null; // 종합 리포트에서 펼친 항목(아코디언)
53
+ this.loading = false;
54
+ this.loadingText = '';
55
+ /* 슬롯 클릭으로 선택기를 연 경우 대상 슬롯 인덱스 (null이면 뒤에 추가) */
56
+ this.pendingSlotIndex = null;
57
+ this._resetPhotos = () => {
58
+ this.photos.forEach(p => URL.revokeObjectURL(p.url));
59
+ this.photos = [];
31
60
  };
32
- this.frameNaturalWidth = 0;
33
- this.frameNaturalHeight = 0;
34
- this.displayedImageRect = null;
35
- this.distance = null;
36
- this.unit = null;
37
- this.analyzingError = null;
38
- this._onImageLoad = () => {
39
- if (!this.frameImageEl || !this.imageWrapperEl)
40
- return;
41
- const img = this.frameImageEl;
42
- const wrapperRect = this.imageWrapperEl.getBoundingClientRect();
43
- const imgRect = img.getBoundingClientRect();
44
- this.frameNaturalWidth = img.naturalWidth;
45
- this.frameNaturalHeight = img.naturalHeight;
46
- this.displayedImageRect = {
47
- width: imgRect.width,
48
- height: imgRect.height,
49
- left: imgRect.left - wrapperRect.left,
50
- top: imgRect.top - wrapperRect.top
51
- };
61
+ this._openReport = () => {
62
+ this.panel = 'report';
63
+ this.selectedItem = null;
64
+ this.expandedReportId = null;
52
65
  };
53
- this._onImageClick = (event) => {
54
- if (!this.frameImageEl || !this.imageWrapperEl)
55
- return;
56
- const imgRect = this.frameImageEl.getBoundingClientRect();
57
- const wrapperRect = this.imageWrapperEl.getBoundingClientRect();
58
- const clientX = event.clientX;
59
- const clientY = event.clientY;
60
- // Ignore clicks outside the displayed image area
61
- if (clientX < imgRect.left || clientX > imgRect.right || clientY < imgRect.top || clientY > imgRect.bottom) {
62
- return;
63
- }
64
- // 새로운 마크 입력 시 이전 거리/단위 결과 초기화
65
- this.distance = null;
66
- this.unit = null;
67
- this.analyzingError = null;
68
- const relativeX = clientX - imgRect.left;
69
- const relativeY = clientY - imgRect.top;
70
- const xOnNatural = Math.round((relativeX * this.frameNaturalWidth) / imgRect.width);
71
- const yOnNatural = Math.round((relativeY * this.frameNaturalHeight) / imgRect.height);
72
- if (this.selectedPoints.x1 === null || this.selectedPoints.y1 === null) {
73
- this.selectedPoints = Object.assign(Object.assign({}, this.selectedPoints), { x1: xOnNatural, y1: yOnNatural });
74
- }
75
- else if (this.selectedPoints.x2 === null || this.selectedPoints.y2 === null) {
76
- this.selectedPoints = Object.assign(Object.assign({}, this.selectedPoints), { x2: xOnNatural, y2: yOnNatural });
77
- }
78
- else {
79
- // 세 번째 클릭부터는 초기화 후 첫 점으로 설정
80
- this.selectedPoints = { x1: xOnNatural, y1: yOnNatural, x2: null, y2: null };
81
- }
82
- // Update displayed rect cache (in case layout changed)
83
- this.displayedImageRect = {
84
- width: imgRect.width,
85
- height: imgRect.height,
86
- left: imgRect.left - wrapperRect.left,
87
- top: imgRect.top - wrapperRect.top
88
- };
66
+ this._closePanel = () => {
67
+ this.panel = 'none';
68
+ this.selectedItem = null;
89
69
  };
90
70
  }
91
71
  get context() {
92
72
  return {
93
- title: '검측 관리 상세 - AI 거리 측정'
73
+ title: '검측 관리 상세 - AI 자동 검측'
94
74
  };
95
75
  }
76
+ /* 엔진에 보낼 검측요청서: 체크리스트 항목 → {item_id, category, text} */
77
+ get engineChecklist() {
78
+ var _a, _b;
79
+ const items = ((_b = (_a = this.buildingInspection) === null || _a === void 0 ? void 0 : _a.checklist) === null || _b === void 0 ? void 0 : _b.checklistItems) || [];
80
+ return [...items]
81
+ .sort((a, b) => (a.sequence || 0) - (b.sequence || 0))
82
+ .map(item => ({
83
+ item_id: item.id,
84
+ category: [item.mainType, item.detailType].filter(Boolean).join(' · '),
85
+ text: item.name || ''
86
+ }));
87
+ }
96
88
  render() {
97
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
89
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
98
90
  return html `
99
91
  <building-inspection-detail-header
100
92
  .buildingInspectionId=${(_a = this.buildingInspection) === null || _a === void 0 ? void 0 : _a.id}
@@ -102,80 +94,336 @@ let BuildingInspectionAiMeasurement = class BuildingInspectionAiMeasurement exte
102
94
  .projectName=${this.project.name}
103
95
  .buildingName=${(_f = (_e = (_d = this.buildingInspection) === null || _d === void 0 ? void 0 : _d.buildingLevel) === null || _e === void 0 ? void 0 : _e.building) === null || _f === void 0 ? void 0 : _f.name}
104
96
  .buildingLevelFloor=${(_h = (_g = this.buildingInspection) === null || _g === void 0 ? void 0 : _g.buildingLevel) === null || _h === void 0 ? void 0 : _h.floor}
97
+ .projectId=${this.project.id}
105
98
  ></building-inspection-detail-header>
106
99
 
107
100
  <div body>
108
- <!-- Preview selected or recorded video -->
109
- <div preview>
110
- ${((_j = this.frame) === null || _j === void 0 ? void 0 : _j.download_url)
101
+ <div class="page-title">
102
+ AI 자동 검측 – ${((_k = (_j = this.buildingInspection) === null || _j === void 0 ? void 0 : _j.checklist) === null || _k === void 0 ? void 0 : _k.location) || ''}
103
+ <span target>${((_m = (_l = this.buildingInspection) === null || _l === void 0 ? void 0 : _l.checklist) === null || _m === void 0 ? void 0 : _m.name) || ''}</span>
104
+ </div>
105
+
106
+ ${this.result ? this._renderResult() : this._renderGuide()}
107
+ </div>
108
+
109
+ <!-- 모바일에서 촬영/앨범 선택이 함께 뜨도록 capture 속성 없이 사용 -->
110
+ <input id="photoInput" type="file" accept="image/*" multiple @change=${this._onPhotosSelected} />
111
+
112
+ ${this.loading
113
+ ? html `
114
+ <div class="loading-overlay">
115
+ <div class="box">
116
+ <md-circular-progress indeterminate></md-circular-progress>
117
+ <span>${this.loadingText}</span>
118
+ </div>
119
+ </div>
120
+ `
121
+ : ''}
122
+ `;
123
+ }
124
+ /* ─── 1단계: 촬영 가이드 화면 ─── */
125
+ _renderGuide() {
126
+ if (!this.guide) {
127
+ return html `<div class="empty-state">촬영 가이드를 준비 중입니다...</div>`;
128
+ }
129
+ const shots = this.guide.shots || [];
130
+ const total = this.guide.total_shots || shots.length;
131
+ const taken = this.photos.length;
132
+ const current = shots[Math.min(taken, shots.length - 1)];
133
+ const done = taken >= total;
134
+ return html `
135
+ <div class="guide-container">
136
+ <div class="step-badge">${Math.min(taken + 1, total)}단계 · ${(current === null || current === void 0 ? void 0 : current.target) || '촬영'}</div>
137
+ <div class="guide-title">${(current === null || current === void 0 ? void 0 : current.target) || ''}${done ? ' 촬영 완료' : ''}</div>
138
+ <div class="guide-desc">이 사진을 토대로 AI가 적용할 체크리스트를 결정하고 일부를 자동 판정합니다.</div>
139
+
140
+ <div class="card">
141
+ <h4>촬영 가이드</h4>
142
+ <ul>
143
+ ${shots.map(s => html `<li>${s.target} — ${s.how}</li>`)}
144
+ </ul>
145
+ </div>
146
+
147
+ <div class="card" style="display: flex; flex-direction: column; gap: 10px;">
148
+ <div class="progress-row">
149
+ <span>사진 진행</span>
150
+ <span>${taken} / ${total}</span>
151
+ </div>
152
+ <div class="progress-bar"><div style="width: ${total ? (taken / total) * 100 : 0}%"></div></div>
153
+ ${!done ? html `<div class="next-shot">다음 촬영: ${(current === null || current === void 0 ? void 0 : current.how) || (current === null || current === void 0 ? void 0 : current.target) || ''}</div>` : ''}
154
+ <div class="slot-grid">
155
+ ${shots.map((s, i) => html `
156
+ <div class="slot" @click=${() => this._openPhotoPicker(i)}>
157
+ ${this.photos[i]
158
+ ? html `<img src=${this.photos[i].url} alt=${s.target} />`
159
+ : html `<md-icon>add_a_photo</md-icon>${s.target}`}
160
+ </div>
161
+ `)}
162
+ </div>
163
+ </div>
164
+
165
+ <button class="primary-button" ?disabled=${taken === 0} @click=${() => this._runInspection()}>
166
+ 사진 선택 ${taken ? `(${taken}/${total})` : ''}
167
+ </button>
168
+ ${taken > 0
111
169
  ? html `
112
- <div class="image-wrapper" @click=${this._onImageClick}>
113
- <img
114
- id="frameImage"
115
- src="${this.KEYPOINT_RULER_API_BASE_URL + ((_k = this.frame) === null || _k === void 0 ? void 0 : _k.download_url)}"
116
- crossorigin="anonymous"
117
- alt="frame"
118
- loading="eager"
119
- @load=${this._onImageLoad}
120
- />
121
- ${this._renderMarkers()}
170
+ <div style="display: flex; justify-content: flex-end;">
171
+ <button class="text-button" @click=${this._resetPhotos}>사진 초기화</button>
122
172
  </div>
123
173
  `
124
- : // 동영상 선택 후 동영상 프리뷰
125
- this.capturedVideoUrl
126
- ? html `<video src=${this.capturedVideoUrl} controls playsinline></video>`
127
- : // 초기 상태, 동영상 선택 전
128
- html `<md-icon>videocam</md-icon>`}
174
+ : ''}
175
+ </div>
176
+ `;
177
+ }
178
+ /* ─── 2단계: 판정 결과 화면 ─── */
179
+ _renderResult() {
180
+ const items = this._sortedItems();
181
+ const autoItems = items.filter(it => it.verdict === 'pass' || it.verdict === 'fail');
182
+ const moreItems = items.filter(it => it.verdict !== 'pass' && it.verdict !== 'fail');
183
+ // 클릭한 항목의 used_images만 표시 (선택 없으면 전체 항목 합집합). used_images는 전체 사진 기준 1-based
184
+ const focusItems = this.selectedItem ? [this.selectedItem] : items;
185
+ const usedNos = new Set();
186
+ for (const it of focusItems)
187
+ for (const n of it.used_images || [])
188
+ usedNos.add(n);
189
+ const usedPhotos = this.photos.map((photo, i) => ({ photo, no: i + 1 })).filter(x => usedNos.has(x.no));
190
+ const mainPhoto = usedPhotos[this.mainPhotoIndex];
191
+ return html `
192
+ <div class="result-body">
193
+ <div class="photo-pane">
194
+ <div class="caption">판정에 사용된 사진</div>
195
+ <div class="photo-main">${mainPhoto ? html `<img src=${mainPhoto.photo.url} alt="현장 사진" />` : ''}</div>
196
+ <div class="photo-thumbs">
197
+ ${usedPhotos.map((x, i) => html `
198
+ <img
199
+ src=${x.photo.url}
200
+ alt="사진 ${x.no}"
201
+ ?selected=${i === this.mainPhotoIndex}
202
+ @click=${() => (this.mainPhotoIndex = i)}
203
+ />
204
+ `)}
205
+ </div>
129
206
  </div>
130
207
 
131
- <div controls>
132
- <span class="controls-title">동영상 촬영</span>
133
-
134
- <!-- 가운데 버튼 -->
135
- ${((_l = this.frame) === null || _l === void 0 ? void 0 : _l.download_url) && this._hasTwoPoints() && this.distance && this.unit
136
- ? // 거리 측정 완료 시, 사진 저장 버튼 표시
137
- html `<button class="save" ?disabled=${this.analyzing} @click=${this._savePhoto}>사진 저장</button>`
138
- : // 거리 측정 완료 전, 동영상 촬영 버튼 표시
139
- html `<div class="camera-shutter">
140
- <md-icon>camera</md-icon>
141
- <input id="cameraInput" type="file" accept="video/*" @change="${this._onVideoSelected}" />
142
- </div>`}
143
-
144
- <!-- Action buttons -->
145
- <div action-buttons>
146
- ${this.frame
208
+ <div class="items-pane">
209
+ <div class="summary-banner">
210
+ 적용 항목 ${items.length}개 · 자동 판정 ${autoItems.length}개 · 추가 촬영 ${moreItems.length}개
211
+ </div>
212
+
213
+ ${autoItems.length
147
214
  ? html `
148
- <button class="photo" ?disabled=${this.analyzing || !this._hasTwoPoints()} @click=${this._onAnalyzePhoto}>
149
- 사진 분석
150
- </button>
215
+ <div class="group-title">자동 판정 완료 (${autoItems.length})</div>
216
+ ${autoItems.map((it, i) => this._renderItemCard(it, i + 1))}
151
217
  `
152
- : html `
153
- <button class="save" ?disabled=${this.analyzing || !this.capturedVideoUrl} @click=${this._onAnalyzeVideo}>
154
- 동영상 분석
155
- </button>
156
- `}
157
- <button class="retry" @click=${this._onRetry}>초기화</button>
218
+ : ''}
219
+ ${moreItems.length
220
+ ? html `
221
+ <div class="group-title">추가 촬영 필요 (${moreItems.length} 남음)</div>
222
+ ${moreItems.map((it, i) => this._renderItemCard(it, i + 1))}
223
+ `
224
+ : ''}
225
+
226
+ <button class="primary-button" @click=${this._openReport}>종합 검사 리포트</button>
227
+ </div>
228
+
229
+ ${this.panel !== 'none' ? this._renderSidePane() : ''}
230
+ </div>
231
+ `;
232
+ }
233
+ _renderItemCard(item, no) {
234
+ var _a;
235
+ const auto = item.verdict === 'pass' || item.verdict === 'fail';
236
+ return html `
237
+ <div class="item-card" ?selected=${((_a = this.selectedItem) === null || _a === void 0 ? void 0 : _a.item_id) === item.item_id} @click=${() => this._openDetail(item)}>
238
+ <div class="item-no">${no}</div>
239
+ <div class="item-main">
240
+ <div class="item-name">${item.text}</div>
241
+ <div>
242
+ <span class="chip">${ROUTING_LABEL[item.routing] || item.routing}</span>
243
+ <span class="chip-verdict">${VERDICT_LABEL[item.verdict] || item.verdict}</span>
158
244
  </div>
245
+ <div class="item-desc">${item.observation ? `관찰: ${item.observation}` : item.reason || ''}</div>
159
246
  </div>
247
+ ${auto
248
+ ? html `
249
+ <div class="badge" ?pass=${item.verdict === 'pass'} ?fail=${item.verdict === 'fail'}>
250
+ ${VERDICT_LABEL[item.verdict]}
251
+ </div>
252
+ `
253
+ : item.verdict === 'hold'
254
+ ? html `
255
+ <button
256
+ class="capture-button"
257
+ @click=${(e) => {
258
+ e.stopPropagation();
259
+ this._openRecapture(item);
260
+ }}
261
+ >
262
+ 촬영하기
263
+ </button>
264
+ `
265
+ : html `<div class="badge" etc>${VERDICT_LABEL[item.verdict] || item.verdict}</div>`}
160
266
  </div>
267
+ `;
268
+ }
269
+ /* ─── 우측 패널: 상세 / 재촬영 / 리포트 ─── */
270
+ _renderSidePane() {
271
+ if (this.panel === 'detail' && this.selectedItem)
272
+ return this._renderDetailPane(this.selectedItem);
273
+ if (this.panel === 'recapture' && this.selectedItem)
274
+ return this._renderRecapturePane(this.selectedItem);
275
+ if (this.panel === 'report')
276
+ return this._renderReportPane();
277
+ return '';
278
+ }
279
+ _renderDetailPane(item) {
280
+ const auto = item.verdict === 'pass' || item.verdict === 'fail';
281
+ const routingLabel = ROUTING_LABEL[item.routing] || item.routing;
282
+ const verdictLabel = VERDICT_LABEL[item.verdict] || item.verdict;
283
+ return html `
284
+ <div class="side-pane">
285
+ <div class="detail-header">
286
+ <span class="badge" ?pass=${item.verdict === 'pass'} ?fail=${item.verdict === 'fail'} ?etc=${!auto}>
287
+ ${routingLabel}
288
+ </span>
289
+ <h3>${verdictLabel}</h3>
290
+ </div>
291
+ <div class="detail-summary">${(item.observation || item.ai_report || '').trim()}</div>
292
+ <div class="kv-grid">
293
+ <div class="kv">
294
+ <label>판정</label>
295
+ <span class="verdict-value" ?pass=${item.verdict === 'pass'} ?fail=${item.verdict === 'fail'}>
296
+ ${verdictLabel || '-'}
297
+ </span>
298
+ </div>
299
+ ${item.confidence != null
300
+ ? html `
301
+ <div class="kv">
302
+ <label>신뢰도</label>
303
+ <span>${Math.round(item.confidence * 100)}%</span>
304
+ </div>
305
+ `
306
+ : ''}
307
+ <div class="kv">
308
+ <label>처리</label>
309
+ <span>${ROUTING_LABEL[item.routing] || item.routing || '-'}</span>
310
+ </div>
311
+ </div>
312
+
313
+ <div class="reason-box">
314
+ <div class="reason-verdict">
315
+ <label style="font-size: 11px;color: #7d2f2f; display: block; font-weight: bold;">판단 근거</label>
316
+ ${item.reason}
317
+ </div>
318
+ </div>
161
319
 
162
- <!-- 전체 화면을 덮는 로딩 오버레이 -->
163
- ${this.analyzing
164
- ? html `<div class="loading-overlay">
165
- <div class="loading-container">
166
- <md-circular-progress indeterminate></md-circular-progress>
167
- <div class="loading-text">분석 중...</div>
168
- </div>
169
- </div>`
320
+ <button class="text-button" @click=${this._closePanel}>닫기</button>
321
+ </div>
322
+ `;
323
+ }
324
+ _renderRecapturePane(item) {
325
+ var _a;
326
+ const shots = ((_a = item.recapture_shots) === null || _a === void 0 ? void 0 : _a.length)
327
+ ? item.recapture_shots
328
+ : [{ target: '추가 사진', how: '항목이 잘 보이도록 촬영해주세요.' }];
329
+ const taken = this.photos.length - this.recaptureBase;
330
+ const total = shots.length;
331
+ return html `
332
+ <div class="side-pane">
333
+ <h3>${item.text}</h3>
334
+ <div class="why-box">
335
+ <b>왜 추가 촬영이 필요한가요?</b><br />
336
+ ${item.reason || '판정을 위해 추가 사진이 필요합니다.'}
337
+ </div>
338
+ <div>
339
+ <h4 style="margin: 0 0 8px 0; font-size: 13px;">촬영 가이드</h4>
340
+ <ul
341
+ style="margin: 0; padding-left: 18px; font-size: 12px; color: #555; display: flex; flex-direction: column; gap: 6px;"
342
+ >
343
+ ${shots.map(s => html `<li>${s.target} — ${s.how}</li>`)}
344
+ </ul>
345
+ </div>
346
+ <div class="progress-row">
347
+ <span>사진 진행</span>
348
+ <span>${Math.min(taken, total)} / ${total}</span>
349
+ </div>
350
+ <div class="progress-bar"><div style="width: ${total ? Math.min(taken / total, 1) * 100 : 0}%"></div></div>
351
+ <div class="slot-grid">
352
+ ${shots.map((s, i) => {
353
+ const photo = this.photos[this.recaptureBase + i];
354
+ return html `
355
+ <div class="slot" @click=${() => this._openPhotoPicker(this.recaptureBase + i)}>
356
+ ${photo ? html `<img src=${photo.url} alt=${s.target} />` : html `<md-icon>add_a_photo</md-icon>${s.target}`}
357
+ </div>
358
+ `;
359
+ })}
360
+ </div>
361
+ ${taken > 0
362
+ ? html `<button class="primary-button" @click=${() => this._runInspection()}>재검사 실행 (${taken}장)</button>`
170
363
  : ''}
364
+ <button class="text-button" @click=${this._closePanel}>닫기</button>
365
+ </div>
171
366
  `;
172
367
  }
173
- async updated(changes) { }
368
+ _renderReportPane() {
369
+ var _a;
370
+ const items = this._sortedItems();
371
+ const summary = ((_a = this.result) === null || _a === void 0 ? void 0 : _a.summary) || {};
372
+ const etcCount = (summary.hold || 0) + (summary.pending || 0) + (summary.error || 0);
373
+ return html `
374
+ <div class="side-pane">
375
+ <h3>종합 검사 리포트</h3>
376
+ <div class="report-counts">
377
+ <div pass><b>${summary.pass || 0}</b><small>적합</small></div>
378
+ <div fail><b>${summary.fail || 0}</b><small>부적합</small></div>
379
+ <div etc><b>${etcCount}</b><small>미검사</small></div>
380
+ </div>
381
+ <div style="overflow-y: auto; max-height: 380px;">
382
+ ${items.map((it, i) => {
383
+ var _a;
384
+ const expanded = this.expandedReportId === it.item_id;
385
+ return html `
386
+ <div class="report-item">
387
+ <div class="report-head" @click=${() => this._toggleReportItem(it.item_id)}>
388
+ <span class="report-text">${i + 1}. ${it.text}</span>
389
+ <span
390
+ class="verdict"
391
+ ?pass=${it.verdict === 'pass'}
392
+ ?fail=${it.verdict === 'fail'}
393
+ ?etc=${it.verdict !== 'pass' && it.verdict !== 'fail'}
394
+ >
395
+ ${VERDICT_LABEL[it.verdict] || it.verdict}
396
+ </span>
397
+ <md-icon class="report-caret" ?open=${expanded}>expand_more</md-icon>
398
+ </div>
399
+ ${expanded
400
+ ? html `
401
+ <div class="report-basis">
402
+ ${it.ai_report ? html `<div class="report-ai">${(it.ai_report || '').trim()}</div>` : ''}
403
+ ${it.observation ? html `<div>관찰: ${it.observation}</div>` : ''}
404
+ ${it.reason ? html `<div>판단: ${it.reason}</div>` : ''}
405
+ ${((_a = it.used_images) === null || _a === void 0 ? void 0 : _a.length)
406
+ ? html `<div class="report-used">근거 사진: ${it.used_images.map(n => `#${n}`).join(', ')}</div>`
407
+ : ''}
408
+ </div>
409
+ `
410
+ : ''}
411
+ </div>
412
+ `;
413
+ })}
414
+ </div>
415
+ <button class="primary-button" @click=${this._registerInspection}>검사 등록</button>
416
+ <button class="text-button" @click=${this._closePanel}>닫기</button>
417
+ </div>
418
+ `;
419
+ }
420
+ /* ─── 라이프사이클 ─── */
174
421
  async pageUpdated(changes, lifecycle) {
175
422
  if (this.active) {
176
- this._onRetry();
423
+ this._resetAll();
177
424
  this.buildingInspectionId = lifecycle.resourceId || '';
178
425
  await this.initBuildingInspection(this.buildingInspectionId);
426
+ await this._loadShotGuide();
179
427
  }
180
428
  }
181
429
  async initBuildingInspection(buildingInspectionId = '') {
@@ -186,22 +434,21 @@ let BuildingInspectionAiMeasurement = class BuildingInspectionAiMeasurement exte
186
434
  buildingInspection(id: $buildingInspectionId) {
187
435
  id
188
436
  status
189
- requestDate
190
- drawingMarker
191
437
  checklist {
192
438
  id
439
+ name
193
440
  location
194
- inspectionDrawingType
441
+ checklistItems {
442
+ id
443
+ sequence
444
+ mainType
445
+ detailType
446
+ name
447
+ }
195
448
  }
196
449
  buildingLevel {
197
450
  id
198
451
  floor
199
- mainDrawing {
200
- id
201
- name
202
- fullpath
203
- }
204
- mainDrawingImage
205
452
  building {
206
453
  id
207
454
  name
@@ -213,9 +460,7 @@ let BuildingInspectionAiMeasurement = class BuildingInspectionAiMeasurement exte
213
460
  }
214
461
  }
215
462
  `,
216
- variables: {
217
- buildingInspectionId
218
- }
463
+ variables: { buildingInspectionId }
219
464
  });
220
465
  if (response.errors)
221
466
  return;
@@ -232,245 +477,251 @@ let BuildingInspectionAiMeasurement = class BuildingInspectionAiMeasurement exte
232
477
  }
233
478
  }
234
479
  `,
235
- variables: {
236
- buildingComplexId
237
- }
480
+ variables: { buildingComplexId }
238
481
  });
239
482
  if (response.errors)
240
483
  return;
241
484
  this.project = response.data.project;
242
485
  }
243
- _onVideoSelected(event) {
244
- var _a;
245
- const input = event.target;
246
- const file = (_a = input === null || input === void 0 ? void 0 : input.files) === null || _a === void 0 ? void 0 : _a[0];
247
- if (!file)
248
- return;
249
- // Only allow video files
250
- if (!file.type.startsWith('video/')) {
251
- console.warn('선택된 파일이 동영상이 아닙니다.');
252
- return;
253
- }
254
- // Revoke old object URL if exists
255
- if (this.capturedVideoUrl) {
256
- URL.revokeObjectURL(this.capturedVideoUrl);
486
+ /* ─── 엔진 호출 ─── */
487
+ /* 엔진 실행 공통 호출: input(JSON) + 사진 파일들을 multipart로 전송 */
488
+ async _callEngine(input, files = []) {
489
+ const form = new FormData();
490
+ form.append('input', JSON.stringify(input));
491
+ for (const file of files)
492
+ form.append('files', file, file.name);
493
+ const response = await fetch(ENGINE_RUN_URL, {
494
+ method: 'POST',
495
+ headers: { Authorization: ENGINE_AUTH },
496
+ body: form
497
+ });
498
+ const raw = await response.json();
499
+ const data = (raw === null || raw === void 0 ? void 0 : raw.result) || {};
500
+ if ((data === null || data === void 0 ? void 0 : data.status) !== 'success') {
501
+ throw new Error((data === null || data === void 0 ? void 0 : data.error) || (raw === null || raw === void 0 ? void 0 : raw.error) || 'AI 엔진 호출에 실패했습니다.');
257
502
  }
258
- this.videoFile = file;
259
- this.capturedVideoUrl = URL.createObjectURL(file);
503
+ return data.result;
260
504
  }
261
- async _onAnalyzeVideo() {
262
- var _a, _b, _c, _d, _e, _f, _g;
263
- if (!this.videoFile) {
264
- console.warn('선택된 동영상이 없습니다.');
505
+ /* [1단계] 촬영 가이드 생성 (plan-shots) */
506
+ async _loadShotGuide() {
507
+ const checklist = this.engineChecklist;
508
+ if (!checklist.length) {
509
+ notify({ message: '체크리스트 항목이 없습니다.', level: 'warn' });
265
510
  return;
266
511
  }
267
512
  try {
268
- this.analyzing = true;
269
- const form = new FormData();
270
- form.append('files', this.videoFile, this.videoFile.name);
271
- const response = await fetch(`${this.KEYPOINT_RULER_API_BASE_URL}/api/run/keypoint-ruler`, {
272
- method: 'POST',
273
- headers: { Authorization: `Basic ${btoa('admin:admin1234')}` },
274
- body: form
275
- });
276
- const data = await response.json();
277
- this.frame = {
278
- file_id: (_b = (_a = data === null || data === void 0 ? void 0 : data.result) === null || _a === void 0 ? void 0 : _a.output_files[0]) === null || _b === void 0 ? void 0 : _b.file_id,
279
- download_url: (_d = (_c = data === null || data === void 0 ? void 0 : data.result) === null || _c === void 0 ? void 0 : _c.output_files[0]) === null || _d === void 0 ? void 0 : _d.download_url,
280
- file_path: (_f = (_e = data === null || data === void 0 ? void 0 : data.result) === null || _e === void 0 ? void 0 : _e.frames[0]) === null || _f === void 0 ? void 0 : _f.file_path,
281
- calibration: (_g = data === null || data === void 0 ? void 0 : data.result) === null || _g === void 0 ? void 0 : _g.calibration
282
- };
513
+ this.loading = true;
514
+ this.loadingText = 'AI 촬영 가이드 생성 중...';
515
+ this.guide = await this._callEngine({ action: 'plan-shots', checklist });
283
516
  }
284
517
  catch (error) {
285
- console.error('동영상 업로드 실패:', error);
518
+ console.error('촬영 가이드 생성 실패:', error);
519
+ notify({ message: error.message, level: 'error' });
286
520
  }
287
521
  finally {
288
- this.analyzing = false;
289
- }
290
- }
291
- _onRetry() {
292
- // Clear current selection and reopen camera
293
- if (this.capturedVideoUrl) {
294
- URL.revokeObjectURL(this.capturedVideoUrl);
522
+ this.loading = false;
295
523
  }
296
- this.capturedVideoUrl = null;
297
- this.videoFile = null;
298
- this.frame = null;
299
- this.selectedPoints = { x1: null, y1: null, x2: null, y2: null };
300
- this.displayedImageRect = null;
301
- this.frameNaturalWidth = 0;
302
- this.frameNaturalHeight = 0;
303
- this.cameraInputEl.value = '';
304
- this.distance = null;
305
- this.unit = null;
306
- this.analyzingError = null;
307
524
  }
308
- _hasTwoPoints() {
309
- const { x1, y1, x2, y2 } = this.selectedPoints;
310
- return x1 !== null && y1 !== null && x2 !== null && y2 !== null;
311
- }
312
- _renderMarkers() {
313
- if (!this.displayedImageRect)
314
- return html ``;
315
- const { left, top, width, height } = this.displayedImageRect;
316
- const { x1, y1, x2, y2 } = this.selectedPoints;
317
- const toDisplay = (x, y) => {
318
- const dx = left + (x * width) / (this.frameNaturalWidth || 1);
319
- const dy = top + (y * height) / (this.frameNaturalHeight || 1);
320
- return { dx, dy };
321
- };
322
- return html `
323
- <!-- 첫 번째 점 -->
324
- ${x1 !== null && y1 !== null
325
- ? (() => {
326
- const p = toDisplay(x1, y1);
327
- return html `<div class="point-marker point-1" style="left:${p.dx}px; top:${p.dy}px;"></div>`;
328
- })()
329
- : ''}
330
- <!-- 두 번째 점 -->
331
- ${x2 !== null && y2 !== null
332
- ? (() => {
333
- const p = toDisplay(x2, y2);
334
- return html `<div class="point-marker point-2" style="left:${p.dx}px; top:${p.dy}px;"></div>`;
335
- })()
336
- : ''}
337
- <!-- 두점 사이 선 -->
338
- ${this._hasTwoPoints()
339
- ? (() => {
340
- const p1 = toDisplay(x1, y1);
341
- const p2 = toDisplay(x2, y2);
342
- const cx = (p1.dx + p2.dx) / 2;
343
- const cy = (p1.dy + p2.dy) / 2;
344
- // 선의 길이와 각도 계산
345
- const length = Math.sqrt(Math.pow(p2.dx - p1.dx, 2) + Math.pow(p2.dy - p1.dy, 2));
346
- const angle = Math.atan2(p2.dy - p1.dy, p2.dx - p1.dx) * (180 / Math.PI);
347
- return html `
348
- <!-- DIV로 그린 선 -->
349
- <div
350
- class="line-overlay"
351
- style="
352
- left: ${p1.dx}px;
353
- top: ${p1.dy}px;
354
- width: ${length}px;
355
- height: 3px;
356
- transform: rotate(${angle}deg);
357
- "
358
- ></div>
359
- <!-- 거리 라벨 -->
360
- ${this.analyzingError
361
- ? html `<div class="distance-label" style="left:${cx}px; top:${cy}px;">${this.analyzingError}</div>`
362
- : this.distance && this.unit
363
- ? html `<div class="distance-label" style="left:${cx}px; top:${cy}px;">${this.distance} ${this.unit}</div>`
364
- : ''}
365
- `;
366
- })()
367
- : ''}
368
- `;
369
- }
370
- async _onAnalyzePhoto() {
371
- if (!this.frame) {
372
- console.warn('분석할 사진이 없습니다.');
373
- return;
374
- }
375
- if (!this._hasTwoPoints()) {
376
- console.warn('두 점을 먼저 선택해 주세요.');
525
+ /* [2단계] 자동 판정 실행 (inspect)
526
+ * - 최초 판정: 전체 사진 + 전체 체크리스트
527
+ * - 재검사(재촬영 패널): 이번에 새로 찍은 사진만 + 해당 항목만 재판정 결과 병합 */
528
+ async _runInspection() {
529
+ const isRecapture = this.panel === 'recapture' && !!this.selectedItem;
530
+ // 재검사는 recaptureBase 이후에 찍은 사진만 전송
531
+ const files = (isRecapture ? this.photos.slice(this.recaptureBase) : this.photos).map(p => p.file);
532
+ if (!files.length) {
533
+ notify({ message: '촬영된 사진이 없습니다.', level: 'warn' });
377
534
  return;
378
535
  }
536
+ const checklist = isRecapture
537
+ ? this.engineChecklist.filter(c => c.item_id === this.selectedItem.item_id)
538
+ : this.engineChecklist;
379
539
  try {
380
- this.analyzing = true;
381
- const payload = {
382
- x1: this.selectedPoints.x1,
383
- y1: this.selectedPoints.y1,
384
- x2: this.selectedPoints.x2,
385
- y2: this.selectedPoints.y2,
386
- file_path: this.frame.file_path,
387
- calibration: this.frame.calibration
388
- };
389
- const form = new FormData();
390
- form.append('input', JSON.stringify(payload));
391
- const response = await fetch(`${this.KEYPOINT_RULER_API_BASE_URL}/api/run/keypoint-ruler`, {
392
- method: 'POST',
393
- headers: { Authorization: `Basic ${btoa('admin:admin1234')}` },
394
- body: form
395
- });
396
- const result = await response.json();
397
- const { distance, unit, error } = result.result || {};
398
- this.distance = distance;
399
- this.unit = unit;
400
- this.analyzingError = error;
540
+ this.loading = true;
541
+ this.loadingText = 'AI 자동 판정 중... (최대 1~2분 소요)';
542
+ const result = await this._callEngine({ action: 'inspect', checklist }, files);
543
+ if (isRecapture) {
544
+ this._mergeRecaptureResult(result);
545
+ }
546
+ else {
547
+ this.result = result;
548
+ }
549
+ this.panel = 'none';
550
+ this.selectedItem = null;
551
+ this.mainPhotoIndex = 0;
401
552
  }
402
553
  catch (error) {
403
- console.error('사진 분석 실패:', error);
554
+ console.error('AI 자동 판정 실패:', error);
555
+ notify({ message: error.message, level: 'error' });
404
556
  }
405
557
  finally {
406
- this.analyzing = false;
558
+ this.loading = false;
407
559
  }
408
560
  }
409
- async _savePhoto() {
410
- var _a, _b, _c;
411
- if (!this.frame || !this._hasTwoPoints() || this.distance === null || !this.unit) {
412
- notify({ message: '분석 완료 후 저장할 수 있습니다.', level: 'warn' });
561
+ /* 재검사(부분 전송) 결과를 기존 전체 결과에 병합
562
+ * - used_images는 전송한 부분집합 기준 1-based → 전체 사진 인덱스로 보정(recaptureBase 가산)
563
+ * - 해당 항목만 교체 summary 재계산 */
564
+ _mergeRecaptureResult(result) {
565
+ var _a, _b;
566
+ const base = this.recaptureBase;
567
+ const updated = ((result === null || result === void 0 ? void 0 : result.items_sorted) || (result === null || result === void 0 ? void 0 : result.items) || []).map((it) => (Object.assign(Object.assign({}, it), { used_images: (it.used_images || []).map((n) => n + base) })));
568
+ const byId = new Map(updated.map((it) => [it.item_id, it]));
569
+ const replace = (arr) => (arr || []).map(it => byId.get(it.item_id) || it);
570
+ const items = replace((_a = this.result) === null || _a === void 0 ? void 0 : _a.items);
571
+ const itemsSorted = ((_b = this.result) === null || _b === void 0 ? void 0 : _b.items_sorted) ? replace(this.result.items_sorted) : undefined;
572
+ const summary = { pass: 0, fail: 0, hold: 0, pending: 0, error: 0 };
573
+ for (const it of itemsSorted || items)
574
+ summary[it.verdict] = (summary[it.verdict] || 0) + 1;
575
+ this.result = Object.assign(Object.assign(Object.assign(Object.assign({}, this.result), { items }), (itemsSorted ? { items_sorted: itemsSorted } : {})), { summary });
576
+ }
577
+ /* ─── 사진 선택 ─── */
578
+ /* 슬롯 클릭 → 사진 선택기 열기 (모바일: 촬영/앨범 선택 팝업, 채워진 슬롯이면 교체) */
579
+ _openPhotoPicker(slotIndex = null) {
580
+ var _a;
581
+ this.pendingSlotIndex = slotIndex;
582
+ (_a = this.photoInputEl) === null || _a === void 0 ? void 0 : _a.click();
583
+ }
584
+ _onPhotosSelected(event) {
585
+ const input = event.target;
586
+ const files = Array.from((input === null || input === void 0 ? void 0 : input.files) || []).filter(f => f.type.startsWith('image/') || /\.heic$/i.test(f.name));
587
+ input.value = '';
588
+ const slotIndex = this.pendingSlotIndex;
589
+ this.pendingSlotIndex = null;
590
+ if (!files.length)
413
591
  return;
414
- }
415
- const checklistId = (_b = (_a = this.buildingInspection) === null || _a === void 0 ? void 0 : _a.checklist) === null || _b === void 0 ? void 0 : _b.id;
416
- let file;
417
- try {
418
- file = await this.getCurrentImage();
419
- }
420
- catch (e) {
421
- notify({ message: '이미지 합성에 실패했습니다.', level: 'error' });
592
+ // 채워진 슬롯을 클릭한 경우 → 해당 사진 교체 (첫 파일만 사용)
593
+ if (slotIndex !== null && slotIndex < this.photos.length) {
594
+ URL.revokeObjectURL(this.photos[slotIndex].url);
595
+ const next = [...this.photos];
596
+ next[slotIndex] = { file: files[0], url: URL.createObjectURL(files[0]) };
597
+ this.photos = next;
422
598
  return;
423
599
  }
424
- const response = await client.mutate({
425
- mutation: gql `
426
- mutation ($attachments: [NewAttachment!]!) {
427
- createAttachments(attachments: $attachments) {
428
- id
429
- }
600
+ // 슬롯/추가 순서대로 뒤에 추가
601
+ // 엔진 제한은 요청(호출)당 MAX_IMAGES장. 재검사는 새로 찍은 사진만 별도 전송되므로
602
+ // 누적 합계가 아니라 이번 회차(최초=전체, 재검사=recaptureBase 이후) 기준으로만 제한한다.
603
+ const batchBase = this.panel === 'recapture' ? this.recaptureBase : 0;
604
+ const cap = batchBase + MAX_IMAGES;
605
+ if (this.photos.length + files.length > cap) {
606
+ notify({ message: `사진은 회차당 최대 ${MAX_IMAGES}장까지 사용할 수 있습니다.`, level: 'warn' });
607
+ files.length = Math.max(0, cap - this.photos.length);
608
+ if (!files.length)
609
+ return;
430
610
  }
431
- `,
432
- variables: {
433
- attachments: [{ file, refBy: checklistId, refType: 'Checklist', description: (_c = this.buildingInspection) === null || _c === void 0 ? void 0 : _c.status }]
434
- },
435
- context: { hasUpload: true }
436
- });
437
- if (!response.errors) {
438
- notify({ message: '사진 대지를 저장하였습니다.', level: 'info' });
611
+ this.photos = [...this.photos, ...files.map(file => ({ file, url: URL.createObjectURL(file) }))];
612
+ }
613
+ _resetAll() {
614
+ this._resetPhotos();
615
+ this.guide = null;
616
+ this.result = null;
617
+ this.panel = 'none';
618
+ this.selectedItem = null;
619
+ this.mainPhotoIndex = 0;
620
+ this.recaptureBase = 0;
621
+ }
622
+ /* ─── 패널 열기/닫기 ─── */
623
+ _openDetail(item) {
624
+ // 보류(hold)는 추가 촬영이 필요한 케이스 → 상세 대신 추가 촬영 UI를 바로 표시
625
+ if (item.verdict === 'hold') {
626
+ this._openRecapture(item);
439
627
  return;
440
628
  }
441
- return response.data.createAttachments;
629
+ this.selectedItem = item;
630
+ this.mainPhotoIndex = 0; // 선택 항목의 첫 사용 사진부터 표시
631
+ this.panel = 'detail';
632
+ }
633
+ _openRecapture(item) {
634
+ this.selectedItem = item;
635
+ this.recaptureBase = this.photos.length;
636
+ this.panel = 'recapture';
637
+ }
638
+ /* 종합 리포트 항목 펼치기/접기 (한 번에 하나만 펼침) */
639
+ _toggleReportItem(id) {
640
+ this.expandedReportId = this.expandedReportId === id ? null : id;
641
+ }
642
+ _sortedItems() {
643
+ var _a, _b;
644
+ return ((_a = this.result) === null || _a === void 0 ? void 0 : _a.items_sorted) || ((_b = this.result) === null || _b === void 0 ? void 0 : _b.items) || [];
442
645
  }
443
- async getCurrentImage() {
444
- if (!this.imageWrapperEl) {
445
- throw new Error('이미지 영역을 찾을 수 없습니다.');
646
+ /* ─── [6단계] 검사 등록: 감리자 판정 자동 반영 + AI판단 근거 첨부 ─── */
647
+ async _registerInspection() {
648
+ var _a, _b;
649
+ const items = this._sortedItems();
650
+ if (!items.length) {
651
+ notify({ message: '판정 결과가 없습니다.', level: 'warn' });
652
+ return;
446
653
  }
654
+ // 적합/부적합으로 자동 판정된 항목만 반영 (보류/미지원/오류는 미검사로 건너뜀)
655
+ const judged = items.filter(it => it.verdict === 'pass' || it.verdict === 'fail');
447
656
  try {
448
- // html2canvas로 .image-wrapper 영역 캡처
449
- const canvas = await html2canvas(this.imageWrapperEl, {
450
- backgroundColor: null, // 투명 배경
451
- scale: 1, // 1:1 스케일
452
- useCORS: true, // CORS 이미지 허용
453
- allowTaint: false, // 보안상 false
454
- logging: false // 로그 비활성화
455
- });
456
- // 캔버스를 Blob으로 변환
457
- const blob = await new Promise(resolve => {
458
- canvas.toBlob(resolve, 'image/png', 1.0);
459
- });
460
- // File 객체로 변환하여 반환
461
- return new File([blob], `inspection_${Date.now()}.png`, {
462
- type: 'image/png'
463
- });
657
+ this.loading = true;
658
+ this.loadingText = '검사 결과 등록 중...';
659
+ // 1) 감리자 적합/부적합 자동 채움 (적합/부적합 판정된 항목만, 없으면 건너뜀)
660
+ if (judged.length) {
661
+ const response = await client.mutate({
662
+ mutation: gql `
663
+ mutation UpdateChecklistItemsAiJudgment($items: [ChecklistItemAiJudgment!]!) {
664
+ updateChecklistItemsAiJudgment(items: $items)
665
+ }
666
+ `,
667
+ variables: {
668
+ items: judged.map(it => ({
669
+ id: it.item_id,
670
+ supervisoryConfirmStatus: it.verdict === 'pass' ? 'T' : 'F'
671
+ }))
672
+ }
673
+ });
674
+ if (response.errors)
675
+ throw new Error(((_a = response.errors[0]) === null || _a === void 0 ? void 0 : _a.message) || '검사 등록에 실패했습니다.');
676
+ }
677
+ // 2) 항목별 첨부: 판단 근거(AI판단 텍스트) + 근거 사진 (판정 여부와 무관하게 전체 항목)
678
+ const attachments = [];
679
+ for (const it of items) {
680
+ if (it.ai_report) {
681
+ attachments.push({
682
+ file: new File([it.ai_report], `AI판단_${(it.text || it.item_id).slice(0, 30)}.txt`, { type: 'text/plain' }),
683
+ refBy: it.item_id,
684
+ refType: 'ChecklistItem',
685
+ description: 'AI판단'
686
+ });
687
+ }
688
+ for (const n of it.used_images || []) {
689
+ const photo = this.photos[n - 1];
690
+ if (photo) {
691
+ attachments.push({ file: photo.file, refBy: it.item_id, refType: 'ChecklistItem', description: 'AI판단' });
692
+ }
693
+ }
694
+ }
695
+ if (attachments.length) {
696
+ const attachResponse = await client.mutate({
697
+ mutation: gql `
698
+ mutation CreateAttachments($attachments: [NewAttachment!]!) {
699
+ createAttachments(attachments: $attachments) {
700
+ id
701
+ }
702
+ }
703
+ `,
704
+ variables: { attachments },
705
+ context: { hasUpload: true }
706
+ });
707
+ if (attachResponse.errors)
708
+ throw new Error(((_b = attachResponse.errors[0]) === null || _b === void 0 ? void 0 : _b.message) || 'AI판단 자료 첨부에 실패했습니다.');
709
+ }
710
+ notify({ message: `검사 결과를 등록하였습니다. (자동 판정 ${judged.length}건 반영)` });
711
+ // 등록 후 체크리스트 화면으로 이동
712
+ navigate(`building-inspection-detail-checklist/${this.buildingInspectionId}`);
464
713
  }
465
714
  catch (error) {
466
- console.error('html2canvas 캡처 실패:', error);
467
- throw new Error('화면 캡처에 실패했습니다.');
715
+ console.error('검사 등록 실패:', error);
716
+ notify({ message: error.message, level: 'error' });
717
+ }
718
+ finally {
719
+ this.loading = false;
468
720
  }
469
721
  }
470
722
  };
471
723
  BuildingInspectionAiMeasurement.styles = [
472
724
  ScrollbarStyles,
473
- CommonGristStyles,
474
725
  css `
475
726
  :host {
476
727
  display: grid;
@@ -481,272 +732,613 @@ BuildingInspectionAiMeasurement.styles = [
481
732
  background-color: #f7f7f7;
482
733
  }
483
734
 
735
+ * {
736
+ box-sizing: border-box;
737
+ }
738
+
484
739
  div[body] {
485
740
  display: flex;
486
- justify-items: center;
487
- gap: var(--spacing-medium);
488
- margin: var(--spacing-medium);
741
+ flex-direction: column;
489
742
  min-height: 0;
743
+ overflow-y: auto;
744
+ padding: var(--spacing-medium);
745
+ gap: 12px;
490
746
  }
491
747
 
492
- div[preview] {
493
- flex: 1;
494
- border: 2px solid #ddd;
495
- border-radius: 10px;
748
+ .page-title {
749
+ text-align: center;
750
+ font-size: 16px;
751
+ font-weight: 700;
752
+ color: #333;
753
+ }
496
754
 
755
+ .page-title span[target] {
756
+ color: #7d2f2f;
757
+ text-decoration: underline;
758
+ }
759
+
760
+ /* ─── 1단계: 촬영 가이드 ─── */
761
+ .guide-container {
762
+ max-width: 560px;
763
+ width: 100%;
764
+ margin: 0 auto;
497
765
  display: flex;
498
- justify-content: center;
499
- align-items: center;
500
- min-height: 0;
501
- overflow: hidden;
766
+ flex-direction: column;
767
+ gap: 14px;
502
768
  }
503
769
 
504
- div[preview] img {
505
- max-width: 100%;
506
- max-height: 100%;
507
- object-fit: contain;
770
+ .step-badge {
771
+ align-self: flex-start;
772
+ background: #f7e9e9;
773
+ color: #7d2f2f;
774
+ font-size: 12px;
775
+ font-weight: 700;
776
+ padding: 4px 10px;
777
+ border-radius: 12px;
508
778
  }
509
779
 
510
- div[preview] video {
511
- max-width: 100%;
512
- max-height: 100%;
513
- object-fit: contain;
780
+ .guide-title {
781
+ font-size: 26px;
782
+ font-weight: 800;
783
+ color: #222;
784
+ line-height: 1.3;
785
+ white-space: pre-line;
514
786
  }
515
787
 
516
- div[preview] md-icon {
517
- --md-icon-size: 160px;
788
+ .guide-desc {
789
+ font-size: 13px;
790
+ color: #777;
518
791
  }
519
792
 
520
- .loading-container {
521
- display: flex;
522
- flex-direction: column;
523
- align-items: center;
524
- justify-content: center;
525
- gap: 12px;
793
+ .card {
794
+ background: #fff;
795
+ border: 1px solid #e5e5e5;
796
+ border-radius: 10px;
797
+ padding: 16px;
526
798
  }
527
799
 
528
- .loading-text {
800
+ .card h4 {
801
+ margin: 0 0 10px 0;
529
802
  font-size: 14px;
530
- color: #666;
803
+ color: #333;
531
804
  }
532
805
 
533
- .frame-container {
534
- width: 100%;
535
- height: 100%;
536
- max-height: 100%;
537
- min-height: 0;
538
- overflow-y: auto;
806
+ .card ul {
807
+ margin: 0;
808
+ padding-left: 4px;
809
+ list-style: none;
539
810
  display: flex;
540
811
  flex-direction: column;
541
- gap: 12px;
542
- padding: 12px;
812
+ gap: 8px;
543
813
  }
544
814
 
545
- .frames-header {
815
+ .card ul li {
816
+ font-size: 13px;
817
+ color: #555;
546
818
  display: flex;
819
+ gap: 8px;
820
+ }
821
+
822
+ .card ul li::before {
823
+ content: '';
824
+ flex-shrink: 0;
825
+ width: 6px;
826
+ height: 6px;
827
+ border-radius: 50%;
828
+ background: #7d2f2f;
829
+ margin-top: 6px;
830
+ }
831
+
832
+ .progress-row {
833
+ display: flex;
834
+ justify-content: space-between;
547
835
  align-items: center;
548
- justify-content: center;
549
- gap: 12px;
550
- font-size: 20px;
836
+ font-size: 13px;
837
+ color: #555;
838
+ font-weight: 600;
839
+ }
840
+
841
+ .progress-bar {
842
+ height: 6px;
843
+ border-radius: 3px;
844
+ background: #eee;
845
+ overflow: hidden;
846
+ }
847
+
848
+ .progress-bar div {
849
+ height: 100%;
850
+ background: #7d2f2f;
851
+ transition: width 0.3s;
852
+ }
853
+
854
+ .next-shot {
855
+ font-size: 13px;
551
856
  font-weight: 700;
552
857
  color: #333;
858
+ }
859
+
860
+ .slot-grid {
861
+ display: flex;
862
+ gap: 8px;
863
+ }
864
+
865
+ .slot {
866
+ flex: 1;
867
+ aspect-ratio: 1;
868
+ border: 1px dashed #ccc;
869
+ border-radius: 8px;
870
+ background: #fafafa;
871
+ display: flex;
872
+ flex-direction: column;
873
+ justify-content: center;
874
+ align-items: center;
875
+ gap: 4px;
876
+ font-size: 12px;
877
+ color: #999;
553
878
  text-align: center;
554
- padding-block: 8px;
879
+ padding: 4px;
880
+ overflow: hidden;
881
+ cursor: pointer;
882
+ }
883
+
884
+ .slot md-icon {
885
+ --md-icon-size: 28px;
886
+ color: #b5b5b5;
887
+ }
888
+
889
+ .slot img {
890
+ width: 100%;
891
+ height: 100%;
892
+ object-fit: cover;
893
+ border-radius: 6px;
555
894
  }
556
895
 
557
- .frames-count {
896
+ .primary-button {
897
+ width: 100%;
898
+ border: none;
899
+ border-radius: 8px;
900
+ background: #7d2f2f;
901
+ color: #fff;
558
902
  font-size: 16px;
559
- color: #777;
560
- font-weight: 600;
903
+ font-weight: 700;
904
+ padding: 14px;
905
+ cursor: pointer;
561
906
  }
562
907
 
563
- .frames-grid {
564
- display: grid;
565
- grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
566
- gap: 12px;
908
+ .primary-button:disabled {
909
+ background: #ccc;
910
+ cursor: default;
567
911
  }
568
912
 
569
- .frame-item {
913
+ .text-button {
570
914
  border: 1px solid #ddd;
571
915
  border-radius: 8px;
572
- overflow: hidden;
573
916
  background: #fff;
917
+ color: #555;
918
+ font-size: 13px;
919
+ padding: 8px 14px;
574
920
  cursor: pointer;
575
- transition:
576
- box-shadow 0.2s,
577
- transform 0.1s;
578
921
  }
579
922
 
580
- .frame-item:hover {
581
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
582
- transform: translateY(-1px);
923
+ input[type='file'] {
924
+ display: none;
583
925
  }
584
926
 
585
- .frame-item img {
586
- display: block;
587
- width: 100%;
588
- height: 120px;
589
- object-fit: cover;
927
+ /* ─── 2단계: 판정 결과 ─── */
928
+ .result-body {
929
+ display: flex;
930
+ gap: 14px;
931
+ align-items: flex-start;
932
+ min-height: 0;
590
933
  }
591
934
 
592
- .image-wrapper {
593
- position: relative;
594
- width: 100%;
595
- height: 100%;
596
- align-content: center;
935
+ .photo-pane {
936
+ flex: 1.1;
937
+ min-width: 0;
938
+ display: flex;
939
+ flex-direction: column;
940
+ gap: 8px;
941
+ }
942
+
943
+ .photo-pane .caption {
597
944
  text-align: center;
945
+ font-size: 12px;
946
+ color: #999;
598
947
  }
599
948
 
600
- .point-marker {
601
- position: absolute;
602
- width: 14px;
603
- height: 14px;
604
- border-radius: 50%;
605
- border: 2px solid #fff;
606
- transform: translate(-50%, -50%);
607
- pointer-events: none;
608
- box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.25);
949
+ .photo-main {
950
+ border-radius: 10px;
951
+ overflow: hidden;
952
+ background: #000;
953
+ display: flex;
954
+ justify-content: center;
955
+ max-height: 480px;
609
956
  }
610
957
 
611
- .point-1 {
612
- background-color: #e53935;
958
+ .photo-main img {
959
+ max-width: 100%;
960
+ max-height: 480px;
961
+ object-fit: contain;
613
962
  }
614
963
 
615
- .point-2 {
616
- background-color: #1e88e5;
964
+ .photo-thumbs {
965
+ display: flex;
966
+ gap: 8px;
967
+ overflow-x: auto;
968
+ padding-bottom: 4px;
617
969
  }
618
970
 
619
- .line-overlay {
620
- position: absolute;
621
- pointer-events: none;
622
- background-color: #ff9800;
623
- transform-origin: left center;
971
+ .photo-thumbs img {
972
+ width: 96px;
973
+ height: 72px;
974
+ object-fit: cover;
975
+ border-radius: 6px;
976
+ cursor: pointer;
977
+ border: 2px solid transparent;
624
978
  }
625
979
 
626
- .distance-label {
627
- position: absolute;
628
- background: rgba(0, 0, 0, 0.7);
629
- color: #fff;
630
- font-size: 14px;
631
- padding: 2px 6px;
632
- border-radius: 4px;
633
- transform: translate(-50%, -120%);
634
- pointer-events: none;
635
- white-space: nowrap;
980
+ .photo-thumbs img[selected] {
981
+ border-color: #7d2f2f;
636
982
  }
637
983
 
638
- div[controls] {
639
- width: 240px;
984
+ .items-pane {
985
+ flex: 1.4;
986
+ min-width: 0;
640
987
  display: flex;
641
988
  flex-direction: column;
642
- justify-content: space-between;
643
- align-items: center;
989
+ gap: 10px;
644
990
  }
645
991
 
646
- div[action-buttons] {
647
- display: flex;
648
- flex-direction: row;
649
- justify-content: space-between;
650
- gap: 15px;
992
+ .summary-banner {
993
+ background: #f7e9e9;
994
+ border: 1px solid #eedada;
995
+ color: #7d2f2f;
996
+ font-size: 13px;
997
+ font-weight: 700;
998
+ border-radius: 8px;
999
+ padding: 10px 14px;
651
1000
  }
652
1001
 
653
- .switch-container {
1002
+ .group-title {
1003
+ font-size: 14px;
1004
+ font-weight: 700;
1005
+ color: #333;
1006
+ margin-top: 4px;
1007
+ }
1008
+
1009
+ .item-card {
654
1010
  display: flex;
655
- align-items: center;
656
1011
  gap: 10px;
657
- font-size: 24px;
658
- font-weight: bold;
1012
+ align-items: flex-start;
1013
+ background: #fff;
1014
+ border: 1px solid #e5e5e5;
1015
+ border-radius: 10px;
1016
+ padding: 12px;
1017
+ cursor: pointer;
659
1018
  }
660
1019
 
661
- ox-input-switch {
662
- --ox-simple-switch-fullwidth: 68px;
663
- --ox-simple-switch-fullheight: 34px;
664
- --ox-simple-switch-thumbnail-size: 34px;
1020
+ .item-card[selected] {
1021
+ border-color: #7d2f2f;
1022
+ box-shadow: 0 0 0 1px #7d2f2f inset;
665
1023
  }
666
1024
 
667
- .camera-shutter {
1025
+ .item-no {
1026
+ flex-shrink: 0;
1027
+ width: 24px;
1028
+ height: 24px;
1029
+ border-radius: 50%;
1030
+ background: #7d2f2f;
1031
+ color: #fff;
1032
+ font-size: 12px;
1033
+ font-weight: 700;
668
1034
  display: flex;
669
1035
  justify-content: center;
670
1036
  align-items: center;
1037
+ }
1038
+
1039
+ .item-main {
1040
+ flex: 1;
1041
+ min-width: 0;
1042
+ }
1043
+
1044
+ .item-name {
1045
+ font-size: 14px;
1046
+ font-weight: 700;
1047
+ color: #222;
1048
+ margin-bottom: 4px;
1049
+ }
1050
+
1051
+ .item-desc {
1052
+ font-size: 12px;
1053
+ color: #777;
1054
+ display: -webkit-box;
1055
+ -webkit-line-clamp: 2;
1056
+ -webkit-box-orient: vertical;
1057
+ overflow: hidden;
1058
+ }
1059
+
1060
+ .chip {
1061
+ display: inline-block;
1062
+ font-size: 11px;
1063
+ font-weight: 700;
1064
+ border-radius: 4px;
1065
+ padding: 2px 6px;
1066
+ margin-right: 6px;
1067
+ background: #eef2f7;
1068
+ color: #3f71a0;
1069
+ }
1070
+
1071
+ .chip-verdict {
1072
+ font-size: 12px;
1073
+ font-weight: 700;
1074
+ color: #555;
1075
+ }
1076
+
1077
+ .badge {
1078
+ flex-shrink: 0;
1079
+ font-size: 12px;
1080
+ font-weight: 700;
1081
+ border-radius: 14px;
1082
+ padding: 5px 12px;
1083
+ white-space: nowrap;
1084
+ }
1085
+
1086
+ .badge[pass] {
1087
+ background: #e8f5e9;
1088
+ color: #2e7d32;
1089
+ }
1090
+
1091
+ .badge[fail] {
1092
+ background: #fdecea;
1093
+ color: #c62828;
1094
+ }
1095
+
1096
+ .badge[etc] {
1097
+ background: #fff8e1;
1098
+ color: #b7791f;
1099
+ }
1100
+
1101
+ .capture-button {
1102
+ flex-shrink: 0;
1103
+ border: none;
1104
+ border-radius: 14px;
1105
+ background: #fff3e0;
1106
+ color: #b7791f;
1107
+ font-size: 12px;
1108
+ font-weight: 700;
1109
+ padding: 6px 12px;
671
1110
  cursor: pointer;
672
- position: relative;
673
1111
  }
674
1112
 
675
- .controls-title {
1113
+ /* ─── 우측 패널 (상세/재촬영/리포트) ─── */
1114
+ .side-pane {
1115
+ flex: 1;
1116
+ min-width: 280px;
1117
+ max-width: 360px;
1118
+ background: #fff;
1119
+ border: 1px solid #e5e5e5;
1120
+ border-radius: 10px;
1121
+ padding: 16px;
1122
+ display: flex;
1123
+ flex-direction: column;
1124
+ gap: 12px;
1125
+ }
1126
+
1127
+ .side-pane h3 {
1128
+ margin: 0;
1129
+ font-size: 16px;
1130
+ color: #222;
1131
+ }
1132
+
1133
+ .detail-header {
1134
+ display: flex;
1135
+ align-items: center;
1136
+ gap: 8px;
1137
+ }
1138
+
1139
+ .detail-header h3 {
676
1140
  font-size: 18px;
1141
+ }
1142
+
1143
+ .detail-summary {
1144
+ font-size: 12px;
1145
+ color: #555;
1146
+ line-height: 1.7;
1147
+ white-space: pre-line;
1148
+ border-bottom: 1px solid #f0f0f0;
1149
+ padding-bottom: 10px;
1150
+ }
1151
+
1152
+ .kv-grid {
1153
+ display: grid;
1154
+ grid-template-columns: 1fr 1fr;
1155
+ gap: 10px;
1156
+ }
1157
+
1158
+ .kv {
1159
+ display: flex;
1160
+ flex-direction: column;
1161
+ gap: 2px;
1162
+ border: 1px solid #eee;
1163
+ border-radius: 8px;
1164
+ padding: 10px 12px;
1165
+ background: #fff;
1166
+ }
1167
+
1168
+ .kv label {
1169
+ font-size: 11px;
1170
+ color: #999;
1171
+ }
1172
+
1173
+ .kv span {
1174
+ font-size: 15px;
677
1175
  font-weight: 700;
678
1176
  color: #333;
679
- margin-bottom: 8px;
1177
+ }
1178
+
1179
+ .kv .verdict-value[pass] {
1180
+ color: #2e7d32;
1181
+ }
1182
+
1183
+ .kv .verdict-value[fail] {
1184
+ color: #c62828;
1185
+ }
1186
+
1187
+ .reason-box {
1188
+ display: flex;
1189
+ flex-direction: column;
1190
+ gap: 10px;
1191
+ font-size: 12px;
1192
+ line-height: 1.7;
1193
+ }
1194
+
1195
+ .reason-verdict {
1196
+ border-left: 3px solid #7d2f2f;
1197
+ background: #fdf6f6;
1198
+ color: #333;
1199
+ padding: 8px 10px;
1200
+ }
1201
+
1202
+ .why-box {
1203
+ background: #fff8e1;
1204
+ border: 1px solid #f4e4b5;
1205
+ border-radius: 8px;
1206
+ padding: 10px;
1207
+ font-size: 12px;
1208
+ color: #7a5c14;
1209
+ line-height: 1.6;
1210
+ }
1211
+
1212
+ .report-counts {
1213
+ display: flex;
680
1214
  text-align: center;
1215
+ border: 1px solid #eee;
1216
+ border-radius: 8px;
1217
+ overflow: hidden;
681
1218
  }
682
1219
 
683
- .camera-shutter md-icon {
684
- --md-icon-size: 100px;
1220
+ .report-counts div {
1221
+ flex: 1;
1222
+ padding: 10px 0;
685
1223
  }
686
1224
 
687
- .camera-shutter input {
688
- opacity: 0;
689
- width: 100%;
690
- height: 100%;
691
- position: absolute;
692
- left: 0;
693
- top: 0;
694
- cursor: pointer;
1225
+ .report-counts b {
1226
+ display: block;
1227
+ font-size: 20px;
695
1228
  }
696
1229
 
697
- button {
698
- padding: 10px 20px;
699
- font-size: 16px;
700
- border-radius: 5px;
701
- border: none;
1230
+ .report-counts small {
1231
+ font-size: 11px;
1232
+ color: #888;
1233
+ }
1234
+
1235
+ .report-counts div[pass] b {
1236
+ color: #2e7d32;
1237
+ }
1238
+
1239
+ .report-counts div[fail] b {
1240
+ color: #c62828;
1241
+ }
1242
+
1243
+ .report-counts div[etc] b {
1244
+ color: #b7791f;
1245
+ }
1246
+
1247
+ .report-item {
1248
+ border-bottom: 1px solid #f0f0f0;
1249
+ font-size: 13px;
1250
+ color: #333;
1251
+ }
1252
+
1253
+ .report-head {
1254
+ display: flex;
1255
+ align-items: center;
1256
+ gap: 6px;
1257
+ padding: 10px 0;
702
1258
  cursor: pointer;
703
1259
  }
704
1260
 
705
- button.save {
706
- background-color: #4caf50;
707
- color: white;
1261
+ .report-head .report-text {
1262
+ flex: 1;
1263
+ min-width: 0;
1264
+ }
708
1265
 
709
- &:disabled {
710
- background-color: #ccc;
711
- cursor: default;
712
- }
1266
+ .report-caret {
1267
+ --md-icon-size: 20px;
1268
+ color: #999;
1269
+ transition: transform 0.2s;
713
1270
  }
714
1271
 
715
- button.retry {
716
- background-color: #f0ad4e;
717
- color: white;
1272
+ .report-caret[open] {
1273
+ transform: rotate(180deg);
718
1274
  }
719
1275
 
720
- button.photo {
721
- background-color: #1e88e5;
722
- color: white;
1276
+ .report-basis {
1277
+ display: flex;
1278
+ flex-direction: column;
1279
+ gap: 6px;
1280
+ font-size: 12px;
1281
+ color: #555;
1282
+ background: #f7f7f7;
1283
+ border-radius: 6px;
1284
+ padding: 10px;
1285
+ margin: 0 0 10px 0;
1286
+ }
723
1287
 
724
- &:disabled {
725
- background-color: #ccc;
726
- cursor: default;
727
- }
1288
+ .report-basis .report-ai {
1289
+ white-space: pre-line;
1290
+ color: #333;
728
1291
  }
729
1292
 
1293
+ .report-basis .report-used {
1294
+ color: #888;
1295
+ }
1296
+
1297
+ .report-item .verdict {
1298
+ font-weight: 700;
1299
+ flex-shrink: 0;
1300
+ }
1301
+
1302
+ .report-item .verdict[pass] {
1303
+ color: #2e7d32;
1304
+ }
1305
+
1306
+ .report-item .verdict[fail] {
1307
+ color: #c62828;
1308
+ }
1309
+
1310
+ .report-item .verdict[etc] {
1311
+ color: #b7791f;
1312
+ }
1313
+
1314
+ /* ─── 로딩 오버레이 ─── */
730
1315
  .loading-overlay {
731
1316
  position: fixed;
732
- top: 0;
733
- left: 0;
734
- right: 0;
735
- bottom: 0;
1317
+ inset: 0;
736
1318
  background: rgba(0, 0, 0, 0.5);
737
1319
  display: flex;
738
- flex-direction: column;
739
- align-items: center;
740
1320
  justify-content: center;
741
- gap: 12px;
1321
+ align-items: center;
742
1322
  z-index: 2000;
743
1323
  }
744
1324
 
745
- .loading-overlay .loading-container {
746
- background: white;
747
- padding: 32px;
1325
+ .loading-overlay .box {
1326
+ background: #fff;
1327
+ padding: 32px 48px;
748
1328
  border-radius: 12px;
749
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
1329
+ display: flex;
1330
+ flex-direction: column;
1331
+ align-items: center;
1332
+ gap: 12px;
1333
+ font-size: 14px;
1334
+ color: #555;
1335
+ }
1336
+
1337
+ .empty-state {
1338
+ text-align: center;
1339
+ color: #999;
1340
+ font-size: 14px;
1341
+ padding: 60px 0;
750
1342
  }
751
1343
  `
752
1344
  ];
@@ -765,59 +1357,49 @@ __decorate([
765
1357
  __decorate([
766
1358
  state(),
767
1359
  __metadata("design:type", Object)
768
- ], BuildingInspectionAiMeasurement.prototype, "capturedVideoUrl", void 0);
1360
+ ], BuildingInspectionAiMeasurement.prototype, "guide", void 0);
769
1361
  __decorate([
770
1362
  state(),
771
- __metadata("design:type", Object)
772
- ], BuildingInspectionAiMeasurement.prototype, "videoFile", void 0);
1363
+ __metadata("design:type", Array)
1364
+ ], BuildingInspectionAiMeasurement.prototype, "photos", void 0);
773
1365
  __decorate([
774
1366
  state(),
775
1367
  __metadata("design:type", Object)
776
- ], BuildingInspectionAiMeasurement.prototype, "frame", void 0);
777
- __decorate([
778
- state(),
779
- __metadata("design:type", Boolean)
780
- ], BuildingInspectionAiMeasurement.prototype, "analyzing", void 0);
781
- __decorate([
782
- state(),
783
- __metadata("design:type", Object)
784
- ], BuildingInspectionAiMeasurement.prototype, "selectedPoints", void 0);
1368
+ ], BuildingInspectionAiMeasurement.prototype, "result", void 0);
785
1369
  __decorate([
786
1370
  state(),
787
1371
  __metadata("design:type", Number)
788
- ], BuildingInspectionAiMeasurement.prototype, "frameNaturalWidth", void 0);
1372
+ ], BuildingInspectionAiMeasurement.prototype, "mainPhotoIndex", void 0);
789
1373
  __decorate([
790
1374
  state(),
791
- __metadata("design:type", Number)
792
- ], BuildingInspectionAiMeasurement.prototype, "frameNaturalHeight", void 0);
1375
+ __metadata("design:type", String)
1376
+ ], BuildingInspectionAiMeasurement.prototype, "panel", void 0);
793
1377
  __decorate([
794
1378
  state(),
795
1379
  __metadata("design:type", Object)
796
- ], BuildingInspectionAiMeasurement.prototype, "displayedImageRect", void 0);
1380
+ ], BuildingInspectionAiMeasurement.prototype, "selectedItem", void 0);
797
1381
  __decorate([
798
1382
  state(),
799
- __metadata("design:type", Object)
800
- ], BuildingInspectionAiMeasurement.prototype, "distance", void 0);
1383
+ __metadata("design:type", Number)
1384
+ ], BuildingInspectionAiMeasurement.prototype, "recaptureBase", void 0);
801
1385
  __decorate([
802
1386
  state(),
803
1387
  __metadata("design:type", Object)
804
- ], BuildingInspectionAiMeasurement.prototype, "unit", void 0);
1388
+ ], BuildingInspectionAiMeasurement.prototype, "expandedReportId", void 0);
805
1389
  __decorate([
806
1390
  state(),
807
- __metadata("design:type", Object)
808
- ], BuildingInspectionAiMeasurement.prototype, "analyzingError", void 0);
809
- __decorate([
810
- query('img#frameImage'),
811
- __metadata("design:type", HTMLImageElement)
812
- ], BuildingInspectionAiMeasurement.prototype, "frameImageEl", void 0);
1391
+ __metadata("design:type", Boolean)
1392
+ ], BuildingInspectionAiMeasurement.prototype, "loading", void 0);
813
1393
  __decorate([
814
- query('div.image-wrapper'),
815
- __metadata("design:type", HTMLDivElement)
816
- ], BuildingInspectionAiMeasurement.prototype, "imageWrapperEl", void 0);
1394
+ state(),
1395
+ __metadata("design:type", String)
1396
+ ], BuildingInspectionAiMeasurement.prototype, "loadingText", void 0);
817
1397
  __decorate([
818
- query('#cameraInput'),
819
- __metadata("design:type", HTMLInputElement)
820
- ], BuildingInspectionAiMeasurement.prototype, "cameraInputEl", void 0);
1398
+ query('#photoInput'),
1399
+ __metadata("design:type", HTMLInputElement
1400
+ /* 슬롯 클릭으로 선택기를 연 경우 대상 슬롯 인덱스 (null이면 뒤에 추가) */
1401
+ )
1402
+ ], BuildingInspectionAiMeasurement.prototype, "photoInputEl", void 0);
821
1403
  BuildingInspectionAiMeasurement = __decorate([
822
1404
  customElement('building-inspection-detail-ai-measurement')
823
1405
  ], BuildingInspectionAiMeasurement);