WIP(style): UI 컴포넌트 하드코딩 제거 및 CSS 통합 (진행 중)

- 작업 상태: 진행 중 (Work In Progress)
- 주요 변경 사항:
  1. CSS 파일 통합: HWModal, SWModal, ListFactory 등에서 인라인 스타일(style 속성) 전면 제거 및 클래스 기반으로 재작성
  2. 폰트/타이포그래피 스케일업: 최소 폰트 14px 기준으로 전체 텍스트 크기 상향 및 굵기(font-weight) 상향 조정
  3. GNB(상단바) 레이아웃 개편: 2단 구조(로고 라인 / 메뉴 라인)로 변경 및 카테고리 텍스트 라벨 생략을 통한 간결화
  4. 로고 이미지 교체: image 92.png로 업데이트 및 경로 정리
  5. 디자인 가이드 분리: README에서 design_rule.md로 디자인 정책 문서 독립

* 참고: 현재 디자인 검토를 위한 중간 반영 상태이며, 피드백에 따라 추가 수정 예정임.
This commit is contained in:
2026-06-12 15:57:20 +09:00
parent 56abdddbc7
commit b169176d57
12 changed files with 420 additions and 302 deletions

View File

@@ -22,39 +22,6 @@ class HwAssetModal extends BaseModal {
protected renderFrameHTML(): string {
return `
<style>
.autocomplete-list {
position: absolute;
top: 100%;
left: 0;
right: 0;
max-height: 150px;
overflow-y: auto;
background-color: white;
border: 1px solid var(--border-color, #E2E8F0);
border-top: none;
border-radius: 0 0 4px 4px;
z-index: 1000;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.autocomplete-item {
padding: 8px 12px;
font-size: 13px;
color: #334155;
cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.autocomplete-item:hover {
background-color: #F1F5F9;
color: #1E5149;
font-weight: 600;
}
.hidden {
display: none !important;
}
</style>
<div id="hw-asset-modal" class="modal-overlay hidden">
<div class="modal-content wide">
<div class="modal-header">
@@ -100,14 +67,14 @@ class HwAssetModal extends BaseModal {
</div>
<div class="form-group">
<label>${ASSET_SCHEMA.SERVICE_TYPE.ui}</label>
<select id="hw-service_type" name="service_type" style="${inputStyle}">
<select id="hw-service_type" name="service_type">
<option value="외부">외부</option>
<option value="내부">내부</option>
</select>
</div>
<div class="form-group full-width" style="grid-column: span 2;">
<div class="form-group full-width">
<label>${ASSET_SCHEMA.ASSET_PURPOSE.ui}</label>
<input type="text" id="hw-asset_purpose" name="asset_purpose" placeholder="자산의 용도를 입력하세요" style="${inputStyle} width: 100%;" />
<input type="text" id="hw-asset_purpose" name="asset_purpose" placeholder="자산의 용도를 입력하세요" />
</div>
<div class="form-group infra-only monitoring-field">
<label>${ASSET_SCHEMA.MONITORING.ui}</label>
@@ -118,18 +85,18 @@ class HwAssetModal extends BaseModal {
</div>
<!-- [SECTION 2] 조직 및 사용자 정보 -->
<div class="form-section-title" style="margin-top: 24px; margin-bottom: 12px;">사용자 및 조직 정보</div>
<div class="form-group">
<div class="form-section-title org-user-section">사용자 및 조직 정보</div>
<div class="form-group org-user-field">
<label>${ASSET_SCHEMA.CURRENT_DEPT.ui}</label>
<select id="hw-current_dept" name="current_dept">${generateOptionsHTML(ORG_LIST)}</select>
</div>
<div class="form-group">
<div class="form-group org-user-field">
<label>${ASSET_SCHEMA.MANAGER_MAIN.ui}</label>
<input type="text" id="hw-manager_primary" name="manager_primary" />
</div>
<div class="form-group">
<label>${ASSET_SCHEMA.MANAGER_SUB.ui}</label>
<input type="text" id="hw-manager_secondary" name="manager_secondary" style="${inputStyle}" />
<input type="text" id="hw-manager_secondary" name="manager_secondary" />
</div>
<div class="form-group personal-only">
<label>${ASSET_SCHEMA.CURRENT_USER.ui}</label>
@@ -164,17 +131,17 @@ class HwAssetModal extends BaseModal {
</div>
<div class="form-group spec-only" style="position: relative;">
<label>${ASSET_SCHEMA.CPU.ui}</label>
<input type="text" id="hw-cpu" name="cpu" autocomplete="off" placeholder="CPU 부품 검색..." style="${inputStyle}" />
<input type="text" id="hw-cpu" name="cpu" autocomplete="off" placeholder="CPU 부품 검색..." />
<div id="hw-cpu-autocomplete" class="autocomplete-list hidden"></div>
</div>
<div class="form-group spec-only" style="position: relative;">
<label>${ASSET_SCHEMA.RAM.ui}</label>
<input type="text" id="hw-ram" name="ram" autocomplete="off" placeholder="RAM 부품 검색..." style="${inputStyle}" />
<input type="text" id="hw-ram" name="ram" autocomplete="off" placeholder="RAM 부품 검색..." />
<div id="hw-ram-autocomplete" class="autocomplete-list hidden"></div>
</div>
<div class="form-group spec-only" style="position: relative;">
<label>${ASSET_SCHEMA.GPU.ui}</label>
<input type="text" id="hw-gpu" name="gpu" autocomplete="off" placeholder="GPU 부품 검색..." style="${inputStyle}" />
<input type="text" id="hw-gpu" name="gpu" autocomplete="off" placeholder="GPU 부품 검색..." />
<div id="hw-gpu-autocomplete" class="autocomplete-list hidden"></div>
</div>
<div class="form-group spec-only">
@@ -183,7 +150,7 @@ class HwAssetModal extends BaseModal {
</div>
<div class="form-group spec-only">
<label>성능 등급</label>
<div id="hw-pc-grade-container" style="display: flex; align-items: center; height: 38px;">
<div id="hw-pc-grade-container" class="grade-badge-container">
<span class="badge b-yellow" id="hw-pc-grade-badge">-</span>
</div>
</div>
@@ -266,7 +233,10 @@ class HwAssetModal extends BaseModal {
<div class="modal-history-area">
<div class="history-header">
<h3>자산 변동 이력</h3>
<button type="button" id="btn-add-hw-log" class="btn btn-outline btn-sm">이력 추가</button>
<div class="history-actions">
<button type="button" id="btn-view-asset-flow" class="btn btn-outline btn-sm">흐름 보기</button>
<button type="button" id="btn-add-hw-log" class="btn btn-outline btn-sm">이력 추가</button>
</div>
</div>
<div id="hw-history-list" class="history-timeline"></div>
</div>

View File

@@ -24,93 +24,111 @@ const MENU_CONFIG: any = {
};
export function renderNavigation(onTabChange: (tab: string) => void) {
const navContainer = document.getElementById('main-nav')!;
const headerContainer = document.querySelector('.header-container')!;
if (!headerContainer) return;
const render = () => {
navContainer.innerHTML = '';
// 1. 헤더 레이아웃 구조 생성
headerContainer.innerHTML = `
<!-- [TOP ROW] 로고 및 사용자 액션 -->
<div class="header-top-row">
<div class="brand" id="btn-home-logo" style="cursor: pointer;">
<img src="img/image_92.png" class="main-logo" alt="HM Logo" />
<h1>IT 자산 통합 관리 <span class="sub-title">ITAM</span></h1>
</div>
<div class="header-actions">
<div class="role-switcher">
<span class="role-label user ${state.currentUserRole === 'user' ? 'active' : ''}">실무자</span>
<label class="switch">
<input type="checkbox" id="role-toggle-checkbox" ${state.currentUserRole === 'admin' ? 'checked' : ''}>
<span class="slider"></span>
</label>
<span class="role-label admin ${state.currentUserRole === 'admin' ? 'active' : ''}">관리자</span>
</div>
<div class="notification-area">
<button class="icon-btn" title="알림"><i data-lucide="bell" style="width:18px; height:18px;"></i></button>
</div>
</div>
</div>
<!-- [BOTTOM ROW] 통합 내비게이션 (2단 메뉴) -->
<div class="header-bottom-row">
<nav class="integrated-nav" id="main-nav-list"></nav>
</div>
`;
const navList = document.getElementById('main-nav-list')!;
// 기존 메뉴 렌더링
// 2. 메뉴 그룹화 및 렌더링 (대분류 제목 제외, 간격으로 구분)
(Object.keys(MENU_CONFIG) as Array<keyof typeof MENU_CONFIG>).forEach(catKey => {
const config = MENU_CONFIG[catKey];
// 역할에 따라 노출할 서브탭 필터링
const visibleTabs = config.tabs.filter((tab: string) => {
if (state.currentUserRole === 'admin') {
// 관리자(admin)일 경우 대시보드 탭만 노출
return tab === '대시보드';
} else {
// 실무자(user)일 경우 대시보드 제외한 모든 탭 노출
return tab !== '대시보드';
}
if (state.currentUserRole === 'admin') return tab === '대시보드';
return tab !== '대시보드';
});
// 노출할 서브탭이 없으면 해당 대분류 GNB 메뉴도 렌더링하지 않음
if (visibleTabs.length === 0) {
return;
}
const isActive = state.activeCategory === catKey;
if (visibleTabs.length === 0) return;
const group = document.createElement('div');
group.className = `nav-group ${isActive ? 'active is-showing-shelf' : ''}`;
group.className = 'nav-group';
const trigger = document.createElement('div');
trigger.className = 'gnb-trigger';
trigger.textContent = config.label;
const itemsContainer = document.createElement('div');
itemsContainer.className = 'nav-group-items';
trigger.addEventListener('click', () => {
if (state.activeCategory !== catKey) {
state.activeCategory = catKey as any;
const firstTab = visibleTabs[0] || config.tabs[0];
state.activeSubTab = firstTab;
render();
onTabChange(firstTab);
}
});
group.appendChild(trigger);
const shelf = document.createElement('div');
shelf.className = 'lnb-shelf';
visibleTabs.forEach((tab: string) => {
if (tab === '부품 마스터') return; // 메뉴바에서 표시 생략
if (tab === '부품 마스터') return;
const item = document.createElement('div');
item.className = `lnb-item ${isActive && state.activeSubTab === tab ? 'active' : ''}`;
const isActive = state.activeSubTab === tab;
item.className = `gnb-trigger ${isActive ? 'active' : ''}`;
item.textContent = tab;
item.addEventListener('click', (e) => {
e.stopPropagation();
state.activeCategory = catKey as any;
state.activeSubTab = tab;
render();
render(); // 재렌더링하여 활성 상태 반영
onTabChange(tab);
});
shelf.appendChild(item);
itemsContainer.appendChild(item);
});
group.appendChild(shelf);
navContainer.appendChild(group);
group.appendChild(itemsContainer);
navList.appendChild(group);
});
// ─── '관리자' 메뉴 별도 추가 (GNB 스타일 - 관리자 역할일 때만 노출) ───
// 3. 관리자 전용 '관리도구' (원래 '관리자' 메뉴)
if (state.currentUserRole === 'admin') {
const adminGroup = document.createElement('div');
adminGroup.className = 'nav-group';
const adminTrigger = document.createElement('div');
adminTrigger.className = 'gnb-trigger';
adminTrigger.innerHTML = '관리';
adminTrigger.style.color = 'var(--text-muted)';
adminTrigger.style.borderLeft = '1px solid var(--border-color)';
adminTrigger.style.marginLeft = '1rem';
adminTrigger.style.paddingLeft = '1.5rem';
adminTrigger.addEventListener('click', () => {
window.open('/map_editor.html', '_blank');
});
adminTrigger.className = 'gnb-trigger admin-trigger';
adminTrigger.innerHTML = '관리도구';
adminTrigger.addEventListener('click', () => window.open('/map_editor.html', '_blank'));
adminGroup.appendChild(adminTrigger);
navContainer.appendChild(adminGroup);
navList.appendChild(adminGroup);
}
// 4. 이벤트 바인딩 (로고 클릭 및 역할 전환)
document.getElementById('btn-home-logo')?.addEventListener('click', () => location.reload());
const roleToggle = document.getElementById('role-toggle-checkbox') as HTMLInputElement;
roleToggle?.addEventListener('change', () => {
state.currentUserRole = roleToggle.checked ? 'admin' : 'user';
if (state.currentUserRole === 'admin') {
state.activeCategory = 'hw';
state.activeSubTab = '대시보드';
} else {
state.activeCategory = 'hw';
state.activeSubTab = '서버';
}
render();
onTabChange(state.activeSubTab);
});
// 아이콘 생성
// @ts-ignore
if (window.lucide) window.lucide.createIcons();
};
render();