@dssp/dkpi 1.0.0-alpha.49 → 1.0.0-alpha.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist-client/components/kpi-single-boxplot-chart.d.ts +24 -0
  2. package/dist-client/components/kpi-single-boxplot-chart.js +317 -0
  3. package/dist-client/components/kpi-single-boxplot-chart.js.map +1 -0
  4. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.d.ts +5 -4
  5. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js +94 -120
  6. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js.map +1 -1
  7. package/dist-client/pages/project-complete-tabs/{pc-tab3-rating.d.ts → pc-tab2-rating.d.ts} +2 -2
  8. package/dist-client/pages/project-complete-tabs/{pc-tab3-rating.js → pc-tab2-rating.js} +15 -16
  9. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js.map +1 -0
  10. package/dist-client/pages/project-complete-tabs/{pc-tab4-upload.d.ts → pc-tab3-upload.d.ts} +2 -3
  11. package/dist-client/pages/project-complete-tabs/{pc-tab4-upload.js → pc-tab3-upload.js} +17 -21
  12. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js.map +1 -0
  13. package/dist-client/pages/sv-project-complete.d.ts +4 -8
  14. package/dist-client/pages/sv-project-complete.js +29 -123
  15. package/dist-client/pages/sv-project-complete.js.map +1 -1
  16. package/dist-client/pages/sv-project-completed-list.d.ts +1 -0
  17. package/dist-client/pages/sv-project-completed-list.js +18 -4
  18. package/dist-client/pages/sv-project-completed-list.js.map +1 -1
  19. package/dist-client/pages/sv-project-detail.js +1 -1
  20. package/dist-client/pages/sv-project-detail.js.map +1 -1
  21. package/dist-client/pages/sv-project-list.d.ts +3 -0
  22. package/dist-client/pages/sv-project-list.js +52 -3
  23. package/dist-client/pages/sv-project-list.js.map +1 -1
  24. package/dist-client/shared/complete-api.d.ts +5 -3
  25. package/dist-client/shared/complete-api.js +57 -16
  26. package/dist-client/shared/complete-api.js.map +1 -1
  27. package/dist-client/themes/light.css +2 -2
  28. package/dist-client/tsconfig.tsbuildinfo +1 -1
  29. package/dist-client/viewparts/menu-tools.js +1 -2
  30. package/dist-client/viewparts/menu-tools.js.map +1 -1
  31. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js +8 -0
  32. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js.map +1 -1
  33. package/dist-server/service/kpi-metric-value/kpi-metric-value-query.js +1 -1
  34. package/dist-server/service/kpi-metric-value/kpi-metric-value-query.js.map +1 -1
  35. package/dist-server/tsconfig.tsbuildinfo +1 -1
  36. package/package.json +3 -3
  37. package/schema.graphql +4 -35
  38. package/dist-client/pages/project-complete-tabs/pc-tab2-final.d.ts +0 -11
  39. package/dist-client/pages/project-complete-tabs/pc-tab2-final.js +0 -277
  40. package/dist-client/pages/project-complete-tabs/pc-tab2-final.js.map +0 -1
  41. package/dist-client/pages/project-complete-tabs/pc-tab3-rating.js.map +0 -1
  42. package/dist-client/pages/project-complete-tabs/pc-tab4-upload.js.map +0 -1
@@ -2,25 +2,32 @@ import { __decorate, __metadata } from "tslib";
2
2
  import { css, html, LitElement } from 'lit';
3
3
  import { customElement, property } from 'lit/decorators.js';
4
4
  import { calcDiff, calcDateDiff } from '../../shared/func';
5
+ import { getKpiMetricValues, getKpiMetrics, updateProjectCompleteStep1 } from '../../shared/complete-api';
6
+ import moment from 'moment-timezone';
7
+ import { notify } from '@operato/layout';
8
+ const KPI_METRIC_KEY_MAPPING = [
9
+ { label: '공사기간', projectKey: 'constructionPeriod' },
10
+ { label: '총 근로자수', projectKey: 'totalWorkerCount' },
11
+ { label: '연간 근로자 수', projectKey: 'annualWorkerCount' },
12
+ { label: '투입인력', projectKey: 'workerCount' },
13
+ { label: '재해 건수', projectKey: 'accidentCount' },
14
+ { label: '일정 이탈 수준', projectKey: 'scheduleDeviationLevel' },
15
+ { label: '총 건설 폐기물 발생량', projectKey: 'totalConstructionWasteAmount' },
16
+ { label: '용적율', projectKey: 'floorAreaRatio' },
17
+ { label: '총 설계 변경 건', projectKey: 'designChangeCount' },
18
+ { label: '공사비', projectKey: 'constructionCost' },
19
+ { label: '연면적', projectKey: 'area' },
20
+ { label: '지상층수', projectKey: 'upperFloorCount' },
21
+ { label: '지하층수', projectKey: 'lowerFloorCount' }
22
+ ];
5
23
  let SvProjectCompleteTab1Plan = class SvProjectCompleteTab1Plan extends LitElement {
6
24
  constructor() {
7
25
  super(...arguments);
8
- this.data = {};
26
+ this.kpiMetricValues = [];
27
+ this.kpiMetrics = [];
9
28
  this.project = {};
10
29
  }
11
30
  render() {
12
- var _a;
13
- const project = this.project;
14
- const buildingComplex = (_a = this.project) === null || _a === void 0 ? void 0 : _a.buildingComplex;
15
- const data = this.data;
16
- const startDateDiff = calcDateDiff(project.startDate, this.data.actualStartDate);
17
- const endDateDiff = calcDateDiff(project.endDate, this.data.actualEndDate);
18
- const coverageRatioDiff = calcDiff(buildingComplex === null || buildingComplex === void 0 ? void 0 : buildingComplex.coverageRatio, this.data.actualCoverageRatio);
19
- const floorAreaRatioDiff = calcDiff(buildingComplex === null || buildingComplex === void 0 ? void 0 : buildingComplex.floorAreaRatio, this.data.actualFloorAreaRatio);
20
- const workerCountDiff = calcDiff(buildingComplex === null || buildingComplex === void 0 ? void 0 : buildingComplex.workerCount, this.data.actualWorkerCount);
21
- const designChangeCountDiff = calcDiff(buildingComplex === null || buildingComplex === void 0 ? void 0 : buildingComplex.designChangeCount, this.data.actualDesignChangeCount);
22
- const constructionCostDiff = calcDiff(buildingComplex === null || buildingComplex === void 0 ? void 0 : buildingComplex.constructionCost, this.data.actualConstructionCost);
23
- const areaDiff = calcDiff(buildingComplex === null || buildingComplex === void 0 ? void 0 : buildingComplex.area, this.data.actualArea);
24
31
  return html `
25
32
  <div class="title">
26
33
  <div>
@@ -38,127 +45,90 @@ let SvProjectCompleteTab1Plan = class SvProjectCompleteTab1Plan extends LitEleme
38
45
  <div class="header-label">편차</div>
39
46
  </div>
40
47
 
41
- <div class="row">
42
- <div class="label">• 공사 시작일</div>
43
- <div class="cell"><input .value=${project.startDate} disabled /> 일</div>
44
- <div class="cell">
45
- <input type="date" name="actualStartDate" .value=${data.actualStartDate} @input=${this._onInputChange} />
46
-
47
- </div>
48
- <div class="unit ${this._getDiffClass(startDateDiff)}">${this._getDisplay(startDateDiff)} 일</div>
49
- </div>
50
-
51
- <div class="row">
52
- <div class="label">• 공사 종료일</div>
53
- <div class="cell"><input .value=${project.endDate} disabled /> 일</div>
54
- <div class="cell">
55
- <input type="date" name="actualEndDate" .value=${data.actualEndDate} @input=${this._onInputChange} />
56
-
57
- </div>
58
- <div class="unit ${this._getDiffClass(endDateDiff)}">${this._getDisplay(endDateDiff)} 일</div>
59
- </div>
60
-
61
- <div class="row">
62
- <div class="label">• 건폐율</div>
63
- <div class="cell"><input .value=${(buildingComplex === null || buildingComplex === void 0 ? void 0 : buildingComplex.coverageRatio) || ''} disabled /> %</div>
64
- <div class="cell">
65
- <input name="actualCoverageRatio" numeric .value=${data.actualCoverageRatio || ''} @input=${this._onInputChange} />
66
- %
67
- </div>
68
- <div class="unit ${this._getDiffClass(coverageRatioDiff)}">${this._getDisplay(coverageRatioDiff)} %</div>
69
- </div>
70
-
71
- <div class="row">
72
- <div class="label">• 용적률</div>
73
- <div class="cell"><input .value=${(buildingComplex === null || buildingComplex === void 0 ? void 0 : buildingComplex.floorAreaRatio) || ''} disabled /> %</div>
74
- <div class="cell">
75
- <input name="actualFloorAreaRatio" numeric .value=${data.actualFloorAreaRatio || ''} @input=${this._onInputChange} />
76
- %
77
- </div>
78
- <div class="unit ${this._getDiffClass(floorAreaRatioDiff)}">${this._getDisplay(floorAreaRatioDiff)} %</div>
79
- </div>
80
-
81
- <div class="row">
82
- <div class="label">• 투입인력</div>
83
- <div class="cell"><input .value=${(buildingComplex === null || buildingComplex === void 0 ? void 0 : buildingComplex.workerCount) || ''} disabled /> 명</div>
84
- <div class="cell">
85
- <input name="actualWorkerCount" numeric .value=${data.actualWorkerCount || ''} @input=${this._onInputChange} />
86
-
87
- </div>
88
- <div class="unit ${this._getDiffClass(workerCountDiff)}">${this._getDisplay(workerCountDiff)} 명</div>
89
- </div>
90
-
91
- <div class="row">
92
- <div class="label">• 설계변경</div>
93
- <div class="cell"><input .value=${(buildingComplex === null || buildingComplex === void 0 ? void 0 : buildingComplex.designChangeCount) || ''} disabled /> 회</div>
94
- <div class="cell">
95
- <input
96
- name="actualDesignChangeCount"
97
- numeric
98
- .value=${data.actualDesignChangeCount || ''}
99
- @input=${this._onInputChange}
100
- />
101
-
102
- </div>
103
- <div class="unit ${this._getDiffClass(designChangeCountDiff)}">${this._getDisplay(designChangeCountDiff)} 회</div>
104
- </div>
105
-
106
- <div class="row">
107
- <div class="label">• 공사비</div>
108
- <div class="cell"><input .value=${(buildingComplex === null || buildingComplex === void 0 ? void 0 : buildingComplex.constructionCost) || ''} disabled /> 원</div>
109
- <div class="cell">
110
- <input
111
- name="actualConstructionCost"
112
- numeric
113
- .value=${data.actualConstructionCost || ''}
114
- @input=${this._onInputChange}
115
- />
116
-
117
- </div>
118
- <div class="unit ${this._getDiffClass(constructionCostDiff)}">${this._getDisplay(constructionCostDiff)} 원</div>
119
- </div>
120
-
121
- <div class="row">
122
- <div class="label">• 연면적</div>
123
- <div class="cell"><input .value=${(buildingComplex === null || buildingComplex === void 0 ? void 0 : buildingComplex.area) || ''} disabled /> ㎡</div>
124
- <div class="cell">
125
- <input name="actualArea" numeric .value=${data.actualArea || ''} @input=${this._onInputChange} />
126
-
127
- </div>
128
- <div class="unit ${this._getDiffClass(areaDiff)}">${this._getDisplay(areaDiff)} ㎡</div>
129
- </div>
48
+ ${this.kpiMetrics.map(metric => {
49
+ var _a, _b, _c;
50
+ // 상수로 정의된 매핑 정보에서 metric.name으로 projectKey를 찾음
51
+ const projectKey = ((_a = KPI_METRIC_KEY_MAPPING.find(item => item.label === metric.name)) === null || _a === void 0 ? void 0 : _a.projectKey) || '';
52
+ const kpiMetricValue = this.kpiMetricValues.find((item) => item.metricId === metric.id) || {};
53
+ // planValue는 project에서 찾고 없으면 buildingComplex의 값에서 찾음
54
+ let planValue = this.project[projectKey] || ((_c = (_b = this.project) === null || _b === void 0 ? void 0 : _b.buildingComplex) === null || _c === void 0 ? void 0 : _c[projectKey]) || 0;
55
+ // 공사기간은 기간을 일 단위로 계산
56
+ if (projectKey === 'constructionPeriod') {
57
+ planValue = calcDateDiff(this.project.startDate, this.project.endDate);
58
+ }
59
+ const diffValue = calcDiff(planValue, kpiMetricValue.value);
60
+ const diffClass = diffValue === 0 ? '' : diffValue > 0 ? 'plus' : 'minus';
61
+ const diffSign = diffValue === 0 ? '' : diffValue > 0 ? '+' : '-';
62
+ const unit = kpiMetricValue.unit || metric.unit || '';
63
+ return html `<div class="row">
64
+ <div class="label">• ${metric.name}</div>
65
+ <div class="cell"><input .value=${planValue} disabled /> ${unit}</div>
66
+ <div class="cell">
67
+ <input numeric .value=${kpiMetricValue.value || 0} @input=${(e) => this._onInputChange(e, metric)} />
68
+ ${unit}
69
+ </div>
70
+ <div class="unit ${diffClass}">${diffSign} ${Math.abs(diffValue).toLocaleString()} ${unit}</div>
71
+ </div>`;
72
+ })}
130
73
 
131
74
  <div class="button-line">
132
75
  <div class="ghost-btn" @click=${this._reset}>초기화</div>
133
- <div class="ghost-btn" @click=${() => this._saveAndMove()}>저장 & 다음</div>
76
+ <div class="ghost-btn secondary" @click=${() => this._save()}>저장</div>
134
77
  </div>
135
78
  </div>
136
79
  `;
137
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._getInitData();
87
+ }
88
+ }
89
+ async _getInitData() {
90
+ const kpiMetrics = await getKpiMetrics();
91
+ this.kpiMetrics = kpiMetrics.filter(item => !item.name.includes('평가')) || []; // 평가 텍스트가 들어간건 제외
92
+ this.kpiMetricValues = await getKpiMetricValues(this.project.id);
93
+ }
138
94
  // Input 요소의 값이 변경될 때 호출되는 콜백 함수
139
- _onInputChange(event, idx) {
95
+ _onInputChange(event, metric) {
140
96
  const target = event.target;
141
97
  let inputVal = target.value;
142
98
  // 숫자 타입은 다른 문자 입력 제거
143
99
  if (target.hasAttribute('numeric')) {
144
100
  inputVal = Number(inputVal.replace(/[^\d.]/g, ''));
145
101
  }
146
- this.data = Object.assign(Object.assign({}, this.data), { [target.name]: inputVal });
147
- this.dispatchEvent(new CustomEvent('complete-data-change', { detail: { tab: 1, data: this.data } }));
148
- }
149
- _getDiffClass(diff) {
150
- return diff === 0 ? '' : diff > 0 ? 'plus' : 'minus';
151
- }
152
- _getDisplay(value) {
153
- const sign = value === 0 ? '' : value > 0 ? '+' : '-';
154
- return `${sign} ${Math.abs(value).toLocaleString()}`;
102
+ // 기존 배열에서 해당 id를 가진 항목을 찾음
103
+ const existingItemIndex = this.kpiMetricValues.findIndex((item) => item.metricId === metric.id);
104
+ if (existingItemIndex !== -1) {
105
+ // 기존 항목이 있으면 업데이트
106
+ this.kpiMetricValues = this.kpiMetricValues.map((item) => item.metricId === metric.id ? Object.assign(Object.assign({}, item), { value: inputVal }) : item);
107
+ }
108
+ else {
109
+ // 기존 항목이 없으면 새로 추가
110
+ this.kpiMetricValues = [
111
+ ...this.kpiMetricValues,
112
+ {
113
+ id: crypto.randomUUID(),
114
+ value: inputVal,
115
+ metricId: metric.id,
116
+ unit: metric.unit || '',
117
+ org: this.project.id, // 프로젝트 ID 추가
118
+ periodType: metric.periodType,
119
+ valueDate: moment().tz('Asia/Seoul').format('YYYY-MM-DD')
120
+ }
121
+ ];
122
+ }
155
123
  }
156
- _saveAndMove() {
157
- this.dispatchEvent(new CustomEvent('complete-tab-change', { detail: { toTab: 2, data: this.data } }));
124
+ async _save() {
125
+ const response = await updateProjectCompleteStep1(this.kpiMetricValues);
126
+ if (!response.errors) {
127
+ notify({ message: '저장되었습니다.' });
128
+ }
158
129
  }
159
130
  _reset() {
160
- this.data = {};
161
- this.dispatchEvent(new CustomEvent('complete-tab-reset', { detail: { tab: 1 }, bubbles: true, composed: true }));
131
+ this._getInitData();
162
132
  }
163
133
  };
164
134
  SvProjectCompleteTab1Plan.styles = [
@@ -255,14 +225,18 @@ SvProjectCompleteTab1Plan.styles = [
255
225
  cursor: pointer;
256
226
  }
257
227
  .ghost-btn.secondary {
258
- background: #7a91ac;
228
+ background: #24be7b;
259
229
  }
260
230
  `
261
231
  ];
262
232
  __decorate([
263
- property({ type: Object }),
233
+ property({ type: Array }),
234
+ __metadata("design:type", Object)
235
+ ], SvProjectCompleteTab1Plan.prototype, "kpiMetricValues", void 0);
236
+ __decorate([
237
+ property({ type: Array }),
264
238
  __metadata("design:type", Object)
265
- ], SvProjectCompleteTab1Plan.prototype, "data", void 0);
239
+ ], SvProjectCompleteTab1Plan.prototype, "kpiMetrics", void 0);
266
240
  __decorate([
267
241
  property({ type: Object }),
268
242
  __metadata("design:type", Object)
@@ -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'\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: #7a91ac;\n }\n `\n ]\n\n @property({ type: Object }) data: any = {}\n @property({ type: Object }) project: any = {}\n\n render() {\n const project = this.project\n const buildingComplex = this.project?.buildingComplex\n const data = this.data\n\n const startDateDiff = calcDateDiff(project.startDate, this.data.actualStartDate)\n const endDateDiff = calcDateDiff(project.endDate, this.data.actualEndDate)\n const coverageRatioDiff = calcDiff(buildingComplex?.coverageRatio as any, this.data.actualCoverageRatio)\n const floorAreaRatioDiff = calcDiff(buildingComplex?.floorAreaRatio as any, this.data.actualFloorAreaRatio)\n const workerCountDiff = calcDiff(buildingComplex?.workerCount as any, this.data.actualWorkerCount)\n const designChangeCountDiff = calcDiff(buildingComplex?.designChangeCount as any, this.data.actualDesignChangeCount)\n const constructionCostDiff = calcDiff(buildingComplex?.constructionCost as any, this.data.actualConstructionCost)\n const areaDiff = calcDiff(buildingComplex?.area as any, this.data.actualArea)\n\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 <div class=\"row\">\n <div class=\"label\">• 공사 시작일</div>\n <div class=\"cell\"><input .value=${project.startDate} disabled /> 일</div>\n <div class=\"cell\">\n <input type=\"date\" name=\"actualStartDate\" .value=${data.actualStartDate} @input=${this._onInputChange} />\n 일\n </div>\n <div class=\"unit ${this._getDiffClass(startDateDiff)}\">${this._getDisplay(startDateDiff)} 일</div>\n </div>\n\n <div class=\"row\">\n <div class=\"label\">• 공사 종료일</div>\n <div class=\"cell\"><input .value=${project.endDate} disabled /> 일</div>\n <div class=\"cell\">\n <input type=\"date\" name=\"actualEndDate\" .value=${data.actualEndDate} @input=${this._onInputChange} />\n 일\n </div>\n <div class=\"unit ${this._getDiffClass(endDateDiff)}\">${this._getDisplay(endDateDiff)} 일</div>\n </div>\n\n <div class=\"row\">\n <div class=\"label\">• 건폐율</div>\n <div class=\"cell\"><input .value=${buildingComplex?.coverageRatio || ''} disabled /> %</div>\n <div class=\"cell\">\n <input name=\"actualCoverageRatio\" numeric .value=${data.actualCoverageRatio || ''} @input=${this._onInputChange} />\n %\n </div>\n <div class=\"unit ${this._getDiffClass(coverageRatioDiff)}\">${this._getDisplay(coverageRatioDiff)} %</div>\n </div>\n\n <div class=\"row\">\n <div class=\"label\">• 용적률</div>\n <div class=\"cell\"><input .value=${buildingComplex?.floorAreaRatio || ''} disabled /> %</div>\n <div class=\"cell\">\n <input name=\"actualFloorAreaRatio\" numeric .value=${data.actualFloorAreaRatio || ''} @input=${this._onInputChange} />\n %\n </div>\n <div class=\"unit ${this._getDiffClass(floorAreaRatioDiff)}\">${this._getDisplay(floorAreaRatioDiff)} %</div>\n </div>\n\n <div class=\"row\">\n <div class=\"label\">• 투입인력</div>\n <div class=\"cell\"><input .value=${buildingComplex?.workerCount || ''} disabled /> 명</div>\n <div class=\"cell\">\n <input name=\"actualWorkerCount\" numeric .value=${data.actualWorkerCount || ''} @input=${this._onInputChange} />\n 명\n </div>\n <div class=\"unit ${this._getDiffClass(workerCountDiff)}\">${this._getDisplay(workerCountDiff)} 명</div>\n </div>\n\n <div class=\"row\">\n <div class=\"label\">• 설계변경</div>\n <div class=\"cell\"><input .value=${buildingComplex?.designChangeCount || ''} disabled /> 회</div>\n <div class=\"cell\">\n <input\n name=\"actualDesignChangeCount\"\n numeric\n .value=${data.actualDesignChangeCount || ''}\n @input=${this._onInputChange}\n />\n 회\n </div>\n <div class=\"unit ${this._getDiffClass(designChangeCountDiff)}\">${this._getDisplay(designChangeCountDiff)} 회</div>\n </div>\n\n <div class=\"row\">\n <div class=\"label\">• 공사비</div>\n <div class=\"cell\"><input .value=${buildingComplex?.constructionCost || ''} disabled /> 원</div>\n <div class=\"cell\">\n <input\n name=\"actualConstructionCost\"\n numeric\n .value=${data.actualConstructionCost || ''}\n @input=${this._onInputChange}\n />\n 원\n </div>\n <div class=\"unit ${this._getDiffClass(constructionCostDiff)}\">${this._getDisplay(constructionCostDiff)} 원</div>\n </div>\n\n <div class=\"row\">\n <div class=\"label\">• 연면적</div>\n <div class=\"cell\"><input .value=${buildingComplex?.area || ''} disabled /> ㎡</div>\n <div class=\"cell\">\n <input name=\"actualArea\" numeric .value=${data.actualArea || ''} @input=${this._onInputChange} />\n ㎡\n </div>\n <div class=\"unit ${this._getDiffClass(areaDiff)}\">${this._getDisplay(areaDiff)} ㎡</div>\n </div>\n\n <div class=\"button-line\">\n <div class=\"ghost-btn\" @click=${this._reset}>초기화</div>\n <div class=\"ghost-btn\" @click=${() => this._saveAndMove()}>저장 & 다음</div>\n </div>\n </div>\n `\n }\n\n // Input 요소의 값이 변경될 때 호출되는 콜백 함수\n private _onInputChange(event: InputEvent, idx: number) {\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 this.data = { ...this.data, [target.name]: inputVal }\n this.dispatchEvent(new CustomEvent('complete-data-change', { detail: { tab: 1, data: this.data } }))\n }\n\n private _getDiffClass(diff: number) {\n return diff === 0 ? '' : diff > 0 ? 'plus' : 'minus'\n }\n\n private _getDisplay(value: number) {\n const sign = value === 0 ? '' : value > 0 ? '+' : '-'\n return `${sign} ${Math.abs(value).toLocaleString()}`\n }\n\n private _saveAndMove() {\n this.dispatchEvent(new CustomEvent('complete-tab-change', { detail: { toTab: 2, data: this.data } }))\n }\n\n private _reset() {\n this.data = {}\n this.dispatchEvent(new CustomEvent('complete-tab-reset', { detail: { tab: 1 }, bubbles: true, composed: true }))\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 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\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 @property({ type: Array }) kpiMetricValues: any = []\n @property({ type: Array }) 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 // 상수로 정의된 매핑 정보에서 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\n // planValue는 project에서 찾고 없으면 buildingComplex의 값에서 찾음\n let planValue = this.project[projectKey] || this.project?.buildingComplex?.[projectKey] || 0\n\n // 공사기간은 기간을 일 단위로 계산\n if (projectKey === 'constructionPeriod') {\n planValue = calcDateDiff(this.project.startDate, this.project.endDate)\n }\n\n const diffValue = calcDiff(planValue, kpiMetricValue.value)\n const diffClass = diffValue === 0 ? '' : diffValue > 0 ? 'plus' : 'minus'\n const diffSign = diffValue === 0 ? '' : diffValue > 0 ? '+' : '-'\n\n const unit = kpiMetricValue.unit || metric.unit || ''\n\n return html`<div class=\"row\">\n <div class=\"label\">• ${metric.name}</div>\n <div class=\"cell\"><input .value=${planValue} disabled /> ${unit}</div>\n <div class=\"cell\">\n <input numeric .value=${kpiMetricValue.value || 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 async _save() {\n const response = await updateProjectCompleteStep1(this.kpiMetricValues)\n if (!response.errors) {\n notify({ message: '저장되었습니다.' })\n }\n }\n\n private _reset() {\n this._getInitData()\n }\n}\n"]}
@@ -1,10 +1,10 @@
1
1
  import { LitElement } from 'lit';
2
- export declare class SvProjectCompleteTab3Rating extends LitElement {
2
+ export declare class SvProjectCompleteTab2Rating extends LitElement {
3
3
  static styles: import("lit").CSSResult[];
4
4
  data: any;
5
5
  kpiCategories: any;
6
6
  render(): import("lit-html").TemplateResult<1>;
7
7
  private _setRatingHalf;
8
- private _saveAndMove;
8
+ private _save;
9
9
  private _reset;
10
10
  }
@@ -2,7 +2,7 @@ import { __decorate, __metadata } from "tslib";
2
2
  import { css, html, LitElement } from 'lit';
3
3
  import { customElement, property } from 'lit/decorators.js';
4
4
  import { calcDiff } from '../../shared/func';
5
- let SvProjectCompleteTab3Rating = class SvProjectCompleteTab3Rating extends LitElement {
5
+ let SvProjectCompleteTab2Rating = class SvProjectCompleteTab2Rating extends LitElement {
6
6
  constructor() {
7
7
  super(...arguments);
8
8
  this.data = [];
@@ -58,9 +58,8 @@ let SvProjectCompleteTab3Rating = class SvProjectCompleteTab3Rating extends LitE
58
58
  })}
59
59
 
60
60
  <div class="button-line">
61
- <div class="ghost-btn" @click=${() => this._saveAndMove(2)}>이전</div>
62
61
  <div class="ghost-btn" @click=${this._reset}>초기화</div>
63
- <div class="ghost-btn" @click=${() => this._saveAndMove(4)}>저장 & 다음</div>
62
+ <div class="ghost-btn secondary" @click=${() => this._save()}>저장</div>
64
63
  </div>
65
64
  </div>
66
65
  `;
@@ -74,17 +73,17 @@ let SvProjectCompleteTab3Rating = class SvProjectCompleteTab3Rating extends LitE
74
73
  else
75
74
  next.push({ id: itemId, value: score10 });
76
75
  this.data = next;
77
- this.dispatchEvent(new CustomEvent('complete-data-change', { detail: { tab: 3, data: this.data } }));
76
+ this.dispatchEvent(new CustomEvent('complete-data-change', { detail: { tab: 2, data: this.data } }));
78
77
  }
79
- _saveAndMove(toTab) {
80
- this.dispatchEvent(new CustomEvent('complete-tab-change', { detail: { toTab, data: this.data } }));
78
+ _save() {
79
+ this.dispatchEvent(new CustomEvent('complete-tab-save', { detail: { data: this.data } }));
81
80
  }
82
81
  _reset() {
83
82
  this.data = {};
84
- this.dispatchEvent(new CustomEvent('complete-tab-reset', { detail: { tab: 3 }, bubbles: true, composed: true }));
83
+ this.dispatchEvent(new CustomEvent('complete-tab-reset', { detail: { tab: 2 }, bubbles: true, composed: true }));
85
84
  }
86
85
  };
87
- SvProjectCompleteTab3Rating.styles = [
86
+ SvProjectCompleteTab2Rating.styles = [
88
87
  css `
89
88
  :host {
90
89
  display: block;
@@ -211,20 +210,20 @@ SvProjectCompleteTab3Rating.styles = [
211
210
  cursor: pointer;
212
211
  }
213
212
  .ghost-btn.secondary {
214
- background: #7a91ac;
213
+ background: #24be7b;
215
214
  }
216
215
  `
217
216
  ];
218
217
  __decorate([
219
218
  property({ type: Array }),
220
219
  __metadata("design:type", Object)
221
- ], SvProjectCompleteTab3Rating.prototype, "data", void 0);
220
+ ], SvProjectCompleteTab2Rating.prototype, "data", void 0);
222
221
  __decorate([
223
222
  property({ type: Array }),
224
223
  __metadata("design:type", Object)
225
- ], SvProjectCompleteTab3Rating.prototype, "kpiCategories", void 0);
226
- SvProjectCompleteTab3Rating = __decorate([
227
- customElement('sv-pc-tab3-rating')
228
- ], SvProjectCompleteTab3Rating);
229
- export { SvProjectCompleteTab3Rating };
230
- //# sourceMappingURL=pc-tab3-rating.js.map
224
+ ], SvProjectCompleteTab2Rating.prototype, "kpiCategories", void 0);
225
+ SvProjectCompleteTab2Rating = __decorate([
226
+ customElement('sv-pc-tab2-rating')
227
+ ], SvProjectCompleteTab2Rating);
228
+ export { SvProjectCompleteTab2Rating };
229
+ //# sourceMappingURL=pc-tab2-rating.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pc-tab2-rating.js","sourceRoot":"","sources":["../../../client/pages/project-complete-tabs/pc-tab2-rating.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { calcDiff } from '../../shared/func'\n\n@customElement('sv-pc-tab2-rating')\nexport class SvProjectCompleteTab2Rating 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: 300px 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: 300px 1fr 1fr 200px;\n gap: 6px 10px;\n padding: 8px 25px;\n align-items: center;\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 .stars {\n display: inline-flex;\n gap: 6px;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n }\n .star-wrap {\n position: relative;\n width: 28px;\n height: 28px;\n display: inline-block;\n }\n .star-base,\n .star-fill {\n position: absolute;\n top: 0;\n left: 0;\n font-size: 28px;\n line-height: 28px;\n user-select: none;\n }\n .star-base {\n color: #d0d7e2;\n }\n .star-fill {\n color: #ffb400;\n overflow: hidden;\n width: 0%;\n }\n .click-half {\n position: absolute;\n top: 0;\n width: 50%;\n height: 100%;\n }\n .click-half.left {\n left: 0;\n }\n .click-half.right {\n right: 0;\n }\n .score {\n color: #212529;\n text-align: right;\n }\n .plus {\n color: #e13232;\n font-weight: 700;\n }\n .minus {\n color: #1e88e5;\n font-weight: 700;\n }\n .unit {\n text-align: center;\n color: #212529;\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 @property({ type: Array }) data: any = []\n @property({ type: Array }) kpiCategories: 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.kpiCategories.map((item, idx) => {\n const completeScore = this.data.find(v => v.id === item.id)\n const diff = calcDiff(item.value, completeScore?.value)\n const diffClass = diff === 0 ? '' : diff > 0 ? 'plus' : 'minus'\n const diffSign = diff === 0 ? '' : diff > 0 ? '+' : '-'\n\n return html`\n <div class=\"row\">\n <div class=\"label\">• ${item.name}</div>\n <div class=\"cell\">${item?.value || 0}</div>\n <div class=\"stars\" @mouseleave=${() => {}}>\n ${[1, 2, 3, 4, 5].map(starIndex => {\n const score10 = Number(completeScore?.value ?? 0) // 0~10\n const fullUntil = Math.floor(score10 / 2) // 정수 별 개수\n const hasHalf = score10 % 2 === 1\n const fillForThis = starIndex <= fullUntil ? 100 : starIndex === fullUntil + 1 && hasHalf ? 50 : 0\n\n return html`\n <span class=\"star-wrap\">\n <span class=\"star-base\">☆</span>\n <span class=\"star-fill\" style=\"width: ${fillForThis}%;\">★</span>\n <span class=\"click-half left\" @click=${() => this._setRatingHalf(item.id, (starIndex - 1) * 2 + 1)}></span>\n <span class=\"click-half right\" @click=${() => this._setRatingHalf(item.id, starIndex * 2)}></span>\n </span>\n `\n })}\n </div>\n <div class=\"unit ${diffClass}\">${diffSign} ${Math.abs(diff).toLocaleString()}</div>\n </div>\n `\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 _setRatingHalf(itemId: string, score10: number) {\n // score10: 0~10, 0.5 단위\n const next = Array.isArray(this.data) ? [...this.data] : []\n const idx = next.findIndex((v: any) => v.id === itemId)\n if (idx >= 0) next[idx] = { ...next[idx], value: score10 }\n else next.push({ id: itemId, value: score10 })\n\n this.data = next\n this.dispatchEvent(new CustomEvent('complete-data-change', { detail: { tab: 2, data: this.data } }))\n }\n\n private _save() {\n this.dispatchEvent(new CustomEvent('complete-tab-save', { detail: { data: this.data } }))\n }\n\n private _reset() {\n this.data = {}\n this.dispatchEvent(new CustomEvent('complete-tab-reset', { detail: { tab: 2 }, bubbles: true, composed: true }))\n }\n}\n"]}
@@ -1,12 +1,11 @@
1
1
  import { LitElement } from 'lit';
2
- export declare class SvProjectCompleteTab4Upload extends LitElement {
2
+ export declare class SvProjectCompleteTab3Upload extends LitElement {
3
3
  static styles: import("lit").CSSResult[];
4
4
  data: any;
5
5
  project: any;
6
6
  render(): import("lit-html").TemplateResult<1>;
7
- private _saveAndMove;
7
+ private _save;
8
8
  private _reset;
9
- private _submit;
10
9
  private _onFileSelect;
11
10
  private _uploadFiles;
12
11
  private _deleteAttachment;
@@ -3,7 +3,7 @@ import { css, html, LitElement } from 'lit';
3
3
  import { customElement, property } from 'lit/decorators.js';
4
4
  import { client } from '@operato/graphql';
5
5
  import { gql } from '@apollo/client';
6
- let SvProjectCompleteTab4Upload = class SvProjectCompleteTab4Upload extends LitElement {
6
+ let SvProjectCompleteTab3Upload = class SvProjectCompleteTab3Upload extends LitElement {
7
7
  constructor() {
8
8
  super(...arguments);
9
9
  this.data = [];
@@ -37,23 +37,19 @@ let SvProjectCompleteTab4Upload = class SvProjectCompleteTab4Upload extends LitE
37
37
  </div>
38
38
 
39
39
  <div class="button-line">
40
- <div class="ghost-btn" @click=${() => this._saveAndMove(3)}>이전</div>
41
- <div class="ghost-btn" @click=${this._reset}>초기화</div>
42
- <div class="ghost-btn" @click=${() => this._submit()}>저장 & 완공 처리</div>
40
+ <div class="ghost-btn " @click=${this._reset}>초기화</div>
41
+ <div class="ghost-btn secondary" @click=${() => this._save()}>저장</div>
43
42
  </div>
44
43
  </div>
45
44
  `;
46
45
  }
47
- _saveAndMove(toTab) {
48
- this.dispatchEvent(new CustomEvent('complete-tab-change', { detail: { toTab, data: this.data } }));
46
+ _save() {
47
+ this.dispatchEvent(new CustomEvent('complete-tab-save', { detail: { data: this.data } }));
49
48
  }
50
49
  async _reset() {
51
50
  var _a;
52
51
  await ((_a = this.data) === null || _a === void 0 ? void 0 : _a.forEach(attachment => this._deleteAttachment(attachment.id)));
53
- this.dispatchEvent(new CustomEvent('complete-tab-reset', { detail: { tab: 4 } }));
54
- }
55
- _submit() {
56
- this.dispatchEvent(new CustomEvent('complete-submit'));
52
+ this.dispatchEvent(new CustomEvent('complete-tab-reset', { detail: { tab: 3 } }));
57
53
  }
58
54
  async _onFileSelect(e) {
59
55
  const files = e.detail;
@@ -87,7 +83,7 @@ let SvProjectCompleteTab4Upload = class SvProjectCompleteTab4Upload extends LitE
87
83
  if (!response.errors) {
88
84
  const uploaded = response.data.createAttachments;
89
85
  this.data = [...this.data, ...uploaded];
90
- this.dispatchEvent(new CustomEvent('complete-data-change', { detail: { tab: 4, data: this.data } }));
86
+ this.dispatchEvent(new CustomEvent('complete-data-change', { detail: { tab: 3, data: this.data } }));
91
87
  }
92
88
  }
93
89
  async _deleteAttachment(attachmentId) {
@@ -102,11 +98,11 @@ let SvProjectCompleteTab4Upload = class SvProjectCompleteTab4Upload extends LitE
102
98
  });
103
99
  if (!response.errors) {
104
100
  this.data = ((_a = this.data) === null || _a === void 0 ? void 0 : _a.filter(a => a.id !== attachmentId)) || [];
105
- this.dispatchEvent(new CustomEvent('complete-data-change', { detail: { tab: 4, data: this.data } }));
101
+ this.dispatchEvent(new CustomEvent('complete-data-change', { detail: { tab: 3, data: this.data } }));
106
102
  }
107
103
  }
108
104
  };
109
- SvProjectCompleteTab4Upload.styles = [
105
+ SvProjectCompleteTab3Upload.styles = [
110
106
  css `
111
107
  :host {
112
108
  display: block;
@@ -198,20 +194,20 @@ SvProjectCompleteTab4Upload.styles = [
198
194
  cursor: pointer;
199
195
  }
200
196
  .ghost-btn.secondary {
201
- background: #7a91ac;
197
+ background: #24be7b;
202
198
  }
203
199
  `
204
200
  ];
205
201
  __decorate([
206
202
  property({ type: Array }),
207
203
  __metadata("design:type", Object)
208
- ], SvProjectCompleteTab4Upload.prototype, "data", void 0);
204
+ ], SvProjectCompleteTab3Upload.prototype, "data", void 0);
209
205
  __decorate([
210
206
  property({ type: Object }),
211
207
  __metadata("design:type", Object)
212
- ], SvProjectCompleteTab4Upload.prototype, "project", void 0);
213
- SvProjectCompleteTab4Upload = __decorate([
214
- customElement('sv-pc-tab4-upload')
215
- ], SvProjectCompleteTab4Upload);
216
- export { SvProjectCompleteTab4Upload };
217
- //# sourceMappingURL=pc-tab4-upload.js.map
208
+ ], SvProjectCompleteTab3Upload.prototype, "project", void 0);
209
+ SvProjectCompleteTab3Upload = __decorate([
210
+ customElement('sv-pc-tab3-upload')
211
+ ], SvProjectCompleteTab3Upload);
212
+ export { SvProjectCompleteTab3Upload };
213
+ //# 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'\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 position: relative;\n z-index: 1;\n display: flex;\n min-height: 80px;\n justify-content: center;\n flex-direction: column;\n gap: 8px;\n border: 1px solid #eee;\n border-radius: 8px;\n padding: 12px;\n margin-top: 8px;\n }\n\n .attachment-row {\n display: grid;\n grid-template-columns: 1fr auto;\n align-items: center;\n gap: 8px;\n padding: 8px 10px;\n border: 1px solid #ddd;\n border-radius: 6px;\n background: #fafafa;\n }\n\n .attachment-name {\n font-size: 14px;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n }\n\n .empty-state {\n text-align: center;\n color: #666;\n font-style: italic;\n }\n\n .delete-icon {\n cursor: pointer;\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 `\n ]\n\n @property({ type: Array }) data: any = []\n @property({ type: Object }) project: any = {}\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 <div class=\"upload-controls\">\n <ox-input-file accept=\"application/pdf,.pdf\" hide-filelist @change=${this._onFileSelect} multiple=\"true\">\n </ox-input-file>\n </div>\n\n <div class=\"attachment-list\">\n ${!this.data?.length\n ? html`<div class=\"empty-state\">업로드된 파일이 없습니다.</div>`\n : this.data?.map(\n file =>\n html`<div class=\"attachment-row\">\n <div class=\"attachment-name\" title=\"${file.name}\">${file.name}</div>\n <md-icon class=\"delete-icon\" @click=${() => this._deleteAttachment(file.id)}>delete</md-icon>\n </div>`\n )}\n </div>\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 _save() {\n this.dispatchEvent(new CustomEvent('complete-tab-save', { detail: { data: this.data } }))\n }\n\n private async _reset() {\n await this.data?.forEach(attachment => this._deleteAttachment(attachment.id))\n this.dispatchEvent(new CustomEvent('complete-tab-reset', { detail: { tab: 3 } }))\n }\n\n private async _onFileSelect(e: CustomEvent) {\n const files = e.detail as File[]\n await this._uploadFiles(files)\n }\n\n private async _uploadFiles(files: File[]) {\n const response = await client.mutate({\n mutation: gql`\n mutation ($attachments: [NewAttachment!]!) {\n createAttachments(attachments: $attachments) {\n id\n name\n fullpath\n }\n }\n `,\n variables: {\n attachments: files.map(file => ({\n file,\n refBy: this.project?.id,\n refType: 'ProjectCompleteReport'\n }))\n },\n context: {\n hasUpload: true\n }\n })\n\n if (!response.errors) {\n const uploaded = response.data.createAttachments\n this.data = [...this.data, ...uploaded]\n this.dispatchEvent(new CustomEvent('complete-data-change', { detail: { tab: 3, data: this.data } }))\n }\n }\n\n private async _deleteAttachment(attachmentId: string) {\n const response = await client.mutate({\n mutation: gql`\n mutation DeleteAttachment($deleteAttachmentId: String!) {\n deleteAttachment(id: $deleteAttachmentId)\n }\n `,\n variables: { deleteAttachmentId: attachmentId }\n })\n\n if (!response.errors) {\n this.data = this.data?.filter(a => a.id !== attachmentId) || []\n this.dispatchEvent(new CustomEvent('complete-data-change', { detail: { tab: 3, data: this.data } }))\n }\n }\n}\n"]}
@@ -1,9 +1,8 @@
1
1
  import '@material/web/icon/icon.js';
2
2
  import { PageView } from '@operato/shell';
3
3
  import './project-complete-tabs/pc-tab1-plan';
4
- import './project-complete-tabs/pc-tab2-final';
5
- import './project-complete-tabs/pc-tab3-rating';
6
- import './project-complete-tabs/pc-tab4-upload';
4
+ import './project-complete-tabs/pc-tab2-rating';
5
+ import './project-complete-tabs/pc-tab3-upload';
7
6
  declare const SvProjectCompletePage_base: typeof PageView & import("@open-wc/dedupe-mixin").Constructor<import("@open-wc/scoped-elements/types/src/types").ScopedElementsHost>;
8
7
  export declare class SvProjectCompletePage extends SvProjectCompletePage_base {
9
8
  static styles: import("lit").CSSResult[];
@@ -14,13 +13,10 @@ export declare class SvProjectCompletePage extends SvProjectCompletePage_base {
14
13
  private projectId;
15
14
  private project;
16
15
  private kpiCategories;
17
- private completeData;
18
16
  render(): import("lit-html").TemplateResult<1>;
19
17
  pageUpdated(changes: any, lifecycle: any): Promise<void>;
20
18
  initProject(projectId?: string): Promise<void>;
21
- private _onDataChange;
22
- private _onTabMove;
23
- private _onTabReset;
24
- private _onSubmit;
19
+ private _onTabClick;
20
+ private _onComplete;
25
21
  }
26
22
  export {};