refactor: 프로젝트 정리 및 최적화 (미사용 파일 제거, 코드 중복 제거, 정적 이미지 빌드 경로 수정)
- 미사용 목업 파일(dummyData.ts, realServerData.ts, server_data.json) 및 중복 기획서 제거 - excelHandler.ts 내 미사용 대용량 엑셀 처리 함수들을 삭제하여 xlsx 의존성 제거 및 클라이언트 빌드 크기 최적화 - ListFactory.ts와 utils.ts 간에 중복으로 존재하던 calculatePcScoreDeductive 함수를 하나로 일원화 - 기획서 및 계획 문서들을 docs/plans/ 하위 폴더로 이동하여 프로젝트 루트 정리 - 정적 이미지 폴더(img/)를 public/img/로 이동하여 프로덕션 빌드 시 로고 및 장비 사진 엑박 오류 해결
This commit is contained in:
@@ -1,167 +1,7 @@
|
||||
import * as XLSX from 'xlsx';
|
||||
import { ASSET_SCHEMA } from './schema';
|
||||
import { HardwareAsset, SoftwareAsset, SWUser, HardwareLog, MasterAssetData } from './types';
|
||||
|
||||
/**
|
||||
* ITAM 엑셀 핸들러 (Database Synchronized Edition)
|
||||
* 데이터베이스 실제 스키마 컬럼과 엑셀 헤더를 1:1로 일치시킵니다.
|
||||
* ITAM 엑셀 핸들러 (지정 날짜 포맷팅 유틸리티)
|
||||
*/
|
||||
|
||||
/**
|
||||
* DB 컬럼 순서 및 구성 정의 (실제 DB 스키마 dump 기준)
|
||||
*/
|
||||
const DB_MAPPING: Record<string, (keyof typeof ASSET_SCHEMA)[]> = {
|
||||
pc: [
|
||||
'ASSET_TYPE', 'HW_STATUS', 'CURRENT_DEPT', 'PREV_DEPT', 'USER_POSITION',
|
||||
'EMP_NO', 'CURRENT_USER',
|
||||
'CPU', 'RAM', 'GPU', 'SSD1', 'SSD2', 'HDD1', 'HDD2', 'HDD3', 'HDD4', 'MAC_ADDR',
|
||||
'MANAGER_MAIN', 'MANAGER_SUB', 'PURCHASE_CORP', 'PURCHASE_DATE', 'PURCHASE_AMOUNT',
|
||||
'PURCHASE_VENDOR', 'MEMO', 'MAINBOARD'
|
||||
],
|
||||
server: [
|
||||
'ASSET_TYPE', 'MODEL_NAME', 'ASSET_PURPOSE', 'HW_STATUS',
|
||||
'CURRENT_DEPT', 'CPU', 'RAM', 'GPU', 'SSD1', 'SSD2', 'HDD1', 'HDD2', 'IP_ADDR',
|
||||
'REMOTE_TOOL', 'REMOTE_ID', 'REMOTE_PW', 'LOCATION', 'LOC_DETAIL', 'MANAGER_MAIN',
|
||||
'PURCHASE_CORP', 'PURCHASE_DATE', 'PURCHASE_AMOUNT', 'PURCHASE_VENDOR',
|
||||
'MEMO', 'PREV_DEPT', 'MANAGER_SUB', 'IP_ADDR2', 'MONITORING', 'HDD3', 'HDD4', 'EMP_NO'
|
||||
],
|
||||
storage: [
|
||||
'ASSET_TYPE', 'HW_STATUS', 'VOLUME', 'MODEL_NAME',
|
||||
'EMP_NO', 'CURRENT_USER',
|
||||
'SERIAL_NUM', 'LOCATION', 'LOC_DETAIL', 'MANAGER_MAIN', 'MANAGER_SUB',
|
||||
'PURCHASE_CORP', 'PURCHASE_DATE', 'PURCHASE_AMOUNT', 'PURCHASE_VENDOR',
|
||||
'MEMO', 'CURRENT_DEPT', 'PREV_DEPT'
|
||||
],
|
||||
network: [
|
||||
'PURCHASE_CORP', 'HW_STATUS', 'CURRENT_DEPT', 'PREV_DEPT',
|
||||
'EMP_NO', 'CURRENT_USER',
|
||||
'ASSET_TYPE', 'ASSET_MFR', 'MODEL_NAME', 'LOCATION', 'LOC_DETAIL', 'MANAGER_MAIN',
|
||||
'MANAGER_SUB', 'PURCHASE_DATE', 'PURCHASE_AMOUNT', 'PURCHASE_VENDOR', 'MEMO'
|
||||
],
|
||||
survey: [ // asset_survey (공간정보장비)
|
||||
'HW_STATUS', 'ASSET_NAME', 'LOCATION', 'LOC_DETAIL',
|
||||
'EMP_NO', 'CURRENT_USER',
|
||||
'MANAGER_MAIN', 'MANAGER_SUB', 'PURCHASE_CORP', 'PURCHASE_DATE', 'PURCHASE_AMOUNT',
|
||||
'PURCHASE_VENDOR', 'MEMO'
|
||||
],
|
||||
pcParts: [
|
||||
'HW_STATUS', 'ASSET_TYPE', 'ASSET_MFR', 'MODEL_NAME', 'VOLUME',
|
||||
'EMP_NO', 'CURRENT_USER',
|
||||
'MONITOR_INCH', 'LOCATION', 'LOC_DETAIL', 'PURCHASE_CORP', 'PURCHASE_DATE',
|
||||
'PURCHASE_AMOUNT', 'PURCHASE_VENDOR', 'MEMO'
|
||||
],
|
||||
equipment: [
|
||||
'HW_STATUS', 'ASSET_STATUS', 'ASSET_TYPE', 'ASSET_MFR',
|
||||
'EMP_NO', 'CURRENT_USER',
|
||||
'MODEL_NAME', 'LOCATION', 'LOC_DETAIL', 'MANAGER_MAIN', 'MANAGER_SUB',
|
||||
'PURCHASE_CORP', 'PURCHASE_DATE', 'PURCHASE_AMOUNT', 'PURCHASE_VENDOR',
|
||||
'MEMO'
|
||||
],
|
||||
officeSupplies: [ // asset_office_supplies (시설자산)
|
||||
'HW_STATUS', 'ASSET_TYPE', 'ASSET_MFR', 'MODEL_NAME',
|
||||
'EMP_NO', 'CURRENT_USER',
|
||||
'ASSET_COUNT', 'LOCATION', 'LOC_DETAIL', 'MANAGER_MAIN', 'MANAGER_SUB',
|
||||
'PURCHASE_CORP', 'PURCHASE_DATE', 'PURCHASE_AMOUNT', 'PURCHASE_VENDOR',
|
||||
'MEMO'
|
||||
],
|
||||
swInternal: [
|
||||
'SW_FIELD', 'DEV_OBJ', 'SW_STATUS', 'SW_TYPE', 'MANAGER_MAIN',
|
||||
'DEV_MGR', 'PLANNING_MGR', 'SALES_MGR', 'PURCHASE_CORP', 'MEMO'
|
||||
],
|
||||
swExternal: [
|
||||
'PRODUCT_NAME', 'SW_TYPE', 'SW_STATUS', 'SW_FIELD', 'CURRENT_DEPT',
|
||||
'PREV_DEPT', 'MANAGER_MAIN', 'PURCHASE_CORP', 'PURCHASE_DATE', 'PURCHASE_AMOUNT',
|
||||
'PURCHASE_VENDOR', 'EMAIL_ACCOUNT', 'MEMO', 'EMP_NO', 'CURRENT_USER'
|
||||
],
|
||||
cloud: [
|
||||
'ASSET_PURPOSE', 'PURCHASE_METHOD', 'PURCHASE_VENDOR', 'PURCHASE_CORP',
|
||||
'PURCHASE_DATE', 'PURCHASE_AMOUNT', 'MANAGER_MAIN', 'MANAGER_SUB',
|
||||
'MEMO', 'SW_ID', 'SW_PW'
|
||||
],
|
||||
domain: [
|
||||
'DOMAIN_ADDR', 'ASSET_PURPOSE', 'PURCHASE_VENDOR', 'ASSET_TYPE',
|
||||
'PURCHASE_CORP', 'PURCHASE_DATE', 'PURCHASE_AMOUNT', 'MANAGER_MAIN', 'MANAGER_SUB',
|
||||
'MEMO'
|
||||
],
|
||||
cost: [
|
||||
'ASSET_TYPE', 'ASSET_PURPOSE', 'LOCATION', 'LOC_DETAIL', 'MANAGER_MAIN',
|
||||
'MANAGER_SUB', 'PURCHASE_CORP', 'PURCHASE_DATE', 'PURCHASE_AMOUNT', 'PURCHASE_VENDOR',
|
||||
'EMAIL_ACCOUNT', 'EMAIL_PW', 'MEMO', 'EMP_NO', 'CURRENT_USER'
|
||||
],
|
||||
vip: [ // asset_vip (선물)
|
||||
'ASSET_NAME', 'MODEL_NAME', 'LOCATION', 'LOC_DETAIL',
|
||||
'PURCHASE_CORP', 'PURCHASE_DATE', 'EXPIRED_DATE', 'PURCHASE_VENDOR', 'MEMO'
|
||||
]
|
||||
};
|
||||
|
||||
export function downloadTemplate() {
|
||||
const wb = XLSX.utils.book_new();
|
||||
|
||||
const tabConfigs = [
|
||||
{ name: 'PC', key: 'pc' },
|
||||
{ name: '서버', key: 'server' },
|
||||
{ name: '스토리지', key: 'storage' },
|
||||
{ name: '공간정보장비', key: 'survey' },
|
||||
{ name: 'PC부품', key: 'pcParts' },
|
||||
{ name: '네트워크', key: 'network' },
|
||||
{ name: '업무지원장비', key: 'equipment' },
|
||||
{ name: '내부SW', key: 'swInternal' },
|
||||
{ name: '외부SW', key: 'swExternal' },
|
||||
{ name: '클라우드', key: 'cloud' },
|
||||
{ name: '도메인', key: 'domain' },
|
||||
{ name: '비용관리', key: 'cost' },
|
||||
{ name: '선물', key: 'vip' },
|
||||
{ name: '시설자산', key: 'officeSupplies' }
|
||||
];
|
||||
|
||||
tabConfigs.forEach(config => {
|
||||
const keys = DB_MAPPING[config.key];
|
||||
const headers = keys.map(k => ASSET_SCHEMA[k].ui);
|
||||
const ws = XLSX.utils.aoa_to_sheet([headers]);
|
||||
ws['!cols'] = Array(headers.length).fill({ wch: 20 });
|
||||
XLSX.utils.book_append_sheet(wb, ws, config.name);
|
||||
});
|
||||
|
||||
XLSX.writeFile(wb, 'itam_template_db_aligned.xlsx');
|
||||
}
|
||||
|
||||
export function exportToExcel(masterData: MasterAssetData) {
|
||||
const wb = XLSX.utils.book_new();
|
||||
|
||||
const exportConfigs = [
|
||||
{ name: 'PC', list: masterData.pc, key: 'pc' },
|
||||
{ name: '서버', list: masterData.server, key: 'server' },
|
||||
{ name: '스토리지', list: masterData.storage, key: 'storage' },
|
||||
{ name: '공간정보장비', list: masterData.survey || [], key: 'survey' },
|
||||
{ name: 'PC부품', list: masterData.pcParts || [], key: 'pcParts' },
|
||||
{ name: '네트워크', list: masterData.network || [], key: 'network' },
|
||||
{ name: '업무지원장비', list: masterData.equipment || [], key: 'equipment' },
|
||||
{ name: '내부SW', list: masterData.swInternal, key: 'swInternal' },
|
||||
{ name: '외부SW', list: masterData.swExternal, key: 'swExternal' },
|
||||
{ name: '클라우드', list: masterData.cloud || [], key: 'cloud' },
|
||||
{ name: '도메인', list: masterData.domain || [], key: 'domain' },
|
||||
{ name: '비용관리', list: masterData.cost || [], key: 'cost' },
|
||||
{ name: '선물', list: masterData.vip || [], key: 'vip' },
|
||||
{ name: '시설자산', list: masterData.officeSupplies || [], key: 'officeSupplies' }
|
||||
];
|
||||
|
||||
exportConfigs.forEach(config => {
|
||||
const schemaKeys = DB_MAPPING[config.key];
|
||||
const headers = schemaKeys.map(k => ASSET_SCHEMA[k].ui);
|
||||
const rows = config.list.map(asset =>
|
||||
schemaKeys.map(k => {
|
||||
const dbField = ASSET_SCHEMA[k].db;
|
||||
return asset[dbField] || asset[ASSET_SCHEMA[k].key] || '';
|
||||
})
|
||||
);
|
||||
|
||||
const ws = XLSX.utils.aoa_to_sheet([headers, ...rows]);
|
||||
XLSX.utils.book_append_sheet(wb, ws, config.name);
|
||||
});
|
||||
|
||||
XLSX.writeFile(wb, `itam_export_${new Date().toISOString().split('T')[0]}.xlsx`);
|
||||
}
|
||||
|
||||
export function formatExcelDate(val: any): string {
|
||||
if (!val) return '';
|
||||
if (typeof val === 'number') {
|
||||
@@ -173,54 +13,3 @@ export function formatExcelDate(val: any): string {
|
||||
}
|
||||
return String(val);
|
||||
}
|
||||
|
||||
export async function parseExcel(file: File): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const workbook = XLSX.read(e.target?.result, { type: 'array' });
|
||||
const parsedData: any = {};
|
||||
|
||||
workbook.SheetNames.forEach(sheetName => {
|
||||
const ws = workbook.Sheets[sheetName];
|
||||
const rows = XLSX.utils.sheet_to_json(ws, { defval: "" }) as any[];
|
||||
const list: any[] = [];
|
||||
|
||||
rows.forEach(r => {
|
||||
const data: any = { id: Math.random().toString(36).substring(2, 9) };
|
||||
|
||||
// Set default category based on sheet name
|
||||
data['category'] = sheetName;
|
||||
|
||||
Object.keys(r).forEach(label => {
|
||||
const schemaEntry = Object.values(ASSET_SCHEMA).find(s => s.ui === label);
|
||||
const key = schemaEntry ? schemaEntry.db : label;
|
||||
let val = r[label];
|
||||
|
||||
if (label.includes('일자') || label.includes('연월') || label.includes('만료일') || label.includes('시작일')) {
|
||||
val = formatExcelDate(val);
|
||||
}
|
||||
data[key] = val;
|
||||
});
|
||||
|
||||
list.push(data);
|
||||
});
|
||||
|
||||
// Sheet Name Mapping back to state keys
|
||||
const nameMap: Record<string, string> = {
|
||||
'PC': 'pc', '서버': 'server', '스토리지': 'storage', '공간정보장비': 'survey',
|
||||
'PC부품': 'pcParts', '네트워크': 'network', '업무지원장비': 'equipment',
|
||||
'내부SW': 'swInternal', '외부SW': 'swExternal', '클라우드': 'cloud',
|
||||
'도메인': 'domain', '비용관리': 'cost', '선물': 'vip', '시설자산': 'officeSupplies'
|
||||
};
|
||||
|
||||
const stateKey = nameMap[sheetName] || sheetName;
|
||||
if (list.length > 0) parsedData[stateKey] = list;
|
||||
});
|
||||
resolve(parsedData);
|
||||
} catch (err) { reject(err); }
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user