Files
ITAM-test/src/components/Modal/HWModal.ts
T

432 lines
19 KiB
TypeScript

import { createIcons, Paperclip, History, Plus, X } from 'lucide';
import { HardwareAsset, HardwareLog } from '../../core/state';
import { state, saveHardwareAsset, deleteHardwareAsset } from '../../core/state';
import { setEditLock } from './ModalUtils';
let currentAsset: HardwareAsset | null = null;
let isEditMode = false;
// 공통 옵션 생성 함수
const generateOptionsHTML = (options: string[]) =>
options.map(opt => `<option value="${opt}">${opt}</option>`).join('');
const ORG_LIST = ["경영지원팀", "전략기획팀", "IT운영팀", "공정기술팀", "품질관리팀"];
const HW_TYPE_LIST = ["서버", "데스크탑", "노트북", "워크스테이션", "태블릿", "기타"];
const STATUS_LIST = ["사용중", "유휴", "수리중", "폐기예정", "기타"];
const LOCATION_DATA: Record<string, string[]> = {
"본사": ["1층 전산실", "2층 사무실", "3층 회의실"],
"IDC": ["A구역 랙 01", "A구역 랙 02", "B구역 랙 05"],
"공장": ["생산라인 1", "제어실", "창고"]
};
const HW_MODAL_HTML = `
<div id="hw-asset-modal" class="modal-overlay hidden">
<div class="modal-content modal-lg">
<div class="modal-header">
<h2 id="hw-modal-title">H/W 자산 정보</h2>
<button id="btn-close-hw-modal" class="btn-icon" aria-label="닫기"><i data-lucide="x"></i></button>
</div>
<div class="modal-body">
<div class="modal-body-split">
<div class="modal-form-area">
<form id="hw-asset-form" class="grid-form">
<input type="hidden" id="hw-asset-id" />
<input type="hidden" id="hw-asset-type" />
<!-- Group 1: 기본 정보 (Identity) -->
<div class="form-section-title">기본 정보 (Identity)</div>
<div class="form-group">
<label for="hw-자산번호">자산번호</label>
<input type="text" id="hw-자산번호" readonly />
</div>
<div class="form-group">
<label for="hw-관리번호">관리번호/코드</label>
<div style="display:flex; gap:0.5rem;">
<input type="text" id="hw-관리번호" readonly placeholder="자동 생성 또는 직접 입력" required />
<button type="button" id="btn-generate-hw-code" class="btn btn-outline btn-sm hidden" style="white-space:nowrap;">생성</button>
</div>
</div>
<div class="form-group">
<label for="hw-사용자조직">실사용조직</label>
<select id="hw-사용자조직">${generateOptionsHTML(ORG_LIST)}</select>
</div>
<div class="form-group" id="hw-관리조직-group">
<label for="hw-관리조직">자산관리조직</label>
<input type="text" id="hw-관리조직" readonly />
</div>
<div class="form-group">
<label for="hw-자산구분">자산구분</label>
<select id="hw-자산구분">${generateOptionsHTML(HW_TYPE_LIST)}</select>
</div>
<div class="form-group" id="hw-서버용도-group" style="display:none;">
<label for="hw-서버용도">서버용도</label>
<select id="hw-서버용도">
<option value="">선택하세요</option>
<option value="DB서버">DB 서버</option>
<option value="웹서버">웹 서버</option>
<option value="어플리케이션">어플리케이션</option>
<option value="가상화">가상화 호스트</option>
</select>
</div>
<!-- 운영 및 상태 관리 -->
<div class="form-section-title op-only">운영 및 상태 관리</div>
<div class="form-group op-only">
<label for="hw-보관위치">보관위치</label>
<input type="text" id="hw-보관위치" placeholder="예: 7층 비상계단옆 창고" />
</div>
<div class="form-group op-only">
<label for="hw-상태">상태</label>
<select id="hw-상태">${generateOptionsHTML(STATUS_LIST)}</select>
</div>
<!-- 네트워크 및 서버 정보 -->
<div class="form-section-title server-only" id="hw-network-title">네트워크 정보 (Connectivity)</div>
<div class="form-group server-only" id="hw-ip-group">
<label for="hw-IP주소">IP 주소 1</label>
<input type="text" id="hw-IP주소" />
</div>
<div class="form-group server-only" id="hw-ip2-group">
<label for="hw-IP2">IP 주소 2</label>
<input type="text" id="hw-IP2" />
</div>
<div class="form-group server-only" id="hw-원격접속">원격 접속/관리</div>
<div class="form-group server-only" id="hw-remote-group">
<label for="hw-원격방법">원격 방법</label>
<input type="text" id="hw-원격방법" />
</div>
<div class="form-group server-only" id="hw-server-id-group">
<label for="hw-서버ID">서버 ID</label>
<input type="text" id="hw-서버ID" />
</div>
<div class="form-group server-only" id="hw-server-pw-group">
<label for="hw-서버PW">서버 PW</label>
<input type="password" id="hw-서버PW" />
</div>
<div class="form-group non-server" id="hw-ip-non-server-group">
<label for="hw-IP주소-non-server">IP 주소</label>
<input type="text" id="hw-IP주소-non-server" />
</div>
<!-- 하드웨어 사양 -->
<div class="form-section-title" id="hw-spec-title">하드웨어 사양 (Specifications)</div>
<div class="form-group" id="hw-model-group">
<label for="hw-모델명">모델명</label>
<input type="text" id="hw-모델명" />
</div>
<div class="form-group" id="hw-os-group">
<label for="hw-OS">운영체제 (OS)</label>
<input type="text" id="hw-OS" />
</div>
<div class="form-group" id="hw-cpu-group">
<label for="hw-CPU">CPU 사양</label>
<input type="text" id="hw-CPU" />
</div>
<div class="form-group" id="hw-ram-group">
<label for="hw-RAM">RAM 용량</label>
<input type="text" id="hw-RAM" />
</div>
<div class="form-group" id="hw-ssd1-group">
<label for="hw-SSD1">Storage 1 (SSD/HDD)</label>
<input type="text" id="hw-SSD1" />
</div>
<div class="form-group" id="hw-ssd2-group">
<label for="hw-SSD2">Storage 2 (SSD/HDD)</label>
<input type="text" id="hw-SSD2" />
</div>
<div class="form-group server-only" id="hw-monitoring-group">
<label for="hw-모니터링">모니터링 유무</label>
<input type="text" id="hw-모니터링" />
</div>
<div class="form-group full-width non-server" id="hw-hwspec-group">
<label for="hw-HW사양">사양 상세</label>
<textarea id="hw-HW사양" rows="2"></textarea>
</div>
<!-- 설치/위치 관리 -->
<div class="form-section-title" id="hw-op-title">설치 및 위치 관리</div>
<div class="form-group loc-standard">
<label for="hw-설치장소">설치장소(대분류)</label>
<select id="hw-설치장소">${generateOptionsHTML(Object.keys(LOCATION_DATA))}</select>
</div>
<div class="form-group loc-standard">
<label for="hw-세부위치">세부 설치 위치</label>
<select id="hw-세부위치"><option value="">선택하세요</option></select>
</div>
<div class="form-group" id="hw-위치기타-group" style="display:none;">
<label for="hw-위치기타">직접 입력 (기타)</label>
<input type="text" id="hw-위치기타" />
</div>
<div class="form-group">
<label for="hw-도입일">도입일</label>
<input type="text" id="hw-도입일" />
</div>
<div class="form-group">
<label for="hw-구매가">구매가</label>
<input type="text" id="hw-구매가" oninput="this.value = this.value.replace(/[^0-9]/g, '').replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',')" />
</div>
<div class="form-group full-width">
<label for="hw-비고">비고</label>
<textarea id="hw-비고" rows="2"></textarea>
</div>
</form>
</div>
<div class="modal-history-area">
<div class="history-header">
<h3><i data-lucide="history" style="width:16px; height:16px;"></i> 유지보수 및 변경 이력</h3>
<button type="button" id="btn-add-hw-log" class="btn btn-outline btn-sm">
이력 추가 <i data-lucide="plus" style="width:14px; height:14px;"></i>
</button>
</div>
<div id="hw-history-list" class="history-timeline"></div>
</div>
</div>
</div>
<div class="modal-footer">
<button id="btn-delete-hw-asset" class="btn btn-outline btn-danger">삭제</button>
<div class="footer-actions">
<button id="btn-revert-hw-edit" class="btn btn-outline hidden">취소</button>
<button id="btn-save-hw-asset" class="btn btn-primary">수정</button>
<button id="btn-close-modal-bottom" class="btn btn-outline">닫기</button>
</div>
</div>
</div>
</div>
<!-- 이력 추가 모달 -->
<div id="hw-log-modal" class="modal-overlay hidden" style="z-index: 1100;">
<div class="modal-content" style="max-width: 400px;">
<div class="modal-header">
<h2>이력 추가</h2>
<button id="btn-close-hw-log" class="btn-icon"><i data-lucide="x"></i></button>
</div>
<div class="modal-body">
<div class="grid-form" style="grid-template-columns: 1fr;">
<div class="form-group">
<label>일자</label>
<input type="date" id="new-hw-log-date" />
</div>
<div class="form-group">
<label>변경/유지보수 내용</label>
<textarea id="new-hw-log-details" rows="3" placeholder="예: [유지보수] 먼지 제거, [부품교체] 배터리 교체 완료"></textarea>
</div>
</div>
</div>
<div class="modal-footer">
<div></div>
<div class="footer-actions">
<button id="btn-cancel-hw-log" class="btn btn-outline">취소</button>
<button id="btn-confirm-hw-log" class="btn btn-primary">추가</button>
</div>
</div>
</div>
</div>
`;
function renderHwHistory(assetId: string) {
const container = document.getElementById('hw-history-list');
if (!container) return;
const logs = (state.masterData.logs || []).filter(l => l.assetId === assetId);
if (logs.length === 0) {
container.innerHTML = '<div class="empty-history">기록이 없습니다.</div>';
return;
}
container.innerHTML = logs.map(l => `
<div class="history-item">
<div class="history-date">${l.date}</div>
<div class="history-user">${l.user}</div>
<div class="history-details">${l.details}</div>
</div>
`).join('');
}
function getFieldValue(id: string): string {
return (document.getElementById(id) as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement)?.value || '';
}
function applyTypeSpecificUI(type: string) {
const detailPurpose = getFieldValue('hw-서버용도');
const upperType = (type || '').toUpperCase();
const groups: Record<string, HTMLElement | null> = {
detailPurpose: document.getElementById('hw-서버용도-group'),
networkTitle: document.getElementById('hw-network-title'),
specTitle: document.getElementById('hw-spec-title'),
opTitle: document.getElementById('hw-op-title')
};
const isServer = upperType.includes('서버') || upperType.includes('SERVER');
const serverOnly = document.querySelectorAll('.server-only');
const nonServer = document.querySelectorAll('.non-server');
serverOnly.forEach(el => (el as HTMLElement).style.display = isServer ? 'block' : 'none');
nonServer.forEach(el => (el as HTMLElement).style.display = isServer ? 'none' : 'block');
if (groups.detailPurpose) groups.detailPurpose.style.display = isServer ? 'block' : 'none';
}
export function openHwModal(asset: any) {
currentAsset = asset;
isEditMode = false;
const modal = document.getElementById('hw-asset-modal')!;
const form = document.getElementById('hw-asset-form') as HTMLFormElement;
form.reset();
fillHwFormData(asset);
renderHwHistory(asset.id);
setEditLock('hw-asset-form', 'view', { saveBtnId: 'btn-save-hw-asset', revertBtnId: 'btn-revert-hw-edit' });
modal.classList.remove('hidden');
createIcons({ icons: { Paperclip, History, Plus, X } });
}
function fillHwFormData(asset: any) {
const setVal = (id: string, val: any) => {
const el = document.getElementById(id) as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
if (el) el.value = val || '';
};
setVal('hw-asset-id', asset.id);
setVal('hw-asset-type', asset.type);
setVal('hw-자산번호', asset.자산번호 || '');
setVal('hw-관리번호', asset.관리번호 || asset.관리코드 || '');
setVal('hw-사용자조직', asset.실사용조직 || '');
setVal('hw-관리조직', asset.관리조직 || '전산팀');
setVal('hw-자산구분', asset.자산구분 || asset.type || '');
setVal('hw-서버용도', asset.서버용도 || '');
setVal('hw-보관위치', asset.보관위치 || '');
setVal('hw-상태', asset.상태 || '사용중');
setVal('hw-IP주소', asset.IP주소 || '');
setVal('hw-IP2', asset.IP2 || '');
setVal('hw-원격방법', asset.원격방법 || '');
setVal('hw-서버ID', asset.서버ID || '');
setVal('hw-서버PW', asset.서버PW || '');
setVal('hw-IP주소-non-server', asset.IP주소 || '');
setVal('hw-모델명', asset.모델명 || '');
setVal('hw-OS', asset.OS || '');
setVal('hw-CPU', asset.CPU || '');
setVal('hw-RAM', asset.RAM || '');
setVal('hw-SSD1', asset.SSD1 || '');
setVal('hw-SSD2', asset.SSD2 || '');
setVal('hw-모니터링', asset.모니터링 || '');
setVal('hw-HW사양', asset.HW사양 || '');
setVal('hw-설치장소', asset.설치장소 || '');
setVal('hw-도입일', asset.도입일 || '');
setVal('hw-구매가', asset.구매가 || '');
setVal('hw-비고', asset.비고 || '');
applyTypeSpecificUI(asset.type);
}
export function initHwModal(onSave: () => void) {
document.body.insertAdjacentHTML('beforeend', HW_MODAL_HTML);
const modal = document.getElementById('hw-asset-modal')!;
const closeBtn = document.getElementById('btn-close-hw-modal')!;
const closeBtnBottom = document.getElementById('btn-close-modal-bottom')!;
const saveBtn = document.getElementById('btn-save-hw-asset')!;
const deleteBtn = document.getElementById('btn-delete-hw-asset')!;
const revertBtn = document.getElementById('btn-revert-hw-edit')!;
const closeModalAction = () => {
modal.classList.add('hidden');
isEditMode = false;
};
closeBtn.addEventListener('click', closeModalAction);
closeBtnBottom.addEventListener('click', closeModalAction);
saveBtn.addEventListener('click', () => {
if (!isEditMode) {
setEditLock('hw-asset-form', 'edit', { saveBtnId: 'btn-save-hw-asset', revertBtnId: 'btn-revert-hw-edit' });
isEditMode = true;
return;
}
const updated: any = {
...currentAsset,
관리번호: getFieldValue('hw-관리번호'),
실사용조직: getFieldValue('hw-사용자조직'),
자산구분: getFieldValue('hw-자산구분'),
서버용도: getFieldValue('hw-서버용도'),
보관위치: getFieldValue('hw-보관위치'),
상태: getFieldValue('hw-상태'),
IP주소: getFieldValue('hw-IP주소') || getFieldValue('hw-IP주소-non-server'),
IP2: getFieldValue('hw-IP2'),
원격방법: getFieldValue('hw-원격방법'),
서버ID: getFieldValue('hw-서버ID'),
서버PW: getFieldValue('hw-서버PW'),
모델명: getFieldValue('hw-모델명'),
OS: getFieldValue('hw-OS'),
CPU: getFieldValue('hw-CPU'),
RAM: getFieldValue('hw-RAM'),
SSD1: getFieldValue('hw-SSD1'),
SSD2: getFieldValue('hw-SSD2'),
모니터링: getFieldValue('hw-모니터링'),
HW사양: getFieldValue('hw-HW사양'),
설치장소: getFieldValue('hw-설치장소'),
도입일: getFieldValue('hw-도입일'),
구매가: getFieldValue('hw-구매가'),
비고: getFieldValue('hw-비고')
};
saveHardwareAsset(updated);
onSave();
setEditLock('hw-asset-form', 'view', { saveBtnId: 'btn-save-hw-asset', revertBtnId: 'btn-revert-hw-edit' });
isEditMode = false;
});
deleteBtn.addEventListener('click', () => {
if (currentAsset && confirm('정말 삭제하시겠습니까?')) {
deleteHardwareAsset(currentAsset.id);
onSave();
closeModalAction();
}
});
revertBtn.addEventListener('click', () => {
if (currentAsset) {
fillHwFormData(currentAsset);
setEditLock('hw-asset-form', 'view', { saveBtnId: 'btn-save-hw-asset', revertBtnId: 'btn-revert-hw-edit' });
isEditMode = false;
}
});
// 이력 추가 관련 이벤트
const logModal = document.getElementById('hw-log-modal')!;
document.getElementById('btn-add-hw-log')?.addEventListener('click', () => {
(document.getElementById('new-hw-log-date') as HTMLInputElement).value = new Date().toISOString().split('T')[0];
(document.getElementById('new-hw-log-details') as HTMLTextAreaElement).value = '';
logModal.classList.remove('hidden');
});
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 (!details) {
alert('내용을 입력해주세요.');
return;
}
const newLog: HardwareLog = {
id: Math.random().toString(36).substring(2, 9),
assetId: currentAsset.id,
date,
details,
user: '관리자'
};
if (!state.masterData.logs) state.masterData.logs = [];
state.masterData.logs.unshift(newLog);
renderHwHistory(currentAsset.id);
logModal.classList.add('hidden');
});
}