import { state } from '../../core/state'; import { SoftwareAsset } from '../../core/excelHandler'; import { openSwDashboardDetail, openSwUsageDetail, openCloudDashboardDetail } from '../../components/Modal/DashboardDetailModal'; import { normalizeDate } from '../../core/utils'; declare var Chart: any; export function renderSwDashboard(container: HTMLElement) { let subQty = 0, subUsed = 0, subExp = 0, subTotal = 0; let permQty = 0, permUsed = 0, permExp = 0, permTotal = 0; let subCost2026 = 0; let permCost2026 = 0; const currentYear = new Date().getFullYear(); const corps = ['한맥', '삼안', '바론']; const categories = ['업무공통', '개발S/W', '디자인', '설계S/W']; const costByCorp: Record = { '한맥': 0, '삼안': 0, '바론': 0 }; const costByCat: Record = {}; categories.forEach(c => costByCat[c] = 0); // 통합 SW 데이터 (클라우드 제외) const allSw = [...state.masterData.subSw, ...state.masterData.permSw]; allSw.forEach(sw => { const assigned = state.masterData.swUsers.filter(u => u.sw_id === sw.id).length; const qty = typeof sw.수량 === 'number' ? sw.수량 : parseInt(sw.수량||'0', 10); const priceStr = sw.금액 ? String(sw.금액).replace(/,/g, '') : '0'; const price = parseInt(priceStr, 10) || 0; if (sw.type === '구독SW') { subQty += qty; subUsed += assigned; subTotal++; if (isSWExpiring(sw)) subExp++; } else if (sw.type === '영구SW') { permQty += qty; permUsed += assigned; permTotal++; if (isSWExpiring(sw)) permExp++; } // 초기 도입 비용 (2026년 구매건) if (sw.구매일 && sw.구매일.startsWith('2026')) { if (sw.type === '구독SW') subCost2026 += price; else if (sw.type === '영구SW') permCost2026 += price; if (costByCorp[sw.법인] !== undefined) costByCorp[sw.법인] += price; if (sw.분야 && costByCat[sw.분야] !== undefined) costByCat[sw.분야] += price; } }); // 누적 추가 비용 집계 (2026년 계약 업데이트 로그 기반) if (state.masterData.logs) { state.masterData.logs.forEach(log => { if (log.date && log.date.startsWith('2026') && log.cost) { const asset = allSw.find(a => a.id === log.assetId); if (asset) { const cost = Number(log.cost) || 0; if (asset.type === '구독SW') subCost2026 += cost; else if (asset.type === '영구SW') permCost2026 += cost; if (costByCorp[asset.법인] !== undefined) costByCorp[asset.법인] += cost; if (asset.분야 && costByCat[asset.분야] !== undefined) costByCat[asset.분야] += cost; } } }); } const subPer = subQty > 0 ? Math.round((subUsed/subQty)*100) : 0; const permPer = permQty > 0 ? Math.round((permUsed/permQty)*100) : 0; const subExpPer = subTotal > 0 ? Math.round((subExp/subTotal)*100) : 0; const permExpPer = permTotal > 0 ? Math.round((permExp/permTotal)*100) : 0; container.innerHTML = `

소프트웨어 라이선스 현황

구독 소프트웨어 사용율
${subQty}카피 중 ${subUsed}개 할당
${subPer}%
영구 소프트웨어 사용율
${permQty}카피 중 ${permUsed}개 할당
${permPer}%
구독 SW 만료 예정
(30일 이내)
${subExp}개 제품
${subExpPer}%
유지보수 만료 예정
(30일 이내)
${permExp}개 제품
${permExpPer}%

2026년 누적 도입 비용 분석

구독 SW 누적 비용 (2026)
갱신 및 추가 비용 합계
₩ ${subCost2026.toLocaleString()}
영구 SW 누적 비용 (2026)
유지보수 및 신규 도입 합계
₩ ${permCost2026.toLocaleString()}
`; container.querySelector('[data-action="sub-usage"]')?.addEventListener('click', () => openSwUsageDetail('구독 소프트웨어 사용 목록', state.masterData.subSw)); container.querySelector('[data-action="perm-usage"]')?.addEventListener('click', () => openSwUsageDetail('영구 소프트웨어 사용 목록', state.masterData.permSw)); container.querySelector('[data-action="sub-exp"]')?.addEventListener('click', () => openSwDashboardDetail('구독 SW 만료 예정 목록', state.masterData.subSw.filter(sw => isSWExpiring(sw)))); container.querySelector('[data-action="perm-exp"]')?.addEventListener('click', () => openSwDashboardDetail('유지보수 만료 예정 목록', state.masterData.permSw.filter(sw => isSWExpiring(sw)))); } function isSWExpiring(sw: SoftwareAsset) { if (sw.type === '구독SW' && sw.만료일) { const endMs = new Date(normalizeDate(sw.만료일)).getTime(); const diffDays = (endMs - Date.now()) / (1000 * 60 * 60 * 24); return diffDays >= 0 && diffDays <= 30; } else if (sw.type === '영구SW' && sw.비고 && sw.비고.includes('유지보수: ~')) { try { const parts = sw.비고.split('~'); if (parts.length > 1) { const endMs = new Date(normalizeDate(parts[1].trim())).getTime(); const diffDays = (endMs - Date.now()) / (1000 * 60 * 60 * 24); return diffDays >= 0 && diffDays <= 30; } } catch { return false; } } return false; }