feat: migrate ServerPC data to asset_pc, enhance filters with location, and standardize page headers
- 서버PC 자산을 asset_pc 테이블로 통합 마이그레이션 및 스키마 확장 (위치, IP 정보 복구 완료) - 하드웨어 자산 페이지의 구매법인 필터를 자산위치 필터로 교체 및 동적 데이터 바인딩 적용 - 모든 자산 리스트 페이지 상단에 설명(Description) 필드 추가 및 헤더 표준화 - 상세 모달 내 삭제 버튼 기능 구현 및 서버PC 용도 필드 노출 오류 수정 - 현 사용조직 필터 리스트가 비어있던 DOM 셀렉터 버그 수정
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { state, saveAsset } from '../../core/state';
|
||||
import { state, saveAsset, deleteAsset } from '../../core/state';
|
||||
import { ASSET_SCHEMA, UI_TEXT } from '../../core/schema';
|
||||
import {
|
||||
generateOptionsHTML,
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
getCombinedLocation,
|
||||
applyDateMask
|
||||
} from './ModalUtils';
|
||||
import { CORP_LIST, LOCATION_DATA, ORG_LIST } from './SharedData';
|
||||
import { CORP_LIST, LOCATION_DATA, ORG_LIST, CATEGORY_TYPE_MAP, HW_STATUS_LIST } from './SharedData';
|
||||
import { createIcons, X, History, Plus, Save, Paperclip, Calendar, Monitor, Cpu, Network, ShieldCheck } from 'lucide';
|
||||
|
||||
let currentHwAsset: any | null = null;
|
||||
@@ -44,16 +44,20 @@ const HW_MODAL_HTML = `
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>${ASSET_SCHEMA.CATEGORY.ui}</label>
|
||||
<input type="text" id="hw-category" name="category" />
|
||||
<select id="hw-category" name="category">
|
||||
<option value="">선택</option>
|
||||
${generateOptionsHTML(Object.keys(CATEGORY_TYPE_MAP), '', false)}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>${ASSET_SCHEMA.ASSET_TYPE.ui}</label>
|
||||
<select id="hw-asset_type" name="asset_type">
|
||||
<option value="">구분을 먼저 선택하세요</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>${ASSET_SCHEMA.HW_STATUS.ui}</label>
|
||||
<select id="hw-hw_status" name="hw_status">
|
||||
<option value="운영">운영</option>
|
||||
<option value="재고">재고</option>
|
||||
<option value="수리">수리</option>
|
||||
<option value="폐기">폐기</option>
|
||||
</select>
|
||||
<select id="hw-hw_status" name="hw_status">${generateOptionsHTML(HW_STATUS_LIST)}</select>
|
||||
</div>
|
||||
<div class="form-group dept-field">
|
||||
<label>${ASSET_SCHEMA.CURRENT_DEPT.ui}</label>
|
||||
@@ -71,11 +75,15 @@ const HW_MODAL_HTML = `
|
||||
<label>${ASSET_SCHEMA.MANAGER_SUB.ui}</label>
|
||||
<input type="text" id="hw-manager_secondary" name="manager_secondary" />
|
||||
</div>
|
||||
<div class="form-group pc-only">
|
||||
<div class="form-group user-tracking-field">
|
||||
<label>${ASSET_SCHEMA.CURRENT_USER.ui}</label>
|
||||
<input type="text" id="hw-current_user" name="current_user" />
|
||||
<input type="text" id="hw-user_current" name="user_current" />
|
||||
</div>
|
||||
<div class="form-group pc-only">
|
||||
<div class="form-group user-tracking-field pc-only">
|
||||
<label>${ASSET_SCHEMA.USER_POSITION.ui}</label>
|
||||
<input type="text" id="hw-user_position" name="user_position" />
|
||||
</div>
|
||||
<div class="form-group user-tracking-field">
|
||||
<label>${ASSET_SCHEMA.PREV_USER.ui}</label>
|
||||
<input type="text" id="hw-previous_user" name="previous_user" />
|
||||
</div>
|
||||
@@ -88,15 +96,13 @@ const HW_MODAL_HTML = `
|
||||
<div class="form-section-title">설치 위치</div>
|
||||
<div class="form-group">
|
||||
<label>건물/위치</label>
|
||||
<select id="hw-bldg-select">${generateOptionsHTML(Object.keys(LOCATION_DATA))}</select>
|
||||
<select id="hw-bldg-select" name="location">${generateOptionsHTML(Object.keys(LOCATION_DATA))}</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>상세 위치(층/구역)</label>
|
||||
<select id="hw-floor-select"><option value="">선택</option></select>
|
||||
</div>
|
||||
<div class="form-group full-width" id="hw-loc-etc-group" style="display:none;">
|
||||
<label>기타 상세 위치</label>
|
||||
<input type="text" id="hw-loc-etc" placeholder="직접 입력" />
|
||||
<label>${ASSET_SCHEMA.LOC_DETAIL.ui}</label>
|
||||
<select id="hw-location_detail" name="location_detail">
|
||||
<option value="">선택</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Group 3: 시스템 사양 -->
|
||||
@@ -250,13 +256,52 @@ export function initHwModal(onSave: () => void, closeModals: () => void) {
|
||||
const btnCloseHeader = document.getElementById('btn-close-hw-modal')!;
|
||||
const btnCancelFooter = document.getElementById('btn-cancel-hw-modal')!;
|
||||
|
||||
bindLocationEvents('hw-bldg-select', 'hw-floor-select', 'hw-loc-etc-group', 'hw-loc-etc');
|
||||
bindLocationEvents('hw-bldg-select', 'hw-location_detail', '', '');
|
||||
applyDateMask(document.getElementById('hw-purchase_date') as HTMLInputElement);
|
||||
|
||||
// Category -> Asset Type Cascading
|
||||
const categorySelect = document.getElementById('hw-category') as HTMLSelectElement;
|
||||
const typeSelect = document.getElementById('hw-asset_type') as HTMLSelectElement;
|
||||
|
||||
categorySelect.addEventListener('change', () => {
|
||||
const selectedCat = categorySelect.value;
|
||||
const types = CATEGORY_TYPE_MAP[selectedCat] || [];
|
||||
typeSelect.innerHTML = types.length > 0
|
||||
? generateOptionsHTML(types, '', true)
|
||||
: '<option value="">구분을 먼저 선택하세요</option>';
|
||||
});
|
||||
|
||||
const closeModalAction = () => { closeModals(); isEditMode = false; };
|
||||
btnCloseHeader.addEventListener('click', closeModalAction);
|
||||
btnCancelFooter.addEventListener('click', closeModalAction);
|
||||
|
||||
deleteBtn.addEventListener('click', async () => {
|
||||
if (!currentHwAsset) return;
|
||||
if (!confirm(UI_TEXT.MESSAGES.CONFIRM_DELETE)) return;
|
||||
|
||||
let categoryKey = 'pc';
|
||||
const cat = currentHwAsset.category;
|
||||
const type = currentHwAsset.asset_type;
|
||||
const code = currentHwAsset.asset_code || '';
|
||||
|
||||
if (type === '서버PC') categoryKey = 'pc';
|
||||
else if (cat === '서버' || code.startsWith('SVR')) categoryKey = 'server';
|
||||
else if (cat === '스토리지' || code.startsWith('STO')) categoryKey = 'storage';
|
||||
else if (cat === '네트워크' || code.startsWith('NET')) categoryKey = 'network';
|
||||
else if (cat === '업무지원장비' || code.startsWith('EQP')) categoryKey = 'equipment';
|
||||
else if (cat === '공간정보장비') categoryKey = 'survey';
|
||||
else if (cat === 'PC부품') categoryKey = 'pcParts';
|
||||
else if (cat === '사무가구' || cat === '사무소모품') categoryKey = 'officeSupplies';
|
||||
else if (cat === 'PC' || code.startsWith('PC')) categoryKey = 'pc';
|
||||
|
||||
const success = await deleteAsset(categoryKey, currentHwAsset.id);
|
||||
if (success) {
|
||||
alert('성공적으로 삭제되었습니다.');
|
||||
onSave(); // Refresh list
|
||||
closeModalAction();
|
||||
}
|
||||
});
|
||||
|
||||
revertBtn.addEventListener('click', () => {
|
||||
setEditLock('hw-asset-form', 'view', { saveBtnId: 'btn-save-hw-asset', revertBtnId: 'btn-revert-hw-edit' });
|
||||
isEditMode = false;
|
||||
@@ -277,13 +322,28 @@ export function initHwModal(onSave: () => void, closeModals: () => void) {
|
||||
if (key !== 'id') updated[key] = value;
|
||||
});
|
||||
|
||||
// Handle combined location
|
||||
updated.location = getCombinedLocation('hw-bldg-select', 'hw-floor-select', 'hw-loc-etc');
|
||||
// Handle location columns:
|
||||
// 'location' stores only the building name
|
||||
// 'location_detail' is already handled via the dynamic FormData loop
|
||||
updated.location = getFieldValue('hw-bldg-select');
|
||||
|
||||
let categoryKey = 'pc';
|
||||
if (updated.asset_code?.startsWith('SVR')) categoryKey = 'server';
|
||||
else if (updated.asset_code?.startsWith('STO')) categoryKey = 'storage';
|
||||
else if (updated.asset_code?.startsWith('EQP')) categoryKey = 'equipment';
|
||||
|
||||
// 서버PC인 경우 category는 PC이지만 UI상 서버로 취급되므로,
|
||||
// 저장은 반드시 'pc' 엔드포인트(/api/pc)로 되어야 함.
|
||||
if (updated.asset_type === '서버PC') {
|
||||
categoryKey = 'pc';
|
||||
} else if (updated.asset_code?.startsWith('SVR') || updated.category === '서버') {
|
||||
categoryKey = 'server';
|
||||
} else if (updated.asset_code?.startsWith('STO') || updated.category === '스토리지') {
|
||||
categoryKey = 'storage';
|
||||
} else if (updated.asset_code?.startsWith('EQP') || updated.category === '업무지원장비') {
|
||||
categoryKey = 'equipment';
|
||||
} else if (updated.category === '공간정보장비') {
|
||||
categoryKey = 'survey';
|
||||
} else if (updated.category === 'PC부품') {
|
||||
categoryKey = 'pcParts';
|
||||
}
|
||||
|
||||
const success = await saveAsset(categoryKey, updated);
|
||||
if (success) {
|
||||
@@ -314,13 +374,16 @@ export function openHwModal(asset: any, mode: 'view' | 'edit' | 'add' = 'view')
|
||||
const serverOnly = document.querySelectorAll('.server-only');
|
||||
const nonServer = document.querySelectorAll('.non-server');
|
||||
const pcOnly = document.querySelectorAll('.pc-only');
|
||||
const userFields = document.querySelectorAll('.user-tracking-field');
|
||||
|
||||
const isServer = asset.category === '서버' || asset.asset_code?.startsWith('SVR');
|
||||
const isServer = asset.category === '서버' || asset.asset_code?.startsWith('SVR') || asset.asset_type === '서버PC';
|
||||
const isPc = asset.category === 'PC' || asset.asset_code?.startsWith('PC');
|
||||
const isVip = asset.category === '선물' || asset.category === 'VIP';
|
||||
|
||||
serverOnly.forEach(el => (el as HTMLElement).style.display = isServer ? 'flex' : 'none');
|
||||
nonServer.forEach(el => (el as HTMLElement).style.display = !isServer ? 'flex' : 'none');
|
||||
pcOnly.forEach(el => (el as HTMLElement).style.display = isPc ? 'flex' : 'none');
|
||||
userFields.forEach(el => (el as HTMLElement).style.display = (!isServer && !isVip) ? 'flex' : 'none');
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
}
|
||||
@@ -330,12 +393,24 @@ function fillHwFormData(asset: any) {
|
||||
setFieldValue('hw-asset_code', asset.asset_code || '');
|
||||
setFieldValue('hw-purchase_corp', asset.purchase_corp || '');
|
||||
setFieldValue('hw-category', asset.category || '');
|
||||
|
||||
// Populate asset_type options based on category
|
||||
const typeSelect = document.getElementById('hw-asset_type') as HTMLSelectElement;
|
||||
const types = CATEGORY_TYPE_MAP[asset.category] || [];
|
||||
if (typeSelect) {
|
||||
typeSelect.innerHTML = types.length > 0
|
||||
? generateOptionsHTML(types, asset.asset_type, true)
|
||||
: '<option value="">구분을 먼저 선택하세요</option>';
|
||||
}
|
||||
setFieldValue('hw-asset_type', asset.asset_type || '');
|
||||
|
||||
setFieldValue('hw-hw_status', asset.hw_status || '운영');
|
||||
setFieldValue('hw-current_dept', asset.current_dept || '');
|
||||
setFieldValue('hw-previous_dept', asset.previous_dept || '');
|
||||
setFieldValue('hw-manager_primary', asset.manager_primary || '');
|
||||
setFieldValue('hw-manager_secondary', asset.manager_secondary || '');
|
||||
setFieldValue('hw-current_user', asset.current_user || '');
|
||||
setFieldValue('hw-user_current', asset.user_current || '');
|
||||
setFieldValue('hw-user_position', asset.user_position || '');
|
||||
setFieldValue('hw-previous_user', asset.previous_user || '');
|
||||
setFieldValue('hw-asset_purpose', asset.asset_purpose || '');
|
||||
|
||||
@@ -365,8 +440,9 @@ function fillHwFormData(asset: any) {
|
||||
(document.getElementById('hw-approval_document_name') as HTMLElement).textContent = asset.approval_document || '';
|
||||
|
||||
setFieldValue('hw-memo', asset.memo || '');
|
||||
setFieldValue('hw-location_detail', asset.location_detail || '');
|
||||
|
||||
parseAndSetLocation(asset.location || '', 'hw-bldg-select', 'hw-floor-select', 'hw-loc-etc-group', 'hw-loc-etc');
|
||||
parseAndSetLocation(asset.location || '', asset.location_detail || '', 'hw-bldg-select', 'hw-location_detail');
|
||||
renderHwHistory(asset.id);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user