import { createIcons, Download, Upload, FileSpreadsheet, Plus, X, LayoutDashboard, Monitor, Server, Database, Laptop, CalendarClock, Key, Cpu, Layers, Users, Paperclip, Edit2 } from 'lucide'; import { MasterAssetData, HardwareAsset, SoftwareAsset, downloadTemplate, exportToExcel, parseExcel } from './excelHandler'; import { generateDummyData } from './dummyDataGenerator'; declare var Chart: any; let activeCharts: any[] = []; // --- State Management --- let masterData: MasterAssetData = generateDummyData(); let activeCategory: 'hw' | 'sw' = 'hw'; let activeSubTab: string = '대시보드'; // --- DOM Elements --- const mainContent = document.getElementById('main-content') as HTMLElement; const titleElement = document.getElementById('current-tab-title') as HTMLHeadingElement; const uploadInput = document.getElementById('excel-upload') as HTMLInputElement; const btnAddAsset = document.getElementById('btn-add-asset') as HTMLButtonElement; const btnDownloadTemp = document.getElementById('btn-download-template') as HTMLButtonElement; const btnExport = document.getElementById('btn-export-excel') as HTMLButtonElement; // Nav items const navItems = document.querySelectorAll('.nav-list li'); // Modal Elements const hwModal = document.getElementById('hw-asset-modal') as HTMLDivElement; const swModal = document.getElementById('sw-asset-modal') as HTMLDivElement; const pcModal = document.getElementById('pc-asset-modal') as HTMLDivElement; const storageModal = document.getElementById('storage-asset-modal') as HTMLDivElement; const hwForm = document.getElementById('hw-asset-form') as HTMLFormElement; const swForm = document.getElementById('sw-asset-form') as HTMLFormElement; const pcForm = document.getElementById('pc-asset-form') as HTMLFormElement; const storageForm = document.getElementById('storage-asset-form') as HTMLFormElement; const dashboardDetailModal = document.getElementById('dashboard-detail-modal') as HTMLDivElement; const btnCloseDashboardDetail = document.getElementById('btn-close-dashboard-detail') as HTMLButtonElement; btnCloseDashboardDetail?.addEventListener('click', () => { dashboardDetailModal.classList.add('hidden'); }); // Initialize Icons createIcons({ icons: { Download, Upload, FileSpreadsheet, Plus, X, LayoutDashboard, Monitor, Server, Database, Laptop, CalendarClock, Key, Cpu, Layers, Users, Paperclip, Edit2 } }); // --- Navigation Logic --- navItems.forEach(item => { item.addEventListener('click', (e) => { // 탭 UI 업데이트 navItems.forEach(nav => nav.classList.remove('active')); item.classList.add('active'); // 상태 업데이트 activeCategory = item.getAttribute('data-category') as 'hw' | 'sw'; activeSubTab = item.getAttribute('data-tab') || '대시보드'; // 타이틀 업데이트 const catName = activeCategory === 'hw' ? '하드웨어' : '소프트웨어'; titleElement.textContent = `${catName} / ${activeSubTab}`; // 추가 버튼 노출 여부 if (activeSubTab === '대시보드') { btnAddAsset.classList.add('hidden'); } else { btnAddAsset.classList.remove('hidden'); } renderContent(); }); }); // --- Render Logic --- function renderContent() { mainContent.innerHTML = ''; // Clear if (activeSubTab === '대시보드') { renderDashboard(); } else { renderTable(); } } function isHwIdle(a: HardwareAsset) { if (a.type === '개인PC') return !a.사용자 || a.사용자.trim() === '' || a.사용자.trim() === '-'; if (a.type === '스토리지') return !a.담당자_정 || a.담당자_정.trim() === '' || a.담당자_정.trim() === '-'; return !a.관리자 || a.관리자.trim() === '' || a.관리자.trim() === '-'; } function getHwAgeYears(a: HardwareAsset) { if (!a.구매일) return 0; return (Date.now() - new Date(a.구매일).getTime()) / (1000 * 60 * 60 * 24 * 365.25); } function isSWExpiring(sw: SoftwareAsset) { if (sw.type === '구독SW' && sw.구독일) { const parts = sw.구독일.split('~'); if (parts.length > 1) { const endStr = parts[1].trim(); const endMs = new Date(endStr.replace(/\\./g, '-')).getTime(); const diffDays = (endMs - Date.now()) / (1000 * 60 * 60 * 24); return diffDays >= 0 && diffDays <= 30; } } else if (sw.type === '영구SW' && sw.비고 && sw.비고.includes('유지보수: ~')) { const endStr = sw.비고.split('~')[1].trim(); const endMs = new Date(endStr.replace(/\\./g, '-')).getTime(); const diffDays = (endMs - Date.now()) / (1000 * 60 * 60 * 24); return diffDays >= 0 && diffDays <= 30; } return false; } function openDashboardDetail(title: string, list: HardwareAsset[]) { const modal = document.getElementById('dashboard-detail-modal') as HTMLDivElement; const titleEl = document.getElementById('dashboard-detail-modal-title') as HTMLHeadingElement; const tbody = document.getElementById('dashboard-detail-tbody') as HTMLTableSectionElement; const thead = tbody.closest('table')!.querySelector('thead')!; titleEl.textContent = title; thead.innerHTML = `No유형자산코드명칭/모델위치담당/사용자구매일금액`; tbody.innerHTML = ''; if (list.length === 0) { tbody.innerHTML = `해당 조건의 자산이 없습니다.`; } else { list.forEach((asset, idx) => { let manager = asset.관리자 || asset.사용자 || asset.담당자_정 || '-'; let name = asset.명칭 || asset.모델명 || '-'; let typeDisplay = asset.type === '전산비품' ? `전산비품(${asset.비품유형||'-'})` : asset.type; const tr = document.createElement('tr'); tr.innerHTML = ` ${idx+1} ${typeDisplay} ${asset.자산코드} ${name} ${asset.위치||'-'} ${manager} ${asset.구매일||'-'} ${asset.금액||'-'} `; tbody.appendChild(tr); }); } modal.classList.remove('hidden'); } function openSwDashboardDetail(title: string, list: SoftwareAsset[]) { const modal = document.getElementById('dashboard-detail-modal') as HTMLDivElement; const titleEl = document.getElementById('dashboard-detail-modal-title') as HTMLHeadingElement; const tbody = document.getElementById('dashboard-detail-tbody') as HTMLTableSectionElement; const thead = tbody.closest('table')!.querySelector('thead')!; titleEl.textContent = title; thead.innerHTML = `No유형법인제품명구독일/비고수량금액`; tbody.innerHTML = ''; if (list.length === 0) { tbody.innerHTML = `해당 조건의 소프트웨어가 없습니다.`; } else { list.forEach((sw, idx) => { const tr = document.createElement('tr'); tr.innerHTML = ` ${idx+1} ${sw.type === '구독SW' ? '구독' : '영구'} ${sw.법인||'-'} ${sw.제품명||'-'} ${sw.구독일 || sw.비고 || '-'} ${sw.수량||'-'} ${sw.금액||'-'} `; tbody.appendChild(tr); }); } modal.classList.remove('hidden'); } function openSwUsageDetail(title: string, list: SoftwareAsset[]) { const modal = document.getElementById('dashboard-detail-modal') as HTMLDivElement; const titleEl = document.getElementById('dashboard-detail-modal-title') as HTMLHeadingElement; const tbody = document.getElementById('dashboard-detail-tbody') as HTMLTableSectionElement; const thead = tbody.closest('table')!.querySelector('thead')!; titleEl.textContent = title; thead.innerHTML = `No법인제품명수량사용중사용가능구매일금액`; tbody.innerHTML = ''; if (list.length === 0) { tbody.innerHTML = `해당 유형의 소프트웨어가 없습니다.`; } else { list.forEach((sw, idx) => { const assigned = masterData.swUsers.filter(u => u.swId === sw.id).length; const qty = typeof sw.수량 === 'number' ? sw.수량 : parseInt(sw.수량 || '0', 10); const available = qty - assigned; const availColor = available > 0 ? 'var(--dash-primary)' : 'var(--dash-danger)'; const tr = document.createElement('tr'); tr.innerHTML = ` ${idx+1} ${sw.법인||'-'} ${sw.제품명||'-'} ${qty} ${assigned} ${available} ${sw.구매일||'-'} ${sw.금액||'-'} `; tbody.appendChild(tr); }); } modal.classList.remove('hidden'); } function renderDashboard() { mainContent.innerHTML = ''; activeCharts.forEach(c => c.destroy()); activeCharts = []; if (activeCategory === 'hw') { // 1. Data Aggregation const types = ['개인PC', '서버', '스토리지', '전산비품']; const units = ['대', '대', '대', '개']; const groups: any = {}; types.forEach(t => { groups[t] = { idle: [], active: [], aged: [], normal: [] }; }); masterData.hw.forEach(a => { if (!groups[a.type]) return; if (isHwIdle(a)) groups[a.type].idle.push(a); else groups[a.type].active.push(a); const ageY = getHwAgeYears(a); const isAged = a.type === '전산비품' ? ageY >= 3 : ageY >= 5; if (isAged) groups[a.type].aged.push(a); else groups[a.type].normal.push(a); }); const counts = types.map(t => groups[t].idle.length + groups[t].active.length); // 사용현황 카드 생성 let usageCards = ''; types.forEach((t, i) => { const total = groups[t].idle.length + groups[t].active.length; const used = groups[t].active.length; const per = total > 0 ? Math.round((used / total) * 100) : 0; const barColor = per >= 50 ? 'var(--dash-primary)' : 'var(--dash-danger)'; const perColor = per >= 50 ? 'var(--dash-primary)' : 'var(--dash-danger)'; const badge = per >= 50 ? `
잘 사용하고 있어요
` : `
점검이 필요합니다
`; usageCards += `
${t} 사용현황
${total}${units[i]} 중 ${used}${units[i]} 사용 중 · 유휴 ${groups[t].idle.length}${units[i]}
${per}%
${badge}
`; }); // 노후화 카드 생성 let agedCards = ''; types.forEach((t, i) => { const total = groups[t].aged.length + groups[t].normal.length; const agedCount = groups[t].aged.length; const agedPer = total > 0 ? Math.round((agedCount / total) * 100) : 0; const threshold = t === '전산비품' ? '3년' : '5년'; agedCards += `
${t} 노후화 현황 ${threshold} 초과
전체 ${total}${units[i]} 중 ${agedCount}${units[i]} 노후 장비
${agedCount}${units[i]}
${agedPer}%
`; }); mainContent.innerHTML = `

사용현황

${usageCards}

노후화 자산 비율

${agedCards}
`; // HW 카드 클릭 이벤트 바인딩 mainContent.querySelectorAll('[data-action="idle"]').forEach(card => { card.addEventListener('click', () => { const t = card.getAttribute('data-type')!; openDashboardDetail(`[${t}] 유휴 자산 목록`, groups[t].idle); }); }); mainContent.querySelectorAll('[data-action="aged"]').forEach(card => { card.addEventListener('click', () => { const t = card.getAttribute('data-type')!; openDashboardDetail(`[${t}] 노후 장비 목록`, groups[t].aged); }); }); } else { // S/W Dashboard let subQty = 0, subUsed = 0, subExp = 0, subTotal = 0; let permQty = 0, permUsed = 0, permExp = 0, permTotal = 0; masterData.sw.forEach(sw => { const assigned = masterData.swUsers.filter(u => u.swId === sw.id).length; const qty = typeof sw.수량 === 'number' ? sw.수량 : parseInt(sw.수량||'0', 10); if (sw.type === '구독SW') { subQty += qty; subUsed += assigned; subTotal++; if (isSWExpiring(sw)) subExp++; } else { permQty += qty; permUsed += assigned; permTotal++; if (isSWExpiring(sw)) permExp++; } }); const subPer = subQty > 0 ? Math.round((subUsed/subQty)*100) : 0; const permPer = permQty > 0 ? Math.round((permUsed/permQty)*100) : 0; const subExpPer = subTotal > 0 ? Math.round((subExp/subTotal)*100) : 0; const permExpPer = permTotal > 0 ? Math.round((permExp/permTotal)*100) : 0; // 상태 뱃지 (50% 미만이면 빨간색 경고) const subBadge = subPer >= 50 ? `
잘 사용하고 있어요
` : `
점검이 필요합니다
`; const permBadge = permPer >= 50 ? `
잘 사용하고 있어요
` : `
점검이 필요합니다
`; const subBarColor = subPer >= 50 ? 'var(--dash-primary)' : 'var(--dash-danger)'; const permBarColor = permPer >= 50 ? 'var(--dash-primary)' : 'var(--dash-danger)'; const subPerColor = subPer >= 50 ? 'var(--dash-primary)' : 'var(--dash-danger)'; const permPerColor = permPer >= 50 ? 'var(--dash-primary)' : 'var(--dash-danger)'; mainContent.innerHTML = `
구독 소프트웨어 사용정보
${subQty}개의 제품 중 ${subUsed}개 사용 중
${subPer}%
${subBadge}
영구 소프트웨어 사용정보
${permQty}개의 제품 중 ${permUsed}개 사용 중
${permPer}%
${permBadge}
구독 SW 만료 예정
30일 이내에 ${subTotal}개의 제품 중 ${subExp}개 만료 예정
${subExp}개
${subExpPer}%
유지보수 만료 예정
30일 이내에 ${permTotal}개의 제품 중 ${permExp}개 만료 예정
${permExp}개
${permExpPer}%
`; // SW 사용정보 카드 클릭 이벤트 - 사용 가능 소프트웨어 목록 표시 const subSwList = masterData.sw.filter(sw => sw.type === '구독SW'); const permSwList = masterData.sw.filter(sw => sw.type === '영구SW'); mainContent.querySelector('[data-action="sub-usage"]')?.addEventListener('click', () => { openSwUsageDetail('구독 소프트웨어 사용 목록', subSwList); }); mainContent.querySelector('[data-action="perm-usage"]')?.addEventListener('click', () => { openSwUsageDetail('영구 소프트웨어 사용 목록', permSwList); }); // SW 만료 카드 클릭 이벤트 const expSubList = masterData.sw.filter(sw => sw.type === '구독SW' && isSWExpiring(sw)); const expPermList = masterData.sw.filter(sw => sw.type === '영구SW' && isSWExpiring(sw)); mainContent.querySelector('[data-action="sub-exp"]')?.addEventListener('click', () => { openSwDashboardDetail('구독 SW 만료 예정 목록', expSubList); }); mainContent.querySelector('[data-action="perm-exp"]')?.addEventListener('click', () => { openSwDashboardDetail('유지보수 만료 예정 목록', expPermList); }); } } function renderTable() { const container = document.createElement('div'); container.className = 'table-container'; const table = document.createElement('table'); if (activeCategory === 'hw') { const list = masterData.hw.filter(a => a.type === activeSubTab); // Header if (activeSubTab === '개인PC') { table.innerHTML = ` No법인자산코드사용자 위치CPUGPURAM SSD1SSD2HDD1HDD2 구매일금액납품업체품의서관리 `; container.appendChild(table); mainContent.appendChild(container); const tbody = document.getElementById('dynamic-tbody')!; if (list.length === 0) { tbody.innerHTML = `등록된 [${activeSubTab}] 자산이 없습니다.`; return; } list.forEach((asset, idx) => { const tr = document.createElement('tr'); tr.style.cursor = 'pointer'; tr.innerHTML = ` ${idx + 1}${asset.법인}${asset.자산코드}${asset.사용자||''} ${asset.위치||''}${asset.CPU||''}${asset.GPU||''}${asset.RAM||''} ${asset.SSD1||'-'}${asset.SSD2||'-'}${asset.HDD1||'-'}${asset.HDD2||'-'} ${asset.구매일||''} ${asset.금액 ? Number(asset.금액.replace(/,/g, '')).toLocaleString() : ''} ${asset.납품업체||''} ${asset.품의서명 ? `` : '-'} `; tr.addEventListener('click', (e) => { if ((e.target as HTMLElement).tagName.toLowerCase() === 'button') return; openPcModal(asset); }); tr.querySelector('.btn-edit')?.addEventListener('click', () => openPcModal(asset)); tbody.appendChild(tr); }); } else if (activeSubTab === '스토리지') { table.innerHTML = ` No법인유형자산코드명칭 위치모델명용량담당자(정)담당자(부) IP주소MAC 주소 구매일금액납품업체품의서관리 `; container.appendChild(table); mainContent.appendChild(container); const tbody = document.getElementById('dynamic-tbody')!; if (list.length === 0) { tbody.innerHTML = `등록된 [${activeSubTab}] 자산이 없습니다.`; return; } list.forEach((asset, idx) => { const tr = document.createElement('tr'); tr.style.cursor = 'pointer'; tr.innerHTML = ` ${idx + 1}${asset.법인}${asset.storage유형||''}${asset.자산코드}${asset.명칭} ${asset.위치||''}${asset.모델명||''}${asset.용량||''}${asset.담당자_정||''}${asset.담당자_부||''} ${asset.IP주소||''}${asset.MACaddress||''} ${asset.구매일||''} ${asset.금액 ? Number(asset.금액.replace(/,/g, '')).toLocaleString() : ''} ${asset.납품업체||''} ${asset.품의서명 ? `` : '-'} `; tr.addEventListener('click', (e) => { if ((e.target as HTMLElement).tagName.toLowerCase() === 'button') return; openStorageModal(asset); }); tr.querySelector('.btn-edit')?.addEventListener('click', () => openStorageModal(asset)); tbody.appendChild(tr); }); } else { table.innerHTML = ` No법인 ${activeSubTab === '전산비품' ? '유형' : ''} 자산코드명칭 위치관리자IP주소MAC 주소 H/W 사양OS 구매일금액납품업체품의서관리 `; container.appendChild(table); mainContent.appendChild(container); const tbody = document.getElementById('dynamic-tbody')!; if (list.length === 0) { tbody.innerHTML = `등록된 [${activeSubTab}] 자산이 없습니다.`; return; } list.forEach((asset, idx) => { const tr = document.createElement('tr'); tr.style.cursor = 'pointer'; tr.innerHTML = ` ${idx + 1}${asset.법인} ${activeSubTab === '전산비품' ? `${asset.비품유형||'-'}` : ''} ${asset.자산코드}${asset.명칭} ${asset.위치}${asset.관리자}${asset.IP주소}${asset.MACaddress} ${asset.HW사양} ${asset.OS} ${asset.구매일||''} ${asset.금액 ? Number(asset.금액.replace(/,/g, '')).toLocaleString() : ''} ${asset.납품업체||''} ${asset.품의서명 ? `` : '-'} `; tr.addEventListener('click', (e) => { if ((e.target as HTMLElement).tagName.toLowerCase() === 'button') return; openHwModal(asset); }); tr.querySelector('.btn-edit')?.addEventListener('click', () => openHwModal(asset)); tbody.appendChild(tr); }); } } else { // SW const list = masterData.sw.filter(a => a.type === activeSubTab); // Header let theadHTML = ''; if (activeSubTab === '구독SW') { theadHTML = ` No법인제품명구매일구독일 금액수량사용가능계정명납품업체비고관리 `; } else { theadHTML = ` No법인제품명구매일유지보수 금액수량사용가능계정명납품업체비고관리 `; } table.innerHTML = ` ${theadHTML} `; container.appendChild(table); mainContent.appendChild(container); const tbody = document.getElementById('dynamic-tbody')!; if (list.length === 0) { tbody.innerHTML = `등록된 [${activeSubTab}] 정보가 없습니다.`; createIcons({ icons: { Download, Upload, FileSpreadsheet, Plus, X, LayoutDashboard, Monitor, Server, Database, Mouse, CalendarClock, Key, Cpu, Layers, Users, Paperclip, Edit2 } }); return; } list.forEach((asset, idx) => { const assignedUsers = masterData.swUsers.filter(u => u.swId === asset.id); const available = asset.수량 - assignedUsers.length; const tr = document.createElement('tr'); tr.style.cursor = 'pointer'; let middleRowHTML = ''; if (activeSubTab === '구독SW') { middleRowHTML = `${asset.구독일||''}`; } else { middleRowHTML = `${asset.유지보수여부 ? 'Y' : 'N'}`; } tr.innerHTML = ` ${idx + 1} ${asset.법인} ${asset.제품명} ${asset.구매일||''} ${middleRowHTML} ${asset.금액 ? Number(asset.금액.replace(/,/g, '')).toLocaleString() : ''} ${asset.수량} ${available} ${asset.계정명||''} ${asset.납품업체||''} ${asset.비고||''} `; tr.addEventListener('click', (e) => { const target = e.target as HTMLElement; if (target.tagName.toLowerCase() === 'button' || target.closest('button')) return; openSwModal(asset); }); tr.querySelector('.btn-edit')?.addEventListener('click', () => openSwModal(asset)); tr.querySelector('.btn-users')?.addEventListener('click', () => openSwUserModal(asset)); tbody.appendChild(tr); }); } // BUG FIX: initialize icons for newly inserted DOM blocks createIcons({ icons: { Download, Upload, FileSpreadsheet, Plus, X, LayoutDashboard, Monitor, Server, Database, Mouse, CalendarClock, Key, Cpu, Layers, Users, Paperclip, Edit2 } }); } // --- Modals --- function closeModals() { hwModal.classList.add('hidden'); swModal.classList.add('hidden'); pcModal.classList.add('hidden'); storageModal.classList.add('hidden'); document.getElementById('sw-user-modal')?.classList.add('hidden'); document.getElementById('sw-user-edit-modal')?.classList.add('hidden'); } document.querySelectorAll('.btn-icon, #btn-cancel-hw-modal, #btn-cancel-sw-modal, #btn-cancel-pc-modal, #btn-cancel-storage-modal').forEach(btn => { btn.addEventListener('click', closeModals); }); // ESC키, 외부클릭 닫기 window.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeModals(); }); document.querySelectorAll('.modal-overlay').forEach(overlay => { overlay.addEventListener('click', (e) => { if (e.target === overlay) closeModals(); }); }); // Add Element Button btnAddAsset.addEventListener('click', () => { if (activeSubTab === '대시보드') return; if (activeCategory === 'hw') { if (activeSubTab === '개인PC') { openPcModal(); } else if (activeSubTab === '스토리지') { openStorageModal(); } else { openHwModal(); } } else { openSwModal(); } }); // HW Modal open logic function openHwModal(asset?: HardwareAsset) { hwModal.classList.remove('hidden'); hwForm.reset(); const deleteBtn = document.getElementById('btn-delete-hw-asset')!; if (asset) { document.getElementById('hw-modal-title')!.textContent = '자산 상세 정보 수정'; deleteBtn.style.display = 'block'; (document.getElementById('hw-asset-id') as HTMLInputElement).value = asset.id; (document.getElementById('hw-asset-type') as HTMLInputElement).value = asset.type; (document.getElementById('hw-법인') as HTMLInputElement).value = asset.법인; (document.getElementById('hw-자산코드') as HTMLInputElement).value = asset.자산코드; (document.getElementById('hw-명칭') as HTMLInputElement).value = asset.명칭; (document.getElementById('hw-위치') as HTMLInputElement).value = asset.위치; (document.getElementById('hw-관리자') as HTMLInputElement).value = asset.관리자; (document.getElementById('hw-IP주소') as HTMLInputElement).value = asset.IP주소; (document.getElementById('hw-MACaddress') as HTMLInputElement).value = asset.MACaddress; (document.getElementById('hw-OS') as HTMLInputElement).value = asset.OS; (document.getElementById('hw-HW사양') as HTMLTextAreaElement).value = asset.HW사양; (document.getElementById('hw-구매일') as HTMLInputElement).value = asset.구매일 || ''; (document.getElementById('hw-금액') as HTMLInputElement).value = asset.금액 ? Number(asset.금액.replace(/,/g, '')).toLocaleString() : ''; (document.getElementById('hw-납품업체') as HTMLInputElement).value = asset.납품업체 || ''; (document.getElementById('hw-품의서명') as HTMLElement).innerText = asset.품의서명 ? `📎${asset.품의서명}` : ''; (document.getElementById('hw-비품유형') as HTMLSelectElement).value = asset.비품유형 || '노트북'; } else { document.getElementById('hw-modal-title')!.textContent = `새 ${activeSubTab} 자산 추가`; deleteBtn.style.display = 'none'; (document.getElementById('hw-asset-id') as HTMLInputElement).value = ''; (document.getElementById('hw-asset-type') as HTMLInputElement).value = activeSubTab; (document.getElementById('hw-품의서명') as HTMLElement).innerText = ''; (document.getElementById('hw-비품유형') as HTMLSelectElement).value = '노트북'; } if (activeSubTab === '전산비품') { document.getElementById('hw-비품유형-group')!.style.display = 'block'; } else { document.getElementById('hw-비품유형-group')!.style.display = 'none'; } } // PC Modal open logic function openPcModal(asset?: HardwareAsset) { pcModal.classList.remove('hidden'); pcForm.reset(); const deleteBtn = document.getElementById('btn-delete-pc-asset')!; if (asset) { document.getElementById('pc-modal-title')!.textContent = '개인PC 상세 정보 수정'; deleteBtn.style.display = 'block'; (document.getElementById('pc-asset-id') as HTMLInputElement).value = asset.id; (document.getElementById('pc-법인') as HTMLSelectElement).value = asset.법인; (document.getElementById('pc-자산코드') as HTMLInputElement).value = asset.자산코드; (document.getElementById('pc-사용자') as HTMLInputElement).value = asset.사용자 || ''; (document.getElementById('pc-위치') as HTMLInputElement).value = asset.위치 || ''; (document.getElementById('pc-CPU') as HTMLInputElement).value = asset.CPU || ''; (document.getElementById('pc-GPU') as HTMLInputElement).value = asset.GPU || ''; (document.getElementById('pc-RAM') as HTMLInputElement).value = asset.RAM || ''; (document.getElementById('pc-SSD1') as HTMLInputElement).value = asset.SSD1 || ''; (document.getElementById('pc-SSD2') as HTMLInputElement).value = asset.SSD2 || ''; (document.getElementById('pc-HDD1') as HTMLInputElement).value = asset.HDD1 || ''; (document.getElementById('pc-HDD2') as HTMLInputElement).value = asset.HDD2 || ''; (document.getElementById('pc-구매일') as HTMLInputElement).value = asset.구매일 || ''; (document.getElementById('pc-금액') as HTMLInputElement).value = asset.금액 ? Number(asset.금액.replace(/,/g, '')).toLocaleString() : ''; (document.getElementById('pc-납품업체') as HTMLInputElement).value = asset.납품업체 || ''; (document.getElementById('pc-품의서명') as HTMLElement).innerText = asset.품의서명 ? `📎${asset.품의서명}` : ''; } else { document.getElementById('pc-modal-title')!.textContent = '새 개인PC 자산 추가'; deleteBtn.style.display = 'none'; (document.getElementById('pc-asset-id') as HTMLInputElement).value = ''; (document.getElementById('pc-법인') as HTMLSelectElement).value = '한맥'; (document.getElementById('pc-품의서명') as HTMLElement).innerText = ''; } } // Storage Modal open logic function openStorageModal(asset?: HardwareAsset) { storageModal.classList.remove('hidden'); storageForm.reset(); const deleteBtn = document.getElementById('btn-delete-storage-asset')!; if (asset) { document.getElementById('storage-modal-title')!.textContent = '스토리지 상세 정보 수정'; deleteBtn.style.display = 'block'; (document.getElementById('storage-asset-id') as HTMLInputElement).value = asset.id; (document.getElementById('storage-법인') as HTMLSelectElement).value = asset.법인; (document.getElementById('storage-유형') as HTMLSelectElement).value = asset.storage유형 || 'NAS'; (document.getElementById('storage-자산코드') as HTMLInputElement).value = asset.자산코드; (document.getElementById('storage-명칭') as HTMLInputElement).value = asset.명칭; (document.getElementById('storage-위치') as HTMLInputElement).value = asset.위치 || ''; (document.getElementById('storage-모델명') as HTMLInputElement).value = asset.모델명 || ''; (document.getElementById('storage-용량') as HTMLInputElement).value = asset.용량 || ''; (document.getElementById('storage-담당자_정') as HTMLInputElement).value = asset.담당자_정 || ''; (document.getElementById('storage-담당자_부') as HTMLInputElement).value = asset.담당자_부 || ''; (document.getElementById('storage-IP주소') as HTMLInputElement).value = asset.IP주소 || ''; (document.getElementById('storage-MAC주소') as HTMLInputElement).value = asset.MACaddress || ''; (document.getElementById('storage-구매일') as HTMLInputElement).value = asset.구매일 || ''; (document.getElementById('storage-금액') as HTMLInputElement).value = asset.금액 ? Number(asset.금액.replace(/,/g, '')).toLocaleString() : ''; (document.getElementById('storage-납품업체') as HTMLInputElement).value = asset.납품업체 || ''; (document.getElementById('storage-품의서명') as HTMLElement).innerText = asset.품의서명 ? `📎${asset.품의서명}` : ''; } else { document.getElementById('storage-modal-title')!.textContent = '새 스토리지 자산 추가'; deleteBtn.style.display = 'none'; (document.getElementById('storage-asset-id') as HTMLInputElement).value = ''; (document.getElementById('storage-법인') as HTMLSelectElement).value = '한맥'; (document.getElementById('storage-유형') as HTMLSelectElement).value = 'NAS'; (document.getElementById('storage-품의서명') as HTMLElement).innerText = ''; } } // SW Modal open logic function openSwModal(asset?: SoftwareAsset) { swModal.classList.remove('hidden'); swForm.reset(); const deleteBtn = document.getElementById('btn-delete-sw-asset')!; const subGroup = document.getElementById('sw-구독일-group')!; const permGroup = document.getElementById('sw-유지보수-group')!; if (activeSubTab === '구독SW') { subGroup.style.display = 'block'; permGroup.style.display = 'none'; } else { subGroup.style.display = 'none'; permGroup.style.display = 'block'; } if (asset) { document.getElementById('sw-modal-title')!.textContent = `${activeSubTab} 상세 정보 수정`; deleteBtn.style.display = 'block'; (document.getElementById('sw-asset-id') as HTMLInputElement).value = asset.id; (document.getElementById('sw-asset-type') as HTMLInputElement).value = asset.type; (document.getElementById('sw-법인') as HTMLSelectElement).value = asset.법인; (document.getElementById('sw-제품명') as HTMLInputElement).value = asset.제품명; (document.getElementById('sw-구매일') as HTMLInputElement).value = asset.구매일 || ''; (document.getElementById('sw-구독일') as HTMLInputElement).value = asset.구독일 || ''; (document.getElementById('sw-유지보수여부') as HTMLInputElement).checked = !!asset.유지보수여부; (document.getElementById('sw-금액') as HTMLInputElement).value = asset.금액 ? Number(asset.금액.replace(/,/g, '')).toLocaleString() : ''; (document.getElementById('sw-수량') as HTMLInputElement).value = String(asset.수량); (document.getElementById('sw-계정명') as HTMLInputElement).value = asset.계정명 || ''; (document.getElementById('sw-납품업체') as HTMLInputElement).value = asset.납품업체 || ''; (document.getElementById('sw-비고') as HTMLInputElement).value = asset.비고 || ''; } else { document.getElementById('sw-modal-title')!.textContent = `새 ${activeSubTab} 자산 추가`; deleteBtn.style.display = 'none'; (document.getElementById('sw-asset-id') as HTMLInputElement).value = ''; (document.getElementById('sw-asset-type') as HTMLInputElement).value = activeSubTab; (document.getElementById('sw-법인') as HTMLSelectElement).value = '한맥'; } } // HW Save document.getElementById('btn-save-hw-asset')?.addEventListener('click', (e) => { e.preventDefault(); if (!hwForm.checkValidity()) { hwForm.reportValidity(); return; } const id = (document.getElementById('hw-asset-id') as HTMLInputElement).value; const fileInput = document.getElementById('hw-품의서') as HTMLInputElement; const 품의서명 = fileInput.files && fileInput.files.length > 0 ? fileInput.files[0].name : (document.getElementById('hw-품의서명') as HTMLElement).innerText.replace('📎', ''); const newAsset: HardwareAsset = { id: id || Math.random().toString(36).substring(2, 9), type: (document.getElementById('hw-asset-type') as HTMLInputElement).value, 법인: (document.getElementById('hw-법인') as HTMLInputElement).value, 자산코드: (document.getElementById('hw-자산코드') as HTMLInputElement).value, 명칭: (document.getElementById('hw-명칭') as HTMLInputElement).value, 위치: (document.getElementById('hw-위치') as HTMLInputElement).value, 관리자: (document.getElementById('hw-관리자') as HTMLInputElement).value, IP주소: (document.getElementById('hw-IP주소') as HTMLInputElement).value, MACaddress: (document.getElementById('hw-MACaddress') as HTMLInputElement).value, OS: (document.getElementById('hw-OS') as HTMLInputElement).value, HW사양: (document.getElementById('hw-HW사양') as HTMLTextAreaElement).value, 구매일: (document.getElementById('hw-구매일') as HTMLInputElement).value, 금액: (document.getElementById('hw-금액') as HTMLInputElement).value, 납품업체: (document.getElementById('hw-납품업체') as HTMLInputElement).value, 품의서명, 비품유형: (document.getElementById('hw-asset-type') as HTMLInputElement).value === '전산비품' ? (document.getElementById('hw-비품유형') as HTMLSelectElement).value : undefined }; if (id) { const idx = masterData.hw.findIndex(a => a.id === id); if(idx !== -1) masterData.hw[idx] = newAsset; } else { masterData.hw.push(newAsset); } closeModals(); renderContent(); }); // PC Save document.getElementById('btn-save-pc-asset')?.addEventListener('click', (e) => { e.preventDefault(); if (!pcForm.checkValidity()) { pcForm.reportValidity(); return; } const id = (document.getElementById('pc-asset-id') as HTMLInputElement).value; const fileInput = document.getElementById('pc-품의서') as HTMLInputElement; const 품의서명 = fileInput.files && fileInput.files.length > 0 ? fileInput.files[0].name : (document.getElementById('pc-품의서명') as HTMLElement).innerText.replace('📎', ''); const newAsset: HardwareAsset = { id: id || Math.random().toString(36).substring(2, 9), type: '개인PC', 법인: (document.getElementById('pc-법인') as HTMLSelectElement).value, 자산코드: (document.getElementById('pc-자산코드') as HTMLInputElement).value, 명칭: '', 위치: (document.getElementById('pc-위치') as HTMLInputElement).value, 관리자: '', IP주소: '', MACaddress: '', HW사양: '', OS: '', 사용자: (document.getElementById('pc-사용자') as HTMLInputElement).value, CPU: (document.getElementById('pc-CPU') as HTMLInputElement).value, GPU: (document.getElementById('pc-GPU') as HTMLInputElement).value, RAM: (document.getElementById('pc-RAM') as HTMLInputElement).value, SSD1: (document.getElementById('pc-SSD1') as HTMLInputElement).value, SSD2: (document.getElementById('pc-SSD2') as HTMLInputElement).value, HDD1: (document.getElementById('pc-HDD1') as HTMLInputElement).value, HDD2: (document.getElementById('pc-HDD2') as HTMLInputElement).value, 구매일: (document.getElementById('pc-구매일') as HTMLInputElement).value, 금액: (document.getElementById('pc-금액') as HTMLInputElement).value, 납품업체: (document.getElementById('pc-납품업체') as HTMLInputElement).value, 품의서명 }; if (id) { const idx = masterData.hw.findIndex(a => a.id === id); if(idx !== -1) masterData.hw[idx] = newAsset; } else { masterData.hw.push(newAsset); } closeModals(); renderContent(); }); // Storage Save document.getElementById('btn-save-storage-asset')?.addEventListener('click', (e) => { e.preventDefault(); if (!storageForm.checkValidity()) { storageForm.reportValidity(); return; } const id = (document.getElementById('storage-asset-id') as HTMLInputElement).value; const fileInput = document.getElementById('storage-품의서') as HTMLInputElement; const 품의서명 = fileInput.files && fileInput.files.length > 0 ? fileInput.files[0].name : (document.getElementById('storage-품의서명') as HTMLElement).innerText.replace('📎', ''); const newAsset: HardwareAsset = { id: id || Math.random().toString(36).substring(2, 9), type: '스토리지', 법인: (document.getElementById('storage-법인') as HTMLSelectElement).value, storage유형: (document.getElementById('storage-유형') as HTMLSelectElement).value, 자산코드: (document.getElementById('storage-자산코드') as HTMLInputElement).value, 명칭: (document.getElementById('storage-명칭') as HTMLInputElement).value, 위치: (document.getElementById('storage-위치') as HTMLInputElement).value, 관리자: '', IP주소: (document.getElementById('storage-IP주소') as HTMLInputElement).value, MACaddress: (document.getElementById('storage-MAC주소') as HTMLInputElement).value, HW사양: '', OS: '', 모델명: (document.getElementById('storage-모델명') as HTMLInputElement).value, 용량: (document.getElementById('storage-용량') as HTMLInputElement).value, 담당자_정: (document.getElementById('storage-담당자_정') as HTMLInputElement).value, 담당자_부: (document.getElementById('storage-담당자_부') as HTMLInputElement).value, 구매일: (document.getElementById('storage-구매일') as HTMLInputElement).value, 금액: (document.getElementById('storage-금액') as HTMLInputElement).value, 납품업체: (document.getElementById('storage-납품업체') as HTMLInputElement).value, 품의서명 }; if (id) { const idx = masterData.hw.findIndex(a => a.id === id); if(idx !== -1) masterData.hw[idx] = newAsset; } else { masterData.hw.push(newAsset); } closeModals(); renderContent(); }); // SW Save document.getElementById('btn-save-sw-asset')?.addEventListener('click', (e) => { e.preventDefault(); if (!swForm.checkValidity()) { swForm.reportValidity(); return; } const id = (document.getElementById('sw-asset-id') as HTMLInputElement).value; const newAsset: SoftwareAsset = { id: id || Math.random().toString(36).substring(2, 9), type: (document.getElementById('sw-asset-type') as HTMLInputElement).value, 법인: (document.getElementById('sw-법인') as HTMLSelectElement).value, 제품명: (document.getElementById('sw-제품명') as HTMLInputElement).value, 구매일: (document.getElementById('sw-구매일') as HTMLInputElement).value, 구독일: (document.getElementById('sw-구독일') as HTMLInputElement).value, 유지보수여부: (document.getElementById('sw-유지보수여부') as HTMLInputElement).checked, 금액: (document.getElementById('sw-금액') as HTMLInputElement).value, 수량: parseInt((document.getElementById('sw-수량') as HTMLInputElement).value || '1', 10), 계정명: (document.getElementById('sw-계정명') as HTMLInputElement).value, 납품업체: (document.getElementById('sw-납품업체') as HTMLInputElement).value, 비고: (document.getElementById('sw-비고') as HTMLInputElement).value, }; if (id) { const idx = masterData.sw.findIndex(a => a.id === id); if(idx !== -1) masterData.sw[idx] = newAsset; } else { masterData.sw.push(newAsset); } closeModals(); renderContent(); }); // Deletes document.getElementById('btn-delete-hw-asset')?.addEventListener('click', (e) => { e.preventDefault(); const id = (document.getElementById('hw-asset-id') as HTMLInputElement).value; if (confirm('삭제하시겠습니까?')) { masterData.hw = masterData.hw.filter(a => a.id !== id); closeModals(); renderContent(); } }); document.getElementById('btn-delete-pc-asset')?.addEventListener('click', (e) => { e.preventDefault(); const id = (document.getElementById('pc-asset-id') as HTMLInputElement).value; if (confirm('삭제하시겠습니까?')) { masterData.hw = masterData.hw.filter(a => a.id !== id); closeModals(); renderContent(); } }); document.getElementById('btn-delete-storage-asset')?.addEventListener('click', (e) => { e.preventDefault(); const id = (document.getElementById('storage-asset-id') as HTMLInputElement).value; if (confirm('삭제하시겠습니까?')) { masterData.hw = masterData.hw.filter(a => a.id !== id); closeModals(); renderContent(); } }); document.getElementById('btn-delete-sw-asset')?.addEventListener('click', (e) => { e.preventDefault(); const id = (document.getElementById('sw-asset-id') as HTMLInputElement).value; if (confirm('삭제하시겠습니까?')) { masterData.sw = masterData.sw.filter(a => a.id !== id); closeModals(); renderContent(); } }); // --- Excel Controls --- btnDownloadTemp.addEventListener('click', () => downloadTemplate()); btnExport.addEventListener('click', () => exportToExcel(masterData)); uploadInput.addEventListener('change', async (e) => { const file = (e.target as HTMLInputElement).files?.[0]; if (!file) return; try { masterData = await parseExcel(file); renderContent(); alert('모든 시트 데이터 연동 완료'); } catch (error) { console.error(error); alert('엑셀 파싱 오류: 템플릿 양식이 다르거나 파일이 손상되었습니다.'); } finally { uploadInput.value = ''; } }); // --- SW User Modal Logic --- const swUserModal = document.getElementById('sw-user-modal') as HTMLDivElement; const swUserEditModal = document.getElementById('sw-user-edit-modal') as HTMLDivElement; let currentSwUserAssetId: string = ''; let tempSwUsers: import("./excelHandler").SWUser[] = []; function renderUserList() { const tbody = document.getElementById('user-list-body')!; tbody.innerHTML = ''; if (tempSwUsers.length === 0) { tbody.innerHTML = '할당된 사용자가 없습니다.'; return; } tempSwUsers.forEach((user, idx) => { const tr = document.createElement('tr'); tr.style.cssText = 'border-bottom: 1px solid var(--border); transition: background-color 0.2s;'; tr.addEventListener('mouseenter', () => tr.style.backgroundColor = 'var(--bg-light)'); tr.addEventListener('mouseleave', () => tr.style.backgroundColor = 'transparent'); // 법인, 부서/팀, 직위, 이름, 사용기간, 첨부파일, 관리 const deptTeam = [user.부서, user.팀].filter(Boolean).join(' / ') || '-'; const attachIcon = user.신청서명 ? `` : '-'; tr.innerHTML = ` ${user.법인} ${deptTeam} ${user.직위 || '-'} ${user.이름} ${user.사용기간 || '-'} ${attachIcon} `; tbody.appendChild(tr); }); createIcons({ icons: { Download, Upload, FileSpreadsheet, Plus, X, LayoutDashboard, Monitor, Server, Database, Mouse, CalendarClock, Key, Cpu, Layers, Users, Paperclip, Edit2 } }); tbody.querySelectorAll('.btn-edit-user').forEach(btn => { btn.addEventListener('click', (e) => { const idx = parseInt((e.currentTarget as HTMLElement).getAttribute('data-idx')!); openUserEditModal(idx); }); }); tbody.querySelectorAll('.btn-remove-user').forEach(btn => { btn.addEventListener('click', (e) => { e.stopPropagation(); const idx = parseInt((e.currentTarget as HTMLButtonElement).getAttribute('data-idx')!); tempSwUsers.splice(idx, 1); renderUserList(); }); }); } function openSwUserModal(asset: SoftwareAsset) { swUserModal.classList.remove('hidden'); currentSwUserAssetId = asset.id; tempSwUsers = masterData.swUsers.filter(u => u.swId === asset.id).map(u => ({...u})); renderUserList(); } function openUserEditModal(idx: number) { swUserEditModal.classList.remove('hidden'); (document.getElementById('edit-user-idx') as HTMLInputElement).value = String(idx); if (idx === -1) { document.getElementById('sw-user-edit-modal-title')!.innerText = '새 사용자 추가'; (document.getElementById('new-user-법인') as HTMLSelectElement).value = '한맥'; (document.getElementById('new-user-부서') as HTMLInputElement).value = ''; (document.getElementById('new-user-팀') as HTMLInputElement).value = ''; (document.getElementById('new-user-직위') as HTMLInputElement).value = ''; (document.getElementById('new-user-이름') as HTMLInputElement).value = ''; (document.getElementById('new-user-사용기간') as HTMLInputElement).value = ''; (document.getElementById('new-user-신청서') as HTMLInputElement).value = ''; document.getElementById('new-user-신청서명')!.innerText = ''; } else { document.getElementById('sw-user-edit-modal-title')!.innerText = '사용자 수정'; const u = tempSwUsers[idx]; (document.getElementById('new-user-법인') as HTMLSelectElement).value = u.법인; (document.getElementById('new-user-부서') as HTMLInputElement).value = u.부서; (document.getElementById('new-user-팀') as HTMLInputElement).value = u.팀; (document.getElementById('new-user-직위') as HTMLInputElement).value = u.직위; (document.getElementById('new-user-이름') as HTMLInputElement).value = u.이름; (document.getElementById('new-user-사용기간') as HTMLInputElement).value = u.사용기간; (document.getElementById('new-user-신청서') as HTMLInputElement).value = ''; // file input cannot be programmatically set document.getElementById('new-user-신청서명')!.innerText = u.신청서명 ? `📎${u.신청서명}` : ''; } } document.getElementById('btn-open-add-user')?.addEventListener('click', () => { openUserEditModal(-1); }); document.getElementById('btn-save-edit-user')?.addEventListener('click', () => { const idx = parseInt((document.getElementById('edit-user-idx') as HTMLInputElement).value); const 법인 = (document.getElementById('new-user-법인') as HTMLSelectElement).value; const 부서 = (document.getElementById('new-user-부서') as HTMLInputElement).value; const 팀 = (document.getElementById('new-user-팀') as HTMLInputElement).value; const 직위 = (document.getElementById('new-user-직위') as HTMLInputElement).value; const 이름 = (document.getElementById('new-user-이름') as HTMLInputElement).value.trim(); const 사용기간 = (document.getElementById('new-user-사용기간') as HTMLInputElement).value; const fileInput = document.getElementById('new-user-신청서') as HTMLInputElement; let 신청서명 = ''; if (fileInput.files && fileInput.files.length > 0) { 신청서명 = fileInput.files[0].name; } else if (idx !== -1) { // Keep existing file if not changed 신청서명 = tempSwUsers[idx].신청서명; } if (!이름) { alert('이름을 입력해주세요.'); return; } if (idx === -1) { tempSwUsers.push({ id: Math.random().toString(36).substring(2, 9), swId: currentSwUserAssetId, 법인, 부서, 팀, 직위, 이름, 사용기간, 신청서명 }); } else { tempSwUsers[idx] = { ...tempSwUsers[idx], 법인, 부서, 팀, 직위, 이름, 사용기간, 신청서명 }; } swUserEditModal.classList.add('hidden'); renderUserList(); }); document.getElementById('btn-cancel-sw-user-edit')?.addEventListener('click', () => { swUserEditModal.classList.add('hidden'); }); document.getElementById('btn-close-sw-user-edit-modal')?.addEventListener('click', () => { swUserEditModal.classList.add('hidden'); }); document.getElementById('btn-save-sw-user-mapping')?.addEventListener('click', () => { // Remove existing mappings for this asset masterData.swUsers = masterData.swUsers.filter(u => u.swId !== currentSwUserAssetId); // Add updated mappings masterData.swUsers.push(...tempSwUsers); swUserModal.classList.add('hidden'); renderContent(); }); document.getElementById('btn-cancel-sw-user-modal')?.addEventListener('click', () => { swUserModal.classList.add('hidden'); }); document.getElementById('btn-close-sw-user-modal')?.addEventListener('click', () => { swUserModal.classList.add('hidden'); }); // Initial Render renderContent();