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