feat: 대시보드 폰트 크기, 패딩 조절 및 자산목록 정렬 기준 변경(updated_at 내림차순)
This commit is contained in:
@@ -18,10 +18,10 @@ export function renderHwDashboard(container: HTMLElement) {
|
||||
|
||||
// 2. 1페이지 매거진 리포트(제목바 제거, '| 제목' 미니멀리즘 스타일) HTML 빌드
|
||||
container.innerHTML = `
|
||||
<div class="view-container" style="overflow: hidden; padding: 0.75rem 1.5rem; background-color: #F8FAFC; height: calc(100vh - var(--header-height) - 48px); box-sizing: border-box; display: flex; flex-direction: column; gap: 0.9rem; font-family: 'Pretendard', sans-serif; color: #1E293B;">
|
||||
<div class="view-container" style="overflow: hidden; padding: 0.4rem 1.2rem; background-color: #F8FAFC; height: calc(100vh - var(--header-height) - 48px); box-sizing: border-box; display: flex; flex-direction: column; gap: 0.5rem; font-family: 'Pretendard', sans-serif; color: #1E293B;">
|
||||
|
||||
<!-- 대시보드 타이틀 및 사용조직 필터 -->
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-end; flex-shrink: 0; padding-bottom: 0.75rem;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-end; flex-shrink: 0; padding-bottom: 0.4rem;">
|
||||
<div style="border-left: 4px solid #1E5149; padding-left: 8px;">
|
||||
<h2 style="font-size: 1.65rem; font-weight: 850; color: #1E5149; margin: 0; letter-spacing: -0.5px; display: flex; align-items: center; gap: 0.6rem;">
|
||||
개인 PC 자산 대시보드
|
||||
@@ -44,62 +44,62 @@ export function renderHwDashboard(container: HTMLElement) {
|
||||
</div>
|
||||
|
||||
<!-- 메인 2단 컬럼 레이아웃 (5:5 비율) -->
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 0.9rem; flex: 1; min-height: 0; margin-bottom: 0.1rem;">
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; flex: 1; min-height: 0; margin-bottom: 0.1rem;">
|
||||
|
||||
<!-- 좌측 컬럼 (Left Column) -->
|
||||
<div style="display: flex; flex-direction: column; gap: 0.7rem; min-height: 0;">
|
||||
<div style="display: flex; flex-direction: column; gap: 0.5rem; min-height: 0;">
|
||||
|
||||
<!-- 핵심 지표 카드 -->
|
||||
<div class="stat-card" style="background: transparent; border-radius: 0; padding: 0.55rem 0.25rem; border: none; border-bottom: 1px solid #E2E8F0; display: flex !important; flex-direction: row !important; align-items: center; justify-content: space-between; flex-shrink: 0; gap: 0;">
|
||||
<div class="stat-card" style="background: transparent; border-radius: 0; padding: 0.75rem 0.25rem; border: none; border-bottom: 1px solid #E2E8F0; display: grid !important; grid-template-columns: 1fr 1fr; gap: 0.6rem 0.9rem; flex-shrink: 0;">
|
||||
|
||||
<!-- 1. 보유 자산 수량 -->
|
||||
<div style="flex: 1; border-right: 1px solid #EEF2F6; padding-right: 0.85rem;">
|
||||
<div style="border-right: 1px solid #EEF2F6; border-bottom: 1px solid #EEF2F6; padding-bottom: 0.65rem; padding-right: 1.0rem;">
|
||||
<div style="border-left: 4px solid #1E5149; padding-left: 8px; margin-bottom: 0.5rem; display: flex; align-items: center; line-height: 1;">
|
||||
<span style="font-size: 1.03rem; font-weight: 800; color: #1E293B; white-space: nowrap;">보유 자산 수량</span>
|
||||
<span style="font-size: 1.15rem; font-weight: 800; color: #1E293B; white-space: nowrap;">보유 자산 수량</span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: flex-end; justify-content: space-between;">
|
||||
<div>
|
||||
<div id="metric-total-pcs" style="font-size: 1.96rem; font-weight: 900; color: #1E5149; line-height: 1; margin-bottom: 0.2rem;">0대</div>
|
||||
<span style="font-size: 0.93rem; color: #64748B; font-weight: 700; white-space: nowrap;">전사 보유 개인용 PC</span>
|
||||
<div id="metric-total-pcs" style="font-size: 2.3rem; font-weight: 900; color: #1E5149; line-height: 1; margin-bottom: 0.35rem;">0대</div>
|
||||
<span style="font-size: 1.05rem; color: #64748B; font-weight: 700; white-space: nowrap;">전사 보유 개인용 PC</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2. 사양 부족 검토 -->
|
||||
<div id="card-under-spec" style="flex: 1; border-right: 1px solid #EEF2F6; padding-left: 0.85rem; padding-right: 0.85rem; cursor: pointer; transition: opacity 0.2s;" onmouseover="this.style.opacity='0.7'" onmouseout="this.style.opacity='1'">
|
||||
<!-- 2. 사양 부족 -->
|
||||
<div id="card-under-spec" style="border-bottom: 1px solid #EEF2F6; padding-bottom: 0.65rem; padding-left: 1.0rem; cursor: pointer; transition: opacity 0.2s;" onmouseover="this.style.opacity='0.7'" onmouseout="this.style.opacity='1'">
|
||||
<div style="border-left: 4px solid #EF4444; padding-left: 8px; margin-bottom: 0.5rem; display: flex; align-items: center; line-height: 1;">
|
||||
<span style="font-size: 1.03rem; font-weight: 800; color: #1E293B; white-space: nowrap;">사양 부족 검토</span>
|
||||
<span style="font-size: 1.15rem; font-weight: 800; color: #1E293B; white-space: nowrap;">사양 부족</span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: flex-end; justify-content: space-between;">
|
||||
<div>
|
||||
<div id="metric-under-spec" style="font-size: 1.96rem; font-weight: 900; color: #EF4444; line-height: 1; margin-bottom: 0.2rem;">0명</div>
|
||||
<span style="font-size: 0.93rem; color: #64748B; font-weight: 700; white-space: nowrap;">사양 교체 권고 자산</span>
|
||||
<div id="metric-under-spec" style="font-size: 2.3rem; font-weight: 900; color: #EF4444; line-height: 1; margin-bottom: 0.35rem;">0대</div>
|
||||
<span style="font-size: 1.05rem; color: #64748B; font-weight: 700; white-space: nowrap;">사양 교체 권고 자산</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3. 오버스펙 검토 -->
|
||||
<div id="card-over-spec" style="flex: 1; border-right: 1px solid #EEF2F6; padding-left: 0.85rem; padding-right: 0.85rem; cursor: pointer; transition: opacity 0.2s;" onmouseover="this.style.opacity='0.7'" onmouseout="this.style.opacity='1'">
|
||||
<!-- 3. 오버 스펙 -->
|
||||
<div id="card-over-spec" style="border-right: 1px solid #EEF2F6; padding-top: 0.65rem; padding-right: 1.0rem; cursor: pointer; transition: opacity 0.2s;" onmouseover="this.style.opacity='0.7'" onmouseout="this.style.opacity='1'">
|
||||
<div style="border-left: 4px solid #F59E0B; padding-left: 8px; margin-bottom: 0.5rem; display: flex; align-items: center; line-height: 1;">
|
||||
<span style="font-size: 1.03rem; font-weight: 800; color: #1E293B; white-space: nowrap;">오버스펙 검토</span>
|
||||
<span style="font-size: 1.15rem; font-weight: 800; color: #1E293B; white-space: nowrap;">오버 스펙</span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: flex-end; justify-content: space-between;">
|
||||
<div>
|
||||
<div id="metric-over-spec" style="font-size: 1.96rem; font-weight: 900; color: #F59E0B; line-height: 1; margin-bottom: 0.2rem;">0명</div>
|
||||
<span style="font-size: 0.93rem; color: #64748B; font-weight: 700; white-space: nowrap;">사양 회수 권고 자산</span>
|
||||
<div id="metric-over-spec" style="font-size: 2.3rem; font-weight: 900; color: #F59E0B; line-height: 1; margin-bottom: 0.35rem;">0대</div>
|
||||
<span style="font-size: 1.05rem; color: #64748B; font-weight: 700; white-space: nowrap;">사양 회수 권고 자산</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 4. 윈도우 11 불가 PC -->
|
||||
<div id="card-win11-incompatible" style="flex: 1; padding-left: 0.85rem; padding-right: 0.25rem; cursor: pointer; transition: opacity 0.2s;" onmouseover="this.style.opacity='0.7'" onmouseout="this.style.opacity='1'">
|
||||
<div id="card-win11-incompatible" style="padding-top: 0.65rem; padding-left: 1.0rem; cursor: pointer; transition: opacity 0.2s;" onmouseover="this.style.opacity='0.7'" onmouseout="this.style.opacity='1'">
|
||||
<div style="border-left: 4px solid #3B82F6; padding-left: 8px; margin-bottom: 0.5rem; display: flex; align-items: center; line-height: 1;">
|
||||
<span style="font-size: 1.03rem; font-weight: 800; color: #1E293B; white-space: nowrap;">윈도우 11 불가 PC</span>
|
||||
<span style="font-size: 1.15rem; font-weight: 800; color: #1E293B; white-space: nowrap;">윈도우 11 불가 PC</span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: flex-end; justify-content: space-between;">
|
||||
<div>
|
||||
<div id="metric-win11-incompatible" style="font-size: 1.96rem; font-weight: 900; color: #3B82F6; line-height: 1; margin-bottom: 0.2rem;">0대</div>
|
||||
<span style="font-size: 0.93rem; color: #64748B; font-weight: 700; white-space: nowrap;">업데이트 미지원 하드웨어</span>
|
||||
<div id="metric-win11-incompatible" style="font-size: 2.3rem; font-weight: 900; color: #3B82F6; line-height: 1; margin-bottom: 0.35rem;">0대</div>
|
||||
<span style="font-size: 1.05rem; color: #64748B; font-weight: 700; white-space: nowrap;">업데이트 미지원 하드웨어</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -107,27 +107,25 @@ export function renderHwDashboard(container: HTMLElement) {
|
||||
</div>
|
||||
|
||||
|
||||
<!-- PC 등급별 보유 현황 (등급별 게이지 + 우측 사양 적정성 도넛차트) -->
|
||||
<!-- 등급별 자산 종합 현황 (하나로 통합) -->
|
||||
<div style="background: transparent; border-radius: 0; padding: 0.75rem 0.25rem; border: none; border-bottom: 1px solid #E2E8F0; display: grid; grid-template-columns: 1.65fr 1fr; gap: 1.5rem; flex: 1.0; min-height: 0;">
|
||||
<!-- 등급별 자산 종합 현황 (좌측 하단 단독 배치 및 크기 확대) -->
|
||||
<div style="background: transparent; border-radius: 0; padding: 0.75rem 0.25rem; border: none; border-bottom: 1px solid #E2E8F0; display: flex; flex-direction: column; flex: 1.0; min-height: 0;">
|
||||
|
||||
<!-- 1열: 등급별 보유 현황 및 종합 매트릭스 테이블 영역 -->
|
||||
<div style="display: flex; flex-direction: column; gap: 0.6rem; justify-content: flex-start; padding-left: 0.5rem;">
|
||||
<div style="display: flex; flex-direction: column; gap: 0.9rem; justify-content: flex-start; padding-left: 0.5rem; height: 100%;">
|
||||
<!-- 메인 제목 -->
|
||||
<div style="border-left: 4px solid #1E5149; padding-left: 8px; margin-bottom: 0.65rem; display: flex; align-items: center; line-height: 1; height: 1.5rem;">
|
||||
<span style="font-size: 1.11rem; font-weight: 800; color: #1E293B;">등급별 자산 종합 현황</span>
|
||||
<div style="border-left: 4px solid #1E5149; padding-left: 8px; margin-bottom: 0.4rem; display: flex; align-items: center; line-height: 1; height: 1.7rem; flex-shrink: 0;">
|
||||
<span style="font-size: 1.25rem; font-weight: 850; color: #1E293B;">등급별 자산 종합 현황</span>
|
||||
</div>
|
||||
|
||||
<!-- 종합 매트릭스 테이블 -->
|
||||
<div style="width: 100%; overflow-x: auto;">
|
||||
<table style="width: 100%; border-collapse: collapse; text-align: left; font-size: 1.15rem;">
|
||||
<!-- 종합 매트릭스 테이블 (폰트 크기 1.25rem 으로 확대 및 꽉 채우기) -->
|
||||
<div style="width: 100%; overflow-x: auto; flex: 1; display: flex; align-items: stretch;">
|
||||
<table style="width: 100%; border-collapse: collapse; text-align: left; font-size: 1.25rem; height: 100%;">
|
||||
<thead>
|
||||
<tr style="border-bottom: 2px solid #1E5149; color: #475569; font-weight: 800;">
|
||||
<th style="padding: 14px 4px; width: 32%;">구분 (등급)</th>
|
||||
<th style="padding: 14px 4px; text-align: center; width: 17%;">보유량</th>
|
||||
<th style="padding: 14px 4px; text-align: center; width: 17%;">할당량</th>
|
||||
<th style="padding: 14px 4px; text-align: center; width: 17%;">여분</th>
|
||||
<th style="padding: 14px 4px; text-align: center; width: 17%; color: #EF4444;">부족분</th>
|
||||
<tr style="border-bottom: 2px solid #1E5149; color: #475569; font-weight: 850;">
|
||||
<th style="padding: 14px 10px; width: 32%; font-size: 1.25rem;">구분 (등급)</th>
|
||||
<th style="padding: 14px 10px; text-align: center; width: 17%; font-size: 1.25rem;">보유량</th>
|
||||
<th style="padding: 14px 10px; text-align: center; width: 17%; font-size: 1.25rem;">운영중</th>
|
||||
<th style="padding: 14px 10px; text-align: center; width: 17%; font-size: 1.25rem;">재고</th>
|
||||
<th style="padding: 14px 10px; text-align: center; width: 17%; color: #EF4444; font-size: 1.25rem;">부족분</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="pc-grade-matrix-tbody">
|
||||
@@ -137,20 +135,40 @@ export function renderHwDashboard(container: HTMLElement) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2열: 등급별 보유 비율 도넛 영역 -->
|
||||
<div style="display: flex; flex-direction: column; align-items: center; justify-content: flex-start; gap: 0.8rem; padding-top: 0.5rem;">
|
||||
<!-- 서브 제목 -->
|
||||
<div style="margin-bottom: 0.35rem; display: flex; align-items: center; justify-content: center; line-height: 1; height: 1.5rem;">
|
||||
<span style="font-size: 1.06rem; font-weight: 800; color: #475569; text-transform: uppercase;">등급별 보유 비율</span>
|
||||
</div>
|
||||
|
||||
<!-- 도넛 그래프 (크기 확대 및 범례 추가, 제목 상단 이관) -->
|
||||
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; flex-shrink: 0; padding-right: 0.25rem; width: 100%;">
|
||||
<div style="width: 170px; height: 170px; position: relative;">
|
||||
</div>
|
||||
|
||||
<!-- 우측 컬럼 (Right Column) -->
|
||||
<div style="display: flex; flex-direction: column; gap: 0.5rem; min-height: 0;">
|
||||
|
||||
<!-- 직무별 사양 적정성 분석 차트 카드 -->
|
||||
<div style="background: transparent; border-radius: 0; padding: 0.7rem 0.25rem; border: none; border-bottom: 1px solid #E2E8F0; display: flex; flex-direction: column; flex: 1.0; min-height: 0;">
|
||||
<div style="border-left: 4px solid #1E5149; padding-left: 8px; margin-bottom: 0.5rem; display: flex; align-items: center; line-height: 1; flex-shrink: 0;">
|
||||
<span style="font-size: 1.25rem; font-weight: 850; color: #1E293B;">직무별 사양 적정성 분석</span>
|
||||
</div>
|
||||
<div style="flex: 1; min-height: 0; width: 100%; position: relative;">
|
||||
<canvas id="chart-job-scores" style="width: 100%; height: 100%;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 우측 하단: 등급별 보유 비율 도넛 & 연도별 PC 노후도 통합 배치 (너비 축소) -->
|
||||
<div style="background: transparent; border-radius: 0; padding: 0.7rem 0.25rem; border: none; border-bottom: 1px solid #E2E8F0; display: grid; grid-template-columns: 1.15fr 1.25fr; gap: 0.8rem; flex: 1.0; min-height: 0;">
|
||||
|
||||
<!-- 1열: 등급별 보유 비율 도넛 영역 -->
|
||||
<div style="display: flex; flex-direction: column; align-items: center; justify-content: flex-start; gap: 0.7rem; padding-top: 0.1rem; min-height: 0; height: 100%;">
|
||||
<!-- 서브 제목 -->
|
||||
<div style="width: 100%; border-left: 4px solid #1E5149; padding-left: 8px; margin-bottom: 0.5rem; display: flex; align-items: center; line-height: 1; flex-shrink: 0; height: 1.5rem;">
|
||||
<span style="font-size: 1.25rem; font-weight: 850; color: #1E293B;">등급별 보유 비율</span>
|
||||
</div>
|
||||
|
||||
<!-- 도넛 그래프 (크기 조절 및 수직 가운데 정렬) -->
|
||||
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; flex: 1; width: 100%; min-height: 0;">
|
||||
<div style="width: 180px; height: 180px; position: relative;">
|
||||
<canvas id="chart-overall-donut"></canvas>
|
||||
</div>
|
||||
<!-- 커스텀 범례 -->
|
||||
<div style="display: flex; gap: 0.65rem; justify-content: center; align-items: center; margin-top: 8px; font-size: 1.15rem; font-weight: 700; color: #475569;">
|
||||
<!-- 커스텀 범례 (폰트 최적화) -->
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 0.4rem 0.6rem; justify-content: center; align-items: center; margin-top: 10px; font-size: 1.05rem; font-weight: 700; color: #475569; width: 100%;">
|
||||
<div style="display: flex; align-items: center; gap: 4px;">
|
||||
<span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: #11302B;"></span>
|
||||
<span>최상급</span>
|
||||
@@ -171,35 +189,18 @@ export function renderHwDashboard(container: HTMLElement) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2열: 연도별 PC 노후도 및 교체 주기 예측 카드 (너비 줄임) -->
|
||||
<div style="display: flex; flex-direction: column; min-height: 0;">
|
||||
<div style="border-left: 4px solid #1E5149; padding-left: 8px; margin-bottom: 0.5rem; display: flex; align-items: center; line-height: 1; flex-shrink: 0; height: 1.5rem;">
|
||||
<span style="font-size: 1.25rem; font-weight: 850; color: #1E293B; white-space: nowrap;">연도별 PC 노후도 및 예측</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 우측 컬럼 (Right Column) -->
|
||||
<div style="display: flex; flex-direction: column; gap: 0.9rem; min-height: 0;">
|
||||
|
||||
<!-- 직무별 사양 적정성 분석 차트 카드 -->
|
||||
<div style="background: transparent; border-radius: 0; padding: 0.85rem 0.25rem; border: none; border-bottom: 1px solid #E2E8F0; display: flex; flex-direction: column; flex: 1.1; min-height: 0;">
|
||||
<div style="border-left: 4px solid #1E5149; padding-left: 8px; margin-bottom: 0.9rem; display: flex; align-items: center; line-height: 1; flex-shrink: 0;">
|
||||
<span style="font-size: 1.11rem; font-weight: 800; color: #1E293B;">직무별 사양 적정성 분석</span>
|
||||
</div>
|
||||
<div style="flex: 1; min-height: 0; width: 100%; position: relative;">
|
||||
<canvas id="chart-job-scores" style="width: 100%; height: 100%;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 연도별 PC 노후도 및 교체 주기 예측 카드 -->
|
||||
<div style="background: transparent; border-radius: 0; padding: 0.85rem 0.25rem; border: none; border-bottom: 1px solid #E2E8F0; display: flex; flex-direction: column; flex: 0.9; min-height: 0;">
|
||||
<div style="border-left: 4px solid #1E5149; padding-left: 8px; margin-bottom: 0.9rem; display: flex; align-items: center; line-height: 1; flex-shrink: 0;">
|
||||
<span style="font-size: 1.15rem; font-weight: 800; color: #1E293B;">연도별 PC 노후도 및 교체 주기 예측</span>
|
||||
</div>
|
||||
<div style="flex: 1; overflow: hidden; min-height: 0; padding-left: 12px;">
|
||||
<div style="flex: 1; overflow: hidden; min-height: 0; padding-right: 0.2rem;">
|
||||
<table style="width: 100%; border-collapse: collapse; text-align: left; font-size: 1.15rem;">
|
||||
<thead style="position: sticky; top: 0; background: white; z-index: 5;">
|
||||
<tr style="border-bottom: 2px solid #1E5149; color: #475569; font-weight: 800;">
|
||||
<th style="padding: 10px 4px 10px 0; width: 50%;">구분 (사용 연한)</th>
|
||||
<th style="padding: 10px 4px; width: 25%; text-align: center;">보유 대수</th>
|
||||
<th style="padding: 10px 4px; width: 25%; text-align: center;">권장 조치</th>
|
||||
<tr style="border-bottom: 2px solid #1E5149; color: #475569; font-weight: 850;">
|
||||
<th style="padding: 12px 10px; width: 45%; font-size: 1.15rem;">구분 (연한)</th>
|
||||
<th style="padding: 12px 10px; text-align: center; width: 25%; font-size: 1.15rem;">보유</th>
|
||||
<th style="padding: 12px 10px; text-align: center; width: 30%; font-size: 1.15rem;">권장 조치</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="pc-aging-tbody">
|
||||
@@ -208,6 +209,7 @@ export function renderHwDashboard(container: HTMLElement) {
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -322,20 +324,35 @@ function updateDashboardData(pcs: any[], selectedDept: string) {
|
||||
const job = p[ASSET_SCHEMA.USER_POSITION.key] || '미분류';
|
||||
const avg = jobScores[job]?.avg || 0;
|
||||
|
||||
const win11Incompatible = isWindows11Incompatible(p.cpu, p.ram);
|
||||
let isUnder = false;
|
||||
|
||||
if (avg > 0 && job !== '재고PC') {
|
||||
if (score < avg * 0.6) {
|
||||
isUnder = true;
|
||||
p._spec_status = '사양 부족';
|
||||
} else if (score > avg * 1.5 && !win11Incompatible) {
|
||||
p._spec_status = '오버스펙';
|
||||
criticalList.push(p);
|
||||
overSpecCount++;
|
||||
} else if (win11Incompatible) {
|
||||
isUnder = true;
|
||||
p._spec_status = '사양 부족';
|
||||
} else {
|
||||
p._spec_status = '적정';
|
||||
}
|
||||
} else {
|
||||
if (win11Incompatible) {
|
||||
isUnder = true;
|
||||
p._spec_status = '사양 부족';
|
||||
}
|
||||
}
|
||||
|
||||
if (isUnder) {
|
||||
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 = '적정';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,8 +364,8 @@ function updateDashboardData(pcs: any[], selectedDept: string) {
|
||||
|
||||
// 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-under-spec')!.textContent = `${underSpecCount}대`;
|
||||
document.getElementById('metric-over-spec')!.textContent = `${overSpecCount}대`;
|
||||
document.getElementById('metric-win11-incompatible')!.textContent = `${win11IncompatibleCount}대`;
|
||||
|
||||
// 6. 종합 매트릭스 테이블 렌더링 및 바인딩
|
||||
@@ -358,16 +375,18 @@ 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: 9px 4px; text-align: center; font-weight: 700; cursor: pointer; transition: background 0.2s;`;
|
||||
const cellStyle = `padding: 14px 12px; text-align: center; font-weight: 700; cursor: pointer; transition: background 0.2s; font-size: 1.25rem;`;
|
||||
const hoverEvents = `onmouseover="this.style.background='#F1F5F9'" onmouseout="this.style.background='none'"`;
|
||||
|
||||
const shortage = Math.max(0, data.under - data.stock);
|
||||
|
||||
return `
|
||||
<tr style="border-bottom: 1px solid #F1F5F9;">
|
||||
<td style="padding: 9px 4px; font-weight: 800; color: ${color};">${label}</td>
|
||||
<td style="padding: 14px 12px; font-weight: 800; color: ${color}; font-size: 1.25rem;">${label}</td>
|
||||
<td class="matrix-cell" data-grade="${gradeKey}" data-type="total" style="${cellStyle}" ${hoverEvents}>${data.total}대 <span style="font-size:1.0rem; 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}>${data.under}대</td>
|
||||
<td class="matrix-cell" data-grade="${gradeKey}" data-type="under" style="${cellStyle} color: #EF4444;" ${hoverEvents}>${shortage}대</td>
|
||||
</tr>
|
||||
`;
|
||||
};
|
||||
@@ -375,9 +394,14 @@ function updateDashboardData(pcs: any[], selectedDept: string) {
|
||||
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;
|
||||
|
||||
const cellStyleHeader = `padding: 9px 4px; text-align: center; font-weight: 800; cursor: pointer; transition: background 0.2s; background: #F8FAFC;`;
|
||||
const premiumShortage = Math.max(0, matrix.premium.under - matrix.premium.stock);
|
||||
const highShortage = Math.max(0, matrix.high.under - matrix.high.stock);
|
||||
const normalShortage = Math.max(0, matrix.normal.under - matrix.normal.stock);
|
||||
const entryShortage = Math.max(0, matrix.entry.under - matrix.entry.stock);
|
||||
const totalShortage = premiumShortage + highShortage + normalShortage + entryShortage;
|
||||
|
||||
const cellStyleHeader = `padding: 14px 12px; text-align: center; font-weight: 800; cursor: pointer; transition: background 0.2s; background: #F8FAFC; font-size: 1.25rem;`;
|
||||
const hoverEventsHeader = `onmouseover="this.style.background='#EEF2F6'" onmouseout="this.style.background='#F8FAFC'"`;
|
||||
|
||||
matrixTbody.innerHTML = `
|
||||
@@ -386,11 +410,11 @@ function updateDashboardData(pcs: any[], selectedDept: string) {
|
||||
${renderMatrixRow('normal', '중급 PC (40점 ~ 70점)', '#10B981')}
|
||||
${renderMatrixRow('entry', '보급 PC (40점 미만)', '#64748B')}
|
||||
<tr style="background: #F8FAFC; border-top: 2px solid #E2E8F0; font-weight: 800;">
|
||||
<td style="padding: 9px 4px; color: #1E293B; font-weight: 800;">합계 (Total)</td>
|
||||
<td class="matrix-cell" data-grade="all" data-type="total" style="${cellStyleHeader}" ${hoverEventsHeader}>${totalPcs}대 <span style="font-size:1.0rem; color:#64748B; font-weight:600;">(100%)</span></td>
|
||||
<td style="padding: 14px 12px; color: #1E293B; font-weight: 800; font-size: 1.25rem;">합계 (Total)</td>
|
||||
<td class="matrix-cell" data-grade="all" data-type="total" style="${cellStyleHeader}" ${hoverEventsHeader}>${totalPcs}대 <span style="font-size:1.125rem; color:#64748B; font-weight:600;">(100%)</span></td>
|
||||
<td class="matrix-cell" data-grade="all" data-type="active" style="${cellStyleHeader}" ${hoverEventsHeader}>${totalActive}대</td>
|
||||
<td class="matrix-cell" data-grade="all" data-type="stock" style="${cellStyleHeader}" ${hoverEventsHeader}>${totalStock}대</td>
|
||||
<td class="matrix-cell" data-grade="all" data-type="under" style="${cellStyleHeader} color: #EF4444;" ${hoverEventsHeader}>${totalUnder}대</td>
|
||||
<td class="matrix-cell" data-grade="all" data-type="under" style="${cellStyleHeader} color: #EF4444;" ${hoverEventsHeader}>${totalShortage}대</td>
|
||||
</tr>
|
||||
`;
|
||||
|
||||
@@ -413,8 +437,8 @@ function updateDashboardData(pcs: any[], selectedDept: string) {
|
||||
|
||||
const getTypeLabel = (t: string) => {
|
||||
if (t === 'total') return '보유';
|
||||
if (t === 'active') return '할당 (운영)';
|
||||
if (t === 'stock') return '여분 (재고)';
|
||||
if (t === 'active') return '운영중';
|
||||
if (t === 'stock') return '재고';
|
||||
if (t === 'under') return '부족 (사양 부족)';
|
||||
return '';
|
||||
};
|
||||
@@ -473,9 +497,9 @@ function updateDashboardData(pcs: any[], selectedDept: string) {
|
||||
const renderAgingRow = (label: string, list: any[], badgeText: string, badgeStyle: string, ageGroupKey: string) => {
|
||||
return `
|
||||
<tr style="border-bottom:1px solid #F1F5F9; cursor:pointer; transition: background 0.2s;" class="aging-row" data-group="${ageGroupKey}" onmouseover="this.style.background='#F8FAFC'" onmouseout="this.style.background='none'">
|
||||
<td style="padding:9px 4px 9px 0; font-weight:700; color:#334155;">${label}</td>
|
||||
<td style="padding:9px 4px; text-align:center; font-weight:700; color:#334155;">${list.length}대</td>
|
||||
<td style="padding:9px 4px; text-align:center;">
|
||||
<td style="padding:10px 10px; font-weight:700; color:#334155; font-size: 1.15rem;">${label}</td>
|
||||
<td style="padding:10px 10px; text-align:center; font-weight:700; color:#334155; font-size: 1.15rem;">${list.length}대</td>
|
||||
<td style="padding:10px 10px; text-align:center;">
|
||||
<span style="padding:2px 8px; border-radius:4px; font-size:14px; font-weight:800; ${badgeStyle}">${badgeText}</span>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -520,8 +544,8 @@ function updateDashboardData(pcs: any[], selectedDept: string) {
|
||||
};
|
||||
|
||||
// 사양 부족 / 오버 스펙 / 윈도우 11 불가 클릭 리스너 설정
|
||||
bindCardClick('card-under-spec', '사양 부족 검토 대상', p => p._spec_status === '사양 부족');
|
||||
bindCardClick('card-over-spec', '오버스펙 검토 대상', p => p._spec_status === '오버스펙');
|
||||
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));
|
||||
|
||||
// 9. 직무별 사양 적정성 대수 연산 및 차트 데이터 셋 구성 (누적 막대 그래프화)
|
||||
@@ -758,7 +782,7 @@ function renderChart(labels: string[], underData: number[], normalData: number[]
|
||||
return specStatus === clickedStatus;
|
||||
});
|
||||
|
||||
showMiniListModal(`${clickedJob} - ${clickedStatus === '적정' ? '적정 사양' : clickedStatus} 자산`, matchedPcs);
|
||||
showMiniListModal(`${clickedJob} - ${clickedStatus === '적정' ? '적정 사양' : (clickedStatus === '오버스펙' ? '오버 스펙' : clickedStatus)} 자산`, matchedPcs);
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
@@ -766,10 +790,10 @@ function renderChart(labels: string[], underData: number[], normalData: number[]
|
||||
position: 'top',
|
||||
align: 'end',
|
||||
labels: {
|
||||
font: { family: 'Pretendard', size: 11, weight: '700' },
|
||||
font: { family: 'Pretendard', size: 16, weight: '700' },
|
||||
color: '#475569',
|
||||
boxWidth: 8,
|
||||
boxHeight: 8,
|
||||
boxWidth: 12,
|
||||
boxHeight: 12,
|
||||
usePointStyle: true
|
||||
}
|
||||
},
|
||||
@@ -792,7 +816,7 @@ function renderChart(labels: string[], underData: number[], normalData: number[]
|
||||
stacked: true,
|
||||
ticks: {
|
||||
callback: (val: any) => `${val}대`,
|
||||
font: { family: 'Pretendard', size: 10, weight: '600' },
|
||||
font: { family: 'Pretendard', size: 14, weight: '600' },
|
||||
color: '#64748B'
|
||||
},
|
||||
grid: { color: '#EEF2F6' }
|
||||
@@ -800,7 +824,7 @@ function renderChart(labels: string[], underData: number[], normalData: number[]
|
||||
y: {
|
||||
stacked: true,
|
||||
ticks: {
|
||||
font: { family: 'Pretendard', size: 11, weight: '700' },
|
||||
font: { family: 'Pretendard', size: 16, weight: '700' },
|
||||
color: '#475569'
|
||||
},
|
||||
grid: { display: false }
|
||||
@@ -868,7 +892,7 @@ function renderDonutChart(premium: number, high: number, normal: number, entry:
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -46%);
|
||||
font-size: 1.75rem;
|
||||
font-size: 1.65rem;
|
||||
font-weight: 900;
|
||||
color: #1E5149;
|
||||
font-family: 'Pretendard', sans-serif;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ASSET_SCHEMA, UI_TEXT } from '../../core/schema';
|
||||
import { dynamicSort, renderPageHeader, calculateAssetAge, formatInline } from '../../core/utils';
|
||||
import { dynamicSort, renderPageHeader, calculateAssetAge, formatInline, isWindows11Incompatible } from '../../core/utils';
|
||||
import { setupTableSorting, SortState } from '../../core/tableHandler';
|
||||
import { renderFilterBar, applyCommonFilters } from '../../core/filterHandler';
|
||||
import { state } from '../../core/state';
|
||||
@@ -921,14 +921,31 @@ export function createListView(container: HTMLElement, config: ListViewConfig) {
|
||||
const score = pc['_pc_score'];
|
||||
const avg = jobScores[job].avg;
|
||||
|
||||
const cpu = pc[ASSET_SCHEMA.CPU.key] || '';
|
||||
const ram = pc[ASSET_SCHEMA.RAM.key] || '';
|
||||
const win11Incompatible = isWindows11Incompatible(cpu, ram);
|
||||
|
||||
let isUnder = false;
|
||||
if (avg > 0) {
|
||||
if (score < avg * 0.6) {
|
||||
isUnder = true;
|
||||
pc['_spec_status'] = '사양 부족';
|
||||
criticalPcList.push(pc);
|
||||
} else if (score > avg * 1.5) {
|
||||
} else if (score > avg * 1.5 && !win11Incompatible) {
|
||||
pc['_spec_status'] = '오버스펙';
|
||||
criticalPcList.push(pc);
|
||||
} else if (win11Incompatible) {
|
||||
isUnder = true;
|
||||
pc['_spec_status'] = '사양 부족';
|
||||
}
|
||||
} else {
|
||||
if (win11Incompatible) {
|
||||
isUnder = true;
|
||||
pc['_spec_status'] = '사양 부족';
|
||||
}
|
||||
}
|
||||
|
||||
if (isUnder) {
|
||||
criticalPcList.push(pc);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -960,7 +977,7 @@ export function createListView(container: HTMLElement, config: ListViewConfig) {
|
||||
<td style="padding: 10px 0; font-weight: 600; color: #334155; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" title="${user}">${user}</td>
|
||||
<td style="padding: 10px 0; color: #475569; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" title="${dept} (${job})">${dept} (${job})</td>
|
||||
<td style="padding: 10px 0; white-space: nowrap; text-align: center;">
|
||||
<span style="padding: 2px 6px; border-radius: 4px; font-size: 10px; font-weight: 700; ${badgeColor}">${status}</span>
|
||||
<span style="padding: 2px 6px; border-radius: 4px; font-size: 10px; font-weight: 700; ${badgeColor}">${status === '오버스펙' ? '오버 스펙' : status}</span>
|
||||
</td>
|
||||
<td style="padding: 10px 0; font-family: monospace; color: #64748B; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" title="${assetCode}">${assetCode}</td>
|
||||
</tr>
|
||||
|
||||
@@ -3,16 +3,27 @@ import { openHwModal } from '../../components/Modal/HWModal';
|
||||
import { sortAssets, formatInline, calculatePcScoreDeductive, getPcGrade } from '../../core/utils';
|
||||
import { ASSET_SCHEMA } from '../../core/schema';
|
||||
import { createListView } from './ListFactory';
|
||||
import { SortState } from '../../core/tableHandler';
|
||||
|
||||
let persistentSortState: SortState = { key: 'updated_at', direction: 'desc' };
|
||||
|
||||
export function renderPcList(container: HTMLElement) {
|
||||
createListView(container, {
|
||||
title: 'PC',
|
||||
persistentSortState,
|
||||
dataSource: () => {
|
||||
const list = (state.masterData.pc || []).filter((a: any) => a.asset_type !== '서버PC');
|
||||
list.forEach((a: any) => {
|
||||
a['_pc_score'] = calculatePcScoreDeductive(a[ASSET_SCHEMA.CPU.key], a[ASSET_SCHEMA.RAM.key], a[ASSET_SCHEMA.GPU.key], a.purchase_date);
|
||||
});
|
||||
return sortAssets(list);
|
||||
// 변경일시(updated_at) 내림차순 정렬 (최신 변경 항목이 맨 위로)
|
||||
return list.sort((a: any, b: any) => {
|
||||
const dateA = a.updated_at || a.created_at || '';
|
||||
const dateB = b.updated_at || b.created_at || '';
|
||||
if (dateA < dateB) return 1;
|
||||
if (dateA > dateB) return -1;
|
||||
return 0;
|
||||
});
|
||||
},
|
||||
searchKeys: ['CURRENT_DEPT', 'CURRENT_USER', 'MODEL_NAME', 'MAC_ADDR', 'MANAGER_MAIN', 'ASSET_TYPE'],
|
||||
filterOptions: {
|
||||
|
||||
Reference in New Issue
Block a user