feat: 5등급 PC 분류 체계 도입 (교체 대상 PC 등급 신설)
This commit is contained in:
@@ -288,11 +288,12 @@ export function calculatePcScoreDeductive(cpu: string, ram: string, gpu: string,
|
||||
/**
|
||||
* 성능 점수 기준 등급 뱃지 메타 정보 가져오기
|
||||
*/
|
||||
export function getPcGrade(score: number): { name: string; class: string; color: string } {
|
||||
export function getPcGrade(score: number, isWin11Incompatible?: boolean): { name: string; class: string; color: string } {
|
||||
if (score >= 85) return { name: '최상급', class: 'b-purple', color: '#7C3AED' };
|
||||
if (score >= 70) return { name: '상급', class: 'b-primary', color: '#4F46E5' };
|
||||
if (score >= 40) return { name: '중급', class: 'b-green', color: '#10B981' };
|
||||
return { name: '보급', class: 'b-yellow', color: '#F59E0B' };
|
||||
if (score >= 20 && !isWin11Incompatible) return { name: '보급', class: 'b-yellow', color: '#F59E0B' };
|
||||
return { name: '교체 대상', class: 'badge-danger', color: '#EF4444' };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>`;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user