feat: restructure navigation, customize list columns, and move action buttons to search bar

1. Restructured navigation hierarchy (Hardware, Software, Ops Support, etc.).
2. Customized table columns for all asset categories according to new specs.
3. Moved Template/Upload/Export/Add buttons to search bar with layout optimization.
4. Hidden Asset Code and Previous User from list views (Modal only).
5. Added Current/Previous User and detailed PC spec fields (GPU, HDD3/4).
This commit is contained in:
2026-05-20 14:34:07 +09:00
parent 2af79cdad3
commit d34ebb8500
23 changed files with 1509 additions and 964 deletions

View File

@@ -1,4 +1,4 @@
import { state, loadMasterDataFromDB } from './core/state';
import { state, loadMasterDataFromDB, saveAsset } from './core/state';
import { renderNavigation } from './components/Navigation';
import { renderDashboard } from './views/DashboardView';
import { renderSWTable } from './views/SW_Table';
@@ -35,15 +35,15 @@ async function apiBatchSave(url: string, data: any[], label: string) {
const savePcToDB = () => apiBatchSave(`http://${location.hostname}:3000/api/pc/batch`, state.masterData.pc, '개인PC');
const saveServerToDB = () => apiBatchSave(`http://${location.hostname}:3000/api/server/batch`, state.masterData.server, '서버');
const saveStorageToDB = () => apiBatchSave(`http://${location.hostname}:3000/api/storage/batch`, state.masterData.storage, '스토리지');
const saveEquipToDB = () => apiBatchSave(`http://${location.hostname}:3000/api/equip/batch`, state.masterData.equip, '전산비품');
const saveMobileToDB = () => apiBatchSave(`http://${location.hostname}:3000/api/mobile/batch`, state.masterData.mobile, '모바일기기');
const saveSubSwToDB = () => apiBatchSave(`http://${location.hostname}:3000/api/asset/software/subscription/batch`, state.masterData.subSw, '구독SW');
const savePermSwToDB = () => apiBatchSave(`http://${location.hostname}:3000/api/asset/software/perpetual/batch`, state.masterData.permSw, '영구SW');
const saveCloudToDB = () => apiBatchSave(`http://${location.hostname}:3000/api/asset/cloud/batch`, state.masterData.cloud, '클라우드');
const saveNetworkToDB = () => apiBatchSave(`http://${location.hostname}:3000/api/network/batch`, state.masterData.network, '네트워크');
const saveEquipToDB = () => apiBatchSave(`http://${location.hostname}:3000/api/equipment/batch`, state.masterData.equipment, '업무지원장비');
const saveSwInternalToDB = () => apiBatchSave(`http://${location.hostname}:3000/api/sw/internal/batch`, state.masterData.swInternal, '내부SW');
const saveSwExternalToDB = () => apiBatchSave(`http://${location.hostname}:3000/api/sw/external/batch`, state.masterData.swExternal, '외부SW');
const saveCloudToDB = () => apiBatchSave(`http://${location.hostname}:3000/api/cloud/batch`, state.masterData.cloud, '클라우드');
const saveSwUsersToDB = () => apiBatchSave(`http://${location.hostname}:3000/api/asset/software/assignment/batch`, state.masterData.swUsers, 'SW사용자');
const saveLogsToDB = () => apiBatchSave(`http://${location.hostname}:3000/api/asset/history/batch`, state.masterData.logs, '자산 로그');
// 화면 갱신 통합 핸들러 (대시보드 vs 리스트)
// 화면 갱신 통합 핸들러
function refreshView() {
const mainContent = document.getElementById('main-content')!;
if (!mainContent) return;
@@ -55,32 +55,15 @@ function refreshView() {
}
}
// 모든 하드웨어 DB 동기화
async function saveAllHardwareToDB() {
await Promise.all([
savePcToDB(),
saveServerToDB(),
saveStorageToDB(),
saveEquipToDB(),
saveMobileToDB(),
saveLogsToDB()
]);
await loadMasterDataFromDB();
refreshView();
}
// 모든 소프트웨어 DB 동기화
async function saveAllSoftwareToDB() {
await Promise.all([
saveSubSwToDB(),
savePermSwToDB(),
saveCloudToDB(),
saveSwUsersToDB(),
saveLogsToDB()
]);
// 저장 후 최신 데이터 다시 로드 (정합성)
await loadMasterDataFromDB();
refreshView();
// 통합 저장 및 갱신
async function saveAllDataToDB() {
await Promise.all([
savePcToDB(), saveServerToDB(), saveStorageToDB(), saveNetworkToDB(),
saveEquipToDB(), saveSwInternalToDB(), saveSwExternalToDB(),
saveCloudToDB(), saveSwUsersToDB(), saveLogsToDB()
]);
await loadMasterDataFromDB();
refreshView();
}
// --- App Initialization ---
@@ -91,7 +74,6 @@ function initApp() {
const { closeAllModals } = initBaseModal();
try {
// 네비게이션 렌더링 및 콜백 연결
renderNavigation((tab) => {
if (tab === '대시보드') {
renderDashboard(mainContent);
@@ -100,9 +82,8 @@ function initApp() {
}
});
// 각종 모달 및 가이드 초기화
initHwModal(() => saveAllHardwareToDB(), closeAllModals);
initSwModal(() => saveAllSoftwareToDB(), closeAllModals);
initHwModal(() => saveAllDataToDB(), closeAllModals);
initSwModal(() => saveAllDataToDB(), closeAllModals);
initSwUserModal(() => {
saveSwUsersToDB().then(() => {
@@ -118,7 +99,6 @@ function initApp() {
});
initGuide();
// DB 데이터 로드 및 초기 화면 렌더링
loadMasterDataFromDB().then((success) => {
if (success) {
refreshView();
@@ -126,58 +106,62 @@ function initApp() {
});
} catch (e) { console.error('❌ Initialization failed:', e); }
console.log('🚀 ITAM App Version 2.1.0 Loaded');
console.log('🚀 ITAM App Multi-Table Optimized');
// 버튼 이벤트 바인딩
document.getElementById('btn-download-template')?.addEventListener('click', () => downloadTemplate());
document.getElementById('btn-export-excel')?.addEventListener('click', () => exportToExcel(state.masterData));
// --- 통합 이벤트 위임 (Dynamic Elements 지원) ---
document.addEventListener('click', (e) => {
const target = e.target as HTMLElement;
// 양식 다운로드
if (target.closest('#btn-download-template')) {
downloadTemplate();
return;
}
// 엑셀 내보내기
if (target.closest('#btn-export-excel')) {
exportToExcel(state.masterData);
return;
}
// 자산 추가
if (target.closest('#btn-add-asset')) {
const tab = state.activeSubTab;
const cat = state.activeCategory;
const newId = Math.random().toString(36).substring(2, 9);
if (cat === 'hw') {
openHwModal({ id: newId, asset_code: '', category: tab } as any, 'add');
} else if (cat === 'sw') {
openSwModal({ id: newId, asset_type: tab === '대시보드' ? '외부SW' : tab } as any, 'add');
} else if (cat === 'ops') {
if (tab === '도메인') openDomainModal(null);
}
return;
}
});
const uploadInput = document.getElementById('excel-upload') as HTMLInputElement;
uploadInput?.addEventListener('change', async (e) => {
const file = (e.target as HTMLInputElement).files?.[0];
if (file) {
console.log('📂 File selected:', file.name);
try {
const data = await parseExcel(file);
console.log('📊 Parsed data keys:', Object.keys(data));
openUploadPreview(data);
// Clear input so same file can be selected again
uploadInput.value = '';
} catch (err) {
alert('엑셀 파일을 읽는 중 오류가 발생했습니다.');
console.error(err);
// 엑셀 업로드 (Change 이벤트 위임)
document.addEventListener('change', async (e) => {
const target = e.target as HTMLInputElement;
if (target.id === 'excel-upload') {
const file = target.files?.[0];
if (file) {
try {
const data = await parseExcel(file);
openUploadPreview(data);
target.value = '';
} catch (err) {
alert('엑셀 파일을 읽는 중 오류가 발생했습니다.');
}
}
}
});
document.getElementById('btn-add-asset')?.addEventListener('click', () => {
const tab = state.activeSubTab;
const cat = state.activeCategory;
if (cat === 'hw') {
let defaultType = (tab === '개인PC') ? 'PC' : (tab === '서버' ? '서버' : (tab === '스토리지' ? '스토리지' : (tab === '전산비품' ? 'CPU' : '모바일')));
openHwModal({ id: Math.random().toString(36).substring(2, 9), type: defaultType, : '한맥', : '', : '', : '', MACaddress: '', HW사양: '', OS: '', : '', : '' } as any, 'add');
} else if (cat === 'sw') {
openSwModal({ id: Math.random().toString(36).substring(2, 9), type: tab === '대시보드' ? '구독SW' : tab, : '', : '', 수량: 1, : '', : '', : '', : '한맥' } as any, 'add');
} else if (cat === 'ops') {
if (tab === '도메인') openDomainModal(null);
}
});
// 시크릿 클라우드 트리거
document.getElementById('secret-cloud-trigger')?.addEventListener('click', () => {
state.activeCategory = 'sw';
state.activeSubTab = '클라우드';
const mainContent = document.getElementById('main-content')!;
renderSWTable(mainContent);
});
createIcons({
icons: { Download, Upload, FileSpreadsheet, Plus, X, LayoutDashboard, Monitor, Server, Database, Laptop, CalendarClock, Key, Cpu, Layers, Users, Paperclip, Edit2, History, RefreshCcw, BookOpen, Settings }
});
window.addEventListener('refresh-view', () => {
console.log('🔄 Refreshing view due to event');
refreshView();
});
window.addEventListener('refresh-view', () => refreshView());
}
document.addEventListener('DOMContentLoaded', initApp);