From 407b9ba531427584cf2e6f771872a39702b7dfff Mon Sep 17 00:00:00 2001 From: JooWangi Date: Fri, 12 Jun 2026 10:40:30 +0900 Subject: [PATCH] =?UTF-8?q?style:=20=EC=97=B0=EB=8F=84=EB=B3=84=20PC=20?= =?UTF-8?q?=EB=85=B8=ED=9B=84=EB=8F=84=20=EC=98=88=EC=B8=A1=20=ED=91=9C?= =?UTF-8?q?=EC=9D=98=20=EC=99=BC=ED=8E=B8=20=EC=97=AC=EB=B0=B1=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20=EB=B0=8F=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20?= =?UTF-8?q?=EC=A0=95=EB=A0=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/Dashboard/HwDashboard.ts | 325 +++++++++++++++-------------- 1 file changed, 172 insertions(+), 153 deletions(-) diff --git a/src/views/Dashboard/HwDashboard.ts b/src/views/Dashboard/HwDashboard.ts index 1b6c443..9364620 100644 --- a/src/views/Dashboard/HwDashboard.ts +++ b/src/views/Dashboard/HwDashboard.ts @@ -18,7 +18,7 @@ export function renderHwDashboard(container: HTMLElement) { // 2. 1페이지 매거진 리포트(제목바 제거, '| 제목' 미니멀리즘 스타일) HTML 빌드 container.innerHTML = ` -
+
@@ -44,13 +44,13 @@ export function renderHwDashboard(container: HTMLElement) {
-
+
-
+
- -
+ +
@@ -108,51 +108,38 @@ export function renderHwDashboard(container: HTMLElement) { -
+ +
- -
+ +
-
- 등급별 보유 현황 +
+ 등급별 자산 종합 현황
- -
- -
-
- 최상급 PC (85점 이상) - 0대(0%) -
-
- -
-
- 상급 PC (70점 ~ 85점) - 0대(0%) -
-
- -
-
- 중급 PC (40점 ~ 70점) - 0대(0%) -
-
- -
-
- 보급 PC (40점 미만) - 0대(0%) -
-
+ +
+ + + + + + + + + + + + + +
구분 (등급)보유량할당량여분부족분
-
- +
+
등급별 보유 비율
@@ -163,7 +150,7 @@ export function renderHwDashboard(container: HTMLElement) {
-
+
최상급 @@ -185,48 +172,14 @@ export function renderHwDashboard(container: HTMLElement) {
- - -
-
- 유효 재고 현황 -
- -
- -
-
-
0대
- 최상급 재고 -
-
-
0대
- 중급 재고 -
-
- -
- -
-
-
0대
- 상급 재고 -
-
-
0대
- 보급 재고 -
-
-
-
-
+
-
+
직무별 사양 적정성 분석
@@ -236,15 +189,15 @@ export function renderHwDashboard(container: HTMLElement) {
-
+
- 연도별 PC 노후도 및 교체 주기 예측 + 연도별 PC 노후도 및 교체 주기 예측
-
- +
+
- + @@ -319,18 +272,18 @@ function updateDashboardData(pcs: any[], selectedDept: string) { jobScores[job].avg = jobScores[job].count > 0 ? jobScores[job].totalScore / jobScores[job].count : 0; }); - // 4. 등급 집계 (보유량 vs 유효 재고량) + // 4. 등급 집계 (보유량 vs 실제 할당량 vs 유효 재고량 vs 사양 부족량) const isStock = (p: any) => { return p.hw_status === '재고' || p.hw_status === '대기' || !(p.user_current || '').trim(); }; - const gradeCounts = { - premium: { total: 0, stock: 0 }, - high: { total: 0, stock: 0 }, - normal: { total: 0, stock: 0 }, - entry: { total: 0, stock: 0 } + const matrix = { + premium: { total: 0, active: 0, stock: 0, under: 0, pcs: [] as any[], activePcs: [] as any[], stockPcs: [] as any[], underPcs: [] as any[] }, + high: { total: 0, active: 0, stock: 0, under: 0, pcs: [] as any[], activePcs: [] as any[], stockPcs: [] as any[], underPcs: [] as any[] }, + normal: { total: 0, active: 0, stock: 0, under: 0, pcs: [] as any[], activePcs: [] as any[], stockPcs: [] as any[], underPcs: [] as any[] }, + entry: { total: 0, active: 0, stock: 0, under: 0, pcs: [] as any[], activePcs: [] as any[], stockPcs: [] as any[], underPcs: [] as any[] } }; let scoreSum = 0; @@ -344,35 +297,45 @@ function updateDashboardData(pcs: any[], selectedDept: string) { scoreSum += score; const stockYn = isStock(p); + let target: typeof matrix.premium; if (score >= 85) { - gradeCounts.premium.total++; - if (stockYn) gradeCounts.premium.stock++; + target = matrix.premium; } else if (score >= 70) { - gradeCounts.high.total++; - if (stockYn) gradeCounts.high.stock++; + target = matrix.high; } else if (score >= 40) { - gradeCounts.normal.total++; - if (stockYn) gradeCounts.normal.stock++; + target = matrix.normal; } else { - gradeCounts.entry.total++; - if (stockYn) gradeCounts.entry.stock++; + target = matrix.entry; } - // 직무 적정성 계산 (재직 중이고 실 사용자 매핑 자산만 검토 대상) - const job = p[ASSET_SCHEMA.USER_POSITION.key] || '미분류'; - const avg = jobScores[job]?.avg || 0; + target.pcs.push(p); + target.total++; - if (avg > 0 && job !== '재고PC' && !stockYn) { - if (score < avg * 0.6) { - p._spec_status = '사양 부족'; - criticalList.push(p); - underSpecCount++; - } else if (score > avg * 1.5) { - p._spec_status = '오버스펙'; - criticalList.push(p); - overSpecCount++; - } else { - p._spec_status = '적정'; + if (stockYn) { + target.stock++; + target.stockPcs.push(p); + } else { + target.active++; + target.activePcs.push(p); + + // 직무 적정성 계산 (재직 중이고 실 사용자 매핑 자산만 검토 대상) + const job = p[ASSET_SCHEMA.USER_POSITION.key] || '미분류'; + const avg = jobScores[job]?.avg || 0; + + if (avg > 0 && job !== '재고PC') { + if (score < avg * 0.6) { + p._spec_status = '사양 부족'; + criticalList.push(p); + underSpecCount++; + target.under++; + target.underPcs.push(p); + } else if (score > avg * 1.5) { + p._spec_status = '오버스펙'; + criticalList.push(p); + overSpecCount++; + } else { + p._spec_status = '적정'; + } } } @@ -382,39 +345,107 @@ function updateDashboardData(pcs: any[], selectedDept: string) { } }); - // 5. 핵심 텍스트형 지표 갱신 + // 5. 핵심 텍스트형 요약 지표 갱신 document.getElementById('metric-total-pcs')!.textContent = `${filtered.length}대`; document.getElementById('metric-under-spec')!.textContent = `${underSpecCount}명`; document.getElementById('metric-over-spec')!.textContent = `${overSpecCount}명`; document.getElementById('metric-win11-incompatible')!.textContent = `${win11IncompatibleCount}대`; - - // 6. 등급별 리스트 데이터 바 업데이트 - const total = filtered.length || 1; + // 6. 종합 매트릭스 테이블 렌더링 및 바인딩 + const matrixTbody = document.getElementById('pc-grade-matrix-tbody')!; - const updateCard = (id: string, counts: { total: number; stock: number }) => { - const card = document.getElementById(id)!; - const rate = Math.round((counts.total / total) * 100); + const renderMatrixRow = (gradeKey: keyof typeof matrix, label: string, color: string) => { + const data = matrix[gradeKey]; + const totalRate = filtered.length > 0 ? Math.round((data.total / filtered.length) * 100) : 0; - card.querySelector('.grade-count')!.textContent = `${counts.total}대`; - card.querySelector('.grade-rate')!.textContent = `(${rate}%)`; + const cellStyle = `padding: 9px 4px; text-align: center; font-weight: 700; cursor: pointer; transition: background 0.2s;`; + const hoverEvents = `onmouseover="this.style.background='#F1F5F9'" onmouseout="this.style.background='none'"`; + + return ` + + + + + + + + `; }; - updateCard('grade-premium', gradeCounts.premium); - updateCard('grade-high', gradeCounts.high); - updateCard('grade-normal', gradeCounts.normal); - updateCard('grade-entry', gradeCounts.entry); + const totalPcs = filtered.length; + const totalActive = matrix.premium.active + matrix.high.active + matrix.normal.active + matrix.entry.active; + const totalStock = matrix.premium.stock + matrix.high.stock + matrix.normal.stock + matrix.entry.stock; + const totalUnder = matrix.premium.under + matrix.high.under + matrix.normal.under + matrix.entry.under; - // 6.2 Inventory Summary 수치 업데이트 (골드/민트 텍스트 영역) - const container = document.getElementById('view-body')?.parentElement || document.body; - const setStockVal = (cls: string, val: number) => { - const el = container.querySelector(`.${cls}`); - if (el) el.textContent = `${val}대`; - }; - setStockVal('summary-grade-stock-premium', gradeCounts.premium.stock); - setStockVal('summary-grade-stock-high', gradeCounts.high.stock); - setStockVal('summary-grade-stock-normal', gradeCounts.normal.stock); - setStockVal('summary-grade-stock-entry', gradeCounts.entry.stock); + const cellStyleHeader = `padding: 9px 4px; text-align: center; font-weight: 800; cursor: pointer; transition: background 0.2s; background: #F8FAFC;`; + const hoverEventsHeader = `onmouseover="this.style.background='#EEF2F6'" onmouseout="this.style.background='#F8FAFC'"`; + + matrixTbody.innerHTML = ` + ${renderMatrixRow('premium', '최상급 PC (85점 이상)', '#11302B')} + ${renderMatrixRow('high', '상급 PC (70점 ~ 85점)', '#1E8E7C')} + ${renderMatrixRow('normal', '중급 PC (40점 ~ 70점)', '#10B981')} + ${renderMatrixRow('entry', '보급 PC (40점 미만)', '#64748B')} + + + + + + + + `; + + // 셀별 동적 클릭 리스너 바인딩 + matrixTbody.querySelectorAll('.matrix-cell').forEach(cell => { + cell.addEventListener('click', () => { + const grade = cell.getAttribute('data-grade')!; + const type = cell.getAttribute('data-type')!; + + let targetList: any[] = []; + let title = ''; + + const getGradeLabel = (g: string) => { + if (g === 'premium') return '최상급 PC'; + if (g === 'high') return '상급 PC'; + if (g === 'normal') return '중급 PC'; + if (g === 'entry') return '보급 PC'; + return '전체 PC'; + }; + + const getTypeLabel = (t: string) => { + if (t === 'total') return '보유'; + if (t === 'active') return '할당 (운영)'; + if (t === 'stock') return '여분 (재고)'; + if (t === 'under') return '부족 (사양 부족)'; + return ''; + }; + + if (grade === 'all') { + if (type === 'total') { + targetList = filtered; + } else if (type === 'active') { + targetList = filtered.filter(p => !isStock(p)); + } else if (type === 'stock') { + targetList = filtered.filter(p => isStock(p)); + } else if (type === 'under') { + targetList = criticalList.filter(p => p._spec_status === '사양 부족'); + } + } else { + const data = matrix[grade as keyof typeof matrix]; + if (type === 'total') { + targetList = data.pcs; + } else if (type === 'active') { + targetList = data.activePcs; + } else if (type === 'stock') { + targetList = data.stockPcs; + } else if (type === 'under') { + targetList = data.underPcs; + } + } + + title = `${getGradeLabel(grade)} - ${getTypeLabel(type)} 자산 목록`; + showMiniListModal(title, targetList); + }); + }); // 7. 연도별 PC 노후도 집계 및 렌더링 const agingCounts = { @@ -442,9 +473,9 @@ function updateDashboardData(pcs: any[], selectedDept: string) { const renderAgingRow = (label: string, list: any[], badgeText: string, badgeStyle: string, ageGroupKey: string) => { return ` - - - + + @@ -472,7 +503,7 @@ function updateDashboardData(pcs: any[], selectedDept: string) { }); }); - // 8. 각 등급 행 클릭 리스너 설정 + // 8. 요약 지표 카드 클릭 리스너 설정 const bindCardClick = (id: string, gradeTitle: string, filterFn: (p: any) => boolean) => { const card = document.getElementById(id)!; if (!card) return; @@ -488,23 +519,11 @@ function updateDashboardData(pcs: any[], selectedDept: string) { }; }; - bindCardClick('grade-premium', '최상급 PC', p => p._pc_score >= 85); - bindCardClick('grade-high', '상급 PC', p => p._pc_score >= 70 && p._pc_score < 85); - bindCardClick('grade-normal', '중급 PC', p => p._pc_score >= 40 && p._pc_score < 70); - bindCardClick('grade-entry', '보급 PC', p => p._pc_score < 40); - // 사양 부족 / 오버스펙 / 윈도우 11 불가 클릭 리스너 설정 bindCardClick('card-under-spec', '사양 부족 검토 대상', p => p._spec_status === '사양 부족'); bindCardClick('card-over-spec', '오버스펙 검토 대상', p => p._spec_status === '오버스펙'); bindCardClick('card-win11-incompatible', '윈도우 11 업그레이드 불가 PC', p => isWindows11Incompatible(p.cpu, p.ram)); - - // 8.2 유효 재고 현황 클릭 리스너 설정 - bindCardClick('stock-premium-card', '최상급 유효 재고', p => p._pc_score >= 85 && isStock(p)); - bindCardClick('stock-high-card', '상급 유효 재고', p => p._pc_score >= 70 && p._pc_score < 85 && isStock(p)); - bindCardClick('stock-normal-card', '중급 유효 재고', p => p._pc_score >= 40 && p._pc_score < 70 && isStock(p)); - bindCardClick('stock-entry-card', '보급 유효 재고', p => p._pc_score < 40 && isStock(p)); - // 9. 직무별 사양 적정성 대수 연산 및 차트 데이터 셋 구성 (누적 막대 그래프화) const activeJobs = Array.from( new Set(filtered.map((p: any) => p[ASSET_SCHEMA.USER_POSITION.key] || '미분류').filter(j => j !== '재고PC')) @@ -545,7 +564,7 @@ function updateDashboardData(pcs: any[], selectedDept: string) { // 10. 차트들 렌더링 호출 renderChart(activeJobs, underData, normalData, overData, filtered); - renderDonutChart(gradeCounts.premium.total, gradeCounts.high.total, gradeCounts.normal.total, gradeCounts.entry.total); + renderDonutChart(matrix.premium.total, matrix.high.total, matrix.normal.total, matrix.entry.total); // 전역 상태 등록 state.activeCharts = [jobChartInstance, donutChartInstance]; @@ -849,7 +868,7 @@ function renderDonutChart(premium: number, high: number, normal: number, entry: top: 50%; left: 50%; transform: translate(-50%, -46%); - font-size: 1.56rem; + font-size: 1.75rem; font-weight: 900; color: #1E5149; font-family: 'Pretendard', sans-serif;
구분 (사용 연한)구분 (사용 연한) 보유 대수 권장 조치
${label}${data.total}대 (${totalRate}%)${data.active}대${data.stock}대${data.under}대
합계 (Total)${totalPcs}대 (100%)${totalActive}대${totalStock}대${totalUnder}대
${label}${list.length}대 + ${label}${list.length}대 ${badgeText}