@dssp/dkpi 1.0.0-alpha.67 → 1.0.0-alpha.69
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-client/components/kpi-2d-lookup-chart.d.ts +10 -30
- package/dist-client/components/kpi-2d-lookup-chart.js +145 -362
- package/dist-client/components/kpi-2d-lookup-chart.js.map +1 -1
- package/dist-client/components/kpi-boxplot-chart.js +51 -24
- package/dist-client/components/kpi-boxplot-chart.js.map +1 -1
- package/dist-client/components/kpi-lookup-chart.d.ts +28 -4
- package/dist-client/components/kpi-lookup-chart.js +314 -293
- package/dist-client/components/kpi-lookup-chart.js.map +1 -1
- package/dist-client/components/kpi-radar-chart.js +29 -5
- package/dist-client/components/kpi-radar-chart.js.map +1 -1
- package/dist-client/components/kpi-single-boxplot-chart.js +72 -14
- package/dist-client/components/kpi-single-boxplot-chart.js.map +1 -1
- package/dist-client/pages/kpi-admin/dssp-kpi-overview.d.ts +46 -0
- package/dist-client/pages/kpi-admin/dssp-kpi-overview.js +378 -0
- package/dist-client/pages/kpi-admin/dssp-kpi-overview.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js +0 -9
- package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js.map +1 -1
- package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js +0 -1
- package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js.map +1 -1
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +0 -4
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -1
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js +0 -11
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js.map +1 -1
- package/dist-client/pages/sv-project-completed-list.js +0 -1
- package/dist-client/pages/sv-project-completed-list.js.map +1 -1
- package/dist-client/pages/sv-project-detail.d.ts +11 -1
- package/dist-client/pages/sv-project-detail.js +150 -20
- package/dist-client/pages/sv-project-detail.js.map +1 -1
- package/dist-client/pages/sv-project-list.js +0 -1
- package/dist-client/pages/sv-project-list.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/route.js +3 -0
- package/dist-client/route.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/schema.graphql +26 -5
- package/things-factory.config.js +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sv-project-completed-list.js","sourceRoot":"","sources":["../../client/pages/sv-project-completed-list.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '../components/kpi-single-boxplot-chart'\n\nimport { PageView, navigate } from '@operato/shell'\nimport { css, html } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\nimport { ScopedElementsMixin } from '@open-wc/scoped-elements'\nimport { client } from '@operato/graphql'\nimport gql from 'graphql-tag'\nimport { Project, PROJECT_STATE, ProjectState } from './sv-project-list'\nimport '../components/sv-pagenation-control'\n\n@customElement('sv-project-completed-list')\nexport class SvProjectCompletedListPage 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 --grid-record-emphasized-background-color: red;\n --grid-record-emphasized-color: yellow;\n }\n\n /* Search bar (Figma: content > search bar) */\n div[header] {\n display: flex;\n align-items: center;\n gap: 10px;\n margin: var(--spacing-large, 12px);\n margin-bottom: 0;\n padding: 12px 11px;\n border-radius: 7px;\n background-color: rgba(46, 164, 223, 0.1);\n border: 1px solid rgba(46, 164, 223, 0.3);\n }\n div[header] div[search] {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 186px;\n }\n div[header] div[search] md-icon {\n color: #212529;\n }\n div[header] div[search] input[type='search'] {\n border: none;\n outline: none;\n background: transparent;\n font-size: 16px;\n color: #212529;\n padding: 2px 0;\n border-bottom: 1px solid rgba(0, 0, 0, 0.2);\n }\n div[header] .spacer {\n flex: 1;\n }\n div[header] div[count] {\n font-size: 16px;\n color: #000000;\n margin-right: 6px;\n }\n div[header] md-icon[view] {\n color: #35618e;\n cursor: pointer;\n }\n div[header] md-icon[add] {\n color: #4cbb49;\n cursor: pointer;\n }\n\n /* Table (Figma: content > table) */\n div[table] {\n margin: var(--spacing-large, 12px);\n border: 1px solid rgba(0, 0, 0, 0.15);\n border-top: 2px solid #0c4da2;\n border-radius: var(--md-sys-shape-corner-small, 5px);\n overflow: hidden;\n background: #ffffff;\n overflow-y: auto;\n min-height: fit-content;\n }\n div[table-header] {\n display: grid;\n grid-template-columns: 150px 80px 1fr 220px 150px 100px;\n align-items: center;\n justify-items: center;\n height: 35px;\n background: #f3f3fa;\n padding: 0 25px;\n column-gap: 30px;\n }\n div[table-header] div[col] {\n font-size: 16px;\n color: #212529;\n line-height: 1.875em;\n }\n div[table-body] {\n display: block;\n }\n div[tr] {\n display: grid;\n grid-template-columns: 150px 80px 1fr 220px 150px 100px;\n align-items: center;\n justify-items: center;\n column-gap: 30px;\n padding: 12px 25px;\n background: #ffffff;\n border-top: 1px solid rgba(0, 0, 0, 0.15);\n cursor: pointer;\n }\n /* 좌측 정렬 요구사항 */\n div[td-info],\n div[td-etc] {\n justify-self: start;\n text-align: left;\n }\n div[td-pics] img[pic] {\n width: 150px;\n height: 90px;\n object-fit: fill;\n background: #e5e7eb;\n border-radius: 2px;\n }\n div[td-status] {\n font-weight: 700;\n font-size: 16px;\n color: #4cbb49;\n line-height: 1;\n }\n div[td-info] {\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n div[td-info] .name {\n font-weight: 700;\n font-size: 14px;\n color: #000000;\n }\n div[td-info] .sub {\n font-size: 14px;\n color: #000000;\n line-height: 1.7;\n }\n div[td-etc] {\n font-size: 16px;\n color: #212529;\n white-space: pre-line;\n }\n div[td-request] {\n font-weight: 700;\n font-size: 16px;\n color: #35618e;\n white-space: pre-line;\n }\n div[td-kpi] {\n display: flex;\n align-items: center;\n height: 100%;\n }\n div[td-kpi] .kpi-value {\n font-weight: 700;\n font-size: 22px;\n color: #35618e;\n width: 30px;\n }\n `\n ]\n\n get context() {\n return {\n title: '완료 프로젝트'\n }\n }\n\n @state() private projectName: string = ''\n @state() private projectList: Project[] = []\n @state() private projectCount: number = 0\n @state() private currentPage: number = 1\n @state() private kpiComprehensiveStats: any[] = []\n\n private readonly pageLimit: number = 20\n\n get totalPages(): number {\n return Math.max(1, Math.ceil((this.projectCount || 0) / this.pageLimit))\n }\n\n render() {\n return html`\n <div header>\n <div search>\n <md-icon>manage_search</md-icon>\n <input\n type=\"search\"\n name=\"projectName\"\n placeholder=\"프로젝트명\"\n .value=${this.projectName}\n @input=${this._onInputChange}\n @keypress=${this._onKeypress}\n />\n </div>\n\n <span class=\"spacer\"></span>\n <div count>총 ${this.projectCount}건</div>\n </div>\n\n <div table>\n <div table-header>\n <div col></div>\n <div col>현장상태</div>\n <div col>현장정보</div>\n <div col>기타정보</div>\n <div col>입력요청</div>\n <div col>종합KPI</div>\n </div>\n <div table-body>\n ${this.projectList?.map((project: Project) => {\n const areaText = `연면적 : ${project?.buildingComplex?.area?.toLocaleString() || '-'} ㎡`\n const costText = `공사비 : ${project?.buildingComplex?.constructionCost?.toLocaleString() || '-'} 억원`\n const householdCountText = `세대수 : ${project?.buildingComplex?.householdCount?.toLocaleString() || '-'} 세대`\n\n const kpiValue = project.kpi ? project.kpi : 0\n\n return html`\n <div tr @click=${() => navigate(`project-detail/${project.id}`)}>\n <div td-pics>\n <img pic src=${project.mainPhoto?.fullpath || '/assets/images/project-image.png'} alt=\"${project.name}\" />\n </div>\n <div td-status>${PROJECT_STATE[project.state]}</div>\n <div td-info>\n <div class=\"name\">${project.name}</div>\n <div class=\"sub\">주소 : ${project.buildingComplex?.address || ''}</div>\n <div class=\"sub\">착공~준공 : ${project.startDate} ~ ${project.endDate}</div>\n </div>\n <div td-etc>${areaText + '\\n' + costText + '\\n' + householdCountText}</div>\n <div td-request>KPI입력 요청 - 건</div>\n <div td-kpi>\n <kpi-single-boxplot-chart .data=${this.getBoxPlotDataForProject(project)}></kpi-single-boxplot-chart>\n <span class=\"kpi-value\">${kpiValue?.toFixed(0) ?? '-'}</span>\n </div>\n </div>\n `\n })}\n </div>\n </div>\n <sv-pagenation-control\n .currentPage=${this.currentPage}\n .totalItems=${this.projectCount}\n .pageLimit=${this.pageLimit}\n @page-change=${(e: CustomEvent) => this._changePage(e.detail.page)}\n ></sv-pagenation-control>\n `\n }\n\n async pageUpdated(changes: any, lifecycle: any) {\n if (this.active) {\n this.getProjectList()\n this.getKpiComprehensiveStats()\n }\n }\n\n async getProjectList() {\n const response = await client.query({\n query: gql`\n query Projects($filters: [Filter!], $sortings: [Sorting!], $pagination: Pagination) {\n projects(filters: $filters, sortings: $sortings, pagination: $pagination) {\n items {\n id\n name\n state\n startDate\n endDate\n geoGroup\n mainPhoto {\n fullpath\n }\n totalProgress\n weeklyProgress\n kpi\n inspPassRate\n robotProgressRate\n structuralSafetyRate\n buildingComplex {\n address\n area\n clientCompany\n constructionCost\n householdCount\n }\n }\n total\n }\n }\n `,\n variables: {\n filters: [\n { name: 'name', operator: 'search', value: `%${this.projectName}%` },\n { name: 'state', operator: 'eq', value: ProjectState.COMPLETED }\n ],\n sortings: [{ name: 'createdAt', desc: true }],\n pagination: { page: this.currentPage, limit: this.pageLimit }\n }\n })\n\n this.projectList = response.data.projects?.items || []\n this.projectCount = response.data.projects?.total || 0\n }\n\n async getKpiComprehensiveStats() {\n try {\n const response = await client.query({\n query: gql`\n query GetKpiZValueComprehensiveStats {\n totalKpiZValueComprehensiveStats {\n minVal\n q1Val\n medVal\n q3Val\n maxVal\n }\n }\n `\n })\n\n this.kpiComprehensiveStats = response.data.totalKpiZValueComprehensiveStats || []\n console.log('KPI Z-Value Comprehensive Stats:', this.kpiComprehensiveStats)\n } catch (error) {\n console.error('Failed to fetch KPI comprehensive stats:', error)\n this.kpiComprehensiveStats = []\n }\n }\n\n // Input 요소의 값이 변경될 때 호출되는 콜백 함수\n private _onInputChange(event: InputEvent) {\n const target = event.target as HTMLInputElement\n this[target.name] = target.value\n if (target.name === 'projectName') {\n this.currentPage = 1\n }\n }\n\n // 검색창에서 엔터입력시 검색\n private _onKeypress(event: KeyboardEvent) {\n if (event.code === 'Enter') {\n this.currentPage = 1\n this.getProjectList()\n }\n }\n\n private _changePage(page: number) {\n const nextPage = Math.min(Math.max(1, page), this.totalPages)\n if (nextPage !== this.currentPage) {\n this.currentPage = nextPage\n this.getProjectList()\n }\n }\n\n private getBoxPlotDataForProject(project: Project) {\n // 전체 프로젝트의 통계 찾기\n const stats = this.kpiComprehensiveStats?.[0]\n\n if (!stats) {\n // 전체 통계의 평균값 사용 또는 기본값 반환\n const defaultStats = this.kpiComprehensiveStats.length > 0 ? this.kpiComprehensiveStats[0] : null\n if (defaultStats) {\n return {\n min: defaultStats.minVal,\n max: defaultStats.maxVal,\n mean: (defaultStats.q1Val + defaultStats.q3Val) / 2,\n median: defaultStats.medVal,\n q1: defaultStats.q1Val,\n q3: defaultStats.q3Val,\n value: project.kpi || 0\n }\n }\n\n // 완전한 기본값\n return {\n min: 0,\n max: 100,\n mean: 50,\n median: 50,\n q1: 25,\n q3: 75,\n value: project.kpi || 0\n }\n }\n\n return {\n min: stats.minVal * 100,\n max: stats.maxVal * 100,\n mean: ((stats.q1Val + stats.q3Val) / 2) * 100, // 대략적 평균값\n median: stats.medVal * 100,\n q1: stats.q1Val * 100,\n q3: stats.q3Val * 100,\n value: project.kpi || 0\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"sv-project-completed-list.js","sourceRoot":"","sources":["../../client/pages/sv-project-completed-list.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '../components/kpi-single-boxplot-chart'\n\nimport { PageView, navigate } from '@operato/shell'\nimport { css, html } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\nimport { ScopedElementsMixin } from '@open-wc/scoped-elements'\nimport { client } from '@operato/graphql'\nimport gql from 'graphql-tag'\nimport { Project, PROJECT_STATE, ProjectState } from './sv-project-list'\nimport '../components/sv-pagenation-control'\n\n@customElement('sv-project-completed-list')\nexport class SvProjectCompletedListPage 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 --grid-record-emphasized-background-color: red;\n --grid-record-emphasized-color: yellow;\n }\n\n /* Search bar (Figma: content > search bar) */\n div[header] {\n display: flex;\n align-items: center;\n gap: 10px;\n margin: var(--spacing-large, 12px);\n margin-bottom: 0;\n padding: 12px 11px;\n border-radius: 7px;\n background-color: rgba(46, 164, 223, 0.1);\n border: 1px solid rgba(46, 164, 223, 0.3);\n }\n div[header] div[search] {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 186px;\n }\n div[header] div[search] md-icon {\n color: #212529;\n }\n div[header] div[search] input[type='search'] {\n border: none;\n outline: none;\n background: transparent;\n font-size: 16px;\n color: #212529;\n padding: 2px 0;\n border-bottom: 1px solid rgba(0, 0, 0, 0.2);\n }\n div[header] .spacer {\n flex: 1;\n }\n div[header] div[count] {\n font-size: 16px;\n color: #000000;\n margin-right: 6px;\n }\n div[header] md-icon[view] {\n color: #35618e;\n cursor: pointer;\n }\n div[header] md-icon[add] {\n color: #4cbb49;\n cursor: pointer;\n }\n\n /* Table (Figma: content > table) */\n div[table] {\n margin: var(--spacing-large, 12px);\n border: 1px solid rgba(0, 0, 0, 0.15);\n border-top: 2px solid #0c4da2;\n border-radius: var(--md-sys-shape-corner-small, 5px);\n overflow: hidden;\n background: #ffffff;\n overflow-y: auto;\n min-height: fit-content;\n }\n div[table-header] {\n display: grid;\n grid-template-columns: 150px 80px 1fr 220px 150px 100px;\n align-items: center;\n justify-items: center;\n height: 35px;\n background: #f3f3fa;\n padding: 0 25px;\n column-gap: 30px;\n }\n div[table-header] div[col] {\n font-size: 16px;\n color: #212529;\n line-height: 1.875em;\n }\n div[table-body] {\n display: block;\n }\n div[tr] {\n display: grid;\n grid-template-columns: 150px 80px 1fr 220px 150px 100px;\n align-items: center;\n justify-items: center;\n column-gap: 30px;\n padding: 12px 25px;\n background: #ffffff;\n border-top: 1px solid rgba(0, 0, 0, 0.15);\n cursor: pointer;\n }\n /* 좌측 정렬 요구사항 */\n div[td-info],\n div[td-etc] {\n justify-self: start;\n text-align: left;\n }\n div[td-pics] img[pic] {\n width: 150px;\n height: 90px;\n object-fit: fill;\n background: #e5e7eb;\n border-radius: 2px;\n }\n div[td-status] {\n font-weight: 700;\n font-size: 16px;\n color: #4cbb49;\n line-height: 1;\n }\n div[td-info] {\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n div[td-info] .name {\n font-weight: 700;\n font-size: 14px;\n color: #000000;\n }\n div[td-info] .sub {\n font-size: 14px;\n color: #000000;\n line-height: 1.7;\n }\n div[td-etc] {\n font-size: 16px;\n color: #212529;\n white-space: pre-line;\n }\n div[td-request] {\n font-weight: 700;\n font-size: 16px;\n color: #35618e;\n white-space: pre-line;\n }\n div[td-kpi] {\n display: flex;\n align-items: center;\n height: 100%;\n }\n div[td-kpi] .kpi-value {\n font-weight: 700;\n font-size: 22px;\n color: #35618e;\n width: 30px;\n }\n `\n ]\n\n get context() {\n return {\n title: '완료 프로젝트'\n }\n }\n\n @state() private projectName: string = ''\n @state() private projectList: Project[] = []\n @state() private projectCount: number = 0\n @state() private currentPage: number = 1\n @state() private kpiComprehensiveStats: any[] = []\n\n private readonly pageLimit: number = 20\n\n get totalPages(): number {\n return Math.max(1, Math.ceil((this.projectCount || 0) / this.pageLimit))\n }\n\n render() {\n return html`\n <div header>\n <div search>\n <md-icon>manage_search</md-icon>\n <input\n type=\"search\"\n name=\"projectName\"\n placeholder=\"프로젝트명\"\n .value=${this.projectName}\n @input=${this._onInputChange}\n @keypress=${this._onKeypress}\n />\n </div>\n\n <span class=\"spacer\"></span>\n <div count>총 ${this.projectCount}건</div>\n </div>\n\n <div table>\n <div table-header>\n <div col></div>\n <div col>현장상태</div>\n <div col>현장정보</div>\n <div col>기타정보</div>\n <div col>입력요청</div>\n <div col>종합KPI</div>\n </div>\n <div table-body>\n ${this.projectList?.map((project: Project) => {\n const areaText = `연면적 : ${project?.buildingComplex?.area?.toLocaleString() || '-'} ㎡`\n const costText = `공사비 : ${project?.buildingComplex?.constructionCost?.toLocaleString() || '-'} 억원`\n const householdCountText = `세대수 : ${project?.buildingComplex?.householdCount?.toLocaleString() || '-'} 세대`\n\n const kpiValue = project.kpi ? project.kpi : 0\n\n return html`\n <div tr @click=${() => navigate(`project-detail/${project.id}`)}>\n <div td-pics>\n <img pic src=${project.mainPhoto?.fullpath || '/assets/images/project-image.png'} alt=\"${project.name}\" />\n </div>\n <div td-status>${PROJECT_STATE[project.state]}</div>\n <div td-info>\n <div class=\"name\">${project.name}</div>\n <div class=\"sub\">주소 : ${project.buildingComplex?.address || ''}</div>\n <div class=\"sub\">착공~준공 : ${project.startDate} ~ ${project.endDate}</div>\n </div>\n <div td-etc>${areaText + '\\n' + costText + '\\n' + householdCountText}</div>\n <div td-request>KPI입력 요청 - 건</div>\n <div td-kpi>\n <kpi-single-boxplot-chart .data=${this.getBoxPlotDataForProject(project)}></kpi-single-boxplot-chart>\n <span class=\"kpi-value\">${kpiValue?.toFixed(0) ?? '-'}</span>\n </div>\n </div>\n `\n })}\n </div>\n </div>\n <sv-pagenation-control\n .currentPage=${this.currentPage}\n .totalItems=${this.projectCount}\n .pageLimit=${this.pageLimit}\n @page-change=${(e: CustomEvent) => this._changePage(e.detail.page)}\n ></sv-pagenation-control>\n `\n }\n\n async pageUpdated(changes: any, lifecycle: any) {\n if (this.active) {\n this.getProjectList()\n this.getKpiComprehensiveStats()\n }\n }\n\n async getProjectList() {\n const response = await client.query({\n query: gql`\n query Projects($filters: [Filter!], $sortings: [Sorting!], $pagination: Pagination) {\n projects(filters: $filters, sortings: $sortings, pagination: $pagination) {\n items {\n id\n name\n state\n startDate\n endDate\n geoGroup\n mainPhoto {\n fullpath\n }\n totalProgress\n weeklyProgress\n kpi\n inspPassRate\n robotProgressRate\n structuralSafetyRate\n buildingComplex {\n address\n area\n clientCompany\n constructionCost\n householdCount\n }\n }\n total\n }\n }\n `,\n variables: {\n filters: [\n { name: 'name', operator: 'search', value: `%${this.projectName}%` },\n { name: 'state', operator: 'eq', value: ProjectState.COMPLETED }\n ],\n sortings: [{ name: 'createdAt', desc: true }],\n pagination: { page: this.currentPage, limit: this.pageLimit }\n }\n })\n\n this.projectList = response.data.projects?.items || []\n this.projectCount = response.data.projects?.total || 0\n }\n\n async getKpiComprehensiveStats() {\n try {\n const response = await client.query({\n query: gql`\n query GetKpiZValueComprehensiveStats {\n totalKpiZValueComprehensiveStats {\n minVal\n q1Val\n medVal\n q3Val\n maxVal\n }\n }\n `\n })\n\n this.kpiComprehensiveStats = response.data.totalKpiZValueComprehensiveStats || []\n } catch (error) {\n console.error('Failed to fetch KPI comprehensive stats:', error)\n this.kpiComprehensiveStats = []\n }\n }\n\n // Input 요소의 값이 변경될 때 호출되는 콜백 함수\n private _onInputChange(event: InputEvent) {\n const target = event.target as HTMLInputElement\n this[target.name] = target.value\n if (target.name === 'projectName') {\n this.currentPage = 1\n }\n }\n\n // 검색창에서 엔터입력시 검색\n private _onKeypress(event: KeyboardEvent) {\n if (event.code === 'Enter') {\n this.currentPage = 1\n this.getProjectList()\n }\n }\n\n private _changePage(page: number) {\n const nextPage = Math.min(Math.max(1, page), this.totalPages)\n if (nextPage !== this.currentPage) {\n this.currentPage = nextPage\n this.getProjectList()\n }\n }\n\n private getBoxPlotDataForProject(project: Project) {\n // 전체 프로젝트의 통계 찾기\n const stats = this.kpiComprehensiveStats?.[0]\n\n if (!stats) {\n // 전체 통계의 평균값 사용 또는 기본값 반환\n const defaultStats = this.kpiComprehensiveStats.length > 0 ? this.kpiComprehensiveStats[0] : null\n if (defaultStats) {\n return {\n min: defaultStats.minVal,\n max: defaultStats.maxVal,\n mean: (defaultStats.q1Val + defaultStats.q3Val) / 2,\n median: defaultStats.medVal,\n q1: defaultStats.q1Val,\n q3: defaultStats.q3Val,\n value: project.kpi || 0\n }\n }\n\n // 완전한 기본값\n return {\n min: 0,\n max: 100,\n mean: 50,\n median: 50,\n q1: 25,\n q3: 75,\n value: project.kpi || 0\n }\n }\n\n return {\n min: stats.minVal * 100,\n max: stats.maxVal * 100,\n mean: ((stats.q1Val + stats.q3Val) / 2) * 100, // 대략적 평균값\n median: stats.medVal * 100,\n q1: stats.q1Val * 100,\n q3: stats.q3Val * 100,\n value: project.kpi || 0\n }\n }\n}\n"]}
|
|
@@ -14,7 +14,7 @@ export declare class SvProjectDetailPage extends SvProjectDetailPage_base {
|
|
|
14
14
|
private kpiComprehensiveStats;
|
|
15
15
|
private kpiYComprehensiveStats;
|
|
16
16
|
private projectXKpiValues;
|
|
17
|
-
private
|
|
17
|
+
private showScoreDistribution;
|
|
18
18
|
private selectedKpi;
|
|
19
19
|
render(): import("lit-html").TemplateResult<1>;
|
|
20
20
|
pageUpdated(changes: any, lifecycle: PageLifecycle): Promise<void>;
|
|
@@ -24,6 +24,16 @@ export declare class SvProjectDetailPage extends SvProjectDetailPage_base {
|
|
|
24
24
|
getProjectXKpiValues(projectId: string): Promise<void>;
|
|
25
25
|
private _onMetricLabelClick;
|
|
26
26
|
private _fetchKpiWithGradesByPattern;
|
|
27
|
+
/**
|
|
28
|
+
* 등급 기준으로 표시할지 판별 (차트 대신 등급 설명 목록)
|
|
29
|
+
*
|
|
30
|
+
* - valueType=ASSESSED: 감리자 직접 평가 → 등급 설명
|
|
31
|
+
* - CUSTOM/COMPOSITE이지만 value가 1~5 정수 (증강데이터): 등급 설명 + 히트맵
|
|
32
|
+
* - grades가 배열이 아닌 경우 (2D lookup 객체): 등급 설명 + 히트맵
|
|
33
|
+
*/
|
|
34
|
+
private _isAssessmentOrIntegerScore;
|
|
35
|
+
/** 등급 기준 표시 (ASSESSED / 1~5 정수 score) — 현재 값 강조 + CUSTOM은 2D 히트맵도 표시 */
|
|
36
|
+
private _renderAssessmentGrades;
|
|
27
37
|
private _closePopup;
|
|
28
38
|
private getMetricValue;
|
|
29
39
|
private getYKpiScoreFormatted;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { __decorate, __metadata } from "tslib";
|
|
2
2
|
import { navigate, PageView } from '@operato/shell';
|
|
3
|
+
import { ScrollbarStyles } from '@operato/styles';
|
|
3
4
|
import { css, html } from 'lit';
|
|
4
5
|
import { customElement, state } from 'lit/decorators.js';
|
|
5
6
|
import { ScopedElementsMixin } from '@open-wc/scoped-elements';
|
|
@@ -18,7 +19,7 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
18
19
|
this.kpiComprehensiveStats = [];
|
|
19
20
|
this.kpiYComprehensiveStats = [];
|
|
20
21
|
this.projectXKpiValues = [];
|
|
21
|
-
this.
|
|
22
|
+
this.showScoreDistribution = false;
|
|
22
23
|
this.selectedKpi = null;
|
|
23
24
|
}
|
|
24
25
|
get context() {
|
|
@@ -221,16 +222,31 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
221
222
|
</div>
|
|
222
223
|
</div>
|
|
223
224
|
|
|
224
|
-
${this.
|
|
225
|
+
${this.showScoreDistribution && this.selectedKpi
|
|
225
226
|
? html `
|
|
226
227
|
<div class="popup-overlay" @click=${this._closePopup}>
|
|
227
228
|
<div class="popup-content" @click=${(e) => e.stopPropagation()}>
|
|
228
229
|
<div class="popup-header">
|
|
229
|
-
<div class="popup-title">${this.selectedKpi.kpiName}
|
|
230
|
+
<div class="popup-title">${this.selectedKpi.kpiName} — 성과 분포</div>
|
|
230
231
|
<button class="popup-close" @click=${this._closePopup}>×</button>
|
|
231
232
|
</div>
|
|
232
233
|
<div class="popup-chart-container">
|
|
233
|
-
${
|
|
234
|
+
${ /*
|
|
235
|
+
* scoreType 기반 차트 분기:
|
|
236
|
+
*
|
|
237
|
+
* CUSTOM (X13 등): 2D 룩업 히트맵 — 단, 증강데이터에서는 value가
|
|
238
|
+
* 이미 1~5 정수(룩업 결과)이므로 히트맵 대신 게이지로 폴백.
|
|
239
|
+
* 실제 프로젝트에서는 value가 편차율(소수점)이므로 히트맵 표시.
|
|
240
|
+
*
|
|
241
|
+
* LOOKUP: 성과 분포 커브 (kpi-lookup-chart)
|
|
242
|
+
* ASSESSMENT: 5단계 게이지 (kpi-lookup-chart 자동 감지)
|
|
243
|
+
*/''}
|
|
244
|
+
${(this.selectedKpi.scoreType === 'CUSTOM' ||
|
|
245
|
+
((_g = this.selectedKpi.grades) === null || _g === void 0 ? void 0 : _g.type) === 'PROGRESS_DEVIATION_LOOKUP') &&
|
|
246
|
+
this.selectedKpi.value !== null &&
|
|
247
|
+
!(this.selectedKpi.value === Math.floor(this.selectedKpi.value) &&
|
|
248
|
+
this.selectedKpi.value >= 1 &&
|
|
249
|
+
this.selectedKpi.value <= 5)
|
|
234
250
|
? html `
|
|
235
251
|
<kpi-2d-lookup-chart
|
|
236
252
|
.grades=${this.selectedKpi.grades}
|
|
@@ -240,14 +256,17 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
240
256
|
kpiName=${this.selectedKpi.kpiName || ''}
|
|
241
257
|
></kpi-2d-lookup-chart>
|
|
242
258
|
`
|
|
243
|
-
:
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
259
|
+
: this._isAssessmentOrIntegerScore()
|
|
260
|
+
? this._renderAssessmentGrades()
|
|
261
|
+
: html `
|
|
262
|
+
<kpi-lookup-chart
|
|
263
|
+
.grades=${this.selectedKpi.grades || []}
|
|
264
|
+
.value=${this.selectedKpi.value}
|
|
265
|
+
.scoreType=${this.selectedKpi.scoreType || ''}
|
|
266
|
+
.valueType=${this.selectedKpi.valueType || ''}
|
|
267
|
+
unit=${this.selectedKpi.unit || ''}
|
|
268
|
+
></kpi-lookup-chart>
|
|
269
|
+
`}
|
|
251
270
|
</div>
|
|
252
271
|
</div>
|
|
253
272
|
</div>
|
|
@@ -345,7 +364,6 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
345
364
|
`
|
|
346
365
|
});
|
|
347
366
|
this.kpiComprehensiveStats = response.data.totalKpiZValueComprehensiveStats || [];
|
|
348
|
-
console.log('KPI Z-Value Comprehensive Stats:', this.kpiComprehensiveStats);
|
|
349
367
|
}
|
|
350
368
|
catch (error) {
|
|
351
369
|
console.error('Failed to fetch KPI comprehensive stats:', error);
|
|
@@ -371,7 +389,6 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
371
389
|
`
|
|
372
390
|
});
|
|
373
391
|
this.kpiYComprehensiveStats = response.data.totalKpiYValueComprehensiveStats || [];
|
|
374
|
-
console.log('KPI Y-Value Comprehensive Stats:', this.kpiYComprehensiveStats);
|
|
375
392
|
}
|
|
376
393
|
catch (error) {
|
|
377
394
|
console.error('Failed to fetch KPI Y comprehensive stats:', error);
|
|
@@ -398,7 +415,6 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
398
415
|
variables: { projectId }
|
|
399
416
|
});
|
|
400
417
|
this.projectXKpiValues = response.data.projectXKpiValues || [];
|
|
401
|
-
console.log('Project X KPI Values:', this.projectXKpiValues);
|
|
402
418
|
}
|
|
403
419
|
catch (error) {
|
|
404
420
|
console.error('Failed to fetch project X KPI values:', error);
|
|
@@ -433,6 +449,8 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
433
449
|
id
|
|
434
450
|
name
|
|
435
451
|
grades
|
|
452
|
+
scoreType
|
|
453
|
+
valueType
|
|
436
454
|
vizMeta
|
|
437
455
|
}
|
|
438
456
|
}
|
|
@@ -452,17 +470,130 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
452
470
|
this.selectedKpi = {
|
|
453
471
|
kpiName: displayName,
|
|
454
472
|
grades: kpi.grades || [],
|
|
473
|
+
scoreType: kpi.scoreType || '',
|
|
474
|
+
valueType: kpi.valueType || '',
|
|
455
475
|
value: (_b = kpiValue.value) !== null && _b !== void 0 ? _b : null,
|
|
456
476
|
unit: ((_c = kpi.vizMeta) === null || _c === void 0 ? void 0 : _c.unit) || ''
|
|
457
477
|
};
|
|
458
|
-
this.
|
|
478
|
+
this.showScoreDistribution = true;
|
|
459
479
|
}
|
|
460
480
|
catch (error) {
|
|
461
481
|
console.error('Error fetching KPI:', error);
|
|
462
482
|
}
|
|
463
483
|
}
|
|
484
|
+
/**
|
|
485
|
+
* 등급 기준으로 표시할지 판별 (차트 대신 등급 설명 목록)
|
|
486
|
+
*
|
|
487
|
+
* - valueType=ASSESSED: 감리자 직접 평가 → 등급 설명
|
|
488
|
+
* - CUSTOM/COMPOSITE이지만 value가 1~5 정수 (증강데이터): 등급 설명 + 히트맵
|
|
489
|
+
* - grades가 배열이 아닌 경우 (2D lookup 객체): 등급 설명 + 히트맵
|
|
490
|
+
*/
|
|
491
|
+
_isAssessmentOrIntegerScore() {
|
|
492
|
+
const kpi = this.selectedKpi;
|
|
493
|
+
if (!kpi)
|
|
494
|
+
return false;
|
|
495
|
+
// valueType 기반 판별 (우선)
|
|
496
|
+
if (kpi.valueType === 'ASSESSED')
|
|
497
|
+
return true;
|
|
498
|
+
// CUSTOM/COMPOSITE이지만 value가 1~5 정수 (증강데이터)
|
|
499
|
+
if ((kpi.scoreType === 'CUSTOM' || kpi.valueType === 'COMPOSITE') &&
|
|
500
|
+
kpi.value !== null &&
|
|
501
|
+
kpi.value === Math.floor(kpi.value) &&
|
|
502
|
+
kpi.value >= 1 &&
|
|
503
|
+
kpi.value <= 5)
|
|
504
|
+
return true;
|
|
505
|
+
// grades가 배열이 아닌 경우 (2D lookup 객체)
|
|
506
|
+
if (kpi.grades && !Array.isArray(kpi.grades))
|
|
507
|
+
return true;
|
|
508
|
+
return false;
|
|
509
|
+
}
|
|
510
|
+
/** 등급 기준 표시 (ASSESSED / 1~5 정수 score) — 현재 값 강조 + CUSTOM은 2D 히트맵도 표시 */
|
|
511
|
+
_renderAssessmentGrades() {
|
|
512
|
+
var _a, _b, _c;
|
|
513
|
+
const kpi = this.selectedKpi;
|
|
514
|
+
const currentScore = (kpi === null || kpi === void 0 ? void 0 : kpi.value) != null ? Math.round(kpi.value) : 0;
|
|
515
|
+
const grades = Array.isArray(kpi === null || kpi === void 0 ? void 0 : kpi.grades) ? kpi.grades : [];
|
|
516
|
+
const colors = ['#e53935', '#ff9800', '#ffca28', '#66bb6a', '#2e7d32'];
|
|
517
|
+
const is2D = (kpi === null || kpi === void 0 ? void 0 : kpi.scoreType) === 'CUSTOM' || (kpi === null || kpi === void 0 ? void 0 : kpi.valueType) === 'COMPOSITE' ||
|
|
518
|
+
((kpi === null || kpi === void 0 ? void 0 : kpi.grades) && typeof kpi.grades === 'object' && !Array.isArray(kpi.grades) && kpi.grades.type === 'PROGRESS_DEVIATION_LOOKUP');
|
|
519
|
+
// grades가 있으면 description 사용, 없으면 기본 라벨
|
|
520
|
+
const defaultLabels = ['매우 미흡', '미흡', '보통', '양호', '우수'];
|
|
521
|
+
const items = [1, 2, 3, 4, 5].map(score => {
|
|
522
|
+
const grade = grades.find((g) => g.score === score);
|
|
523
|
+
return {
|
|
524
|
+
score,
|
|
525
|
+
desc: (grade === null || grade === void 0 ? void 0 : grade.description) || (grade === null || grade === void 0 ? void 0 : grade.name) || defaultLabels[score - 1],
|
|
526
|
+
color: colors[score - 1],
|
|
527
|
+
active: score === currentScore
|
|
528
|
+
};
|
|
529
|
+
});
|
|
530
|
+
// ASSESSED(감리자 평가)이고 grades에 description이 있는 경우만 한 줄씩 상세 표시
|
|
531
|
+
// X13 등 COMPOSITE/CUSTOM은 인라인 칩으로 간결하게
|
|
532
|
+
const hasDescriptions = !is2D && grades.length > 0 && grades.some((g) => g.description);
|
|
533
|
+
return html `
|
|
534
|
+
<div style="padding: 20px;">
|
|
535
|
+
<div style="font-size: 1rem; font-weight: 700; margin-bottom: 16px; color: #333;">
|
|
536
|
+
등급 기준 ${currentScore >= 1 ? html `<span style="color: ${colors[currentScore - 1]}; margin-left: 8px;">현재: ${currentScore}점</span>` : ''}
|
|
537
|
+
</div>
|
|
538
|
+
${hasDescriptions
|
|
539
|
+
? items.map(item => html `
|
|
540
|
+
<div
|
|
541
|
+
style="
|
|
542
|
+
display: flex; align-items: center; gap: 10px;
|
|
543
|
+
padding: 10px 14px; margin-bottom: 6px;
|
|
544
|
+
border-radius: 8px;
|
|
545
|
+
background: ${item.active ? item.color + '18' : '#f9f9f9'};
|
|
546
|
+
border: 2px solid ${item.active ? item.color : 'transparent'};
|
|
547
|
+
transition: all 0.2s;
|
|
548
|
+
"
|
|
549
|
+
>
|
|
550
|
+
<span
|
|
551
|
+
style="
|
|
552
|
+
width: 32px; height: 32px; border-radius: 50%;
|
|
553
|
+
background: ${item.active ? item.color : '#e0e0e0'};
|
|
554
|
+
color: ${item.active ? '#fff' : '#999'};
|
|
555
|
+
display: flex; align-items: center; justify-content: center;
|
|
556
|
+
font-weight: 700; font-size: 14px; flex-shrink: 0;
|
|
557
|
+
"
|
|
558
|
+
>${item.score}</span>
|
|
559
|
+
<span style="font-size: 0.9rem; color: ${item.active ? '#333' : '#666'}; font-weight: ${item.active ? '600' : '400'};">
|
|
560
|
+
${item.desc}
|
|
561
|
+
</span>
|
|
562
|
+
</div>`)
|
|
563
|
+
: html `
|
|
564
|
+
<div style="display: flex; gap: 6px; flex-wrap: wrap;">
|
|
565
|
+
${items.map(item => html `
|
|
566
|
+
<span style="
|
|
567
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
568
|
+
width: 36px; height: 36px; border-radius: 50%;
|
|
569
|
+
background: ${item.active ? item.color : '#e0e0e0'};
|
|
570
|
+
color: ${item.active ? '#fff' : '#999'};
|
|
571
|
+
font-weight: 700; font-size: 14px;
|
|
572
|
+
border: 2px solid ${item.active ? item.color : 'transparent'};
|
|
573
|
+
">${item.score}</span>`)}
|
|
574
|
+
</div>
|
|
575
|
+
`}
|
|
576
|
+
|
|
577
|
+
${is2D && ((_a = kpi === null || kpi === void 0 ? void 0 : kpi.grades) === null || _a === void 0 ? void 0 : _a.rows)
|
|
578
|
+
? html `
|
|
579
|
+
<div style="margin-top: 20px; border-top: 1px solid #e0e0e0; padding-top: 16px;">
|
|
580
|
+
<div style="font-size: 0.9rem; font-weight: 700; color: #333; margin-bottom: 8px;">
|
|
581
|
+
공정률 × 공기편차 성과 분포
|
|
582
|
+
</div>
|
|
583
|
+
<div style="height: 250px;">
|
|
584
|
+
<kpi-2d-lookup-chart
|
|
585
|
+
.grades=${kpi.grades}
|
|
586
|
+
.progressRate=${(_c = (_b = this.project) === null || _b === void 0 ? void 0 : _b.totalProgress) !== null && _c !== void 0 ? _c : 50}
|
|
587
|
+
></kpi-2d-lookup-chart>
|
|
588
|
+
</div>
|
|
589
|
+
</div>
|
|
590
|
+
`
|
|
591
|
+
: ''}
|
|
592
|
+
</div>
|
|
593
|
+
`;
|
|
594
|
+
}
|
|
464
595
|
_closePopup() {
|
|
465
|
-
this.
|
|
596
|
+
this.showScoreDistribution = false;
|
|
466
597
|
this.selectedKpi = null;
|
|
467
598
|
}
|
|
468
599
|
getMetricValue(kpiNamePattern) {
|
|
@@ -507,8 +638,6 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
507
638
|
name: '품질성과 (Y3)',
|
|
508
639
|
score: this.getYKpiScoreFormatted('Y3. 품질성과'),
|
|
509
640
|
metrics: [
|
|
510
|
-
{ label: '품질시험 불합격률', pattern: 'X31', value: this.getMetricValue('X31') },
|
|
511
|
-
{ label: '검수자재 불합격률', pattern: 'X32', value: this.getMetricValue('X32') },
|
|
512
641
|
{ label: '검측 불합격률', pattern: 'X33', value: this.getMetricValue('X33') },
|
|
513
642
|
{ label: '품질 SL-PA 결과', pattern: 'X34', value: this.getMetricValue('X34') },
|
|
514
643
|
{ label: '품질성과 수준 평가', pattern: 'X35', value: this.getMetricValue('X35') }
|
|
@@ -654,6 +783,7 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
654
783
|
}
|
|
655
784
|
};
|
|
656
785
|
SvProjectDetailPage.styles = [
|
|
786
|
+
ScrollbarStyles,
|
|
657
787
|
css `
|
|
658
788
|
:host {
|
|
659
789
|
display: flex;
|
|
@@ -1094,7 +1224,7 @@ __decorate([
|
|
|
1094
1224
|
__decorate([
|
|
1095
1225
|
state(),
|
|
1096
1226
|
__metadata("design:type", Boolean)
|
|
1097
|
-
], SvProjectDetailPage.prototype, "
|
|
1227
|
+
], SvProjectDetailPage.prototype, "showScoreDistribution", void 0);
|
|
1098
1228
|
__decorate([
|
|
1099
1229
|
state(),
|
|
1100
1230
|
__metadata("design:type", Object)
|