merge: 공동작업자 HW_Dashboard 브랜치 병합 (대시보드 UI 및 가독성 개선 사항 병합)
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -153,6 +153,7 @@ export interface ListViewConfig {
|
||||
showField?: boolean;
|
||||
showType?: boolean;
|
||||
showStatus?: boolean;
|
||||
showPosition?: boolean;
|
||||
};
|
||||
columns: ColumnDef[];
|
||||
onRowClick?: (asset: any) => void;
|
||||
@@ -450,35 +451,63 @@ export function createListView(container: HTMLElement, config: ListViewConfig) {
|
||||
});
|
||||
|
||||
// DB 기준 사양 데이터 맵핑 (state.masterData.jobSpecs 이용)
|
||||
const jobSpecsMap: Record<string, number> = {};
|
||||
const jobSpecsMap: Record<string, string> = {};
|
||||
if (state.masterData.jobSpecs) {
|
||||
state.masterData.jobSpecs.forEach((s: any) => {
|
||||
jobSpecsMap[s.job_name] = s.min_score;
|
||||
jobSpecsMap[s.job_name] = s.required_grade || '중급';
|
||||
});
|
||||
}
|
||||
|
||||
// 사용자 이름 → 세부 직무 맵 생성 (system_users.position 기준)
|
||||
const userPositionMap: Record<string, string> = {};
|
||||
if (state.masterData.users) {
|
||||
state.masterData.users.forEach((u: any) => {
|
||||
if (u.user_name && u.position) {
|
||||
userPositionMap[u.user_name.trim()] = u.position.trim();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const GRADE_RANK: Record<string, number> = {
|
||||
'premium': 4, '최상급': 4,
|
||||
'high': 3, '상급': 3,
|
||||
'normal': 2, '중급': 2,
|
||||
'entry': 1, '보급': 1,
|
||||
'replace': 0, '교체 대상': 0
|
||||
};
|
||||
|
||||
// 기준 대비 사양 부족/오버스펙 분류
|
||||
const criticalPcList: any[] = [];
|
||||
pcs.forEach((pc: any) => {
|
||||
const job = pc[ASSET_SCHEMA.USER_POSITION.key] || '미분류';
|
||||
const userName = (pc[ASSET_SCHEMA.CURRENT_USER.key] || '').trim();
|
||||
const job = userPositionMap[userName] || pc[ASSET_SCHEMA.USER_POSITION.key] || '미분류';
|
||||
const score = pc['_pc_score'];
|
||||
const standardScore = jobSpecsMap[job] !== undefined ? jobSpecsMap[job] : (jobScores[job]?.avg || 0);
|
||||
const requiredGrade = jobSpecsMap[job] || jobSpecsMap[pc[ASSET_SCHEMA.USER_POSITION.key]] || '중급';
|
||||
|
||||
const cpu = pc[ASSET_SCHEMA.CPU.key] || '';
|
||||
const ram = pc[ASSET_SCHEMA.RAM.key] || '';
|
||||
const win11Incompatible = isWindows11Incompatible(cpu, ram);
|
||||
|
||||
let actualGrade = 'replace';
|
||||
if (score >= 85) actualGrade = 'premium';
|
||||
else if (score >= 70) actualGrade = 'high';
|
||||
else if (score >= 40) actualGrade = 'normal';
|
||||
else if (score >= 20) actualGrade = 'entry';
|
||||
|
||||
const reqRank = GRADE_RANK[requiredGrade] !== undefined ? GRADE_RANK[requiredGrade] : 2;
|
||||
const actRank = GRADE_RANK[actualGrade] !== undefined ? GRADE_RANK[actualGrade] : 0;
|
||||
|
||||
let isUnder = false;
|
||||
if (standardScore > 0) {
|
||||
if (score < standardScore * 0.6) {
|
||||
if (job !== '재고PC') {
|
||||
if (win11Incompatible) {
|
||||
isUnder = true;
|
||||
pc['_spec_status'] = '사양 부족';
|
||||
} else if (score > standardScore * 1.5 && !win11Incompatible) {
|
||||
} else if (actRank < reqRank) {
|
||||
isUnder = true;
|
||||
pc['_spec_status'] = '사양 부족';
|
||||
} else if (actRank > reqRank) {
|
||||
pc['_spec_status'] = '오버스펙';
|
||||
criticalPcList.push(pc);
|
||||
} else if (win11Incompatible) {
|
||||
isUnder = true;
|
||||
pc['_spec_status'] = '사양 부족';
|
||||
} else {
|
||||
pc['_spec_status'] = '적정';
|
||||
}
|
||||
@@ -496,16 +525,38 @@ export function createListView(container: HTMLElement, config: ListViewConfig) {
|
||||
}
|
||||
});
|
||||
|
||||
// 정렬: 기준 점수 대비 사양 부족이 심한 순(비율이 낮은 순)으로 정렬
|
||||
// 정렬: 요구 등급 대비 실제 성능이 많이 부족한 순(등급 편차가 큰 순)으로 정렬
|
||||
criticalPcList.sort((a: any, b: any) => {
|
||||
const jobA = a[ASSET_SCHEMA.USER_POSITION.key] || '미분류';
|
||||
const jobB = b[ASSET_SCHEMA.USER_POSITION.key] || '미분류';
|
||||
const stdA = jobSpecsMap[jobA] !== undefined ? jobSpecsMap[jobA] : (jobScores[jobA]?.avg || 0);
|
||||
const stdB = jobSpecsMap[jobB] !== undefined ? jobSpecsMap[jobB] : (jobScores[jobB]?.avg || 0);
|
||||
const userNameA = (a[ASSET_SCHEMA.CURRENT_USER.key] || '').trim();
|
||||
const userNameB = (b[ASSET_SCHEMA.CURRENT_USER.key] || '').trim();
|
||||
const jobA = userPositionMap[userNameA] || a[ASSET_SCHEMA.USER_POSITION.key] || '미분류';
|
||||
const jobB = userPositionMap[userNameB] || b[ASSET_SCHEMA.USER_POSITION.key] || '미분류';
|
||||
|
||||
const ratioA = stdA > 0 ? a['_pc_score'] / stdA : 1;
|
||||
const ratioB = stdB > 0 ? b['_pc_score'] / stdB : 1;
|
||||
return ratioA - ratioB;
|
||||
const reqA = jobSpecsMap[jobA] || jobSpecsMap[a[ASSET_SCHEMA.USER_POSITION.key]] || '중급';
|
||||
const reqB = jobSpecsMap[jobB] || jobSpecsMap[b[ASSET_SCHEMA.USER_POSITION.key]] || '중급';
|
||||
|
||||
const scoreA = a['_pc_score'];
|
||||
const scoreB = b['_pc_score'];
|
||||
|
||||
let actA = 'replace';
|
||||
if (scoreA >= 85) actA = 'premium';
|
||||
else if (scoreA >= 70) actA = 'high';
|
||||
else if (scoreA >= 40) actA = 'normal';
|
||||
else if (scoreA >= 20) actA = 'entry';
|
||||
|
||||
let actB = 'replace';
|
||||
if (scoreB >= 85) actB = 'premium';
|
||||
else if (scoreB >= 70) actB = 'high';
|
||||
else if (scoreB >= 40) actB = 'normal';
|
||||
else if (scoreB >= 20) actB = 'entry';
|
||||
|
||||
const devA = (GRADE_RANK[reqA] || 2) - (GRADE_RANK[actA] || 0);
|
||||
const devB = (GRADE_RANK[reqB] || 2) - (GRADE_RANK[actB] || 0);
|
||||
|
||||
if (devA !== devB) {
|
||||
return devB - devA; // 편차가 큰 것(더 많이 부족한 것)이 먼저 정렬됨
|
||||
}
|
||||
return scoreA - scoreB; // 편차가 같으면 성능 점수가 낮은 순
|
||||
});
|
||||
|
||||
if (criticalPcList.length === 0) {
|
||||
@@ -709,7 +760,6 @@ export function createListView(container: HTMLElement, config: ListViewConfig) {
|
||||
<td class="text-center">${asset[ASSET_SCHEMA.LOC_DETAIL.key] || '-'}</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
|
||||
tbody.querySelectorAll('.mini-row').forEach(row => {
|
||||
row.addEventListener('click', () => {
|
||||
tbody.querySelectorAll('.mini-row').forEach(r => r.classList.remove('active'));
|
||||
@@ -806,7 +856,7 @@ export function createListView(container: HTMLElement, config: ListViewConfig) {
|
||||
</button>
|
||||
` : ''}
|
||||
<button id="btn-add-asset" class="btn btn-primary">
|
||||
<i data-lucide="plus" class="icon-sm"></i> 자산 추가
|
||||
<i data-lucide="plus" class="icon-sm"></i> ${config.title === '직무별 기준 사양' ? '기준 사양 추가' : (config.title === '부품 마스터' ? '표준 부품 추가' : '자산 추가')}
|
||||
</button>
|
||||
`;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user