@dssp/dkpi 1.0.0-alpha.78 → 1.0.0-alpha.79
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/project-complete-tabs/pc-tab1-plan.d.ts +27 -0
- package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js +332 -9
- package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js.map +1 -1
- 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/tsconfig.tsbuildinfo +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -3
- package/schema.graphql +141 -0
|
@@ -4,7 +4,34 @@ export declare class SvProjectCompleteTab1Plan extends LitElement {
|
|
|
4
4
|
kpiMetricValues: any;
|
|
5
5
|
kpiMetrics: any;
|
|
6
6
|
project: any;
|
|
7
|
+
/** 연동 진행 상태: source → 'idle' | 'collecting' | 'done' */
|
|
8
|
+
collectStatus: Record<string, 'idle' | 'collecting' | 'done'>;
|
|
9
|
+
/** 자동 수집한 실제값의 출처: metricId → 시스템 라벨 */
|
|
10
|
+
valueSources: Record<string, string>;
|
|
11
|
+
/** 자동 수집한 계획값: projectKey → 값 (키스콘이 제공하는 계획공사기간/계획공사비) */
|
|
12
|
+
collectedPlan: Record<string, number>;
|
|
13
|
+
/** 계획값 출처: projectKey → 시스템 라벨 */
|
|
14
|
+
planSources: Record<string, string>;
|
|
15
|
+
/** 시스템별 수집 요약 (카드 표시용): 라벨 → [{label, text}] */
|
|
16
|
+
collectedSummary: Record<string, {
|
|
17
|
+
label: string;
|
|
18
|
+
text: string;
|
|
19
|
+
}[]>;
|
|
20
|
+
/** 방금 채워진 셀 강조용 (metricId 또는 plan:projectKey) */
|
|
21
|
+
justFilled: Record<string, boolean>;
|
|
22
|
+
collecting: boolean;
|
|
23
|
+
/** 계획 칼럼을 갖는 항목 (키스콘이 계획값을 제공) */
|
|
24
|
+
private static readonly PLAN_KEYS;
|
|
7
25
|
render(): import("lit-html").TemplateResult<1>;
|
|
26
|
+
private _renderCollectPanel;
|
|
27
|
+
/** 패널 카드에 표시할, 해당 시스템에서 수집한 값 요약 */
|
|
28
|
+
private _collectedFieldsOf;
|
|
29
|
+
/** 자동 수집 — 시스템별로 차례로 연동하여 값을 폼에 채운다.
|
|
30
|
+
* 키스콘이 주는 계획공사기간/계획공사비는 "계획" 칼럼으로,
|
|
31
|
+
* 세움터/올바로 등 나머지는 "실제" 칼럼으로 채운다. */
|
|
32
|
+
private _autoCollect;
|
|
33
|
+
/** 메트릭의 실제값 셀을 set/add (사용자 입력과 동일 경로) */
|
|
34
|
+
private _setMetricValue;
|
|
8
35
|
willUpdate(changedProperties: Map<string, any>): void;
|
|
9
36
|
private _getInitData;
|
|
10
37
|
private _onInputChange;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
var SvProjectCompleteTab1Plan_1;
|
|
1
2
|
import { __decorate, __metadata } from "tslib";
|
|
2
3
|
import { css, html, LitElement } from 'lit';
|
|
3
4
|
import { customElement, property, state } from 'lit/decorators.js';
|
|
4
5
|
import { calcDiff, calcDateDiff } from '../../shared/func';
|
|
5
6
|
import { getKpiMetricValues, getKpiMetrics, updateProjectCompleteStep1 } from '../../shared/complete-api';
|
|
7
|
+
import { collectFromSource, INTEGRATION_SOURCES } from '../../shared/integration-fetch';
|
|
6
8
|
import moment from 'moment-timezone';
|
|
7
9
|
import { notify } from '@operato/layout';
|
|
8
10
|
const KPI_METRIC_KEY_MAPPING = [
|
|
@@ -28,12 +30,25 @@ const EXCLUDE_FIELDS = [
|
|
|
28
30
|
{ id: '036d6e1c-193e-46bd-8d6e-93f248af8124', name: '계획공사기간' },
|
|
29
31
|
{ id: '5df77618-db44-4da3-a22d-43c067cb5e86', name: '실제공사기간' }
|
|
30
32
|
];
|
|
31
|
-
let SvProjectCompleteTab1Plan = class SvProjectCompleteTab1Plan extends LitElement {
|
|
33
|
+
let SvProjectCompleteTab1Plan = SvProjectCompleteTab1Plan_1 = class SvProjectCompleteTab1Plan extends LitElement {
|
|
32
34
|
constructor() {
|
|
33
35
|
super(...arguments);
|
|
34
36
|
this.kpiMetricValues = [];
|
|
35
37
|
this.kpiMetrics = [];
|
|
36
38
|
this.project = {};
|
|
39
|
+
/** 연동 진행 상태: source → 'idle' | 'collecting' | 'done' */
|
|
40
|
+
this.collectStatus = {};
|
|
41
|
+
/** 자동 수집한 실제값의 출처: metricId → 시스템 라벨 */
|
|
42
|
+
this.valueSources = {};
|
|
43
|
+
/** 자동 수집한 계획값: projectKey → 값 (키스콘이 제공하는 계획공사기간/계획공사비) */
|
|
44
|
+
this.collectedPlan = {};
|
|
45
|
+
/** 계획값 출처: projectKey → 시스템 라벨 */
|
|
46
|
+
this.planSources = {};
|
|
47
|
+
/** 시스템별 수집 요약 (카드 표시용): 라벨 → [{label, text}] */
|
|
48
|
+
this.collectedSummary = {};
|
|
49
|
+
/** 방금 채워진 셀 강조용 (metricId 또는 plan:projectKey) */
|
|
50
|
+
this.justFilled = {};
|
|
51
|
+
this.collecting = false;
|
|
37
52
|
}
|
|
38
53
|
render() {
|
|
39
54
|
return html `
|
|
@@ -45,6 +60,8 @@ let SvProjectCompleteTab1Plan = class SvProjectCompleteTab1Plan extends LitEleme
|
|
|
45
60
|
</div>
|
|
46
61
|
</div>
|
|
47
62
|
|
|
63
|
+
${this._renderCollectPanel()}
|
|
64
|
+
|
|
48
65
|
<div class="rows">
|
|
49
66
|
<div class="row header">
|
|
50
67
|
<div class="header-label">기본정보</div>
|
|
@@ -54,7 +71,7 @@ let SvProjectCompleteTab1Plan = class SvProjectCompleteTab1Plan extends LitEleme
|
|
|
54
71
|
</div>
|
|
55
72
|
|
|
56
73
|
${this.kpiMetrics.map(metric => {
|
|
57
|
-
var _a, _b, _c, _d;
|
|
74
|
+
var _a, _b, _c, _d, _e;
|
|
58
75
|
// 제외 필드는 표시하지 않음
|
|
59
76
|
if (EXCLUDE_FIELDS.some(item => item.id === metric.id))
|
|
60
77
|
return null;
|
|
@@ -79,14 +96,32 @@ let SvProjectCompleteTab1Plan = class SvProjectCompleteTab1Plan extends LitEleme
|
|
|
79
96
|
const diffValue = calcDiff(planValue, actualValue);
|
|
80
97
|
const diffClass = diffValue === 0 ? '' : diffValue > 0 ? 'plus' : 'minus';
|
|
81
98
|
const diffSign = diffValue === 0 ? '' : diffValue > 0 ? '+' : '-';
|
|
99
|
+
const src = this.valueSources[metric.id];
|
|
100
|
+
// 계획값: 키스콘 수집값이 있으면 그것을, 없으면 계산된 planValue
|
|
101
|
+
const planSrc = this.planSources[projectKey];
|
|
102
|
+
const shownPlan = (_e = this.collectedPlan[projectKey]) !== null && _e !== void 0 ? _e : planValue;
|
|
103
|
+
const planFilled = this.justFilled[`plan:${projectKey}`];
|
|
104
|
+
// 계획값이 키스콘에서 들어왔으면 편차도 그 기준으로 재계산
|
|
105
|
+
const effDiff = calcDiff(shownPlan, actualValue);
|
|
106
|
+
const effDiffClass = effDiff === 0 ? '' : effDiff > 0 ? 'plus' : 'minus';
|
|
107
|
+
const effDiffSign = effDiff === 0 ? '' : effDiff > 0 ? '+' : '-';
|
|
82
108
|
return html `<div class="row">
|
|
83
109
|
<div class="label">• ${metric.name}</div>
|
|
84
|
-
<div class="cell
|
|
85
|
-
|
|
110
|
+
<div class="cell ${planFilled ? 'just-filled' : ''}">
|
|
111
|
+
${isDisplayInput
|
|
112
|
+
? html `<input .value=${shownPlan} disabled /> ${unit}
|
|
113
|
+
${planSrc ? html `<span class="src-chip">🔗 ${planSrc}</span>` : ''}`
|
|
114
|
+
: html `-`}
|
|
115
|
+
</div>
|
|
116
|
+
<div class="cell ${this.justFilled[metric.id] ? 'just-filled' : ''}">
|
|
86
117
|
<input numeric .value=${actualValue !== null && actualValue !== void 0 ? actualValue : 0} @input=${(e) => this._onInputChange(e, metric)} />
|
|
87
118
|
${unit}
|
|
119
|
+
${src ? html `<span class="src-chip">🔗 ${src}</span>` : ''}
|
|
120
|
+
</div>
|
|
121
|
+
<div class="unit ${isDisplayInput ? effDiffClass : diffClass}">
|
|
122
|
+
${isDisplayInput ? effDiffSign : diffSign}
|
|
123
|
+
${Math.abs(isDisplayInput ? effDiff : diffValue).toLocaleString()} ${unit}
|
|
88
124
|
</div>
|
|
89
|
-
<div class="unit ${diffClass}">${diffSign} ${Math.abs(diffValue).toLocaleString()} ${unit}</div>
|
|
90
125
|
</div>`;
|
|
91
126
|
})}
|
|
92
127
|
|
|
@@ -97,6 +132,128 @@ let SvProjectCompleteTab1Plan = class SvProjectCompleteTab1Plan extends LitEleme
|
|
|
97
132
|
</div>
|
|
98
133
|
`;
|
|
99
134
|
}
|
|
135
|
+
_renderCollectPanel() {
|
|
136
|
+
return html `
|
|
137
|
+
<div class="collect-panel">
|
|
138
|
+
<div class="collect-head">
|
|
139
|
+
<div class="ttl">시스템 연동 자동 수집 <small>외부 시스템에서 기본정보를 가져옵니다</small></div>
|
|
140
|
+
<div
|
|
141
|
+
class="collect-btn"
|
|
142
|
+
?disabled=${this.collecting}
|
|
143
|
+
@click=${() => (this.collecting ? null : this._autoCollect())}
|
|
144
|
+
>
|
|
145
|
+
${this.collecting ? '수집 중…' : '⟳ 자동 수집'}
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
<div class="source-list">
|
|
149
|
+
${INTEGRATION_SOURCES.map(meta => {
|
|
150
|
+
const status = this.collectStatus[meta.source] || 'idle';
|
|
151
|
+
const collected = Object.entries(this.valueSources).filter(([, label]) => label === meta.label);
|
|
152
|
+
return html `
|
|
153
|
+
<div class="source-card ${status}">
|
|
154
|
+
<div class="sc-head">
|
|
155
|
+
<span class="sys-icon">${meta.icon}</span>
|
|
156
|
+
<span>${meta.label}</span>
|
|
157
|
+
<span class="sc-status ${status}">
|
|
158
|
+
${status === 'collecting' ? '연동 중…' : status === 'done' ? '완료 ✓' : '대기'}
|
|
159
|
+
</span>
|
|
160
|
+
</div>
|
|
161
|
+
<div class="sc-desc">${meta.desc}</div>
|
|
162
|
+
${status === 'done' && collected.length
|
|
163
|
+
? html `<div class="sc-fields">
|
|
164
|
+
${this._collectedFieldsOf(meta.label).map(f => html `<div class="sc-field"><span>${f.label}</span><b>${f.text}</b></div>`)}
|
|
165
|
+
</div>`
|
|
166
|
+
: ''}
|
|
167
|
+
</div>
|
|
168
|
+
`;
|
|
169
|
+
})}
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
`;
|
|
173
|
+
}
|
|
174
|
+
/** 패널 카드에 표시할, 해당 시스템에서 수집한 값 요약 */
|
|
175
|
+
_collectedFieldsOf(sourceLabel) {
|
|
176
|
+
return this.collectedSummary[sourceLabel] || [];
|
|
177
|
+
}
|
|
178
|
+
/** 자동 수집 — 시스템별로 차례로 연동하여 값을 폼에 채운다.
|
|
179
|
+
* 키스콘이 주는 계획공사기간/계획공사비는 "계획" 칼럼으로,
|
|
180
|
+
* 세움터/올바로 등 나머지는 "실제" 칼럼으로 채운다. */
|
|
181
|
+
async _autoCollect() {
|
|
182
|
+
var _a;
|
|
183
|
+
if (!((_a = this.project) === null || _a === void 0 ? void 0 : _a.id)) {
|
|
184
|
+
notify({ message: '프로젝트 정보가 없습니다.' });
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
this.collecting = true;
|
|
188
|
+
this.valueSources = {};
|
|
189
|
+
this.collectedPlan = {};
|
|
190
|
+
this.planSources = {};
|
|
191
|
+
this.collectedSummary = {};
|
|
192
|
+
this.collectStatus = {};
|
|
193
|
+
for (const meta of INTEGRATION_SOURCES) {
|
|
194
|
+
this.collectStatus = Object.assign(Object.assign({}, this.collectStatus), { [meta.source]: 'collecting' });
|
|
195
|
+
this.requestUpdate();
|
|
196
|
+
try {
|
|
197
|
+
const result = await collectFromSource(meta.source, this.project);
|
|
198
|
+
const summary = [];
|
|
199
|
+
for (const field of result.fields) {
|
|
200
|
+
const unit = field.unit || '';
|
|
201
|
+
summary.push({
|
|
202
|
+
label: field.label,
|
|
203
|
+
text: `${Number(field.value).toLocaleString()} ${unit}`.trim()
|
|
204
|
+
});
|
|
205
|
+
if (SvProjectCompleteTab1Plan_1.PLAN_KEYS.includes(field.projectKey)) {
|
|
206
|
+
// 계획값 — 계획 칼럼에 반영
|
|
207
|
+
this.collectedPlan = Object.assign(Object.assign({}, this.collectedPlan), { [field.projectKey]: Number(field.value) });
|
|
208
|
+
this.planSources = Object.assign(Object.assign({}, this.planSources), { [field.projectKey]: result.label });
|
|
209
|
+
this.justFilled = Object.assign(Object.assign({}, this.justFilled), { [`plan:${field.projectKey}`]: true });
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
// 실제값 — 실제 칼럼(메트릭)에 반영
|
|
213
|
+
const metric = this.kpiMetrics.find((m) => { var _a; return ((_a = KPI_METRIC_KEY_MAPPING.find(x => x.label === m.name)) === null || _a === void 0 ? void 0 : _a.projectKey) === field.projectKey; });
|
|
214
|
+
if (!metric)
|
|
215
|
+
continue;
|
|
216
|
+
this._setMetricValue(metric, field.value);
|
|
217
|
+
this.valueSources = Object.assign(Object.assign({}, this.valueSources), { [metric.id]: result.label });
|
|
218
|
+
this.justFilled = Object.assign(Object.assign({}, this.justFilled), { [metric.id]: true });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
this.collectedSummary = Object.assign(Object.assign({}, this.collectedSummary), { [meta.label]: summary });
|
|
222
|
+
}
|
|
223
|
+
catch (e) {
|
|
224
|
+
console.error(`[자동수집] ${meta.label} 실패`, e);
|
|
225
|
+
notify({ message: `${meta.label} 연동 실패` });
|
|
226
|
+
}
|
|
227
|
+
this.collectStatus = Object.assign(Object.assign({}, this.collectStatus), { [meta.source]: 'done' });
|
|
228
|
+
this.requestUpdate();
|
|
229
|
+
}
|
|
230
|
+
this.collecting = false;
|
|
231
|
+
notify({ message: '외부 시스템 연동 수집이 완료되었습니다. 검토 후 [저장]을 눌러주세요.' });
|
|
232
|
+
setTimeout(() => {
|
|
233
|
+
this.justFilled = {};
|
|
234
|
+
}, 1300);
|
|
235
|
+
}
|
|
236
|
+
/** 메트릭의 실제값 셀을 set/add (사용자 입력과 동일 경로) */
|
|
237
|
+
_setMetricValue(metric, value) {
|
|
238
|
+
const idx = this.kpiMetricValues.findIndex((item) => item.metricId === metric.id);
|
|
239
|
+
if (idx !== -1) {
|
|
240
|
+
this.kpiMetricValues = this.kpiMetricValues.map((item) => item.metricId === metric.id ? Object.assign(Object.assign({}, item), { value }) : item);
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
this.kpiMetricValues = [
|
|
244
|
+
...this.kpiMetricValues,
|
|
245
|
+
{
|
|
246
|
+
id: crypto.randomUUID(),
|
|
247
|
+
value,
|
|
248
|
+
metricId: metric.id,
|
|
249
|
+
unit: metric.unit || '',
|
|
250
|
+
org: this.project.id,
|
|
251
|
+
periodType: metric.periodType,
|
|
252
|
+
valueDate: moment().tz('Asia/Seoul').format('YYYY-MM-DD')
|
|
253
|
+
}
|
|
254
|
+
];
|
|
255
|
+
}
|
|
256
|
+
}
|
|
100
257
|
willUpdate(changedProperties) {
|
|
101
258
|
var _a;
|
|
102
259
|
super.willUpdate(changedProperties);
|
|
@@ -155,20 +312,23 @@ let SvProjectCompleteTab1Plan = class SvProjectCompleteTab1Plan extends LitEleme
|
|
|
155
312
|
? ((_b = this.kpiMetricValues.find((item) => item.metricId === constructionPeriodMetric.id)) === null || _b === void 0 ? void 0 : _b.value) || 0
|
|
156
313
|
: 0;
|
|
157
314
|
EXCLUDE_FIELDS.forEach(excludeField => {
|
|
158
|
-
var _a, _b;
|
|
315
|
+
var _a, _b, _c, _d, _e;
|
|
159
316
|
// 해당 excludeField.id에 매칭되는 메트릭 찾기
|
|
160
317
|
const metric = this.kpiMetrics.find(m => m.id === excludeField.id);
|
|
161
318
|
// 기존에 저장된 데이터가 있는지 확인
|
|
162
319
|
const existingValue = this.kpiMetricValues.find((item) => item.metricId === excludeField.id);
|
|
163
320
|
let value = 0;
|
|
164
321
|
if (excludeField.name == '계획공사비') {
|
|
165
|
-
|
|
322
|
+
// 키스콘 수집 계획값 우선, 없으면 buildingComplex
|
|
323
|
+
value = (_d = (_a = this.collectedPlan['constructionCost']) !== null && _a !== void 0 ? _a : (_c = (_b = this.project) === null || _b === void 0 ? void 0 : _b.buildingComplex) === null || _c === void 0 ? void 0 : _c.constructionCost) !== null && _d !== void 0 ? _d : 0;
|
|
166
324
|
}
|
|
167
325
|
else if (excludeField.name == '실제공사비') {
|
|
168
326
|
value = actualConstructionCostValue;
|
|
169
327
|
}
|
|
170
328
|
else if (excludeField.name == '계획공사기간') {
|
|
171
|
-
|
|
329
|
+
// 키스콘 수집 계획값 우선, 없으면 착공~준공 일자 계산
|
|
330
|
+
value =
|
|
331
|
+
(_e = this.collectedPlan['constructionPeriod']) !== null && _e !== void 0 ? _e : Math.ceil(calcDateDiff(this.project.startDate, this.project.endDate) / 30);
|
|
172
332
|
}
|
|
173
333
|
else if (excludeField.name == '실제공사기간') {
|
|
174
334
|
value = actualConstructionPeriodValue;
|
|
@@ -318,8 +478,143 @@ SvProjectCompleteTab1Plan.styles = [
|
|
|
318
478
|
.ghost-btn.secondary {
|
|
319
479
|
background: #24be7b;
|
|
320
480
|
}
|
|
481
|
+
|
|
482
|
+
/* ── 자동 수집 패널 ── */
|
|
483
|
+
.collect-panel {
|
|
484
|
+
margin: 4px 6px 14px;
|
|
485
|
+
border: 1px solid #d7e3f2;
|
|
486
|
+
border-radius: 8px;
|
|
487
|
+
background: linear-gradient(180deg, #f7fafe 0%, #ffffff 100%);
|
|
488
|
+
overflow: hidden;
|
|
489
|
+
}
|
|
490
|
+
.collect-head {
|
|
491
|
+
display: flex;
|
|
492
|
+
align-items: center;
|
|
493
|
+
justify-content: space-between;
|
|
494
|
+
padding: 10px 16px;
|
|
495
|
+
background: #eaf2fb;
|
|
496
|
+
border-bottom: 1px solid #d7e3f2;
|
|
497
|
+
}
|
|
498
|
+
.collect-head .ttl {
|
|
499
|
+
color: #0c4da2;
|
|
500
|
+
font-weight: 700;
|
|
501
|
+
font-size: 14px;
|
|
502
|
+
letter-spacing: -0.03em;
|
|
503
|
+
}
|
|
504
|
+
.collect-head .ttl small {
|
|
505
|
+
color: #5a7da6;
|
|
506
|
+
font-weight: 400;
|
|
507
|
+
margin-left: 8px;
|
|
508
|
+
}
|
|
509
|
+
.collect-btn {
|
|
510
|
+
display: inline-flex;
|
|
511
|
+
align-items: center;
|
|
512
|
+
gap: 6px;
|
|
513
|
+
padding: 7px 14px;
|
|
514
|
+
background: #0c4da2;
|
|
515
|
+
color: #fff;
|
|
516
|
+
border-radius: 6px;
|
|
517
|
+
cursor: pointer;
|
|
518
|
+
font-size: 13px;
|
|
519
|
+
font-weight: 600;
|
|
520
|
+
}
|
|
521
|
+
.collect-btn[disabled] {
|
|
522
|
+
background: #9bb4d2;
|
|
523
|
+
cursor: default;
|
|
524
|
+
}
|
|
525
|
+
.source-list {
|
|
526
|
+
display: flex;
|
|
527
|
+
gap: 10px;
|
|
528
|
+
padding: 12px 16px;
|
|
529
|
+
flex-wrap: wrap;
|
|
530
|
+
}
|
|
531
|
+
.source-card {
|
|
532
|
+
flex: 1 1 200px;
|
|
533
|
+
border: 1px solid #e3e9f0;
|
|
534
|
+
border-radius: 7px;
|
|
535
|
+
padding: 10px 12px;
|
|
536
|
+
background: #fff;
|
|
537
|
+
transition: border-color 0.2s, box-shadow 0.2s;
|
|
538
|
+
}
|
|
539
|
+
.source-card.collecting {
|
|
540
|
+
border-color: #2e79be;
|
|
541
|
+
box-shadow: 0 0 0 3px rgba(46, 121, 190, 0.12);
|
|
542
|
+
}
|
|
543
|
+
.source-card.done {
|
|
544
|
+
border-color: #24be7b;
|
|
545
|
+
}
|
|
546
|
+
.source-card .sc-head {
|
|
547
|
+
display: flex;
|
|
548
|
+
align-items: center;
|
|
549
|
+
gap: 8px;
|
|
550
|
+
font-weight: 600;
|
|
551
|
+
color: #212529;
|
|
552
|
+
font-size: 13px;
|
|
553
|
+
}
|
|
554
|
+
.source-card .sc-head .sys-icon {
|
|
555
|
+
font-size: 16px;
|
|
556
|
+
}
|
|
557
|
+
.source-card .sc-status {
|
|
558
|
+
margin-left: auto;
|
|
559
|
+
font-size: 12px;
|
|
560
|
+
}
|
|
561
|
+
.sc-status.idle {
|
|
562
|
+
color: #aab4c0;
|
|
563
|
+
}
|
|
564
|
+
.sc-status.collecting {
|
|
565
|
+
color: #2e79be;
|
|
566
|
+
}
|
|
567
|
+
.sc-status.done {
|
|
568
|
+
color: #24be7b;
|
|
569
|
+
font-weight: 700;
|
|
570
|
+
}
|
|
571
|
+
.source-card .sc-fields {
|
|
572
|
+
margin-top: 8px;
|
|
573
|
+
display: flex;
|
|
574
|
+
flex-direction: column;
|
|
575
|
+
gap: 3px;
|
|
576
|
+
}
|
|
577
|
+
.sc-field {
|
|
578
|
+
font-size: 12px;
|
|
579
|
+
color: #5a6b7b;
|
|
580
|
+
display: flex;
|
|
581
|
+
justify-content: space-between;
|
|
582
|
+
}
|
|
583
|
+
.sc-field b {
|
|
584
|
+
color: #0c4da2;
|
|
585
|
+
}
|
|
586
|
+
.sc-desc {
|
|
587
|
+
font-size: 11px;
|
|
588
|
+
color: #9aa7b4;
|
|
589
|
+
margin-top: 2px;
|
|
590
|
+
}
|
|
591
|
+
/* 가져온 값 출처 칩 */
|
|
592
|
+
.src-chip {
|
|
593
|
+
display: inline-flex;
|
|
594
|
+
align-items: center;
|
|
595
|
+
margin-left: 6px;
|
|
596
|
+
padding: 1px 7px;
|
|
597
|
+
font-size: 11px;
|
|
598
|
+
border-radius: 10px;
|
|
599
|
+
background: #e7f3ec;
|
|
600
|
+
color: #1d9c63;
|
|
601
|
+
white-space: nowrap;
|
|
602
|
+
}
|
|
603
|
+
.cell.just-filled input {
|
|
604
|
+
animation: flash 1.1s ease;
|
|
605
|
+
}
|
|
606
|
+
@keyframes flash {
|
|
607
|
+
0% {
|
|
608
|
+
background: #fff7cc;
|
|
609
|
+
}
|
|
610
|
+
100% {
|
|
611
|
+
background: #ffffff;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
321
614
|
`
|
|
322
615
|
];
|
|
616
|
+
/** 계획 칼럼을 갖는 항목 (키스콘이 계획값을 제공) */
|
|
617
|
+
SvProjectCompleteTab1Plan.PLAN_KEYS = ['constructionPeriod', 'constructionCost'];
|
|
323
618
|
__decorate([
|
|
324
619
|
state(),
|
|
325
620
|
__metadata("design:type", Object)
|
|
@@ -332,7 +627,35 @@ __decorate([
|
|
|
332
627
|
property({ type: Object }),
|
|
333
628
|
__metadata("design:type", Object)
|
|
334
629
|
], SvProjectCompleteTab1Plan.prototype, "project", void 0);
|
|
335
|
-
|
|
630
|
+
__decorate([
|
|
631
|
+
state(),
|
|
632
|
+
__metadata("design:type", Object)
|
|
633
|
+
], SvProjectCompleteTab1Plan.prototype, "collectStatus", void 0);
|
|
634
|
+
__decorate([
|
|
635
|
+
state(),
|
|
636
|
+
__metadata("design:type", Object)
|
|
637
|
+
], SvProjectCompleteTab1Plan.prototype, "valueSources", void 0);
|
|
638
|
+
__decorate([
|
|
639
|
+
state(),
|
|
640
|
+
__metadata("design:type", Object)
|
|
641
|
+
], SvProjectCompleteTab1Plan.prototype, "collectedPlan", void 0);
|
|
642
|
+
__decorate([
|
|
643
|
+
state(),
|
|
644
|
+
__metadata("design:type", Object)
|
|
645
|
+
], SvProjectCompleteTab1Plan.prototype, "planSources", void 0);
|
|
646
|
+
__decorate([
|
|
647
|
+
state(),
|
|
648
|
+
__metadata("design:type", Object)
|
|
649
|
+
], SvProjectCompleteTab1Plan.prototype, "collectedSummary", void 0);
|
|
650
|
+
__decorate([
|
|
651
|
+
state(),
|
|
652
|
+
__metadata("design:type", Object)
|
|
653
|
+
], SvProjectCompleteTab1Plan.prototype, "justFilled", void 0);
|
|
654
|
+
__decorate([
|
|
655
|
+
state(),
|
|
656
|
+
__metadata("design:type", Object)
|
|
657
|
+
], SvProjectCompleteTab1Plan.prototype, "collecting", void 0);
|
|
658
|
+
SvProjectCompleteTab1Plan = SvProjectCompleteTab1Plan_1 = __decorate([
|
|
336
659
|
customElement('sv-pc-tab1-plan')
|
|
337
660
|
], SvProjectCompleteTab1Plan);
|
|
338
661
|
export { SvProjectCompleteTab1Plan };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pc-tab1-plan.js","sourceRoot":"","sources":["../../../client/pages/project-complete-tabs/pc-tab1-plan.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { calcDiff, calcDateDiff } from '../../shared/func'\nimport { getKpiMetricValues, getKpiMetrics, updateProjectCompleteStep1 } from '../../shared/complete-api'\nimport moment from 'moment-timezone'\nimport { notify } from '@operato/layout'\n\nconst KPI_METRIC_KEY_MAPPING = [\n { label: '공사기간', projectKey: 'constructionPeriod' },\n { label: '총 근로자수', projectKey: 'totalWorkerCount' },\n { label: '연간 근로자 수', projectKey: 'annualWorkerCount' },\n { label: '투입인력', projectKey: 'workerCount' },\n { label: '재해 건수', projectKey: 'accidentCount' },\n { label: '일정 이탈 수준', projectKey: 'scheduleDeviationLevel' },\n { label: '총 건설 폐기물 발생량', projectKey: 'totalConstructionWasteAmount' },\n { label: '용적율', projectKey: 'floorAreaRatio' },\n { label: '총 설계 변경 건', projectKey: 'designChangeCount' },\n { label: '공사비', projectKey: 'constructionCost' },\n { label: '연면적', projectKey: 'area' },\n { label: '지상층수', projectKey: 'upperFloorCount' },\n { label: '지하층수', projectKey: 'lowerFloorCount' }\n]\n// 계획값이 없는 것들 (실제값으로 표시할 필드)\nconst NO_PLAN_FIELDS = ['workerCount', 'area', 'floorAreaRatio', 'designChangeCount', 'upperFloorCount', 'lowerFloorCount']\n\nconst EXCLUDE_FIELDS = [\n { id: 'b7a583c0-9de9-4c12-af49-dde65198dfce', name: '계획공사비' },\n { id: '9767747a-d2ec-4e36-96c4-4a78bba35b98', name: '실제공사비' },\n { id: '036d6e1c-193e-46bd-8d6e-93f248af8124', name: '계획공사기간' },\n { id: '5df77618-db44-4da3-a22d-43c067cb5e86', name: '실제공사기간' }\n]\n\n@customElement('sv-pc-tab1-plan')\nexport class SvProjectCompleteTab1Plan 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 padding: 8px 6px;\n }\n .row.header {\n min-height: 35px;\n background: #f3f3fa;\n border-top: 2px #0c4da2 solid;\n grid-template-columns: 220px 1fr 1fr 200px;\n padding: 0px 25px;\n\n .header-label {\n color: #212529;\n text-align: center;\n }\n }\n .row {\n display: grid;\n grid-template-columns: 220px 1fr 1fr 200px;\n gap: 6px 10px;\n align-items: center;\n padding: 8px 25px;\n border-bottom: 1px rgba(0, 0, 0, 0.1) solid;\n\n .cell {\n display: flex;\n align-items: center;\n justify-content: center;\n }\n }\n .label {\n color: #35618e;\n font-size: 16px;\n letter-spacing: -0.05em;\n white-space: nowrap;\n display: flex;\n justify-content: center;\n }\n input {\n padding: 6px 8px;\n border: 1px solid rgba(0, 0, 0, 0.1);\n border-radius: 5px;\n background: #ffffff;\n color: #212529;\n font-size: 16px;\n margin-right: 5px;\n\n &:disabled {\n background: #f6f6f6;\n }\n }\n .unit {\n text-align: center;\n color: #212529;\n }\n .plus {\n color: #e13232;\n font-weight: 700;\n }\n .minus {\n color: #1e88e5;\n font-weight: 700;\n }\n .button-line {\n display: flex;\n justify-content: center;\n gap: 10px;\n margin-top: 16px;\n }\n .ghost-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 10px;\n background: #35618e;\n color: #ffffff;\n border-radius: 5px;\n cursor: pointer;\n }\n .ghost-btn.secondary {\n background: #24be7b;\n }\n `\n ]\n\n @state() kpiMetricValues: any = []\n @state() kpiMetrics: any = []\n @property({ type: Object }) project: any = {}\n\n render() {\n return html`\n <div class=\"title\">\n <div>\n 당초 계획 대비 실제 진행 과정에서 변동된 공사비, 공기(공사기간), 면적, 기타 주요 항목을 현실에 맞게 수정·입력합니다.\n <br />이 정보는 성과 분석, KPI 평가, 통계 산출 등에 기준값으로 사용되므로, 가능한 한 실제 값 기준으로 정확히\n 입력해주시기 바랍니다.\n </div>\n </div>\n\n <div class=\"rows\">\n <div class=\"row header\">\n <div class=\"header-label\">기본정보</div>\n <div class=\"header-label\">계획</div>\n <div class=\"header-label\">실제</div>\n <div class=\"header-label\">편차</div>\n </div>\n\n ${this.kpiMetrics.map(metric => {\n // 제외 필드는 표시하지 않음\n if (EXCLUDE_FIELDS.some(item => item.id === metric.id)) return null\n\n // 상수로 정의된 매핑 정보에서 metric.name으로 projectKey를 찾음\n const projectKey = KPI_METRIC_KEY_MAPPING.find(item => item.label === metric.name)?.projectKey || ''\n const kpiMetricValue = this.kpiMetricValues.find((item: any) => item.metricId === metric.id) || {}\n const isDisplayInput = projectKey === 'constructionPeriod' || projectKey === 'constructionCost' // 유효한 계획 필드\n\n // planValue는 project에서 찾고 없으면 buildingComplex의 값에서 찾음\n const basePlanValue = this.project[projectKey] || this.project?.buildingComplex?.[projectKey] || 0\n let planValue = basePlanValue\n const unit = kpiMetricValue.unit || metric.unit || ''\n\n // 2. 계획 값 처리\n // 공사기간은 기간을 일 단위로 계산\n if (projectKey === 'constructionPeriod') {\n planValue = Math.ceil(calcDateDiff(this.project.startDate, this.project.endDate) / 30)\n } else if (!isDisplayInput) {\n planValue = 0\n }\n\n // 1. 실제 값 처리 kpi값을 찾고, 없으면 (NO_PLAN_FIELDS에 해당하는 필드는 원래 계획값 사용)\n const actualValue = kpiMetricValue.value ?? (NO_PLAN_FIELDS.includes(projectKey) ? basePlanValue : 0)\n\n const diffValue = calcDiff(planValue, actualValue)\n const diffClass = diffValue === 0 ? '' : diffValue > 0 ? 'plus' : 'minus'\n const diffSign = diffValue === 0 ? '' : diffValue > 0 ? '+' : '-'\n\n return html`<div class=\"row\">\n <div class=\"label\">• ${metric.name}</div>\n <div class=\"cell\">${isDisplayInput ? html`<input .value=${planValue} disabled /> ${unit}` : html`-`}</div>\n <div class=\"cell\">\n <input numeric .value=${actualValue ?? 0} @input=${(e: InputEvent) => this._onInputChange(e, metric)} />\n ${unit}\n </div>\n <div class=\"unit ${diffClass}\">${diffSign} ${Math.abs(diffValue).toLocaleString()} ${unit}</div>\n </div>`\n })}\n\n <div class=\"button-line\">\n <div class=\"ghost-btn\" @click=${this._reset}>초기화</div>\n <div class=\"ghost-btn secondary\" @click=${() => this._save()}>저장</div>\n </div>\n </div>\n `\n }\n\n willUpdate(changedProperties: Map<string, any>) {\n super.willUpdate(changedProperties)\n\n // project가 변경되고, project.id가 존재하면 데이터 로드\n if (changedProperties.has('project') && this.project?.id) {\n this._getInitData()\n }\n }\n\n private async _getInitData() {\n const kpiMetrics = await getKpiMetrics()\n this.kpiMetrics = kpiMetrics.filter(item => !item.name.includes('평가')) || [] // 평가 텍스트가 들어간건 제외\n this.kpiMetricValues = await getKpiMetricValues(this.project.id)\n }\n\n // Input 요소의 값이 변경될 때 호출되는 콜백 함수\n private _onInputChange(event: InputEvent, metric: any) {\n const target = event.target as HTMLInputElement\n let inputVal: any = target.value\n\n // 숫자 타입은 다른 문자 입력 제거\n if (target.hasAttribute('numeric')) {\n inputVal = Number(inputVal.replace(/[^\\d.]/g, ''))\n }\n\n // 기존 배열에서 해당 id를 가진 항목을 찾음\n const existingItemIndex = this.kpiMetricValues.findIndex((item: any) => item.metricId === metric.id)\n\n if (existingItemIndex !== -1) {\n // 기존 항목이 있으면 업데이트\n this.kpiMetricValues = this.kpiMetricValues.map((item: any) =>\n item.metricId === metric.id ? { ...item, value: inputVal } : item\n )\n } else {\n // 기존 항목이 없으면 새로 추가\n this.kpiMetricValues = [\n ...this.kpiMetricValues,\n {\n id: crypto.randomUUID(),\n value: inputVal,\n metricId: metric.id,\n unit: metric.unit || '',\n org: this.project.id, // 프로젝트 ID 추가\n periodType: metric.periodType,\n valueDate: moment().tz('Asia/Seoul').format('YYYY-MM-DD')\n }\n ]\n }\n }\n\n private _generateExcludeFieldsData() {\n const excludeData: any[] = []\n\n const constructionCostMetric = this.kpiMetrics.find(\n metric => KPI_METRIC_KEY_MAPPING.find(item => item.label === metric.name)?.projectKey === 'constructionCost'\n )\n\n // 공사기간 관련 메트릭을 찾음\n const constructionPeriodMetric = this.kpiMetrics.find(\n metric => KPI_METRIC_KEY_MAPPING.find(item => item.label === metric.name)?.projectKey === 'constructionPeriod'\n )\n\n // 실적공사비 값 (사용자가 입력한 constructionCost 값)\n const actualConstructionCostValue = constructionCostMetric\n ? this.kpiMetricValues.find((item: any) => item.metricId === constructionCostMetric.id)?.value || 0\n : 0\n\n // 실제공사기간 값 (사용자가 입력한 constructionPeriod 값)\n const actualConstructionPeriodValue = constructionPeriodMetric\n ? this.kpiMetricValues.find((item: any) => item.metricId === constructionPeriodMetric.id)?.value || 0\n : 0\n\n EXCLUDE_FIELDS.forEach(excludeField => {\n // 해당 excludeField.id에 매칭되는 메트릭 찾기\n const metric = this.kpiMetrics.find(m => m.id === excludeField.id)\n\n // 기존에 저장된 데이터가 있는지 확인\n const existingValue = this.kpiMetricValues.find((item: any) => item.metricId === excludeField.id)\n\n let value = 0\n if (excludeField.name == '계획공사비') {\n value = this.project?.buildingComplex?.constructionCost || 0\n } else if (excludeField.name == '실제공사비') {\n value = actualConstructionCostValue\n } else if (excludeField.name == '계획공사기간') {\n value = Math.ceil(calcDateDiff(this.project.startDate, this.project.endDate) / 30)\n } else if (excludeField.name == '실제공사기간') {\n value = actualConstructionPeriodValue\n }\n\n excludeData.push({\n id: existingValue?.id || crypto.randomUUID(), // 기존 데이터가 있으면 해당 id 사용\n value,\n metricId: excludeField.id,\n unit: metric?.unit || '',\n org: this.project.id,\n periodType: metric?.periodType,\n valueDate: moment().tz('Asia/Seoul').format('YYYY-MM-DD')\n })\n })\n\n return excludeData\n }\n\n private async _save() {\n // EXCLUDE_FIELDS에 대한 값들을 자동으로 생성\n const excludeData = this._generateExcludeFieldsData()\n\n // EXCLUDE_FIELDS에 해당하는 metricId를 추출\n const excludeMetricIds = EXCLUDE_FIELDS.map(field => field.id)\n // kpiMetricValues에서 EXCLUDE_FIELDS에 해당하는 중복 항목들을 제외\n const filteredKpiMetricValues = this.kpiMetricValues.filter((item: any) => !excludeMetricIds.includes(item.metricId))\n\n // 기본 실제값 엔트리 생성 (사용자 입력이 없는 메트릭에 대해 기본값 채움)\n const existingMetricIds = new Set(filteredKpiMetricValues.map((item: any) => item.metricId))\n const defaultActualData = this.kpiMetrics\n .filter(metric => !EXCLUDE_FIELDS.some(field => field.id === metric.id) && !existingMetricIds.has(metric.id))\n .map(metric => {\n const projectKey = KPI_METRIC_KEY_MAPPING.find(item => item.label === metric.name)?.projectKey || ''\n const basePlanValue = this.project[projectKey] || this.project?.buildingComplex?.[projectKey] || 0\n const value = NO_PLAN_FIELDS.includes(projectKey) ? basePlanValue : 0\n\n return {\n id: crypto.randomUUID(),\n value,\n metricId: metric.id,\n unit: metric.unit || '',\n org: this.project.id,\n periodType: metric.periodType,\n valueDate: moment().tz('Asia/Seoul').format('YYYY-MM-DD')\n }\n })\n\n // 필터링된 데이터와 exclude 데이터, 기본 실제값 데이터를 합침\n const allKpiMetricValues = [...excludeData, ...filteredKpiMetricValues, ...defaultActualData]\n const response = await updateProjectCompleteStep1(allKpiMetricValues)\n if (!response.errors) {\n notify({ message: '저장되었습니다.' })\n }\n }\n\n private _reset() {\n this._getInitData()\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"pc-tab1-plan.js","sourceRoot":"","sources":["../../../client/pages/project-complete-tabs/pc-tab1-plan.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { calcDiff, calcDateDiff } from '../../shared/func'\nimport { getKpiMetricValues, getKpiMetrics, updateProjectCompleteStep1 } from '../../shared/complete-api'\nimport { collectFromSource, INTEGRATION_SOURCES, type IntegrationSource } from '../../shared/integration-fetch'\nimport moment from 'moment-timezone'\nimport { notify } from '@operato/layout'\n\nconst KPI_METRIC_KEY_MAPPING = [\n { label: '공사기간', projectKey: 'constructionPeriod' },\n { label: '총 근로자수', projectKey: 'totalWorkerCount' },\n { label: '연간 근로자 수', projectKey: 'annualWorkerCount' },\n { label: '투입인력', projectKey: 'workerCount' },\n { label: '재해 건수', projectKey: 'accidentCount' },\n { label: '일정 이탈 수준', projectKey: 'scheduleDeviationLevel' },\n { label: '총 건설 폐기물 발생량', projectKey: 'totalConstructionWasteAmount' },\n { label: '용적율', projectKey: 'floorAreaRatio' },\n { label: '총 설계 변경 건', projectKey: 'designChangeCount' },\n { label: '공사비', projectKey: 'constructionCost' },\n { label: '연면적', projectKey: 'area' },\n { label: '지상층수', projectKey: 'upperFloorCount' },\n { label: '지하층수', projectKey: 'lowerFloorCount' }\n]\n// 계획값이 없는 것들 (실제값으로 표시할 필드)\nconst NO_PLAN_FIELDS = ['workerCount', 'area', 'floorAreaRatio', 'designChangeCount', 'upperFloorCount', 'lowerFloorCount']\n\nconst EXCLUDE_FIELDS = [\n { id: 'b7a583c0-9de9-4c12-af49-dde65198dfce', name: '계획공사비' },\n { id: '9767747a-d2ec-4e36-96c4-4a78bba35b98', name: '실제공사비' },\n { id: '036d6e1c-193e-46bd-8d6e-93f248af8124', name: '계획공사기간' },\n { id: '5df77618-db44-4da3-a22d-43c067cb5e86', name: '실제공사기간' }\n]\n\n@customElement('sv-pc-tab1-plan')\nexport class SvProjectCompleteTab1Plan 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 padding: 8px 6px;\n }\n .row.header {\n min-height: 35px;\n background: #f3f3fa;\n border-top: 2px #0c4da2 solid;\n grid-template-columns: 220px 1fr 1fr 200px;\n padding: 0px 25px;\n\n .header-label {\n color: #212529;\n text-align: center;\n }\n }\n .row {\n display: grid;\n grid-template-columns: 220px 1fr 1fr 200px;\n gap: 6px 10px;\n align-items: center;\n padding: 8px 25px;\n border-bottom: 1px rgba(0, 0, 0, 0.1) solid;\n\n .cell {\n display: flex;\n align-items: center;\n justify-content: center;\n }\n }\n .label {\n color: #35618e;\n font-size: 16px;\n letter-spacing: -0.05em;\n white-space: nowrap;\n display: flex;\n justify-content: center;\n }\n input {\n padding: 6px 8px;\n border: 1px solid rgba(0, 0, 0, 0.1);\n border-radius: 5px;\n background: #ffffff;\n color: #212529;\n font-size: 16px;\n margin-right: 5px;\n\n &:disabled {\n background: #f6f6f6;\n }\n }\n .unit {\n text-align: center;\n color: #212529;\n }\n .plus {\n color: #e13232;\n font-weight: 700;\n }\n .minus {\n color: #1e88e5;\n font-weight: 700;\n }\n .button-line {\n display: flex;\n justify-content: center;\n gap: 10px;\n margin-top: 16px;\n }\n .ghost-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 10px;\n background: #35618e;\n color: #ffffff;\n border-radius: 5px;\n cursor: pointer;\n }\n .ghost-btn.secondary {\n background: #24be7b;\n }\n\n /* ── 자동 수집 패널 ── */\n .collect-panel {\n margin: 4px 6px 14px;\n border: 1px solid #d7e3f2;\n border-radius: 8px;\n background: linear-gradient(180deg, #f7fafe 0%, #ffffff 100%);\n overflow: hidden;\n }\n .collect-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 16px;\n background: #eaf2fb;\n border-bottom: 1px solid #d7e3f2;\n }\n .collect-head .ttl {\n color: #0c4da2;\n font-weight: 700;\n font-size: 14px;\n letter-spacing: -0.03em;\n }\n .collect-head .ttl small {\n color: #5a7da6;\n font-weight: 400;\n margin-left: 8px;\n }\n .collect-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 7px 14px;\n background: #0c4da2;\n color: #fff;\n border-radius: 6px;\n cursor: pointer;\n font-size: 13px;\n font-weight: 600;\n }\n .collect-btn[disabled] {\n background: #9bb4d2;\n cursor: default;\n }\n .source-list {\n display: flex;\n gap: 10px;\n padding: 12px 16px;\n flex-wrap: wrap;\n }\n .source-card {\n flex: 1 1 200px;\n border: 1px solid #e3e9f0;\n border-radius: 7px;\n padding: 10px 12px;\n background: #fff;\n transition: border-color 0.2s, box-shadow 0.2s;\n }\n .source-card.collecting {\n border-color: #2e79be;\n box-shadow: 0 0 0 3px rgba(46, 121, 190, 0.12);\n }\n .source-card.done {\n border-color: #24be7b;\n }\n .source-card .sc-head {\n display: flex;\n align-items: center;\n gap: 8px;\n font-weight: 600;\n color: #212529;\n font-size: 13px;\n }\n .source-card .sc-head .sys-icon {\n font-size: 16px;\n }\n .source-card .sc-status {\n margin-left: auto;\n font-size: 12px;\n }\n .sc-status.idle {\n color: #aab4c0;\n }\n .sc-status.collecting {\n color: #2e79be;\n }\n .sc-status.done {\n color: #24be7b;\n font-weight: 700;\n }\n .source-card .sc-fields {\n margin-top: 8px;\n display: flex;\n flex-direction: column;\n gap: 3px;\n }\n .sc-field {\n font-size: 12px;\n color: #5a6b7b;\n display: flex;\n justify-content: space-between;\n }\n .sc-field b {\n color: #0c4da2;\n }\n .sc-desc {\n font-size: 11px;\n color: #9aa7b4;\n margin-top: 2px;\n }\n /* 가져온 값 출처 칩 */\n .src-chip {\n display: inline-flex;\n align-items: center;\n margin-left: 6px;\n padding: 1px 7px;\n font-size: 11px;\n border-radius: 10px;\n background: #e7f3ec;\n color: #1d9c63;\n white-space: nowrap;\n }\n .cell.just-filled input {\n animation: flash 1.1s ease;\n }\n @keyframes flash {\n 0% {\n background: #fff7cc;\n }\n 100% {\n background: #ffffff;\n }\n }\n `\n ]\n\n @state() kpiMetricValues: any = []\n @state() kpiMetrics: any = []\n @property({ type: Object }) project: any = {}\n\n /** 연동 진행 상태: source → 'idle' | 'collecting' | 'done' */\n @state() collectStatus: Record<string, 'idle' | 'collecting' | 'done'> = {}\n /** 자동 수집한 실제값의 출처: metricId → 시스템 라벨 */\n @state() valueSources: Record<string, string> = {}\n /** 자동 수집한 계획값: projectKey → 값 (키스콘이 제공하는 계획공사기간/계획공사비) */\n @state() collectedPlan: Record<string, number> = {}\n /** 계획값 출처: projectKey → 시스템 라벨 */\n @state() planSources: Record<string, string> = {}\n /** 시스템별 수집 요약 (카드 표시용): 라벨 → [{label, text}] */\n @state() collectedSummary: Record<string, { label: string; text: string }[]> = {}\n /** 방금 채워진 셀 강조용 (metricId 또는 plan:projectKey) */\n @state() justFilled: Record<string, boolean> = {}\n @state() collecting = false\n\n /** 계획 칼럼을 갖는 항목 (키스콘이 계획값을 제공) */\n private static readonly PLAN_KEYS = ['constructionPeriod', 'constructionCost']\n\n render() {\n return html`\n <div class=\"title\">\n <div>\n 당초 계획 대비 실제 진행 과정에서 변동된 공사비, 공기(공사기간), 면적, 기타 주요 항목을 현실에 맞게 수정·입력합니다.\n <br />이 정보는 성과 분석, KPI 평가, 통계 산출 등에 기준값으로 사용되므로, 가능한 한 실제 값 기준으로 정확히\n 입력해주시기 바랍니다.\n </div>\n </div>\n\n ${this._renderCollectPanel()}\n\n <div class=\"rows\">\n <div class=\"row header\">\n <div class=\"header-label\">기본정보</div>\n <div class=\"header-label\">계획</div>\n <div class=\"header-label\">실제</div>\n <div class=\"header-label\">편차</div>\n </div>\n\n ${this.kpiMetrics.map(metric => {\n // 제외 필드는 표시하지 않음\n if (EXCLUDE_FIELDS.some(item => item.id === metric.id)) return null\n\n // 상수로 정의된 매핑 정보에서 metric.name으로 projectKey를 찾음\n const projectKey = KPI_METRIC_KEY_MAPPING.find(item => item.label === metric.name)?.projectKey || ''\n const kpiMetricValue = this.kpiMetricValues.find((item: any) => item.metricId === metric.id) || {}\n const isDisplayInput = projectKey === 'constructionPeriod' || projectKey === 'constructionCost' // 유효한 계획 필드\n\n // planValue는 project에서 찾고 없으면 buildingComplex의 값에서 찾음\n const basePlanValue = this.project[projectKey] || this.project?.buildingComplex?.[projectKey] || 0\n let planValue = basePlanValue\n const unit = kpiMetricValue.unit || metric.unit || ''\n\n // 2. 계획 값 처리\n // 공사기간은 기간을 일 단위로 계산\n if (projectKey === 'constructionPeriod') {\n planValue = Math.ceil(calcDateDiff(this.project.startDate, this.project.endDate) / 30)\n } else if (!isDisplayInput) {\n planValue = 0\n }\n\n // 1. 실제 값 처리 kpi값을 찾고, 없으면 (NO_PLAN_FIELDS에 해당하는 필드는 원래 계획값 사용)\n const actualValue = kpiMetricValue.value ?? (NO_PLAN_FIELDS.includes(projectKey) ? basePlanValue : 0)\n\n const diffValue = calcDiff(planValue, actualValue)\n const diffClass = diffValue === 0 ? '' : diffValue > 0 ? 'plus' : 'minus'\n const diffSign = diffValue === 0 ? '' : diffValue > 0 ? '+' : '-'\n\n const src = this.valueSources[metric.id]\n // 계획값: 키스콘 수집값이 있으면 그것을, 없으면 계산된 planValue\n const planSrc = this.planSources[projectKey]\n const shownPlan = this.collectedPlan[projectKey] ?? planValue\n const planFilled = this.justFilled[`plan:${projectKey}`]\n // 계획값이 키스콘에서 들어왔으면 편차도 그 기준으로 재계산\n const effDiff = calcDiff(shownPlan, actualValue)\n const effDiffClass = effDiff === 0 ? '' : effDiff > 0 ? 'plus' : 'minus'\n const effDiffSign = effDiff === 0 ? '' : effDiff > 0 ? '+' : '-'\n\n return html`<div class=\"row\">\n <div class=\"label\">• ${metric.name}</div>\n <div class=\"cell ${planFilled ? 'just-filled' : ''}\">\n ${isDisplayInput\n ? html`<input .value=${shownPlan} disabled /> ${unit}\n ${planSrc ? html`<span class=\"src-chip\">🔗 ${planSrc}</span>` : ''}`\n : html`-`}\n </div>\n <div class=\"cell ${this.justFilled[metric.id] ? 'just-filled' : ''}\">\n <input numeric .value=${actualValue ?? 0} @input=${(e: InputEvent) => this._onInputChange(e, metric)} />\n ${unit}\n ${src ? html`<span class=\"src-chip\">🔗 ${src}</span>` : ''}\n </div>\n <div class=\"unit ${isDisplayInput ? effDiffClass : diffClass}\">\n ${isDisplayInput ? effDiffSign : diffSign}\n ${Math.abs(isDisplayInput ? effDiff : diffValue).toLocaleString()} ${unit}\n </div>\n </div>`\n })}\n\n <div class=\"button-line\">\n <div class=\"ghost-btn\" @click=${this._reset}>초기화</div>\n <div class=\"ghost-btn secondary\" @click=${() => this._save()}>저장</div>\n </div>\n </div>\n `\n }\n\n private _renderCollectPanel() {\n return html`\n <div class=\"collect-panel\">\n <div class=\"collect-head\">\n <div class=\"ttl\">시스템 연동 자동 수집 <small>외부 시스템에서 기본정보를 가져옵니다</small></div>\n <div\n class=\"collect-btn\"\n ?disabled=${this.collecting}\n @click=${() => (this.collecting ? null : this._autoCollect())}\n >\n ${this.collecting ? '수집 중…' : '⟳ 자동 수집'}\n </div>\n </div>\n <div class=\"source-list\">\n ${INTEGRATION_SOURCES.map(meta => {\n const status = this.collectStatus[meta.source] || 'idle'\n const collected = Object.entries(this.valueSources).filter(([, label]) => label === meta.label)\n return html`\n <div class=\"source-card ${status}\">\n <div class=\"sc-head\">\n <span class=\"sys-icon\">${meta.icon}</span>\n <span>${meta.label}</span>\n <span class=\"sc-status ${status}\">\n ${status === 'collecting' ? '연동 중…' : status === 'done' ? '완료 ✓' : '대기'}\n </span>\n </div>\n <div class=\"sc-desc\">${meta.desc}</div>\n ${status === 'done' && collected.length\n ? html`<div class=\"sc-fields\">\n ${this._collectedFieldsOf(meta.label).map(\n f => html`<div class=\"sc-field\"><span>${f.label}</span><b>${f.text}</b></div>`\n )}\n </div>`\n : ''}\n </div>\n `\n })}\n </div>\n </div>\n `\n }\n\n /** 패널 카드에 표시할, 해당 시스템에서 수집한 값 요약 */\n private _collectedFieldsOf(sourceLabel: string): { label: string; text: string }[] {\n return this.collectedSummary[sourceLabel] || []\n }\n\n /** 자동 수집 — 시스템별로 차례로 연동하여 값을 폼에 채운다.\n * 키스콘이 주는 계획공사기간/계획공사비는 \"계획\" 칼럼으로,\n * 세움터/올바로 등 나머지는 \"실제\" 칼럼으로 채운다. */\n private async _autoCollect() {\n if (!this.project?.id) {\n notify({ message: '프로젝트 정보가 없습니다.' })\n return\n }\n this.collecting = true\n this.valueSources = {}\n this.collectedPlan = {}\n this.planSources = {}\n this.collectedSummary = {}\n this.collectStatus = {}\n\n for (const meta of INTEGRATION_SOURCES) {\n this.collectStatus = { ...this.collectStatus, [meta.source]: 'collecting' }\n this.requestUpdate()\n\n try {\n const result = await collectFromSource(meta.source as IntegrationSource, this.project)\n const summary: { label: string; text: string }[] = []\n\n for (const field of result.fields) {\n const unit = field.unit || ''\n summary.push({\n label: field.label,\n text: `${Number(field.value).toLocaleString()} ${unit}`.trim()\n })\n\n if (SvProjectCompleteTab1Plan.PLAN_KEYS.includes(field.projectKey)) {\n // 계획값 — 계획 칼럼에 반영\n this.collectedPlan = { ...this.collectedPlan, [field.projectKey]: Number(field.value) }\n this.planSources = { ...this.planSources, [field.projectKey]: result.label }\n this.justFilled = { ...this.justFilled, [`plan:${field.projectKey}`]: true }\n } else {\n // 실제값 — 실제 칼럼(메트릭)에 반영\n const metric = this.kpiMetrics.find(\n (m: any) => KPI_METRIC_KEY_MAPPING.find(x => x.label === m.name)?.projectKey === field.projectKey\n )\n if (!metric) continue\n this._setMetricValue(metric, field.value)\n this.valueSources = { ...this.valueSources, [metric.id]: result.label }\n this.justFilled = { ...this.justFilled, [metric.id]: true }\n }\n }\n\n this.collectedSummary = { ...this.collectedSummary, [meta.label]: summary }\n } catch (e) {\n console.error(`[자동수집] ${meta.label} 실패`, e)\n notify({ message: `${meta.label} 연동 실패` })\n }\n\n this.collectStatus = { ...this.collectStatus, [meta.source]: 'done' }\n this.requestUpdate()\n }\n\n this.collecting = false\n notify({ message: '외부 시스템 연동 수집이 완료되었습니다. 검토 후 [저장]을 눌러주세요.' })\n\n setTimeout(() => {\n this.justFilled = {}\n }, 1300)\n }\n\n /** 메트릭의 실제값 셀을 set/add (사용자 입력과 동일 경로) */\n private _setMetricValue(metric: any, value: number | string) {\n const idx = this.kpiMetricValues.findIndex((item: any) => item.metricId === metric.id)\n if (idx !== -1) {\n this.kpiMetricValues = this.kpiMetricValues.map((item: any) =>\n item.metricId === metric.id ? { ...item, value } : item\n )\n } else {\n this.kpiMetricValues = [\n ...this.kpiMetricValues,\n {\n id: crypto.randomUUID(),\n value,\n metricId: metric.id,\n unit: metric.unit || '',\n org: this.project.id,\n periodType: metric.periodType,\n valueDate: moment().tz('Asia/Seoul').format('YYYY-MM-DD')\n }\n ]\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._getInitData()\n }\n }\n\n private async _getInitData() {\n const kpiMetrics = await getKpiMetrics()\n this.kpiMetrics = kpiMetrics.filter(item => !item.name.includes('평가')) || [] // 평가 텍스트가 들어간건 제외\n this.kpiMetricValues = await getKpiMetricValues(this.project.id)\n }\n\n // Input 요소의 값이 변경될 때 호출되는 콜백 함수\n private _onInputChange(event: InputEvent, metric: any) {\n const target = event.target as HTMLInputElement\n let inputVal: any = target.value\n\n // 숫자 타입은 다른 문자 입력 제거\n if (target.hasAttribute('numeric')) {\n inputVal = Number(inputVal.replace(/[^\\d.]/g, ''))\n }\n\n // 기존 배열에서 해당 id를 가진 항목을 찾음\n const existingItemIndex = this.kpiMetricValues.findIndex((item: any) => item.metricId === metric.id)\n\n if (existingItemIndex !== -1) {\n // 기존 항목이 있으면 업데이트\n this.kpiMetricValues = this.kpiMetricValues.map((item: any) =>\n item.metricId === metric.id ? { ...item, value: inputVal } : item\n )\n } else {\n // 기존 항목이 없으면 새로 추가\n this.kpiMetricValues = [\n ...this.kpiMetricValues,\n {\n id: crypto.randomUUID(),\n value: inputVal,\n metricId: metric.id,\n unit: metric.unit || '',\n org: this.project.id, // 프로젝트 ID 추가\n periodType: metric.periodType,\n valueDate: moment().tz('Asia/Seoul').format('YYYY-MM-DD')\n }\n ]\n }\n }\n\n private _generateExcludeFieldsData() {\n const excludeData: any[] = []\n\n const constructionCostMetric = this.kpiMetrics.find(\n metric => KPI_METRIC_KEY_MAPPING.find(item => item.label === metric.name)?.projectKey === 'constructionCost'\n )\n\n // 공사기간 관련 메트릭을 찾음\n const constructionPeriodMetric = this.kpiMetrics.find(\n metric => KPI_METRIC_KEY_MAPPING.find(item => item.label === metric.name)?.projectKey === 'constructionPeriod'\n )\n\n // 실적공사비 값 (사용자가 입력한 constructionCost 값)\n const actualConstructionCostValue = constructionCostMetric\n ? this.kpiMetricValues.find((item: any) => item.metricId === constructionCostMetric.id)?.value || 0\n : 0\n\n // 실제공사기간 값 (사용자가 입력한 constructionPeriod 값)\n const actualConstructionPeriodValue = constructionPeriodMetric\n ? this.kpiMetricValues.find((item: any) => item.metricId === constructionPeriodMetric.id)?.value || 0\n : 0\n\n EXCLUDE_FIELDS.forEach(excludeField => {\n // 해당 excludeField.id에 매칭되는 메트릭 찾기\n const metric = this.kpiMetrics.find(m => m.id === excludeField.id)\n\n // 기존에 저장된 데이터가 있는지 확인\n const existingValue = this.kpiMetricValues.find((item: any) => item.metricId === excludeField.id)\n\n let value = 0\n if (excludeField.name == '계획공사비') {\n // 키스콘 수집 계획값 우선, 없으면 buildingComplex\n value = this.collectedPlan['constructionCost'] ?? this.project?.buildingComplex?.constructionCost ?? 0\n } else if (excludeField.name == '실제공사비') {\n value = actualConstructionCostValue\n } else if (excludeField.name == '계획공사기간') {\n // 키스콘 수집 계획값 우선, 없으면 착공~준공 일자 계산\n value =\n this.collectedPlan['constructionPeriod'] ??\n Math.ceil(calcDateDiff(this.project.startDate, this.project.endDate) / 30)\n } else if (excludeField.name == '실제공사기간') {\n value = actualConstructionPeriodValue\n }\n\n excludeData.push({\n id: existingValue?.id || crypto.randomUUID(), // 기존 데이터가 있으면 해당 id 사용\n value,\n metricId: excludeField.id,\n unit: metric?.unit || '',\n org: this.project.id,\n periodType: metric?.periodType,\n valueDate: moment().tz('Asia/Seoul').format('YYYY-MM-DD')\n })\n })\n\n return excludeData\n }\n\n private async _save() {\n // EXCLUDE_FIELDS에 대한 값들을 자동으로 생성\n const excludeData = this._generateExcludeFieldsData()\n\n // EXCLUDE_FIELDS에 해당하는 metricId를 추출\n const excludeMetricIds = EXCLUDE_FIELDS.map(field => field.id)\n // kpiMetricValues에서 EXCLUDE_FIELDS에 해당하는 중복 항목들을 제외\n const filteredKpiMetricValues = this.kpiMetricValues.filter((item: any) => !excludeMetricIds.includes(item.metricId))\n\n // 기본 실제값 엔트리 생성 (사용자 입력이 없는 메트릭에 대해 기본값 채움)\n const existingMetricIds = new Set(filteredKpiMetricValues.map((item: any) => item.metricId))\n const defaultActualData = this.kpiMetrics\n .filter(metric => !EXCLUDE_FIELDS.some(field => field.id === metric.id) && !existingMetricIds.has(metric.id))\n .map(metric => {\n const projectKey = KPI_METRIC_KEY_MAPPING.find(item => item.label === metric.name)?.projectKey || ''\n const basePlanValue = this.project[projectKey] || this.project?.buildingComplex?.[projectKey] || 0\n const value = NO_PLAN_FIELDS.includes(projectKey) ? basePlanValue : 0\n\n return {\n id: crypto.randomUUID(),\n value,\n metricId: metric.id,\n unit: metric.unit || '',\n org: this.project.id,\n periodType: metric.periodType,\n valueDate: moment().tz('Asia/Seoul').format('YYYY-MM-DD')\n }\n })\n\n // 필터링된 데이터와 exclude 데이터, 기본 실제값 데이터를 합침\n const allKpiMetricValues = [...excludeData, ...filteredKpiMetricValues, ...defaultActualData]\n const response = await updateProjectCompleteStep1(allKpiMetricValues)\n if (!response.errors) {\n notify({ message: '저장되었습니다.' })\n }\n }\n\n private _reset() {\n this._getInitData()\n }\n}\n"]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 완공 입력 화면의 "자동 수집" 워크플로용 외부 시스템 연동 수집 함수.
|
|
3
|
+
*
|
|
4
|
+
* 데모 목적: 키스콘·세움터·올바로에서 "연동을 통해 값을 가져온다"는 워크플로를
|
|
5
|
+
* 보여주기 위한 것. 실제 스크래핑/API 연동의 깊이는 이 함수 내부에 캡슐화되어
|
|
6
|
+
* 있으므로, 추후 실연동(integration-kiscon / data-go-kr / integration-allbaro)
|
|
7
|
+
* 으로 교체할 때 이 본문만 바꾸면 화면/저장 흐름은 그대로 유지된다.
|
|
8
|
+
*/
|
|
9
|
+
export type IntegrationSource = 'kiscon' | 'saeumteo' | 'allbaro';
|
|
10
|
+
export interface IntegrationSourceMeta {
|
|
11
|
+
source: IntegrationSource;
|
|
12
|
+
label: string;
|
|
13
|
+
icon: string;
|
|
14
|
+
desc: string;
|
|
15
|
+
}
|
|
16
|
+
/** 화면에 표시할 연동 소스 목록 (수집 순서대로) */
|
|
17
|
+
export declare const INTEGRATION_SOURCES: IntegrationSourceMeta[];
|
|
18
|
+
export interface CollectedField {
|
|
19
|
+
/** 폼의 projectKey (KPI_METRIC_KEY_MAPPING 의 projectKey 와 일치) */
|
|
20
|
+
projectKey: string;
|
|
21
|
+
label: string;
|
|
22
|
+
value: number | string;
|
|
23
|
+
unit?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface CollectionResult {
|
|
26
|
+
source: IntegrationSource;
|
|
27
|
+
label: string;
|
|
28
|
+
fields: CollectedField[];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 한 외부 시스템에서 연동으로 값을 수집한다.
|
|
32
|
+
* 데모에서는 연동 수집을 흉내내기 위해 짧은 지연 후 대표값을 반환한다
|
|
33
|
+
* (프로젝트에 이미 있는 값이 있으면 우선 사용).
|
|
34
|
+
*/
|
|
35
|
+
export declare function collectFromSource(source: IntegrationSource, project: any): Promise<CollectionResult>;
|