@dssp/supervision 1.0.17 → 1.0.19
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.
- package/dist-client/pages/building-inspection/building-inspection-detail-ai-defect.js +7 -0
- package/dist-client/pages/building-inspection/building-inspection-detail-ai-defect.js.map +1 -1
- package/dist-client/pages/building-inspection/building-inspection-detail-ai-measurement.d.ts +7 -0
- package/dist-client/pages/building-inspection/building-inspection-detail-ai-measurement.js +101 -21
- package/dist-client/pages/building-inspection/building-inspection-detail-ai-measurement.js.map +1 -1
- package/dist-client/pages/building-inspection/building-inspection-detail-camera.js +6 -0
- package/dist-client/pages/building-inspection/building-inspection-detail-camera.js.map +1 -1
- package/dist-client/pages/building-inspection/building-inspection-detail-checklist.js +6 -0
- package/dist-client/pages/building-inspection/building-inspection-detail-checklist.js.map +1 -1
- package/dist-client/pages/building-inspection/building-inspection-detail-drawing.js +6 -0
- package/dist-client/pages/building-inspection/building-inspection-detail-drawing.js.map +1 -1
- package/dist-client/pages/building-inspection/component/building-inspection-detail-header.js +6 -6
- package/dist-client/pages/building-inspection/component/building-inspection-detail-header.js.map +1 -1
- package/dist-client/pages/building-inspection/inspection-create-popup.d.ts +1 -0
- package/dist-client/pages/building-inspection/inspection-create-popup.js +35 -8
- package/dist-client/pages/building-inspection/inspection-create-popup.js.map +1 -1
- package/dist-client/pages/building-inspection/inspection-part-picker.d.ts +31 -0
- package/dist-client/pages/building-inspection/inspection-part-picker.js +228 -0
- package/dist-client/pages/building-inspection/inspection-part-picker.js.map +1 -0
- package/dist-client/pages/building-inspection/inspection-with-checklist-create-popup.d.ts +1 -0
- package/dist-client/pages/building-inspection/inspection-with-checklist-create-popup.js +40 -1
- package/dist-client/pages/building-inspection/inspection-with-checklist-create-popup.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/service/building-inspection/building-inspection-mutation.js +5 -1
- package/dist-server/service/building-inspection/building-inspection-mutation.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
|
@@ -9,6 +9,7 @@ import { customElement, state } from 'lit/decorators.js';
|
|
|
9
9
|
import { PageView } from '@operato/shell';
|
|
10
10
|
import { CommonGristStyles, ScrollbarStyles } from '@operato/styles';
|
|
11
11
|
import { client } from '@operato/graphql';
|
|
12
|
+
import { notify } from '@operato/layout';
|
|
12
13
|
import './component/building-inspection-detail-header';
|
|
13
14
|
let BuildingInspectionAiDefect = class BuildingInspectionAiDefect extends PageView {
|
|
14
15
|
constructor() {
|
|
@@ -90,6 +91,12 @@ let BuildingInspectionAiDefect = class BuildingInspectionAiDefect extends PageVi
|
|
|
90
91
|
if (response.errors)
|
|
91
92
|
return;
|
|
92
93
|
this.buildingInspection = response.data.buildingInspection;
|
|
94
|
+
// 삭제(soft delete)됐거나 다른 projectType의 검측이면 서버가 null을 준다.
|
|
95
|
+
// 그대로 진행하면 뒤 쿼리에 undefined가 실려 GraphQL 에러가 난다.
|
|
96
|
+
if (!this.buildingInspection) {
|
|
97
|
+
notify({ message: '삭제되었거나 접근할 수 없는 검측입니다.', level: 'warn' });
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
93
100
|
await this._getProjectByBuildingComplexId((_d = (_c = (_b = (_a = this.buildingInspection) === null || _a === void 0 ? void 0 : _a.buildingLevel) === null || _b === void 0 ? void 0 : _b.building) === null || _c === void 0 ? void 0 : _c.buildingComplex) === null || _d === void 0 ? void 0 : _d.id);
|
|
94
101
|
}
|
|
95
102
|
async _getProjectByBuildingComplexId(buildingComplexId) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"building-inspection-detail-ai-defect.js","sourceRoot":"","sources":["../../../client/pages/building-inspection/building-inspection-detail-ai-defect.ts"],"names":[],"mappings":";AAAA,OAAO,4BAA4B,CAAA;AACnC,OAAO,mCAAmC,CAAA;AAC1C,OAAO,wCAAwC,CAAA;AAC/C,OAAO,6CAA6C,CAAA;AAEpD,OAAO,GAAG,MAAM,aAAa,CAAA;AAC7B,OAAO,EAAE,GAAG,EAAE,IAAI,EAAkB,MAAM,KAAK,CAAA;AAC/C,OAAO,EAAE,aAAa,EAAE,KAAK,EAAS,MAAM,mBAAmB,CAAA;AAE/D,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEpE,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"building-inspection-detail-ai-defect.js","sourceRoot":"","sources":["../../../client/pages/building-inspection/building-inspection-detail-ai-defect.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@operato/input/ox-input-switch.js'\nimport '@operato/mini-map/ox-zoomable-image.js'\nimport '@material/web/progress/circular-progress.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, PropertyValues } from 'lit'\nimport { customElement, state, query } from 'lit/decorators.js'\n\nimport { PageView } from '@operato/shell'\nimport { CommonGristStyles, ScrollbarStyles } from '@operato/styles'\nimport { PageLifecycle } from '@operato/shell/dist/src/app/pages/page-view'\nimport { client } from '@operato/graphql'\nimport { notify } from '@operato/layout'\n\nimport './component/building-inspection-detail-header'\n\n@customElement('building-inspection-detail-ai-defect')\nexport class BuildingInspectionAiDefect extends PageView {\n static styles = [\n ScrollbarStyles,\n CommonGristStyles,\n css`\n :host {\n display: grid;\n grid-template-rows: 75px 1fr;\n color: #4e5055;\n width: 100%;\n height: 100%;\n background-color: #f7f7f7;\n }\n\n div[body] {\n display: flex;\n justify-items: center;\n gap: var(--spacing-medium);\n margin: var(--spacing-medium);\n min-height: 0;\n }\n\n div[preview] {\n flex: 1;\n border: 2px solid #ddd;\n border-radius: 10px;\n\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 0;\n overflow: hidden;\n }\n\n div[preview] video {\n flex: 1;\n max-width: 100%;\n max-height: 100%;\n object-fit: contain;\n }\n `\n ]\n\n @state() project: any = {}\n @state() buildingInspection: any = {}\n @state() buildingInspectionId: string = ''\n\n get context() {\n return {\n title: '검측 관리 상세 - AI 하자 분석'\n }\n }\n\n render() {\n return html`\n <building-inspection-detail-header\n .buildingInspectionId=${this.buildingInspection?.id}\n .buildingLevelId=${this.buildingInspection?.buildingLevel?.id}\n .projectName=${this.project.name}\n .buildingName=${this.buildingInspection?.buildingLevel?.building?.name}\n .buildingLevelFloor=${this.buildingInspection?.buildingLevel?.floor}\n .projectId=${this.project.id}\n ></building-inspection-detail-header>\n\n <div body>\n <div preview>\n <video src=\"/attachment/21e53688-5262-4fae-961a-591db7114b75.mp4\" controls playsinline></video>\n </div>\n </div>\n `\n }\n\n protected async updated(changes: PropertyValues): Promise<void> {}\n\n async pageUpdated(changes: any, lifecycle: PageLifecycle) {\n if (this.active) {\n this.buildingInspectionId = lifecycle.resourceId || ''\n await this.initBuildingInspection(this.buildingInspectionId)\n }\n }\n\n async initBuildingInspection(buildingInspectionId: string = '') {\n const response = await client.query({\n query: gql`\n query BuildingInspection($buildingInspectionId: String!) {\n buildingInspection(id: $buildingInspectionId) {\n id\n status\n requestDate\n drawingMarker\n checklist {\n id\n location\n inspectionDrawingType\n }\n buildingLevel {\n id\n floor\n mainDrawing {\n id\n name\n fullpath\n }\n mainDrawingImage\n building {\n id\n name\n buildingComplex {\n id\n }\n }\n }\n }\n }\n `,\n variables: {\n buildingInspectionId\n }\n })\n\n if (response.errors) return\n\n this.buildingInspection = response.data.buildingInspection\n\n // 삭제(soft delete)됐거나 다른 projectType의 검측이면 서버가 null을 준다.\n // 그대로 진행하면 뒤 쿼리에 undefined가 실려 GraphQL 에러가 난다.\n if (!this.buildingInspection) {\n notify({ message: '삭제되었거나 접근할 수 없는 검측입니다.', level: 'warn' })\n return\n }\n\n await this._getProjectByBuildingComplexId(this.buildingInspection?.buildingLevel?.building?.buildingComplex?.id)\n }\n\n private async _getProjectByBuildingComplexId(buildingComplexId) {\n const response = await client.query({\n query: gql`\n query ProjectByBuildingComplexId($buildingComplexId: String!) {\n project: projectByBuildingComplexId(buildingComplexId: $buildingComplexId) {\n id\n name\n }\n }\n `,\n variables: {\n buildingComplexId\n }\n })\n\n if (response.errors) return\n\n this.project = response.data.project\n }\n}\n"]}
|
package/dist-client/pages/building-inspection/building-inspection-detail-ai-measurement.d.ts
CHANGED
|
@@ -41,6 +41,7 @@ export declare class BuildingInspectionAiMeasurement extends PageView {
|
|
|
41
41
|
setPickerFor: number | null;
|
|
42
42
|
recentSets: any[];
|
|
43
43
|
recentLoading: boolean;
|
|
44
|
+
private autoAttachSince;
|
|
44
45
|
expandedReportId: string | null;
|
|
45
46
|
measureSetIndex: number;
|
|
46
47
|
measureInputs: Record<string, string>;
|
|
@@ -66,7 +67,9 @@ export declare class BuildingInspectionAiMeasurement extends PageView {
|
|
|
66
67
|
private setInputEl;
|
|
67
68
|
private pendingSetIndex;
|
|
68
69
|
private objectUrls;
|
|
70
|
+
private _onVisible;
|
|
69
71
|
connectedCallback(): Promise<void>;
|
|
72
|
+
disconnectedCallback(): void;
|
|
70
73
|
get context(): {
|
|
71
74
|
title: string;
|
|
72
75
|
};
|
|
@@ -96,6 +99,10 @@ export declare class BuildingInspectionAiMeasurement extends PageView {
|
|
|
96
99
|
private _callMeasure;
|
|
97
100
|
private _fetchMeasureImage;
|
|
98
101
|
private _loadShotGuide;
|
|
102
|
+
private _shotTag;
|
|
103
|
+
private _shotTechnique;
|
|
104
|
+
private _shotAppLink;
|
|
105
|
+
private _autoAttachTaggedSets;
|
|
99
106
|
private _renderSetChooser;
|
|
100
107
|
private _loadRecentSets;
|
|
101
108
|
private _openSetChooser;
|
|
@@ -82,6 +82,7 @@ let BuildingInspectionAiMeasurement = class BuildingInspectionAiMeasurement exte
|
|
|
82
82
|
this.setPickerFor = null; // 세트 고르는 중인 촬영 번호 (-1 = 재촬영)
|
|
83
83
|
this.recentSets = []; // 측정 서버에 올라온 최근 세트 (앱이 촬영 직후 자동 전송)
|
|
84
84
|
this.recentLoading = false;
|
|
85
|
+
this.autoAttachSince = 0; // 이 시각(초) 이후 업로드된 세트만 자동 등록
|
|
85
86
|
this.expandedReportId = null; // 종합 리포트에서 펼친 항목(아코디언)
|
|
86
87
|
/* 측정 패널 상태 */
|
|
87
88
|
this.measureSetIndex = 0;
|
|
@@ -101,6 +102,11 @@ let BuildingInspectionAiMeasurement = class BuildingInspectionAiMeasurement exte
|
|
|
101
102
|
/* 세트 업로드 대상: 촬영 건 인덱스(가이드 화면) 또는 -1(재촬영 패널) */
|
|
102
103
|
this.pendingSetIndex = null;
|
|
103
104
|
this.objectUrls = []; // 페이지 이탈 시 정리할 blob URL들
|
|
105
|
+
/* 촬영 앱에 다녀온 뒤 화면이 다시 보이면 올라온 세트를 찾아 자동 등록한다 */
|
|
106
|
+
this._onVisible = () => {
|
|
107
|
+
if (document.visibilityState === 'visible' && this.guide && !this.result)
|
|
108
|
+
this._autoAttachTaggedSets();
|
|
109
|
+
};
|
|
104
110
|
this._openReport = () => {
|
|
105
111
|
this.panel = 'report';
|
|
106
112
|
this.selectedItem = null;
|
|
@@ -129,9 +135,14 @@ let BuildingInspectionAiMeasurement = class BuildingInspectionAiMeasurement exte
|
|
|
129
135
|
}
|
|
130
136
|
async connectedCallback() {
|
|
131
137
|
super.connectedCallback();
|
|
138
|
+
document.addEventListener('visibilitychange', this._onVisible);
|
|
132
139
|
this.canMutate = await canDcspMutate();
|
|
133
140
|
this.updateContext();
|
|
134
141
|
}
|
|
142
|
+
disconnectedCallback() {
|
|
143
|
+
document.removeEventListener('visibilitychange', this._onVisible);
|
|
144
|
+
super.disconnectedCallback();
|
|
145
|
+
}
|
|
135
146
|
get context() {
|
|
136
147
|
return {
|
|
137
148
|
title: '검측 관리 상세 - AI 자동 검측'
|
|
@@ -174,14 +185,10 @@ let BuildingInspectionAiMeasurement = class BuildingInspectionAiMeasurement exte
|
|
|
174
185
|
${this.result ? this._renderResult() : this._renderGuide()}
|
|
175
186
|
</div>
|
|
176
187
|
|
|
177
|
-
<!-- 세트 파일 선택: video.mov/poses.jsonl/meta.json 3개 멀티선택 또는 zip 1
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
accept=".mov,.jsonl,.json,.zip,video/quicktime"
|
|
182
|
-
multiple
|
|
183
|
-
@change=${this._onSetFilesSelected}
|
|
184
|
-
/>
|
|
188
|
+
<!-- 세트 파일 선택: video.mov/poses.jsonl/meta.json 3개 멀티선택 또는 zip 1개.
|
|
189
|
+
accept을 두지 않는다 — iOS는 확장자를 UTI로 바꿔 거르는데 .jsonl은 매핑이 없어
|
|
190
|
+
파일 앱에서 poses.jsonl이 비활성으로 보인다. 파일명 검증은 아래 핸들러가 한다. -->
|
|
191
|
+
<input id="setInput" type="file" multiple @change=${this._onSetFilesSelected} />
|
|
185
192
|
|
|
186
193
|
${this.loading
|
|
187
194
|
? html `
|
|
@@ -232,11 +239,6 @@ let BuildingInspectionAiMeasurement = class BuildingInspectionAiMeasurement exte
|
|
|
232
239
|
<span>${uploaded} / ${total}</span>
|
|
233
240
|
</div>
|
|
234
241
|
<div class="progress-bar"><div style="width: ${total ? (uploaded / total) * 100 : 0}%"></div></div>
|
|
235
|
-
<div class="app-links">
|
|
236
|
-
<span>촬영 앱 열기</span>
|
|
237
|
-
<a href=${appCaptureLink('cover')}>철근 배근 길이</a>
|
|
238
|
-
<a href=${appCaptureLink('thick')}>벽·기둥 두께</a>
|
|
239
|
-
</div>
|
|
240
242
|
${shots.map((s, i) => {
|
|
241
243
|
const set = this.sets[i];
|
|
242
244
|
return html `
|
|
@@ -245,9 +247,8 @@ let BuildingInspectionAiMeasurement = class BuildingInspectionAiMeasurement exte
|
|
|
245
247
|
<div class="set-main">
|
|
246
248
|
<b>촬영 ${s.shot_no || i + 1} · ${s.target}</b>
|
|
247
249
|
${set
|
|
248
|
-
? html `세트
|
|
249
|
-
|
|
250
|
-
: html `탭해서 앱으로 촬영한 세트를 선택 (또는 파일 직접 선택)`}
|
|
250
|
+
? html `세트 연결 완료 (${set.setId}) ${set.warnings.map(w => html `<div class="set-warn">⚠ ${w}</div>`)}`
|
|
251
|
+
: html `「앱으로 촬영」을 누르면 촬영 후 이 칸에 자동 등록됩니다 · 탭하면 직접 선택`}
|
|
251
252
|
</div>
|
|
252
253
|
${set
|
|
253
254
|
? html `<button
|
|
@@ -259,7 +260,9 @@ let BuildingInspectionAiMeasurement = class BuildingInspectionAiMeasurement exte
|
|
|
259
260
|
>
|
|
260
261
|
교체
|
|
261
262
|
</button>`
|
|
262
|
-
:
|
|
263
|
+
: html `<a class="shoot-link" href=${this._shotAppLink(s, i)} @click=${(e) => e.stopPropagation()}>
|
|
264
|
+
<md-icon>photo_camera</md-icon>앱으로 촬영
|
|
265
|
+
</a>`}
|
|
263
266
|
</div>
|
|
264
267
|
${this.setPickerFor === i ? this._renderSetChooser(i) : ''}
|
|
265
268
|
`;
|
|
@@ -504,9 +507,8 @@ let BuildingInspectionAiMeasurement = class BuildingInspectionAiMeasurement exte
|
|
|
504
507
|
`
|
|
505
508
|
: ''}
|
|
506
509
|
<div class="app-links">
|
|
507
|
-
<span
|
|
508
|
-
<a href=${appCaptureLink(
|
|
509
|
-
<a href=${appCaptureLink('thick')}>벽·기둥 두께</a>
|
|
510
|
+
<span>앱으로 재촬영</span>
|
|
511
|
+
<a href=${`${appCaptureLink(this._shotTechnique(shot))}&set=${encodeURIComponent(this._shotTag(0))}`}> 촬영 시작 </a>
|
|
510
512
|
</div>
|
|
511
513
|
<div class="set-card" ?uploaded=${!!this.recaptureSet} @click=${() => this._openSetChooser(-1)}>
|
|
512
514
|
<md-icon>${this.recaptureSet ? 'check_circle' : 'video_file'}</md-icon>
|
|
@@ -766,6 +768,12 @@ let BuildingInspectionAiMeasurement = class BuildingInspectionAiMeasurement exte
|
|
|
766
768
|
if (response.errors)
|
|
767
769
|
return;
|
|
768
770
|
this.buildingInspection = response.data.buildingInspection;
|
|
771
|
+
// 삭제(soft delete)됐거나 다른 projectType의 검측이면 서버가 null을 준다.
|
|
772
|
+
// 그대로 진행하면 뒤 쿼리에 undefined가 실려 GraphQL 에러가 난다.
|
|
773
|
+
if (!this.buildingInspection) {
|
|
774
|
+
notify({ message: '삭제되었거나 접근할 수 없는 검측입니다.', level: 'warn' });
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
769
777
|
await this._getProjectByBuildingComplexId((_d = (_c = (_b = (_a = this.buildingInspection) === null || _a === void 0 ? void 0 : _a.buildingLevel) === null || _b === void 0 ? void 0 : _b.building) === null || _c === void 0 ? void 0 : _c.buildingComplex) === null || _d === void 0 ? void 0 : _d.id);
|
|
770
778
|
}
|
|
771
779
|
async _getProjectByBuildingComplexId(buildingComplexId) {
|
|
@@ -836,6 +844,9 @@ let BuildingInspectionAiMeasurement = class BuildingInspectionAiMeasurement exte
|
|
|
836
844
|
this.loadingText = 'AI 촬영 가이드 생성 중...';
|
|
837
845
|
this.guide = await this._callEngine({ action: 'plan-shots', checklist });
|
|
838
846
|
this.sets = new Array((((_a = this.guide) === null || _a === void 0 ? void 0 : _a.shots) || []).length).fill(null);
|
|
847
|
+
// 자동 등록 기준 시각 — 이 시점 이후 올라온 세트만 자동으로 붙인다.
|
|
848
|
+
// (예전 테스트 세트가 태그만 같다고 채워지면 "찍지도 않았는데 완료"로 보인다)
|
|
849
|
+
this.autoAttachSince = Date.now() / 1000;
|
|
839
850
|
}
|
|
840
851
|
catch (error) {
|
|
841
852
|
console.error('촬영 가이드 생성 실패:', error);
|
|
@@ -845,6 +856,48 @@ let BuildingInspectionAiMeasurement = class BuildingInspectionAiMeasurement exte
|
|
|
845
856
|
this.loading = false;
|
|
846
857
|
}
|
|
847
858
|
}
|
|
859
|
+
/* 촬영 칸마다 고유 태그 — 앱에 부재 ID로 넘겨 두면, 올라온 세트의 member_id로
|
|
860
|
+
"어느 촬영 칸의 것인지" 되찾을 수 있다 (앱↔플랫폼 왕복에 식별자가 없어서 쓰는 방법) */
|
|
861
|
+
_shotTag(shotNo) {
|
|
862
|
+
return `${(this.buildingInspectionId || 'ins').replace(/-/g, '').slice(0, 6)}-${shotNo}`;
|
|
863
|
+
}
|
|
864
|
+
/* 촬영 지시문에서 기술 추론 — 앱의 촬영 안내만 달라진다(측정은 meta.technique를 안 봄) */
|
|
865
|
+
_shotTechnique(shot) {
|
|
866
|
+
const text = `${(shot === null || shot === void 0 ? void 0 : shot.target) || ''} ${(shot === null || shot === void 0 ? void 0 : shot.how) || ''}`;
|
|
867
|
+
if (/두께|콘크리트\s*면|거푸집|탈형|벽체\s*면/.test(text))
|
|
868
|
+
return 'thick';
|
|
869
|
+
return 'cover';
|
|
870
|
+
}
|
|
871
|
+
_shotAppLink(shot, index) {
|
|
872
|
+
const tag = this._shotTag((shot === null || shot === void 0 ? void 0 : shot.shot_no) || index + 1);
|
|
873
|
+
return `${appCaptureLink(this._shotTechnique(shot))}&set=${encodeURIComponent(tag)}`;
|
|
874
|
+
}
|
|
875
|
+
/* 앱에서 촬영하고 돌아오면 태그로 찾아 해당 촬영 칸에 자동 등록 */
|
|
876
|
+
async _autoAttachTaggedSets() {
|
|
877
|
+
var _a, _b;
|
|
878
|
+
if (!((_b = (_a = this.guide) === null || _a === void 0 ? void 0 : _a.shots) === null || _b === void 0 ? void 0 : _b.length))
|
|
879
|
+
return;
|
|
880
|
+
await this._loadRecentSets();
|
|
881
|
+
const used = new Set(this.uploadedSets.map(s => s.setId));
|
|
882
|
+
const attached = [];
|
|
883
|
+
const next = [...this.sets];
|
|
884
|
+
this.guide.shots.forEach((shot, i) => {
|
|
885
|
+
if (next[i])
|
|
886
|
+
return;
|
|
887
|
+
const tag = this._shotTag((shot === null || shot === void 0 ? void 0 : shot.shot_no) || i + 1);
|
|
888
|
+
// 이 화면을 연 뒤에 올라온 것만 — 예전 세트는 목록에서 직접 고르게 둔다
|
|
889
|
+
const hit = this.recentSets.find(s => s.member_id === tag && !used.has(s.set_id) && (s.uploaded_at || 0) >= this.autoAttachSince);
|
|
890
|
+
if (!hit)
|
|
891
|
+
return;
|
|
892
|
+
used.add(hit.set_id);
|
|
893
|
+
next[i] = { setId: hit.set_id, technique: hit.technique, label: `세트 ${hit.set_id}`, warnings: [] };
|
|
894
|
+
attached.push((shot === null || shot === void 0 ? void 0 : shot.shot_no) || i + 1);
|
|
895
|
+
});
|
|
896
|
+
if (attached.length) {
|
|
897
|
+
this.sets = next;
|
|
898
|
+
notify({ message: `촬영 ${attached.join('·')} 세트를 자동으로 등록했습니다.` });
|
|
899
|
+
}
|
|
900
|
+
}
|
|
848
901
|
/* 세트 선택 목록 — 앱이 올린 최근 세트 + 파일 직접 선택 폴백 */
|
|
849
902
|
_renderSetChooser(index) {
|
|
850
903
|
return html `
|
|
@@ -861,7 +914,9 @@ let BuildingInspectionAiMeasurement = class BuildingInspectionAiMeasurement exte
|
|
|
861
914
|
</span>
|
|
862
915
|
</button>
|
|
863
916
|
`)
|
|
864
|
-
: html `<div class="chooser-empty"
|
|
917
|
+
: html `<div class="chooser-empty">
|
|
918
|
+
올라온 세트가 없습니다. 카드의 「앱으로 촬영」으로 찍으면 여기에 나타납니다.
|
|
919
|
+
</div>`}
|
|
865
920
|
<div class="chooser-actions">
|
|
866
921
|
<button class="text-button" @click=${() => this._loadRecentSets()}>새로고침</button>
|
|
867
922
|
<button class="text-button" @click=${() => this._openSetPicker(index)}>파일 직접 선택</button>
|
|
@@ -1543,6 +1598,26 @@ BuildingInspectionAiMeasurement.styles = [
|
|
|
1543
1598
|
color: #777;
|
|
1544
1599
|
}
|
|
1545
1600
|
|
|
1601
|
+
/* 촬영 칸의 「앱으로 촬영」 */
|
|
1602
|
+
.shoot-link {
|
|
1603
|
+
display: inline-flex;
|
|
1604
|
+
align-items: center;
|
|
1605
|
+
gap: 4px;
|
|
1606
|
+
flex-shrink: 0;
|
|
1607
|
+
padding: 7px 12px;
|
|
1608
|
+
border-radius: 999px;
|
|
1609
|
+
background: #7d2f2f;
|
|
1610
|
+
color: #fff;
|
|
1611
|
+
font-size: 12px;
|
|
1612
|
+
font-weight: 600;
|
|
1613
|
+
text-decoration: none;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
.shoot-link md-icon {
|
|
1617
|
+
--md-icon-size: 16px;
|
|
1618
|
+
color: #fff;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1546
1621
|
.app-links a {
|
|
1547
1622
|
display: inline-flex;
|
|
1548
1623
|
align-items: center;
|
|
@@ -1604,6 +1679,11 @@ BuildingInspectionAiMeasurement.styles = [
|
|
|
1604
1679
|
padding-top: 4px;
|
|
1605
1680
|
}
|
|
1606
1681
|
|
|
1682
|
+
.set-chooser .app-links {
|
|
1683
|
+
border-top: 1px solid #f0f0f0;
|
|
1684
|
+
padding: 8px 4px 6px;
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1607
1687
|
/* 세트 업로드 카드 */
|
|
1608
1688
|
.set-card {
|
|
1609
1689
|
display: flex;
|