@dssp/dkpi 1.0.27 → 1.0.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.d.ts +2 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js +19 -4
- package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js.map +1 -1
- package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.d.ts +2 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js +19 -4
- package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js.map +1 -1
- package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.d.ts +2 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js +19 -4
- package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js.map +1 -1
- package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.d.ts +2 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js +78 -4
- package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js.map +1 -1
- package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.d.ts +2 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js +35 -4
- package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js.map +1 -1
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.d.ts +2 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +29 -1
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -1
- package/dist-client/pages/kpi-dashboard/kpi-dashboard.d.ts +11 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard.js +65 -12
- package/dist-client/pages/kpi-dashboard/kpi-dashboard.js.map +1 -1
- package/dist-client/pages/project-complete-tabs/pc-tab1-plan.d.ts +18 -0
- package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js +138 -64
- package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js.map +1 -1
- package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js +80 -8
- package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js.map +1 -1
- package/dist-client/pages/sv-project-detail.d.ts +33 -0
- package/dist-client/pages/sv-project-detail.js +130 -8
- package/dist-client/pages/sv-project-detail.js.map +1 -1
- package/dist-client/pages/sv-project-update.js +98 -31
- package/dist-client/pages/sv-project-update.js.map +1 -1
- package/dist-client/shared/complete-api.d.ts +7 -0
- package/dist-client/shared/complete-api.js +27 -0
- package/dist-client/shared/complete-api.js.map +1 -1
- package/dist-client/shared/project-axis.d.ts +23 -0
- package/dist-client/shared/project-axis.js +42 -0
- package/dist-client/shared/project-axis.js.map +1 -0
- package/dist-client/shared/sentiment-index.d.ts +47 -0
- package/dist-client/shared/sentiment-index.js +119 -0
- package/dist-client/shared/sentiment-index.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/index.js +14 -0
- package/dist-server/index.js.map +1 -1
- package/dist-server/scripts/load-grade-data-migration.js +17 -3
- package/dist-server/scripts/load-grade-data-migration.js.map +1 -1
- package/dist-server/scripts/propagate-parent-kpi-values.js +13 -2
- package/dist-server/scripts/propagate-parent-kpi-values.js.map +1 -1
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.d.ts +22 -0
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js +93 -1
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js.map +1 -1
- package/dist-server/service/kpi-stat/kpi-stat-query.d.ts +9 -9
- package/dist-server/service/kpi-stat/kpi-stat-query.js +71 -84
- package/dist-server/service/kpi-stat/kpi-stat-query.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/schema.graphql +2011 -67
|
@@ -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'\nimport { getProject, updateProjectCompleteStep3, getKpiMetrics, getKpiMetricValues } from '../../shared/complete-api'\nimport { notify } from '@operato/layout'\nimport { hasPrivilege } from '@things-factory/auth-base/dist-client'\n\n@customElement('sv-pc-tab3-upload')\nexport class SvProjectCompleteTab3Upload extends LitElement {\n static styles = [\n css`\n :host {\n display: block;\n }\n .title {\n color: #212529;\n font-size: 13px;\n font-weight: 400;\n line-height: 24px;\n text-align: center;\n }\n\n .rows {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 8px 6px;\n }\n\n .upload-controls {\n display: flex;\n gap: 8px;\n justify-content: center;\n margin-bottom: 16px;\n\n ox-input-file {\n flex: 1;\n max-width: 500px;\n height: 210px;\n }\n }\n\n .attachment-list {\n display: flex;\n justify-content: center;\n margin-top: 8px;\n }\n\n .attachment-row {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 16px;\n padding: 24px 20px;\n border: 2px solid #e3f2fd;\n border-radius: 12px;\n background: linear-gradient(135deg, #f8fbff 0%, #e3f2fd 100%);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n min-width: 300px;\n max-width: 400px;\n }\n\n .file-icon {\n font-size: 40px;\n color: #d32f2f;\n line-height: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n width: auto;\n height: 48px;\n }\n\n .attachment-name {\n font-size: 16px;\n font-weight: 500;\n color: #1976d2;\n text-align: center;\n word-break: break-word;\n line-height: 1.4;\n }\n\n .delete-icon {\n cursor: pointer;\n color: #757575;\n background: rgba(117, 117, 117, 0.1);\n border-radius: 50%;\n padding: 8px;\n transition: all 0.2s ease;\n }\n\n .delete-icon:hover {\n color: #e57373;\n background: rgba(229, 115, 115, 0.15);\n transform: scale(1.1);\n }\n\n .button-line {\n display: flex;\n justify-content: center;\n gap: 10px;\n margin-top: 16px;\n }\n .ghost-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 10px;\n background: #35618e;\n color: #ffffff;\n border-radius: 5px;\n cursor: pointer;\n }\n .ghost-btn.secondary {\n background: #24be7b;\n }\n .ghost-btn.disabled {\n opacity: 0.45;\n cursor: not-allowed;\n }\n\n .slpa-results {\n margin-top: 20px;\n padding: 16px;\n border: 1px solid #e0e0e0;\n border-radius: 8px;\n background: #f8f9fa;\n }\n\n .slpa-title {\n font-size: 16px;\n font-weight: 600;\n color: #35618e;\n margin-bottom: 12px;\n }\n\n .slpa-item {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 8px 0;\n border-bottom: 1px solid #e0e0e0;\n }\n\n .slpa-item:last-child {\n border-bottom: none;\n }\n\n .slpa-label {\n font-weight: 500;\n color: #333;\n }\n\n .slpa-value {\n font-weight: 600;\n color: #1976d2;\n }\n `\n ]\n\n @property({ type: Object }) project: any = {}\n @state() attachment: any = {}\n @state() private pendingFile: File | null = null\n @state() slpaData: any[] = []\n /** kpi:sentiment — Step3 보고서 첨부/저장 권한 */\n @state() canSave = false\n\n async connectedCallback() {\n super.connectedCallback()\n this.canSave = await hasPrivilege({\n category: 'kpi',\n privilege: 'sentiment',\n domainOwnerGranted: true,\n superUserGranted: true\n })\n }\n\n render() {\n return html`\n <div class=\"title\">\n <div>\n 감리 최종 감리보고서의 종합의견서(PDF 형식)를 시스템에 업로드해 주세요.\n <br />\n 보고서에 포함된 텍스트와 평가 내용을 기반으로 기계 판독 및 AI 기반 프로젝트 평가가 자동으로 진행되므로, 반드시 최종\n 확정된 원본 파일을 제출해 주시기 바랍니다.\n </div>\n </div>\n\n <div class=\"rows\">\n ${this._getCurrentFile()\n ? html`\n <div class=\"attachment-list\">\n <div class=\"attachment-row\">\n <md-icon class=\"file-icon\">picture_as_pdf</md-icon>\n <div class=\"attachment-name\" title=\"${this._getCurrentFile()?.name}\">${this._getCurrentFile()?.name}</div>\n <md-icon class=\"delete-icon\" @click=${this._removeFile}>delete</md-icon>\n </div>\n </div>\n `\n : html`\n <div class=\"upload-controls\">\n <ox-input-file accept=\"application/pdf,.pdf\" hide-filelist @change=${this._onFileSelect}> </ox-input-file>\n </div>\n `}\n ${this.slpaData.length > 0\n ? html`\n <div class=\"slpa-results\">\n <div class=\"slpa-title\">AI 분석 결과 (SL-PA 관련 항목)</div>\n ${this.slpaData.map(\n item => html`\n <div class=\"slpa-item\">\n <div class=\"slpa-label\">${item.name}</div>\n <div class=\"slpa-value\">${item.value.toFixed(2)} ${item.unit || ''}</div>\n </div>\n `\n )}\n </div>\n `\n : ''}\n\n <div class=\"button-line\">\n <div class=\"ghost-btn \" @click=${this._reset}>초기화</div>\n <div\n class=\"ghost-btn secondary ${this.canSave ? '' : 'disabled'}\"\n title=${this.canSave ? '' : 'kpi:sentiment 권한 필요'}\n @click=${() => this.canSave && this._save()}\n >\n 저장\n </div>\n </div>\n </div>\n `\n }\n\n willUpdate(changedProperties: Map<string, any>) {\n super.willUpdate(changedProperties)\n\n // project가 변경되고, project.id가 존재하면 데이터 로드\n if (changedProperties.has('project') && this.project?.id) {\n this._getProjectData()\n }\n }\n\n private _getCurrentFile() {\n return this.pendingFile || (this.attachment?.id ? this.attachment : null)\n }\n\n private async _save() {\n // 대기 중인 파일이 있으면 업로드\n if (this.pendingFile) {\n await this._uploadFile(this.pendingFile)\n this.pendingFile = null\n } else if (!this.pendingFile && !this.attachment?.id) {\n notify({ message: '파일은 필수입니다.' })\n }\n }\n\n private _removeFile() {\n this.pendingFile = null\n this.attachment = {}\n this.slpaData = [] // SL-PA 데이터도 초기화\n }\n\n private async _onFileSelect(e: CustomEvent) {\n const files = e.detail as File[]\n if (files.length > 0) {\n this.pendingFile = files[0]\n }\n }\n\n private async _uploadFile(file: File | null) {\n const response = await updateProjectCompleteStep3(file, this.project.id, this.project?.code)\n if (!response.errors) {\n const uploaded = response.data.updateKpiMetricValuesSentiment\n this.attachment = uploaded\n notify({ message: '저장되었습니다.' })\n\n // PDF 업로드 후 SL-PA 관련 데이터를 가져와서 표시\n await this._loadSlpaData()\n }\n }\n\n private async _loadSlpaData() {\n // KPI 메트릭들 가져오기\n const kpiMetrics = await getKpiMetrics(this.project?.code)\n\n // \"SL-PA\"가 포함된 메트릭들만 필터링\n const slpaMetrics = kpiMetrics.filter(metric => metric.name.includes('SL-PA'))\n\n if (slpaMetrics.length > 0) {\n // 해당 프로젝트의 KPI 값들 가져오기\n const kpiMetricValues = await getKpiMetricValues(this.project.id, this.project?.code)\n\n // SL-PA 메트릭들과 해당 값들을 매칭하여 표시용 데이터 생성\n this.slpaData = slpaMetrics.map(metric => {\n const metricValue = kpiMetricValues.find((item: any) => item.metricId === metric.id)\n return {\n name: metric.name,\n value: metricValue?.value || 0,\n unit: metricValue?.unit || metric.unit || ''\n }\n })\n }\n }\n\n private _reset() {\n this.pendingFile = null\n this.slpaData = [] // SL-PA 데이터도 초기화\n this._getProjectData()\n }\n\n private async _getProjectData() {\n const project = await getProject(this.project.id)\n this.attachment = project.completeReport\n\n // 기존에 PDF가 업로드되어 있다면 SL-PA 데이터도 로드\n if (this.attachment?.id) {\n await this._loadSlpaData()\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"pc-tab3-upload.js","sourceRoot":"","sources":["../../../client/pages/project-complete-tabs/pc-tab3-upload.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { client } from '@operato/graphql'\nimport { gql } from '@apollo/client'\nimport { getProject, updateProjectCompleteStep3, getKpiMetrics, getKpiMetricValues } from '../../shared/complete-api'\nimport {\n fetchSentimentGrades,\n matchSentimentBand,\n sentimentBandsOf,\n SENTIMENT_METRICS\n} from '../../shared/sentiment-index'\nimport { notify } from '@operato/layout'\nimport { hasPrivilege } from '@things-factory/auth-base/dist-client'\n\n@customElement('sv-pc-tab3-upload')\nexport class SvProjectCompleteTab3Upload extends LitElement {\n static styles = [\n css`\n :host {\n display: block;\n }\n .title {\n color: #212529;\n font-size: 13px;\n font-weight: 400;\n line-height: 24px;\n text-align: center;\n }\n\n .rows {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 8px 6px;\n }\n\n .upload-controls {\n display: flex;\n gap: 8px;\n justify-content: center;\n margin-bottom: 16px;\n\n ox-input-file {\n flex: 1;\n max-width: 500px;\n height: 210px;\n }\n }\n\n .attachment-list {\n display: flex;\n justify-content: center;\n margin-top: 8px;\n }\n\n .attachment-row {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 16px;\n padding: 24px 20px;\n border: 2px solid #e3f2fd;\n border-radius: 12px;\n background: linear-gradient(135deg, #f8fbff 0%, #e3f2fd 100%);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n min-width: 300px;\n max-width: 400px;\n }\n\n .file-icon {\n font-size: 40px;\n color: #d32f2f;\n line-height: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n width: auto;\n height: 48px;\n }\n\n .attachment-name {\n font-size: 16px;\n font-weight: 500;\n color: #1976d2;\n text-align: center;\n word-break: break-word;\n line-height: 1.4;\n }\n\n .delete-icon {\n cursor: pointer;\n color: #757575;\n background: rgba(117, 117, 117, 0.1);\n border-radius: 50%;\n padding: 8px;\n transition: all 0.2s ease;\n }\n\n .delete-icon:hover {\n color: #e57373;\n background: rgba(229, 115, 115, 0.15);\n transform: scale(1.1);\n }\n\n .button-line {\n display: flex;\n justify-content: center;\n gap: 10px;\n margin-top: 16px;\n }\n .ghost-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 10px;\n background: #35618e;\n color: #ffffff;\n border-radius: 5px;\n cursor: pointer;\n }\n .ghost-btn.secondary {\n background: #24be7b;\n }\n .ghost-btn.disabled {\n opacity: 0.45;\n cursor: not-allowed;\n }\n\n .slpa-results {\n margin-top: 20px;\n padding: 16px;\n border: 1px solid #e0e0e0;\n border-radius: 8px;\n background: #f8f9fa;\n }\n\n .slpa-title {\n font-size: 16px;\n font-weight: 600;\n color: #35618e;\n margin-bottom: 12px;\n }\n\n .slpa-item {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 8px 0;\n border-bottom: 1px solid #e0e0e0;\n }\n\n .slpa-item:last-child {\n border-bottom: none;\n }\n\n .slpa-label {\n font-weight: 500;\n color: #333;\n }\n\n .slpa-readout {\n display: flex;\n align-items: baseline;\n gap: 10px;\n }\n\n .slpa-value {\n font-weight: 600;\n color: #1976d2;\n }\n\n .slpa-result {\n font-weight: 700;\n color: #35618e;\n }\n\n /* 지수 → 5단계 환산 기준. 회의 자료의 \"지수 범위 / 결과\" 표를 그대로 옮긴 것. */\n .slpa-legend {\n margin: 6px 0 10px auto;\n border-collapse: collapse;\n font-size: 12px;\n color: #555;\n }\n\n .slpa-legend th,\n .slpa-legend td {\n padding: 3px 12px;\n border: 1px solid #e0e0e0;\n text-align: center;\n font-weight: 400;\n }\n\n .slpa-legend th {\n background: #eef2f7;\n color: #35618e;\n font-weight: 600;\n }\n\n .slpa-legend tr.current td {\n background: #e3f2fd;\n color: #1976d2;\n font-weight: 700;\n }\n `\n ]\n\n @property({ type: Object }) project: any = {}\n @state() attachment: any = {}\n @state() private pendingFile: File | null = null\n @state() slpaData: any[] = []\n /** kpi:sentiment — Step3 보고서 첨부/저장 권한 */\n @state() canSave = false\n\n async connectedCallback() {\n super.connectedCallback()\n this.canSave = await hasPrivilege({\n category: 'kpi',\n privilege: 'sentiment',\n domainOwnerGranted: true,\n superUserGranted: true\n })\n }\n\n render() {\n return html`\n <div class=\"title\">\n <div>\n 감리 최종 감리보고서의 종합의견서(PDF 형식)를 시스템에 업로드해 주세요.\n <br />\n 보고서에 포함된 텍스트와 평가 내용을 기반으로 기계 판독 및 AI 기반 프로젝트 평가가 자동으로 진행되므로, 반드시 최종\n 확정된 원본 파일을 제출해 주시기 바랍니다.\n </div>\n </div>\n\n <div class=\"rows\">\n ${this._getCurrentFile()\n ? html`\n <div class=\"attachment-list\">\n <div class=\"attachment-row\">\n <md-icon class=\"file-icon\">picture_as_pdf</md-icon>\n <div class=\"attachment-name\" title=\"${this._getCurrentFile()?.name}\">${this._getCurrentFile()?.name}</div>\n <md-icon class=\"delete-icon\" @click=${this._removeFile}>delete</md-icon>\n </div>\n </div>\n `\n : html`\n <div class=\"upload-controls\">\n <ox-input-file accept=\"application/pdf,.pdf\" hide-filelist @change=${this._onFileSelect}> </ox-input-file>\n </div>\n `}\n ${this.slpaData.length > 0\n ? html`\n <div class=\"slpa-results\">\n <div class=\"slpa-title\">AI 분석 결과 (SL-PA 관련 항목)</div>\n ${this.slpaData.map(\n item => html`\n <div class=\"slpa-item\">\n <div class=\"slpa-label\">${item.label}</div>\n <div class=\"slpa-readout\">\n <span class=\"slpa-value\">${item.value.toFixed(2)} ${item.unit || ''}</span>\n ${item.matched ? html`<span class=\"slpa-result\">${item.matched.result}</span>` : ''}\n </div>\n </div>\n ${item.bands.length > 0\n ? html`\n <table class=\"slpa-legend\">\n <tr>\n <th>지수 범위</th>\n <th>결과</th>\n </tr>\n ${item.bands.map(\n (band: any) => html`\n <tr class=${item.matched?.score === band.score ? 'current' : ''}>\n <td>${band.range}</td>\n <td>${band.result}</td>\n </tr>\n `\n )}\n </table>\n `\n : ''}\n `\n )}\n </div>\n `\n : ''}\n\n <div class=\"button-line\">\n <div class=\"ghost-btn \" @click=${this._reset}>초기화</div>\n <div\n class=\"ghost-btn secondary ${this.canSave ? '' : 'disabled'}\"\n title=${this.canSave ? '' : 'kpi:sentiment 권한 필요'}\n @click=${() => this.canSave && this._save()}\n >\n 저장\n </div>\n </div>\n </div>\n `\n }\n\n willUpdate(changedProperties: Map<string, any>) {\n super.willUpdate(changedProperties)\n\n // project가 변경되고, project.id가 존재하면 데이터 로드\n if (changedProperties.has('project') && this.project?.id) {\n this._getProjectData()\n }\n }\n\n private _getCurrentFile() {\n return this.pendingFile || (this.attachment?.id ? this.attachment : null)\n }\n\n private async _save() {\n // 대기 중인 파일이 있으면 업로드\n if (this.pendingFile) {\n await this._uploadFile(this.pendingFile)\n this.pendingFile = null\n } else if (!this.pendingFile && !this.attachment?.id) {\n notify({ message: '파일은 필수입니다.' })\n }\n }\n\n private _removeFile() {\n this.pendingFile = null\n this.attachment = {}\n this.slpaData = [] // SL-PA 데이터도 초기화\n }\n\n private async _onFileSelect(e: CustomEvent) {\n const files = e.detail as File[]\n if (files.length > 0) {\n this.pendingFile = files[0]\n }\n }\n\n private async _uploadFile(file: File | null) {\n const response = await updateProjectCompleteStep3(file, this.project.id, this.project?.code)\n if (!response.errors) {\n const uploaded = response.data.updateKpiMetricValuesSentiment\n this.attachment = uploaded\n notify({ message: '저장되었습니다.' })\n\n // PDF 업로드 후 SL-PA 관련 데이터를 가져와서 표시\n await this._loadSlpaData()\n }\n }\n\n private async _loadSlpaData() {\n // KPI 메트릭들 가져오기\n const kpiMetrics = await getKpiMetrics(this.project?.code)\n\n // \"SL-PA\"가 포함된 메트릭들만 필터링\n const slpaMetrics = kpiMetrics.filter(metric => metric.name.includes('SL-PA'))\n\n if (slpaMetrics.length > 0) {\n // 해당 프로젝트의 KPI 값들과, 지수 → 5단계 환산에 쓸 구간표를 함께 가져온다\n const [kpiMetricValues, grades] = await Promise.all([\n getKpiMetricValues(this.project.id, this.project?.code),\n fetchSentimentGrades(this.project?.code)\n ])\n\n // SL-PA 메트릭들과 해당 값들을 매칭하여 표시용 데이터 생성\n this.slpaData = slpaMetrics.map(metric => {\n const metricValue = kpiMetricValues.find((item: any) => item.metricId === metric.id)\n const value = metricValue?.value || 0\n\n // 체감지수로 정의된 metric 만 화면 이름과 등급을 갖는다. 그 밖의 SL-PA 항목은\n // 환산 기준이 없으므로 이름과 값 그대로 (기존 동작).\n const sentiment = SENTIMENT_METRICS[metric.name]\n const metricGrades = sentiment ? grades[sentiment.kpiCode] : undefined\n\n return {\n label: sentiment?.label || metric.name,\n value,\n unit: metricValue?.unit || metric.unit || '',\n matched: matchSentimentBand(value, metricGrades),\n bands: sentimentBandsOf(metricGrades)\n }\n })\n }\n }\n\n private _reset() {\n this.pendingFile = null\n this.slpaData = [] // SL-PA 데이터도 초기화\n this._getProjectData()\n }\n\n private async _getProjectData() {\n const project = await getProject(this.project.id)\n this.attachment = project.completeReport\n\n // 기존에 PDF가 업로드되어 있다면 SL-PA 데이터도 로드\n if (this.attachment?.id) {\n await this._loadSlpaData()\n }\n }\n}\n"]}
|
|
@@ -14,6 +14,16 @@ export declare class SvProjectDetailPage extends SvProjectDetailPage_base {
|
|
|
14
14
|
private project;
|
|
15
15
|
private kpiComprehensiveStats;
|
|
16
16
|
private kpiYComprehensiveStats;
|
|
17
|
+
/**
|
|
18
|
+
* 기준평균값을 좁히는 축. 빈 문자열이 "전체"다.
|
|
19
|
+
*
|
|
20
|
+
* 이 프로젝트의 값이 아니라 비교 대상(모집단)을 고르는 것이다 — 500억 이상 주거시설
|
|
21
|
+
* 프로젝트를 같은 조건의 프로젝트들과만 견주기 위한 필터.
|
|
22
|
+
*/
|
|
23
|
+
private selectedProjectScale;
|
|
24
|
+
private selectedFacilityType;
|
|
25
|
+
/** 사람이 축을 직접 고른 적이 있는가 — 프로젝트 값으로 되돌리지 않기 위한 표시. */
|
|
26
|
+
private axisTouched;
|
|
17
27
|
private projectXKpiValues;
|
|
18
28
|
private showScoreDistribution;
|
|
19
29
|
private selectedKpi;
|
|
@@ -34,6 +44,29 @@ export declare class SvProjectDetailPage extends SvProjectDetailPage_base {
|
|
|
34
44
|
* · 그 외 → (metric, periodType) 매칭 row 없으면 pending
|
|
35
45
|
*/
|
|
36
46
|
private _loadPendingMetrics;
|
|
47
|
+
/**
|
|
48
|
+
* 축을 바꾸면 기준평균값 모집단이 바뀌므로 두 통계를 다시 받는다.
|
|
49
|
+
*
|
|
50
|
+
* 프로젝트 자체의 값(Point, 각 지표 측정값)은 다시 받지 않는다 — 필터는 비교 대상만
|
|
51
|
+
* 고르는 것이지 이 프로젝트의 성적을 바꾸지 않는다.
|
|
52
|
+
*/
|
|
53
|
+
private _onAxisChange;
|
|
54
|
+
/**
|
|
55
|
+
* 비교 대상의 초기값을 이 프로젝트 자신의 규모·시설물유형으로 둔다.
|
|
56
|
+
*
|
|
57
|
+
* "같은 조건끼리 비교한다" 가 이 필터의 목적인데 초기값이 `전체` 면 화면을 열 때마다
|
|
58
|
+
* 사람이 두 번 골라야 그 목적에 닿는다. 프로젝트가 이미 자기 값을 갖고 있으므로 거기서
|
|
59
|
+
* 시작하고, 넓게 보려면 `전체` 로 바꾸면 된다.
|
|
60
|
+
*
|
|
61
|
+
* 사람이 한 번이라도 고른 뒤에는 건드리지 않는다 — 프로젝트를 다시 읽을 때마다 선택이
|
|
62
|
+
* 되돌아가면 필터가 제멋대로 움직이는 것처럼 보인다.
|
|
63
|
+
*
|
|
64
|
+
* 프로젝트에 값이 없으면(증강 데이터가 아닌 실제 프로젝트는 아직 비어 있을 수 있다)
|
|
65
|
+
* 빈 문자열이 되어 `전체` 로 남는다 — 없는 조건으로 좁혀 빈 기준선을 보여 주지 않는다.
|
|
66
|
+
*/
|
|
67
|
+
private _applyOwnAxesAsDefault;
|
|
68
|
+
/** 두 통계 쿼리에 함께 넘길 축. 빈 문자열은 null 로 — 서버에서 "전체"가 된다. */
|
|
69
|
+
private get axisVariables();
|
|
37
70
|
getKpiComprehensiveStats(): Promise<void>;
|
|
38
71
|
getKpiYComprehensiveStats(): Promise<void>;
|
|
39
72
|
getProjectXKpiValues(projectId: string): Promise<void>;
|
|
@@ -15,12 +15,23 @@ import '../components/kpi-radar-chart';
|
|
|
15
15
|
import '../components/kpi-lookup-chart';
|
|
16
16
|
import '../components/kpi-step-lookup-chart';
|
|
17
17
|
import '../components/kpi-2d-lookup-chart';
|
|
18
|
+
import { PROJECT_SCALE_OPTIONS, FACILITY_TYPE_OPTIONS, axisLabelOf } from '../shared/project-axis';
|
|
18
19
|
let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(PageView) {
|
|
19
20
|
constructor() {
|
|
20
21
|
super(...arguments);
|
|
21
22
|
this.project = {};
|
|
22
23
|
this.kpiComprehensiveStats = [];
|
|
23
24
|
this.kpiYComprehensiveStats = [];
|
|
25
|
+
/**
|
|
26
|
+
* 기준평균값을 좁히는 축. 빈 문자열이 "전체"다.
|
|
27
|
+
*
|
|
28
|
+
* 이 프로젝트의 값이 아니라 비교 대상(모집단)을 고르는 것이다 — 500억 이상 주거시설
|
|
29
|
+
* 프로젝트를 같은 조건의 프로젝트들과만 견주기 위한 필터.
|
|
30
|
+
*/
|
|
31
|
+
this.selectedProjectScale = '';
|
|
32
|
+
this.selectedFacilityType = '';
|
|
33
|
+
/** 사람이 축을 직접 고른 적이 있는가 — 프로젝트 값으로 되돌리지 않기 위한 표시. */
|
|
34
|
+
this.axisTouched = false;
|
|
24
35
|
this.projectXKpiValues = [];
|
|
25
36
|
this.showScoreDistribution = false;
|
|
26
37
|
this.selectedKpi = null;
|
|
@@ -100,6 +111,27 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
100
111
|
</div>
|
|
101
112
|
|
|
102
113
|
<div class="detail-grid">
|
|
114
|
+
<!--
|
|
115
|
+
규모·시설물유형은 이미지 옆 목록이 아니라 여기에 둔다. 위쪽 목록은 사진
|
|
116
|
+
높이에 맞춰 다섯 줄이고, 두 줄을 더 얹으면 사진보다 길어져 그 아래 2열
|
|
117
|
+
격자의 왼쪽 칸이 빈 채로 시작한다.
|
|
118
|
+
-->
|
|
119
|
+
<div class="grid-item row">
|
|
120
|
+
<div class="label">• 규모</div>
|
|
121
|
+
<div class="value">
|
|
122
|
+
${this.project.projectScale
|
|
123
|
+
? axisLabelOf(PROJECT_SCALE_OPTIONS, this.project.projectScale)
|
|
124
|
+
: '-'}
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
<div class="grid-item row">
|
|
128
|
+
<div class="label">• 시설물유형</div>
|
|
129
|
+
<div class="value">
|
|
130
|
+
${this.project.facilityType
|
|
131
|
+
? axisLabelOf(FACILITY_TYPE_OPTIONS, this.project.facilityType)
|
|
132
|
+
: '-'}
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
103
135
|
<div class="grid-item row">
|
|
104
136
|
<div class="label">• 계획공사비</div>
|
|
105
137
|
<div class="value">${((_d = (_c = this.project.buildingComplex) === null || _c === void 0 ? void 0 : _c.constructionCost) === null || _d === void 0 ? void 0 : _d.toLocaleString()) || '-'} 억원</div>
|
|
@@ -175,8 +207,28 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
175
207
|
<div>종합 KPI</div>
|
|
176
208
|
<div class="triangle"></div>
|
|
177
209
|
</div>
|
|
178
|
-
<div class="
|
|
210
|
+
<div class="axis-filters">
|
|
211
|
+
<select
|
|
212
|
+
title="규모"
|
|
213
|
+
.value=${this.selectedProjectScale}
|
|
214
|
+
@change=${(e) => this._onAxisChange('selectedProjectScale', e)}
|
|
215
|
+
>
|
|
216
|
+
${PROJECT_SCALE_OPTIONS.map(option => html `<option value=${option.value} ?selected=${this.selectedProjectScale === option.value}>
|
|
217
|
+
${option.label}
|
|
218
|
+
</option>`)}
|
|
219
|
+
</select>
|
|
220
|
+
<select
|
|
221
|
+
title="시설물유형"
|
|
222
|
+
.value=${this.selectedFacilityType}
|
|
223
|
+
@change=${(e) => this._onAxisChange('selectedFacilityType', e)}
|
|
224
|
+
>
|
|
225
|
+
${FACILITY_TYPE_OPTIONS.map(option => html `<option value=${option.value} ?selected=${this.selectedFacilityType === option.value}>
|
|
226
|
+
${option.label}
|
|
227
|
+
</option>`)}
|
|
228
|
+
</select>
|
|
229
|
+
</div>
|
|
179
230
|
</div>
|
|
231
|
+
<div class="kpi-desc">프로젝트의 6개 성과를 종합한 종합 KPI포인트와 각 지표별 분포</div>
|
|
180
232
|
|
|
181
233
|
<div class="kpi-summary">
|
|
182
234
|
<div class="mini-boxplot" title="boxplot placeholder">
|
|
@@ -283,7 +335,8 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
283
335
|
* ASSESSMENT: 5단계 게이지 (kpi-lookup-chart 자동 감지)
|
|
284
336
|
*/ ''}
|
|
285
337
|
${
|
|
286
|
-
//
|
|
338
|
+
// 체감지수 구간표(X34 품질, X43 안전)는 비활성 KPI 로 남아 감리의견서 화면의
|
|
339
|
+
// 범례가 읽는다. 여기서는 정성 평가로 흡수됐으므로 목록에 나오지 않는다.
|
|
287
340
|
// (다른 KPI 차트 경로에는 영향 없음)
|
|
288
341
|
this.selectedKpi.pattern === 'X34' || this.selectedKpi.pattern === 'X43'
|
|
289
342
|
? html `
|
|
@@ -393,6 +446,8 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
393
446
|
projectType
|
|
394
447
|
sectorType
|
|
395
448
|
buildingUsage
|
|
449
|
+
projectScale
|
|
450
|
+
facilityType
|
|
396
451
|
mainPhoto {
|
|
397
452
|
fullpath
|
|
398
453
|
}
|
|
@@ -448,6 +503,7 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
448
503
|
if (response.errors)
|
|
449
504
|
return;
|
|
450
505
|
this.project = ((_b = response.data) === null || _b === void 0 ? void 0 : _b.project) || {};
|
|
506
|
+
this._applyOwnAxesAsDefault();
|
|
451
507
|
this.updateContext();
|
|
452
508
|
this._loadPendingMetrics();
|
|
453
509
|
}
|
|
@@ -528,13 +584,58 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
528
584
|
this.pendingMetrics = [];
|
|
529
585
|
}
|
|
530
586
|
}
|
|
587
|
+
/**
|
|
588
|
+
* 축을 바꾸면 기준평균값 모집단이 바뀌므로 두 통계를 다시 받는다.
|
|
589
|
+
*
|
|
590
|
+
* 프로젝트 자체의 값(Point, 각 지표 측정값)은 다시 받지 않는다 — 필터는 비교 대상만
|
|
591
|
+
* 고르는 것이지 이 프로젝트의 성적을 바꾸지 않는다.
|
|
592
|
+
*/
|
|
593
|
+
_onAxisChange(field, e) {
|
|
594
|
+
this[field] = e.target.value;
|
|
595
|
+
this.axisTouched = true;
|
|
596
|
+
this.getKpiComprehensiveStats();
|
|
597
|
+
this.getKpiYComprehensiveStats();
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* 비교 대상의 초기값을 이 프로젝트 자신의 규모·시설물유형으로 둔다.
|
|
601
|
+
*
|
|
602
|
+
* "같은 조건끼리 비교한다" 가 이 필터의 목적인데 초기값이 `전체` 면 화면을 열 때마다
|
|
603
|
+
* 사람이 두 번 골라야 그 목적에 닿는다. 프로젝트가 이미 자기 값을 갖고 있으므로 거기서
|
|
604
|
+
* 시작하고, 넓게 보려면 `전체` 로 바꾸면 된다.
|
|
605
|
+
*
|
|
606
|
+
* 사람이 한 번이라도 고른 뒤에는 건드리지 않는다 — 프로젝트를 다시 읽을 때마다 선택이
|
|
607
|
+
* 되돌아가면 필터가 제멋대로 움직이는 것처럼 보인다.
|
|
608
|
+
*
|
|
609
|
+
* 프로젝트에 값이 없으면(증강 데이터가 아닌 실제 프로젝트는 아직 비어 있을 수 있다)
|
|
610
|
+
* 빈 문자열이 되어 `전체` 로 남는다 — 없는 조건으로 좁혀 빈 기준선을 보여 주지 않는다.
|
|
611
|
+
*/
|
|
612
|
+
_applyOwnAxesAsDefault() {
|
|
613
|
+
var _a, _b;
|
|
614
|
+
if (this.axisTouched)
|
|
615
|
+
return;
|
|
616
|
+
const scale = ((_a = this.project) === null || _a === void 0 ? void 0 : _a.projectScale) || '';
|
|
617
|
+
const facility = ((_b = this.project) === null || _b === void 0 ? void 0 : _b.facilityType) || '';
|
|
618
|
+
if (scale === this.selectedProjectScale && facility === this.selectedFacilityType)
|
|
619
|
+
return;
|
|
620
|
+
this.selectedProjectScale = scale;
|
|
621
|
+
this.selectedFacilityType = facility;
|
|
622
|
+
this.getKpiComprehensiveStats();
|
|
623
|
+
this.getKpiYComprehensiveStats();
|
|
624
|
+
}
|
|
625
|
+
/** 두 통계 쿼리에 함께 넘길 축. 빈 문자열은 null 로 — 서버에서 "전체"가 된다. */
|
|
626
|
+
get axisVariables() {
|
|
627
|
+
return {
|
|
628
|
+
projectScale: this.selectedProjectScale || null,
|
|
629
|
+
facilityType: this.selectedFacilityType || null
|
|
630
|
+
};
|
|
631
|
+
}
|
|
531
632
|
async getKpiComprehensiveStats() {
|
|
532
633
|
var _a;
|
|
533
634
|
try {
|
|
534
635
|
const response = await client.query({
|
|
535
636
|
query: gql `
|
|
536
|
-
query GetKpiZValueComprehensiveStats {
|
|
537
|
-
totalKpiZValueComprehensiveStats {
|
|
637
|
+
query GetKpiZValueComprehensiveStats($projectScale: String, $facilityType: String) {
|
|
638
|
+
totalKpiZValueComprehensiveStats(projectScale: $projectScale, facilityType: $facilityType) {
|
|
538
639
|
minVal
|
|
539
640
|
q1Val
|
|
540
641
|
medVal
|
|
@@ -543,6 +644,7 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
543
644
|
}
|
|
544
645
|
}
|
|
545
646
|
`,
|
|
647
|
+
variables: this.axisVariables,
|
|
546
648
|
context: { headers: tenantHeaders((_a = this.project) === null || _a === void 0 ? void 0 : _a.code) }
|
|
547
649
|
});
|
|
548
650
|
this.kpiComprehensiveStats = response.data.totalKpiZValueComprehensiveStats || [];
|
|
@@ -557,8 +659,8 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
557
659
|
try {
|
|
558
660
|
const response = await client.query({
|
|
559
661
|
query: gql `
|
|
560
|
-
query GetKpiYValueComprehensiveStats {
|
|
561
|
-
totalKpiYValueComprehensiveStats {
|
|
662
|
+
query GetKpiYValueComprehensiveStats($projectScale: String, $facilityType: String) {
|
|
663
|
+
totalKpiYValueComprehensiveStats(projectScale: $projectScale, facilityType: $facilityType) {
|
|
562
664
|
kpiName
|
|
563
665
|
minVal
|
|
564
666
|
q1Val
|
|
@@ -570,6 +672,7 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
570
672
|
}
|
|
571
673
|
}
|
|
572
674
|
`,
|
|
675
|
+
variables: this.axisVariables,
|
|
573
676
|
context: { headers: tenantHeaders((_a = this.project) === null || _a === void 0 ? void 0 : _a.code) }
|
|
574
677
|
});
|
|
575
678
|
this.kpiYComprehensiveStats = response.data.totalKpiYValueComprehensiveStats || [];
|
|
@@ -829,7 +932,6 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
829
932
|
score: this.getYKpiScoreFormatted('품질성과(Y3)'),
|
|
830
933
|
metrics: [
|
|
831
934
|
{ label: '검측 불합격률', pattern: 'X33', value: this.getMetricValue('X33') },
|
|
832
|
-
{ label: '품질 SL-PA 결과', pattern: 'X34', value: this.getMetricValue('X34') },
|
|
833
935
|
{ label: '품질성과 정성 평가', pattern: 'X35', value: this.getMetricValue('X35') }
|
|
834
936
|
]
|
|
835
937
|
},
|
|
@@ -839,7 +941,6 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
839
941
|
metrics: [
|
|
840
942
|
{ label: '재해율', pattern: 'X41', value: this.getMetricValue('X41') },
|
|
841
943
|
{ label: '재해강도율', pattern: 'X42', value: this.getMetricValue('X42') },
|
|
842
|
-
{ label: '안전 SL-PA 결과', pattern: 'X43', value: this.getMetricValue('X43') },
|
|
843
944
|
{ label: '안전성과 정성 평가', pattern: 'X44', value: this.getMetricValue('X44') }
|
|
844
945
|
]
|
|
845
946
|
},
|
|
@@ -1220,6 +1321,19 @@ SvProjectDetailPage.styles = [
|
|
|
1220
1321
|
align-items: center;
|
|
1221
1322
|
gap: 5px;
|
|
1222
1323
|
}
|
|
1324
|
+
.axis-filters {
|
|
1325
|
+
gap: 6px;
|
|
1326
|
+
}
|
|
1327
|
+
.axis-filters select {
|
|
1328
|
+
padding: 4px 8px;
|
|
1329
|
+
border: 1px solid #ddd;
|
|
1330
|
+
border-radius: 4px;
|
|
1331
|
+
background: white;
|
|
1332
|
+
color: #333;
|
|
1333
|
+
font-size: 12px;
|
|
1334
|
+
font-weight: 400;
|
|
1335
|
+
letter-spacing: -0.03em;
|
|
1336
|
+
}
|
|
1223
1337
|
.kpi-desc {
|
|
1224
1338
|
color: #35618e;
|
|
1225
1339
|
font-size: 13px;
|
|
@@ -1474,6 +1588,14 @@ __decorate([
|
|
|
1474
1588
|
state(),
|
|
1475
1589
|
__metadata("design:type", Array)
|
|
1476
1590
|
], SvProjectDetailPage.prototype, "kpiYComprehensiveStats", void 0);
|
|
1591
|
+
__decorate([
|
|
1592
|
+
state(),
|
|
1593
|
+
__metadata("design:type", String)
|
|
1594
|
+
], SvProjectDetailPage.prototype, "selectedProjectScale", void 0);
|
|
1595
|
+
__decorate([
|
|
1596
|
+
state(),
|
|
1597
|
+
__metadata("design:type", String)
|
|
1598
|
+
], SvProjectDetailPage.prototype, "selectedFacilityType", void 0);
|
|
1477
1599
|
__decorate([
|
|
1478
1600
|
state(),
|
|
1479
1601
|
__metadata("design:type", Array)
|