feat: HW 모달 UI 고도화 및 자산 분류 체계 개편

This commit is contained in:
2026-06-09 13:56:05 +09:00
parent 3ab587d342
commit 4b408b0640
5 changed files with 241 additions and 303 deletions

View File

@@ -48,9 +48,14 @@ export function createListView(container: HTMLElement, config: ListViewConfig) {
const toggleWrapper = document.createElement('div');
toggleWrapper.className = 'view-toggle-container';
toggleWrapper.innerHTML = `
<div class="view-toggle">
<button class="toggle-btn ${(state as any).currentViewMode === 'system' ? 'active' : ''}" data-mode="system">자산 현황</button>
<button class="toggle-btn ${(state as any).currentViewMode === 'asset' ? 'active' : ''}" data-mode="asset">자산 목록</button>
<div style="display: flex; justify-content: space-between; align-items: center; width: 100%;">
<div class="view-toggle" style="display: flex; gap: 0;">
<button class="toggle-btn ${(state as any).currentViewMode === 'system' ? 'active' : ''}" data-mode="system">자산 현황</button>
<button class="toggle-btn ${(state as any).currentViewMode === 'asset' ? 'active' : ''}" data-mode="asset">자산 목록</button>
</div>
<button id="btn-add-asset" style="padding: 6px 14px; font-size: 12px; font-weight: 700; background: #1E5149; color: white; border: none; border-radius: 4px; cursor: pointer; display: flex; align-items: center; gap: 4px;">
<span style="font-size: 16px; line-height: 1;">+</span> 자산 추가
</button>
</div>
`;
container.appendChild(toggleWrapper);
@@ -87,8 +92,20 @@ export function createListView(container: HTMLElement, config: ListViewConfig) {
const pcTypeCounts = { public: 0, server: 0, personal: 0 };
// 동적 통계 수집 객체 (Hardcoding 제거)
const extStats = { total: 0, locCounts: {} as Record<string, number>, typeCounts: {} as Record<string, number>, locWarning: 0, typeWarning: 0 };
const intStats = { total: 0, locCounts: {} as Record<string, number>, typeCounts: {} as Record<string, number> };
const extStats = {
total: 0,
locCounts: {} as Record<string, number>,
typeCounts: {} as Record<string, number>,
typeLocMap: {} as Record<string, Record<string, number>>, // 유형별 위치 분포
locWarning: 0,
typeWarning: 0
};
const intStats = {
total: 0,
locCounts: {} as Record<string, number>,
typeCounts: {} as Record<string, number>,
typeLocMap: {} as Record<string, Record<string, number>>
};
// 중앙화된 경고 감지 로직
const checkAnomaly = (serviceType: string, loc: string, type: string) => {
@@ -122,7 +139,12 @@ export function createListView(container: HTMLElement, config: ListViewConfig) {
const targetStat = serviceType === '내부' ? intStats : extStats;
targetStat.total++;
if (loc) targetStat.locCounts[loc] = (targetStat.locCounts[loc] || 0) + 1;
if (type) targetStat.typeCounts[type] = (targetStat.typeCounts[type] || 0) + 1;
if (type) {
targetStat.typeCounts[type] = (targetStat.typeCounts[type] || 0) + 1;
// 유형별 위치 분포 수집
if (!targetStat.typeLocMap[type]) targetStat.typeLocMap[type] = {};
targetStat.typeLocMap[type][loc] = (targetStat.typeLocMap[type][loc] || 0) + 1;
}
if (serviceType === '외부') {
const anomaly = checkAnomaly(serviceType, loc, type);
@@ -132,7 +154,7 @@ export function createListView(container: HTMLElement, config: ListViewConfig) {
});
// 템플릿 제너레이터 함수 (HTML 중복 제거)
const generateDetailStatHTML = (title: string, stats: typeof extStats) => `
const generateDetailStatHTML = (title: string, stats: any) => `
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.5rem; gap: 0.5rem;">
<span style="font-size: 14px; font-weight: 800; color: var(--text-main); white-space: nowrap;">${title}</span>
<div style="display: flex; gap: 4px; flex-wrap: wrap; justify-content: flex-end;">
@@ -142,12 +164,20 @@ export function createListView(container: HTMLElement, config: ListViewConfig) {
</div>
<div style="display: flex; flex-direction: column; gap: 0.3rem; font-size: 13px; color: var(--text-muted);">
<div style="display: flex; gap: 0.75rem; flex-wrap: wrap;">
${Object.entries(stats.locCounts).sort((a, b) => b[1] - a[1]).slice(0, 4).map(([l, c]) => `<span>${l}: <strong style="color:var(--text-main); font-size: 14px;">${c}</strong></span>`).join('')}
${Object.entries(stats.locCounts as Record<string, number>).sort((a, b) => b[1] - a[1]).slice(0, 4).map(([l, c]) => `<span>${l}: <strong style="color:var(--text-main); font-size: 14px;">${c}</strong></span>`).join('')}
</div>
<div style="display: flex; gap: 0.6rem; flex-wrap: wrap; opacity: 0.9; border-top: 1px dashed var(--border-color); padding-top: 4px; margin-top: 2px;">
${Object.entries(stats.typeCounts).sort((a, b) => b[1] - a[1]).slice(0, 6).map(([t, c]) => {
${Object.entries(stats.typeCounts as Record<string, number>).sort((a, b) => b[1] - a[1]).slice(0, 6).map(([t, c]) => {
const isTypeWarning = title.includes('외부') && t.toLowerCase().replace(/\s/g, '').includes('서버pc');
return `<span style="${isTypeWarning ? 'color:#E11D48; font-weight:700;' : ''}; font-size: 13px;">${t}: <strong style="color:var(--text-main); font-size: 14px;">${c}</strong></span>`;
// 위치별 상세 정보 생성 (툴팁용)
const locDist = stats.typeLocMap[t] || {};
const locHint = Object.entries(locDist)
.sort((a: any, b: any) => b[1] - a[1])
.map(([l, count]) => `${l}: ${count}`)
.join('\n');
return `<span title="${locHint}" style="${isTypeWarning ? 'color:#E11D48; font-weight:700;' : ''}; font-size: 13px; cursor: help;">${t}: <strong style="color:var(--text-main); font-size: 14px;">${c}</strong></span>`;
}).join('')}
</div>
</div>