diff --git a/index.html b/index.html index 5bbbd7f..758464e 100644 --- a/index.html +++ b/index.html @@ -394,6 +394,16 @@ +
+ + +
+
+
+ + +
+
diff --git a/src/components/Modal/SWModal.ts b/src/components/Modal/SWModal.ts index cb2ccc0..c6d7dd8 100644 --- a/src/components/Modal/SWModal.ts +++ b/src/components/Modal/SWModal.ts @@ -19,7 +19,9 @@ export function initSWModal(renderContent: () => void, closeModals: () => void) 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 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).value, @@ -82,7 +84,9 @@ export function openSwModal(asset?: SoftwareAsset) { (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 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).value = asset.구독일 || ''; @@ -97,6 +101,8 @@ export function openSwModal(asset?: SoftwareAsset) { deleteBtn.style.display = 'none'; (document.getElementById('sw-asset-id') as HTMLInputElement).value = ''; (document.getElementById('sw-asset-type') as HTMLInputElement).value = state.activeSubTab; + (document.getElementById('sw-분야') as HTMLSelectElement).value = '업무공통'; (document.getElementById('sw-법인') as HTMLSelectElement).value = '한맥'; + (document.getElementById('sw-부서') as HTMLInputElement).value = ''; } } diff --git a/src/dummyDataGenerator.ts b/src/dummyDataGenerator.ts index 3a292a8..5b48c9a 100644 --- a/src/dummyDataGenerator.ts +++ b/src/dummyDataGenerator.ts @@ -140,7 +140,9 @@ export function generateDummyData(): MasterAssetData { sw.push({ id: swId, type: '구독SW', + 분야: rand(['업무공통', '개발S/W', '디자인', '설계S/W']), 법인: rand(corps), + 부서: rand(depts), 제품명: rand(['Adobe CC All Apps', 'Microsoft 365', 'Slack Pro', 'Notion Team']), 구매일: '2024-01-01', 구독일: `2024.01.01 ~ ${endStr}`, @@ -182,7 +184,9 @@ export function generateDummyData(): MasterAssetData { sw.push({ id: swId, type: '영구SW', + 분야: rand(['업무공통', '개발S/W', '디자인', '설계S/W']), 법인: rand(corps), + 부서: rand(depts), 제품명: rand(['AutoCAD 2024', 'Windows 10 Pro', '한컴오피스 2022', 'Visual Studio 2022']), 구매일: '2020-05-15', 유지보수여부: true, diff --git a/src/excelHandler.ts b/src/excelHandler.ts index 36479bc..6b9bc1a 100644 --- a/src/excelHandler.ts +++ b/src/excelHandler.ts @@ -35,7 +35,9 @@ export interface HardwareAsset { export interface SoftwareAsset { id: string; type: string; // '구독SW', '영구SW' + 분야?: string; 법인: string; + 부서?: string; 제품명: string; 구매일: string; 구독일?: string; @@ -71,8 +73,8 @@ const SW_TABS = ['구독SW', '영구SW']; const HW_HEADERS = ['법인', '자산코드', '명칭', '위치', '관리자', 'IP주소', 'MACaddress', 'HW사양', 'OS', '구매일', '금액', '납품업체', '품의서명']; const PC_HEADERS = ['법인', '자산코드', '사용자', '위치', 'CPU', 'GPU', 'RAM', 'SSD1', 'SSD2', 'HDD1', 'HDD2', '구매일', '금액', '납품업체', '품의서명']; const STORAGE_HEADERS = ['법인', '유형', '자산코드', '명칭', '위치', '모델명', '용량', '담당자(정)', '담당자(부)', 'IP주소', 'MAC주소', '구매일', '금액', '납품업체', '품의서명']; -const SUB_SW_HEADERS = ['ID', '법인', '제품명', '구매일', '구독일', '금액', '수량', '계정명', '납품업체', '비고']; -const PERM_SW_HEADERS = ['ID', '법인', '제품명', '구매일', '유지보수여부', '금액', '수량', '계정명', '납품업체', '비고']; +const SUB_SW_HEADERS = ['ID', '분야', '법인', '부서', '제품명', '구매일', '구독일', '금액', '수량', '계정명', '납품업체', '비고']; +const PERM_SW_HEADERS = ['ID', '분야', '법인', '부서', '제품명', '구매일', '유지보수여부', '금액', '수량', '계정명', '납품업체', '비고']; const SW_USER_HEADERS = ['id', 'swId', '법인', '부서', '팀', '직위', '이름', '사용기간', '신청서명']; /** @@ -102,7 +104,7 @@ export function downloadTemplate() { SW_TABS.forEach(tab => { let hd = tab === '구독SW' ? SUB_SW_HEADERS : PERM_SW_HEADERS; const ws = XLSX.utils.aoa_to_sheet([hd]); - ws['!cols'] = [{wch:15}, {wch:15}, {wch:30}, {wch:15}, {wch:20}, {wch:15}, {wch:10}, {wch:20}, {wch:20}, {wch:30}]; + ws['!cols'] = [{wch:15}, {wch:15}, {wch:15}, {wch:20}, {wch:30}, {wch:15}, {wch:20}, {wch:15}, {wch:10}, {wch:20}, {wch:20}, {wch:30}]; XLSX.utils.book_append_sheet(wb, ws, tab); }); @@ -157,16 +159,16 @@ export function exportToExcel(masterData: MasterAssetData) { if (tab === '구독SW') { wsData = [ SUB_SW_HEADERS, - ...targetAssets.map(a => [a.id, a.법인, a.제품명, a.구매일, a.구독일, a.금액, a.수량, a.계정명, a.납품업체, a.비고]) + ...targetAssets.map(a => [a.id, a.분야||'', a.법인, a.부서||'', a.제품명, a.구매일, a.구독일, a.금액, a.수량, a.계정명, a.납품업체, a.비고]) ]; } else { wsData = [ PERM_SW_HEADERS, - ...targetAssets.map(a => [a.id, a.법인, a.제품명, a.구매일, a.유지보수여부 ? 'Y' : 'N', a.금액, a.수량, a.계정명, a.납품업체, a.비고]) + ...targetAssets.map(a => [a.id, a.분야||'', a.법인, a.부서||'', a.제품명, a.구매일, a.유지보수여부 ? 'Y' : 'N', a.금액, a.수량, a.계정명, a.납품업체, a.비고]) ]; } const ws = XLSX.utils.aoa_to_sheet(wsData); - ws['!cols'] = [{wch:15}, {wch:15}, {wch:30}, {wch:15}, {wch:20}, {wch:15}, {wch:10}, {wch:20}, {wch:20}, {wch:30}]; + ws['!cols'] = [{wch:15}, {wch:15}, {wch:15}, {wch:20}, {wch:30}, {wch:15}, {wch:20}, {wch:15}, {wch:10}, {wch:20}, {wch:20}, {wch:30}]; XLSX.utils.book_append_sheet(wb, ws, tab); }); @@ -281,7 +283,9 @@ export async function parseExcel(file: File): Promise { swAssets.push({ id: row['ID'] ? String(row['ID']) : Math.random().toString(36).substring(2, 9), type: sheetName, + 분야: row['분야'] || '', 법인: row['법인'] || '', + 부서: row['부서'] || '', 제품명: row['제품명'] || '', 구매일: row['구매일'] || '', 구독일: row['구독일'] || '', diff --git a/src/style.css b/src/style.css index 4b6238e..0bcbce1 100644 --- a/src/style.css +++ b/src/style.css @@ -300,6 +300,10 @@ tbody tr:last-child td { border-bottom: none; } tbody tr:hover { background-color: var(--bg-color); } .empty-row td { text-align: center; padding: 3rem; color: var(--text-muted); } +.sw-table td { + text-align: center; +} + /* Modal */ .modal-overlay { position: fixed; diff --git a/src/views/AssetTableView.ts b/src/views/AssetTableView.ts index 6bb2d81..e193d79 100644 --- a/src/views/AssetTableView.ts +++ b/src/views/AssetTableView.ts @@ -78,18 +78,19 @@ function renderSwTable(table: HTMLTableElement, container: HTMLElement, mainCont const list = state.masterData.sw.filter(a => a.type === state.activeSubTab); const isSub = state.activeSubTab === '구독SW'; - table.innerHTML = `No법인제품명구매일${isSub ? '구독일' : ''}수량사용가능관리`; + table.classList.add('sw-table'); + table.innerHTML = `No.분야법인부서제품명구매일${isSub ? '구독일' : ''}수량사용가능관리`; container.appendChild(table); mainContent.appendChild(container); const tbody = document.getElementById('dynamic-tbody')!; - if (list.length === 0) { tbody.innerHTML = `정보가 없습니다.`; return; } + if (list.length === 0) { tbody.innerHTML = `정보가 없습니다.`; return; } list.forEach((asset, idx) => { const assigned = state.masterData.swUsers.filter(u => u.swId === asset.id).length; const avail = (typeof asset.수량 === 'number' ? asset.수량 : parseInt(asset.수량||'0', 10)) - assigned; const tr = document.createElement('tr'); tr.style.cursor = 'pointer'; - tr.innerHTML = `${idx+1}${asset.법인}${asset.제품명}${asset.구매일||''}${isSub ? `${asset.구독일||''}` : ''}${asset.수량}${avail}`; + tr.innerHTML = `${idx+1}${asset.분야||''}${asset.법인}${asset.부서||''}${asset.제품명}${asset.구매일||''}${isSub ? `${asset.구독일||''}` : ''}${asset.수량}${avail}`; tr.addEventListener('click', (e) => { if (!(e.target as HTMLElement).closest('button')) openSwModal(asset); }); tr.querySelector('.btn-edit')?.addEventListener('click', () => openSwModal(asset)); tr.querySelector('.btn-users')?.addEventListener('click', () => openSwUserModal(asset));