@dssp/dkpi 1.0.0-alpha.68 → 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.
Files changed (33) hide show
  1. package/dist-client/components/kpi-2d-lookup-chart.js +2 -7
  2. package/dist-client/components/kpi-2d-lookup-chart.js.map +1 -1
  3. package/dist-client/components/kpi-boxplot-chart.js +0 -4
  4. package/dist-client/components/kpi-boxplot-chart.js.map +1 -1
  5. package/dist-client/components/kpi-lookup-chart.d.ts +14 -1
  6. package/dist-client/components/kpi-lookup-chart.js +101 -36
  7. package/dist-client/components/kpi-lookup-chart.js.map +1 -1
  8. package/dist-client/pages/kpi-admin/dssp-kpi-overview.d.ts +46 -0
  9. package/dist-client/pages/kpi-admin/dssp-kpi-overview.js +378 -0
  10. package/dist-client/pages/kpi-admin/dssp-kpi-overview.js.map +1 -0
  11. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js +0 -9
  12. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js.map +1 -1
  13. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js +0 -1
  14. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js.map +1 -1
  15. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +0 -4
  16. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -1
  17. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js +0 -11
  18. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js.map +1 -1
  19. package/dist-client/pages/sv-project-completed-list.js +0 -1
  20. package/dist-client/pages/sv-project-completed-list.js.map +1 -1
  21. package/dist-client/pages/sv-project-detail.d.ts +10 -0
  22. package/dist-client/pages/sv-project-detail.js +136 -24
  23. package/dist-client/pages/sv-project-detail.js.map +1 -1
  24. package/dist-client/pages/sv-project-list.js +0 -1
  25. package/dist-client/pages/sv-project-list.js.map +1 -1
  26. package/dist-client/route.d.ts +1 -1
  27. package/dist-client/route.js +3 -0
  28. package/dist-client/route.js.map +1 -1
  29. package/dist-client/tsconfig.tsbuildinfo +1 -1
  30. package/dist-server/tsconfig.tsbuildinfo +1 -1
  31. package/package.json +2 -2
  32. package/schema.graphql +26 -5
  33. 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"]}
@@ -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';
@@ -231,20 +232,17 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
231
232
  </div>
232
233
  <div class="popup-chart-container">
233
234
  ${ /*
234
- * X13 (일정 이탈 수준)은 PROGRESS_DEVIATION_LOOKUP 타입으로,
235
- * 본래 공정률(%)×편차율(%) 2D 룩업으로 1~5점을 산출하는 KPI이다.
235
+ * scoreType 기반 차트 분기:
236
236
  *
237
- * 실제 프로젝트(DSSP/DCSP)에서는 kpi_values.value에 편차율(raw 연속값)이,
238
- * score에 2D 룩업 결과(1~5) 저장되므로 히트맵(kpi-2d-lookup-chart)이 적합하다.
237
+ * CUSTOM (X13 등): 2D 룩업 히트맵 — 단, 증강데이터에서는 value가
238
+ * 이미 1~5 정수(룩업 결과)이므로 히트맵 대신 게이지로 폴백.
239
+ * 실제 프로젝트에서는 value가 편차율(소수점)이므로 히트맵 표시.
239
240
  *
240
- * 그러나 증강데이터(DKPI)에서는 원본 Excel이 이미 룩업 결과(1~5 정수)
241
- * 제공하므로 value=score=1~5 된다. 경우 히트맵에 편차율 축을 그릴 수 없으므로
242
- * 1~5 평가 게이지(kpi-lookup-chart의 세그먼트 바)로 표시한다.
243
- *
244
- * 판별: value가 1~5 정수이면 증강데이터 → 게이지,
245
- * 그 외(소수점 편차율)이면 실제 프로젝트 → 히트맵
241
+ * LOOKUP: 성과 분포 커브 (kpi-lookup-chart)
242
+ * ASSESSMENT: 5단계 게이지 (kpi-lookup-chart 자동 감지)
246
243
  */''}
247
- ${((_g = this.selectedKpi.grades) === null || _g === void 0 ? void 0 : _g.type) === 'PROGRESS_DEVIATION_LOOKUP' &&
244
+ ${(this.selectedKpi.scoreType === 'CUSTOM' ||
245
+ ((_g = this.selectedKpi.grades) === null || _g === void 0 ? void 0 : _g.type) === 'PROGRESS_DEVIATION_LOOKUP') &&
248
246
  this.selectedKpi.value !== null &&
249
247
  !(this.selectedKpi.value === Math.floor(this.selectedKpi.value) &&
250
248
  this.selectedKpi.value >= 1 &&
@@ -258,14 +256,17 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
258
256
  kpiName=${this.selectedKpi.kpiName || ''}
259
257
  ></kpi-2d-lookup-chart>
260
258
  `
261
- : html `
262
- <kpi-lookup-chart
263
- .grades=${this.selectedKpi.grades || []}
264
- .value=${this.selectedKpi.value}
265
- unit=${this.selectedKpi.unit || ''}
266
- kpiName=${this.selectedKpi.kpiName || ''}
267
- ></kpi-lookup-chart>
268
- `}
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
+ `}
269
270
  </div>
270
271
  </div>
271
272
  </div>
@@ -363,7 +364,6 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
363
364
  `
364
365
  });
365
366
  this.kpiComprehensiveStats = response.data.totalKpiZValueComprehensiveStats || [];
366
- console.log('KPI Z-Value Comprehensive Stats:', this.kpiComprehensiveStats);
367
367
  }
368
368
  catch (error) {
369
369
  console.error('Failed to fetch KPI comprehensive stats:', error);
@@ -389,7 +389,6 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
389
389
  `
390
390
  });
391
391
  this.kpiYComprehensiveStats = response.data.totalKpiYValueComprehensiveStats || [];
392
- console.log('KPI Y-Value Comprehensive Stats:', this.kpiYComprehensiveStats);
393
392
  }
394
393
  catch (error) {
395
394
  console.error('Failed to fetch KPI Y comprehensive stats:', error);
@@ -416,7 +415,6 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
416
415
  variables: { projectId }
417
416
  });
418
417
  this.projectXKpiValues = response.data.projectXKpiValues || [];
419
- console.log('Project X KPI Values:', this.projectXKpiValues);
420
418
  }
421
419
  catch (error) {
422
420
  console.error('Failed to fetch project X KPI values:', error);
@@ -451,6 +449,8 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
451
449
  id
452
450
  name
453
451
  grades
452
+ scoreType
453
+ valueType
454
454
  vizMeta
455
455
  }
456
456
  }
@@ -470,6 +470,8 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
470
470
  this.selectedKpi = {
471
471
  kpiName: displayName,
472
472
  grades: kpi.grades || [],
473
+ scoreType: kpi.scoreType || '',
474
+ valueType: kpi.valueType || '',
473
475
  value: (_b = kpiValue.value) !== null && _b !== void 0 ? _b : null,
474
476
  unit: ((_c = kpi.vizMeta) === null || _c === void 0 ? void 0 : _c.unit) || ''
475
477
  };
@@ -479,6 +481,117 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
479
481
  console.error('Error fetching KPI:', error);
480
482
  }
481
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
+ }
482
595
  _closePopup() {
483
596
  this.showScoreDistribution = false;
484
597
  this.selectedKpi = null;
@@ -525,8 +638,6 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
525
638
  name: '품질성과 (Y3)',
526
639
  score: this.getYKpiScoreFormatted('Y3. 품질성과'),
527
640
  metrics: [
528
- { label: '품질시험 불합격률', pattern: 'X31', value: this.getMetricValue('X31') },
529
- { label: '검수자재 불합격률', pattern: 'X32', value: this.getMetricValue('X32') },
530
641
  { label: '검측 불합격률', pattern: 'X33', value: this.getMetricValue('X33') },
531
642
  { label: '품질 SL-PA 결과', pattern: 'X34', value: this.getMetricValue('X34') },
532
643
  { label: '품질성과 수준 평가', pattern: 'X35', value: this.getMetricValue('X35') }
@@ -672,6 +783,7 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
672
783
  }
673
784
  };
674
785
  SvProjectDetailPage.styles = [
786
+ ScrollbarStyles,
675
787
  css `
676
788
  :host {
677
789
  display: flex;