@dssp/dkpi 1.0.0-alpha.87 → 1.0.0-alpha.89

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 (70) hide show
  1. package/_index.html +4 -4
  2. package/assets/helps/index.md +1 -1
  3. package/assets/manifest.json +2 -2
  4. package/dist-client/bootstrap.js +1 -1
  5. package/dist-client/bootstrap.js.map +1 -1
  6. package/dist-client/components/kpi-boxplot-chart.d.ts +7 -0
  7. package/dist-client/components/kpi-boxplot-chart.js +20 -5
  8. package/dist-client/components/kpi-boxplot-chart.js.map +1 -1
  9. package/dist-client/components/kpi-lookup-chart.js +1 -17
  10. package/dist-client/components/kpi-lookup-chart.js.map +1 -1
  11. package/dist-client/components/kpi-mini-trend-chart.d.ts +1 -1
  12. package/dist-client/components/kpi-mini-trend-chart.js +45 -13
  13. package/dist-client/components/kpi-mini-trend-chart.js.map +1 -1
  14. package/dist-client/components/kpi-radar-chart.js +2 -2
  15. package/dist-client/components/kpi-radar-chart.js.map +1 -1
  16. package/dist-client/components/kpi-single-boxplot-chart.d.ts +11 -0
  17. package/dist-client/components/kpi-single-boxplot-chart.js +45 -26
  18. package/dist-client/components/kpi-single-boxplot-chart.js.map +1 -1
  19. package/dist-client/components/kpi-step-lookup-chart.d.ts +33 -0
  20. package/dist-client/components/kpi-step-lookup-chart.js +181 -0
  21. package/dist-client/components/kpi-step-lookup-chart.js.map +1 -0
  22. package/dist-client/components/kpi-trend-chart.js +41 -20
  23. package/dist-client/components/kpi-trend-chart.js.map +1 -1
  24. package/dist-client/google-map/common-google-map.js +5 -1
  25. package/dist-client/google-map/common-google-map.js.map +1 -1
  26. package/dist-client/pages/kpi-admin/dssp-kpi-overview.d.ts +2 -1
  27. package/dist-client/pages/kpi-admin/dssp-kpi-overview.js +24 -9
  28. package/dist-client/pages/kpi-admin/dssp-kpi-overview.js.map +1 -1
  29. package/dist-client/pages/kpi-admin/kpi-system-guide.js +311 -158
  30. package/dist-client/pages/kpi-admin/kpi-system-guide.js.map +1 -1
  31. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.d.ts +8 -0
  32. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js +142 -56
  33. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js.map +1 -1
  34. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.d.ts +0 -9
  35. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js +6 -91
  36. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js.map +1 -1
  37. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.d.ts +2 -0
  38. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js +162 -69
  39. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js.map +1 -1
  40. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +85 -41
  41. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -1
  42. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js +15 -7
  43. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js.map +1 -1
  44. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js +16 -25
  45. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js.map +1 -1
  46. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js +9 -13
  47. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js.map +1 -1
  48. package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.js +9 -15
  49. package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.js.map +1 -1
  50. package/dist-client/pages/sv-project-complete.js +7 -11
  51. package/dist-client/pages/sv-project-complete.js.map +1 -1
  52. package/dist-client/pages/sv-project-completed-list.js +4 -4
  53. package/dist-client/pages/sv-project-completed-list.js.map +1 -1
  54. package/dist-client/pages/sv-project-detail.d.ts +1 -0
  55. package/dist-client/pages/sv-project-detail.js +73 -55
  56. package/dist-client/pages/sv-project-detail.js.map +1 -1
  57. package/dist-client/pages/sv-project-list.js +15 -12
  58. package/dist-client/pages/sv-project-list.js.map +1 -1
  59. package/dist-client/tsconfig.tsbuildinfo +1 -1
  60. package/dist-client/viewparts/menu-tools.js +17 -3
  61. package/dist-client/viewparts/menu-tools.js.map +1 -1
  62. package/dist-server/service/kpi-stat/kpi-stat-query.js +20 -19
  63. package/dist-server/service/kpi-stat/kpi-stat-query.js.map +1 -1
  64. package/dist-server/tsconfig.tsbuildinfo +1 -1
  65. package/openapi/unstable.yaml +2 -2
  66. package/package.json +3 -3
  67. package/schema.graphql +20 -0
  68. package/translations/ko.json +2 -2
  69. package/views/auth-page.html +4 -4
  70. package/views/public/home.html +5 -5
@@ -1 +1 @@
1
- {"version":3,"file":"kpi-system-guide.js","sourceRoot":"","sources":["../../../client/pages/kpi-admin/kpi-system-guide.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, css, nothing } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\nimport { PageView } from '@operato/shell'\nimport { ScrollbarStyles } from '@operato/styles'\nimport { client } from '@operato/graphql'\nimport { navigate } from '@operato/shell'\nimport gql from 'graphql-tag'\n\n/**\n * KPI 시스템 종합 안내 페이지\n *\n * DB에서 실시간으로 KPI 계층, 산식, 메트릭, scoreType/valueType 등을 조회하여\n * 시스템 전체 구조를 한눈에 파악할 수 있는 매뉴얼 겸 라이브 대시보드.\n */\n\nconst GET_KPI_SYSTEM = gql`\n query {\n kpiRoot: kpisLevel1 {\n id\n name\n description\n formula\n weight\n scoreType\n valueType\n active\n kpis: children {\n id\n name\n description\n formula\n weight\n scoreType\n valueType\n active\n grades\n }\n }\n kpiMetrics {\n items {\n id\n name\n unit\n source\n collectType\n active\n }\n }\n }\n`\n\nconst SCORE_TYPE_LABELS: Record<string, { label: string; color: string }> = {\n DIRECT: { label: 'DIRECT (변환 없음)', color: '#888' },\n FORMULA: { label: 'FORMULA (수식 변환)', color: '#9c27b0' },\n LOOKUP: { label: 'LOOKUP (등급표)', color: '#1976d2' },\n CUSTOM: { label: 'CUSTOM (2D 룩업)', color: '#e65100' }\n}\n\n/** 외부 시스템 URL 매핑 */\nconst EXTERNAL_SYSTEM_URLS: Record<string, string> = {\n '세움터(EAIS)': 'https://cloud.eais.go.kr',\n '키스콘(KISCON)': 'https://www.kiscon.net',\n '올바로(Allbaro)': 'https://www.allbaro.or.kr',\n '전자카드제': 'https://www.cw.or.kr'\n}\n\nconst VALUE_TYPE_LABELS: Record<string, { label: string; color: string; icon: string }> = {\n MEASURED: { label: '외부 수집', color: '#1976d2', icon: '📡' },\n ASSESSED: { label: '감리자 평가', color: '#2e7d32', icon: '✍️' },\n CALCULATED: { label: '산식 계산', color: '#9c27b0', icon: '🔢' },\n COMPOSITE: { label: '복합 입력', color: '#e65100', icon: '🔀' }\n}\n\n@customElement('kpi-system-guide')\nexport class KpiSystemGuide extends PageView {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: block;\n overflow-y: auto;\n background: #f5f6fa;\n padding: 24px;\n }\n\n .page-title {\n font-size: 1.5rem;\n font-weight: 800;\n color: #1a237e;\n margin-bottom: 4px;\n }\n .page-subtitle {\n font-size: 0.9rem;\n color: #666;\n margin-bottom: 24px;\n }\n\n /* 계층 트리 */\n .tree-section {\n background: #fff;\n border-radius: 12px;\n padding: 24px;\n margin-bottom: 24px;\n box-shadow: 0 2px 8px rgba(0,0,0,0.06);\n }\n .section-title {\n font-size: 1.1rem;\n font-weight: 700;\n color: #333;\n margin-bottom: 16px;\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n /* Z 루트 */\n .z-node {\n background: linear-gradient(135deg, #1a237e, #283593);\n color: #fff;\n border-radius: 10px;\n padding: 16px 20px;\n margin-bottom: 16px;\n cursor: pointer;\n }\n .z-node:hover { opacity: 0.95; }\n .z-name { font-size: 1.1rem; font-weight: 700; }\n .z-formula { font-size: 0.8rem; opacity: 0.8; margin-top: 4px; font-family: monospace; }\n\n /* Y 그룹 */\n .y-group {\n margin-bottom: 16px;\n border: 1px solid #e0e0e0;\n border-radius: 10px;\n overflow: hidden;\n }\n .y-header {\n background: linear-gradient(135deg, #0c4da2, #1565c0);\n color: #fff;\n padding: 12px 16px;\n display: flex;\n justify-content: space-between;\n align-items: center;\n cursor: pointer;\n }\n .y-header:hover { opacity: 0.95; }\n .y-name { font-weight: 700; font-size: 1rem; }\n .y-weight {\n background: rgba(255,255,255,0.2);\n padding: 2px 10px;\n border-radius: 12px;\n font-size: 0.8rem;\n }\n .y-body { padding: 12px; }\n .y-formula {\n font-size: 0.8rem;\n color: #555;\n background: #f8f9fa;\n padding: 8px 12px;\n border-radius: 6px;\n font-family: monospace;\n margin-bottom: 10px;\n word-break: break-all;\n }\n\n /* X 카드 */\n .x-cards { display: flex; flex-wrap: wrap; gap: 8px; }\n .x-card {\n flex: 1 1 220px;\n background: #fff;\n border: 1px solid #e8e8e8;\n border-radius: 8px;\n padding: 10px 14px;\n cursor: pointer;\n transition: all 0.2s;\n min-width: 200px;\n }\n .x-card:hover {\n border-color: #1976d2;\n box-shadow: 0 2px 8px rgba(25,118,210,0.15);\n }\n .x-name { font-weight: 600; font-size: 0.85rem; color: #333; margin-bottom: 4px; }\n .x-formula {\n font-size: 0.75rem; color: #888; font-family: monospace;\n white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\n margin-bottom: 6px;\n }\n .x-badges { display: flex; gap: 4px; flex-wrap: wrap; }\n .badge {\n font-size: 0.65rem;\n padding: 2px 6px;\n border-radius: 4px;\n font-weight: 600;\n }\n .badge-score { background: #e3f2fd; color: #1565c0; }\n .badge-value { background: #e8f5e9; color: #2e7d32; }\n\n /* 메트릭 섹션 */\n .metrics-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));\n gap: 10px;\n }\n .metric-card {\n background: #fff;\n border: 1px solid #e8e8e8;\n border-radius: 8px;\n padding: 10px 14px;\n transition: all 0.2s;\n }\n .metric-card:hover { border-color: #4caf50; }\n .metric-name { font-weight: 600; font-size: 0.85rem; color: #333; }\n .metric-unit { font-size: 0.75rem; color: #888; }\n .metric-used {\n font-size: 0.7rem; color: #1976d2; margin-top: 4px;\n }\n\n /* 팝업 */\n .popup-overlay {\n position: fixed; top: 0; left: 0; right: 0; bottom: 0;\n background: rgba(0,0,0,0.3); z-index: 1000;\n display: flex; align-items: center; justify-content: center;\n }\n .popup-box {\n background: #fff; border-radius: 12px; padding: 24px;\n min-width: 400px; max-width: 600px; max-height: 80vh;\n overflow-y: auto; box-shadow: 0 8px 32px rgba(0,0,0,0.2);\n }\n .popup-title {\n font-size: 1.1rem; font-weight: 700; color: #1a237e;\n margin-bottom: 16px; display: flex; justify-content: space-between;\n }\n .popup-close {\n background: none; border: none; font-size: 1.2rem;\n cursor: pointer; color: #999;\n }\n .popup-row {\n display: flex; gap: 8px; margin-bottom: 8px;\n font-size: 0.85rem;\n }\n .popup-label { color: #888; min-width: 100px; flex-shrink: 0; }\n .popup-value { color: #333; font-weight: 500; }\n .popup-formula {\n background: #f5f5f5; padding: 10px; border-radius: 6px;\n font-family: monospace; font-size: 0.85rem; margin: 8px 0;\n word-break: break-all;\n }\n .popup-link {\n color: #1976d2; cursor: pointer; text-decoration: underline;\n font-size: 0.85rem;\n }\n .popup-link:hover { color: #0d47a1; }\n\n /* 범례 */\n .legend {\n display: flex; gap: 16px; flex-wrap: wrap;\n margin-bottom: 20px; padding: 12px 16px;\n background: #fff; border-radius: 8px;\n box-shadow: 0 1px 4px rgba(0,0,0,0.04);\n }\n .legend-group { display: flex; align-items: center; gap: 4px; font-size: 0.8rem; }\n .legend-title { font-weight: 700; color: #555; margin-right: 4px; }\n `\n ]\n\n @state() kpiRoot: any[] = []\n @state() metrics: any[] = []\n @state() loading = true\n @state() selectedDetail: any = null\n\n get context() {\n return { title: 'KPI 시스템 가이드' }\n }\n\n async firstUpdated() {\n try {\n const { data } = await client.query({ query: GET_KPI_SYSTEM })\n this.kpiRoot = data.kpiRoot || []\n this.metrics = data.kpiMetrics?.items || []\n } catch (e) {\n console.error('Failed to load KPI system data:', e)\n } finally {\n this.loading = false\n }\n }\n\n /** 메트릭이 사용되는 KPI 목록 — 비활성 KPI/Y 그룹 제외 */\n private getMetricUsage(metricName: string): string[] {\n const used: string[] = []\n for (const y of this.kpiRoot) {\n if (y.active === false) continue\n for (const x of (y.kpis || [])) {\n if (x.active === false) continue\n if (x.formula?.includes(`[${metricName}]`)) {\n used.push(x.name)\n }\n }\n }\n return used\n }\n\n private showDetail(item: any, type: 'kpi' | 'metric') {\n this.selectedDetail = { ...item, _type: type }\n }\n\n private closeDetail() {\n this.selectedDetail = null\n }\n\n render() {\n if (this.loading) return html`<div style=\"padding:40px;text-align:center;color:#888;\">로딩 중...</div>`\n\n // Z 노드 (루트의 부모)\n const allX = this.kpiRoot.flatMap((y: any) => (y.kpis || []).filter((x: any) => x.active !== false))\n\n return html`\n <div class=\"page-title\">KPI 시스템 가이드</div>\n <div class=\"page-subtitle\">\n 건축현장 성과평가 KPI 체계의 전체 구조, 산식, 메트릭 연관관계를 확인합니다.\n 모든 정보는 DB에서 실시간 조회됩니다.\n </div>\n\n <!-- 범례 -->\n <div class=\"legend\">\n <span class=\"legend-title\">Score 산정:</span>\n ${Object.entries(SCORE_TYPE_LABELS).map(([, v]) =>\n html`<span class=\"legend-group\"><span class=\"badge badge-score\">${v.label}</span></span>`\n )}\n <span style=\"width:16px\"></span>\n <span class=\"legend-title\">Value 획득:</span>\n ${Object.entries(VALUE_TYPE_LABELS).map(([, v]) =>\n html`<span class=\"legend-group\">${v.icon} <span class=\"badge badge-value\">${v.label}</span></span>`\n )}\n </div>\n\n <!-- 1. KPI 계층 구조 -->\n <div class=\"tree-section\">\n <div class=\"section-title\">📊 KPI 계층 구조 (Z → Y → X)</div>\n\n <!-- Z 루트 -->\n <div class=\"z-node\" @click=${() => this.showDetail({ name: 'Z. 전체스코어', description: 'Y1~Y6 가중합', formula: 'Y1×w1 + Y2×w2 + ... + Y6×w6', scoreType: 'DIRECT', valueType: 'CALCULATED' }, 'kpi')}>\n <div class=\"z-name\">Z. 전체스코어</div>\n <div class=\"z-formula\">= ${this.kpiRoot\n .filter((y: any) => y.active !== false)\n .map((y: any) => `${y.name.split('.')[0]}×${y.weight}`)\n .join(' + ')}</div>\n </div>\n\n <!-- Y 그룹들 (비활성 Y 제외) -->\n ${this.kpiRoot.filter((y: any) => y.active !== false).map((y: any) => {\n const activeKpis = (y.kpis || []).filter((x: any) => x.active !== false)\n return html`\n <div class=\"y-group\">\n <div class=\"y-header\" @click=${() => this.showDetail(y, 'kpi')}>\n <span class=\"y-name\">${y.name}</span>\n <span class=\"y-weight\">가중치: ${y.weight}</span>\n </div>\n <div class=\"y-body\">\n ${y.formula ? html`<div class=\"y-formula\">${y.formula}</div>` : ''}\n <div class=\"x-cards\">\n ${activeKpis.map((x: any) => {\n const st = SCORE_TYPE_LABELS[x.scoreType] || { label: x.scoreType || '미설정', color: '#999' }\n const vt = VALUE_TYPE_LABELS[x.valueType] || { label: x.valueType || '미설정', color: '#999', icon: '?' }\n return html`\n <div class=\"x-card\" @click=${() => this.showDetail(x, 'kpi')}>\n <div class=\"x-name\">${x.name}</div>\n <div class=\"x-formula\">${x.formula || '—'}</div>\n <div class=\"x-badges\">\n <span class=\"badge badge-score\">${st.label}</span>\n <span class=\"badge badge-value\">${vt.icon} ${vt.label}</span>\n </div>\n </div>\n `\n })}\n </div>\n </div>\n </div>\n `\n })}\n </div>\n\n <!-- 2. 원천 메트릭 — active=true 이면서 활성 KPI formula 에 실제 참조되는 것만 -->\n ${(() => {\n const usedMetrics = this.metrics\n .filter((m: any) => m.active)\n .map((m: any) => ({ m, usage: this.getMetricUsage(m.name) }))\n .filter(({ usage }: any) => usage.length > 0)\n return html`\n <div class=\"tree-section\">\n <div class=\"section-title\">📋 원천 메트릭 (${usedMetrics.length}개 사용)</div>\n <div class=\"metrics-grid\">\n ${usedMetrics.map(({ m, usage }: any) => html`\n <div class=\"metric-card\" @click=${() => this.showDetail(m, 'metric')}>\n <div class=\"metric-name\">${m.name}</div>\n <div class=\"metric-unit\">\n ${m.unit || '—'} ·\n ${m.collectType === 'EXTERNAL' && m.source\n ? EXTERNAL_SYSTEM_URLS[m.source]\n ? html`<a href=\"${EXTERNAL_SYSTEM_URLS[m.source]}\" target=\"_blank\" rel=\"noopener\"\n style=\"color:#1976d2;text-decoration:none;\" @click=${(e: Event) => e.stopPropagation()}\n >${m.source} ↗</a>`\n : m.source\n : m.collectType || 'MANUAL'}\n </div>\n <div class=\"metric-used\">→ ${usage.join(', ')}</div>\n </div>\n `)}\n </div>\n </div>\n `\n })()}\n\n <!-- 3. 시스템 요약 -->\n <div class=\"tree-section\">\n <div class=\"section-title\">📈 시스템 요약</div>\n <div style=\"display:flex;gap:16px;flex-wrap:wrap;\">\n <div style=\"flex:1;min-width:200px;background:#e3f2fd;padding:16px;border-radius:8px;text-align:center;\">\n <div style=\"font-size:2rem;font-weight:800;color:#1565c0;\">${allX.length}</div>\n <div style=\"font-size:0.85rem;color:#555;\">X-level 지표</div>\n </div>\n <div style=\"flex:1;min-width:200px;background:#e8f5e9;padding:16px;border-radius:8px;text-align:center;\">\n <div style=\"font-size:2rem;font-weight:800;color:#2e7d32;\">${this.kpiRoot.filter((y: any) => y.active !== false).length}</div>\n <div style=\"font-size:0.85rem;color:#555;\">Y-level 영역</div>\n </div>\n <div style=\"flex:1;min-width:200px;background:#fff3e0;padding:16px;border-radius:8px;text-align:center;\">\n <div style=\"font-size:2rem;font-weight:800;color:#e65100;\">${this.metrics.filter((m: any) => m.active && this.getMetricUsage(m.name).length > 0).length}</div>\n <div style=\"font-size:0.85rem;color:#555;\">원천 메트릭</div>\n </div>\n <div style=\"flex:1;min-width:200px;background:#fce4ec;padding:16px;border-radius:8px;text-align:center;\">\n <div style=\"font-size:2rem;font-weight:800;color:#c62828;\">${allX.filter((x: any) => x.valueType === 'ASSESSED').length}</div>\n <div style=\"font-size:0.85rem;color:#555;\">감리자 평가 항목</div>\n </div>\n </div>\n </div>\n\n <!-- 팝업 -->\n ${this.selectedDetail ? this._renderDetailPopup() : nothing}\n `\n }\n\n private _renderDetailPopup() {\n const d = this.selectedDetail\n const isKpi = d._type === 'kpi'\n\n return html`\n <div class=\"popup-overlay\" @click=${() => this.closeDetail()}>\n <div class=\"popup-box\" @click=${(e: Event) => e.stopPropagation()}>\n <div class=\"popup-title\">\n <span>${d.name}</span>\n <button class=\"popup-close\" @click=${() => this.closeDetail()}>×</button>\n </div>\n\n ${d.description ? html`\n <div class=\"popup-row\">\n <span class=\"popup-label\">설명</span>\n <span class=\"popup-value\">${d.description}</span>\n </div>\n ` : ''}\n\n ${isKpi ? html`\n ${d.formula ? html`\n <div class=\"popup-row\"><span class=\"popup-label\">산식</span></div>\n <div class=\"popup-formula\">${d.formula}</div>\n ` : ''}\n\n ${d.scoreType ? html`\n <div class=\"popup-row\">\n <span class=\"popup-label\">Score 산정</span>\n <span class=\"popup-value\" style=\"color:${(SCORE_TYPE_LABELS[d.scoreType] || {}).color || '#333'}\">\n ${(SCORE_TYPE_LABELS[d.scoreType] || {}).label || d.scoreType}\n </span>\n </div>\n ` : ''}\n\n ${d.valueType ? html`\n <div class=\"popup-row\">\n <span class=\"popup-label\">Value 획득</span>\n <span class=\"popup-value\" style=\"color:${(VALUE_TYPE_LABELS[d.valueType] || {}).color || '#333'}\">\n ${(VALUE_TYPE_LABELS[d.valueType] || {}).icon || ''} ${(VALUE_TYPE_LABELS[d.valueType] || {}).label || d.valueType}\n </span>\n </div>\n ` : ''}\n\n ${d.weight ? html`\n <div class=\"popup-row\">\n <span class=\"popup-label\">가중치</span>\n <span class=\"popup-value\">${d.weight}</span>\n </div>\n ` : ''}\n\n <div style=\"margin-top:16px;padding-top:12px;border-top:1px solid #eee;\">\n <span class=\"popup-link\" @click=${() => { this.closeDetail(); navigate('kpi-overview') }}>\n → KPI 개요에서 보기\n </span>\n &nbsp;&nbsp;\n <span class=\"popup-link\" @click=${() => { this.closeDetail(); navigate('kpi-admin') }}>\n → KPI 관리에서 편집\n </span>\n </div>\n ` : html`\n <div class=\"popup-row\">\n <span class=\"popup-label\">단위</span>\n <span class=\"popup-value\">${d.unit || '—'}</span>\n </div>\n <div class=\"popup-row\">\n <span class=\"popup-label\">수집 방식</span>\n <span class=\"popup-value\">${d.collectType || 'MANUAL'}</span>\n </div>\n <div class=\"popup-row\">\n <span class=\"popup-label\">데이터 소스</span>\n <span class=\"popup-value\">\n ${d.source && EXTERNAL_SYSTEM_URLS[d.source]\n ? html`<a href=\"${EXTERNAL_SYSTEM_URLS[d.source]}\" target=\"_blank\" rel=\"noopener\" style=\"color:#1976d2;\">${d.source} ↗</a>`\n : d.source || '미설정'}\n </span>\n </div>\n\n <div style=\"margin-top:12px;\">\n <div class=\"popup-label\" style=\"margin-bottom:6px;\">사용처:</div>\n ${this.getMetricUsage(d.name).map(kpi =>\n html`<div style=\"font-size:0.85rem;color:#1976d2;margin-bottom:2px;\">· ${kpi}</div>`\n )}\n ${this.getMetricUsage(d.name).length === 0 ? html`<div style=\"color:#ccc;font-size:0.85rem;\">미사용</div>` : ''}\n </div>\n\n <div style=\"margin-top:16px;padding-top:12px;border-top:1px solid #eee;\">\n <span class=\"popup-link\" @click=${() => { this.closeDetail(); navigate('kpi-metric-value-list') }}>\n → 메트릭 값 관리\n </span>\n </div>\n `}\n </div>\n </div>\n `\n }\n}\n"]}
1
+ {"version":3,"file":"kpi-system-guide.js","sourceRoot":"","sources":["../../../client/pages/kpi-admin/kpi-system-guide.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, css, nothing } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\nimport { PageView } from '@operato/shell'\nimport { ScrollbarStyles } from '@operato/styles'\nimport { client } from '@operato/graphql'\nimport { navigate } from '@operato/shell'\nimport gql from 'graphql-tag'\n\n/**\n * KPI 시스템 종합 안내 페이지\n *\n * DB에서 실시간으로 KPI 계층, 산식, 메트릭, scoreType/valueType 등을 조회하여\n * 시스템 전체 구조를 한눈에 파악할 수 있는 매뉴얼 겸 라이브 대시보드.\n */\n\nconst GET_KPI_SYSTEM = gql`\n query {\n kpiRoot: kpisLevel1 {\n id\n name\n description\n formula\n weight\n scoreType\n valueType\n active\n kpis: children {\n id\n name\n description\n formula\n weight\n scoreType\n valueType\n active\n grades\n }\n }\n kpiMetrics {\n items {\n id\n name\n unit\n source\n collectType\n active\n }\n }\n }\n`\n\nconst SCORE_TYPE_LABELS: Record<string, { label: string; color: string }> = {\n DIRECT: { label: 'DIRECT (변환 없음)', color: '#888' },\n FORMULA: { label: 'FORMULA (수식 변환)', color: '#9c27b0' },\n LOOKUP: { label: 'LOOKUP (등급표)', color: '#1976d2' },\n CUSTOM: { label: 'CUSTOM (2D 룩업)', color: '#e65100' }\n}\n\n/** 외부 시스템 URL 매핑 */\nconst EXTERNAL_SYSTEM_URLS: Record<string, string> = {\n '세움터(EAIS)': 'https://cloud.eais.go.kr',\n '키스콘(KISCON)': 'https://www.kiscon.net',\n '올바로(Allbaro)': 'https://www.allbaro.or.kr',\n 전자카드제: 'https://www.cw.or.kr'\n}\n\nconst VALUE_TYPE_LABELS: Record<string, { label: string; color: string; icon: string }> = {\n MEASURED: { label: '외부 수집', color: '#1976d2', icon: '📡' },\n ASSESSED: { label: '감리자 평가', color: '#2e7d32', icon: '✍️' },\n CALCULATED: { label: '산식 계산', color: '#9c27b0', icon: '🔢' },\n COMPOSITE: { label: '복합 입력', color: '#e65100', icon: '🔀' }\n}\n\n@customElement('kpi-system-guide')\nexport class KpiSystemGuide extends PageView {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: block;\n overflow-y: auto;\n background: #f5f6fa;\n padding: 24px;\n }\n\n .page-title {\n font-size: 1.5rem;\n font-weight: 800;\n color: #1a237e;\n margin-bottom: 4px;\n }\n .page-subtitle {\n font-size: 0.9rem;\n color: #666;\n margin-bottom: 24px;\n }\n\n /* 계층 트리 */\n .tree-section {\n background: #fff;\n border-radius: 12px;\n padding: 24px;\n margin-bottom: 24px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);\n }\n .section-title {\n font-size: 1.1rem;\n font-weight: 700;\n color: #333;\n margin-bottom: 16px;\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n /* Z 루트 */\n .z-node {\n background: linear-gradient(135deg, #1a237e, #283593);\n color: #fff;\n border-radius: 10px;\n padding: 16px 20px;\n margin-bottom: 16px;\n cursor: pointer;\n }\n .z-node:hover {\n opacity: 0.95;\n }\n .z-name {\n font-size: 1.1rem;\n font-weight: 700;\n }\n .z-formula {\n font-size: 0.8rem;\n opacity: 0.8;\n margin-top: 4px;\n font-family: monospace;\n }\n\n /* Y 그룹 */\n .y-group {\n margin-bottom: 16px;\n border: 1px solid #e0e0e0;\n border-radius: 10px;\n overflow: hidden;\n }\n .y-header {\n background: linear-gradient(135deg, #0c4da2, #1565c0);\n color: #fff;\n padding: 12px 16px;\n display: flex;\n justify-content: space-between;\n align-items: center;\n cursor: pointer;\n }\n .y-header:hover {\n opacity: 0.95;\n }\n .y-name {\n font-weight: 700;\n font-size: 1rem;\n }\n .y-weight {\n background: rgba(255, 255, 255, 0.2);\n padding: 2px 10px;\n border-radius: 12px;\n font-size: 0.8rem;\n }\n .y-body {\n padding: 12px;\n }\n .y-formula {\n font-size: 0.8rem;\n color: #555;\n background: #f8f9fa;\n padding: 8px 12px;\n border-radius: 6px;\n font-family: monospace;\n margin-bottom: 10px;\n word-break: break-all;\n }\n\n /* X 카드 */\n .x-cards {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n }\n .x-card {\n flex: 1 1 220px;\n background: #fff;\n border: 1px solid #e8e8e8;\n border-radius: 8px;\n padding: 10px 14px;\n cursor: pointer;\n transition: all 0.2s;\n min-width: 200px;\n }\n .x-card:hover {\n border-color: #1976d2;\n box-shadow: 0 2px 8px rgba(25, 118, 210, 0.15);\n }\n .x-name {\n font-weight: 600;\n font-size: 0.85rem;\n color: #333;\n margin-bottom: 4px;\n }\n .x-formula {\n font-size: 0.75rem;\n color: #888;\n font-family: monospace;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n margin-bottom: 6px;\n }\n .x-badges {\n display: flex;\n gap: 4px;\n flex-wrap: wrap;\n }\n .badge {\n font-size: 0.65rem;\n padding: 2px 6px;\n border-radius: 4px;\n font-weight: 600;\n }\n .badge-score {\n background: #e3f2fd;\n color: #1565c0;\n }\n .badge-value {\n background: #e8f5e9;\n color: #2e7d32;\n }\n\n /* 메트릭 섹션 */\n .metrics-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));\n gap: 10px;\n }\n .metric-card {\n background: #fff;\n border: 1px solid #e8e8e8;\n border-radius: 8px;\n padding: 10px 14px;\n transition: all 0.2s;\n }\n .metric-card:hover {\n border-color: #4caf50;\n }\n .metric-name {\n font-weight: 600;\n font-size: 0.85rem;\n color: #333;\n }\n .metric-unit {\n font-size: 0.75rem;\n color: #888;\n }\n .metric-used {\n font-size: 0.7rem;\n color: #1976d2;\n margin-top: 4px;\n }\n\n /* 팝업 */\n .popup-overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: 1000;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .popup-box {\n background: #fff;\n border-radius: 12px;\n padding: 24px;\n min-width: 400px;\n max-width: 600px;\n max-height: 80vh;\n overflow-y: auto;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);\n }\n .popup-title {\n font-size: 1.1rem;\n font-weight: 700;\n color: #1a237e;\n margin-bottom: 16px;\n display: flex;\n justify-content: space-between;\n }\n .popup-close {\n background: none;\n border: none;\n font-size: 1.2rem;\n cursor: pointer;\n color: #999;\n }\n .popup-row {\n display: flex;\n gap: 8px;\n margin-bottom: 8px;\n font-size: 0.85rem;\n }\n .popup-label {\n color: #888;\n min-width: 100px;\n flex-shrink: 0;\n }\n .popup-value {\n color: #333;\n font-weight: 500;\n }\n .popup-formula {\n background: #f5f5f5;\n padding: 10px;\n border-radius: 6px;\n font-family: monospace;\n font-size: 0.85rem;\n margin: 8px 0;\n word-break: break-all;\n }\n .popup-link {\n color: #1976d2;\n cursor: pointer;\n text-decoration: underline;\n font-size: 0.85rem;\n }\n .popup-link:hover {\n color: #0d47a1;\n }\n\n /* 범례 */\n .legend {\n display: flex;\n gap: 16px;\n flex-wrap: wrap;\n margin-bottom: 20px;\n padding: 12px 16px;\n background: #fff;\n border-radius: 8px;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);\n }\n .legend-group {\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 0.8rem;\n }\n .legend-title {\n font-weight: 700;\n color: #555;\n margin-right: 4px;\n }\n `\n ]\n\n @state() kpiRoot: any[] = []\n @state() metrics: any[] = []\n @state() loading = true\n @state() selectedDetail: any = null\n\n get context() {\n return { title: 'KPI 시스템 가이드' }\n }\n\n async firstUpdated() {\n try {\n const { data } = await client.query({ query: GET_KPI_SYSTEM })\n this.kpiRoot = data.kpiRoot || []\n this.metrics = data.kpiMetrics?.items || []\n } catch (e) {\n console.error('Failed to load KPI system data:', e)\n } finally {\n this.loading = false\n }\n }\n\n /** 메트릭이 사용되는 KPI 목록 — 비활성 KPI/Y 그룹 제외 */\n private getMetricUsage(metricName: string): string[] {\n const used: string[] = []\n for (const y of this.kpiRoot) {\n if (y.active === false) continue\n for (const x of y.kpis || []) {\n if (x.active === false) continue\n if (x.formula?.includes(`[${metricName}]`)) {\n used.push(x.name)\n }\n }\n }\n return used\n }\n\n private showDetail(item: any, type: 'kpi' | 'metric') {\n this.selectedDetail = { ...item, _type: type }\n }\n\n private closeDetail() {\n this.selectedDetail = null\n }\n\n render() {\n if (this.loading) return html`<div style=\"padding:40px;text-align:center;color:#888;\">로딩 중...</div>`\n\n // Z 노드 (루트의 부모)\n const allX = this.kpiRoot.flatMap((y: any) => (y.kpis || []).filter((x: any) => x.active !== false))\n\n return html`\n <div class=\"page-title\">KPI 시스템 가이드</div>\n <div class=\"page-subtitle\">\n 건축프로젝트 성과평가 KPI 체계의 전체 구조, 산식, 메트릭 연관관계를 확인합니다. 모든 정보는 DB에서 실시간 조회됩니다.\n </div>\n\n <!-- 범례 -->\n <div class=\"legend\">\n <span class=\"legend-title\">Score 산정:</span>\n ${Object.entries(SCORE_TYPE_LABELS).map(\n ([, v]) => html`<span class=\"legend-group\"><span class=\"badge badge-score\">${v.label}</span></span>`\n )}\n <span style=\"width:16px\"></span>\n <span class=\"legend-title\">Value 획득:</span>\n ${Object.entries(VALUE_TYPE_LABELS).map(\n ([, v]) => html`<span class=\"legend-group\">${v.icon} <span class=\"badge badge-value\">${v.label}</span></span>`\n )}\n </div>\n\n <!-- 1. KPI 계층 구조 -->\n <div class=\"tree-section\">\n <div class=\"section-title\">📊 KPI 계층 구조 (Z → Y → X)</div>\n\n <!-- Z 루트 -->\n <div\n class=\"z-node\"\n @click=${() =>\n this.showDetail(\n {\n name: 'Z. 전체스코어',\n description: 'Y1~Y6 가중합',\n formula: 'Y1×w1 + Y2×w2 + ... + Y6×w6',\n scoreType: 'DIRECT',\n valueType: 'CALCULATED'\n },\n 'kpi'\n )}\n >\n <div class=\"z-name\">Z. 전체스코어</div>\n <div class=\"z-formula\">\n =\n ${this.kpiRoot\n .filter((y: any) => y.active !== false)\n .map((y: any) => `${y.name.split('.')[0]}×${y.weight}`)\n .join(' + ')}\n </div>\n </div>\n\n <!-- Y 그룹들 (비활성 Y 제외) -->\n ${this.kpiRoot\n .filter((y: any) => y.active !== false)\n .map((y: any) => {\n const activeKpis = (y.kpis || []).filter((x: any) => x.active !== false)\n return html`\n <div class=\"y-group\">\n <div class=\"y-header\" @click=${() => this.showDetail(y, 'kpi')}>\n <span class=\"y-name\">${y.name}</span>\n <span class=\"y-weight\">가중치: ${y.weight}</span>\n </div>\n <div class=\"y-body\">\n ${y.formula ? html`<div class=\"y-formula\">${y.formula}</div>` : ''}\n <div class=\"x-cards\">\n ${activeKpis.map((x: any) => {\n const st = SCORE_TYPE_LABELS[x.scoreType] || { label: x.scoreType || '미설정', color: '#999' }\n const vt = VALUE_TYPE_LABELS[x.valueType] || { label: x.valueType || '미설정', color: '#999', icon: '?' }\n return html`\n <div class=\"x-card\" @click=${() => this.showDetail(x, 'kpi')}>\n <div class=\"x-name\">${x.name}</div>\n <div class=\"x-formula\">${x.formula || '—'}</div>\n <div class=\"x-badges\">\n <span class=\"badge badge-score\">${st.label}</span>\n <span class=\"badge badge-value\">${vt.icon} ${vt.label}</span>\n </div>\n </div>\n `\n })}\n </div>\n </div>\n </div>\n `\n })}\n </div>\n\n <!-- 2. 원천 메트릭 — active=true 이면서 활성 KPI formula 에 실제 참조되는 것만 -->\n ${(() => {\n const usedMetrics = this.metrics\n .filter((m: any) => m.active)\n .map((m: any) => ({ m, usage: this.getMetricUsage(m.name) }))\n .filter(({ usage }: any) => usage.length > 0)\n return html`\n <div class=\"tree-section\">\n <div class=\"section-title\">📋 원천 메트릭 (${usedMetrics.length}개 사용)</div>\n <div class=\"metrics-grid\">\n ${usedMetrics.map(\n ({ m, usage }: any) => html`\n <div class=\"metric-card\" @click=${() => this.showDetail(m, 'metric')}>\n <div class=\"metric-name\">${m.name}</div>\n <div class=\"metric-unit\">\n ${m.unit || '—'} ·\n ${m.collectType === 'EXTERNAL' && m.source\n ? EXTERNAL_SYSTEM_URLS[m.source]\n ? html`<a\n href=\"${EXTERNAL_SYSTEM_URLS[m.source]}\"\n target=\"_blank\"\n rel=\"noopener\"\n style=\"color:#1976d2;text-decoration:none;\"\n @click=${(e: Event) => e.stopPropagation()}\n >${m.source} ↗</a\n >`\n : m.source\n : m.collectType || 'MANUAL'}\n </div>\n <div class=\"metric-used\">→ ${usage.join(', ')}</div>\n </div>\n `\n )}\n </div>\n </div>\n `\n })()}\n\n <!-- 3. 시스템 요약 -->\n <div class=\"tree-section\">\n <div class=\"section-title\">📈 시스템 요약</div>\n <div style=\"display:flex;gap:16px;flex-wrap:wrap;\">\n <div style=\"flex:1;min-width:200px;background:#e3f2fd;padding:16px;border-radius:8px;text-align:center;\">\n <div style=\"font-size:2rem;font-weight:800;color:#1565c0;\">${allX.length}</div>\n <div style=\"font-size:0.85rem;color:#555;\">X-level 지표</div>\n </div>\n <div style=\"flex:1;min-width:200px;background:#e8f5e9;padding:16px;border-radius:8px;text-align:center;\">\n <div style=\"font-size:2rem;font-weight:800;color:#2e7d32;\">\n ${this.kpiRoot.filter((y: any) => y.active !== false).length}\n </div>\n <div style=\"font-size:0.85rem;color:#555;\">Y-level 영역</div>\n </div>\n <div style=\"flex:1;min-width:200px;background:#fff3e0;padding:16px;border-radius:8px;text-align:center;\">\n <div style=\"font-size:2rem;font-weight:800;color:#e65100;\">\n ${this.metrics.filter((m: any) => m.active && this.getMetricUsage(m.name).length > 0).length}\n </div>\n <div style=\"font-size:0.85rem;color:#555;\">원천 메트릭</div>\n </div>\n <div style=\"flex:1;min-width:200px;background:#fce4ec;padding:16px;border-radius:8px;text-align:center;\">\n <div style=\"font-size:2rem;font-weight:800;color:#c62828;\">\n ${allX.filter((x: any) => x.valueType === 'ASSESSED').length}\n </div>\n <div style=\"font-size:0.85rem;color:#555;\">감리자 평가 항목</div>\n </div>\n </div>\n </div>\n\n <!-- 팝업 -->\n ${this.selectedDetail ? this._renderDetailPopup() : nothing}\n `\n }\n\n private _renderDetailPopup() {\n const d = this.selectedDetail\n const isKpi = d._type === 'kpi'\n\n return html`\n <div class=\"popup-overlay\" @click=${() => this.closeDetail()}>\n <div class=\"popup-box\" @click=${(e: Event) => e.stopPropagation()}>\n <div class=\"popup-title\">\n <span>${d.name}</span>\n <button class=\"popup-close\" @click=${() => this.closeDetail()}>×</button>\n </div>\n\n ${d.description\n ? html`\n <div class=\"popup-row\">\n <span class=\"popup-label\">설명</span>\n <span class=\"popup-value\">${d.description}</span>\n </div>\n `\n : ''}\n ${isKpi\n ? html`\n ${d.formula\n ? html`\n <div class=\"popup-row\"><span class=\"popup-label\">산식</span></div>\n <div class=\"popup-formula\">${d.formula}</div>\n `\n : ''}\n ${d.scoreType\n ? html`\n <div class=\"popup-row\">\n <span class=\"popup-label\">Score 산정</span>\n <span class=\"popup-value\" style=\"color:${(SCORE_TYPE_LABELS[d.scoreType] || {}).color || '#333'}\">\n ${(SCORE_TYPE_LABELS[d.scoreType] || {}).label || d.scoreType}\n </span>\n </div>\n `\n : ''}\n ${d.valueType\n ? html`\n <div class=\"popup-row\">\n <span class=\"popup-label\">Value 획득</span>\n <span class=\"popup-value\" style=\"color:${(VALUE_TYPE_LABELS[d.valueType] || {}).color || '#333'}\">\n ${(VALUE_TYPE_LABELS[d.valueType] || {}).icon || ''}\n ${(VALUE_TYPE_LABELS[d.valueType] || {}).label || d.valueType}\n </span>\n </div>\n `\n : ''}\n ${d.weight\n ? html`\n <div class=\"popup-row\">\n <span class=\"popup-label\">가중치</span>\n <span class=\"popup-value\">${d.weight}</span>\n </div>\n `\n : ''}\n\n <div style=\"margin-top:16px;padding-top:12px;border-top:1px solid #eee;\">\n <span\n class=\"popup-link\"\n @click=${() => {\n this.closeDetail()\n navigate('kpi-overview')\n }}\n >\n → KPI 개요에서 보기\n </span>\n &nbsp;&nbsp;\n <span\n class=\"popup-link\"\n @click=${() => {\n this.closeDetail()\n navigate('kpi-admin')\n }}\n >\n → KPI 관리에서 편집\n </span>\n </div>\n `\n : html`\n <div class=\"popup-row\">\n <span class=\"popup-label\">단위</span>\n <span class=\"popup-value\">${d.unit || '—'}</span>\n </div>\n <div class=\"popup-row\">\n <span class=\"popup-label\">수집 방식</span>\n <span class=\"popup-value\">${d.collectType || 'MANUAL'}</span>\n </div>\n <div class=\"popup-row\">\n <span class=\"popup-label\">데이터 소스</span>\n <span class=\"popup-value\">\n ${d.source && EXTERNAL_SYSTEM_URLS[d.source]\n ? html`<a href=\"${EXTERNAL_SYSTEM_URLS[d.source]}\" target=\"_blank\" rel=\"noopener\" style=\"color:#1976d2;\"\n >${d.source} ↗</a\n >`\n : d.source || '미설정'}\n </span>\n </div>\n\n <div style=\"margin-top:12px;\">\n <div class=\"popup-label\" style=\"margin-bottom:6px;\">사용처:</div>\n ${this.getMetricUsage(d.name).map(\n kpi => html`<div style=\"font-size:0.85rem;color:#1976d2;margin-bottom:2px;\">· ${kpi}</div>`\n )}\n ${this.getMetricUsage(d.name).length === 0 ? html`<div style=\"color:#ccc;font-size:0.85rem;\">미사용</div>` : ''}\n </div>\n\n <div style=\"margin-top:16px;padding-top:12px;border-top:1px solid #eee;\">\n <span\n class=\"popup-link\"\n @click=${() => {\n this.closeDetail()\n navigate('kpi-metric-value-list')\n }}\n >\n → 메트릭 값 관리\n </span>\n </div>\n `}\n </div>\n </div>\n `\n }\n}\n"]}
@@ -24,9 +24,17 @@ export declare class KpiLeftPanel extends LitElement {
24
24
  fetchRegionalKpiStats(): Promise<void>;
25
25
  fetchMonthlyTrendData(): Promise<void>;
26
26
  private getRegionTrendData;
27
+ /** start~end(YYYY-MM) 사이 모든 월을 순서대로 반환 */
28
+ private _buildMonthRange;
27
29
  private calculateChangeRate;
28
30
  private generateChartData;
29
31
  private _onAllPeriodChange;
32
+ /** 시작월 설정 — 시작 > 종료(역전)면 종료를 시작에 맞춰 보정. (YYYY-MM 문자열 비교 = 시간순) */
33
+ private _setStartYearMonth;
34
+ /** 종료월 설정 — 종료 < 시작(역전)이면 시작을 종료에 맞춰 보정. */
35
+ private _setEndYearMonth;
36
+ /** 기간 변경을 부모로 전파 → 지역 팝업 등 다른 패널이 동일 기간을 쓰도록 동기화 */
37
+ private _emitPeriodChange;
30
38
  private onCategoryChange;
31
39
  private onChartTypeChange;
32
40
  private onRegionClick;
@@ -128,12 +128,12 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
128
128
  // selectedCategory에 따라 kpiName 결정
129
129
  const categoryToKpiName = {
130
130
  '전체 KPI': null,
131
- '일정 성과': 'Y1. 일정성과',
132
- '비용 성과': 'Y2. 비용성과',
133
- '품질 성과': 'Y3. 품질성과',
131
+ '일정 성과': '일정성과(Y1)',
132
+ '비용 성과': '비용성과(Y2)',
133
+ '품질 성과': '품질성과(Y3)',
134
134
  '안전 성과': 'Y4. 안전성과',
135
- '환경 성과': 'Y5. 환경성과',
136
- '생산성 성과': 'Y6. 생산성성과'
135
+ '환경 성과': '환경성과(Y5)',
136
+ '생산성 성과': '생산성성과(Y6)'
137
137
  };
138
138
  const kpiName = categoryToKpiName[this.selectedCategory];
139
139
  const response = await client.query({
@@ -186,12 +186,12 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
186
186
  // selectedCategory에 따라 kpiName 결정
187
187
  const categoryToKpiName = {
188
188
  '전체 KPI': null,
189
- '일정 성과': 'Y1. 일정성과',
190
- '비용 성과': 'Y2. 비용성과',
191
- '품질 성과': 'Y3. 품질성과',
189
+ '일정 성과': '일정성과(Y1)',
190
+ '비용 성과': '비용성과(Y2)',
191
+ '품질 성과': '품질성과(Y3)',
192
192
  '안전 성과': 'Y4. 안전성과',
193
- '환경 성과': 'Y5. 환경성과',
194
- '생산성 성과': 'Y6. 생산성성과'
193
+ '환경 성과': '환경성과(Y5)',
194
+ '생산성 성과': '생산성성과(Y6)'
195
195
  };
196
196
  const kpiName = categoryToKpiName[this.selectedCategory];
197
197
  const response = await client.query({
@@ -239,13 +239,39 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
239
239
  }
240
240
  }
241
241
  getRegionTrendData(region) {
242
- // 해당 지역의 최근 12개월 트렌드 데이터 추출
243
- const regionData = this.monthlyTrendData
242
+ // 해당 지역 월별 (20배 스케일)
243
+ const byMonth = new Map();
244
+ this.monthlyTrendData
244
245
  .filter(d => d.geoGroup === region)
245
- .sort((a, b) => a.yearMonth.localeCompare(b.yearMonth))
246
- .map(d => d.avgVal * 100); // 20배 스케일
247
- // 12개월 데이터가 없으면 배열 반환
248
- return regionData.length > 0 ? regionData : [];
246
+ .forEach(d => byMonth.set(d.yearMonth, d.avgVal * 100));
247
+ // 선택 기간 전체 월을 커버 — 데이터 없는 월은 null(0 아님). 기간 정보 없으면 데이터 월만.
248
+ const months = this.startYearMonth && this.endYearMonth
249
+ ? this._buildMonthRange(this.startYearMonth, this.endYearMonth)
250
+ : [...byMonth.keys()].sort();
251
+ return months.map(ym => (byMonth.has(ym) ? byMonth.get(ym) : null));
252
+ }
253
+ /** start~end(YYYY-MM) 사이 모든 월을 순서대로 반환 */
254
+ _buildMonthRange(start, end) {
255
+ const [sy, sm] = start.split('-').map(Number);
256
+ const [ey, em] = end.split('-').map(Number);
257
+ if (!sy || !sm || !ey || !em)
258
+ return [];
259
+ const out = [];
260
+ let y = sy;
261
+ let m = sm;
262
+ for (let i = 0; i < 240; i++) {
263
+ out.push(`${y}-${String(m).padStart(2, '0')}`);
264
+ if (y === ey && m === em)
265
+ break;
266
+ if (y > ey || (y === ey && m > em))
267
+ break;
268
+ m++;
269
+ if (m > 12) {
270
+ m = 1;
271
+ y++;
272
+ }
273
+ }
274
+ return out;
249
275
  }
250
276
  calculateChangeRate(region) {
251
277
  // 해당 지역의 월별 데이터를 시간순으로 정렬
@@ -263,29 +289,31 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
263
289
  return ((latestValue - oldestValue) / oldestValue) * 100;
264
290
  }
265
291
  generateChartData() {
266
- // KPI 이름을 카테고리로 매핑 축약
267
- const categoryMapping = {
268
- 'Y1. 일정성과': '일정',
269
- 'Y2. 비용성과': '비용',
270
- 'Y3. 품질성과': '품질',
271
- 'Y4. 안전성과': '안전',
272
- 'Y5. 환경성과': '환경',
273
- 'Y6. 생산성성과': '생산성'
292
+ // kpiName 표기가 'Y1. 일정성과' / '일정성과(Y1)' 등으로 환경마다 달라 전체 문자열 매칭은 깨짐.
293
+ // Y1~Y6 코드만 추출해 짧은 라벨로 매핑.
294
+ const yLabelByCode = {
295
+ Y1: '일정',
296
+ Y2: '비용',
297
+ Y3: '품질',
298
+ Y4: '안전',
299
+ Y5: '환경',
300
+ Y6: '생산성'
274
301
  };
302
+ const toCategory = (kpiName) => { var _a, _b; return yLabelByCode[(_b = (_a = (kpiName || '').match(/Y[1-6]/)) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : ''] || kpiName; };
275
303
  if (this.selectedChartType === 'radar') {
276
304
  // 레이더 차트용 데이터 생성 (전체 평균만 표시)
277
305
  const data = [];
278
306
  const categories = [];
279
307
  this.kpiYComprehensiveStats.forEach(stat => {
280
- const category = categoryMapping[stat.kpiName] || stat.kpiName;
308
+ const category = toCategory(stat.kpiName);
281
309
  if (!categories.includes(category)) {
282
310
  categories.push(category);
283
311
  }
284
- // 전체 평균 데이터만 표시 (비교 시리즈 없음)
312
+ // 전체 평균 데이터만 표시 (비교 시리즈 없음) — 박스플롯과 동일 정밀도(Math.round 제거)
285
313
  data.push({
286
314
  org: '전체평균',
287
315
  category,
288
- value: Math.round(stat.avgVal * 100)
316
+ value: stat.avgVal * 100
289
317
  });
290
318
  });
291
319
  this.chartCategories = categories.length > 0 ? categories : ['일정', '비용', '품질', '안전', '환경', '생산성'];
@@ -296,7 +324,7 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
296
324
  const data = [];
297
325
  const categories = [];
298
326
  this.kpiYComprehensiveStats.forEach(stat => {
299
- const category = categoryMapping[stat.kpiName] || stat.kpiName;
327
+ const category = toCategory(stat.kpiName);
300
328
  if (!categories.includes(category)) {
301
329
  categories.push(category);
302
330
  }
@@ -318,9 +346,37 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
318
346
  _onAllPeriodChange(e) {
319
347
  this.isAllPeriod = e.target.checked;
320
348
  if (this.isAllPeriod) {
321
- this.startYearMonth = '';
322
- this.endYearMonth = '';
349
+ // 전체: 시작 = 선택 가능한 가장 이른 월(2022-01), 종료 = 현재월 → 모든 데이터 포함하며 표시도 명확
350
+ // (빈 문자열로 두면 드롭다운이 2022-01~2022-01 로 잘못 보이던 문제 방지)
351
+ const now = new Date();
352
+ this.startYearMonth = '2022-01';
353
+ this.endYearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
354
+ }
355
+ this._emitPeriodChange();
356
+ }
357
+ /** 시작월 설정 — 시작 > 종료(역전)면 종료를 시작에 맞춰 보정. (YYYY-MM 문자열 비교 = 시간순) */
358
+ _setStartYearMonth(ym) {
359
+ this.startYearMonth = ym;
360
+ if (this.endYearMonth && this.startYearMonth > this.endYearMonth) {
361
+ this.endYearMonth = this.startYearMonth;
323
362
  }
363
+ this._emitPeriodChange();
364
+ }
365
+ /** 종료월 설정 — 종료 < 시작(역전)이면 시작을 종료에 맞춰 보정. */
366
+ _setEndYearMonth(ym) {
367
+ this.endYearMonth = ym;
368
+ if (this.startYearMonth && this.endYearMonth < this.startYearMonth) {
369
+ this.startYearMonth = this.endYearMonth;
370
+ }
371
+ this._emitPeriodChange();
372
+ }
373
+ /** 기간 변경을 부모로 전파 → 지역 팝업 등 다른 패널이 동일 기간을 쓰도록 동기화 */
374
+ _emitPeriodChange() {
375
+ this.dispatchEvent(new CustomEvent('period-change', {
376
+ detail: { startYearMonth: this.startYearMonth, endYearMonth: this.endYearMonth, isAllPeriod: this.isAllPeriod },
377
+ bubbles: true,
378
+ composed: true
379
+ }));
324
380
  }
325
381
  onCategoryChange(event) {
326
382
  const target = event.target;
@@ -368,7 +424,10 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
368
424
  const condData = [
369
425
  { 항목: 'KPI 카테고리', 값: this.selectedCategory },
370
426
  { 항목: '사업 유형', 값: this.sectorType === 'PUBLIC' ? '공공' : this.sectorType === 'PRIVATE' ? '민간' : '전체' },
371
- { 항목: '건물 용도', 값: this.buildingUsage === 'RESIDENTIAL' ? '주거' : this.buildingUsage === 'NON_RESIDENTIAL' ? '비주거' : '전체' },
427
+ {
428
+ 항목: '건물 용도',
429
+ 값: this.buildingUsage === 'RESIDENTIAL' ? '주거' : this.buildingUsage === 'NON_RESIDENTIAL' ? '비주거' : '전체'
430
+ },
372
431
  { 항목: '기간', 값: this.isAllPeriod ? '전체 기간' : `${this.startYearMonth} ~ ${this.endYearMonth}` },
373
432
  { 항목: '다운로드 일시', 값: new Date().toLocaleString('ko-KR') }
374
433
  ];
@@ -445,7 +504,7 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
445
504
  return '▲';
446
505
  if (change < 0)
447
506
  return '▼';
448
- return '';
507
+ return ''; // 변동 없음(0)이면 화살표 미표시
449
508
  }
450
509
  render() {
451
510
  return html `
@@ -495,33 +554,49 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
495
554
  <!-- 기간 필터 -->
496
555
  <div class="filter-row">
497
556
  <span class="filter-label">기간</span>
498
- <select class="filter-select" ?disabled=${this.isAllPeriod} @change=${(e) => {
557
+ <select
558
+ class="filter-select"
559
+ ?disabled=${this.isAllPeriod}
560
+ @change=${(e) => {
499
561
  const y = e.target.value;
500
562
  const m = this.startYearMonth.split('-')[1] || '01';
501
- this.startYearMonth = `${y}-${m}`;
502
- }}>
563
+ this._setStartYearMonth(`${y}-${m}`);
564
+ }}
565
+ >
503
566
  ${[2022, 2023, 2024, 2025, 2026].map(y => html `<option value="${y}" ?selected=${this.startYearMonth.startsWith(String(y))}>${y}</option>`)}
504
567
  </select>
505
- <select class="filter-select" ?disabled=${this.isAllPeriod} @change=${(e) => {
568
+ <select
569
+ class="filter-select"
570
+ ?disabled=${this.isAllPeriod}
571
+ @change=${(e) => {
506
572
  const y = this.startYearMonth.split('-')[0] || '2025';
507
573
  const m = e.target.value.padStart(2, '0');
508
- this.startYearMonth = `${y}-${m}`;
509
- }}>
574
+ this._setStartYearMonth(`${y}-${m}`);
575
+ }}
576
+ >
510
577
  ${[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map(m => html `<option value="${m}" ?selected=${parseInt(this.startYearMonth.split('-')[1]) === m}>${m}월</option>`)}
511
578
  </select>
512
579
  <span style="color:#6c757d;font-size:0.8rem;">~</span>
513
- <select class="filter-select" ?disabled=${this.isAllPeriod} @change=${(e) => {
580
+ <select
581
+ class="filter-select"
582
+ ?disabled=${this.isAllPeriod}
583
+ @change=${(e) => {
514
584
  const y = e.target.value;
515
585
  const m = this.endYearMonth.split('-')[1] || '12';
516
- this.endYearMonth = `${y}-${m}`;
517
- }}>
586
+ this._setEndYearMonth(`${y}-${m}`);
587
+ }}
588
+ >
518
589
  ${[2022, 2023, 2024, 2025, 2026].map(y => html `<option value="${y}" ?selected=${this.endYearMonth.startsWith(String(y))}>${y}</option>`)}
519
590
  </select>
520
- <select class="filter-select" ?disabled=${this.isAllPeriod} @change=${(e) => {
591
+ <select
592
+ class="filter-select"
593
+ ?disabled=${this.isAllPeriod}
594
+ @change=${(e) => {
521
595
  const y = this.endYearMonth.split('-')[0] || '2026';
522
596
  const m = e.target.value.padStart(2, '0');
523
- this.endYearMonth = `${y}-${m}`;
524
- }}>
597
+ this._setEndYearMonth(`${y}-${m}`);
598
+ }}
599
+ >
525
600
  ${[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map(m => html `<option value="${m}" ?selected=${parseInt(this.endYearMonth.split('-')[1]) === m}>${m}월</option>`)}
526
601
  </select>
527
602
  <label style="display:flex;align-items:center;gap:3px;cursor:pointer;">
@@ -532,7 +607,7 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
532
607
 
533
608
  <!-- 종합 성과 -->
534
609
  <div class="chart-section">
535
- <div class="sub-title">종합 성과</div>
610
+ <div class="sub-title">성과영역별 KPI</div>
536
611
  <div class="chart-toggle">
537
612
  <button
538
613
  class="toggle-button ${this.selectedChartType === 'boxplot' ? 'active' : ''}"
@@ -548,21 +623,32 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
548
623
  </button>
549
624
  </div>
550
625
  <div class="chart-container">
551
- ${this.selectedChartType === 'boxplot'
552
- ? html ` <sv-kpi-boxplot-chart .data=${this.chartData} .valueKey=${'value'}></sv-kpi-boxplot-chart> `
553
- : html `
554
- <sv-kpi-radar-chart
555
- .data=${this.chartData}
556
- .categories=${this.chartCategories}
557
- .valueKey=${'value'}
558
- ></sv-kpi-radar-chart>
559
- `}
626
+ ${this.chartData.length === 0
627
+ ? html `<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#888;font-size:0.9rem;">
628
+ 선택한 기간에 데이터가 없습니다.
629
+ </div>`
630
+ : this.selectedChartType === 'boxplot'
631
+ ? html `
632
+ <sv-kpi-boxplot-chart
633
+ .data=${this.chartData}
634
+ .valueKey=${'value'}
635
+ .valueLabel=${'전국'}
636
+ .showStatus=${false}
637
+ ></sv-kpi-boxplot-chart>
638
+ `
639
+ : html `
640
+ <sv-kpi-radar-chart
641
+ .data=${this.chartData}
642
+ .categories=${this.chartCategories}
643
+ .valueKey=${'value'}
644
+ ></sv-kpi-radar-chart>
645
+ `}
560
646
  </div>
561
647
  </div>
562
648
 
563
649
  <!-- 시도별 성과 -->
564
650
  <div class="chart-section">
565
- <div class="sub-title">시도별 성과</div>
651
+ <div class="sub-title">시도별 종합 KPI</div>
566
652
  <table class="performance-table">
567
653
  <thead>
568
654
  <tr>
@@ -732,7 +818,7 @@ KpiLeftPanel.styles = [
732
818
  color: #dc3545;
733
819
  }
734
820
  .change-down {
735
- color: #198754;
821
+ color: #2196f3;
736
822
  }
737
823
  .change-neutral {
738
824
  color: #6c757d;