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

@@ -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;