Merge latest code from main into server_dashboard and resolve conflicts

This commit is contained in:
thlee3 committed 2026-04-23 09:35:53 +09:00
commit 11e2f3b4ca
30 files changed
+7014 -2186

No files matched your search

+371 -292
View File
@@ -1,352 +1,431 @@
import { state } from '../../core/state';
import { HardwareAsset } from '../../core/excelHandler';
import { createIcons, Paperclip } from 'lucide';
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 wide">
<div class="modal-content modal-lg">
<div class="modal-header">
<h2 id="hw-modal-title">자산 상세 정보</h2>
<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">
<form id="hw-asset-form" class="grid-form">
<input type="hidden" id="hw-asset-id" />
<input type="hidden" id="hw-asset-type" />
<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: 기본 정보 -->
<div class="form-section-title">기본 정보 (Identity)</div>
<div class="form-group">
<label for="hw-법인">구매법인</label>
<input type="text" id="hw-법인" required />
</div>
<div class="form-group">
<label for="hw-자산코드">자산번호/코드</label>
<input type="text" id="hw-자산코드" required />
</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-현사용조직" />
</div>
<div class="form-group">
<label for="hw-이전사용조직">이전 사용조직</label>
<input type="text" id="hw-이전사용조직" />
</div>
<div class="form-group server-only">
<label for="hw-용도">용도</label>
<input type="text" id="hw-용도" />
</div>
<div class="form-group server-only">
<label for="hw-상세">상세 내용</label>
<input type="text" id="hw-상세" />
</div>
<div class="form-group non-server">
<label for="hw-명칭">명칭</label>
<input type="text" id="hw-명칭" />
</div>
<div class="form-group full-width server-only">
<label for="hw-비고">비고</label>
<input type="text" id="hw-비고" />
<!-- 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>
<!-- Group 2: 네트워크 정보 -->
<div class="form-section-title server-only">네트워크 정보 (Connectivity) <span class="badge-security" style="margin-left: 0.5rem;">보안 정보</span></div>
<div class="form-group server-only">
<label for="hw-IP주소">IP 주소 1</label>
<input type="text" id="hw-IP주소" />
</div>
<div class="form-group server-only">
<label for="hw-IP2">IP 주소 2</label>
<input type="text" id="hw-IP2" />
</div>
<div class="form-group server-only">
<label for="hw-원격접속">원격 도구</label>
<input type="text" id="hw-원격접속" />
</div>
<div class="form-group server-only">
<label for="hw-서버ID">서버 ID</label>
<input type="text" id="hw-서버ID" />
</div>
<div class="form-group server-only">
<label for="hw-서버PW">서버 PW</label>
<input type="text" id="hw-서버PW" />
</div>
<div class="form-group non-server" id="hw-IP주소-group">
<label for="hw-IP주소-non-server">IP 주소</label>
<input type="text" id="hw-IP주소-non-server" />
</div>
<!-- Group 3: 시스템 사양 -->
<div class="form-section-title">시스템 사양 (Specifications)</div>
<div class="form-group">
<label for="hw-모델명">모델명</label>
<input type="text" id="hw-모델명" />
</div>
<div class="form-group">
<label for="hw-OS">운영체제 (OS)</label>
<input type="text" id="hw-OS" />
</div>
<div class="form-group">
<label for="hw-CPU">CPU 사양</label>
<input type="text" id="hw-CPU" />
</div>
<div class="form-group">
<label for="hw-RAM">RAM 용량</label>
<input type="text" id="hw-RAM" />
</div>
<div class="form-group">
<label for="hw-SSD1">Storage 1 (SSD/HDD)</label>
<input type="text" id="hw-SSD1" />
</div>
<div class="form-group">
<label for="hw-SSD2">Storage 2 (SSD/HDD)</label>
<input type="text" id="hw-SSD2" />
</div>
<div class="form-group server-only">
<label for="hw-모니터링">모니터링 여부</label>
<input type="text" id="hw-모니터링" />
</div>
<div class="form-group" id="hw-비품유형-group" style="display:none;">
<label for="hw-비품유형">비품유형</label>
<select id="hw-비품유형">
<option value="노트북">노트북</option><option value="태블릿">태블릿</option><option value="휴대폰">휴대폰</option>
</select>
</div>
<div class="form-group full-width non-server">
<label for="hw-HW사양">H/W 사양 상세</label>
<textarea id="hw-HW사양" rows="2"></textarea>
</div>
<!-- Group 4: 관리 및 운영 -->
<div class="form-section-title">관리 및 운영 (Operation)</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-담당자_정" />
</div>
<div class="form-group">
<label for="hw-담당자_부">담당자 (부)</label>
<input type="text" id="hw-담당자_부" />
</div>
<div class="form-group non-server">
<label for="hw-구매일">구매일</label>
<input type="text" id="hw-구매일" />
</div>
<div class="form-group non-server">
<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>품의서 (파일 증빙)</label>
<div style="display:flex; align-items:center; gap:0.5rem;">
<input type="file" id="hw-품의서" />
<span id="hw-품의서명" style="font-size:0.75rem; color:var(--text-light)"></span>
<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>
</form>
</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-cancel-hw-modal" class="btn btn-outline">닫기</button>
<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>
`;
export function openHwModal(asset: HardwareAsset) {
currentAsset = asset;
isEditMode = false;
const modal = document.getElementById('hw-asset-modal')!;
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')!;
form.reset();
form.classList.remove('is-edit-mode');
form.classList.add('is-view-mode');
saveBtn.textContent = '수정';
revertBtn.classList.add('hidden');
fillHwFormData(asset);
modal.classList.remove('hidden');
createIcons({ icons: { Paperclip } });
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 fillHwFormData(asset: HardwareAsset) {
(document.getElementById('hw-asset-id') as HTMLInputElement).value = asset.id;
(document.getElementById('hw-asset-type') as HTMLInputElement).value = asset.type;
(document.getElementById('hw-법인') as HTMLInputElement).value = asset.법인;
(document.getElementById('hw-자산코드') as HTMLInputElement).value = asset.자산코드;
(document.getElementById('hw-위치') as HTMLInputElement).value = asset.위치;
(document.getElementById('hw-현사용조직') as HTMLInputElement).value = asset.현사용조직 || '';
(document.getElementById('hw-이전사용조직') as HTMLInputElement).value = asset.이전사용조직 || '';
(document.getElementById('hw-모델명') as HTMLInputElement).value = asset.모델명 || '';
(document.getElementById('hw-OS') as HTMLInputElement).value = asset.OS || '';
(document.getElementById('hw-CPU') as HTMLInputElement).value = asset.CPU || '';
(document.getElementById('hw-RAM') as HTMLInputElement).value = asset.RAM || '';
(document.getElementById('hw-SSD1') as HTMLInputElement).value = asset.SSD1 || '';
(document.getElementById('hw-SSD2') as HTMLInputElement).value = asset.SSD2 || '';
(document.getElementById('hw-구매일') as HTMLInputElement).value = asset.구매일 || '';
(document.getElementById('hw-담당자_정') as HTMLInputElement).value = asset.담당자_정 || asset.관리자 || '';
(document.getElementById('hw-담당자_부') as HTMLInputElement).value = asset.담당자_부 || '';
(document.getElementById('hw-품의서명') as HTMLElement).textContent = asset.품의서명 || '';
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');
const equipGroup = document.getElementById('hw-비품유형-group')!;
if (asset.type === '서버') {
serverOnly.forEach(el => (el as HTMLElement).style.display = 'flex');
nonServer.forEach(el => (el as HTMLElement).style.display = 'none');
equipGroup.style.display = 'none';
serverOnly.forEach(el => (el as HTMLElement).style.display = isServer ? 'block' : 'none');
nonServer.forEach(el => (el as HTMLElement).style.display = isServer ? 'none' : 'block');
(document.getElementById('hw-용도') as HTMLInputElement).value = asset.용도 || '';
(document.getElementById('hw-상세') as HTMLInputElement).value = asset.상세 || '';
(document.getElementById('hw-비고') as HTMLInputElement).value = asset.비고 || '';
(document.getElementById('hw-IP주소') as HTMLInputElement).value = asset.IP주소 || '';
(document.getElementById('hw-IP2') as HTMLInputElement).value = (asset as any).IP2 || '';
(document.getElementById('hw-원격접속') as HTMLInputElement).value = asset.원격접속 || '';
(document.getElementById('hw-서버ID') as HTMLInputElement).value = (asset as any).서버ID || '';
(document.getElementById('hw-서버PW') as HTMLInputElement).value = (asset as any).서버PW || '';
(document.getElementById('hw-모니터링') as HTMLInputElement).value = asset.모니터링 || '';
} else {
serverOnly.forEach(el => (el as HTMLElement).style.display = 'none');
nonServer.forEach(el => (el as HTMLElement).style.display = 'flex');
(document.getElementById('hw-명칭') as HTMLInputElement).value = asset.명칭 || '';
(document.getElementById('hw-구매일') as HTMLInputElement).value = asset.구매일 || '';
(document.getElementById('hw-금액') as HTMLInputElement).value = asset.금액 || '';
(document.getElementById('hw-HW사양') as HTMLTextAreaElement).value = asset.HW사양 || '';
(document.getElementById('hw-IP주소-non-server') as HTMLInputElement).value = asset.IP주소 || '';
if (asset.type === '전산비품') {
equipGroup.style.display = 'flex';
(document.getElementById('hw-비품유형') as HTMLSelectElement).value = asset.비품유형 || '노트북';
} else {
equipGroup.style.display = 'none';
}
}
if (groups.detailPurpose) groups.detailPurpose.style.display = isServer ? 'block' : 'none';
}
export function initHwModal(onSave: () => void, closeModals: () => void) {
// HTML 주입
if (!document.getElementById('hw-asset-modal')) {
document.body.insertAdjacentHTML('beforeend', HW_MODAL_HTML);
}
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 cancelBtn = document.getElementById('btn-cancel-hw-modal')!;
const closeBtnBottom = document.getElementById('btn-close-modal-bottom')!;
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 revertBtn = document.getElementById('btn-revert-hw-edit')!;
const closeModal = () => {
closeModals();
const closeModalAction = () => {
modal.classList.add('hidden');
isEditMode = false;
};
const switchToViewMode = () => {
isEditMode = false;
form.classList.remove('is-edit-mode');
form.classList.add('is-view-mode');
saveBtn.textContent = '수정';
revertBtn.classList.add('hidden');
if (currentAsset) fillHwFormData(currentAsset);
};
closeBtn.addEventListener('click', closeModal);
cancelBtn.addEventListener('click', closeModal);
modal.addEventListener('click', (e) => { if (e.target === modal) closeModal(); });
revertBtn.addEventListener('click', () => { switchToViewMode(); });
closeBtn.addEventListener('click', closeModalAction);
closeBtnBottom.addEventListener('click', closeModalAction);
saveBtn.addEventListener('click', () => {
if (!currentAsset) return;
if (!isEditMode) {
setEditLock('hw-asset-form', 'edit', { saveBtnId: 'btn-save-hw-asset', revertBtnId: 'btn-revert-hw-edit' });
isEditMode = true;
form.classList.remove('is-view-mode');
form.classList.add('is-edit-mode');
saveBtn.textContent = '저장';
revertBtn.classList.remove('hidden');
return;
}
const assetId = (document.getElementById('hw-asset-id') as HTMLInputElement).value;
const type = (document.getElementById('hw-asset-type') as HTMLInputElement).value;
const updated: HardwareAsset = {
const updated: any = {
...currentAsset,
법인: (document.getElementById('hw-법인') as HTMLInputElement).value,
자산코드: (document.getElementById('hw-자산코드') as HTMLInputElement).value,
위치: (document.getElementById('hw-위치') as HTMLInputElement).value,
현사용조직: (document.getElementById('hw-현사용조직') as HTMLInputElement).value,
이전사용조직: (document.getElementById('hw-이전사용조직') as HTMLInputElement).value,
모델명: (document.getElementById('hw-모델명') as HTMLInputElement).value,
OS: (document.getElementById('hw-OS') as HTMLInputElement).value,
CPU: (document.getElementById('hw-CPU') as HTMLInputElement).value,
RAM: (document.getElementById('hw-RAM') as HTMLInputElement).value,
SSD1: (document.getElementById('hw-SSD1') as HTMLInputElement).value,
SSD2: (document.getElementById('hw-SSD2') as HTMLInputElement).value,
구매일: (document.getElementById('hw-구매일') as HTMLInputElement).value,
담당자_정: (document.getElementById('hw-담당자_정') as HTMLInputElement).value,
관리자: (document.getElementById('hw-담당자_정') as HTMLInputElement).value,
담당자_부: (document.getElementById('hw-담당자_부') as HTMLInputElement).value,
관리번호: 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-비고')
};
if (type === '서버') {
updated.용도 = (document.getElementById('hw-용도') as HTMLInputElement).value;
updated.상세 = (document.getElementById('hw-상세') as HTMLInputElement).value;
updated.비고 = (document.getElementById('hw-비고') as HTMLInputElement).value;
updated.IP주소 = (document.getElementById('hw-IP주소') as HTMLInputElement).value;
(updated as any).IP2 = (document.getElementById('hw-IP2') as HTMLInputElement).value;
updated.원격접속 = (document.getElementById('hw-원격접속') as HTMLInputElement).value;
(updated as any).서버ID = (document.getElementById('hw-서버ID') as HTMLInputElement).value;
(updated as any).서버PW = (document.getElementById('hw-서버PW') as HTMLInputElement).value;
updated.모니터링 = (document.getElementById('hw-모니터링') as HTMLInputElement).value;
} else {
updated.명칭 = (document.getElementById('hw-명칭') as HTMLInputElement).value;
updated.구매일 = (document.getElementById('hw-구매일') as HTMLInputElement).value;
updated.금액 = (document.getElementById('hw-금액') as HTMLInputElement).value;
updated.HW사양 = (document.getElementById('hw-HW사양') as HTMLTextAreaElement).value;
updated.IP주소 = (document.getElementById('hw-IP주소-non-server') as HTMLInputElement).value;
if (type === '전산비품') {
updated.비품유형 = (document.getElementById('hw-비품유형') as HTMLSelectElement).value;
}
}
const idx = state.masterData.hw.findIndex(a => a.id === assetId);
if (idx > -1) {
state.masterData.hw[idx] = updated;
onSave();
switchToViewMode();
}
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) return;
if (confirm('정말로 이 자산을 삭제하시겠습니까?')) {
state.masterData.hw = state.masterData.hw.filter(a => a.id !== currentAsset!.id);
if (currentAsset && confirm('정말 삭제하시겠습니까?')) {
deleteHardwareAsset(currentAsset.id);
onSave();
closeModal();
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');
});
}