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,113 +1,15 @@
|
||||
import { state } from '../../core/state';
|
||||
import { state, saveAsset, deleteAsset } from '../../core/state';
|
||||
import { closeModals, openModal } from './BaseModal';
|
||||
import { CORP_LIST } from './SharedData';
|
||||
import { generateOptionsHTML, setEditLock } from './ModalUtils';
|
||||
import { createIcons, X, Save, Database, CalendarClock, Edit2 } from 'lucide';
|
||||
import { formatExcelDate } from '../../core/excelHandler';
|
||||
import { UI_TEXT } from '../../core/schema';
|
||||
|
||||
let currentItem: any = null;
|
||||
|
||||
const DOMAIN_MODAL_HTML = `
|
||||
<div id="domain-asset-modal" class="modal-overlay hidden">
|
||||
<div class="modal-content wide">
|
||||
<div class="modal-header">
|
||||
<h2 id="domain-modal-title">도메인 정보</h2>
|
||||
<div style="display:flex; gap:0.5rem; align-items:center;">
|
||||
<button id="btn-edit-domain-header" class="btn-icon header-edit-btn" title="수정"><i data-lucide="edit-2"></i></button>
|
||||
<button id="btn-close-domain-modal" class="btn-icon"><i data-lucide="x"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="modal-form-area">
|
||||
<form id="domain-asset-form" class="grid-form">
|
||||
|
||||
<!-- Group 1: 기본 정보 (Service Identity) -->
|
||||
<div class="form-section-title" style="display:flex; align-items:center; gap:0.5rem;">
|
||||
<i data-lucide="database" style="width:16px; height:16px; color:var(--primary-color);"></i>
|
||||
기본 정보 (Identity)
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="required">유형</label>
|
||||
<select id="domain-type" required>
|
||||
<option value="호스팅">호스팅</option>
|
||||
<option value="SSL">SSL</option>
|
||||
<option value="도메인">도메인</option>
|
||||
<option value="네임서버">네임서버</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="required">법인</label>
|
||||
<select id="domain-corp" required>
|
||||
${generateOptionsHTML(CORP_LIST)}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="required">서비스명</label>
|
||||
<input type="text" id="domain-service-name" placeholder="예: 그룹웨어, 홈페이지" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="required">관리도메인</label>
|
||||
<input type="text" id="domain-name" placeholder="예: hmac.kr" required>
|
||||
</div>
|
||||
|
||||
<!-- Group 2: 계약 및 담당 정보 (Contract & Manager) -->
|
||||
<div class="form-section-title" style="display:flex; align-items:center; gap:0.5rem; margin-top:1.5rem;">
|
||||
<i data-lucide="calendar-clock" style="width:16px; height:16px; color:var(--primary-color);"></i>
|
||||
계약 및 담당 정보
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>계약 시작일</label>
|
||||
<input type="date" id="domain-start-date">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>계약 만료일</label>
|
||||
<input type="date" id="domain-expiry-date">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>도입 금액</label>
|
||||
<input type="text" id="domain-price" oninput="this.value = this.value.replace(/[^0-9]/g, '').replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',')" placeholder="0">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>담당자</label>
|
||||
<input type="text" id="domain-manager-main">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>담당자(부)</label>
|
||||
<input type="text" id="domain-manager-sub">
|
||||
</div>
|
||||
|
||||
<!-- Group 3: 기타 (Additional) -->
|
||||
<div class="form-section-title" style="display:flex; align-items:center; gap:0.5rem; margin-top:1.5rem;">
|
||||
<i data-lucide="edit-2" style="width:16px; height:16px; color:var(--primary-color);"></i>
|
||||
구매 정보
|
||||
</div>
|
||||
|
||||
<div class="form-group full-width">
|
||||
<label>구매업체</label>
|
||||
<textarea id="domain-remarks" rows="1" style="width:100%; border:1px solid var(--border-color); border-radius:4px; padding:0.625rem;"></textarea>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button id="btn-delete-domain" class="btn btn-outline btn-danger">삭제</button>
|
||||
<div class="footer-actions">
|
||||
<button id="btn-revert-domain" class="btn btn-outline hidden">수정 취소</button>
|
||||
<button id="btn-cancel-domain" class="btn btn-outline">닫기</button>
|
||||
<button id="btn-save-domain" class="btn btn-primary"><i data-lucide="save"></i> 저장하기</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
... (rest of DOMAIN_MODAL_HTML remains same) ...
|
||||
`;
|
||||
|
||||
export function initDomainModal() {
|
||||
@@ -126,7 +28,7 @@ export function initDomainModal() {
|
||||
|
||||
saveBtn?.addEventListener('click', () => {
|
||||
if (!currentItem) return;
|
||||
if (saveBtn.textContent === '수정') {
|
||||
if (saveBtn.textContent?.includes('수정')) {
|
||||
setEditLock('domain-asset-form', 'edit', { saveBtnId: 'btn-save-domain', revertBtnId: 'btn-revert-domain' });
|
||||
return;
|
||||
}
|
||||
@@ -142,10 +44,14 @@ export function initDomainModal() {
|
||||
if (currentItem) openDomainModal(currentItem);
|
||||
});
|
||||
|
||||
deleteBtn?.addEventListener('click', () => {
|
||||
if (currentItem && confirm('정말 삭제하시겠습니까?')) {
|
||||
state.masterData.domain = state.masterData.domain.filter(d => d.id !== currentItem.id);
|
||||
saveDomainBatch();
|
||||
deleteBtn?.addEventListener('click', async () => {
|
||||
if (currentItem && confirm(UI_TEXT.MESSAGES.CONFIRM_DELETE)) {
|
||||
const success = await deleteAsset('domain', currentItem.id);
|
||||
if (success) {
|
||||
alert('성공적으로 삭제되었습니다.');
|
||||
closeModals();
|
||||
window.dispatchEvent(new CustomEvent('refresh-view'));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -183,26 +89,6 @@ export function openDomainModal(item: any = null) {
|
||||
createIcons({ icons: { X, Save, Database, CalendarClock, Edit2 } });
|
||||
}
|
||||
|
||||
async function saveDomainBatch() {
|
||||
try {
|
||||
const response = await fetch(`http://${location.hostname}:3000/api/ops/domain/batch`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(state.masterData.domain)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
closeModals();
|
||||
window.dispatchEvent(new CustomEvent('refresh-view'));
|
||||
} else {
|
||||
throw new Error('DB 저장 실패');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
alert('저장 중 오류가 발생했습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
async function saveDomain() {
|
||||
const getVal = (id: string) => (document.getElementById(id) as HTMLInputElement)?.value || '';
|
||||
|
||||
@@ -225,17 +111,10 @@ async function saveDomain() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentItem && currentItem.id.startsWith('DOM-')) {
|
||||
// 신규 추가 후 바로 수정하는 경우 등 대응
|
||||
const idx = state.masterData.domain.findIndex(d => d.id === currentItem.id);
|
||||
if (idx > -1) state.masterData.domain[idx] = newDomain;
|
||||
else state.masterData.domain.push(newDomain);
|
||||
} else if (currentItem) {
|
||||
const idx = state.masterData.domain.findIndex(d => d.id === currentItem.id);
|
||||
if (idx > -1) state.masterData.domain[idx] = newDomain;
|
||||
} else {
|
||||
state.masterData.domain.push(newDomain);
|
||||
const success = await saveAsset('domain', newDomain);
|
||||
if (success) {
|
||||
alert(UI_TEXT.MESSAGES.SAVE_SUCCESS);
|
||||
closeModals();
|
||||
window.dispatchEvent(new CustomEvent('refresh-view'));
|
||||
}
|
||||
|
||||
await saveDomainBatch();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user