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 = `
+
+
+
+
+
+
+
+
+
+
+
+
+ 선택된 탭 없음
+ 0건
+
+
+ * 아래 데이터가 신규로 추가되거나 기존 데이터가 갱신됩니다.
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+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 = `
+
+
+
+ | No. |
+ ${headers.map(h => `${h} | `).join('')}
+
+
+
+ ${data.map((row: any, idx: number) => `
+
+ | ${idx + 1} |
+ ${headers.map(h => `${row[h] || '-'} | `).join('')}
+
+ `).join('')}
+
+
+ `;
+
+ 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);
+ }
}
});