84 lines
4.7 KiB
TypeScript
84 lines
4.7 KiB
TypeScript
import { state } from '../../core/state';
|
|
import { openSwDashboardDetail, openSwUsageDetail } from '../../components/Modal/DashboardDetailModal';
|
|
import { normalizeDate } from '../../core/utils';
|
|
import { ASSET_SCHEMA } from '../../core/schema';
|
|
|
|
export function renderSwDashboard(container: HTMLElement) {
|
|
let extQty = 0, extUsed = 0, extExp = 0, extTotal = 0;
|
|
let intQty = 0, intUsed = 0, intExp = 0, intTotal = 0;
|
|
|
|
let extCost2026 = 0;
|
|
let intCost2026 = 0;
|
|
|
|
// 통합 SW 데이터
|
|
const allSw = [...state.masterData.swExternal, ...state.masterData.swInternal];
|
|
|
|
allSw.forEach(sw => {
|
|
const assigned = state.masterData.swUsers.filter(u => u.sw_id === sw.id).length;
|
|
const qty = typeof sw[ASSET_SCHEMA.ASSET_COUNT.key] === 'number' ? sw[ASSET_SCHEMA.ASSET_COUNT.key] : parseInt(sw[ASSET_SCHEMA.ASSET_COUNT.key]||'0', 10);
|
|
const priceStr = sw[ASSET_SCHEMA.PURCHASE_AMOUNT.key] ? String(sw[ASSET_SCHEMA.PURCHASE_AMOUNT.key]).replace(/,/g, '') : '0';
|
|
const price = parseInt(priceStr, 10) || 0;
|
|
|
|
if (sw.asset_type === '외부SW' || sw.type === '외부SW') {
|
|
extQty += qty; extUsed += assigned; extTotal++;
|
|
if (isSWExpiring(sw)) extExp++;
|
|
if (sw[ASSET_SCHEMA.PURCHASE_DATE.key]?.startsWith('2026')) extCost2026 += price;
|
|
} else {
|
|
intQty += qty; intUsed += assigned; intTotal++;
|
|
if (sw[ASSET_SCHEMA.PURCHASE_DATE.key]?.startsWith('2026')) intCost2026 += price;
|
|
}
|
|
});
|
|
|
|
const extPer = extQty > 0 ? Math.round((extUsed/extQty)*100) : 0;
|
|
const intPer = intQty > 0 ? Math.round((intUsed/intQty)*100) : 0;
|
|
|
|
container.innerHTML = `
|
|
<div class="view-container">
|
|
<h3 class="dashboard-section-title">소프트웨어 라이선스 현황</h3>
|
|
|
|
<div class="dashboard-layout-2col" style="margin-bottom: 1.5rem;">
|
|
<div class="dashboard-card" data-action="ext-usage" style="cursor:pointer; min-height:auto;">
|
|
<span style="font-size:1.21rem; font-weight:700; color:var(--text-main);">외부 소프트웨어 사용율</span>
|
|
<div style="font-size: 1.02rem; color:var(--text-muted); margin-bottom: 1rem;">${extQty}카피 중 ${extUsed}개 할당</div>
|
|
<div style="font-size: 2.21rem; font-weight:700; color:var(--dash-primary);">${extPer}%</div>
|
|
<div style="width: 100%; height: 4px; background-color: var(--border-color); border-radius: 2px; overflow: hidden; margin-top: 0.5rem;">
|
|
<div style="width: ${extPer}%; height: 100%; background-color: var(--dash-primary);"></div>
|
|
</div>
|
|
</div>
|
|
<div class="dashboard-card" data-action="int-usage" style="cursor:pointer; min-height:auto;">
|
|
<span style="font-size:1.21rem; font-weight:700; color:var(--text-main);">내부 소프트웨어 현황</span>
|
|
<div style="font-size: 1.02rem; color:var(--text-muted); margin-bottom: 1rem;">등록된 내부 솔루션: ${intTotal}개</div>
|
|
<div style="font-size: 2.21rem; font-weight:700; color:var(--dash-primary);">${intPer}%</div>
|
|
<div style="width: 100%; height: 4px; background-color: var(--border-color); border-radius: 2px; overflow: hidden; margin-top: 0.5rem;">
|
|
<div style="width: ${intPer}%; height: 100%; background-color: var(--dash-primary);"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<h3 class="dashboard-section-title">2026년 누적 도입 비용 분석</h3>
|
|
|
|
<div style="display:grid; grid-template-columns: repeat(2, 1fr); gap:1.5rem; margin-bottom:1.5rem;">
|
|
<div class="dashboard-card" style="min-height:auto;">
|
|
<span style="font-size:1.21rem; font-weight:700; color:var(--text-main);">외부 SW 누적 비용 (2026)</span>
|
|
<div style="font-size: 2.21rem; font-weight:700; color:var(--dash-primary);">₩ ${extCost2026.toLocaleString()}</div>
|
|
</div>
|
|
<div class="dashboard-card" style="min-height:auto;">
|
|
<span style="font-size:1.21rem; font-weight:700; color:var(--text-main);">내부 SW 누적 비용 (2026)</span>
|
|
<div style="font-size: 2.21rem; font-weight:700; color:#3b82f6;">₩ ${intCost2026.toLocaleString()}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
container.querySelector('[data-action="ext-usage"]')?.addEventListener('click', () => openSwUsageDetail('외부 소프트웨어 사용 목록', state.masterData.swExternal));
|
|
container.querySelector('[data-action="int-usage"]')?.addEventListener('click', () => openSwUsageDetail('내부 소프트웨어 사용 목록', state.masterData.swInternal));
|
|
}
|
|
|
|
function isSWExpiring(sw: any) {
|
|
const expiry = sw[ASSET_SCHEMA.EXPIRED_DATE.key];
|
|
if (!expiry) return false;
|
|
const endMs = new Date(normalizeDate(expiry)).getTime();
|
|
const diffDays = (endMs - Date.now()) / (1000 * 60 * 60 * 24);
|
|
return diffDays >= 0 && diffDays <= 30;
|
|
}
|