diff --git a/src/components/Modal/UploadPreviewModal.ts b/src/components/Modal/UploadPreviewModal.ts new file mode 100644 index 0000000..ff8ffaf --- /dev/null +++ b/src/components/Modal/UploadPreviewModal.ts @@ -0,0 +1,224 @@ +import { openModal, closeModals } from './BaseModal'; +import { createIcons, X, Check, Database, Save, FileSpreadsheet, Layers } from 'lucide'; +import { state, loadMasterDataFromDB } from '../../core/state'; + +let parsedData: any = null; +let currentTab: string = ''; +let onSuccessCallback: (() => void) | null = null; + +const UPLOAD_PREVIEW_MODAL_HTML = ` + +`; + +export function initUploadPreviewModal(onSuccess?: () => void) { + if (onSuccess) onSuccessCallback = onSuccess; + if (!document.getElementById('upload-preview-modal')) { + document.body.insertAdjacentHTML('beforeend', UPLOAD_PREVIEW_MODAL_HTML); + } + + document.getElementById('btn-close-upload-preview')?.addEventListener('click', closeModals); + document.getElementById('btn-cancel-upload')?.addEventListener('click', closeModals); + document.getElementById('btn-confirm-upload')?.addEventListener('click', () => { + confirmUpload(); + }); +} + +export function openUploadPreview(data: any) { + parsedData = data; + const tabNames = Object.keys(data); + if (tabNames.length === 0) { + alert('업로드할 데이터가 없습니다.'); + return; + } + + currentTab = tabNames[0]; + renderTabs(); + renderCurrentTable(); + + openModal('upload-preview-modal'); + createIcons({ icons: { X, Check, Database, Save, FileSpreadsheet, Layers } }); +} + +function renderTabs() { + const container = document.getElementById('upload-tabs-container'); + if (!container) return; + container.innerHTML = ''; + + Object.keys(parsedData).forEach(tab => { + const btn = document.createElement('div'); + btn.className = `upload-tab-btn ${tab === currentTab ? 'active' : ''}`; + btn.style.cssText = ` + padding: 0.75rem 1rem; + border-radius: 8px; + cursor: pointer; + font-size: 13px; + font-weight: 500; + display: flex; + justify-content: space-between; + align-items: center; + transition: all 0.2s; + background: ${tab === currentTab ? 'white' : 'transparent'}; + color: ${tab === currentTab ? 'var(--primary-color)' : 'var(--text-main)'}; + box-shadow: ${tab === currentTab ? '0 2px 4px rgba(0,0,0,0.05)' : 'none'}; + border: 1px solid ${tab === currentTab ? 'var(--border-color)' : 'transparent'}; + `; + + btn.innerHTML = ` + ${tab} + ${parsedData[tab].length} + `; + + btn.onclick = () => { + currentTab = tab; + renderTabs(); + renderCurrentTable(); + }; + container.appendChild(btn); + }); +} + +function renderCurrentTable() { + const tableWrapper = document.getElementById('upload-preview-table-wrapper'); + const tabNameEl = document.getElementById('current-tab-name'); + const tabCountEl = document.getElementById('current-tab-count'); + if (!tableWrapper || !tabNameEl || !tabCountEl) return; + + const data = parsedData[currentTab]; + tabNameEl.textContent = currentTab; + tabCountEl.textContent = `${data.length}건`; + + if (!data || data.length === 0) { + tableWrapper.innerHTML = '
표시할 데이터가 없습니다.
'; + return; + } + + // Get headers from first item keys, excluding 'id' and 'type' for cleaner view + const headers = Object.keys(data[0]).filter(k => k !== 'id' && k !== 'type'); + + let tableHTML = ` + + + + + ${headers.map(h => ``).join('')} + + + + ${data.map((row: any, idx: number) => ` + + + ${headers.map(h => ``).join('')} + + `).join('')} + +
No.${h}
${idx + 1}${row[h] || '-'}
+ `; + + tableWrapper.innerHTML = tableHTML; +} + +async function confirmUpload() { + const confirmBtn = document.getElementById('btn-confirm-upload') as HTMLButtonElement; + if (confirmBtn) { + confirmBtn.disabled = true; + confirmBtn.innerHTML = ' 저장 중...'; + createIcons({ icons: { Save } }); + } + + try { + const tabNames = Object.keys(parsedData); + let successCount = 0; + + for (const tab of tabNames) { + const data = parsedData[tab]; + let endpoint = ''; + + if (tab === '개인PC') endpoint = '/api/pc/batch'; + else if (tab === '서버') endpoint = '/api/server/batch'; + else if (tab === '스토리지') endpoint = '/api/storage/batch'; + else if (tab === '전산비품') endpoint = '/api/equip/batch'; + else if (tab === '모바일기기') endpoint = '/api/mobile/batch'; + else if (tab === '구독SW') endpoint = '/api/sw/sub/batch'; + else if (tab === '영구SW') endpoint = '/api/sw/perm/batch'; + else if (tab === '클라우드') endpoint = '/api/sw/cloud/batch'; + else if (tab === '도메인') endpoint = '/api/ops/domain/batch'; + + if (endpoint) { + const response = await fetch(endpoint, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }); + if (response.ok) successCount++; + } + } + + if (successCount > 0) { + if (onSuccessCallback) onSuccessCallback(); + closeModals(); + alert(`${successCount}개 카테고리의 데이터가 성공적으로 업로드되었습니다.`); + } else { + alert('데이터 업로드에 실패했습니다.'); + } + } catch (err) { + console.error(err); + alert('업로드 중 오류가 발생했습니다.'); + } finally { + if (confirmBtn) { + confirmBtn.disabled = false; + confirmBtn.innerHTML = ' 최종 데이터 저장하기'; + createIcons({ icons: { Save } }); + } + } +} diff --git a/src/core/excelHandler.ts b/src/core/excelHandler.ts index c536d3a..8598da0 100644 --- a/src/core/excelHandler.ts +++ b/src/core/excelHandler.ts @@ -3,74 +3,35 @@ import * as XLSX from 'xlsx'; export interface HardwareAsset { [key: string]: any; id: string; - type: string; // '개인PC', '서버', '스토리지', '전산비품', '모바일기기' + type: string; 법인: string; 자산코드: string; 명칭: string; 위치: string; 관리자: string; IP주소: string; - IP2?: string; MACaddress: string; HW사양: string; OS: string; - 사용자?: string; - CPU?: string; - GPU?: string; - RAM?: string; - SSD1?: string; - SSD2?: string; - HDD1?: string; - HDD2?: string; - storage유형?: string; - 비품유형?: string; - 모델명?: string; - 용량?: string; - 담당자_정?: string; - 담당자_부?: string; - 구매연월?: string; 금액?: string; 납품업체: string; 품의서명: string; - 용도?: string; - 상세?: string; - 원격접속?: string; - 서버ID?: string; - 서버PW?: string; - 모니터링?: string; 비고?: string; - 현사용조직?: string; - 이전사용조직?: string; - 상세용도?: string; - 메인보드?: string; - 보관위치?: string; - 현재상태?: string; } export interface SoftwareAsset { [key: string]: any; id: string; - type: string; // '구독SW', '영구SW', '클라우드' + type: string; 분야?: string; 법인: string; 부서?: string; 제품명: string; - 구매연월?: string; - 구독일?: string; - 만료일?: string; - 라이선스유형?: string; - 라이선스키?: string; - 유지보수여부?: boolean; 금액: string; 수량: number; 계정명: string; 납품업체: string; 비고: string; - 플랫폼명?: string; - 결제수단?: string; - 결제일?: string; - 연결카드번호?: string; - 당월청구액?: string; } export interface SWUser { @@ -104,24 +65,24 @@ export interface MasterAssetData { subSw: SoftwareAsset[]; permSw: SoftwareAsset[]; cloud: SoftwareAsset[]; + domain?: any[]; hw: HardwareAsset[]; sw: SoftwareAsset[]; - swUsers: any[]; // { sw_id, userData: [] } 형태로 처리 + swUsers: SWUser[]; logs: HardwareLog[]; } -const HW_TABS = ['개인PC', '서버', '스토리지', '전산비품', '모바일기기']; -const SW_TABS = ['구독SW', '영구SW', '클라우드']; - const PC_HEADERS = ['법인', '자산코드', '사용자', '위치', '모델명', '메인보드', 'CPU', 'GPU', 'RAM', 'SSD1', 'SSD2', 'HDD1', 'HDD2', 'IP주소', 'HW사양', '구매연월', '금액', '납품업체', '품의서명', '비고']; -const SERVER_HEADERS = ['구매법인', '자산번호', '구매연월', '유형', '용도', '상세내용', '현사용조직', '이전사용조직', '설치위치', '담당자(정)', '담당자(부)', 'IP 주소 1', 'IP 주소 2', '원격도구', '서버 ID', '서버 PW', '모델명', 'OS', 'CPU', 'RAM', 'GPU', 'Storage 1', 'Storage 2', 'Storage 3', '모니터링', '비고']; -const STORAGE_HEADERS = ['구매법인', '유형', '자산코드', '명칭', '위치', '모델명', '용량', '담당자(정)', '담당자(부)', 'IP주소', 'MAC주소', '구매연월', '금액', '납품업체', '품의서명', '비고']; -const EQUIP_HEADERS = ['구매법인', '비품유형', '자산코드', '명칭', '위치', '관리자', 'IP주소', 'MACaddress', 'HW사양', 'OS', '구매연월', '금액', '납품업체', '품의서명', '비고']; -const MOBILE_HEADERS = ['구매법인', '자산코드', '명칭', '위치', '관리자', '기기유형', 'OS', '구매연월', '금액', '납품업체', '품의서명', '비고']; +const SERVER_HEADERS = ['법인', '자산코드', '구매연월', '유형', '용도', '상세내용', '현사용조직', '이전사용조직', '위치', '담당자(정)', '담당자(부)', 'IP 주소 1', 'IP 주소 2', '원격도구', '서버 ID', '서버 PW', '모델명', 'OS', 'CPU', 'RAM', 'GPU', 'Storage 1', 'Storage 2', 'Storage 3', '모니터링', '비고']; +const STORAGE_HEADERS = ['법인', '유형', '자산코드', '명칭', '위치', '모델명', '용량', '담당자(정)', '담당자(부)', 'IP주소', 'MAC주소', '구매연월', '금액', '납품업체', '품의서명', '비고']; +const EQUIP_HEADERS = ['법인', '비품유형', '자산코드', '명칭', '위치', '관리자', 'IP주소', 'MACaddress', 'HW사양', 'OS', '구매연월', '금액', '납품업체', '품의서명', '비고']; +const MOBILE_HEADERS = ['법인', '자산코드', '명칭', '위치', '관리자', '기기유형', 'OS', '구매연월', '금액', '납품업체', '품의서명', '비고']; -const SUB_SW_HEADERS = ['ID', '분야', '법인', '부서', '제품명', '구매연월', '만료일', '라이선스유형', '금액', '수량', '계정명', '납품업체', '비고']; -const PERM_SW_HEADERS = ['ID', '분야', '법인', '부서', '제품명', '구매연월', '만료일', '라이선스키', '금액', '수량', '계정명', '납품업체', '비고']; -const CLOUD_HEADERS = ['ID', '플랫폼명', '법인', '부서', '사용용도(제품명)', '계정명', '결제수단', '결제일', '연결카드번호', '당월청구액', '비고']; +const SUB_SW_HEADERS = ['분야', '법인', '제품명', '부서', '수량', '금액', '구매일', '납품업체', '시작일', '만료일', '라이선스유형', '계정명', '비고']; +const PERM_SW_HEADERS = ['분야', '법인', '제품명', '부서', '수량', '금액', '구매일', '납품업체', '시작일', '만료일', '라이선스키', '계정명', '비고']; +const CLOUD_HEADERS = ['플랫폼명', '법인', '제품명', '부서', '계정명', '결제수단', '결제일', '연결카드번호', '당월청구액', '비고']; + +const DOMAIN_HEADERS = ['유형', '법인', '서비스명', '관리도메인', '시작일', '만료일', '금액', '담당자', '담당자(부)', '비고']; export function downloadTemplate() { const wb = XLSX.utils.book_new(); @@ -130,7 +91,11 @@ export function downloadTemplate() { { name: '서버', headers: SERVER_HEADERS }, { name: '스토리지', headers: STORAGE_HEADERS }, { name: '전산비품', headers: EQUIP_HEADERS }, - { name: '모바일기기', headers: MOBILE_HEADERS } + { name: '모바일기기', headers: MOBILE_HEADERS }, + { name: '구독SW', headers: SUB_SW_HEADERS }, + { name: '영구SW', headers: PERM_SW_HEADERS }, + { name: '클라우드', headers: CLOUD_HEADERS }, + { name: '도메인', headers: DOMAIN_HEADERS } ]; tabConfigs.forEach(config => { @@ -139,61 +104,67 @@ export function downloadTemplate() { XLSX.utils.book_append_sheet(wb, ws, config.name); }); - SW_TABS.forEach(tab => { - let hd = tab === '구독SW' ? SUB_SW_HEADERS : (tab === '클라우드' ? CLOUD_HEADERS : PERM_SW_HEADERS); - const ws = XLSX.utils.aoa_to_sheet([hd]); - ws['!cols'] = Array(hd.length).fill({ wch: 18 }); - XLSX.utils.book_append_sheet(wb, ws, tab); - }); - - XLSX.writeFile(wb, 'itam_assets_template_full.xlsx'); + XLSX.writeFile(wb, 'itam_assets_template.xlsx'); } export function exportToExcel(masterData: MasterAssetData) { const wb = XLSX.utils.book_new(); const exportMap = [ - { tab: '개인PC', list: masterData.pc, headers: PC_HEADERS, map: (a: any) => [a.법인, a.자산코드, a.사용자, a.위치, a.모델명, a.메인보드, a.CPU, a.GPU, a.RAM, a.SSD1, a.SSD2, a.HDD1, a.HDD2, a.IP주소, a.HW사양, a.구매일||a.구매연월, a.금액, a.납품업체, a.품의서명, a.비고] }, - { tab: '서버', list: masterData.server, headers: SERVER_HEADERS, map: (a: any) => [a.법인, a.자산코드, a.구매일||a.구매연월, a.storage유형 || '물리', a.용도, a.상세, a.현사용조직, a.이전사용조직, a.위치, a.담당자_정, a.담당자_부, a.IP주소, a.IP2, a.원격접속, a.서버ID, a.서버PW, a.모델명, a.OS, a.CPU, a.RAM, a.GPU, a.SSD1, a.SSD2, a.HDD1, a.모니터링, a.비고] }, - { tab: '스토리지', list: masterData.storage, headers: STORAGE_HEADERS, map: (a: any) => [a.법인, a.storage유형, a.자산코드, a.명칭, a.위치, a.모델명, a.용량, a.담당자_정, a.담당자_부, a.IP주소, a.MACaddress, a.구매일||a.구매연월, a.금액, a.납품업체, a.품의서명, a.비고] }, - { tab: '전산비품', list: masterData.equip, headers: EQUIP_HEADERS, map: (a: any) => [a.법인, a.비품유형, a.자산코드, a.명칭, a.위치, a.관리자, a.IP주소, a.MACaddress, a.HW사양, a.OS, a.구매일||a.구매연월, a.금액, a.납품업체, a.품의서명, a.비고] }, - { tab: '모바일기기', list: masterData.mobile, headers: MOBILE_HEADERS, map: (a: any) => [a.법인, a.자산코드, a.명칭, a.위치, a.관리자, a.type, a.OS, a.구매일||a.구매연월, a.금액, a.납품업체, a.품의서명, a.비고] }, - { tab: '구독SW', list: masterData.subSw, headers: SUB_SW_HEADERS, map: (a: any) => [a.id, a.분야, a.법인, a.부서, a.제품명, a.구매일||a.구매연월, a.만료일, a.라이선스유형, a.금액, a.수량, a.계정명, a.납품업체, a.비고] }, - { tab: '영구SW', list: masterData.permSw, headers: PERM_SW_HEADERS, map: (a: any) => [a.id, a.분야, a.법인, a.부서, a.제품명, a.구매일||a.구매연월, a.만료일, a.라이선스키, a.금액, a.수량, a.계정명, a.납품업체, a.비고] } + { tab: '개인PC', list: masterData.pc, headers: PC_HEADERS, map: (a: any) => [a.법인, a.자산코드, a.사용자, a.위치, a.모델명, a.메인보드, a.CPU, a.GPU, a.RAM, a.SSD1, a.SSD2, a.HDD1, a.HDD2, a.IP주소, a.HW사양, a.구매연월, a.금액, a.납품업체, a.품의서명, a.비고] }, + { tab: '서버', list: masterData.server, headers: SERVER_HEADERS, map: (a: any) => [a.법인, a.자산코드, a.구매연월, a.storage유형, a.용도, a.상세, a.현사용조직, a.이전사용조직, a.위치, a.담당자_정, a.담당자_부, a.IP주소, a.IP2, a.원격접속, a.서버ID, a.서버PW, a.모델명, a.OS, a.CPU, a.RAM, a.GPU, a.SSD1, a.SSD2, a.HDD1, a.모니터링, a.비고] }, + { tab: '스토리지', list: masterData.storage, headers: STORAGE_HEADERS, map: (a: any) => [a.법인, a.storage유형, a.자산코드, a.명칭, a.위치, a.모델명, a.용량, a.담당자_정, a.담당자_부, a.IP주소, a.MACaddress, a.구매연월, a.금액, a.납품업체, a.품의서명, a.비고] }, + { tab: '전산비품', list: masterData.equip, headers: EQUIP_HEADERS, map: (a: any) => [a.법인, a.비품유형, a.자산코드, a.명칭, a.위치, a.관리자, a.IP주소, a.MACaddress, a.HW사양, a.OS, a.구매연월, a.금액, a.납품업체, a.품의서명, a.비고] }, + { tab: '모바일기기', list: masterData.mobile, headers: MOBILE_HEADERS, map: (a: any) => [a.법인, a.자산코드, a.명칭, a.위치, a.관리자, a.type, a.OS, a.구매연월, a.금액, a.납품업체, a.품의서명, a.비고] }, + { tab: '구독SW', list: masterData.subSw, headers: SUB_SW_HEADERS, map: (a: any) => [a.분야, a.법인, a.제품명, a.부서, a.수량, a.금액, a.구매일, a.납품업체, a.시작일, a.만료일, a.라이선스유형, a.계정명, a.비고] }, + { tab: '영구SW', list: masterData.permSw, headers: PERM_SW_HEADERS, map: (a: any) => [a.분야, a.법인, a.제품명, a.부서, a.수량, a.금액, a.구매일, a.납품업체, a.시작일, a.만료일, a.라이선스키, a.계정명, a.비고] }, + { tab: '클라우드', list: masterData.cloud, headers: CLOUD_HEADERS, map: (a: any) => [a.플랫폼명, a.법인, a.제품명, a.부서, a.계정명, a.결제수단, a.결제일, a.연결카드번호, a.당월청구액, a.비고] }, + { tab: '도메인', list: masterData.domain || [], headers: DOMAIN_HEADERS, map: (a: any) => [a.type, a.corp, a.service_name, a.domain_name, a.start_date, a.expiry_date, a.price, a.manager_main, a.manager_sub, a.remarks] } ]; exportMap.forEach(m => { const ws = XLSX.utils.aoa_to_sheet([m.headers, ...m.list.map(m.map)]); XLSX.utils.book_append_sheet(wb, ws, m.tab); }); - XLSX.writeFile(wb, `itam_master_full_${new Date().toISOString().split('T')[0]}.xlsx`); + XLSX.writeFile(wb, `itam_master_${new Date().toISOString().split('T')[0]}.xlsx`); } -export async function parseExcel(file: File): Promise { +export async function parseExcel(file: File): Promise { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = (e) => { try { const workbook = XLSX.read(e.target?.result, { type: 'binary' }); - const data: MasterAssetData = { pc: [], server: [], storage: [], equip: [], mobile: [], subSw: [], permSw: [], cloud: [], hw: [], sw: [], swUsers: [], logs: [] }; + const parsedData: any = {}; + workbook.SheetNames.forEach(sheetName => { const rows = XLSX.utils.sheet_to_json(workbook.Sheets[sheetName]) as any[]; - if (sheetName === '개인PC') { - rows.forEach(r => data.pc.push({ id: Math.random().toString(36).substring(2, 9), type: '개인PC', 법인: r['법인']||'', 자산코드: r['자산코드']||'', 사용자: r['사용자']||'', 위치: r['위치']||'', 모델명: r['모델명']||'', 메인보드: r['메인보드']||'', CPU: r['CPU']||'', GPU: r['GPU']||'', RAM: r['RAM']||'', SSD1: r['SSD1']||'', SSD2: r['SSD2']||'', HDD1: r['HDD1']||'', HDD2: r['HDD2']||'', IP주소: r['IP주소']||'', HW사양: r['HW사양']||'', 구매일: r['구매일']||r['구매연월']||'', 금액: r['금액']||'', 납품업체: r['납품업체']||'', 품의서명: r['품의서명']||'', 비고: r['비고']||'', 관리자: '', MACaddress: '', OS: '', 명칭: '' })); - } else if (sheetName === '서버') { - rows.forEach(r => data.server.push({ id: Math.random().toString(36).substring(2, 9), type: '서버', 법인: r['구매법인']||r['법인']||'', 자산코드: r['자산번호']||r['자산코드']||'', 구매연월: r['구매연월']||r['구매일자']||r['구매일']||'', storage유형: r['유형']||'물리', 용도: r['용도']||'', 상세: r['상세내용']||'', 현사용조직: r['현사용조직']||'', 이전사용조직: r['이전사용조직']||'', 위치: r['설치위치']||r['위치']||'', 담당자_정: r['담당자(정)']||'', 담당자_부: r['담당자(부)']||'', IP주소: r['IP 주소 1']||r['IP주소']||'', IP2: r['IP 주소 2']||'', 원격접속: r['원격도구']||r['원격접속']||'', 서버ID: r['서버 ID']||r['서버ID']||'', 서버PW: r['서버 PW']||r['서버PW']||'', 모델명: r['모델명']||'', OS: r['OS']||'', CPU: r['CPU']||'', RAM: r['RAM']||'', GPU: r['GPU']||'', SSD1: r['Storage 1']||r['SSD1']||'', SSD2: r['Storage 2']||r['SSD2']||'', HDD1: r['Storage 3']||r['HDD1']||'', 모니터링: r['모니터링']||'', 비고: r['비고']||'', 관리자: '', 명칭: '', MACaddress: '', HW사양: '', 금액: '', 납품업체: '', 품의서명: '' })); - } else if (sheetName === '스토리지') { - rows.forEach(r => data.storage.push({ id: Math.random().toString(36).substring(2, 9), type: '스토리지', 법인: r['구매법인']||r['법인']||'', storage유형: r['유형']||'', 자산코드: r['자산코드']||'', 명칭: r['명칭']||'', 위치: r['위치']||'', 모델명: r['모델명']||'', 용량: r['용량']||'', 담당자_정: r['담당자(정)']||'', 담당자_부: r['담당자(부)']||'', IP주소: r['IP주소']||'', MACaddress: r['MAC주소']||'', 구매연월: r['구매연월']||r['구매일']||'', 금액: r['금액']||'', 납품업체: r['납품업체']||'', 품의서명: r['품의서명']||'', 비고: r['비고']||'', HW사양: '', OS: '', 관리자: '' })); - } else if (sheetName === '전산비품') { - rows.forEach(r => data.equip.push({ id: Math.random().toString(36).substring(2, 9), type: '전산비품', 법인: r['구매법인']||r['법인']||'', 비품유형: r['비품유형']||r['유형']||'', 자산코드: r['자산코드']||'', 명칭: r['명칭']||'', 위치: r['위치']||'', 관리자: r['관리자']||'', IP주소: r['IP주소']||'', MACaddress: r['MACaddress']||'', HW사양: r['HW사양']||'', OS: r['OS']||'', 구매연월: r['구매연월']||r['구매일']||'', 금액: r['금액']||'', 납품업체: r['납품업체']||'', 품의서명: r['품의서명']||'', 비고: r['비고']||'' })); - } else if (sheetName === '모바일기기') { - rows.forEach(r => data.mobile.push({ id: Math.random().toString(36).substring(2, 9), type: '모바일기기', 법인: r['구매법인']||r['법인']||'', 자산코드: r['자산코드']||'', 명칭: r['명칭']||'', 위치: r['위치']||'', 관리자: r['관리자']||'', OS: r['OS']||'', 구매연월: r['구매연월']||r['구매일']||'', 금액: r['금액']||'', 납품업체: r['납품업체']||'', 품의서명: r['품의서명']||'', 비고: r['비고']||'', IP주소: '', MACaddress: '', HW사양: '' })); - } else if (sheetName === '구독SW') { - rows.forEach(r => data.subSw.push({ id: r['ID']||Math.random().toString(36).substring(2, 9), type: '구독SW', 분야: r['분야']||'', 법인: r['법인']||'', 부서: r['부서']||'', 제품명: r['제품명']||'', 구매연월: r['구매연월']||r['구매일']||'', 만료일: r['만료일']||'', 라이선스유형: r['라이선스유형']||'', 금액: r['금액']||'', 수량: parseInt(r['수량']||'1'), 계정명: r['계정명']||'', 납품업체: r['납품업체']||'', 비고: r['비고']||'' })); - } else if (sheetName === '영구SW') { - rows.forEach(r => data.permSw.push({ id: r['ID']||Math.random().toString(36).substring(2, 9), type: '영구SW', 분야: r['분야']||'', 법인: r['법인']||'', 부서: r['부서']||'', 제품명: r['제품명']||'', 구매연월: r['구매연월']||r['구매일']||'', 만료일: r['만료일']||'', 라이선스키: r['라이선스키']||'', 금액: r['금액']||'', 수량: parseInt(r['수량']||'1'), 계정명: r['계정명']||'', 납품업체: r['납품업체']||'', 비고: r['비고']||'' })); - } + const list: any[] = []; + + rows.forEach(r => { + const common = { id: Math.random().toString(36).substring(2, 9) }; + if (sheetName === '개인PC') { + list.push({ ...common, type: '개인PC', 법인: r['법인']||'', 자산코드: r['자산코드']||'', 사용자: r['사용자']||'', 위치: r['위치']||'', 모델명: r['모델명']||'', 메인보드: r['메인보드']||'', CPU: r['CPU']||'', GPU: r['GPU']||'', RAM: r['RAM']||'', SSD1: r['SSD1']||'', SSD2: r['SSD2']||'', HDD1: r['HDD1']||'', HDD2: r['HDD2']||'', IP주소: r['IP주소']||'', HW사양: r['HW사양']||'', 구매연월: r['구매연월']||'', 금액: r['금액']||'', 납품업체: r['납품업체']||'', 품의서명: r['품의서명']||'', 비고: r['비고']||'' }); + } else if (sheetName === '서버') { + list.push({ ...common, type: '서버', 법인: r['법인']||'', 자산코드: r['자산코드']||'', 구매연월: r['구매연월']||'', storage유형: r['유형']||'물리', 용도: r['용도']||'', 상세: r['상세내용']||'', 현사용조직: r['현사용조직']||'', 이전사용조직: r['이전사용조직']||'', 위치: r['위치']||'', 담당자_정: r['담당자(정)']||'', 담당자_부: r['담당자(부)']||'', IP주소: r['IP 주소 1']||'', IP2: r['IP 주소 2']||'', 원격접속: r['원격도구']||'', 서버ID: r['서버 ID']||'', 서버PW: r['서버 PW']||'', 모델명: r['모델명']||'', OS: r['OS']||'', CPU: r['CPU']||'', RAM: r['RAM']||'', GPU: r['GPU']||'', SSD1: r['Storage 1']||'', SSD2: r['Storage 2']||'', HDD1: r['Storage 3']||'', 모니터링: r['모니터링']||'', 비고: r['비고']||'' }); + } else if (sheetName === '스토리지') { + list.push({ ...common, type: '스토리지', 법인: r['법인']||'', storage유형: r['유형']||'', 자산코드: r['자산코드']||'', 명칭: r['명칭']||'', 위치: r['위치']||'', 모델명: r['모델명']||'', 용량: r['용량']||'', 담당자_정: r['담당자(정)']||'', 담당자_부: r['담당자(부)']||'', IP주소: r['IP주소']||'', MACaddress: r['MAC주소']||'', 구매연월: r['구매연월']||'', 금액: r['금액']||'', 납품업체: r['납품업체']||'', 품의서명: r['품의서명']||'', 비고: r['비고']||'' }); + } else if (sheetName === '전산비품') { + list.push({ ...common, type: '전산비품', 법인: r['법인']||'', 비품유형: r['비품유형']||'', 자산코드: r['자산코드']||'', 명칭: r['명칭']||'', 위치: r['위치']||'', 관리자: r['관리자']||'', IP주소: r['IP주소']||'', MACaddress: r['MACaddress']||'', HW사양: r['HW사양']||'', OS: r['OS']||'', 구매연월: r['구매연월']||'', 금액: r['금액']||'', 납품업체: r['납품업체']||'', 품의서명: r['품의서명']||'', 비고: r['비고']||'' }); + } else if (sheetName === '모바일기기') { + list.push({ ...common, type: '모바일기기', 법인: r['법인']||'', 자산코드: r['자산코드']||'', 명칭: r['명칭']||'', 위치: r['위치']||'', 관리자: r['관리자']||'', 기기유형: r['기기유형']||'', OS: r['OS']||'', 구매연월: r['구매연월']||'', 금액: r['금액']||'', 납품업체: r['납품업체']||'', 품의서명: r['품의서명']||'', 비고: r['비고']||'' }); + } else if (sheetName === '구독SW') { + list.push({ ...common, type: '구독SW', 분야: r['분야']||'', 법인: r['법인']||'', 부서: r['부서']||'', 제품명: r['제품명']||'', 구매일: r['구매일']||'', 시작일: r['시작일']||'', 만료일: r['만료일']||'', 라이선스유형: r['라이선스유형']||'', 금액: r['금액']||'', 수량: parseInt(r['수량']||'1'), 계정명: r['계정명']||'', 납품업체: r['납품업체']||'', 비고: r['비고']||'' }); + } else if (sheetName === '영구SW') { + list.push({ ...common, type: '영구SW', 분야: r['분야']||'', 법인: r['법인']||'', 부서: r['부서']||'', 제품명: r['제품명']||'', 구매일: r['구매일']||'', 시작일: r['시작일']||'', 만료일: r['만료일']||'', 라이선스키: r['라이선스키']||'', 금액: r['금액']||'', 수량: parseInt(r['수량']||'1'), 계정명: r['계정명']||'', 납품업체: r['납품업체']||'', 비고: r['비고']||'' }); + } else if (sheetName === '클라우드') { + list.push({ ...common, type: '클라우드', 플랫폼명: r['플랫폼명']||'', 법인: r['법인']||'', 부서: r['부서']||'', 제품명: r['제품명']||'', 계정명: r['계정명']||'', 결제수단: r['결제수단']||'', 결제일: r['결제일']||'', 연결카드번호: r['연결카드번호']||'', 당월청구액: r['당월청구액']||'', 비고: r['비고']||'' }); + } else if (sheetName === '도메인') { + list.push({ ...common, type: r['유형']||'도메인', corp: r['법인']||'', service_name: r['서비스명']||'', domain_name: r['관리도메인']||'', start_date: r['시작일']||'', expiry_date: r['만료일']||'', price: r['금액']||'', manager_main: r['담당자']||'', manager_sub: r['담당자(부)']||'', remarks: r['비고']||'' }); + } + }); + if (list.length > 0) parsedData[sheetName] = list; }); - resolve(data); + resolve(parsedData); } catch (err) { reject(err); } }; reader.readAsBinaryString(file); diff --git a/src/main.ts b/src/main.ts index 212ac7f..74055b9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -8,6 +8,7 @@ import { initHwModal, openHwModal } from './components/Modal/HWModal'; import { initSwModal, openSwModal } from './components/Modal/SWModal'; import { initSwUserModal } from './components/Modal/SWUserModal'; import { initDomainModal, openDomainModal } from './components/Modal/DomainModal'; +import { initUploadPreviewModal, openUploadPreview } from './components/Modal/UploadPreviewModal'; import { initDashboardDetailModal } from './components/Modal/DashboardDetailModal'; import { initGuide } from './components/Guide'; import { createIcons, Download, Upload, FileSpreadsheet, Plus, X, LayoutDashboard, Monitor, Server, Database, Laptop, CalendarClock, Key, Cpu, Layers, Users, Paperclip, Edit2, History, RefreshCcw, BookOpen, Settings } from 'lucide'; @@ -111,6 +112,10 @@ function initApp() { initDashboardDetailModal(); initDomainModal(); + initUploadPreviewModal(async () => { + await loadMasterDataFromDB(); + refreshView(); + }); initGuide(); // DB 데이터 로드 및 초기 화면 렌더링 @@ -129,10 +134,15 @@ function initApp() { uploadInput?.addEventListener('change', async (e) => { const file = (e.target as HTMLInputElement).files?.[0]; if (file) { - const data = await parseExcel(file); - state.masterData = { ...state.masterData, ...data }; - await Promise.all([saveAllHardwareToDB(), saveAllSoftwareToDB()]); - refreshView(); + try { + const data = await parseExcel(file); + openUploadPreview(data); + // Clear input so same file can be selected again + uploadInput.value = ''; + } catch (err) { + alert('엑셀 파일을 읽는 중 오류가 발생했습니다.'); + console.error(err); + } } });