merge: 통합 HW 모달 구현 (PC 상세유형 복구 + 전산비품/모바일 확장 통합)
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { state, saveHardwareAsset, deleteHardwareAsset } from '../../core/state';
|
||||
import { HardwareAsset, MasterAssetData } from '../../core/excelHandler';
|
||||
import { HardwareAsset, MasterAssetData, HardwareLog } from '../../core/excelHandler';
|
||||
import { openModal, closeModals } from './BaseModal';
|
||||
import { createIcons, Paperclip } from 'lucide';
|
||||
import { createIcons, Paperclip, History, Plus, X, Save, Edit2, RotateCcw } from 'lucide';
|
||||
import { CORP_LIST, ORG_LIST, HW_TYPE_LIST, LOCATION_DATA, TYPE_PREFIX_MAP } from './SharedData';
|
||||
import {
|
||||
generateOptionsHTML,
|
||||
@@ -16,6 +16,8 @@ import {
|
||||
let currentAsset: HardwareAsset | null = null;
|
||||
let isEditMode = false;
|
||||
|
||||
const STATUS_LIST = ['대여중', '보관중', '수리중', '기타'];
|
||||
|
||||
const HW_MODAL_HTML = `
|
||||
<div id="hw-asset-modal" class="modal-overlay hidden">
|
||||
<div class="modal-content wide">
|
||||
@@ -24,162 +26,162 @@ const HW_MODAL_HTML = `
|
||||
<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: 기본 정보 (Identity) -->
|
||||
<div class="form-section-title">기본 정보 (Identity)</div>
|
||||
<div class="form-group">
|
||||
<label for="hw-법인">구매법인</label>
|
||||
<select id="hw-법인" required>${generateOptionsHTML(CORP_LIST)}</select>
|
||||
<!-- Group 1: 기본 정보 (Identity) -->
|
||||
<div class="form-section-title">기본 정보 (Identity)</div>
|
||||
<div class="form-group">
|
||||
<label for="hw-법인">구매법인</label>
|
||||
<select id="hw-법인" required>${generateOptionsHTML(CORP_LIST)}</select>
|
||||
</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="서버">서버</option>
|
||||
<option value="개인PC">개인PC</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-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="text" 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-구매일" />
|
||||
</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="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" style="white-space:nowrap; padding:0 10px; font-size:0.8rem;">번호 생성</button>
|
||||
|
||||
<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 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" id="hw-유형-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="서버">서버</option>
|
||||
<option value="개인PC">개인PC</option>
|
||||
</select>
|
||||
</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" id="hw-명칭-group">
|
||||
<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-비고" />
|
||||
</div>
|
||||
|
||||
<!-- Group 2: 네트워크 정보 (Connectivity) -->
|
||||
<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-remote-group">
|
||||
<label for="hw-원격접속">원격 도구 (Anydesk/Chrome 등)</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="text" 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>
|
||||
|
||||
<!-- Group 3: 시스템 사양 (Specifications) -->
|
||||
<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사양">H/W 사양 상세</label>
|
||||
<textarea id="hw-HW사양" rows="2"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Group 4: 관리 및 운영 (Operation) -->
|
||||
<div class="form-section-title" id="hw-op-title">관리 및 운영 (Operation)</div>
|
||||
<div class="form-group hw-location-field">
|
||||
<label for="hw-위치-빌딩">설치위치 (건물)</label>
|
||||
<select id="hw-위치-빌딩">${generateOptionsHTML(Object.keys(LOCATION_DATA))}</select>
|
||||
</div>
|
||||
<div class="form-group hw-location-field">
|
||||
<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-위치-기타" placeholder="상세 위치를 입력하세요" />
|
||||
</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" id="hw-purchase-date-group">
|
||||
<label for="hw-구매일">구매일</label>
|
||||
<input type="text" id="hw-구매일" />
|
||||
</div>
|
||||
<div class="form-group non-server" id="hw-price-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>품의서 (파일 증빙)</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>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button id="btn-delete-hw-asset" class="btn btn-outline btn-danger">삭제</button>
|
||||
@@ -191,13 +193,112 @@ const HW_MODAL_HTML = `
|
||||
</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 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 serverOnly = document.querySelectorAll('.server-only');
|
||||
const nonServer = document.querySelectorAll('.non-server');
|
||||
const opOnly = document.querySelectorAll('.op-only');
|
||||
const standardLoc = document.querySelectorAll('.loc-standard');
|
||||
|
||||
// 1. 초기화
|
||||
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'; });
|
||||
|
||||
// 2. 분류 판별
|
||||
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 === '노트북';
|
||||
|
||||
// 3. 레이아웃 적용
|
||||
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 (isPcType) {
|
||||
if (groups.detailPurpose) groups.detailPurpose.style.display = 'flex';
|
||||
if (detailPurpose === '서버') {
|
||||
serverOnly.forEach(el => (el as HTMLElement).style.display = 'flex');
|
||||
} else {
|
||||
nonServer.forEach(el => (el as HTMLElement).style.display = 'flex');
|
||||
}
|
||||
if (groups.specTitle) groups.specTitle.style.display = 'flex';
|
||||
if (groups.networkTitle) groups.networkTitle.style.display = detailPurpose === '서버' ? 'flex' : 'none';
|
||||
} else if (upperType.includes('서버') || ['스토리지', 'NAS', 'DAS'].includes(upperType)) {
|
||||
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';
|
||||
}
|
||||
|
||||
if (groups.opTitle) groups.opTitle.style.display = 'flex';
|
||||
}
|
||||
|
||||
export function openHwModal(asset: HardwareAsset, mode: 'view' | 'add' = 'view') {
|
||||
currentAsset = asset;
|
||||
const modal = document.getElementById('hw-asset-modal')!;
|
||||
|
||||
// 1. 잠금 상태 통합 제어 (데이터 유무가 아닌 호출 mode에만 의존)
|
||||
setEditLock('hw-asset-form', mode, {
|
||||
saveBtnId: 'btn-save-hw-asset',
|
||||
revertBtnId: 'btn-revert-hw-edit',
|
||||
@@ -205,94 +306,12 @@ export function openHwModal(asset: HardwareAsset, mode: 'view' | 'add' = 'view')
|
||||
});
|
||||
|
||||
isEditMode = (mode === 'add');
|
||||
|
||||
// 2. 데이터 바인딩
|
||||
fillHwFormData(asset);
|
||||
applyTypeSpecificUI(asset.type);
|
||||
renderHwHistory(asset.id);
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
applyTypeSpecificUI(asset.type);
|
||||
createIcons({ icons: { Paperclip } });
|
||||
}
|
||||
|
||||
function applyTypeSpecificUI(type: string) {
|
||||
const detailPurpose = getFieldValue('hw-상세용도');
|
||||
const form = document.getElementById('hw-asset-form') as HTMLFormElement;
|
||||
if (!form) return;
|
||||
|
||||
const serverOnly = document.querySelectorAll('.server-only');
|
||||
const nonServer = document.querySelectorAll('.non-server');
|
||||
const locationFields = document.querySelectorAll('.hw-location-field');
|
||||
|
||||
const groups: Record<string, HTMLElement | null> = {
|
||||
detailPurpose: document.getElementById('hw-상세용도-group'),
|
||||
model: document.getElementById('hw-model-group'),
|
||||
ip: document.getElementById('hw-ip-group'),
|
||||
ip2: document.getElementById('hw-ip2-group'),
|
||||
remote: document.getElementById('hw-remote-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'),
|
||||
monitoring: document.getElementById('hw-monitoring-group'),
|
||||
serverId: document.getElementById('hw-server-id-group'),
|
||||
serverPw: document.getElementById('hw-server-pw-group'),
|
||||
hwSpec: document.getElementById('hw-hwspec-group'),
|
||||
ipNonServer: document.getElementById('hw-ip-non-server-group'),
|
||||
type: document.getElementById('hw-유형-group'),
|
||||
networkTitle: document.getElementById('hw-network-title'),
|
||||
specTitle: document.getElementById('hw-spec-title'),
|
||||
opTitle: document.getElementById('hw-op-title')
|
||||
};
|
||||
|
||||
// 1. 초기화
|
||||
serverOnly.forEach(el => (el as HTMLElement).style.display = 'none');
|
||||
nonServer.forEach(el => (el as HTMLElement).style.display = 'none');
|
||||
locationFields.forEach(el => (el as HTMLElement).style.display = 'none');
|
||||
Object.values(groups).forEach(g => { if (g) g.style.display = 'none'; });
|
||||
|
||||
if (groups.type) groups.type.style.display = 'flex';
|
||||
if (groups.opTitle) groups.opTitle.style.display = 'flex';
|
||||
|
||||
// 2. PC 유형일 때 상세용도 선택창 노출 (복구 핵심)
|
||||
if (type === 'PC' || type === '개인PC' || type === '노트북') {
|
||||
if (groups.detailPurpose) groups.detailPurpose.style.display = 'flex';
|
||||
|
||||
// 상세용도가 '서버'인 경우 서버용 필드 노출, 아니면 일반 PC용 필드 노출
|
||||
if (detailPurpose === '서버') {
|
||||
serverOnly.forEach(el => (el as HTMLElement).style.display = 'flex');
|
||||
locationFields.forEach(el => (el as HTMLElement).style.display = 'flex');
|
||||
if (groups.networkTitle) groups.networkTitle.style.display = 'flex';
|
||||
['ip', 'ip2', 'remote', 'serverId', 'serverPw', 'monitoring', 'model', 'os', 'cpu', 'ram', 'ssd1', 'ssd2'].forEach(k => {
|
||||
if (groups[k]) groups[k]!.style.display = 'flex';
|
||||
});
|
||||
} else {
|
||||
nonServer.forEach(el => (el as HTMLElement).style.display = 'flex');
|
||||
if (groups.specTitle) groups.specTitle.style.display = 'flex';
|
||||
['model', 'os', 'cpu', 'ram', 'ssd1', 'ssd2', 'hwSpec', 'ipNonServer'].forEach(k => {
|
||||
if (groups[k]) groups[k]!.style.display = 'flex';
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (type === '서버') {
|
||||
serverOnly.forEach(el => (el as HTMLElement).style.display = 'flex');
|
||||
locationFields.forEach(el => (el as HTMLElement).style.display = 'flex');
|
||||
Object.values(groups).forEach(g => { if (g) g.style.display = 'flex'; });
|
||||
}
|
||||
else if (['스토리지', 'NAS', 'DAS'].includes(type)) {
|
||||
serverOnly.forEach(el => (el as HTMLElement).style.display = 'flex');
|
||||
locationFields.forEach(el => (el as HTMLElement).style.display = 'flex');
|
||||
if (groups.networkTitle) groups.networkTitle.style.display = 'flex';
|
||||
if (groups.ip) groups.ip.style.display = 'flex';
|
||||
if (groups.specTitle) groups.specTitle.style.display = 'flex';
|
||||
if (groups.model) groups.model.style.display = 'flex';
|
||||
if (groups.ssd1) groups.ssd1.style.display = 'flex';
|
||||
}
|
||||
else {
|
||||
// 기타 유형 (CPU, RAM, 모바일 등)
|
||||
if (groups.specTitle) groups.specTitle.style.display = 'flex';
|
||||
if (groups.model) groups.model.style.display = 'flex';
|
||||
}
|
||||
createIcons({ icons: { X, Save, Edit2, RotateCcw, History, Plus, Paperclip } });
|
||||
}
|
||||
|
||||
function fillHwFormData(asset: HardwareAsset) {
|
||||
@@ -303,39 +322,29 @@ function fillHwFormData(asset: HardwareAsset) {
|
||||
setFieldValue('hw-현사용조직', asset.현사용조직);
|
||||
setFieldValue('hw-이전사용조직', asset.이전사용조직);
|
||||
setFieldValue('hw-상세용도', (asset as any).상세용도);
|
||||
|
||||
parseAndSetLocation(asset.위치, 'hw-위치-빌딩', 'hw-위치-상세', 'hw-위치-기타-group', 'hw-위치-기타');
|
||||
|
||||
setFieldValue('hw-유형', asset.type);
|
||||
setFieldValue('hw-모델명', asset.모델명);
|
||||
setFieldValue('hw-명칭', asset.명칭 || asset.모델명);
|
||||
setFieldValue('hw-보관위치', asset.보관위치 || '');
|
||||
setFieldValue('hw-현재상태', asset.현재상태 || '보관중');
|
||||
setFieldValue('hw-IP주소', asset.IP주소);
|
||||
setFieldValue('hw-IP2', (asset as any).IP2);
|
||||
setFieldValue('hw-원격접속', (asset as any).원격접속);
|
||||
setFieldValue('hw-서버ID', (asset as any).서버ID);
|
||||
setFieldValue('hw-서버PW', (asset as any).서버PW);
|
||||
setFieldValue('hw-모니터링', (asset as any).모니터링);
|
||||
setFieldValue('hw-OS', asset.OS);
|
||||
setFieldValue('hw-CPU', asset.CPU);
|
||||
setFieldValue('hw-RAM', asset.RAM);
|
||||
setFieldValue('hw-SSD1', asset.SSD1);
|
||||
setFieldValue('hw-SSD2', asset.SSD2);
|
||||
setFieldValue('hw-HW사양', asset.HW사양);
|
||||
setFieldValue('hw-담당자_정', asset.담당자_정 || asset.관리자);
|
||||
setFieldValue('hw-담당자_부', asset.담당자_부);
|
||||
setFieldValue('hw-구매일', asset.구매일);
|
||||
setFieldValue('hw-금액', asset.금액);
|
||||
setFieldValue('hw-비고', asset.비고);
|
||||
|
||||
const isServerGrade = asset.type === '서버' || (asset as any).상세용도 === '서버' || asset.type === '스토리지' || ['NAS', 'DAS'].includes(asset.type);
|
||||
|
||||
if (isServerGrade) {
|
||||
setFieldValue('hw-용도', asset.용도 || (asset as any).purpose);
|
||||
setFieldValue('hw-상세', asset.상세 || (asset as any).details);
|
||||
setFieldValue('hw-비고', asset.비고 || (asset as any).remarks);
|
||||
setFieldValue('hw-구매일', asset.구매일 || (asset as any).purchase_date);
|
||||
setFieldValue('hw-유형', asset.storage유형 || asset.type);
|
||||
setFieldValue('hw-IP주소', asset.IP주소 || (asset as any).ip_address);
|
||||
setFieldValue('hw-IP2', (asset as any).IP2 || (asset as any).ip_address_2);
|
||||
setFieldValue('hw-원격접속', asset.원격접속 || (asset as any).remote_tool);
|
||||
setFieldValue('hw-서버ID', (asset as any).서버ID || (asset as any).server_id);
|
||||
setFieldValue('hw-서버PW', (asset as any).서버PW || (asset as any).server_pw);
|
||||
setFieldValue('hw-모니터링', asset.모니터링 || (asset as any).monitoring);
|
||||
} else {
|
||||
setFieldValue('hw-명칭', asset.명칭 || asset.모델명);
|
||||
setFieldValue('hw-구매일', asset.구매일 || (asset as any).purchase_date);
|
||||
setFieldValue('hw-금액', asset.금액 || (asset as any).price);
|
||||
setFieldValue('hw-HW사양', asset.HW사양 || asset.상세 || (asset as any).details);
|
||||
setFieldValue('hw-IP주소-non-server', asset.IP주소 || (asset as any).ip_address);
|
||||
}
|
||||
parseAndSetLocation(asset.위치, 'hw-위치-빌딩', 'hw-위치-상세', 'hw-위치-기타-group', 'hw-위치-기타');
|
||||
}
|
||||
|
||||
export function initHwModal(onSave: () => void, closeModals: () => void) {
|
||||
@@ -349,7 +358,9 @@ export function initHwModal(onSave: () => void, closeModals: () => void) {
|
||||
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));
|
||||
});
|
||||
@@ -361,97 +372,79 @@ export function initHwModal(onSave: () => void, closeModals: () => void) {
|
||||
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'
|
||||
});
|
||||
setEditLock('hw-asset-form', 'view', { saveBtnId: 'btn-save-hw-asset', revertBtnId: 'btn-revert-hw-edit' });
|
||||
isEditMode = false;
|
||||
if (currentAsset) fillHwFormData(currentAsset);
|
||||
});
|
||||
|
||||
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 < 4) { alert('올바른 구매일(연월)을 입력해주세요.'); return; }
|
||||
const prefix = `${typeCode}-${dateStr.substring(2, 6)}-`;
|
||||
try {
|
||||
const res = await fetch(`http://localhost:3000/api/generate-asset-code?prefix=${prefix}`);
|
||||
const data = await res.json();
|
||||
if (data.nextCode) setFieldValue('hw-자산코드', data.nextCode);
|
||||
} catch (err) { alert('자산번호 생성에 실패했습니다.'); }
|
||||
});
|
||||
|
||||
saveBtn.addEventListener('click', () => {
|
||||
if (!currentAsset) return;
|
||||
if (!isEditMode) {
|
||||
setEditLock('hw-asset-form', 'edit', {
|
||||
saveBtnId: 'btn-save-hw-asset',
|
||||
revertBtnId: 'btn-revert-hw-edit'
|
||||
});
|
||||
setEditLock('hw-asset-form', 'edit', { saveBtnId: 'btn-save-hw-asset', revertBtnId: 'btn-revert-hw-edit' });
|
||||
isEditMode = true;
|
||||
applyTypeSpecificUI(getFieldValue('hw-유형'));
|
||||
return;
|
||||
}
|
||||
|
||||
const type = typeSelect.value;
|
||||
const detailPurpose = detailPurposeSelect.value;
|
||||
const type = getFieldValue('hw-유형');
|
||||
const storageLoc = getFieldValue('hw-보관위치');
|
||||
const isOpType = ['CPU', 'RAM', 'HDD', 'GPU'].some(t => type.toUpperCase().includes(t)) || type.includes('비품') || ['모바일', '태블릿', '노트북'].some(t => type.includes(t));
|
||||
|
||||
const updated: any = {
|
||||
...currentAsset,
|
||||
법인: getFieldValue('hw-법인'),
|
||||
자산코드: getFieldValue('hw-자산코드'),
|
||||
현사용조직: getFieldValue('hw-현사용조직'),
|
||||
이전사용조직: getFieldValue('hw-이전사용조직'),
|
||||
위치: getCombinedLocation('hw-위치-빌딩', 'hw-위치-상세', 'hw-위치-기타'),
|
||||
모델명: getFieldValue('hw-모델명'),
|
||||
type: type,
|
||||
상세용도: getFieldValue('hw-상세용도'),
|
||||
명칭: getFieldValue('hw-명칭'),
|
||||
보관위치: storageLoc,
|
||||
현재상태: getFieldValue('hw-현재상태'),
|
||||
OS: getFieldValue('hw-OS'),
|
||||
CPU: getFieldValue('hw-CPU'),
|
||||
RAM: getFieldValue('hw-RAM'),
|
||||
SSD1: getFieldValue('hw-SSD1'),
|
||||
SSD2: getFieldValue('hw-SSD2'),
|
||||
IP주소: getFieldValue('hw-IP주소') || getFieldValue('hw-IP주소-non-server'),
|
||||
담당자_정: getFieldValue('hw-담당자_정'),
|
||||
관리자: getFieldValue('hw-담당자_정'),
|
||||
담당자_부: getFieldValue('hw-담당자_부'),
|
||||
type: type,
|
||||
상세용도: detailPurpose
|
||||
구매일: getFieldValue('hw-구매일'),
|
||||
금액: getFieldValue('hw-금액'),
|
||||
비고: getFieldValue('hw-비고'),
|
||||
위치: isOpType ? storageLoc : getCombinedLocation('hw-위치-빌딩', 'hw-위치-상세', 'hw-위치-기타')
|
||||
};
|
||||
|
||||
if (type === '서버' || (type === 'PC' && detailPurpose === '서버') || ['스토리지', 'NAS', 'DAS'].includes(type)) {
|
||||
updated.용도 = getFieldValue('hw-용도');
|
||||
updated.상세 = getFieldValue('hw-상세');
|
||||
updated.비고 = getFieldValue('hw-비고');
|
||||
updated.storage유형 = type;
|
||||
updated.IP주소 = getFieldValue('hw-IP주소');
|
||||
updated.IP2 = getFieldValue('hw-IP2');
|
||||
updated.원격접속 = getFieldValue('hw-원격접속');
|
||||
updated.서버ID = getFieldValue('hw-서버ID');
|
||||
updated.서버PW = getFieldValue('hw-서버PW');
|
||||
updated.모니터링 = getFieldValue('hw-모니터링');
|
||||
} else {
|
||||
updated.명칭 = getFieldValue('hw-명칭');
|
||||
updated.구매일 = getFieldValue('hw-구매일');
|
||||
updated.금액 = getFieldValue('hw-금액');
|
||||
updated.HW사양 = getFieldValue('hw-HW사양');
|
||||
updated.IP주소 = getFieldValue('hw-IP주소-non-server');
|
||||
}
|
||||
|
||||
saveHardwareAsset(updated);
|
||||
onSave();
|
||||
setEditLock('hw-asset-form', 'view', {
|
||||
saveBtnId: 'btn-save-hw-asset',
|
||||
revertBtnId: 'btn-revert-hw-edit'
|
||||
});
|
||||
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('정말로 이 자산을 삭제하시겠습니까?')) {
|
||||
if (currentAsset && confirm('정말로 삭제하시겠습니까?')) {
|
||||
deleteHardwareAsset(currentAsset.id);
|
||||
onSave();
|
||||
closeModals();
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user