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

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 (28) hide show
  1. package/dist-client/components/kpi-boxplot-chart.d.ts +24 -0
  2. package/dist-client/components/kpi-boxplot-chart.js +328 -0
  3. package/dist-client/components/kpi-boxplot-chart.js.map +1 -0
  4. package/dist-client/components/kpi-radar-chart.d.ts +16 -0
  5. package/dist-client/components/kpi-radar-chart.js +139 -0
  6. package/dist-client/components/kpi-radar-chart.js.map +1 -0
  7. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.d.ts +3 -1
  8. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js +25 -12
  9. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js.map +1 -1
  10. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.d.ts +6 -3
  11. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js +101 -92
  12. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js.map +1 -1
  13. package/dist-client/pages/sv-project-complete.d.ts +0 -2
  14. package/dist-client/pages/sv-project-complete.js +14 -20
  15. package/dist-client/pages/sv-project-complete.js.map +1 -1
  16. package/dist-client/pages/sv-project-detail.d.ts +3 -0
  17. package/dist-client/pages/sv-project-detail.js +98 -34
  18. package/dist-client/pages/sv-project-detail.js.map +1 -1
  19. package/dist-client/shared/complete-api.d.ts +4 -1
  20. package/dist-client/shared/complete-api.js +68 -16
  21. package/dist-client/shared/complete-api.js.map +1 -1
  22. package/dist-client/tsconfig.tsbuildinfo +1 -1
  23. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.d.ts +2 -1
  24. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js +28 -20
  25. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js.map +1 -1
  26. package/dist-server/tsconfig.tsbuildinfo +1 -1
  27. package/package.json +3 -3
  28. package/schema.graphql +2 -2
@@ -2,11 +2,13 @@ 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
+ import { getKpiCategories, getKpiMetrics, updateProjectCompleteStep2 } from '../../shared/complete-api';
6
+ import { notify } from '@operato/layout';
5
7
  let SvProjectCompleteTab2Rating = class SvProjectCompleteTab2Rating extends LitElement {
6
8
  constructor() {
7
9
  super(...arguments);
8
- this.data = [];
9
10
  this.kpiCategories = [];
11
+ this.kpiMetrics = [];
10
12
  }
11
13
  render() {
12
14
  return html `
@@ -27,7 +29,7 @@ let SvProjectCompleteTab2Rating = class SvProjectCompleteTab2Rating extends LitE
27
29
  </div>
28
30
 
29
31
  ${this.kpiCategories.map((item, idx) => {
30
- const completeScore = this.data.find(v => v.id === item.id);
32
+ const completeScore = this.kpiMetrics.find(v => v.id === item.id);
31
33
  const diff = calcDiff(item.value, completeScore === null || completeScore === void 0 ? void 0 : completeScore.value);
32
34
  const diffClass = diff === 0 ? '' : diff > 0 ? 'plus' : 'minus';
33
35
  const diffSign = diff === 0 ? '' : diff > 0 ? '+' : '-';
@@ -41,7 +43,7 @@ let SvProjectCompleteTab2Rating = class SvProjectCompleteTab2Rating extends LitE
41
43
  const score10 = Number((_a = completeScore === null || completeScore === void 0 ? void 0 : completeScore.value) !== null && _a !== void 0 ? _a : 0); // 0~10
42
44
  const fullUntil = Math.floor(score10 / 2); // 정수 별 개수
43
45
  const hasHalf = score10 % 2 === 1;
44
- const fillForThis = starIndex <= fullUntil ? 100 : starIndex === fullUntil + 1 && hasHalf ? 50 : 0;
46
+ const fillForThis = starIndex <= fullUntil ? 100 : starIndex === fullUntil + 1 && hasHalf ? 44 : 0;
45
47
  return html `
46
48
  <span class="star-wrap">
47
49
  <span class="star-base">☆</span>
@@ -64,23 +66,34 @@ let SvProjectCompleteTab2Rating = class SvProjectCompleteTab2Rating extends LitE
64
66
  </div>
65
67
  `;
66
68
  }
69
+ connectedCallback() {
70
+ super.connectedCallback();
71
+ this._getInitData();
72
+ }
73
+ async _getInitData() {
74
+ const kpiMetrics = await getKpiMetrics();
75
+ this.kpiMetrics = kpiMetrics.filter(item => !item.name.includes('평가')) || []; // 평가 텍스트가 들어간건 제외
76
+ this.kpiCategories = await getKpiCategories();
77
+ }
67
78
  _setRatingHalf(itemId, score10) {
68
79
  // score10: 0~10, 0.5 단위
69
- const next = Array.isArray(this.data) ? [...this.data] : [];
80
+ const next = Array.isArray(this.kpiMetrics) ? [...this.kpiMetrics] : [];
70
81
  const idx = next.findIndex((v) => v.id === itemId);
71
82
  if (idx >= 0)
72
83
  next[idx] = Object.assign(Object.assign({}, next[idx]), { value: score10 });
73
84
  else
74
85
  next.push({ id: itemId, value: score10 });
75
- this.data = next;
76
- this.dispatchEvent(new CustomEvent('complete-data-change', { detail: { tab: 2, data: this.data } }));
86
+ this.kpiMetrics = next;
87
+ this.dispatchEvent(new CustomEvent('complete-data-change', { detail: { tab: 2, data: this.kpiMetrics } }));
77
88
  }
78
- _save() {
79
- this.dispatchEvent(new CustomEvent('complete-tab-save', { detail: { data: this.data } }));
89
+ async _save() {
90
+ const response = await updateProjectCompleteStep2(this.kpiCategories);
91
+ if (!response.errors) {
92
+ notify({ message: '저장되었습니다.' });
93
+ }
80
94
  }
81
95
  _reset() {
82
- this.data = {};
83
- this.dispatchEvent(new CustomEvent('complete-tab-reset', { detail: { tab: 2 }, bubbles: true, composed: true }));
96
+ this._getInitData();
84
97
  }
85
98
  };
86
99
  SvProjectCompleteTab2Rating.styles = [
@@ -217,11 +230,11 @@ SvProjectCompleteTab2Rating.styles = [
217
230
  __decorate([
218
231
  property({ type: Array }),
219
232
  __metadata("design:type", Object)
220
- ], SvProjectCompleteTab2Rating.prototype, "data", void 0);
233
+ ], SvProjectCompleteTab2Rating.prototype, "kpiCategories", void 0);
221
234
  __decorate([
222
235
  property({ type: Array }),
223
236
  __metadata("design:type", Object)
224
- ], SvProjectCompleteTab2Rating.prototype, "kpiCategories", void 0);
237
+ ], SvProjectCompleteTab2Rating.prototype, "kpiMetrics", void 0);
225
238
  SvProjectCompleteTab2Rating = __decorate([
226
239
  customElement('sv-pc-tab2-rating')
227
240
  ], SvProjectCompleteTab2Rating);
@@ -1 +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
+ {"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'\nimport { getKpiCategories, getKpiMetrics, updateProjectCompleteStep2 } from '../../shared/complete-api'\nimport { notify } from '@operato/layout'\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 }) kpiCategories: any = []\n @property({ type: Array }) kpiMetrics: 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.kpiMetrics.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 ? 44 : 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 connectedCallback() {\n super.connectedCallback()\n this._getInitData()\n }\n\n private async _getInitData() {\n const kpiMetrics = await getKpiMetrics()\n this.kpiMetrics = kpiMetrics.filter(item => !item.name.includes('평가')) || [] // 평가 텍스트가 들어간건 제외\n this.kpiCategories = await getKpiCategories()\n }\n\n private _setRatingHalf(itemId: string, score10: number) {\n // score10: 0~10, 0.5 단위\n const next = Array.isArray(this.kpiMetrics) ? [...this.kpiMetrics] : []\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.kpiMetrics = next\n this.dispatchEvent(new CustomEvent('complete-data-change', { detail: { tab: 2, data: this.kpiMetrics } }))\n }\n\n private async _save() {\n const response = await updateProjectCompleteStep2(this.kpiCategories)\n if (!response.errors) {\n notify({ message: '저장되었습니다.' })\n }\n }\n\n private _reset() {\n this._getInitData()\n }\n}\n"]}
@@ -1,12 +1,15 @@
1
1
  import { LitElement } from 'lit';
2
2
  export declare class SvProjectCompleteTab3Upload extends LitElement {
3
3
  static styles: import("lit").CSSResult[];
4
- data: any;
5
4
  project: any;
5
+ attachment: any;
6
+ private pendingFile;
6
7
  render(): import("lit-html").TemplateResult<1>;
8
+ willUpdate(changedProperties: Map<string, any>): void;
9
+ private _getCurrentFile;
7
10
  private _save;
11
+ private _removeFile;
8
12
  private _reset;
9
13
  private _onFileSelect;
10
- private _uploadFiles;
11
- private _deleteAttachment;
14
+ private _uploadFile;
12
15
  }
@@ -1,13 +1,14 @@
1
1
  import { __decorate, __metadata } from "tslib";
2
2
  import { css, html, LitElement } from 'lit';
3
- import { customElement, property } from 'lit/decorators.js';
4
- import { client } from '@operato/graphql';
5
- import { gql } from '@apollo/client';
3
+ import { customElement, property, state } from 'lit/decorators.js';
4
+ import { getProject, updateProjectCompleteStep3 } from '../../shared/complete-api';
5
+ import { notify } from '@operato/layout';
6
6
  let SvProjectCompleteTab3Upload = class SvProjectCompleteTab3Upload extends LitElement {
7
7
  constructor() {
8
8
  super(...arguments);
9
- this.data = [];
10
9
  this.project = {};
10
+ this.attachment = {};
11
+ this.pendingFile = null;
11
12
  }
12
13
  render() {
13
14
  var _a, _b;
@@ -22,19 +23,21 @@ let SvProjectCompleteTab3Upload = class SvProjectCompleteTab3Upload extends LitE
22
23
  </div>
23
24
 
24
25
  <div class="rows">
25
- <div class="upload-controls">
26
- <ox-input-file accept="application/pdf,.pdf" hide-filelist @change=${this._onFileSelect} multiple="true">
27
- </ox-input-file>
28
- </div>
29
-
30
- <div class="attachment-list">
31
- ${!((_a = this.data) === null || _a === void 0 ? void 0 : _a.length)
32
- ? html `<div class="empty-state">업로드된 파일이 없습니다.</div>`
33
- : (_b = this.data) === null || _b === void 0 ? void 0 : _b.map(file => html `<div class="attachment-row">
34
- <div class="attachment-name" title="${file.name}">${file.name}</div>
35
- <md-icon class="delete-icon" @click=${() => this._deleteAttachment(file.id)}>delete</md-icon>
36
- </div>`)}
37
- </div>
26
+ ${this._getCurrentFile()
27
+ ? html `
28
+ <div class="attachment-list">
29
+ <div class="attachment-row">
30
+ <md-icon class="file-icon">picture_as_pdf</md-icon>
31
+ <div class="attachment-name" title="${(_a = this._getCurrentFile()) === null || _a === void 0 ? void 0 : _a.name}">${(_b = this._getCurrentFile()) === null || _b === void 0 ? void 0 : _b.name}</div>
32
+ <md-icon class="delete-icon" @click=${this._removeFile}>delete</md-icon>
33
+ </div>
34
+ </div>
35
+ `
36
+ : html `
37
+ <div class="upload-controls">
38
+ <ox-input-file accept="application/pdf,.pdf" hide-filelist @change=${this._onFileSelect}> </ox-input-file>
39
+ </div>
40
+ `}
38
41
 
39
42
  <div class="button-line">
40
43
  <div class="ghost-btn " @click=${this._reset}>초기화</div>
@@ -43,62 +46,51 @@ let SvProjectCompleteTab3Upload = class SvProjectCompleteTab3Upload extends LitE
43
46
  </div>
44
47
  `;
45
48
  }
46
- _save() {
47
- this.dispatchEvent(new CustomEvent('complete-tab-save', { detail: { data: this.data } }));
48
- }
49
- async _reset() {
49
+ willUpdate(changedProperties) {
50
50
  var _a;
51
- await ((_a = this.data) === null || _a === void 0 ? void 0 : _a.forEach(attachment => this._deleteAttachment(attachment.id)));
52
- this.dispatchEvent(new CustomEvent('complete-tab-reset', { detail: { tab: 3 } }));
51
+ super.willUpdate(changedProperties);
52
+ // project가 변경되고, project.id가 존재하면 attachment 설정
53
+ if (changedProperties.has('project') && ((_a = this.project) === null || _a === void 0 ? void 0 : _a.id)) {
54
+ this.attachment = this.project.completeReport;
55
+ }
53
56
  }
54
- async _onFileSelect(e) {
55
- const files = e.detail;
56
- await this._uploadFiles(files);
57
+ _getCurrentFile() {
58
+ var _a;
59
+ return this.pendingFile || (((_a = this.attachment) === null || _a === void 0 ? void 0 : _a.id) ? this.attachment : null);
57
60
  }
58
- async _uploadFiles(files) {
59
- const response = await client.mutate({
60
- mutation: gql `
61
- mutation ($attachments: [NewAttachment!]!) {
62
- createAttachments(attachments: $attachments) {
63
- id
64
- name
65
- fullpath
66
- }
61
+ async _save() {
62
+ var _a;
63
+ // 대기 중인 파일이 있으면 업로드
64
+ if (this.pendingFile) {
65
+ await this._uploadFile(this.pendingFile);
66
+ this.pendingFile = null;
67
67
  }
68
- `,
69
- variables: {
70
- attachments: files.map(file => {
71
- var _a;
72
- return ({
73
- file,
74
- refBy: (_a = this.project) === null || _a === void 0 ? void 0 : _a.id,
75
- refType: 'ProjectCompleteReport'
76
- });
77
- })
78
- },
79
- context: {
80
- hasUpload: true
81
- }
82
- });
83
- if (!response.errors) {
84
- const uploaded = response.data.createAttachments;
85
- this.data = [...this.data, ...uploaded];
86
- this.dispatchEvent(new CustomEvent('complete-data-change', { detail: { tab: 3, data: this.data } }));
68
+ else if (!this.pendingFile && ((_a = this.attachment) === null || _a === void 0 ? void 0 : _a.id)) {
69
+ notify({ message: '파일은 필수입니다.' });
87
70
  }
88
71
  }
89
- async _deleteAttachment(attachmentId) {
90
- var _a;
91
- const response = await client.mutate({
92
- mutation: gql `
93
- mutation DeleteAttachment($deleteAttachmentId: String!) {
94
- deleteAttachment(id: $deleteAttachmentId)
72
+ _removeFile() {
73
+ this.pendingFile = null;
74
+ this.attachment = {};
75
+ }
76
+ // 대기 중인 파일, 저장된 파일 초기화
77
+ async _reset() {
78
+ this.pendingFile = null;
79
+ const project = await getProject(this.project.id);
80
+ this.attachment = project.completeReport;
81
+ }
82
+ async _onFileSelect(e) {
83
+ const files = e.detail;
84
+ if (files.length > 0) {
85
+ this.pendingFile = files[0];
95
86
  }
96
- `,
97
- variables: { deleteAttachmentId: attachmentId }
98
- });
87
+ }
88
+ async _uploadFile(file) {
89
+ const response = await updateProjectCompleteStep3(file, this.project.id);
99
90
  if (!response.errors) {
100
- this.data = ((_a = this.data) === null || _a === void 0 ? void 0 : _a.filter(a => a.id !== attachmentId)) || [];
101
- this.dispatchEvent(new CustomEvent('complete-data-change', { detail: { tab: 3, data: this.data } }));
91
+ const uploaded = response.data.updateKpiMetricValuesSentiment;
92
+ this.attachment = uploaded;
93
+ notify({ message: '저장되었습니다.' });
102
94
  }
103
95
  }
104
96
  };
@@ -136,45 +128,58 @@ SvProjectCompleteTab3Upload.styles = [
136
128
  }
137
129
 
138
130
  .attachment-list {
139
- position: relative;
140
- z-index: 1;
141
131
  display: flex;
142
- min-height: 80px;
143
132
  justify-content: center;
144
- flex-direction: column;
145
- gap: 8px;
146
- border: 1px solid #eee;
147
- border-radius: 8px;
148
- padding: 12px;
149
133
  margin-top: 8px;
150
134
  }
151
135
 
152
136
  .attachment-row {
153
- display: grid;
154
- grid-template-columns: 1fr auto;
137
+ display: flex;
138
+ flex-direction: column;
155
139
  align-items: center;
156
- gap: 8px;
157
- padding: 8px 10px;
158
- border: 1px solid #ddd;
159
- border-radius: 6px;
160
- background: #fafafa;
140
+ gap: 16px;
141
+ padding: 24px 20px;
142
+ border: 2px solid #e3f2fd;
143
+ border-radius: 12px;
144
+ background: linear-gradient(135deg, #f8fbff 0%, #e3f2fd 100%);
145
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
146
+ min-width: 300px;
147
+ max-width: 400px;
161
148
  }
162
149
 
163
- .attachment-name {
164
- font-size: 14px;
165
- overflow: hidden;
166
- white-space: nowrap;
167
- text-overflow: ellipsis;
150
+ .file-icon {
151
+ font-size: 40px;
152
+ color: #d32f2f;
153
+ line-height: 1;
154
+ display: flex;
155
+ align-items: center;
156
+ justify-content: center;
157
+ width: auto;
158
+ height: 48px;
168
159
  }
169
160
 
170
- .empty-state {
161
+ .attachment-name {
162
+ font-size: 16px;
163
+ font-weight: 500;
164
+ color: #1976d2;
171
165
  text-align: center;
172
- color: #666;
173
- font-style: italic;
166
+ word-break: break-word;
167
+ line-height: 1.4;
174
168
  }
175
169
 
176
170
  .delete-icon {
177
171
  cursor: pointer;
172
+ color: #757575;
173
+ background: rgba(117, 117, 117, 0.1);
174
+ border-radius: 50%;
175
+ padding: 8px;
176
+ transition: all 0.2s ease;
177
+ }
178
+
179
+ .delete-icon:hover {
180
+ color: #e57373;
181
+ background: rgba(229, 115, 115, 0.15);
182
+ transform: scale(1.1);
178
183
  }
179
184
 
180
185
  .button-line {
@@ -198,14 +203,18 @@ SvProjectCompleteTab3Upload.styles = [
198
203
  }
199
204
  `
200
205
  ];
201
- __decorate([
202
- property({ type: Array }),
203
- __metadata("design:type", Object)
204
- ], SvProjectCompleteTab3Upload.prototype, "data", void 0);
205
206
  __decorate([
206
207
  property({ type: Object }),
207
208
  __metadata("design:type", Object)
208
209
  ], SvProjectCompleteTab3Upload.prototype, "project", void 0);
210
+ __decorate([
211
+ state(),
212
+ __metadata("design:type", Object)
213
+ ], SvProjectCompleteTab3Upload.prototype, "attachment", void 0);
214
+ __decorate([
215
+ state(),
216
+ __metadata("design:type", Object)
217
+ ], SvProjectCompleteTab3Upload.prototype, "pendingFile", void 0);
209
218
  SvProjectCompleteTab3Upload = __decorate([
210
219
  customElement('sv-pc-tab3-upload')
211
220
  ], SvProjectCompleteTab3Upload);
@@ -1 +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
+ {"version":3,"file":"pc-tab3-upload.js","sourceRoot":"","sources":["../../../client/pages/project-complete-tabs/pc-tab3-upload.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { client } from '@operato/graphql'\nimport { gql } from '@apollo/client'\nimport { getProject, updateProjectCompleteStep3 } from '../../shared/complete-api'\nimport { notify } from '@operato/layout'\n\n@customElement('sv-pc-tab3-upload')\nexport class SvProjectCompleteTab3Upload extends LitElement {\n static styles = [\n css`\n :host {\n display: block;\n }\n .title {\n color: #212529;\n font-size: 13px;\n font-weight: 400;\n line-height: 24px;\n text-align: center;\n }\n\n .rows {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 8px 6px;\n }\n\n .upload-controls {\n display: flex;\n gap: 8px;\n justify-content: center;\n margin-bottom: 16px;\n\n ox-input-file {\n flex: 1;\n max-width: 500px;\n height: 210px;\n }\n }\n\n .attachment-list {\n display: flex;\n justify-content: center;\n margin-top: 8px;\n }\n\n .attachment-row {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 16px;\n padding: 24px 20px;\n border: 2px solid #e3f2fd;\n border-radius: 12px;\n background: linear-gradient(135deg, #f8fbff 0%, #e3f2fd 100%);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n min-width: 300px;\n max-width: 400px;\n }\n\n .file-icon {\n font-size: 40px;\n color: #d32f2f;\n line-height: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n width: auto;\n height: 48px;\n }\n\n .attachment-name {\n font-size: 16px;\n font-weight: 500;\n color: #1976d2;\n text-align: center;\n word-break: break-word;\n line-height: 1.4;\n }\n\n .delete-icon {\n cursor: pointer;\n color: #757575;\n background: rgba(117, 117, 117, 0.1);\n border-radius: 50%;\n padding: 8px;\n transition: all 0.2s ease;\n }\n\n .delete-icon:hover {\n color: #e57373;\n background: rgba(229, 115, 115, 0.15);\n transform: scale(1.1);\n }\n\n .button-line {\n display: flex;\n justify-content: center;\n gap: 10px;\n margin-top: 16px;\n }\n .ghost-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 10px;\n background: #35618e;\n color: #ffffff;\n border-radius: 5px;\n cursor: pointer;\n }\n .ghost-btn.secondary {\n background: #24be7b;\n }\n `\n ]\n\n @property({ type: Object }) project: any = {}\n @state() attachment: any = {}\n @state() private pendingFile: File | null = null\n\n render() {\n return html`\n <div class=\"title\">\n <div>\n 감리 최종 감리보고서의 종합의견서(PDF 형식)를 시스템에 업로드해 주세요.\n <br />\n 보고서에 포함된 텍스트와 평가 내용을 기반으로 기계 판독 및 AI 기반 프로젝트 평가가 자동으로 진행되므로, 반드시 최종\n 확정된 원본 파일을 제출해 주시기 바랍니다.\n </div>\n </div>\n\n <div class=\"rows\">\n ${this._getCurrentFile()\n ? html`\n <div class=\"attachment-list\">\n <div class=\"attachment-row\">\n <md-icon class=\"file-icon\">picture_as_pdf</md-icon>\n <div class=\"attachment-name\" title=\"${this._getCurrentFile()?.name}\">${this._getCurrentFile()?.name}</div>\n <md-icon class=\"delete-icon\" @click=${this._removeFile}>delete</md-icon>\n </div>\n </div>\n `\n : html`\n <div class=\"upload-controls\">\n <ox-input-file accept=\"application/pdf,.pdf\" hide-filelist @change=${this._onFileSelect}> </ox-input-file>\n </div>\n `}\n\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가 존재하면 attachment 설정\n if (changedProperties.has('project') && this.project?.id) {\n this.attachment = this.project.completeReport\n }\n }\n\n private _getCurrentFile() {\n return this.pendingFile || (this.attachment?.id ? this.attachment : null)\n }\n\n private async _save() {\n // 대기 중인 파일이 있으면 업로드\n if (this.pendingFile) {\n await this._uploadFile(this.pendingFile)\n this.pendingFile = null\n } else if (!this.pendingFile && this.attachment?.id) {\n notify({ message: '파일은 필수입니다.' })\n }\n }\n\n private _removeFile() {\n this.pendingFile = null\n this.attachment = {}\n }\n\n // 대기 중인 파일, 저장된 파일 초기화\n private async _reset() {\n this.pendingFile = null\n const project = await getProject(this.project.id)\n this.attachment = project.completeReport\n }\n\n private async _onFileSelect(e: CustomEvent) {\n const files = e.detail as File[]\n if (files.length > 0) {\n this.pendingFile = files[0]\n }\n }\n\n private async _uploadFile(file: File | null) {\n const response = await updateProjectCompleteStep3(file, this.project.id)\n if (!response.errors) {\n const uploaded = response.data.updateKpiMetricValuesSentiment\n this.attachment = uploaded\n notify({ message: '저장되었습니다.' })\n }\n }\n}\n"]}
@@ -12,10 +12,8 @@ export declare class SvProjectCompletePage extends SvProjectCompletePage_base {
12
12
  private activeTab;
13
13
  private projectId;
14
14
  private project;
15
- private kpiCategories;
16
15
  render(): import("lit-html").TemplateResult<1>;
17
16
  pageUpdated(changes: any, lifecycle: any): Promise<void>;
18
- initProject(projectId?: string): Promise<void>;
19
17
  private _onTabClick;
20
18
  private _onComplete;
21
19
  }
@@ -7,26 +7,30 @@ import { ScopedElementsMixin } from '@open-wc/scoped-elements';
7
7
  import './project-complete-tabs/pc-tab1-plan';
8
8
  import './project-complete-tabs/pc-tab2-rating';
9
9
  import './project-complete-tabs/pc-tab3-upload';
10
- import { getProject, getKpiCategories } from '../shared/complete-api';
10
+ import { getProject, updateProjectCompleteFinalize } from '../shared/complete-api';
11
11
  import { notify } from '@operato/layout';
12
+ import { OxPrompt } from '@operato/popup';
12
13
  let SvProjectCompletePage = class SvProjectCompletePage extends ScopedElementsMixin(PageView) {
13
14
  constructor() {
14
15
  super(...arguments);
15
16
  this.activeTab = 1;
16
17
  this.projectId = '';
17
18
  this.project = {};
18
- this.kpiCategories = [];
19
+ // 클릭
19
20
  this._onTabClick = async (tabNumber) => {
20
21
  if (tabNumber === this.activeTab)
21
22
  return;
22
23
  this.activeTab = tabNumber;
23
24
  };
24
- this._onComplete = () => {
25
- console.log('완공 처리 요청', {
26
- projectId: this.projectId
27
- });
28
- // TODO: 실제 완공 처리 API 호출 구현 필요
29
- notify({ message: '완공 처리가 요청되었습니다.', level: 'info' });
25
+ // 완공 처리
26
+ this._onComplete = async () => {
27
+ if (await OxPrompt.open({ title: '완공 처리를 하시겠습니까?', confirmButton: { text: '확인' }, cancelButton: { text: '취소' } })) {
28
+ const result = await updateProjectCompleteFinalize(this.projectId);
29
+ if (!result.errors) {
30
+ notify({ message: '완공 처리가 완료되었습니다.', level: 'info' });
31
+ navigate(`project-detail/${this.projectId}`);
32
+ }
33
+ }
30
34
  };
31
35
  }
32
36
  get context() {
@@ -63,7 +67,7 @@ let SvProjectCompletePage = class SvProjectCompletePage extends ScopedElementsMi
63
67
  ${this.activeTab === 1
64
68
  ? html `<sv-pc-tab1-plan .project=${this.project}></sv-pc-tab1-plan>`
65
69
  : this.activeTab === 2
66
- ? html `<sv-pc-tab2-rating .kpiCategories=${this.kpiCategories}></sv-pc-tab2-rating>`
70
+ ? html `<sv-pc-tab2-rating></sv-pc-tab2-rating>`
67
71
  : html `<sv-pc-tab3-upload .project=${this.project}></sv-pc-tab3-upload>`}
68
72
  </div>
69
73
  </div>
@@ -72,15 +76,9 @@ let SvProjectCompletePage = class SvProjectCompletePage extends ScopedElementsMi
72
76
  async pageUpdated(changes, lifecycle) {
73
77
  if (this.active) {
74
78
  this.projectId = lifecycle.resourceId || '';
75
- await this.initProject(this.projectId);
79
+ this.project = await getProject(this.projectId); // View용 데이터
76
80
  }
77
81
  }
78
- async initProject(projectId = '') {
79
- const project = await getProject(projectId);
80
- const kpiCategories = await getKpiCategories();
81
- this.project = project; // View용 데이터
82
- this.kpiCategories = kpiCategories; // View용 데이터
83
- }
84
82
  };
85
83
  SvProjectCompletePage.styles = [
86
84
  css `
@@ -202,10 +200,6 @@ __decorate([
202
200
  state(),
203
201
  __metadata("design:type", Object)
204
202
  ], SvProjectCompletePage.prototype, "project", void 0);
205
- __decorate([
206
- state(),
207
- __metadata("design:type", Array)
208
- ], SvProjectCompletePage.prototype, "kpiCategories", void 0);
209
203
  SvProjectCompletePage = __decorate([
210
204
  customElement('sv-project-complete')
211
205
  ], SvProjectCompletePage);
@@ -1 +1 @@
1
- {"version":3,"file":"sv-project-complete.js","sourceRoot":"","sources":["../../client/pages/sv-project-complete.ts"],"names":[],"mappings":";AAAA,OAAO,4BAA4B,CAAA;AAEnC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAA;AAC/B,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAE9D,OAAO,sCAAsC,CAAA;AAC7C,OAAO,wCAAwC,CAAA;AAC/C,OAAO,wCAAwC,CAAA;AAC/C,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AACrE,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAGjC,IAAM,qBAAqB,GAA3B,MAAM,qBAAsB,SAAQ,mBAAmB,CAAC,QAAQ,CAAC;IAAjE;;QAoHY,cAAS,GAAW,CAAC,CAAA;QACrB,cAAS,GAAW,EAAE,CAAA;QACtB,YAAO,GAAQ,EAAE,CAAA;QACjB,kBAAa,GAAU,EAAE,CAAA;QAqDlC,gBAAW,GAAG,KAAK,EAAE,SAAiB,EAAE,EAAE;YAChD,IAAI,SAAS,KAAK,IAAI,CAAC,SAAS;gBAAE,OAAM;YACxC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;QAC5B,CAAC,CAAA;QAEO,gBAAW,GAAG,GAAG,EAAE;YACzB,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE;gBACtB,SAAS,EAAE,IAAI,CAAC,SAAS;aAC1B,CAAC,CAAA;YACF,8BAA8B;YAC9B,MAAM,CAAC,EAAE,OAAO,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;QACvD,CAAC,CAAA;IACH,CAAC;IA1EC,IAAI,OAAO;QACT,OAAO;YACL,KAAK,EAAE,YAAY;SACpB,CAAA;IACH,CAAC;IAOD,MAAM;QACJ,OAAO,IAAI,CAAA;;;;;wCAKyB,GAAG,EAAE,CAAC,QAAQ,CAAC,kBAAkB,IAAI,CAAC,SAAS,EAAE,CAAC;;;;iDAIzC,IAAI,CAAC,WAAW;;;;;;;;qCAQ5B,IAAI,CAAC,SAAS,KAAK,CAAC,WAAW,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;;;qCAGxD,IAAI,CAAC,SAAS,KAAK,CAAC,WAAW,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;qCACxD,IAAI,CAAC,SAAS,KAAK,CAAC,WAAW,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;;;;YAIjF,IAAI,CAAC,SAAS,KAAK,CAAC;YACpB,CAAC,CAAC,IAAI,CAAA,6BAA6B,IAAI,CAAC,OAAO,qBAAqB;YACpE,CAAC,CAAC,IAAI,CAAC,SAAS,KAAK,CAAC;gBACpB,CAAC,CAAC,IAAI,CAAA,qCAAqC,IAAI,CAAC,aAAa,uBAAuB;gBACpF,CAAC,CAAC,IAAI,CAAA,+BAA+B,IAAI,CAAC,OAAO,uBAAuB;;;KAGjF,CAAA;IACH,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,OAAY,EAAE,SAAc;QAC5C,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC,UAAU,IAAI,EAAE,CAAA;YAC3C,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QACxC,CAAC;IACH,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,YAAoB,EAAE;QACtC,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,SAAS,CAAC,CAAA;QAC3C,MAAM,aAAa,GAAG,MAAM,gBAAgB,EAAE,CAAA;QAE9C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA,CAAC,YAAY;QACnC,IAAI,CAAC,aAAa,GAAG,aAAa,CAAA,CAAC,YAAY;IACjD,CAAC;;AAzKM,4BAAM,GAAG;IACd,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAyGF;CACF,AA3GY,CA2GZ;AAQgB;IAAhB,KAAK,EAAE;;wDAA8B;AACrB;IAAhB,KAAK,EAAE;;wDAA+B;AACtB;IAAhB,KAAK,EAAE;;sDAA0B;AACjB;IAAhB,KAAK,EAAE;;4DAAkC;AAvH/B,qBAAqB;IADjC,aAAa,CAAC,qBAAqB,CAAC;GACxB,qBAAqB,CAwLjC","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport { navigate, PageView } from '@operato/shell'\nimport { css, html } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\nimport { ScopedElementsMixin } from '@open-wc/scoped-elements'\n\nimport './project-complete-tabs/pc-tab1-plan'\nimport './project-complete-tabs/pc-tab2-rating'\nimport './project-complete-tabs/pc-tab3-upload'\nimport { getProject, getKpiCategories } from '../shared/complete-api'\nimport { notify } from '@operato/layout'\n\n@customElement('sv-project-complete')\nexport class SvProjectCompletePage extends ScopedElementsMixin(PageView) {\n static styles = [\n css`\n :host {\n display: flex;\n flex-direction: column;\n overflow-y: auto;\n\n width: 100%;\n height: 100%;\n background-color: var(--md-sys-color-background, #f6f6f6);\n }\n\n .page-header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 16px 20px 8px 20px;\n }\n .page-title {\n color: #35618e;\n font-weight: 700;\n font-size: 22px;\n letter-spacing: -0.05em;\n }\n .triangle {\n width: 0;\n height: 0;\n border-left: 6px solid transparent;\n border-right: 6px solid transparent;\n border-top: 8px solid #35618e;\n }\n\n .card {\n background: #ffffff;\n border-radius: 8px;\n padding: 12px;\n box-shadow: 2px 2px 2px 0 rgba(0, 0, 0, 0.08);\n margin: 0 20px 20px 20px;\n }\n\n .tabs {\n display: flex;\n justify-content: center;\n padding: 0 12px 8px 12px;\n border-bottom: 1px dashed rgba(0, 0, 0, 0.15);\n }\n .tab {\n display: inline-flex;\n align-items: center;\n padding: 6px 14px;\n border: 1px solid rgba(0, 0, 0, 0.12);\n color: #35618e;\n background: #f3f6f9;\n cursor: pointer;\n font-size: 14px;\n letter-spacing: -0.02em;\n }\n .tab:first-child {\n border-top-left-radius: 10px;\n border-bottom-left-radius: 10px;\n }\n .tab:last-child {\n border-top-right-radius: 10px;\n border-bottom-right-radius: 10px;\n }\n /* 중간 탭 이중 보더 방지: 좌측 보더 제거 */\n .tab + .tab {\n border-left: 0;\n }\n .tab[active] {\n background: #02a8a2;\n color: #ffffff;\n border-color: rgba(148, 163, 184, 0.5);\n font-weight: 700;\n }\n\n .tab-body {\n padding: 12px 8px 4px 8px;\n }\n\n .button-line {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin: 0 20px 10px 20px;\n }\n .spacer {\n flex: 1;\n }\n .ghost-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 7px 12px;\n background: #35618e;\n color: #ffffff;\n border-radius: 5px;\n box-shadow: 2px 2px 2px 0 rgba(0, 0, 0, 0.1);\n cursor: pointer;\n }\n .ghost-btn.secondary {\n background: #3395f1;\n }\n .ghost-btn.complete {\n background: #16a085;\n }\n `\n ]\n\n get context() {\n return {\n title: '프로젝트 완공 처리'\n }\n }\n\n @state() private activeTab: number = 1\n @state() private projectId: string = ''\n @state() private project: any = {}\n @state() private kpiCategories: any[] = []\n\n render() {\n return html`\n <div class=\"page-header\">\n <div class=\"page-title\">프로젝트 완공 처리</div>\n <div class=\"triangle\"></div>\n <span class=\"spacer\"></span>\n <div class=\"ghost-btn\" @click=${() => navigate(`project-detail/${this.projectId}`)}>\n <md-icon>arrow_back</md-icon>\n <div>상세로 돌아가기</div>\n </div>\n <div class=\"ghost-btn complete\" @click=${this._onComplete}>\n <md-icon>check_circle</md-icon>\n <div>완공 처리</div>\n </div>\n </div>\n\n <div class=\"card\">\n <div class=\"tabs\">\n <div class=\"tab\" ?active=${this.activeTab === 1} @click=${() => this._onTabClick(1)}>\n Step1. 프로젝트 기본정보 현행화\n </div>\n <div class=\"tab\" ?active=${this.activeTab === 2} @click=${() => this._onTabClick(2)}>Step2. 프로젝트 완료 평가</div>\n <div class=\"tab\" ?active=${this.activeTab === 3} @click=${() => this._onTabClick(3)}>Step3. 준공 문서 업로드</div>\n </div>\n\n <div class=\"tab-body\">\n ${this.activeTab === 1\n ? html`<sv-pc-tab1-plan .project=${this.project}></sv-pc-tab1-plan>`\n : this.activeTab === 2\n ? html`<sv-pc-tab2-rating .kpiCategories=${this.kpiCategories}></sv-pc-tab2-rating>`\n : html`<sv-pc-tab3-upload .project=${this.project}></sv-pc-tab3-upload>`}\n </div>\n </div>\n `\n }\n\n async pageUpdated(changes: any, lifecycle: any) {\n if (this.active) {\n this.projectId = lifecycle.resourceId || ''\n await this.initProject(this.projectId)\n }\n }\n\n async initProject(projectId: string = '') {\n const project = await getProject(projectId)\n const kpiCategories = await getKpiCategories()\n\n this.project = project // View용 데이터\n this.kpiCategories = kpiCategories // View용 데이터\n }\n\n private _onTabClick = async (tabNumber: number) => {\n if (tabNumber === this.activeTab) return\n this.activeTab = tabNumber\n }\n\n private _onComplete = () => {\n console.log('완공 처리 요청', {\n projectId: this.projectId\n })\n // TODO: 실제 완공 처리 API 호출 구현 필요\n notify({ message: '완공 처리가 요청되었습니다.', level: 'info' })\n }\n}\n"]}
1
+ {"version":3,"file":"sv-project-complete.js","sourceRoot":"","sources":["../../client/pages/sv-project-complete.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport { navigate, PageView } from '@operato/shell'\nimport { css, html } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\nimport { ScopedElementsMixin } from '@open-wc/scoped-elements'\n\nimport './project-complete-tabs/pc-tab1-plan'\nimport './project-complete-tabs/pc-tab2-rating'\nimport './project-complete-tabs/pc-tab3-upload'\nimport { getProject, getKpiCategories, updateProjectCompleteFinalize } from '../shared/complete-api'\nimport { notify } from '@operato/layout'\nimport { OxPrompt } from '@operato/popup'\n\n@customElement('sv-project-complete')\nexport class SvProjectCompletePage extends ScopedElementsMixin(PageView) {\n static styles = [\n css`\n :host {\n display: flex;\n flex-direction: column;\n overflow-y: auto;\n\n width: 100%;\n height: 100%;\n background-color: var(--md-sys-color-background, #f6f6f6);\n }\n\n .page-header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 16px 20px 8px 20px;\n }\n .page-title {\n color: #35618e;\n font-weight: 700;\n font-size: 22px;\n letter-spacing: -0.05em;\n }\n .triangle {\n width: 0;\n height: 0;\n border-left: 6px solid transparent;\n border-right: 6px solid transparent;\n border-top: 8px solid #35618e;\n }\n\n .card {\n background: #ffffff;\n border-radius: 8px;\n padding: 12px;\n box-shadow: 2px 2px 2px 0 rgba(0, 0, 0, 0.08);\n margin: 0 20px 20px 20px;\n }\n\n .tabs {\n display: flex;\n justify-content: center;\n padding: 0 12px 8px 12px;\n border-bottom: 1px dashed rgba(0, 0, 0, 0.15);\n }\n .tab {\n display: inline-flex;\n align-items: center;\n padding: 6px 14px;\n border: 1px solid rgba(0, 0, 0, 0.12);\n color: #35618e;\n background: #f3f6f9;\n cursor: pointer;\n font-size: 14px;\n letter-spacing: -0.02em;\n }\n .tab:first-child {\n border-top-left-radius: 10px;\n border-bottom-left-radius: 10px;\n }\n .tab:last-child {\n border-top-right-radius: 10px;\n border-bottom-right-radius: 10px;\n }\n /* 중간 탭 이중 보더 방지: 좌측 보더 제거 */\n .tab + .tab {\n border-left: 0;\n }\n .tab[active] {\n background: #02a8a2;\n color: #ffffff;\n border-color: rgba(148, 163, 184, 0.5);\n font-weight: 700;\n }\n\n .tab-body {\n padding: 12px 8px 4px 8px;\n }\n\n .button-line {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin: 0 20px 10px 20px;\n }\n .spacer {\n flex: 1;\n }\n .ghost-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 7px 12px;\n background: #35618e;\n color: #ffffff;\n border-radius: 5px;\n box-shadow: 2px 2px 2px 0 rgba(0, 0, 0, 0.1);\n cursor: pointer;\n }\n .ghost-btn.secondary {\n background: #3395f1;\n }\n .ghost-btn.complete {\n background: #16a085;\n }\n `\n ]\n\n get context() {\n return {\n title: '프로젝트 완공 처리'\n }\n }\n\n @state() private activeTab: number = 1\n @state() private projectId: string = ''\n @state() private project: any = {}\n\n render() {\n return html`\n <div class=\"page-header\">\n <div class=\"page-title\">프로젝트 완공 처리</div>\n <div class=\"triangle\"></div>\n <span class=\"spacer\"></span>\n <div class=\"ghost-btn\" @click=${() => navigate(`project-detail/${this.projectId}`)}>\n <md-icon>arrow_back</md-icon>\n <div>상세로 돌아가기</div>\n </div>\n <div class=\"ghost-btn complete\" @click=${this._onComplete}>\n <md-icon>check_circle</md-icon>\n <div>완공 처리</div>\n </div>\n </div>\n\n <div class=\"card\">\n <div class=\"tabs\">\n <div class=\"tab\" ?active=${this.activeTab === 1} @click=${() => this._onTabClick(1)}>\n Step1. 프로젝트 기본정보 현행화\n </div>\n <div class=\"tab\" ?active=${this.activeTab === 2} @click=${() => this._onTabClick(2)}>Step2. 프로젝트 완료 평가</div>\n <div class=\"tab\" ?active=${this.activeTab === 3} @click=${() => this._onTabClick(3)}>Step3. 준공 문서 업로드</div>\n </div>\n\n <div class=\"tab-body\">\n ${this.activeTab === 1\n ? html`<sv-pc-tab1-plan .project=${this.project}></sv-pc-tab1-plan>`\n : this.activeTab === 2\n ? html`<sv-pc-tab2-rating></sv-pc-tab2-rating>`\n : html`<sv-pc-tab3-upload .project=${this.project}></sv-pc-tab3-upload>`}\n </div>\n </div>\n `\n }\n\n async pageUpdated(changes: any, lifecycle: any) {\n if (this.active) {\n this.projectId = lifecycle.resourceId || ''\n\n this.project = await getProject(this.projectId) // View용 데이터\n }\n }\n\n // 탭 클릭\n private _onTabClick = async (tabNumber: number) => {\n if (tabNumber === this.activeTab) return\n this.activeTab = tabNumber\n }\n\n // 완공 처리\n private _onComplete = async () => {\n if (\n await OxPrompt.open({ title: '완공 처리를 하시겠습니까?', confirmButton: { text: '확인' }, cancelButton: { text: '취소' } })\n ) {\n const result = await updateProjectCompleteFinalize(this.projectId)\n if (!result.errors) {\n notify({ message: '완공 처리가 완료되었습니다.', level: 'info' })\n navigate(`project-detail/${this.projectId}`)\n }\n }\n }\n}\n"]}
@@ -1,4 +1,7 @@
1
1
  import { PageLifecycle, PageView } from '@operato/shell';
2
+ import '../components/kpi-boxplot-chart';
3
+ import '../components/kpi-single-boxplot-chart';
4
+ import '../components/kpi-radar-chart';
2
5
  declare const SvProjectDetailPage_base: typeof PageView & import("@open-wc/dedupe-mixin").Constructor<import("@open-wc/scoped-elements/types/src/types").ScopedElementsHost>;
3
6
  export declare class SvProjectDetailPage extends SvProjectDetailPage_base {
4
7
  static styles: import("lit").CSSResult[];