feat: 등급별 자산 종합 현황 및 사양 적정성 분석 레이아웃 5:5 콤팩트 최적화
This commit is contained in:
@@ -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 = `
|
||||
|
||||
Reference in New Issue
Block a user