+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
원격 접속 정보
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
설치 위치
+
+
+
+
+
@@ -280,8 +328,13 @@ class HwAssetModal extends BaseModal {
this.setEditLockMode('view');
if (this.currentAsset) this.fillFormData(this.currentAsset);
this.updateMapButtonVisibility();
+ this.toggleEditOnlyBtns(false);
});
+ // 동적 볼륨 추가 기능 연결
+ const btnAddVolume = document.getElementById('btn-add-volume')!;
+ btnAddVolume.addEventListener('click', () => this.addVolumeRow());
+
const fileInput = document.getElementById('hw-approval_document_file') as HTMLInputElement;
const fileNameDisplay = document.getElementById('hw-file-name-display');
const fileLinkContainer = document.getElementById('hw-file-link-container');
@@ -317,12 +370,25 @@ class HwAssetModal extends BaseModal {
this.isEditMode = true;
this.updateMapButtonVisibility();
this.toggleFileUploadUI(true);
+ this.toggleEditOnlyBtns(true);
return;
}
+
+ // 동적 볼륨 데이터 수집 및 배열 생성
+ const vols: any[] = [];
+ document.querySelectorAll('#hw-volume-container .volume-row').forEach((row, idx) => {
+ const type = (row.querySelector('.vol-type') as HTMLSelectElement).value;
+ const cap = (row.querySelector('.vol-cap') as HTMLInputElement).value;
+ const unit = (row.querySelector('.vol-unit') as HTMLSelectElement).value;
+ if (cap) vols.push({ type, capacity: parseFloat(cap), unit, slot: idx + 1 });
+ });
+ setFieldValue('hw-volumes-data', JSON.stringify(vols));
+
const formData = new FormData(this.formEl!);
const updated = { ...this.currentAsset };
formData.forEach((value, key) => { if (key !== 'id') updated[key] = value; });
updated.location = getFieldValue('hw-bldg-select');
+
if (await saveAsset(this.getCategoryKey(updated), updated)) {
alert(UI_TEXT.MESSAGES.SAVE_SUCCESS);
onSave(); this.close(); closeModals();
@@ -330,6 +396,44 @@ class HwAssetModal extends BaseModal {
});
}
+ private addVolumeRow(vol: any = { type: 'SSD', capacity: '', unit: 'GB' }) {
+ const container = document.getElementById('hw-volume-container');
+ if (!container) return;
+
+ const row = document.createElement('div');
+ row.style.display = 'flex';
+ row.style.gap = '8px';
+ row.style.alignItems = 'center';
+ row.className = 'volume-row';
+
+ const inputStyle = 'height: 38px !important; box-sizing: border-box !important; font-size: 13px; margin: 0; padding: 0 8px;';
+
+ row.innerHTML = `
+
+
+
+
+ `;
+
+ row.querySelector('.btn-remove-vol')?.addEventListener('click', () => row.remove());
+ container.appendChild(row);
+ }
+
+ private toggleEditOnlyBtns(isEdit: boolean) {
+ const addBtn = document.getElementById('btn-add-volume');
+ if (addBtn) addBtn.style.display = isEdit ? 'inline-flex' : 'none';
+ document.querySelectorAll('.edit-only-btn').forEach(btn => {
+ (btn as HTMLElement).style.display = isEdit ? 'inline-flex' : 'none';
+ });
+ }
+
protected fillFormData(asset: any): void {
setFieldValue('hw-id', asset.id);
setFieldValue('hw-asset_code', asset.asset_code || '');
@@ -347,12 +451,35 @@ class HwAssetModal extends BaseModal {
setFieldValue('hw-user_position', asset.user_position || '');
setFieldValue('hw-previous_user', asset.previous_user || '');
setFieldValue('hw-model_name', asset.model_name || '');
+ setFieldValue('hw-asset_mfr', asset.asset_mfr || '');
+ setFieldValue('hw-os', asset.os || '');
setFieldValue('hw-cpu', asset.cpu || '');
setFieldValue('hw-ram', asset.ram || '');
- setFieldValue('hw-os', asset.os || '');
- setFieldValue('hw-mac_address', asset.mac_address || '');
+ setFieldValue('hw-gpu', asset.gpu || '');
+ setFieldValue('hw-mainboard', asset.mainboard || '');
+
+ // 동적 볼륨 렌더링 초기화 및 생성
+ const volumeContainer = document.getElementById('hw-volume-container');
+ if (volumeContainer) {
+ volumeContainer.innerHTML = '';
+ let vols = [];
+ try {
+ vols = asset.volumes ? (typeof asset.volumes === 'string' ? JSON.parse(asset.volumes) : asset.volumes) : [];
+ } catch(e) {}
+ vols.forEach((v: any) => this.addVolumeRow(v));
+ }
+
setFieldValue('hw-ip_address', asset.ip_address || '');
+ setFieldValue('hw-ip_address_2', asset.ip_address_2 || '');
+ setFieldValue('hw-mac_address', asset.mac_address || '');
+ setFieldValue('hw-remote_tool', asset.remote_tool || '');
+ setFieldValue('hw-remote_id', asset.remote_id || '');
+ setFieldValue('hw-remote_pw', asset.remote_pw || '');
setFieldValue('hw-monitoring', asset.monitoring || '비대상');
+ setFieldValue('hw-serial_num', asset.serial_num || '');
+ setFieldValue('hw-monitor_inch', asset.monitor_inch || '');
+ setFieldValue('hw-volume', asset.volume || '');
+ setFieldValue('hw-asset_count', asset.asset_count || '');
setFieldValue('hw-purchase_date', asset.purchase_date || '');
setFieldValue('hw-purchase_vendor', asset.purchase_vendor || '');
setFieldValue('hw-purchase_amount', asset.purchase_amount || '');
@@ -379,6 +506,7 @@ class HwAssetModal extends BaseModal {
const genBtn = document.getElementById('btn-gen-hw-code');
if (genBtn) genBtn.style.display = (mode === 'add') ? 'inline-flex' : 'none';
this.toggleFileUploadUI(mode !== 'view');
+ this.toggleEditOnlyBtns(mode !== 'view');
this.updateMapButtonVisibility(asset);
this.applyRoleVisibility();
}
@@ -392,15 +520,41 @@ class HwAssetModal extends BaseModal {
const category = (document.getElementById('hw-category') as HTMLSelectElement)?.value || '';
const type = (document.getElementById('hw-asset_type') as HTMLSelectElement)?.value || '';
- // 인프라 장비 (서버, 서버PC, 저장시스템 등)
- const isInfra = ['서버', '저장매체', '네트워크', '공간정보장비'].includes(category) || type.includes('서버') || type.includes('저장시스템');
- // 개인 장비 (PC, 노트북) - '서버PC'는 제외
- const isPersonal = (['PC', '노트북'].includes(category) || type.includes('개인PC') || type.includes('노트북')) && !type.includes('서버PC');
+ // 인프라 장비 (서버, 저장매체, 네트워크, 보안장비, 공간정보장비, 서버PC)
+ const infraCategories = ['서버', '저장매체', '네트워크', '보안장비', '공간정보장비'];
+ const isInfra = infraCategories.includes(category) || type.includes('서버') || type.includes('저장시스템');
- // JS에서 display: block을 강제하지 않고, 빈 문자열로 설정하여 CSS의 flex가 작동하게 함
- document.querySelectorAll('.server-only').forEach(el => (el as HTMLElement).style.display = isInfra ? '' : 'none');
- document.querySelectorAll('.infra-only').forEach(el => (el as HTMLElement).style.display = isInfra ? '' : 'none');
- document.querySelectorAll('.user-tracking-field').forEach(el => (el as HTMLElement).style.display = isPersonal ? '' : 'none');
+ // 개인 장비 (PC, 노트북, 모바일, 태블릿) - '서버PC'는 제외
+ const personalCategories = ['PC', '노트북', '모바일', '태블릿'];
+ const isPersonal = (personalCategories.includes(category) || type.includes('개인PC') || type.includes('노트북')) && !type.includes('서버PC');
+
+ // 시스템 사양 (PC, 서버 등)
+ const specCategories = ['PC', '서버', '노트북', '스토리지', '워크스테이션'];
+ const hasSpec = specCategories.includes(category) || type.includes('서버PC');
+
+ // 네트워크 정보 (IP/MAC)
+ const noNetCategories = ['저장매체', '네트워크', '공간정보장비', 'PC부품', '사무가구'];
+ const showNet = (isInfra || isPersonal) && !noNetCategories.includes(category);
+
+ // 시리얼 번호
+ const hasSN = !['사무가구', 'PC부품'].includes(category);
+
+ // 수량/용량 전용 (부품)
+ const isParts = ['PC부품', '사무가구'].includes(category);
+
+ // 원격 접속 (서버 전용)
+ const showRemote = category === '서버' || type.includes('서버');
+
+ // JS에서 display: block 강제 대신 빈 문자열 할당하여 네이티브 CSS flex 활용
+ document.querySelectorAll('.remote-section, .remote-field, .monitoring-field').forEach(el => (el as HTMLElement).style.display = showRemote ? '' : 'none');
+ document.querySelectorAll('.net-only').forEach(el => (el as HTMLElement).style.display = showNet ? '' : 'none');
+ document.querySelectorAll('.spec-only').forEach(el => (el as HTMLElement).style.display = hasSpec ? '' : 'none');
+ document.querySelectorAll('.location-section, .location-field').forEach(el => (el as HTMLElement).style.display = (isInfra || category === '공간정보장비') ? '' : 'none');
+ document.querySelectorAll('.org-user-section, .org-user-field').forEach(el => (el as HTMLElement).style.display = (isPersonal || isParts || category === '업무지원장비') ? '' : 'none');
+ document.querySelectorAll('.personal-only').forEach(el => (el as HTMLElement).style.display = isPersonal ? '' : 'none');
+ document.querySelectorAll('.sn-only').forEach(el => (el as HTMLElement).style.display = hasSN ? '' : 'none');
+ document.querySelectorAll('.monitor-only').forEach(el => (el as HTMLElement).style.display = type.includes('모니터') ? '' : 'none');
+ document.querySelectorAll('.parts-only').forEach(el => (el as HTMLElement).style.display = isParts ? '' : 'none');
}
private updateMapButtonVisibility(asset?: any) {
diff --git a/src/components/Modal/SharedData.ts b/src/components/Modal/SharedData.ts
index 31e5b56..3cc2071 100644
--- a/src/components/Modal/SharedData.ts
+++ b/src/components/Modal/SharedData.ts
@@ -20,8 +20,8 @@ export const CATEGORY_TYPE_MAP: Record
= {
'PC부품': ['CPU', 'RAM', 'GPU', 'SSD', 'HDD', 'RAM', '모니터'],
'공간정보장비': ['드론', '측량장비', '보조기기'],
'업무지원장비': ['카메라', '스피커', 'TV', '모바일', '유선전화기', 'XR', '프린터', '전산소모품'],
- '외부': ['영구', '구독'],
- '내부': ['판매용', 'Solutions', 'Inhouse', 'Engine&Module'],
+ '외부SW': ['영구', '구독'],
+ '내부SW': ['판매용', 'Solutions', 'Inhouse', 'Engine&Module'],
'비용관리': ['클라우드', '도메인', '전화', '인터넷', '이메일'],
'내빈/외빈': ['선물'],
'시설자산': ['사무가구']
diff --git a/src/components/Navigation.ts b/src/components/Navigation.ts
index 9dd3668..a3b050b 100644
--- a/src/components/Navigation.ts
+++ b/src/components/Navigation.ts
@@ -7,7 +7,7 @@ const MENU_CONFIG: any = {
},
sw: {
label: '소프트웨어',
- tabs: ['외부', '내부']
+ tabs: ['외부SW', '내부SW']
},
ops: {
label: '운영지원',
diff --git a/src/core/schema.ts b/src/core/schema.ts
index fa4934d..ba4ed39 100644
--- a/src/core/schema.ts
+++ b/src/core/schema.ts
@@ -120,12 +120,12 @@ export const PAGE_DESCRIPTIONS: Record sortAssets(isInternal ? state.masterData.swInternal : state.masterData.swExternal),
searchKeys: ['PRODUCT_NAME', 'CURRENT_USER', 'CURRENT_DEPT', 'ASSET_TYPE'],
emptyMessage: '검색 결과가 없습니다.',
diff --git a/src/views/SW_Table.ts b/src/views/SW_Table.ts
index a02684a..10e408d 100644
--- a/src/views/SW_Table.ts
+++ b/src/views/SW_Table.ts
@@ -41,7 +41,7 @@ export function renderSWTable(mainContent: HTMLElement) {
container.innerHTML = `"${tab}" 탭에 대한 하드웨어 리스트 뷰가 정의되지 않았습니다.
`;
}
} else if (state.activeCategory === 'sw') {
- if (tab === '외부' || tab === '내부') {
+ if (tab === '외부SW' || tab === '내부SW') {
renderSwList(container);
} else {
container.innerHTML = `"${tab}" 탭에 대한 소프트웨어 리스트 뷰가 정의되지 않았습니다.
`;