import { PAGE_DESCRIPTIONS } from './schema'; export const API_BASE_URL = ''; /** * ITAM 공통 유틸리티 함수 */ /** * 페이지 헤더(타이틀 및 설명) 렌더링 */ export function renderPageHeader(container: HTMLElement, pageId: string) { const config = PAGE_DESCRIPTIONS[pageId]; if (!config) return; const header = document.createElement('div'); header.className = 'page-header'; header.innerHTML = `

${config.title}

${config.description}

`; container.appendChild(header); } /** * 숫자에 천 단위 콤마 추가 (금액 표시용) */ export function formatPrice(value: string | number): string { if (value === undefined || value === null) return ''; const num = String(value).replace(/[^0-9]/g, ''); if (!num) return ''; return num.replace(/\B(?=(\d{3})+(?!\d))/g, ','); } /** * HTML 배지 생성 (정/부 담당자, 원격도구 등) */ export function createBadge(text: string, type: 'primary' | 'muted' | 'success' | 'danger' = 'primary'): string { return `${text}`; } /** * 텍스트 내 줄바꿈을 구분자(/)로 변경하여 한 줄로 표시 */ export function formatInline(value: any): string { return String(value || '').replace(/\n/g, ' / ').trim(); } /** * 날짜 문자열 포맷팅 (YYYY.MM.DD -> YYYY-MM-DD) */ export function normalizeDate(dateStr: string): string { if (!dateStr) return ''; let str = String(dateStr).replace(/\./g, '-').trim(); // YYYYMM 형식 처리 (6자리 숫자) if (/^\d{6}$/.test(str)) { return `${str.substring(0, 4)}-${str.substring(4, 6)}`; } return str; } /** * 구매일로부터 현재까지의 경과 연수 계산 (소수점 첫째자리) */ export function calculateAssetAge(purchaseDate: string): number { const normalized = normalizeDate(purchaseDate); if (!normalized) return 0; const purchase = new Date(normalized); if (isNaN(purchase.getTime())) return 0; const diffMs = Date.now() - purchase.getTime(); const age = diffMs / (1000 * 60 * 60 * 24 * 365.25); return Math.max(0, parseFloat(age.toFixed(1))); } /** * 고유 ID 생성 (7자리 랜덤 문자열) */ export function generateId(): string { return Math.random().toString(36).substring(2, 9); } /** * 두 자산 객체 간의 변경 사항 감지 */ export function getAssetChanges(oldAsset: any, newAsset: any, fields: {key: string, label: string}[]): string { const changes: string[] = []; fields.forEach(field => { const oldVal = String(oldAsset[field.key] || '').trim(); const newVal = String(newAsset[field.key] || '').trim(); if (oldVal !== newVal) { changes.push(`${field.label}: ${oldVal || '없음'} → ${newVal || '없음'}`); } }); return changes.join('\n'); } /** * 자산 목록 정렬 (기본: 법인별 -> 자산번호 순) */ export function sortAssets(list: T[]): T[] { return [...list].sort((a: any, b: any) => { // 1순위: 법인 (가나다순) const corpA = String(a.법인 || a.corp || '').trim(); const corpB = String(b.법인 || b.corp || '').trim(); if (corpA < corpB) return -1; if (corpA > corpB) return 1; // 2순위: 자산번호/코드 (영문/숫자순) const codeA = String(a.자산코드 || a.자산번호 || a.id || '').trim(); const codeB = String(b.자산코드 || b.자산번호 || b.id || '').trim(); if (codeA < codeB) return -1; if (codeA > codeB) return 1; return 0; }); } /** * 동적 정렬 함수 * @param list 정렬할 목록 * @param key 정렬 기준 필드 * @param direction 정렬 방향 ('asc' | 'desc') */ export function dynamicSort(list: T[], key: string, direction: 'asc' | 'desc'): T[] { return [...list].sort((a: any, b: any) => { let valA = a[key]; let valB = b[key]; // 숫자인 경우 처리 if (typeof valA === 'number' && typeof valB === 'number') { return direction === 'asc' ? valA - valB : valB - valA; } // 금액 필드 (숫자형 문자열 포함) 처리 if (key === '금액' || key === 'price' || key === '수량' || key === 'qty') { const numA = typeof valA === 'number' ? valA : parseInt(String(valA || '0').replace(/[^0-9-]/g, ''), 10); const numB = typeof valB === 'number' ? valB : parseInt(String(valB || '0').replace(/[^0-9-]/g, ''), 10); return direction === 'asc' ? numA - numB : numB - numA; } // 문자열 정렬 (기본) valA = String(valA || '').toLowerCase(); valB = String(valB || '').toLowerCase(); if (valA < valB) return direction === 'asc' ? -1 : 1; if (valA > valB) return direction === 'asc' ? 1 : -1; return 0; }); } /** * 목록 뷰용 액션 버튼 HTML 생성 (중복 제거를 위해 비워둠) */ export function getActionButtonsHTML(): string { return ''; } /** * 100점 만점 감점형 PC 성능 점수 계산 (CPU + RAM + GPU + 연식) */ export function calculatePcScoreDeductive(cpu: string, ram: string, gpu: string, purchaseDate: string): number { let score = 100; if (!cpu) cpu = ''; if (!ram) ram = ''; if (!gpu) gpu = ''; const cpuUpper = cpu.toUpperCase(); const ramUpper = ram.toUpperCase(); const gpuUpper = gpu.toUpperCase(); // 1. CPU 등급 감점 (최대 -30점) let cpuDeduction = 0; if (cpuUpper.includes('I9') || cpuUpper.includes('RYZEN 9') || cpuUpper.includes('RYZEN9')) { cpuDeduction = 0; } else if (cpuUpper.includes('I7') || cpuUpper.includes('RYZEN 7') || cpuUpper.includes('RYZEN7')) { cpuDeduction = 5; } else if (cpuUpper.includes('I5') || cpuUpper.includes('RYZEN 5') || cpuUpper.includes('RYZEN5')) { cpuDeduction = 15; } else if (cpuUpper.includes('I3') || cpuUpper.includes('RYZEN 3') || cpuUpper.includes('RYZEN3')) { cpuDeduction = 25; } else { cpuDeduction = 30; } score -= cpuDeduction; // 2. CPU 세대 노후 감점 (최대 -15점) let genDeduction = 0; const intelMatch = cpuUpper.match(/I\d-?(\d+)/); let gen = 0; if (intelMatch && intelMatch[1]) { const numStr = intelMatch[1]; if (numStr.length === 5) gen = parseInt(numStr.substring(0, 2), 10); else if (numStr.length === 4) gen = parseInt(numStr.substring(0, 1), 10); } const amdMatch = cpuUpper.match(/RYZEN\s?\d\s?-?(\d+)/); let amdGen = 0; if (amdMatch && amdMatch[1] && !intelMatch) { const numStr = amdMatch[1]; if (numStr.length === 4) amdGen = parseInt(numStr.substring(0, 1), 10); } if (intelMatch) { if (gen >= 12) genDeduction = 0; else if (gen >= 10) genDeduction = 5; else if (gen >= 8) genDeduction = 10; else genDeduction = 15; } else if (amdMatch) { if (amdGen >= 5) genDeduction = 0; else if (amdGen >= 3) genDeduction = 5; else genDeduction = 10; } else { genDeduction = 15; } score -= genDeduction; // 3. RAM 용량 감점 (최대 -25점) const ramMatch = ramUpper.match(/(\d+)\s*GB/); let ramDeduction = 25; if (ramMatch && ramMatch[1]) { const ramVal = parseInt(ramMatch[1], 10); if (ramVal >= 32) ramDeduction = 0; else if (ramVal >= 16) ramDeduction = 10; else if (ramVal >= 8) ramDeduction = 20; else ramDeduction = 25; } score -= ramDeduction; // 4. GPU 성능 감점 (최대 -25점) let gpuDeduction = 25; if (!gpuUpper || gpuUpper === '-' || gpuUpper.trim() === '') { gpuDeduction = 25; } else if ( gpuUpper.includes('RTX 4090') || gpuUpper.includes('RTX 4080') || gpuUpper.includes('RTX 4070') || gpuUpper.includes('RTX 3090') || gpuUpper.includes('RTX 3080') || gpuUpper.includes('RTX A5000') || gpuUpper.includes('RTX A6000') || gpuUpper.includes('RTX A4000') ) { gpuDeduction = 0; } else if ( gpuUpper.includes('RTX 3070') || gpuUpper.includes('RTX 3060') || gpuUpper.includes('RTX 2060') || gpuUpper.includes('RTX A2000') || gpuUpper.includes('RTX A3000') || gpuUpper.includes('QUADRO') ) { gpuDeduction = 5; } else if ( gpuUpper.includes('GTX 1660') || gpuUpper.includes('GTX 1080') || gpuUpper.includes('GTX 1070') || gpuUpper.includes('GTX 1060') || gpuUpper.includes('RX 6700') || gpuUpper.includes('RX 6600') ) { gpuDeduction = 15; } else { gpuDeduction = 25; } score -= gpuDeduction; // 5. 연식(노후도) 감점 (최대 -15점) let age = 0; if (purchaseDate && purchaseDate !== '-') { let normalized = purchaseDate.replace(/\./g, '-').trim(); if (/^\d{6}$/.test(normalized)) { normalized = `${normalized.substring(0, 4)}-${normalized.substring(4, 6)}`; } const purchase = new Date(normalized); if (!isNaN(purchase.getTime())) { // 2026년 5월 31일 기준 경과연수 계산 const mockToday = new Date('2026-05-31'); const diffMs = mockToday.getTime() - purchase.getTime(); age = diffMs / (1000 * 60 * 60 * 24 * 365.25); age = Math.max(0, parseFloat(age.toFixed(1))); } } let ageDeduction = 0; if (age < 1) ageDeduction = 0; else if (age < 2) ageDeduction = 3; else if (age < 3) ageDeduction = 6; else if (age < 4) ageDeduction = 9; else if (age < 5) ageDeduction = 12; else ageDeduction = 15; score -= ageDeduction; return Math.max(10, score); } /** * 성능 점수 기준 등급 뱃지 메타 정보 가져오기 */ 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' }; if (score >= 20 && !isWin11Incompatible) return { name: '보급', class: 'b-yellow', color: '#F59E0B' }; return { name: '교체 대상', class: 'badge-danger', color: '#EF4444' }; } /** * Windows 11 업그레이드 지원 불가능한 하드웨어 조건인지 판별 */ export function isWindows11Incompatible(cpu: string, ram: string): boolean { if (!cpu) return true; const cpuUpper = cpu.toUpperCase(); // 1. RAM 4GB 미만은 공식 미지원 if (ram) { const ramMatch = ram.toUpperCase().match(/(\d+)\s*GB/); if (ramMatch && ramMatch[1]) { const ramVal = parseInt(ramMatch[1], 10); if (ramVal < 4) return true; } } // 2. CPU 세대 검사 // Intel CPU 세대 판정 const intelMatch = cpuUpper.match(/I\d-?(\d+)/); if (intelMatch && intelMatch[1]) { const numStr = intelMatch[1]; let gen = 0; if (numStr.length === 5) gen = parseInt(numStr.substring(0, 2), 10); else if (numStr.length === 4) gen = parseInt(numStr.substring(0, 1), 10); else if (numStr.length === 3) gen = parseInt(numStr.substring(0, 1), 10); // 3자리수 구형 세대 (예: i5-750) if (gen > 0 && gen < 8) return true; // 8세대 미만 불가 return false; } // AMD Ryzen CPU 세대 판정 const amdMatch = cpuUpper.match(/RYZEN\s?\d\s?-?(\d+)/); if (amdMatch && amdMatch[1]) { const numStr = amdMatch[1]; let amdGen = 0; if (numStr.length === 4) amdGen = parseInt(numStr.substring(0, 1), 10); // 1xxx, 2xxx 등 if (amdGen > 0 && amdGen < 2) return true; // Ryzen 1세대 이하는 불가 return false; } // Apple Silicon은 지원 if (cpuUpper.includes('APPLE') || cpuUpper.includes('M1') || cpuUpper.includes('M2') || cpuUpper.includes('M3') || cpuUpper.includes('M4')) { return false; } // 그 외 확실한 구형 CPU 제품군 const knownOldCpus = ['CORE2', 'CORE 2', 'PENTIUM', 'CELERON', 'ATHLON', 'PHENOM', 'XEON']; if (knownOldCpus.some(name => cpuUpper.includes(name))) { return true; } // 세대 매칭은 안되었으나 Intel Core i 시리즈 구조이면 구형(1세대 등)으로 간주 if (cpuUpper.includes('I3') || cpuUpper.includes('I5') || cpuUpper.includes('I7') || cpuUpper.includes('I9')) { // i5-620M 처럼 옛날 구형 모바일 칩 등 return true; } return false; }