refactor: 프로젝트 구조 최적화 및 컴포넌트 기반 모달 시스템 도입

주요 정리 내용:
- 핵심 엔진 분리: state, excelHandler 등을 src/core/ 디렉토리로 격리
- 모달 컴포넌트화: index.html의 거대 HTML 구조를 각 모달 TS 파일로 내장 및 동적 주입
- index.html 최적화: 수백 줄의 중복 코드를 제거하여 슬림한 Shell 구조로 변환
- 전역 복구: 병합 과정에서 발생한 한글 인코딩 깨짐 전수 복구 및 빌드 오류 해결
- 경로 정합성: 파일 구조 변경에 따른 모든 import 경로 일괄 업데이트
This commit is contained in:
2026-04-15 13:56:04 +09:00
parent 7c4ccf6bba
commit a805d9ce06
16 changed files with 762 additions and 862 deletions

View File

@@ -2,33 +2,22 @@
* 모든 모달의 공통 기능 (닫기, ESC 처리, 배경 클릭 등)을 관리하는 베이스 모듈입니다.
*/
export function initBaseModal() {
const modals = document.querySelectorAll('.modal-overlay');
const closeButtons = document.querySelectorAll('.btn-icon, [id^="btn-cancel-"]');
// 모든 모달 닫기 함수
const closeAllModals = () => {
const modals = document.querySelectorAll('.modal-overlay');
modals.forEach(modal => modal.classList.add('hidden'));
// SW 관련 추가 모달 처리
document.getElementById('sw-user-modal')?.classList.add('hidden');
document.getElementById('sw-user-edit-modal')?.classList.add('hidden');
document.getElementById('dashboard-detail-modal')?.classList.add('hidden');
};
// 닫기 버튼 이벤트 바인딩
closeButtons.forEach(btn => {
btn.addEventListener('click', closeAllModals);
});
// ESC 키로 닫기
window.addEventListener('keydown', (e) => {
if (e.key === 'Escape') closeAllModals();
});
// 배경(Overlay) 클릭 시 닫기
modals.forEach(modal => {
modal.addEventListener('click', (e) => {
if (e.target === modal) closeAllModals();
});
// 배경(Overlay) 클릭 시 닫기 (동적 생성된 모달 대응을 위해 이벤트 위임 고려 가능하나 일단 단순 구현)
document.addEventListener('click', (e) => {
const target = e.target as HTMLElement;
if (target.classList.contains('modal-overlay')) {
closeAllModals();
}
});
return { closeAllModals };

View File

@@ -0,0 +1,109 @@
import { HardwareAsset, SoftwareAsset } from '../../core/excelHandler';
import { state } from '../../core/state';
const DASHBOARD_DETAIL_MODAL_HTML = `
<div id="dashboard-detail-modal" class="modal-overlay hidden">
<div class="modal-content wide" style="max-width: 1000px;">
<div class="modal-header">
<h2 id="dashboard-detail-modal-title">상세 목록</h2>
<button id="btn-close-dashboard-detail-modal" class="btn-icon" aria-label="닫기"><i data-lucide="x"></i></button>
</div>
<div class="modal-body">
<div class="table-container">
<table style="width:100%;">
<thead></thead>
<tbody id="dashboard-detail-tbody"></tbody>
</table>
</div>
</div>
<div class="modal-footer">
<div></div>
<button id="btn-cancel-dashboard-detail-modal" class="btn btn-outline">닫기</button>
</div>
</div>
</div>
`;
export function initDashboardDetailModal() {
if (!document.getElementById('dashboard-detail-modal')) {
document.body.insertAdjacentHTML('beforeend', DASHBOARD_DETAIL_MODAL_HTML);
}
const modal = document.getElementById('dashboard-detail-modal')!;
const closeBtn = document.getElementById('btn-close-dashboard-detail-modal')!;
const cancelBtn = document.getElementById('btn-cancel-dashboard-detail-modal')!;
const closeModal = () => modal.classList.add('hidden');
closeBtn.addEventListener('click', closeModal);
cancelBtn.addEventListener('click', closeModal);
modal.addEventListener('click', (e) => { if (e.target === modal) closeModal(); });
}
export function openDashboardDetail(title: string, list: HardwareAsset[]) {
const modal = document.getElementById('dashboard-detail-modal');
if (!modal) return;
const titleEl = document.getElementById('dashboard-detail-modal-title');
const tbody = document.getElementById('dashboard-detail-tbody');
if (!titleEl || !tbody) return;
const thead = tbody.closest('table')?.querySelector('thead');
if (!thead) return;
titleEl.textContent = title;
thead.innerHTML = `<tr><th>No</th><th>유형</th><th>자산코드</th><th>명칭/모델</th><th>위치</th><th>담당/사용자</th><th>구매일</th><th>금액</th></tr>`;
tbody.innerHTML = '';
if (list.length === 0) {
tbody.innerHTML = `<tr><td colspan="8" style="text-align:center; padding: 2rem;">해당 조건의 자산이 없습니다.</td></tr>`;
} else {
list.forEach((asset, idx) => {
let manager = asset. || asset. || asset._정 || '-';
let name = asset. || asset. || '-';
const tr = document.createElement('tr');
tr.innerHTML = `<td>${idx+1}</td><td>${asset.type}</td><td>${asset.}</td><td>${name}</td><td>${asset.||'-'}</td><td>${manager}</td><td>${asset.||'-'}</td><td>${asset.||'-'}</td>`;
tbody.appendChild(tr);
});
}
modal.classList.remove('hidden');
}
export function openSwDashboardDetail(title: string, list: SoftwareAsset[]) {
const modal = document.getElementById('dashboard-detail-modal');
if (!modal) return;
const titleEl = document.getElementById('dashboard-detail-modal-title');
const tbody = document.getElementById('dashboard-detail-tbody');
if (!titleEl || !tbody) return;
const thead = tbody.closest('table')?.querySelector('thead');
if (!thead) return;
titleEl.textContent = title;
thead.innerHTML = `<tr><th>No</th><th>유형</th><th>법인</th><th>제품명</th><th>수량</th><th>금액</th></tr>`;
tbody.innerHTML = '';
list.forEach((sw, idx) => {
const tr = document.createElement('tr');
tr.innerHTML = `<td>${idx+1}</td><td>${sw.type}</td><td>${sw.}</td><td>${sw.}</td><td>${sw.}</td><td>${sw.}</td>`;
tbody.appendChild(tr);
});
modal.classList.remove('hidden');
}
export function openSwUsageDetail(title: string, list: SoftwareAsset[]) {
const modal = document.getElementById('dashboard-detail-modal');
if (!modal) return;
const titleEl = document.getElementById('dashboard-detail-modal-title');
const tbody = document.getElementById('dashboard-detail-tbody');
if (!titleEl || !tbody) return;
const thead = tbody.closest('table')?.querySelector('thead');
if (!thead) return;
titleEl.textContent = title;
thead.innerHTML = `<tr><th>No</th><th>법인</th><th>제품명</th><th>수량</th><th>사용중</th><th>사용가능</th></tr>`;
tbody.innerHTML = '';
list.forEach((sw, idx) => {
const assigned = state.masterData.swUsers.filter(u => u.swId === sw.id).length;
const qty = typeof sw. === 'number' ? sw.수량 : parseInt(sw.||'0', 10);
const avail = qty - assigned;
const tr = document.createElement('tr');
tr.innerHTML = `<td>${idx+1}</td><td>${sw.}</td><td>${sw.}</td><td>${qty}</td><td>${assigned}</td><td>${avail}</td>`;
tbody.appendChild(tr);
});
modal.classList.remove('hidden');
}

View File

@@ -1,14 +1,164 @@
import { state } from '../../state';
import { HardwareAsset } from '../../excelHandler';
import { state } from '../../core/state';
import { HardwareAsset } from '../../core/excelHandler';
import { renderTable } from '../../views/AssetTableView';
import { createIcons, Paperclip } from 'lucide';
let currentAsset: HardwareAsset | null = null;
let isEditMode = false;
const HW_MODAL_HTML = `
<div id="hw-asset-modal" class="modal-overlay hidden">
<div class="modal-content wide">
<div class="modal-header">
<h2 id="hw-modal-title">자산 상세 정보</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" />
<!-- 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 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-비고" />
</div>
<!-- Group 2: 네트워크 정보 -->
<div class="form-section-title server-only">네트워크 정보 (Connectivity)</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-원격접속">원격 도구 (Anydesk/Chrome 등)</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>
</div>
</form>
</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-save-hw-asset" class="btn btn-primary">수정</button>
</div>
</div>
</div>
</div>
`;
export function openHwModal(asset: HardwareAsset) {
currentAsset = asset;
isEditMode = false; // 항상 조회 모드로 시작
isEditMode = false;
const modal = document.getElementById('hw-asset-modal')!;
const form = document.getElementById('hw-asset-form') as HTMLFormElement;
@@ -19,18 +169,14 @@ export function openHwModal(asset: HardwareAsset) {
form.classList.remove('is-edit-mode');
form.classList.add('is-view-mode');
saveBtn.textContent = '수정';
revertBtn.classList.add('hidden'); // 조회 모드에서는 숨김
revertBtn.classList.add('hidden');
// 데이터 채우기 함수 호출 (수정 취소 시에도 재사용)
fillHwFormData(asset);
modal.classList.remove('hidden');
createIcons({ icons: { Paperclip } });
}
/**
* 폼 필드에 자산 데이터 채우기
*/
function fillHwFormData(asset: HardwareAsset) {
(document.getElementById('hw-asset-id') as HTMLInputElement).value = asset.id;
(document.getElementById('hw-asset-type') as HTMLInputElement).value = asset.type;
@@ -85,6 +231,11 @@ function fillHwFormData(asset: HardwareAsset) {
}
export function initHwModal() {
// HTML 주입
if (!document.getElementById('hw-asset-modal')) {
document.body.insertAdjacentHTML('beforeend', HW_MODAL_HTML);
}
const modal = document.getElementById('hw-asset-modal')!;
const form = document.getElementById('hw-asset-form') as HTMLFormElement;
const closeBtn = document.getElementById('btn-close-hw-modal')!;
@@ -104,20 +255,13 @@ export function initHwModal() {
form.classList.add('is-view-mode');
saveBtn.textContent = '수정';
revertBtn.classList.add('hidden');
if (currentAsset) fillHwFormData(currentAsset); // 원래 데이터로 복구
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();
});
modal.addEventListener('click', (e) => { if (e.target === modal) closeModal(); });
revertBtn.addEventListener('click', () => { switchToViewMode(); });
saveBtn.addEventListener('click', () => {
if (!currentAsset) return;
@@ -127,11 +271,10 @@ export function initHwModal() {
form.classList.remove('is-view-mode');
form.classList.add('is-edit-mode');
saveBtn.textContent = '저장';
revertBtn.classList.remove('hidden'); // 수정 모드에서 취소 버튼 표시
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;
@@ -177,7 +320,7 @@ export function initHwModal() {
if (idx > -1) {
state.masterData.hw[idx] = updated;
renderTable(document.getElementById('main-content')!);
switchToViewMode(); // 저장 후 다시 조회 모드로
switchToViewMode();
}
});

View File

@@ -1,23 +1,143 @@
import { state } from '../../state';
import { HardwareAsset, HardwareLog } from '../../excelHandler';
import { state } from '../../core/state';
import { HardwareAsset, HardwareLog } from '../../core/excelHandler';
import { openModal } from './BaseModal';
/**
* 개인PC 모달 초기화 및 로직 제어
*/
const PC_MODAL_HTML = `
<div id="pc-asset-modal" class="modal-overlay hidden">
<div class="modal-content wide">
<div class="modal-header">
<h2 id="pc-modal-title">개인PC 상세 정보</h2>
<button id="btn-close-pc-modal" class="btn-icon" aria-label="닫기"><i data-lucide="x"></i></button>
</div>
<div class="modal-body">
<div class="modal-body-split">
<div class="modal-form-area">
<form id="pc-asset-form" class="grid-form">
<input type="hidden" id="pc-asset-id" />
<input type="hidden" id="pc-asset-type" value="개인PC" />
<div class="form-group">
<label for="pc-법인">법인</label>
<select id="pc-법인" required>
<option value="한맥">한맥 (HM)</option><option value="삼안">삼안 (SM)</option><option value="바론">바론 (BR)</option>
</select>
</div>
<div class="form-group">
<label for="pc-자산코드">자산코드</label>
<input type="text" id="pc-자산코드" placeholder="ex) HM-PC-2018-001" required />
</div>
<div class="form-group">
<label for="pc-사용자">사용자</label>
<input type="text" id="pc-사용자" required />
</div>
<div class="form-group">
<label for="pc-위치">위치</label>
<input type="text" id="pc-위치" />
</div>
<div class="form-group">
<label for="pc-CPU">CPU</label>
<input type="text" id="pc-CPU" />
</div>
<div class="form-group">
<label for="pc-GPU">GPU</label>
<input type="text" id="pc-GPU" />
</div>
<div class="form-group">
<label for="pc-RAM">RAM</label>
<input type="text" id="pc-RAM" />
</div>
<div class="form-group">
<label for="pc-SSD1">SSD1</label>
<input type="text" id="pc-SSD1" />
</div>
<div class="form-group">
<label for="pc-SSD2">SSD2</label>
<input type="text" id="pc-SSD2" />
</div>
<div class="form-group">
<label for="pc-HDD1">HDD1</label>
<input type="text" id="pc-HDD1" />
</div>
<div class="form-group">
<label for="pc-HDD2">HDD2</label>
<input type="text" id="pc-HDD2" />
</div>
<div class="form-group">
<label for="pc-구매일">구매일</label>
<input type="text" id="pc-구매일" placeholder="ex) 2024-01-01" />
</div>
<div class="form-group">
<label for="pc-금액">금액</label>
<input type="text" id="pc-금액" placeholder="ex) 1,000,000" oninput="this.value = this.value.replace(/[^0-9]/g, '').replace(/\\B(?=(\\d{3})+(?!\d))/g, ',')" />
</div>
<div class="form-group">
<label for="pc-납품업체">납품업체</label>
<input type="text" id="pc-납품업체" />
</div>
<div class="form-group full-width">
<label>품의서 (파일)</label>
<div style="display:flex; align-items:center; gap:0.5rem;">
<input type="file" id="pc-품의서" />
<span id="pc-품의서명" style="font-size:0.75rem; color:var(--text-light)"></span>
</div>
</div>
</form>
</div>
<div class="modal-history-area">
<div class="history-header">
<h3><i data-lucide="history" style="width:16px; height:16px;"></i> 수정 이력</h3>
</div>
<div id="pc-history-list" class="history-timeline">
<div class="empty-history">이력이 없습니다.</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button id="btn-delete-pc-asset" class="btn btn-outline btn-danger">삭제</button>
<div class="footer-actions">
<button id="btn-cancel-pc-modal" class="btn btn-outline">취소</button>
<button id="btn-save-pc-asset" class="btn btn-primary">저장</button>
</div>
</div>
</div>
</div>
`;
export function initPcModal(renderContent: () => void, closeModals: () => void) {
if (!document.getElementById('pc-asset-modal')) {
document.body.insertAdjacentHTML('beforeend', PC_MODAL_HTML);
}
const pcForm = document.getElementById('pc-asset-form') as HTMLFormElement;
const btnSavePc = document.getElementById('btn-save-pc-asset') as HTMLButtonElement;
const btnDeletePc = document.getElementById('btn-delete-pc-asset') as HTMLButtonElement;
const btnCancelPc = document.getElementById('btn-cancel-pc-modal') as HTMLButtonElement;
const btnClosePc = document.getElementById('btn-close-pc-modal') as HTMLButtonElement;
btnCancelPc?.addEventListener('click', closeModals);
btnClosePc?.addEventListener('click', closeModals);
// 저장 버튼 이벤트
btnSavePc?.addEventListener('click', (e) => {
e.preventDefault();
if (!pcForm.checkValidity()) { pcForm.reportValidity(); return; }
const id = (document.getElementById('pc-asset-id') as HTMLInputElement).value;
const fileInput = document.getElementById('pc-품의서') as HTMLInputElement;
const = fileInput.files && fileInput.files.length > 0 ? fileInput.files[0].name : (document.getElementById('pc-품의서명') as HTMLElement).innerText.replace('📎', '');
const = fileInput.files && fileInput.files.length > 0 ? fileInput.files[0].name : (document.getElementById('pc-품의서명') as HTMLElement).innerText.replace('첨부: ', '');
const newAsset: HardwareAsset = {
id: id || Math.random().toString(36).substring(2, 9),
@@ -26,11 +146,7 @@ export function initPcModal(renderContent: () => void, closeModals: () => void)
: (document.getElementById('pc-자산코드') as HTMLInputElement).value,
: '',
: (document.getElementById('pc-위치') as HTMLInputElement).value,
: '',
IP주소: '',
MACaddress: '',
HW사양: '',
OS: '',
: '', IP주소: '', MACaddress: '', HW사양: '', OS: '', : (document.getElementById('pc-납품업체') as HTMLInputElement).value,
: (document.getElementById('pc-사용자') as HTMLInputElement).value,
CPU: (document.getElementById('pc-CPU') as HTMLInputElement).value,
GPU: (document.getElementById('pc-GPU') as HTMLInputElement).value,
@@ -41,7 +157,6 @@ export function initPcModal(renderContent: () => void, closeModals: () => void)
HDD2: (document.getElementById('pc-HDD2') as HTMLInputElement).value,
: (document.getElementById('pc-구매일') as HTMLInputElement).value,
: (document.getElementById('pc-금액') as HTMLInputElement).value,
: (document.getElementById('pc-납품업체') as HTMLInputElement).value,
};
@@ -50,22 +165,15 @@ export function initPcModal(renderContent: () => void, closeModals: () => void)
if(idx !== -1) {
const oldAsset = state.masterData.hw[idx];
const changes = getChangeDetails(oldAsset, newAsset);
if (changes) {
// 로그인 기능이 없으므로 '관리자'가 로그인한 것으로 가정
const modifier = '관리자';
const log: HardwareLog = {
state.masterData.logs.push({
id: Math.random().toString(36).substring(2, 9),
assetId: id,
date: new Date().toLocaleString(),
details: changes,
user: modifier
};
state.masterData.logs.push(log);
user: '관리자'
});
}
state.masterData.hw[idx] = newAsset;
}
} else {
@@ -76,22 +184,17 @@ export function initPcModal(renderContent: () => void, closeModals: () => void)
renderContent();
});
// 삭제 버튼 이벤트
btnDeletePc?.addEventListener('click', (e) => {
e.preventDefault();
const id = (document.getElementById('pc-asset-id') as HTMLInputElement).value;
if (confirm('삭제하시겠습니까?')) {
state.masterData.hw = state.masterData.hw.filter(a => a.id !== id);
// 관련 로그도 삭제할지 여부는 정책에 따라 (여기서는 유지)
closeModals();
renderContent();
}
});
}
/**
* 개인PC 상세 모달 열기
*/
export function openPcModal(asset?: HardwareAsset) {
const pcForm = document.getElementById('pc-asset-form') as HTMLFormElement;
const deleteBtn = document.getElementById('btn-delete-pc-asset')!;
@@ -118,15 +221,15 @@ export function openPcModal(asset?: HardwareAsset) {
(document.getElementById('pc-HDD1') as HTMLInputElement).value = asset.HDD1 || '';
(document.getElementById('pc-HDD2') as HTMLInputElement).value = asset.HDD2 || '';
(document.getElementById('pc-구매일') as HTMLInputElement).value = asset. || '';
(document.getElementById('pc-금액') as HTMLInputElement).value = asset. ? asset..replace(/,/g, '').replace(/\B(?=(\d{3})+(?!\d))/g, ',') : '';
(document.getElementById('pc-금액') as HTMLInputElement).value = asset. || '';
(document.getElementById('pc-납품업체') as HTMLInputElement).value = asset. || '';
(document.getElementById('pc-품의서명') as HTMLElement).innerText = asset. ? `📎${asset.}` : '';
(document.getElementById('pc-품의서명') as HTMLElement).innerText = asset. ? `첨부: ${asset.}` : '';
renderHistory(asset.id);
} else {
document.getElementById('pc-modal-title')!.textContent = ' 개인PC 자산 추가';
document.getElementById('pc-modal-title')!.textContent = '신규 개인PC 자산 추가';
deleteBtn.style.display = 'none';
if (historyArea) historyArea.style.display = 'none'; // 신규 시 이력 숨김
if (historyArea) historyArea.style.display = 'none';
(document.getElementById('pc-asset-id') as HTMLInputElement).value = '';
(document.getElementById('pc-법인') as HTMLSelectElement).value = '한맥';
@@ -134,9 +237,6 @@ export function openPcModal(asset?: HardwareAsset) {
}
}
/**
* 변경 사항 감지 및 문자열 생성
*/
function getChangeDetails(oldAsset: HardwareAsset, newAsset: HardwareAsset): string {
const changes: string[] = [];
const fields = [
@@ -160,18 +260,13 @@ function getChangeDetails(oldAsset: HardwareAsset, newAsset: HardwareAsset): str
fields.forEach(field => {
const oldVal = (oldAsset as any)[field.key] || '';
const newVal = (newAsset as any)[field.key] || '';
if (oldVal !== newVal) {
changes.push(`${field.label}: ${oldVal || '없음'}${newVal || '없음'}`);
}
});
return changes.join('\n');
}
/**
* 이력 리스트 렌더링
*/
function renderHistory(assetId: string) {
const historyList = document.getElementById('pc-history-list');
if (!historyList) return;
@@ -189,7 +284,7 @@ function renderHistory(assetId: string) {
<div class="history-item">
<div class="history-date">${log.date}</div>
<div class="history-user">수정자: ${log.user}</div>
<div class="history-details">${log.details.replace(/\n/g, '<br>')}</div>
<div class="history-details">${log.details.replace(/\\n/g, '<br>')}</div>
</div>
`).join('');
}

View File

@@ -1,16 +1,104 @@
import { state } from '../../state';
import { SoftwareAsset } from '../../excelHandler';
import { state } from '../../core/state';
import { SoftwareAsset } from '../../core/excelHandler';
import { openModal } from './BaseModal';
/**
* 소프트웨어 모달 초기화 및 로직 제어
*/
const SW_MODAL_HTML = `
<div id="sw-asset-modal" class="modal-overlay hidden">
<div class="modal-content">
<div class="modal-header">
<h2 id="sw-modal-title">S/W 상세 정보</h2>
<button id="btn-close-sw-modal" class="btn-icon" aria-label="닫기"><i data-lucide="x"></i></button>
</div>
<div class="modal-body">
<form id="sw-asset-form" class="grid-form">
<input type="hidden" id="sw-asset-id" />
<input type="hidden" id="sw-asset-type" />
<div class="form-group">
<label for="sw-분야">분야</label>
<select id="sw-분야" required>
<option value="업무공통">업무공통</option>
<option value="개발S/W">개발S/W</option>
<option value="디자인">디자인</option>
<option value="설계S/W">설계S/W</option>
</select>
</div>
<div class="form-group">
<label for="sw-법인">법인</label>
<select id="sw-법인" required>
<option value="한맥">한맥 (HM)</option>
<option value="삼안 (SM)">삼안 (SM)</option>
<option value="바론 (BR)">바론 (BR)</option>
</select>
</div>
<div class="form-group">
<label for="sw-부서">부서</label>
<input type="text" id="sw-부서" placeholder="ex) 경영지원팀" required />
</div>
<div class="form-group">
<label for="sw-제품명">제품명</label>
<input type="text" id="sw-제품명" required />
</div>
<div class="form-group">
<label for="sw-구매일">구매일</label>
<input type="text" id="sw-구매일" placeholder="ex) 2024-01-01" />
</div>
<div class="form-group" id="sw-구독일-group">
<label for="sw-구독일">구독일(시작~끝)</label>
<input type="text" id="sw-구독일" placeholder="ex) 2024-01-01 ~ 2024-12-31" />
</div>
<div class="form-group" id="sw-유지보수-group" style="display:none;">
<label for="sw-유지보수여부">유지보수 여부</label>
<label style="display:flex; align-items:center; gap:0.5rem; height: 38px; cursor: pointer;">
<input type="checkbox" id="sw-유지보수여부" /> 대상 여부
</label>
</div>
<div class="form-group">
<label for="sw-금액">금액</label>
<input type="text" id="sw-금액" placeholder="ex) 1,000,000" oninput="this.value = this.value.replace(/[^0-9]/g, '').replace(/\\B(?=(\\d{3})+(?!\d))/g, ',')" />
</div>
<div class="form-group">
<label for="sw-수량">수량 (보유량)</label>
<input type="number" id="sw-수량" min="1" value="1" />
</div>
<div class="form-group">
<label for="sw-계정명">계정명</label>
<input type="text" id="sw-계정명" />
</div>
<div class="form-group">
<label for="sw-납품업체">납품업체</label>
<input type="text" id="sw-납품업체" />
</div>
<div class="form-group">
<label for="sw-비고">비고</label>
<input type="text" id="sw-비고" />
</div>
</form>
</div>
<div class="modal-footer">
<button id="btn-delete-sw-asset" class="btn btn-outline btn-danger">삭제</button>
<div class="footer-actions">
<button id="btn-cancel-sw-modal" class="btn btn-outline">닫기</button>
<button id="btn-save-sw-asset" class="btn btn-primary">수정</button>
</div>
</div>
</div>
</div>
`;
export function initSwModal(renderContent: () => void, closeModals: () => void) {
if (!document.getElementById('sw-asset-modal')) {
document.body.insertAdjacentHTML('beforeend', SW_MODAL_HTML);
}
const swForm = document.getElementById('sw-asset-form') as HTMLFormElement;
const btnSaveSw = document.getElementById('btn-save-sw-asset') as HTMLButtonElement;
const btnDeleteSw = document.getElementById('btn-delete-sw-asset') as HTMLButtonElement;
const btnCancelSw = document.getElementById('btn-cancel-sw-modal') as HTMLButtonElement;
const btnCloseSw = document.getElementById('btn-close-sw-modal') as HTMLButtonElement;
btnCancelSw?.addEventListener('click', closeModals);
btnCloseSw?.addEventListener('click', closeModals);
// 저장 버튼 이벤트
btnSaveSw?.addEventListener('click', (e) => {
e.preventDefault();
if (!swForm.checkValidity()) { swForm.reportValidity(); return; }
@@ -44,7 +132,6 @@ export function initSwModal(renderContent: () => void, closeModals: () => void)
renderContent();
});
// 삭제 버튼 이벤트
btnDeleteSw?.addEventListener('click', (e) => {
e.preventDefault();
const id = (document.getElementById('sw-asset-id') as HTMLInputElement).value;
@@ -56,12 +143,7 @@ export function initSwModal(renderContent: () => void, closeModals: () => void)
});
}
/**
* 소프트웨어 상세 모달 열기
* @param asset 수정 시 자산 데이터, 신규 시 undefined
*/
export function openSwModal(asset?: SoftwareAsset) {
const swModal = document.getElementById('sw-asset-modal') as HTMLDivElement;
const swForm = document.getElementById('sw-asset-form') as HTMLFormElement;
const deleteBtn = document.getElementById('btn-delete-sw-asset')!;
@@ -71,11 +153,11 @@ export function openSwModal(asset?: SoftwareAsset) {
const subGroup = document.getElementById('sw-구독일-group')!;
const permGroup = document.getElementById('sw-유지보수-group')!;
if (state.activeSubTab === '구독SW') {
subGroup.style.display = 'block';
subGroup.style.display = 'flex';
permGroup.style.display = 'none';
} else {
subGroup.style.display = 'none';
permGroup.style.display = 'block';
permGroup.style.display = 'flex';
}
if (asset) {
@@ -91,13 +173,13 @@ export function openSwModal(asset?: SoftwareAsset) {
(document.getElementById('sw-구매일') as HTMLInputElement).value = asset. || '';
(document.getElementById('sw-구독일') as HTMLInputElement).value = asset. || '';
(document.getElementById('sw-유지보수여부') as HTMLInputElement).checked = !!asset.;
(document.getElementById('sw-금액') as HTMLInputElement).value = asset. ? Number(asset..replace(/,/g, '')).toLocaleString() : '';
(document.getElementById('sw-금액') as HTMLInputElement).value = asset. || '';
(document.getElementById('sw-수량') as HTMLInputElement).value = String(asset.);
(document.getElementById('sw-계정명') as HTMLInputElement).value = asset. || '';
(document.getElementById('sw-납품업체') as HTMLInputElement).value = asset. || '';
(document.getElementById('sw-비고') as HTMLInputElement).value = asset. || '';
} else {
document.getElementById('sw-modal-title')!.textContent = ` ${state.activeSubTab} 자산 추가`;
document.getElementById('sw-modal-title')!.textContent = `신규 ${state.activeSubTab} 자산 추가`;
deleteBtn.style.display = 'none';
(document.getElementById('sw-asset-id') as HTMLInputElement).value = '';
(document.getElementById('sw-asset-type') as HTMLInputElement).value = state.activeSubTab;

View File

@@ -1,73 +1,154 @@
import { state } from '../../state';
import { SoftwareAsset, SWUser } from '../../excelHandler';
import { state } from '../../core/state';
import { SoftwareAsset, SWUser } from '../../core/excelHandler';
import { openModal } from './BaseModal';
import { createIcons, Edit2, X, Paperclip } from 'lucide';
let currentSwUserAssetId: string = '';
let tempSwUsers: SWUser[] = [];
/**
* 소프트웨어 사용자 할당 모달 초기화
*/
const SW_USER_MODAL_HTML = `
<!-- S/W 할당 사용자 목록 모달 -->
<div id="sw-user-modal" class="modal-overlay hidden">
<div class="modal-content" style="max-width: 800px;">
<div class="modal-header">
<h2 id="sw-user-modal-title">S/W 할당 사용자 목록</h2>
<button id="btn-close-sw-user-modal" class="btn-icon" aria-label="닫기"><i data-lucide="x"></i></button>
</div>
<div class="modal-body">
<input type="hidden" id="sw-user-asset-id" />
<div style="text-align: right; margin-bottom: 0.75rem;">
<button type="button" id="btn-open-add-user" class="btn btn-primary btn-sm"><i data-lucide="plus"></i> 사용자 추가</button>
</div>
<div class="table-container">
<table style="width:100%;">
<thead>
<tr>
<th>법인</th>
<th>부서/팀</th>
<th>직위</th>
<th>이름</th>
<th>사용기간</th>
<th>증빙</th>
<th style="text-align:center;">관리</th>
</tr>
</thead>
<tbody id="user-list-body"></tbody>
</table>
</div>
</div>
<div class="modal-footer">
<div></div>
<div class="footer-actions">
<button id="btn-save-sw-user-mapping" class="btn btn-primary">변경사항 저장</button>
<button id="btn-cancel-sw-user-modal" class="btn btn-outline">닫기</button>
</div>
</div>
</div>
</div>
<!-- 사용자 추가/수정 서브 모달 -->
<div id="sw-user-edit-modal" class="modal-overlay hidden" style="z-index: 1100;">
<div class="modal-content" style="max-width: 500px;">
<div class="modal-header">
<h2 id="sw-user-edit-modal-title">사용자 정보</h2>
<button id="btn-close-sw-user-edit" class="btn-icon"><i data-lucide="x"></i></button>
</div>
<div class="modal-body">
<input type="hidden" id="edit-user-idx" />
<div class="grid-form" style="grid-template-columns: 1fr;">
<div class="form-group">
<label>법인</label>
<select id="new-user-법인">
<option value="한맥">한맥</option><option value="삼안">삼안</option><option value="바론">바론</option>
</select>
</div>
<div class="form-group">
<label>부서</label>
<input type="text" id="new-user-부서" />
</div>
<div class="form-group">
<label>팀</label>
<input type="text" id="new-user-팀" />
</div>
<div class="form-group">
<label>직위</label>
<input type="text" id="new-user-직위" />
</div>
<div class="form-group">
<label>이름</label>
<input type="text" id="new-user-이름" required />
</div>
<div class="form-group">
<label>사용기간</label>
<input type="text" id="new-user-사용기간" placeholder="ex) 2024.01 ~ 2024.12" />
</div>
<div class="form-group">
<label>신청서 (증빙파일)</label>
<input type="file" id="new-user-신청서" />
<span id="new-user-신청서명" style="font-size:0.75rem; color:var(--text-muted);"></span>
</div>
</div>
</div>
<div class="modal-footer">
<button id="btn-cancel-sw-user-edit" class="btn btn-outline">취소</button>
<button id="btn-save-edit-user" class="btn btn-primary">확인</button>
</div>
</div>
</div>
`;
export function initSwUserModal(renderContent: () => void, closeModals: () => void) {
if (!document.getElementById('sw-user-modal')) {
document.body.insertAdjacentHTML('beforeend', SW_USER_MODAL_HTML);
}
const btnOpenAddUser = document.getElementById('btn-open-add-user');
const btnSaveEditUser = document.getElementById('btn-save-edit-user');
const btnSaveSwUserMapping = document.getElementById('btn-save-sw-user-mapping');
const btnCancelUserEdit = document.getElementById('btn-cancel-sw-user-edit');
const btnCloseUserEdit = document.getElementById('btn-close-sw-user-edit');
const btnCancelUserModal = document.getElementById('btn-cancel-sw-user-modal');
const btnCloseUserModal = document.getElementById('btn-close-sw-user-modal');
btnOpenAddUser?.addEventListener('click', () => {
openUserEditModal(-1);
});
btnSaveEditUser?.addEventListener('click', () => {
saveUserEdit();
});
btnOpenAddUser?.addEventListener('click', () => openUserEditModal(-1));
btnSaveEditUser?.addEventListener('click', () => saveUserEdit());
btnSaveSwUserMapping?.addEventListener('click', () => {
// 변경사항 전역 상태에 반영
state.masterData.swUsers = state.masterData.swUsers.filter(u => u.swId !== currentSwUserAssetId);
state.masterData.swUsers.push(...tempSwUsers);
document.getElementById('sw-user-modal')?.classList.add('hidden');
renderContent();
});
// 취소 버튼들
document.getElementById('btn-cancel-sw-user-edit')?.addEventListener('click', () => {
document.getElementById('sw-user-edit-modal')?.classList.add('hidden');
});
document.getElementById('btn-cancel-sw-user-modal')?.addEventListener('click', () => {
document.getElementById('sw-user-modal')?.classList.add('hidden');
});
btnCancelUserEdit?.addEventListener('click', () => document.getElementById('sw-user-edit-modal')?.classList.add('hidden'));
btnCloseUserEdit?.addEventListener('click', () => document.getElementById('sw-user-edit-modal')?.classList.add('hidden'));
btnCancelUserModal?.addEventListener('click', () => document.getElementById('sw-user-modal')?.classList.add('hidden'));
btnCloseUserModal?.addEventListener('click', () => document.getElementById('sw-user-modal')?.classList.add('hidden'));
}
/**
* 소프트웨어 사용자 목록 렌더링
*/
function renderUserList() {
const tbody = document.getElementById('user-list-body')!;
tbody.innerHTML = '';
if (tempSwUsers.length === 0) {
tbody.innerHTML = '<tr><td colspan="7" style="padding: 1rem; text-align: center; color: var(--text-muted);">할당된 사용자가 없습니다.</td></tr>';
tbody.innerHTML = '<tr><td colspan="7" style="padding: 2rem; text-align: center; color: var(--text-muted);">할당된 사용자가 없습니다.</td></tr>';
return;
}
tempSwUsers.forEach((user, idx) => {
const tr = document.createElement('tr');
tr.style.cssText = 'border-bottom: 1px solid var(--border); transition: background-color 0.2s;';
const deptTeam = [user., user.].filter(Boolean).join(' / ') || '-';
const attachIcon = user. ? `<i data-lucide="paperclip" class="text-primary" style="width:16px; height:16px;" title="${user.}"></i>` : '-';
tr.innerHTML = `
<td style="padding:0.5rem; text-align:left;">${user.}</td>
<td style="padding:0.5rem; text-align:left;">${deptTeam}</td>
<td style="padding:0.5rem; text-align:left;">${user. || '-'}</td>
<td style="padding:0.5rem; text-align:left;"><strong>${user.}</strong></td>
<td style="padding:0.5rem; text-align:center;">${user. || '-'}</td>
<td style="padding:0.5rem; text-align:center; color: var(--text-light);">${attachIcon}</td>
<td style="padding:0.5rem; text-align:center; display:flex; justify-content:center; gap:0.25rem;">
<button type="button" class="btn-icon btn-edit-user" data-idx="${idx}" style="color: var(--primary);" title="수정"><i data-lucide="edit-2" style="width:14px; height:14px;"></i></button>
<button type="button" class="btn-icon btn-remove-user" data-idx="${idx}" style="color: var(--danger);" title="삭제"><i data-lucide="x" style="width:14px; height:14px;"></i></button>
<td>${user.}</td>
<td>${deptTeam}</td>
<td>${user. || '-'}</td>
<td><strong>${user.}</strong></td>
<td style="text-align:center;">${user. || '-'}</td>
<td style="text-align:center;">${attachIcon}</td>
<td style="text-align:center;">
<button type="button" class="btn-icon btn-edit-user" data-idx="${idx}" style="color: var(--primary-color);"><i data-lucide="edit-2" style="width:14px; height:14px;"></i></button>
<button type="button" class="btn-icon btn-remove-user" data-idx="${idx}" style="color: var(--danger);"><i data-lucide="x" style="width:14px; height:14px;"></i></button>
</td>
`;
tbody.appendChild(tr);
@@ -84,7 +165,6 @@ function renderUserList() {
tbody.querySelectorAll('.btn-remove-user').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const idx = parseInt((e.currentTarget as HTMLButtonElement).getAttribute('data-idx')!);
tempSwUsers.splice(idx, 1);
renderUserList();
@@ -92,9 +172,6 @@ function renderUserList() {
});
}
/**
* 사용자 할당 모달 열기
*/
export function openSwUserModal(asset: SoftwareAsset) {
openModal('sw-user-modal');
currentSwUserAssetId = asset.id;
@@ -102,9 +179,6 @@ export function openSwUserModal(asset: SoftwareAsset) {
renderUserList();
}
/**
* 사용자 추가/수정 모달 열기
*/
function openUserEditModal(idx: number) {
const editModal = document.getElementById('sw-user-edit-modal')!;
editModal.classList.remove('hidden');
@@ -121,7 +195,7 @@ function openUserEditModal(idx: number) {
(document.getElementById('new-user-신청서') as HTMLInputElement).value = '';
document.getElementById('new-user-신청서명')!.innerText = '';
} else {
document.getElementById('sw-user-edit-modal-title')!.innerText = '사용자 수정';
document.getElementById('sw-user-edit-modal-title')!.innerText = '사용자 정보 수정';
const u = tempSwUsers[idx];
(document.getElementById('new-user-법인') as HTMLSelectElement).value = u.;
(document.getElementById('new-user-부서') as HTMLInputElement).value = u.;
@@ -130,22 +204,15 @@ function openUserEditModal(idx: number) {
(document.getElementById('new-user-이름') as HTMLInputElement).value = u.;
(document.getElementById('new-user-사용기간') as HTMLInputElement).value = u.;
(document.getElementById('new-user-신청서') as HTMLInputElement).value = '';
document.getElementById('new-user-신청서명')!.innerText = u. ? `📎${u.}` : '';
document.getElementById('new-user-신청서명')!.innerText = u. ? `첨부: ${u.}` : '';
}
}
/**
* 사용자 추가/수정 내용 저장 (임시 목록에 반영)
*/
function saveUserEdit() {
const idx = parseInt((document.getElementById('edit-user-idx') as HTMLInputElement).value);
const = (document.getElementById('new-user-법인') as HTMLSelectElement).value;
const = (document.getElementById('new-user-부서') as HTMLInputElement).value;
const = (document.getElementById('new-user-팀') as HTMLInputElement).value;
const = (document.getElementById('new-user-직위') as HTMLInputElement).value;
const = (document.getElementById('new-user-이름') as HTMLInputElement).value.trim();
const = (document.getElementById('new-user-사용기간') as HTMLInputElement).value;
if (!) { alert('이름을 입력해주세요.'); return; }
const fileInput = document.getElementById('new-user-신청서') as HTMLInputElement;
let = '';
if (fileInput.files && fileInput.files.length > 0) {
@@ -154,17 +221,20 @@ function saveUserEdit() {
= tempSwUsers[idx].;
}
if (!) { alert('이름을 입력해주세요.'); return; }
const userData: SWUser = {
id: idx === -1 ? Math.random().toString(36).substring(2, 9) : tempSwUsers[idx].id,
swId: currentSwUserAssetId,
: (document.getElementById('new-user-법인') as HTMLSelectElement).value,
: (document.getElementById('new-user-부서') as HTMLInputElement).value,
: (document.getElementById('new-user-팀') as HTMLInputElement).value,
: (document.getElementById('new-user-직위') as HTMLInputElement).value,
,
: (document.getElementById('new-user-사용기간') as HTMLInputElement).value,
};
if (idx === -1) {
tempSwUsers.push({
id: Math.random().toString(36).substring(2, 9),
swId: currentSwUserAssetId,
, , , , , ,
});
} else {
tempSwUsers[idx] = { ...tempSwUsers[idx], , , , , , , };
}
if (idx === -1) tempSwUsers.push(userData);
else tempSwUsers[idx] = userData;
document.getElementById('sw-user-edit-modal')?.classList.add('hidden');
renderUserList();

View File

@@ -1,45 +1,77 @@
import { state } from '../../state';
import { HardwareAsset } from '../../excelHandler';
import { state } from '../../core/state';
import { HardwareAsset } from '../../core/excelHandler';
import { openModal } from './BaseModal';
/**
* 스토리지 모달 초기화 및 로직 제어
*/
const STORAGE_MODAL_HTML = `
<div id="storage-asset-modal" class="modal-overlay hidden">
<div class="modal-content">
<div class="modal-header">
<h2 id="storage-modal-title">스토리지 상세 정보</h2>
<button id="btn-close-storage-modal" class="btn-icon" aria-label="닫기"><i data-lucide="x"></i></button>
</div>
<div class="modal-body">
<form id="storage-asset-form" class="grid-form">
<input type="hidden" id="storage-asset-id" />
<input type="hidden" id="storage-asset-type" value="스토리지" />
<div class="form-group"><label for="storage-법인">법인</label><input type="text" id="storage-법인" required /></div>
<div class="form-group"><label for="storage-유형">유형</label><input type="text" id="storage-유형" required /></div>
<div class="form-group"><label for="storage-자산코드">자산코드</label><input type="text" id="storage-자산코드" required /></div>
<div class="form-group"><label for="storage-명칭">명칭</label><input type="text" id="storage-명칭" required /></div>
<div class="form-group"><label for="storage-위치">위치</label><input type="text" id="storage-위치" /></div>
<div class="form-group"><label for="storage-모델명">모델명</label><input type="text" id="storage-모델명" /></div>
<div class="form-group"><label for="storage-용량">용량</label><input type="text" id="storage-용량" /></div>
<div class="form-group"><label for="storage-담당자_정">담당자(정)</label><input type="text" id="storage-담당자_정" /></div>
<div class="form-group"><label for="storage-IP주소">IP주소</label><input type="text" id="storage-IP주소" /></div>
<div class="form-group"><label for="storage-구매일">구매일</label><input type="text" id="storage-구매일" /></div>
<div class="form-group"><label for="storage-금액">금액</label><input type="text" id="storage-금액" oninput="this.value = this.value.replace(/[^0-9]/g, '').replace(/\\B(?=(\\d{3})+(?!\d))/g, ',')" /></div>
</form>
</div>
<div class="modal-footer">
<button id="btn-delete-storage-asset" class="btn btn-outline btn-danger">삭제</button>
<div class="footer-actions">
<button id="btn-cancel-storage-modal" class="btn btn-outline">닫기</button>
<button id="btn-save-storage-asset" class="btn btn-primary">수정</button>
</div>
</div>
</div>
</div>
`;
export function initStorageModal(renderContent: () => void, closeModals: () => void) {
if (!document.getElementById('storage-asset-modal')) {
document.body.insertAdjacentHTML('beforeend', STORAGE_MODAL_HTML);
}
const storageForm = document.getElementById('storage-asset-form') as HTMLFormElement;
const btnSaveStorage = document.getElementById('btn-save-storage-asset') as HTMLButtonElement;
const btnDeleteStorage = document.getElementById('btn-delete-storage-asset') as HTMLButtonElement;
const btnCancelStorage = document.getElementById('btn-cancel-storage-modal') as HTMLButtonElement;
const btnCloseStorage = document.getElementById('btn-close-storage-modal') as HTMLButtonElement;
btnCancelStorage?.addEventListener('click', closeModals);
btnCloseStorage?.addEventListener('click', closeModals);
// 저장 버튼 이벤트
btnSaveStorage?.addEventListener('click', (e) => {
e.preventDefault();
if (!storageForm.checkValidity()) { storageForm.reportValidity(); return; }
const id = (document.getElementById('storage-asset-id') as HTMLInputElement).value;
const fileInput = document.getElementById('storage-품의서') as HTMLInputElement;
const = fileInput.files && fileInput.files.length > 0 ? fileInput.files[0].name : (document.getElementById('storage-품의서명') as HTMLElement).innerText.replace('📎', '');
const newAsset: HardwareAsset = {
id: id || Math.random().toString(36).substring(2, 9),
type: '스토리지',
: (document.getElementById('storage-법인') as HTMLSelectElement).value,
storage유형: (document.getElementById('storage-유형') as HTMLSelectElement).value,
: (document.getElementById('storage-법인') as HTMLInputElement).value,
storage유형: (document.getElementById('storage-유형') as HTMLInputElement).value,
: (document.getElementById('storage-자산코드') as HTMLInputElement).value,
: (document.getElementById('storage-명칭') as HTMLInputElement).value,
: (document.getElementById('storage-위치') as HTMLInputElement).value,
: '',
IP주소: (document.getElementById('storage-IP주소') as HTMLInputElement).value,
MACaddress: (document.getElementById('storage-MAC주소') as HTMLInputElement).value,
HW사양: '',
OS: '',
: (document.getElementById('storage-모델명') as HTMLInputElement).value,
: (document.getElementById('storage-용량') as HTMLInputElement).value,
_정: (document.getElementById('storage-담당자_정') as HTMLInputElement).value,
_부: (document.getElementById('storage-담당자_부') as HTMLInputElement).value,
IP주소: (document.getElementById('storage-IP주소') as HTMLInputElement).value,
: (document.getElementById('storage-구매일') as HTMLInputElement).value,
: (document.getElementById('storage-금액') as HTMLInputElement).value,
: (document.getElementById('storage-납품업체') as HTMLInputElement).value,
: '', MACaddress: '', HW사양: '', OS: '', : '', : ''
};
if (id) {
@@ -53,7 +85,6 @@ export function initStorageModal(renderContent: () => void, closeModals: () => v
renderContent();
});
// 삭제 버튼 이벤트
btnDeleteStorage?.addEventListener('click', (e) => {
e.preventDefault();
const id = (document.getElementById('storage-asset-id') as HTMLInputElement).value;
@@ -65,12 +96,7 @@ export function initStorageModal(renderContent: () => void, closeModals: () => v
});
}
/**
* 스토리지 상세 모달 열기
* @param asset 수정 시 자산 데이터, 신규 시 undefined
*/
export function openStorageModal(asset?: HardwareAsset) {
const storageModal = document.getElementById('storage-asset-modal') as HTMLDivElement;
const storageForm = document.getElementById('storage-asset-form') as HTMLFormElement;
const deleteBtn = document.getElementById('btn-delete-storage-asset')!;
@@ -82,27 +108,20 @@ export function openStorageModal(asset?: HardwareAsset) {
deleteBtn.style.display = 'block';
(document.getElementById('storage-asset-id') as HTMLInputElement).value = asset.id;
(document.getElementById('storage-법인') as HTMLSelectElement).value = asset.;
(document.getElementById('storage-유형') as HTMLSelectElement).value = asset.storage유형 || 'NAS';
(document.getElementById('storage-법인') as HTMLInputElement).value = asset.;
(document.getElementById('storage-유형') as HTMLInputElement).value = asset.storage유형 || 'NAS';
(document.getElementById('storage-자산코드') as HTMLInputElement).value = asset.;
(document.getElementById('storage-명칭') as HTMLInputElement).value = asset.;
(document.getElementById('storage-위치') as HTMLInputElement).value = asset. || '';
(document.getElementById('storage-모델명') as HTMLInputElement).value = asset. || '';
(document.getElementById('storage-용량') as HTMLInputElement).value = asset. || '';
(document.getElementById('storage-담당자_정') as HTMLInputElement).value = asset._정 || '';
(document.getElementById('storage-담당자_부') as HTMLInputElement).value = asset._부 || '';
(document.getElementById('storage-IP주소') as HTMLInputElement).value = asset.IP주소 || '';
(document.getElementById('storage-MAC주소') as HTMLInputElement).value = asset.MACaddress || '';
(document.getElementById('storage-구매일') as HTMLInputElement).value = asset. || '';
(document.getElementById('storage-금액') as HTMLInputElement).value = asset. ? Number(asset..replace(/,/g, '')).toLocaleString() : '';
(document.getElementById('storage-납품업체') as HTMLInputElement).value = asset. || '';
(document.getElementById('storage-품의서명') as HTMLElement).innerText = asset. ? `📎${asset.}` : '';
(document.getElementById('storage-금액') as HTMLInputElement).value = asset. || '';
} else {
document.getElementById('storage-modal-title')!.textContent = ' 스토리지 자산 추가';
document.getElementById('storage-modal-title')!.textContent = '신규 스토리지 자산 추가';
deleteBtn.style.display = 'none';
(document.getElementById('storage-asset-id') as HTMLInputElement).value = '';
(document.getElementById('storage-법인') as HTMLSelectElement).value = '한맥';
(document.getElementById('storage-유형') as HTMLSelectElement).value = 'NAS';
(document.getElementById('storage-품의서명') as HTMLElement).innerText = '';
}
}

View File

@@ -1,4 +1,4 @@
import { state } from '../state';
import { state } from '../core/state';
export function renderSidebar(onTabChange: (tab: string) => void) {
const navItems = document.querySelectorAll('.nav-list li');