chore: clean up build artifacts, temporary excel locks, duplicate plans, and commit current project state
Some checks failed
ITAM Code Check / build-and-config-check (push) Successful in 18s
ITAM Docker Build Check / docker-build-check (push) Failing after 21s

This commit is contained in:
이태훈
2026-06-22 11:26:26 +09:00
parent 7b631ab858
commit 621b05a890
135 changed files with 22565 additions and 42690 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,83 +1,83 @@
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: any) => {
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') {
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 bg-soft">
<h3 class="dashboard-section-title">소프트웨어 라이선스 현황</h3>
<div class="dashboard-layout-2col mb-6">
<div class="dashboard-card clickable" data-action="ext-usage">
<div class="stat-label">외부 소프트웨어 사용율</div>
<div class="stat-sub">${extQty}카피 중 ${extUsed}개 할당</div>
<div class="stat-value text-primary">${extPer}%</div>
<div class="stat-progress-bar">
<div class="progress-fill" style="width: ${extPer}%;"></div>
</div>
</div>
<div class="dashboard-card clickable" data-action="int-usage">
<div class="stat-label">내부 소프트웨어 현황</div>
<div class="stat-sub">등록된 내부 솔루션: ${intTotal}개</div>
<div class="stat-value text-primary">${intPer}%</div>
<div class="stat-progress-bar">
<div class="progress-fill" style="width: ${intPer}%;"></div>
</div>
</div>
</div>
<h3 class="dashboard-section-title">2026년 누적 도입 비용 분석</h3>
<div class="dashboard-layout-2col">
<div class="dashboard-card">
<div class="stat-label">외부 SW 누적 비용 (2026)</div>
<div class="stat-value text-primary">₩ ${extCost2026.toLocaleString()}</div>
</div>
<div class="dashboard-card">
<div class="stat-label">내부 SW 누적 비용 (2026)</div>
<div class="stat-value text-blue">₩ ${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;
}
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: any) => {
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') {
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 bg-soft">
<h3 class="dashboard-section-title">소프트웨어 라이선스 현황</h3>
<div class="dashboard-layout-2col mb-6">
<div class="dashboard-card clickable" data-action="ext-usage">
<div class="stat-label">외부 소프트웨어 사용율</div>
<div class="stat-sub">${extQty}카피 중 ${extUsed}개 할당</div>
<div class="stat-value text-primary">${extPer}%</div>
<div class="stat-progress-bar">
<div class="progress-fill" style="width: ${extPer}%;"></div>
</div>
</div>
<div class="dashboard-card clickable" data-action="int-usage">
<div class="stat-label">내부 소프트웨어 현황</div>
<div class="stat-sub">등록된 내부 솔루션: ${intTotal}개</div>
<div class="stat-value text-primary">${intPer}%</div>
<div class="stat-progress-bar">
<div class="progress-fill" style="width: ${intPer}%;"></div>
</div>
</div>
</div>
<h3 class="dashboard-section-title">2026년 누적 도입 비용 분석</h3>
<div class="dashboard-layout-2col">
<div class="dashboard-card">
<div class="stat-label">외부 SW 누적 비용 (2026)</div>
<div class="stat-value text-primary">₩ ${extCost2026.toLocaleString()}</div>
</div>
<div class="dashboard-card">
<div class="stat-label">내부 SW 누적 비용 (2026)</div>
<div class="stat-value text-blue">₩ ${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;
}

File diff suppressed because it is too large Load Diff