import { state, saveHardwareAsset, deleteHardwareAsset } from '../../core/state'; import { HardwareAsset } from '../../core/excelHandler'; import { openModal } from './BaseModal'; import { ASSET_SCHEMA, UI_TEXT } from '../../core/schema'; import { createIcons, History, Plus, X, Save, Edit2, RotateCcw, Paperclip } from 'lucide'; import { CORP_LIST, ORG_LIST, HW_TYPE_LIST, LOCATION_DATA, TYPE_PREFIX_MAP } from './SharedData'; import { generateOptionsHTML, setFieldValue, getFieldValue, parseAndSetLocation, bindLocationEvents, getCombinedLocation, setEditLock, createModalFrameHTML, autoFillForm, autoExtractForm } from './ModalUtils'; let currentAsset: HardwareAsset | null = null; let isEditMode = false; const STATUS_LIST = ['대여중', '보관중', '수리중', '기타']; /** * 하드웨어 필드 매핑 (통합 스키마 기반) */ const HW_FIELD_MAP: Record = { '유형': ASSET_SCHEMA.TYPE.key, '법인': ASSET_SCHEMA.CORP.key, '자산코드': ASSET_SCHEMA.ASSET_CODE.key, '현사용조직': ASSET_SCHEMA.ORG.key, '이전사용조직': ASSET_SCHEMA.PREV_ORG.key, '상세용도': '상세용도', '모델명': ASSET_SCHEMA.MODEL.key, '메인보드': ASSET_SCHEMA.MAINBOARD.key, '명칭': '명칭', '보관위치': ASSET_SCHEMA.STORE_LOC.key, '현재상태': ASSET_SCHEMA.STATUS.key, 'IP주소': ASSET_SCHEMA.IP_ADDR.key, 'IP2': ASSET_SCHEMA.IP_ADDR2.key, '원격접속': '원격접속', '서버ID': '서버ID', '서버PW': '서버PW', '모니터링': '모니터링', 'OS': ASSET_SCHEMA.OS.key, 'CPU': ASSET_SCHEMA.CPU.key, 'RAM': ASSET_SCHEMA.RAM.key, 'SSD1': ASSET_SCHEMA.STORAGE1.key, 'SSD2': ASSET_SCHEMA.STORAGE2.key, 'HW사양': 'HW사양', '담당자_정': ASSET_SCHEMA.MANAGER_MAIN.key, '담당자_부': ASSET_SCHEMA.MANAGER_SUB.key, '구매일': ASSET_SCHEMA.PURCHASE_YM.key, '금액': ASSET_SCHEMA.PRICE.key, '비고': ASSET_SCHEMA.REMARKS.key, '사용자': ASSET_SCHEMA.USER.key }; const HW_FORM_HTML = `
기본 정보 (Identity)
운영 및 상태 관리
네트워크 정보 (Connectivity)
시스템 사양 (Specifications)
설치 위치 및 관리
`; function renderHwHistory(assetId: string) { const container = document.getElementById('hw-history-list'); if (!container) return; const logs = (state.masterData.logs || []).filter(l => l.assetId === assetId).sort((a,b) => new Date(b.date).getTime() - new Date(a.date).getTime()); if (logs.length === 0) { container.innerHTML = '
기록된 이력이 없습니다.
'; return; } container.innerHTML = logs.map(l => `
${l.date}
${l.user}
${l.details.replace(/\n/g, '
')}
`).join(''); } function applyTypeSpecificUI(type: string) { const detailPurpose = getFieldValue('hw-상세용도'); const upperType = (type || '').toUpperCase(); const groups: Record = { detailPurpose: document.getElementById('hw-상세용도-group'), networkTitle: document.getElementById('hw-network-title'), specTitle: document.getElementById('hw-spec-title'), opTitle: document.getElementById('hw-op-title'), model: document.getElementById('hw-model-group'), mainboard: document.getElementById('hw-mainboard-group'), os: document.getElementById('hw-os-group'), cpu: document.getElementById('hw-cpu-group'), ram: document.getElementById('hw-ram-group'), ssd1: document.getElementById('hw-ssd1-group'), ssd2: document.getElementById('hw-ssd2-group'), hwSpec: document.getElementById('hw-hwspec-group'), monitoring: document.getElementById('hw-monitoring-group'), user: document.querySelector('.pc-only') as HTMLElement }; const serverOnly = document.querySelectorAll('.server-only'); const nonServer = document.querySelectorAll('.non-server'); const opOnly = document.querySelectorAll('.op-only'); const standardLoc = document.querySelectorAll('.loc-standard'); serverOnly.forEach(el => (el as HTMLElement).style.display = 'none'); nonServer.forEach(el => (el as HTMLElement).style.display = 'none'); opOnly.forEach(el => (el as HTMLElement).style.display = 'none'); standardLoc.forEach(el => (el as HTMLElement).style.display = 'flex'); Object.values(groups).forEach(g => { if (g) g.style.display = 'none'; }); const osLabel = document.querySelector('label[for="hw-OS"]') as HTMLElement; const ramLabel = document.querySelector('label[for="hw-RAM"]') as HTMLElement; const modelLabel = document.querySelector('label[for="hw-모델명"]') as HTMLElement; if (osLabel) osLabel.innerText = ASSET_SCHEMA.OS.ui; if (ramLabel) ramLabel.innerText = ASSET_SCHEMA.RAM.ui; if (modelLabel) modelLabel.innerText = ASSET_SCHEMA.MODEL.ui; const isMobileGroup = ['모바일', '태블릿', '휴대폰'].some(t => upperType.includes(t)); const isEquipGroup = ['CPU', 'RAM', 'HDD', 'GPU'].some(t => upperType.includes(t)) || upperType.includes('비품'); const isOpType = isMobileGroup || isEquipGroup; const isPcType = upperType === 'PC' || upperType === '개인PC' || upperType === '노트북'; if (groups.opTitle) groups.opTitle.style.display = isOpType ? 'flex' : 'none'; if (isOpType) { opOnly.forEach(el => (el as HTMLElement).style.display = 'flex'); standardLoc.forEach(el => (el as HTMLElement).style.display = 'none'); if (groups.specTitle) groups.specTitle.style.display = 'flex'; if (groups.model) groups.model.style.display = 'flex'; if (['CPU', 'GPU'].some(t => upperType.includes(t))) { if (groups.os && osLabel) { osLabel.innerText = '출시연월'; groups.os.style.display = 'flex'; } } else if (['RAM', 'HDD'].some(t => upperType.includes(t))) { if (groups.ram && ramLabel) { ramLabel.innerText = '용량'; groups.ram.style.display = 'flex'; } } else { if (groups.hwSpec) groups.hwSpec.style.display = 'flex'; } } else if (isPcType) { if (groups.user) groups.user.style.display = 'flex'; if (groups.specTitle) groups.specTitle.style.display = 'flex'; if (groups.mainboard) groups.mainboard.style.display = 'flex'; if (upperType === '노트북') { if (groups.detailPurpose) groups.detailPurpose.style.display = 'none'; nonServer.forEach(el => (el as HTMLElement).style.display = 'flex'); ['model', 'os', 'cpu', 'ram', 'ssd1', 'ssd2', 'hwSpec'].forEach(k => { if (groups[k]) groups[k]!.style.display = 'flex'; }); } else { if (groups.detailPurpose) groups.detailPurpose.style.display = 'flex'; if (detailPurpose === '서버') { serverOnly.forEach(el => (el as HTMLElement).style.display = 'flex'); if (groups.networkTitle) groups.networkTitle.style.display = 'flex'; ['model', 'os', 'cpu', 'ram', 'ssd1', 'ssd2', 'monitoring'].forEach(k => { if (groups[k]) groups[k]!.style.display = 'flex'; }); } else { nonServer.forEach(el => (el as HTMLElement).style.display = 'flex'); ['model', 'os', 'cpu', 'ram', 'ssd1', 'ssd2', 'hwSpec'].forEach(k => { if (groups[k]) groups[k]!.style.display = 'flex'; }); } } } else { serverOnly.forEach(el => (el as HTMLElement).style.display = 'flex'); if (groups.networkTitle) groups.networkTitle.style.display = 'flex'; if (groups.specTitle) groups.specTitle.style.display = 'flex'; ['model', 'os', 'cpu', 'ram', 'ssd1', 'ssd2', 'monitoring'].forEach(k => { if (groups[k]) groups[k]!.style.display = 'flex'; }); } } export function openHwModal(asset: HardwareAsset, mode: 'view' | 'add' = 'view') { currentAsset = asset; const modal = document.getElementById('hw-asset-modal')!; setEditLock('hw-asset-form', mode, { saveBtnId: 'btn-save-hw-asset', revertBtnId: 'btn-revert-hw-edit', generateBtnId: 'btn-generate-hw-code', addLogBtnId: 'btn-add-hw-log' }); isEditMode = (mode === 'add'); autoFillForm('hw', asset, HW_FIELD_MAP); setFieldValue('hw-명칭', asset.명칭 || asset[ASSET_SCHEMA.MODEL.key]); if (!asset[ASSET_SCHEMA.PURCHASE_YM.key] && asset.구매일) setFieldValue('hw-구매일', asset.구매일); parseAndSetLocation(asset[ASSET_SCHEMA.LOCATION.key], 'hw-위치-빌딩', 'hw-위치-상세', 'hw-위치-기타-group', 'hw-위치-기타'); applyTypeSpecificUI(asset.type); renderHwHistory(asset.id); modal.classList.remove('hidden'); createIcons({ icons: { X, Save, Edit2, RotateCcw, History, Plus, Paperclip } }); } export function initHwModal(onSave: () => void, closeModalsCb: () => void) { if (!document.getElementById('hw-asset-modal')) { const html = createModalFrameHTML('hw', '자산 상세 정보', HW_FORM_HTML, { historyTitle: '분출 및 변경 이력', addLogBtnId: 'btn-add-hw-log' }); document.body.insertAdjacentHTML('beforeend', html); const logModalHTML = ` `; document.body.insertAdjacentHTML('beforeend', logModalHTML); } const form = document.getElementById('hw-asset-form') as HTMLFormElement; const saveBtn = document.getElementById('btn-save-hw-asset')!; const revertBtn = document.getElementById('btn-revert-hw-edit')!; const deleteBtn = document.getElementById('btn-delete-hw-asset')!; const typeSelect = document.getElementById('hw-유형') as HTMLSelectElement; const detailPurposeSelect = document.getElementById('hw-상세용도') as HTMLSelectElement; const logAddBtn = document.getElementById('btn-add-hw-log')!; const logModal = document.getElementById('hw-log-modal')!; [typeSelect, detailPurposeSelect].forEach(el => { el?.addEventListener('change', () => applyTypeSpecificUI(typeSelect.value)); }); bindLocationEvents('hw-위치-빌딩', 'hw-위치-상세', 'hw-위치-기타-group', 'hw-위치-기타'); const closeModalAction = () => { closeModalsCb(); isEditMode = false; }; document.getElementById('btn-close-hw-modal')?.addEventListener('click', closeModalAction); document.getElementById('btn-cancel-hw-modal')?.addEventListener('click', closeModalAction); revertBtn.addEventListener('click', () => { setEditLock('hw-asset-form', 'view', { saveBtnId: 'btn-save-hw-asset', revertBtnId: 'btn-revert-hw-edit', generateBtnId: 'btn-generate-hw-code', addLogBtnId: 'btn-add-hw-log' }); isEditMode = false; if (currentAsset) openHwModal(currentAsset, 'view'); }); document.getElementById('btn-generate-hw-code')?.addEventListener('click', async () => { const typeValue = typeSelect.value; const purchaseDate = getFieldValue('hw-구매일'); const typeCode = TYPE_PREFIX_MAP[typeValue] || 'ETC'; const dateStr = purchaseDate.replace(/[^0-9]/g, ''); if (dateStr.length < 6) { alert('올바른 구매연월(YYYYMM)을 입력해주세요.'); return; } const prefix = `${typeCode}-${dateStr.substring(0, 6)}-`; try { const res = await fetch(`http://172.16.40.100:3000/api/generate-asset-code?prefix=${prefix}`); const data = await res.json(); if (data.nextCode) setFieldValue('hw-자산코드', data.nextCode); } catch (err) { alert('자산번호 생성에 실패했습니다.'); } }); ['hw-구매일', 'hw-OS'].forEach(id => { const el = document.getElementById(id) as HTMLInputElement; el?.addEventListener('input', (e) => { const target = e.target as HTMLInputElement; const label = document.querySelector(`label[for="${id}"]`) as HTMLElement; if (id === 'hw-OS' && label?.innerText !== '출시연월') return; target.value = target.value.replace(/[^0-9]/g, '').substring(0, 6); }); }); saveBtn.addEventListener('click', () => { if (!currentAsset) return; if (!isEditMode) { setEditLock('hw-asset-form', 'edit', { saveBtnId: 'btn-save-hw-asset', revertBtnId: 'btn-revert-hw-edit', generateBtnId: 'btn-generate-hw-code', addLogBtnId: 'btn-add-hw-log' }); isEditMode = true; applyTypeSpecificUI(getFieldValue('hw-유형')); return; } const extracted = autoExtractForm('hw', HW_FIELD_MAP); if (!extracted[ASSET_SCHEMA.ASSET_CODE.key]) { alert('자산번호가 없습니다. [생성] 버튼을 눌러 자산번호를 먼저 부여해주세요.'); return; } const upperType = (extracted.type || '').toUpperCase(); const isOpType = ['CPU', 'RAM', 'HDD', 'GPU'].some(t => upperType.includes(t)) || upperType.includes('비품') || ['모바일', '태블릿', '휴대폰'].some(t => upperType.includes(t)); if (HW_TYPE_LIST.includes(extracted.type) || extracted.type === '개인PC') { const diffLogs: string[] = []; const compareFields = [ { key: ASSET_SCHEMA.ORG.key, label: ASSET_SCHEMA.ORG.ui }, { key: ASSET_SCHEMA.LOCATION.key, label: ASSET_SCHEMA.LOCATION.ui }, { key: ASSET_SCHEMA.MANAGER_MAIN.key, label: '담당자' }, { key: ASSET_SCHEMA.STATUS.key, label: ASSET_SCHEMA.STATUS.ui }, { key: ASSET_SCHEMA.IP_ADDR.key, label: ASSET_SCHEMA.IP_ADDR.ui }, { key: '상세용도', label: '상세유형' }, { key: ASSET_SCHEMA.MODEL.key, label: ASSET_SCHEMA.MODEL.ui } ]; if (!currentAsset || !currentAsset.자산코드) { diffLogs.push('자산 신규 등록'); } else { const asset = currentAsset!; const newIp = String(getFieldValue('hw-IP주소') || getFieldValue('hw-IP주소-non-server') || '').trim(); const newLocation = String(isOpType ? extracted[ASSET_SCHEMA.STORE_LOC.key] : getCombinedLocation('hw-위치-빌딩', 'hw-위치-상세', 'hw-위치-기타') || '').trim(); compareFields.forEach(f => { let oldVal = ''; let newVal = ''; if (f.key === ASSET_SCHEMA.IP_ADDR.key) { oldVal = String(asset[ASSET_SCHEMA.IP_ADDR.key] || '').trim(); newVal = newIp; } else if (f.key === ASSET_SCHEMA.LOCATION.key) { oldVal = String(asset[ASSET_SCHEMA.LOCATION.key] || '').trim(); newVal = newLocation; } else if (f.key === ASSET_SCHEMA.MANAGER_MAIN.key) { oldVal = String(asset[ASSET_SCHEMA.MANAGER_MAIN.key] || '').trim(); newVal = String(extracted[ASSET_SCHEMA.MANAGER_MAIN.key] || '').trim(); } else if (f.key === '상세용도') { oldVal = String(asset.상세용도 || '').trim(); newVal = String((extracted.type !== 'PC' && extracted.type !== '개인PC') ? extracted.type : (extracted.상세용도 || '')).trim(); } else { oldVal = String((asset as any)[f.key] || '').trim(); newVal = String(extracted[f.key] || '').trim(); } if (oldVal !== newVal) { diffLogs.push(`${f.label}: ${oldVal || '(없음)'} → ${newVal || '(없음)'}`); } }); } if (diffLogs.length > 0) { state.masterData.logs = state.masterData.logs || []; state.masterData.logs.push({ id: Math.random().toString(36).substring(2, 9), assetId: currentAsset.id, date: new Date().toISOString().split('T')[0], user: '담당자', details: diffLogs.join('\n') }); } } const updated: any = { ...currentAsset, ...extracted, [ASSET_SCHEMA.IP_ADDR.key]: getFieldValue('hw-IP주소') || getFieldValue('hw-IP주소-non-server'), 위치: isOpType ? extracted[ASSET_SCHEMA.STORE_LOC.key] : getCombinedLocation('hw-위치-빌딩', 'hw-위치-상세', 'hw-위치-기타') }; if (currentAsset[ASSET_SCHEMA.ORG.key] && currentAsset[ASSET_SCHEMA.ORG.key] !== extracted[ASSET_SCHEMA.ORG.key]) { updated[ASSET_SCHEMA.PREV_ORG.key] = currentAsset[ASSET_SCHEMA.ORG.key]; } if (updated.type !== 'PC') { updated.상세용도 = updated.type; } saveHardwareAsset(updated); onSave(); setEditLock('hw-asset-form', 'view', { saveBtnId: 'btn-save-hw-asset', revertBtnId: 'btn-revert-hw-edit', generateBtnId: 'btn-generate-hw-code', addLogBtnId: 'btn-add-hw-log' }); isEditMode = false; }); deleteBtn.addEventListener('click', () => { if (currentAsset && confirm(UI_TEXT.MESSAGES.CONFIRM_DELETE)) { deleteHardwareAsset(currentAsset.id); onSave(); closeModalAction(); } }); logAddBtn.addEventListener('click', () => { logModal.classList.remove('hidden'); (document.getElementById('new-hw-log-date') as HTMLInputElement).value = new Date().toISOString().split('T')[0]; (document.getElementById('new-hw-log-details') as HTMLTextAreaElement).value = ''; }); document.getElementById('btn-close-hw-log')?.addEventListener('click', () => logModal.classList.add('hidden')); document.getElementById('btn-cancel-hw-log')?.addEventListener('click', () => logModal.classList.add('hidden')); document.getElementById('btn-confirm-hw-log')?.addEventListener('click', () => { if (!currentAsset) return; const date = (document.getElementById('new-hw-log-date') as HTMLInputElement).value; const details = (document.getElementById('new-hw-log-details') as HTMLTextAreaElement).value; if (!date || !details) return; state.masterData.logs = state.masterData.logs || []; state.masterData.logs.push({ id: Math.random().toString(36).substring(2, 9), assetId: currentAsset.id, date, user: '담당자', details }); logModal.classList.add('hidden'); renderHwHistory(currentAsset.id); }); }