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]}
`;
});
// 노후화 카드 생성
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]}
`;
});
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}개 사용 중
영구 소프트웨어 사용정보
${permQty}개의 제품 중 ${permUsed}개 사용 중
구독 SW 만료 예정
30일 이내에 ${subTotal}개의 제품 중 ${subExp}개 만료 예정
${subExp}개
유지보수 만료 예정
30일 이내에 ${permTotal}개의 제품 중 ${permExp}개 만료 예정
${permExp}개
`;
// 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 | 법인 | 자산코드 | 사용자 |
위치 | CPU | GPU | RAM |
SSD1 | SSD2 | HDD1 | HDD2 |
구매일 | 금액 | 납품업체 | 품의서 | 관리 |
`;
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();