feat: 등급별 자산 종합 현황 및 사양 적정성 분석 레이아웃 5:5 콤팩트 최적화

This commit is contained in:
2026-06-18 15:56:51 +09:00
parent abc531a41e
commit 1d32a0350b

View File

@@ -42,8 +42,8 @@ export function renderHwDashboard(container: HTMLElement) {
</div>
</div>
<!-- 상단 섹션 (전체 높이의 약 35% 차지, stat-card와 donut/aging 나열) -->
<div style="display: grid; grid-template-columns: 1fr 1.25fr; gap: 0.75rem; height: 33%; min-height: 0; flex-shrink: 0; margin-bottom: 0.1rem;">
<!-- 상단 섹션 (전체 높이의 약 50% 차지, stat-card와 donut/aging 나열) -->
<div style="display: grid; grid-template-columns: 1fr 1.25fr; gap: 0.75rem; height: 48.5%; min-height: 0; flex-shrink: 0; margin-bottom: 0.1rem;">
<!-- 상단 좌측: 핵심 지표 4개 카드 그리드 -->
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; height: 100%;">
@@ -175,25 +175,25 @@ export function renderHwDashboard(container: HTMLElement) {
</div>
<!-- 하단 섹션 (등급별 자산 종합 현황 및 사양 적정성 분석 대형 카드) -->
<div style="background: #ffffff; border-radius: 12px; border: 1px solid #E2E8F0; padding: 1.25rem; box-shadow: 0 4px 12px rgba(0,0,0,0.03); display: flex; flex-direction: column; height: 63%; min-height: 0; transition: all 0.25s ease;"
<div style="background: #ffffff; border-radius: 12px; border: 1px solid #E2E8F0; padding: 0.8rem; box-shadow: 0 4px 12px rgba(0,0,0,0.03); display: flex; flex-direction: column; height: 49%; min-height: 0; transition: all 0.25s ease;"
onmouseover="this.style.boxShadow='0 8px 24px rgba(0,0,0,0.05)';" onmouseout="this.style.boxShadow='0 4px 12px rgba(0,0,0,0.03)';">
<div style="display: flex; flex-direction: column; gap: 0.6rem; justify-content: flex-start; height: 100%;">
<div style="display: flex; flex-direction: column; gap: 0.4rem; justify-content: flex-start; height: 100%;">
<!-- 메인 제목 -->
<div style="border-left: 4px solid #1E5149; padding-left: 8px; margin-bottom: 0.1rem; display: flex; align-items: center; line-height: 1; height: 1.6rem; flex-shrink: 0;">
<span style="font-size: 1.25rem; font-weight: 850; color: #1E293B;">등급별 자산 종합 현황 및 사양 적정성 분석</span>
<div style="border-left: 4px solid #1E5149; padding-left: 8px; margin-bottom: 0.05rem; display: flex; align-items: center; line-height: 1; height: 1.5rem; flex-shrink: 0;">
<span style="font-size: 1.15rem; font-weight: 850; color: #1E293B;">등급별 자산 종합 현황 및 사양 적정성 분석</span>
</div>
<!-- 종합 매트릭스 테이블 -->
<div style="width: 100%; overflow-y: auto; flex: 1; border-radius: 8px; border: 1px solid #E2E8F0;">
<table style="width: 100%; border-collapse: collapse; text-align: left; font-size: 1.05rem;">
<table style="width: 100%; border-collapse: collapse; text-align: left; font-size: 0.95rem;">
<thead style="position: sticky; top: 0; background: #F8FAFC; z-index: 10;">
<tr style="border-bottom: 2px solid #E2E8F0; color: #475569; font-weight: 850;">
<th style="padding: 12px 10px; width: 22%; font-size: 1.05rem; background: #F8FAFC; border-bottom: 2px solid #1E5149;">구분 (등급)</th>
<th style="padding: 12px 10px; text-align: center; width: 11%; font-size: 1.05rem; background: #F8FAFC; border-bottom: 2px solid #1E5149;">보유량</th>
<th style="padding: 12px 10px; text-align: center; width: 11%; font-size: 1.05rem; background: #F8FAFC; border-bottom: 2px solid #1E5149;">운영중</th>
<th style="padding: 12px 10px; text-align: center; width: 11%; font-size: 1.05rem; background: #F8FAFC; border-bottom: 2px solid #1E5149;">재고</th>
<th style="padding: 12px 10px; text-align: center; width: 11%; color: #EF4444; font-size: 1.05rem; background: #F8FAFC; border-bottom: 2px solid #1E5149;">구매 필요</th>
<th style="padding: 12px 10px; text-align: center; width: 34%; font-size: 1.05rem; background: #F8FAFC; border-bottom: 2px solid #1E5149;">사양 적정성 분석 (직무 기준)</th>
<th style="padding: 6px 8px; width: 22%; font-size: 0.95rem; background: #F8FAFC; border-bottom: 2px solid #1E5149;">구분 (등급)</th>
<th style="padding: 6px 8px; text-align: center; width: 11%; font-size: 0.95rem; background: #F8FAFC; border-bottom: 2px solid #1E5149;">보유량</th>
<th style="padding: 6px 8px; text-align: center; width: 11%; font-size: 0.95rem; background: #F8FAFC; border-bottom: 2px solid #1E5149;">운영중</th>
<th style="padding: 6px 8px; text-align: center; width: 11%; font-size: 0.95rem; background: #F8FAFC; border-bottom: 2px solid #1E5149;">재고</th>
<th style="padding: 6px 8px; text-align: center; width: 11%; color: #EF4444; font-size: 0.95rem; background: #F8FAFC; border-bottom: 2px solid #1E5149;">구매 필요</th>
<th style="padding: 6px 8px; text-align: center; width: 34%; font-size: 0.95rem; background: #F8FAFC; border-bottom: 2px solid #1E5149;">사양 적정성 분석 (직무 기준)</th>
</tr>
</thead>
<tbody id="pc-grade-matrix-tbody">
@@ -405,7 +405,7 @@ function updateDashboardData(pcs: any[], selectedDept: string) {
const data = matrix[gradeKey];
const totalRate = filtered.length > 0 ? Math.round((data.total / filtered.length) * 100) : 0;
const cellStyle = `padding: 10px 8px; text-align: center; font-weight: 700; cursor: pointer; transition: background 0.2s; font-size: 1.05rem;`;
const cellStyle = `padding: 6px 8px; text-align: center; font-weight: 700; cursor: pointer; transition: background 0.2s; font-size: 0.95rem;`;
const hoverEvents = `onmouseover="this.style.background='#F1F5F9'" onmouseout="this.style.background='none'"`;
// 사양 적정성 분석 데이터 계산 (운영중인 자산만)
@@ -419,31 +419,31 @@ function updateDashboardData(pcs: any[], selectedDept: string) {
let barGraphHtml = '';
if (activeCount > 0) {
barGraphHtml = `
<div style="display: flex; flex-direction: column; gap: 6px; align-items: center; justify-content: center; width: 100%;">
<div style="display: flex; height: 16px; border-radius: 8px; overflow: hidden; background: #EEF2F6; width: 100%; max-width: 220px; box-shadow: inset 0 1px 2px rgba(0,0,0,0.06);">
<div style="display: flex; flex-direction: column; gap: 2px; align-items: center; justify-content: center; width: 100%;">
<div style="display: flex; height: 11px; border-radius: 8px; overflow: hidden; background: #EEF2F6; width: 100%; max-width: 220px; box-shadow: inset 0 1px 2px rgba(0,0,0,0.06);">
${under > 0 ? `<div style="width: ${underPct}%; background: #EF4444; border-right: 2px solid #ffffff; cursor: pointer; transition: opacity 0.15s;" title="사양 부족: ${under}대 (${Math.round(underPct)}%)" class="spec-segment-btn" data-grade="${gradeKey}" data-spec-status="사양 부족" onmouseover="this.style.opacity='0.85'" onmouseout="this.style.opacity='1'"></div>` : ''}
${normal > 0 ? `<div style="width: ${normalPct}%; background: #1E5149; border-right: 2px solid #ffffff; cursor: pointer; transition: opacity 0.15s;" title="적정 사양: ${normal}대 (${Math.round(normalPct)}%)" class="spec-segment-btn" data-grade="${gradeKey}" data-spec-status="적정" onmouseover="this.style.opacity='0.85'" onmouseout="this.style.opacity='1'"></div>` : ''}
${over > 0 ? `<div style="width: ${overPct}%; background: #F59E0B; cursor: pointer; transition: opacity 0.15s;" title="오버 스펙: ${over}대 (${Math.round(overPct)}%)" class="spec-segment-btn" data-grade="${gradeKey}" data-spec-status="오버스펙" onmouseover="this.style.opacity='0.85'" onmouseout="this.style.opacity='1'"></div>` : ''}
</div>
<div style="display: flex; gap: 6px; align-items: center;">
${under > 0 ? `<span style="padding: 2px 8px; border-radius: 9999px; font-size: 11px; font-weight: 800; background: #FEE2E2; color: #EF4444; cursor: pointer; transition: all 0.2s;" class="spec-text-btn" data-grade="${gradeKey}" data-spec-status="사양 부족" onmouseover="this.style.opacity='0.8'" onmouseout="this.style.opacity='1'">부족 ${under}</span>` : ''}
${normal > 0 ? `<span style="padding: 2px 8px; border-radius: 9999px; font-size: 11px; font-weight: 800; background: #D1FAE5; color: #065F46; cursor: pointer; transition: all 0.2s;" class="spec-text-btn" data-grade="${gradeKey}" data-spec-status="적정" onmouseover="this.style.opacity='0.8'" onmouseout="this.style.opacity='1'">적정 ${normal}</span>` : ''}
${over > 0 ? `<span style="padding: 2px 8px; border-radius: 9999px; font-size: 11px; font-weight: 800; background: #FEF3C7; color: #92400E; cursor: pointer; transition: all 0.2s;" class="spec-text-btn" data-grade="${gradeKey}" data-spec-status="오버스펙" onmouseover="this.style.opacity='0.8'" onmouseout="this.style.opacity='1'">오버 ${over}</span>` : ''}
<div style="display: flex; gap: 4px; align-items: center;">
${under > 0 ? `<span style="padding: 1px 5px; border-radius: 9999px; font-size: 10px; font-weight: 800; background: #FEE2E2; color: #EF4444; cursor: pointer; transition: all 0.2s;" class="spec-text-btn" data-grade="${gradeKey}" data-spec-status="사양 부족" onmouseover="this.style.opacity='0.8'" onmouseout="this.style.opacity='1'">부족 ${under}</span>` : ''}
${normal > 0 ? `<span style="padding: 1px 5px; border-radius: 9999px; font-size: 10px; font-weight: 800; background: #D1FAE5; color: #065F46; cursor: pointer; transition: all 0.2s;" class="spec-text-btn" data-grade="${gradeKey}" data-spec-status="적정" onmouseover="this.style.opacity='0.8'" onmouseout="this.style.opacity='1'">적정 ${normal}</span>` : ''}
${over > 0 ? `<span style="padding: 1px 5px; border-radius: 9999px; font-size: 10px; font-weight: 800; background: #FEF3C7; color: #92400E; cursor: pointer; transition: all 0.2s;" class="spec-text-btn" data-grade="${gradeKey}" data-spec-status="오버스펙" onmouseover="this.style.opacity='0.8'" onmouseout="this.style.opacity='1'">오버 ${over}</span>` : ''}
</div>
</div>
`;
} else {
barGraphHtml = `<span style="font-size: 0.88rem; color: #94A3B8; font-weight: 550;">운영중 자산 없음</span>`;
barGraphHtml = `<span style="font-size: 0.85rem; color: #94A3B8; font-weight: 550;">운영중 자산 없음</span>`;
}
return `
<tr style="border-bottom: 1px solid #E2E8F0;">
<td style="padding: 12px 10px; font-weight: 800; color: ${color}; font-size: 1.05rem;">${label}</td>
<td class="matrix-cell" data-grade="${gradeKey}" data-type="total" style="${cellStyle}" ${hoverEvents}>${data.total}대 <span style="font-size:0.88rem; color:#64748B; font-weight:500;">(${totalRate}%)</span></td>
<td style="padding: 6px 8px; font-weight: 800; color: ${color}; font-size: 0.95rem;">${label}</td>
<td class="matrix-cell" data-grade="${gradeKey}" data-type="total" style="${cellStyle}" ${hoverEvents}>${data.total}대 <span style="font-size:0.82rem; color:#64748B; font-weight:500;">(${totalRate}%)</span></td>
<td class="matrix-cell" data-grade="${gradeKey}" data-type="active" style="${cellStyle}" ${hoverEvents}>${data.active}대</td>
<td class="matrix-cell" data-grade="${gradeKey}" data-type="stock" style="${cellStyle}" ${hoverEvents}>${data.stock}대</td>
<td class="matrix-cell" data-grade="${gradeKey}" data-type="under" style="${cellStyle} color: #EF4444;" ${hoverEvents}>${shortage}대</td>
<td style="padding: 10px 8px; text-align: center; font-weight: 700; font-size: 1.05rem; vertical-align: middle;">
<td style="padding: 6px 8px; text-align: center; font-weight: 700; font-size: 0.95rem; vertical-align: middle;">
${barGraphHtml}
</td>
</tr>
@@ -475,24 +475,24 @@ function updateDashboardData(pcs: any[], selectedDept: string) {
let totBarGraphHtml = '';
if (totalActive > 0) {
totBarGraphHtml = `
<div style="display: flex; flex-direction: column; gap: 6px; align-items: center; justify-content: center; width: 100%;">
<div style="display: flex; height: 16px; border-radius: 8px; overflow: hidden; background: #EEF2F6; width: 100%; max-width: 220px; box-shadow: inset 0 1px 2px rgba(0,0,0,0.06);">
<div style="display: flex; flex-direction: column; gap: 2px; align-items: center; justify-content: center; width: 100%;">
<div style="display: flex; height: 11px; border-radius: 8px; overflow: hidden; background: #EEF2F6; width: 100%; max-width: 220px; box-shadow: inset 0 1px 2px rgba(0,0,0,0.06);">
${totUnder > 0 ? `<div style="width: ${totUnderPct}%; background: #EF4444; border-right: 2px solid #ffffff; cursor: pointer; transition: opacity 0.15s;" title="사양 부족: ${totUnder}대 (${Math.round(totUnderPct)}%)" class="spec-segment-btn" data-grade="all" data-spec-status="사양 부족" onmouseover="this.style.opacity='0.85'" onmouseout="this.style.opacity='1'"></div>` : ''}
${totNormal > 0 ? `<div style="width: ${totNormalPct}%; background: #1E5149; border-right: 2px solid #ffffff; cursor: pointer; transition: opacity 0.15s;" title="적정 사양: ${totNormal}대 (${Math.round(totNormalPct)}%)" class="spec-segment-btn" data-grade="all" data-spec-status="적정" onmouseover="this.style.opacity='0.85'" onmouseout="this.style.opacity='1'"></div>` : ''}
${totOver > 0 ? `<div style="width: ${totOverPct}%; background: #F59E0B; cursor: pointer; transition: opacity 0.15s;" title="오버 스펙: ${totOver}대 (${Math.round(totOverPct)}%)" class="spec-segment-btn" data-grade="all" data-spec-status="오버스펙" onmouseover="this.style.opacity='0.85'" onmouseout="this.style.opacity='1'"></div>` : ''}
${totOver > 0 ? `<div style="width: ${totOverPct}%; background: #F59E0B; border-right: 2px solid #ffffff; cursor: pointer; transition: opacity 0.15s;" title="오버 스펙: ${totOver}대 (${Math.round(totOverPct)}%)" class="spec-segment-btn" data-grade="all" data-spec-status="오버스펙" onmouseover="this.style.opacity='0.85'" onmouseout="this.style.opacity='1'"></div>` : ''}
</div>
<div style="display: flex; gap: 6px; align-items: center;">
${totUnder > 0 ? `<span style="padding: 2px 8px; border-radius: 9999px; font-size: 11px; font-weight: 800; background: #FEE2E2; color: #EF4444; cursor: pointer; transition: all 0.2s;" class="spec-text-btn" data-grade="all" data-spec-status="사양 부족" onmouseover="this.style.opacity='0.8'" onmouseout="this.style.opacity='1'">부족 ${totUnder}</span>` : ''}
${totNormal > 0 ? `<span style="padding: 2px 8px; border-radius: 9999px; font-size: 11px; font-weight: 800; background: #D1FAE5; color: #065F46; cursor: pointer; transition: all 0.2s;" class="spec-text-btn" data-grade="all" data-spec-status="적정" onmouseover="this.style.opacity='0.8'" onmouseout="this.style.opacity='1'">적정 ${totNormal}</span>` : ''}
${totOver > 0 ? `<span style="padding: 2px 8px; border-radius: 9999px; font-size: 11px; font-weight: 800; background: #FEF3C7; color: #92400E; cursor: pointer; transition: all 0.2s;" class="spec-text-btn" data-grade="all" data-spec-status="오버스펙" onmouseover="this.style.opacity='0.8'" onmouseout="this.style.opacity='1'">오버 ${totOver}</span>` : ''}
<div style="display: flex; gap: 4px; align-items: center;">
${totUnder > 0 ? `<span style="padding: 1px 5px; border-radius: 9999px; font-size: 10px; font-weight: 800; background: #FEE2E2; color: #EF4444; cursor: pointer; transition: all 0.2s;" class="spec-text-btn" data-grade="all" data-spec-status="사양 부족" onmouseover="this.style.opacity='0.8'" onmouseout="this.style.opacity='1'">부족 ${totUnder}</span>` : ''}
${totNormal > 0 ? `<span style="padding: 1px 5px; border-radius: 9999px; font-size: 10px; font-weight: 800; background: #D1FAE5; color: #065F46; cursor: pointer; transition: all 0.2s;" class="spec-text-btn" data-grade="all" data-spec-status="적정" onmouseover="this.style.opacity='0.8'" onmouseout="this.style.opacity='1'">적정 ${totNormal}</span>` : ''}
${totOver > 0 ? `<span style="padding: 1px 5px; border-radius: 9999px; font-size: 10px; font-weight: 800; background: #FEF3C7; color: #92400E; cursor: pointer; transition: all 0.2s;" class="spec-text-btn" data-grade="all" data-spec-status="오버스펙" onmouseover="this.style.opacity='0.8'" onmouseout="this.style.opacity='1'">오버 ${totOver}</span>` : ''}
</div>
</div>
`;
} else {
totBarGraphHtml = `<span style="font-size: 0.88rem; color: #94A3B8; font-weight: 550;">운영중 자산 없음</span>`;
totBarGraphHtml = `<span style="font-size: 0.85rem; color: #94A3B8; font-weight: 550;">운영중 자산 없음</span>`;
}
const cellStyleHeader = `padding: 12px 10px; text-align: center; font-weight: 800; cursor: pointer; transition: background 0.2s; background: #F8FAFC; font-size: 1.05rem;`;
const cellStyleHeader = `padding: 6px 8px; text-align: center; font-weight: 800; cursor: pointer; transition: background 0.2s; background: #F8FAFC; font-size: 0.95rem;`;
const hoverEventsHeader = `onmouseover="this.style.background='#EEF2F6'" onmouseout="this.style.background='#F8FAFC'"`;
matrixTbody.innerHTML = `