feat: 5등급 PC 분류 체계 도입 (교체 대상 PC 등급 신설)

This commit is contained in:
2026-06-15 13:12:07 +09:00
parent a4b620099c
commit 97cecb8b50
3 changed files with 31 additions and 18 deletions

View File

@@ -182,9 +182,13 @@ export function renderHwDashboard(container: HTMLElement) {
<span>중급</span>
</div>
<div style="display: flex; align-items: center; gap: 4px;">
<span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: #94A3B8;"></span>
<span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: #F59E0B;"></span>
<span>보급</span>
</div>
<div style="display: flex; align-items: center; gap: 4px;">
<span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: #EF4444;"></span>
<span>교체 대상</span>
</div>
</div>
</div>
</div>
@@ -285,7 +289,8 @@ function updateDashboardData(pcs: any[], selectedDept: string) {
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[] }
entry: { total: 0, active: 0, stock: 0, under: 0, pcs: [] as any[], activePcs: [] as any[], stockPcs: [] as any[], underPcs: [] as any[] },
replace: { total: 0, active: 0, stock: 0, under: 0, pcs: [] as any[], activePcs: [] as any[], stockPcs: [] as any[], underPcs: [] as any[] }
};
let scoreSum = 0;
@@ -298,6 +303,7 @@ function updateDashboardData(pcs: any[], selectedDept: string) {
const score = p._pc_score;
scoreSum += score;
const stockYn = isStock(p);
const win11Incompatible = isWindows11Incompatible(p.cpu, p.ram);
let target: typeof matrix.premium;
if (score >= 85) {
@@ -306,8 +312,10 @@ function updateDashboardData(pcs: any[], selectedDept: string) {
target = matrix.high;
} else if (score >= 40) {
target = matrix.normal;
} else {
} else if (score >= 20 && !win11Incompatible) {
target = matrix.entry;
} else {
target = matrix.replace;
}
target.pcs.push(p);
@@ -324,7 +332,6 @@ 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') {
@@ -392,14 +399,15 @@ 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 totalActive = matrix.premium.active + matrix.high.active + matrix.normal.active + matrix.entry.active + matrix.replace.active;
const totalStock = matrix.premium.stock + matrix.high.stock + matrix.normal.stock + matrix.entry.stock + matrix.replace.stock;
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 replaceShortage = Math.max(0, matrix.replace.under - matrix.replace.stock);
const totalShortage = premiumShortage + highShortage + normalShortage + entryShortage + replaceShortage;
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'"`;
@@ -408,7 +416,8 @@ function updateDashboardData(pcs: any[], selectedDept: string) {
${renderMatrixRow('premium', '최상급 PC (85점 이상)', '#11302B')}
${renderMatrixRow('high', '상급 PC (70점 ~ 85점)', '#1E8E7C')}
${renderMatrixRow('normal', '중급 PC (40점 ~ 70점)', '#10B981')}
${renderMatrixRow('entry', '보급 PC (40점 미만)', '#64748B')}
${renderMatrixRow('entry', '보급 PC (20점 ~ 40점 & Win11 가능)', '#F59E0B')}
${renderMatrixRow('replace', '교체 대상 PC (20점 미만 또는 Win11 불가)', '#EF4444')}
<tr style="background: #F8FAFC; border-top: 2px solid #E2E8F0; font-weight: 800;">
<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>
@@ -432,6 +441,7 @@ function updateDashboardData(pcs: any[], selectedDept: string) {
if (g === 'high') return '상급 PC';
if (g === 'normal') return '중급 PC';
if (g === 'entry') return '보급 PC';
if (g === 'replace') return '교체 대상 PC';
return '전체 PC';
};
@@ -588,7 +598,7 @@ function updateDashboardData(pcs: any[], selectedDept: string) {
// 10. 차트들 렌더링 호출
renderChart(activeJobs, underData, normalData, overData, filtered);
renderDonutChart(matrix.premium.total, matrix.high.total, matrix.normal.total, matrix.entry.total);
renderDonutChart(matrix.premium.total, matrix.high.total, matrix.normal.total, matrix.entry.total, matrix.replace.total);
// 전역 상태 등록
state.activeCharts = [jobChartInstance, donutChartInstance];
@@ -837,7 +847,7 @@ function renderChart(labels: string[], underData: number[], normalData: number[]
/**
* 실시간 사양 적정률 원형 도넛 그래프 (Active Spec Rate)
*/
function renderDonutChart(premium: number, high: number, normal: number, entry: number) {
function renderDonutChart(premium: number, high: number, normal: number, entry: number, replace: number) {
const ctx = document.getElementById('chart-overall-donut') as HTMLCanvasElement;
if (!ctx || typeof Chart === 'undefined') return;
@@ -846,19 +856,20 @@ function renderDonutChart(premium: number, high: number, normal: number, entry:
donutChartInstance = null;
}
const total = premium + high + normal + entry;
const total = premium + high + normal + entry + replace;
donutChartInstance = new Chart(ctx, {
type: 'doughnut',
data: {
labels: ['최상급', '상급', '중급', '보급'],
labels: ['최상급', '상급', '중급', '보급', '교체 대상'],
datasets: [{
data: [premium, high, normal, entry],
data: [premium, high, normal, entry, replace],
backgroundColor: [
'#11302B', // premium (Hanmac Dark Green)
'#1E8E7C', // high (Hanmac Teal)
'#10B981', // normal (Hanmac Mint)
'#94A3B8' // entry (Slate Gray)
'#F59E0B', // entry (Yellow-Orange)
'#EF4444' // replace (Red)
],
borderColor: '#ffffff',
borderWidth: 2

View File

@@ -1,6 +1,6 @@
import { state } from '../../core/state';
import { openHwModal } from '../../components/Modal/HWModal';
import { sortAssets, formatInline, calculatePcScoreDeductive, getPcGrade } from '../../core/utils';
import { sortAssets, formatInline, calculatePcScoreDeductive, getPcGrade, isWindows11Incompatible } from '../../core/utils';
import { ASSET_SCHEMA } from '../../core/schema';
import { createListView } from './ListFactory';
import { SortState } from '../../core/tableHandler';
@@ -104,7 +104,8 @@ export function renderPcList(container: HTMLElement) {
width: '8%',
render: a => {
const score = a._pc_score !== undefined ? a._pc_score : calculatePcScoreDeductive(a[ASSET_SCHEMA.CPU.key], a[ASSET_SCHEMA.RAM.key], a[ASSET_SCHEMA.GPU.key], a.purchase_date);
const grade = getPcGrade(score);
const isWin11Incompatible = isWindows11Incompatible(a[ASSET_SCHEMA.CPU.key], a[ASSET_SCHEMA.RAM.key]);
const grade = getPcGrade(score, isWin11Incompatible);
return `<span class="badge ${grade.class}" title="성능 점수: ${score}점">${grade.name}</span>`;
}
}