1. 컬럼 드래그 너비 조정 버그 수정 및 개선 (ListFactory.ts) - 드래그 완료 시 click 이벤트 전파 차단으로 정렬(sorting) 오작동 방지 - getBoundingClientRect().width 활용한 소수점 정밀 너비 고정 및 레이아웃 시프트 방지 - 마우스 업 시점의 모든 컬럼 너비를 config.columns에 동기화하여 재렌더링 시 너비 영속성 보장 2. PC 자산 모달 필드 잠금 정책 세분화 (HWModal.ts) - 자산 추가(add) 모드에서는 모든 필드(사용자 정보 포함) 입력 허용 - 자산 수정(edit) 모드에서만 사용자/조직 정보 관련 필드(lockedUserFields) 선택적 잠금 적용 - 시스템 사양, 네트워크, 위치, 구매 등 다른 모든 섹션은 수정 가능하도록 복구 및 안내 배너 갱신 3. 관리자 전용 메뉴 단일 페이지 앱(SPA) 통합 (Navigation.ts, main.ts, MapEditor.ts) - 기존의 실사 승인 탭과 독립 실행형 좌표 에디터(MapEditor)를 GNB '관리도구' 하위 메뉴로 통합 - '실사 승인', '위치지정'을 GNB에서 ↳ 화살표 및 11px 폰트의 계층형 탭 스타일로 렌더링 - 내부 서브 탭 바를 삭제하고 메인 영역 전체 높이(calc(100vh - var(--header-height) - 48px))를 확보 - 다른 탭으로 이동 시 MapEditor 인스턴스의 window 이벤트 및 전역 바인딩을 소거하는 destroy() 리사이클 구현 4. 자산 이력(History) 가독성 개선 및 포맷팅 (HWModal.ts, SWModal.ts, DomainModal.ts) - 자산 변경 이력 로그를 일자별로 그룹화하여 타임라인 렌더링 - 최초 등록 데이터에 녹색 '[최초등록]' 배지 추가 - 기존의 생 JSON 이력 데이터를 친절한 한국어 텍스트 포맷으로 가공하여 가독성 극대화
132 lines
4.4 KiB
TypeScript
132 lines
4.4 KiB
TypeScript
import { state } from '../core/state';
|
|
|
|
const MENU_CONFIG: any = {
|
|
hw: {
|
|
label: '하드웨어',
|
|
tabs: ['대시보드', '서버', 'PC', '스토리지', '공간정보장비', 'PC부품', '부품 마스터', '네트워크', '업무지원장비']
|
|
},
|
|
sw: {
|
|
label: '소프트웨어',
|
|
tabs: ['외부SW', '내부SW']
|
|
},
|
|
ops: {
|
|
label: '운영지원',
|
|
tabs: ['클라우드', '도메인', '비용관리', '사용자']
|
|
},
|
|
vip: {
|
|
label: '내빈/외빈',
|
|
tabs: ['선물']
|
|
},
|
|
fac: {
|
|
label: '시설자산',
|
|
tabs: ['사무가구']
|
|
}
|
|
};
|
|
|
|
export function renderNavigation(onTabChange: (tab: string) => void) {
|
|
const header = document.querySelector('.main-header') as HTMLElement;
|
|
const headerContainer = document.querySelector('.header-container')!;
|
|
if (!headerContainer) return;
|
|
|
|
const render = () => {
|
|
// 1. 헤더 구조 (Vercel Style: Clean Single Row)
|
|
headerContainer.innerHTML = `
|
|
<div class="brand" id="btn-home-logo" style="cursor: pointer;">
|
|
<img src="img/image_92.png" class="main-logo" alt="HM Logo" />
|
|
<h1>한맥자산관리시스템</h1>
|
|
</div>
|
|
|
|
<nav class="integrated-nav" id="main-nav-list"></nav>
|
|
|
|
<div class="header-actions">
|
|
<div class="role-toggle-wrapper">
|
|
<span class="role-label user ${state.currentUserRole === 'user' ? 'active' : ''}">실무자</span>
|
|
<label class="role-toggle">
|
|
<input type="checkbox" id="role-toggle-checkbox" ${state.currentUserRole === 'admin' ? 'checked' : ''}>
|
|
<span class="role-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>
|
|
`;
|
|
|
|
const navList = document.getElementById('main-nav-list')!;
|
|
|
|
// 2. GNB 메뉴 렌더링 (Ghost Tab Style)
|
|
Object.keys(MENU_CONFIG).forEach(catKey => {
|
|
const config = MENU_CONFIG[catKey];
|
|
|
|
let visibleTabs = config.tabs.filter((tab: string) => {
|
|
if (state.currentUserRole === 'admin') return tab === '대시보드';
|
|
return tab !== '대시보드';
|
|
});
|
|
|
|
if (state.currentUserRole === 'admin' && catKey === 'hw') {
|
|
visibleTabs = ['대시보드', '관리도구', '실사 승인', '위치지정'];
|
|
}
|
|
|
|
if (visibleTabs.length === 0) return;
|
|
|
|
visibleTabs.forEach((tab: string) => {
|
|
if (tab === '부품 마스터') return;
|
|
const item = document.createElement('div');
|
|
const isActive = state.activeSubTab === tab;
|
|
item.className = `gnb-trigger ${isActive ? 'active' : ''}`;
|
|
|
|
const isSubMenu = tab === '실사 승인' || tab === '위치지정';
|
|
if (isSubMenu) {
|
|
item.innerHTML = `<span style="opacity: 0.5; margin-right: 3px; font-family: sans-serif;">↳</span>${tab}`;
|
|
item.style.fontSize = '11px';
|
|
item.style.fontWeight = '500';
|
|
item.style.marginLeft = '6px';
|
|
if (!isActive) {
|
|
item.style.color = 'var(--mute)';
|
|
}
|
|
} else {
|
|
item.textContent = tab;
|
|
item.style.fontSize = 'var(--fs-sm)';
|
|
}
|
|
|
|
item.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
state.activeCategory = catKey as any;
|
|
if (tab === '관리도구') {
|
|
state.activeSubTab = '실사 승인';
|
|
} else {
|
|
state.activeSubTab = tab;
|
|
}
|
|
render();
|
|
onTabChange(state.activeSubTab);
|
|
});
|
|
navList.appendChild(item);
|
|
});
|
|
});
|
|
|
|
// 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();
|
|
}
|