Compare commits
2 Commits
7c4ccf6bba
...
fde7ef8439
| Author | SHA1 | Date | |
|---|---|---|---|
| fde7ef8439 | |||
| a805d9ce06 |
413
index.html
413
index.html
@@ -64,421 +64,12 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="content-area" id="main-content">
|
<main class="content-area" id="main-content">
|
||||||
|
<!-- 컴포넌트에 의해 동적으로 채워짐 -->
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- HW Asset Modal -->
|
<!-- 모든 모달은 각 TS 컴포넌트 내부에서 동적으로 주입됨 -->
|
||||||
<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" />
|
|
||||||
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<!-- PC Asset Modal -->
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<!-- Storage Asset Modal -->
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<!-- SW Asset Modal -->
|
|
||||||
<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)</option>
|
|
||||||
<option value="바론">바론 (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>
|
|
||||||
|
|
||||||
<!-- SW User Management Modal -->
|
|
||||||
<div id="sw-user-modal" class="modal-overlay hidden">
|
|
||||||
<div class="modal-content">
|
|
||||||
<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" style="padding: 0.375rem 0.75rem;"><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 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-cancel-sw-user-modal" class="btn btn-outline">닫기</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Dashboard Detail Modal -->
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -2,33 +2,33 @@
|
|||||||
* 모든 모달의 공통 기능 (닫기, ESC 처리, 배경 클릭 등)을 관리하는 베이스 모듈입니다.
|
* 모든 모달의 공통 기능 (닫기, ESC 처리, 배경 클릭 등)을 관리하는 베이스 모듈입니다.
|
||||||
*/
|
*/
|
||||||
export function initBaseModal() {
|
export function initBaseModal() {
|
||||||
const modals = document.querySelectorAll('.modal-overlay');
|
|
||||||
const closeButtons = document.querySelectorAll('.btn-icon, [id^="btn-cancel-"]');
|
|
||||||
|
|
||||||
// 모든 모달 닫기 함수
|
|
||||||
const closeAllModals = () => {
|
const closeAllModals = () => {
|
||||||
modals.forEach(modal => modal.classList.add('hidden'));
|
const modals = document.querySelectorAll('.modal-overlay');
|
||||||
// SW 관련 추가 모달 처리
|
modals.forEach(modal => {
|
||||||
document.getElementById('sw-user-modal')?.classList.add('hidden');
|
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 키로 닫기
|
// ESC 키로 닫기
|
||||||
window.addEventListener('keydown', (e) => {
|
window.addEventListener('keydown', (e) => {
|
||||||
if (e.key === 'Escape') closeAllModals();
|
if (e.key === 'Escape') closeAllModals();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 배경(Overlay) 클릭 시 닫기
|
// 배경(Overlay) 및 닫기 버튼 클릭 시 닫기 (이벤트 위임)
|
||||||
modals.forEach(modal => {
|
document.addEventListener('click', (e) => {
|
||||||
modal.addEventListener('click', (e) => {
|
const target = e.target as HTMLElement;
|
||||||
if (e.target === modal) closeAllModals();
|
|
||||||
});
|
// 1. 오버레이 클릭 시 닫기
|
||||||
|
if (target.classList.contains('modal-overlay')) {
|
||||||
|
closeAllModals();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 닫기 아이콘(data-lucide="x") 또는 닫기/취소 버튼 클릭 시 닫기
|
||||||
|
// 버튼 ID가 btn-close- 또는 btn-cancel-로 시작하는 경우 대응
|
||||||
|
const btn = target.closest('button');
|
||||||
|
if (btn && (btn.id.startsWith('btn-close-') || btn.id.startsWith('btn-cancel-'))) {
|
||||||
|
closeAllModals();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return { closeAllModals };
|
return { closeAllModals };
|
||||||
|
|||||||
109
src/components/Modal/DashboardDetailModal.ts
Normal file
109
src/components/Modal/DashboardDetailModal.ts
Normal 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');
|
||||||
|
}
|
||||||
@@ -1,14 +1,164 @@
|
|||||||
import { state } from '../../state';
|
import { state } from '../../core/state';
|
||||||
import { HardwareAsset } from '../../excelHandler';
|
import { HardwareAsset } from '../../core/excelHandler';
|
||||||
import { renderTable } from '../../views/AssetTableView';
|
import { renderTable } from '../../views/AssetTableView';
|
||||||
import { createIcons, Paperclip } from 'lucide';
|
import { createIcons, Paperclip } from 'lucide';
|
||||||
|
|
||||||
let currentAsset: HardwareAsset | null = null;
|
let currentAsset: HardwareAsset | null = null;
|
||||||
let isEditMode = false;
|
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) <span class="badge-security" style="margin-left: 0.5rem;">보안 정보</span></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-원격접속">원격 도구</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) {
|
export function openHwModal(asset: HardwareAsset) {
|
||||||
currentAsset = asset;
|
currentAsset = asset;
|
||||||
isEditMode = false; // 항상 조회 모드로 시작
|
isEditMode = false;
|
||||||
|
|
||||||
const modal = document.getElementById('hw-asset-modal')!;
|
const modal = document.getElementById('hw-asset-modal')!;
|
||||||
const form = document.getElementById('hw-asset-form') as HTMLFormElement;
|
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.remove('is-edit-mode');
|
||||||
form.classList.add('is-view-mode');
|
form.classList.add('is-view-mode');
|
||||||
saveBtn.textContent = '수정';
|
saveBtn.textContent = '수정';
|
||||||
revertBtn.classList.add('hidden'); // 조회 모드에서는 숨김
|
revertBtn.classList.add('hidden');
|
||||||
|
|
||||||
// 데이터 채우기 함수 호출 (수정 취소 시에도 재사용)
|
|
||||||
fillHwFormData(asset);
|
fillHwFormData(asset);
|
||||||
|
|
||||||
modal.classList.remove('hidden');
|
modal.classList.remove('hidden');
|
||||||
createIcons({ icons: { Paperclip } });
|
createIcons({ icons: { Paperclip } });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 폼 필드에 자산 데이터 채우기
|
|
||||||
*/
|
|
||||||
function fillHwFormData(asset: HardwareAsset) {
|
function fillHwFormData(asset: HardwareAsset) {
|
||||||
(document.getElementById('hw-asset-id') as HTMLInputElement).value = asset.id;
|
(document.getElementById('hw-asset-id') as HTMLInputElement).value = asset.id;
|
||||||
(document.getElementById('hw-asset-type') as HTMLInputElement).value = asset.type;
|
(document.getElementById('hw-asset-type') as HTMLInputElement).value = asset.type;
|
||||||
@@ -85,6 +231,11 @@ function fillHwFormData(asset: HardwareAsset) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function initHwModal() {
|
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 modal = document.getElementById('hw-asset-modal')!;
|
||||||
const form = document.getElementById('hw-asset-form') as HTMLFormElement;
|
const form = document.getElementById('hw-asset-form') as HTMLFormElement;
|
||||||
const closeBtn = document.getElementById('btn-close-hw-modal')!;
|
const closeBtn = document.getElementById('btn-close-hw-modal')!;
|
||||||
@@ -104,20 +255,13 @@ export function initHwModal() {
|
|||||||
form.classList.add('is-view-mode');
|
form.classList.add('is-view-mode');
|
||||||
saveBtn.textContent = '수정';
|
saveBtn.textContent = '수정';
|
||||||
revertBtn.classList.add('hidden');
|
revertBtn.classList.add('hidden');
|
||||||
if (currentAsset) fillHwFormData(currentAsset); // 원래 데이터로 복구
|
if (currentAsset) fillHwFormData(currentAsset);
|
||||||
};
|
};
|
||||||
|
|
||||||
closeBtn.addEventListener('click', closeModal);
|
closeBtn.addEventListener('click', closeModal);
|
||||||
cancelBtn.addEventListener('click', closeModal);
|
cancelBtn.addEventListener('click', closeModal);
|
||||||
|
modal.addEventListener('click', (e) => { if (e.target === modal) closeModal(); });
|
||||||
modal.addEventListener('click', (e) => {
|
revertBtn.addEventListener('click', () => { switchToViewMode(); });
|
||||||
if (e.target === modal) closeModal();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 수정 취소 버튼 이벤트
|
|
||||||
revertBtn.addEventListener('click', () => {
|
|
||||||
switchToViewMode();
|
|
||||||
});
|
|
||||||
|
|
||||||
saveBtn.addEventListener('click', () => {
|
saveBtn.addEventListener('click', () => {
|
||||||
if (!currentAsset) return;
|
if (!currentAsset) return;
|
||||||
@@ -127,11 +271,10 @@ export function initHwModal() {
|
|||||||
form.classList.remove('is-view-mode');
|
form.classList.remove('is-view-mode');
|
||||||
form.classList.add('is-edit-mode');
|
form.classList.add('is-edit-mode');
|
||||||
saveBtn.textContent = '저장';
|
saveBtn.textContent = '저장';
|
||||||
revertBtn.classList.remove('hidden'); // 수정 모드에서 취소 버튼 표시
|
revertBtn.classList.remove('hidden');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 실제 저장 로직
|
|
||||||
const assetId = (document.getElementById('hw-asset-id') as HTMLInputElement).value;
|
const assetId = (document.getElementById('hw-asset-id') as HTMLInputElement).value;
|
||||||
const type = (document.getElementById('hw-asset-type') as HTMLInputElement).value;
|
const type = (document.getElementById('hw-asset-type') as HTMLInputElement).value;
|
||||||
|
|
||||||
@@ -177,7 +320,7 @@ export function initHwModal() {
|
|||||||
if (idx > -1) {
|
if (idx > -1) {
|
||||||
state.masterData.hw[idx] = updated;
|
state.masterData.hw[idx] = updated;
|
||||||
renderTable(document.getElementById('main-content')!);
|
renderTable(document.getElementById('main-content')!);
|
||||||
switchToViewMode(); // 저장 후 다시 조회 모드로
|
switchToViewMode();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,143 @@
|
|||||||
import { state } from '../../state';
|
import { state } from '../../core/state';
|
||||||
import { HardwareAsset, HardwareLog } from '../../excelHandler';
|
import { HardwareAsset, HardwareLog } from '../../core/excelHandler';
|
||||||
import { openModal } from './BaseModal';
|
import { openModal } from './BaseModal';
|
||||||
|
|
||||||
/**
|
const PC_MODAL_HTML = `
|
||||||
* 개인PC 모달 초기화 및 로직 제어
|
<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) {
|
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 pcForm = document.getElementById('pc-asset-form') as HTMLFormElement;
|
||||||
const btnSavePc = document.getElementById('btn-save-pc-asset') as HTMLButtonElement;
|
const btnSavePc = document.getElementById('btn-save-pc-asset') as HTMLButtonElement;
|
||||||
const btnDeletePc = document.getElementById('btn-delete-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) => {
|
btnSavePc?.addEventListener('click', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!pcForm.checkValidity()) { pcForm.reportValidity(); return; }
|
if (!pcForm.checkValidity()) { pcForm.reportValidity(); return; }
|
||||||
|
|
||||||
const id = (document.getElementById('pc-asset-id') as HTMLInputElement).value;
|
const id = (document.getElementById('pc-asset-id') as HTMLInputElement).value;
|
||||||
const fileInput = document.getElementById('pc-품의서') as HTMLInputElement;
|
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 = {
|
const newAsset: HardwareAsset = {
|
||||||
id: id || Math.random().toString(36).substring(2, 9),
|
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,
|
||||||
명칭: '',
|
명칭: '',
|
||||||
위치: (document.getElementById('pc-위치') as HTMLInputElement).value,
|
위치: (document.getElementById('pc-위치') as HTMLInputElement).value,
|
||||||
관리자: '',
|
관리자: '', IP주소: '', MACaddress: '', HW사양: '', OS: '', 납품업체: (document.getElementById('pc-납품업체') as HTMLInputElement).value,
|
||||||
IP주소: '',
|
|
||||||
MACaddress: '',
|
|
||||||
HW사양: '',
|
|
||||||
OS: '',
|
|
||||||
사용자: (document.getElementById('pc-사용자') as HTMLInputElement).value,
|
사용자: (document.getElementById('pc-사용자') as HTMLInputElement).value,
|
||||||
CPU: (document.getElementById('pc-CPU') as HTMLInputElement).value,
|
CPU: (document.getElementById('pc-CPU') as HTMLInputElement).value,
|
||||||
GPU: (document.getElementById('pc-GPU') 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,
|
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,
|
금액: (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) {
|
if(idx !== -1) {
|
||||||
const oldAsset = state.masterData.hw[idx];
|
const oldAsset = state.masterData.hw[idx];
|
||||||
const changes = getChangeDetails(oldAsset, newAsset);
|
const changes = getChangeDetails(oldAsset, newAsset);
|
||||||
|
|
||||||
if (changes) {
|
if (changes) {
|
||||||
// 로그인 기능이 없으므로 '관리자'가 로그인한 것으로 가정
|
state.masterData.logs.push({
|
||||||
const modifier = '관리자';
|
|
||||||
|
|
||||||
const log: HardwareLog = {
|
|
||||||
id: Math.random().toString(36).substring(2, 9),
|
id: Math.random().toString(36).substring(2, 9),
|
||||||
assetId: id,
|
assetId: id,
|
||||||
date: new Date().toLocaleString(),
|
date: new Date().toLocaleString(),
|
||||||
details: changes,
|
details: changes,
|
||||||
user: modifier
|
user: '관리자'
|
||||||
};
|
});
|
||||||
|
|
||||||
state.masterData.logs.push(log);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
state.masterData.hw[idx] = newAsset;
|
state.masterData.hw[idx] = newAsset;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -76,22 +184,17 @@ export function initPcModal(renderContent: () => void, closeModals: () => void)
|
|||||||
renderContent();
|
renderContent();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 삭제 버튼 이벤트
|
|
||||||
btnDeletePc?.addEventListener('click', (e) => {
|
btnDeletePc?.addEventListener('click', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const id = (document.getElementById('pc-asset-id') as HTMLInputElement).value;
|
const id = (document.getElementById('pc-asset-id') as HTMLInputElement).value;
|
||||||
if (confirm('삭제하시겠습니까?')) {
|
if (confirm('삭제하시겠습니까?')) {
|
||||||
state.masterData.hw = state.masterData.hw.filter(a => a.id !== id);
|
state.masterData.hw = state.masterData.hw.filter(a => a.id !== id);
|
||||||
// 관련 로그도 삭제할지 여부는 정책에 따라 (여기서는 유지)
|
|
||||||
closeModals();
|
closeModals();
|
||||||
renderContent();
|
renderContent();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 개인PC 상세 모달 열기
|
|
||||||
*/
|
|
||||||
export function openPcModal(asset?: HardwareAsset) {
|
export function openPcModal(asset?: HardwareAsset) {
|
||||||
const pcForm = document.getElementById('pc-asset-form') as HTMLFormElement;
|
const pcForm = document.getElementById('pc-asset-form') as HTMLFormElement;
|
||||||
const deleteBtn = document.getElementById('btn-delete-pc-asset')!;
|
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-HDD1') as HTMLInputElement).value = asset.HDD1 || '';
|
||||||
(document.getElementById('pc-HDD2') as HTMLInputElement).value = asset.HDD2 || '';
|
(document.getElementById('pc-HDD2') as HTMLInputElement).value = asset.HDD2 || '';
|
||||||
(document.getElementById('pc-구매일') as HTMLInputElement).value = asset.구매일 || '';
|
(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 HTMLInputElement).value = asset.납품업체 || '';
|
||||||
(document.getElementById('pc-품의서명') as HTMLElement).innerText = asset.품의서명 ? `📎${asset.품의서명}` : '';
|
(document.getElementById('pc-품의서명') as HTMLElement).innerText = asset.품의서명 ? `첨부: ${asset.품의서명}` : '';
|
||||||
|
|
||||||
renderHistory(asset.id);
|
renderHistory(asset.id);
|
||||||
} else {
|
} else {
|
||||||
document.getElementById('pc-modal-title')!.textContent = '새 개인PC 자산 추가';
|
document.getElementById('pc-modal-title')!.textContent = '신규 개인PC 자산 추가';
|
||||||
deleteBtn.style.display = 'none';
|
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-asset-id') as HTMLInputElement).value = '';
|
||||||
(document.getElementById('pc-법인') as HTMLSelectElement).value = '한맥';
|
(document.getElementById('pc-법인') as HTMLSelectElement).value = '한맥';
|
||||||
@@ -134,9 +237,6 @@ export function openPcModal(asset?: HardwareAsset) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 변경 사항 감지 및 문자열 생성
|
|
||||||
*/
|
|
||||||
function getChangeDetails(oldAsset: HardwareAsset, newAsset: HardwareAsset): string {
|
function getChangeDetails(oldAsset: HardwareAsset, newAsset: HardwareAsset): string {
|
||||||
const changes: string[] = [];
|
const changes: string[] = [];
|
||||||
const fields = [
|
const fields = [
|
||||||
@@ -160,18 +260,13 @@ function getChangeDetails(oldAsset: HardwareAsset, newAsset: HardwareAsset): str
|
|||||||
fields.forEach(field => {
|
fields.forEach(field => {
|
||||||
const oldVal = (oldAsset as any)[field.key] || '';
|
const oldVal = (oldAsset as any)[field.key] || '';
|
||||||
const newVal = (newAsset as any)[field.key] || '';
|
const newVal = (newAsset as any)[field.key] || '';
|
||||||
|
|
||||||
if (oldVal !== newVal) {
|
if (oldVal !== newVal) {
|
||||||
changes.push(`${field.label}: ${oldVal || '없음'} → ${newVal || '없음'}`);
|
changes.push(`${field.label}: ${oldVal || '없음'} → ${newVal || '없음'}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return changes.join('\n');
|
return changes.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 이력 리스트 렌더링
|
|
||||||
*/
|
|
||||||
function renderHistory(assetId: string) {
|
function renderHistory(assetId: string) {
|
||||||
const historyList = document.getElementById('pc-history-list');
|
const historyList = document.getElementById('pc-history-list');
|
||||||
if (!historyList) return;
|
if (!historyList) return;
|
||||||
@@ -189,7 +284,7 @@ function renderHistory(assetId: string) {
|
|||||||
<div class="history-item">
|
<div class="history-item">
|
||||||
<div class="history-date">${log.date}</div>
|
<div class="history-date">${log.date}</div>
|
||||||
<div class="history-user">수정자: ${log.user}</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>
|
</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,104 @@
|
|||||||
import { state } from '../../state';
|
import { state } from '../../core/state';
|
||||||
import { SoftwareAsset } from '../../excelHandler';
|
import { SoftwareAsset } from '../../core/excelHandler';
|
||||||
import { openModal } from './BaseModal';
|
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) {
|
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 swForm = document.getElementById('sw-asset-form') as HTMLFormElement;
|
||||||
const btnSaveSw = document.getElementById('btn-save-sw-asset') as HTMLButtonElement;
|
const btnSaveSw = document.getElementById('btn-save-sw-asset') as HTMLButtonElement;
|
||||||
const btnDeleteSw = document.getElementById('btn-delete-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) => {
|
btnSaveSw?.addEventListener('click', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!swForm.checkValidity()) { swForm.reportValidity(); return; }
|
if (!swForm.checkValidity()) { swForm.reportValidity(); return; }
|
||||||
@@ -44,7 +132,6 @@ export function initSwModal(renderContent: () => void, closeModals: () => void)
|
|||||||
renderContent();
|
renderContent();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 삭제 버튼 이벤트
|
|
||||||
btnDeleteSw?.addEventListener('click', (e) => {
|
btnDeleteSw?.addEventListener('click', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const id = (document.getElementById('sw-asset-id') as HTMLInputElement).value;
|
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) {
|
export function openSwModal(asset?: SoftwareAsset) {
|
||||||
const swModal = document.getElementById('sw-asset-modal') as HTMLDivElement;
|
|
||||||
const swForm = document.getElementById('sw-asset-form') as HTMLFormElement;
|
const swForm = document.getElementById('sw-asset-form') as HTMLFormElement;
|
||||||
const deleteBtn = document.getElementById('btn-delete-sw-asset')!;
|
const deleteBtn = document.getElementById('btn-delete-sw-asset')!;
|
||||||
|
|
||||||
@@ -71,11 +153,11 @@ export function openSwModal(asset?: SoftwareAsset) {
|
|||||||
const subGroup = document.getElementById('sw-구독일-group')!;
|
const subGroup = document.getElementById('sw-구독일-group')!;
|
||||||
const permGroup = document.getElementById('sw-유지보수-group')!;
|
const permGroup = document.getElementById('sw-유지보수-group')!;
|
||||||
if (state.activeSubTab === '구독SW') {
|
if (state.activeSubTab === '구독SW') {
|
||||||
subGroup.style.display = 'block';
|
subGroup.style.display = 'flex';
|
||||||
permGroup.style.display = 'none';
|
permGroup.style.display = 'none';
|
||||||
} else {
|
} else {
|
||||||
subGroup.style.display = 'none';
|
subGroup.style.display = 'none';
|
||||||
permGroup.style.display = 'block';
|
permGroup.style.display = 'flex';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (asset) {
|
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).value = asset.구독일 || '';
|
(document.getElementById('sw-구독일') as HTMLInputElement).value = asset.구독일 || '';
|
||||||
(document.getElementById('sw-유지보수여부') as HTMLInputElement).checked = !!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 = String(asset.수량);
|
||||||
(document.getElementById('sw-계정명') as HTMLInputElement).value = asset.계정명 || '';
|
(document.getElementById('sw-계정명') as HTMLInputElement).value = asset.계정명 || '';
|
||||||
(document.getElementById('sw-납품업체') as HTMLInputElement).value = asset.납품업체 || '';
|
(document.getElementById('sw-납품업체') as HTMLInputElement).value = asset.납품업체 || '';
|
||||||
(document.getElementById('sw-비고') as HTMLInputElement).value = asset.비고 || '';
|
(document.getElementById('sw-비고') as HTMLInputElement).value = asset.비고 || '';
|
||||||
} else {
|
} else {
|
||||||
document.getElementById('sw-modal-title')!.textContent = `새 ${state.activeSubTab} 자산 추가`;
|
document.getElementById('sw-modal-title')!.textContent = `신규 ${state.activeSubTab} 자산 추가`;
|
||||||
deleteBtn.style.display = 'none';
|
deleteBtn.style.display = 'none';
|
||||||
(document.getElementById('sw-asset-id') as HTMLInputElement).value = '';
|
(document.getElementById('sw-asset-id') as HTMLInputElement).value = '';
|
||||||
(document.getElementById('sw-asset-type') as HTMLInputElement).value = state.activeSubTab;
|
(document.getElementById('sw-asset-type') as HTMLInputElement).value = state.activeSubTab;
|
||||||
|
|||||||
@@ -1,73 +1,154 @@
|
|||||||
import { state } from '../../state';
|
import { state } from '../../core/state';
|
||||||
import { SoftwareAsset, SWUser } from '../../excelHandler';
|
import { SoftwareAsset, SWUser } from '../../core/excelHandler';
|
||||||
import { openModal } from './BaseModal';
|
import { openModal } from './BaseModal';
|
||||||
import { createIcons, Edit2, X, Paperclip } from 'lucide';
|
import { createIcons, Edit2, X, Paperclip } from 'lucide';
|
||||||
|
|
||||||
let currentSwUserAssetId: string = '';
|
let currentSwUserAssetId: string = '';
|
||||||
let tempSwUsers: SWUser[] = [];
|
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) {
|
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 btnOpenAddUser = document.getElementById('btn-open-add-user');
|
||||||
const btnSaveEditUser = document.getElementById('btn-save-edit-user');
|
const btnSaveEditUser = document.getElementById('btn-save-edit-user');
|
||||||
const btnSaveSwUserMapping = document.getElementById('btn-save-sw-user-mapping');
|
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', () => {
|
btnOpenAddUser?.addEventListener('click', () => openUserEditModal(-1));
|
||||||
openUserEditModal(-1);
|
btnSaveEditUser?.addEventListener('click', () => saveUserEdit());
|
||||||
});
|
|
||||||
|
|
||||||
btnSaveEditUser?.addEventListener('click', () => {
|
|
||||||
saveUserEdit();
|
|
||||||
});
|
|
||||||
|
|
||||||
btnSaveSwUserMapping?.addEventListener('click', () => {
|
btnSaveSwUserMapping?.addEventListener('click', () => {
|
||||||
// 변경사항 전역 상태에 반영
|
|
||||||
state.masterData.swUsers = state.masterData.swUsers.filter(u => u.swId !== currentSwUserAssetId);
|
state.masterData.swUsers = state.masterData.swUsers.filter(u => u.swId !== currentSwUserAssetId);
|
||||||
state.masterData.swUsers.push(...tempSwUsers);
|
state.masterData.swUsers.push(...tempSwUsers);
|
||||||
|
|
||||||
document.getElementById('sw-user-modal')?.classList.add('hidden');
|
document.getElementById('sw-user-modal')?.classList.add('hidden');
|
||||||
renderContent();
|
renderContent();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 취소 버튼들
|
btnCancelUserEdit?.addEventListener('click', () => document.getElementById('sw-user-edit-modal')?.classList.add('hidden'));
|
||||||
document.getElementById('btn-cancel-sw-user-edit')?.addEventListener('click', () => {
|
btnCloseUserEdit?.addEventListener('click', () => document.getElementById('sw-user-edit-modal')?.classList.add('hidden'));
|
||||||
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'));
|
||||||
document.getElementById('btn-cancel-sw-user-modal')?.addEventListener('click', () => {
|
|
||||||
document.getElementById('sw-user-modal')?.classList.add('hidden');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 소프트웨어 사용자 목록 렌더링
|
|
||||||
*/
|
|
||||||
function renderUserList() {
|
function renderUserList() {
|
||||||
const tbody = document.getElementById('user-list-body')!;
|
const tbody = document.getElementById('user-list-body')!;
|
||||||
tbody.innerHTML = '';
|
tbody.innerHTML = '';
|
||||||
if (tempSwUsers.length === 0) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tempSwUsers.forEach((user, idx) => {
|
tempSwUsers.forEach((user, idx) => {
|
||||||
const tr = document.createElement('tr');
|
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 deptTeam = [user.부서, user.팀].filter(Boolean).join(' / ') || '-';
|
||||||
const attachIcon = user.신청서명 ? `<i data-lucide="paperclip" class="text-primary" style="width:16px; height:16px;" title="${user.신청서명}"></i>` : '-';
|
const attachIcon = user.신청서명 ? `<i data-lucide="paperclip" class="text-primary" style="width:16px; height:16px;" title="${user.신청서명}"></i>` : '-';
|
||||||
|
|
||||||
tr.innerHTML = `
|
tr.innerHTML = `
|
||||||
<td style="padding:0.5rem; text-align:left;">${user.법인}</td>
|
<td>${user.법인}</td>
|
||||||
<td style="padding:0.5rem; text-align:left;">${deptTeam}</td>
|
<td>${deptTeam}</td>
|
||||||
<td style="padding:0.5rem; text-align:left;">${user.직위 || '-'}</td>
|
<td>${user.직위 || '-'}</td>
|
||||||
<td style="padding:0.5rem; text-align:left;"><strong>${user.이름}</strong></td>
|
<td><strong>${user.이름}</strong></td>
|
||||||
<td style="padding:0.5rem; text-align:center;">${user.사용기간 || '-'}</td>
|
<td style="text-align:center;">${user.사용기간 || '-'}</td>
|
||||||
<td style="padding:0.5rem; text-align:center; color: var(--text-light);">${attachIcon}</td>
|
<td style="text-align:center;">${attachIcon}</td>
|
||||||
<td style="padding:0.5rem; text-align:center; display:flex; justify-content:center; gap:0.25rem;">
|
<td style="text-align:center;">
|
||||||
<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-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);" title="삭제"><i data-lucide="x" 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>
|
</td>
|
||||||
`;
|
`;
|
||||||
tbody.appendChild(tr);
|
tbody.appendChild(tr);
|
||||||
@@ -84,7 +165,6 @@ function renderUserList() {
|
|||||||
|
|
||||||
tbody.querySelectorAll('.btn-remove-user').forEach(btn => {
|
tbody.querySelectorAll('.btn-remove-user').forEach(btn => {
|
||||||
btn.addEventListener('click', (e) => {
|
btn.addEventListener('click', (e) => {
|
||||||
e.stopPropagation();
|
|
||||||
const idx = parseInt((e.currentTarget as HTMLButtonElement).getAttribute('data-idx')!);
|
const idx = parseInt((e.currentTarget as HTMLButtonElement).getAttribute('data-idx')!);
|
||||||
tempSwUsers.splice(idx, 1);
|
tempSwUsers.splice(idx, 1);
|
||||||
renderUserList();
|
renderUserList();
|
||||||
@@ -92,9 +172,6 @@ function renderUserList() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 사용자 할당 모달 열기
|
|
||||||
*/
|
|
||||||
export function openSwUserModal(asset: SoftwareAsset) {
|
export function openSwUserModal(asset: SoftwareAsset) {
|
||||||
openModal('sw-user-modal');
|
openModal('sw-user-modal');
|
||||||
currentSwUserAssetId = asset.id;
|
currentSwUserAssetId = asset.id;
|
||||||
@@ -102,9 +179,6 @@ export function openSwUserModal(asset: SoftwareAsset) {
|
|||||||
renderUserList();
|
renderUserList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 사용자 추가/수정 모달 열기
|
|
||||||
*/
|
|
||||||
function openUserEditModal(idx: number) {
|
function openUserEditModal(idx: number) {
|
||||||
const editModal = document.getElementById('sw-user-edit-modal')!;
|
const editModal = document.getElementById('sw-user-edit-modal')!;
|
||||||
editModal.classList.remove('hidden');
|
editModal.classList.remove('hidden');
|
||||||
@@ -121,7 +195,7 @@ function openUserEditModal(idx: number) {
|
|||||||
(document.getElementById('new-user-신청서') as HTMLInputElement).value = '';
|
(document.getElementById('new-user-신청서') as HTMLInputElement).value = '';
|
||||||
document.getElementById('new-user-신청서명')!.innerText = '';
|
document.getElementById('new-user-신청서명')!.innerText = '';
|
||||||
} else {
|
} else {
|
||||||
document.getElementById('sw-user-edit-modal-title')!.innerText = '사용자 수정';
|
document.getElementById('sw-user-edit-modal-title')!.innerText = '사용자 정보 수정';
|
||||||
const u = tempSwUsers[idx];
|
const u = tempSwUsers[idx];
|
||||||
(document.getElementById('new-user-법인') as HTMLSelectElement).value = u.법인;
|
(document.getElementById('new-user-법인') as HTMLSelectElement).value = u.법인;
|
||||||
(document.getElementById('new-user-부서') as HTMLInputElement).value = u.부서;
|
(document.getElementById('new-user-부서') as HTMLInputElement).value = u.부서;
|
||||||
@@ -130,21 +204,14 @@ 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 = u.사용기간;
|
(document.getElementById('new-user-사용기간') as HTMLInputElement).value = u.사용기간;
|
||||||
(document.getElementById('new-user-신청서') as HTMLInputElement).value = '';
|
(document.getElementById('new-user-신청서') as HTMLInputElement).value = '';
|
||||||
document.getElementById('new-user-신청서명')!.innerText = u.신청서명 ? `📎${u.신청서명}` : '';
|
document.getElementById('new-user-신청서명')!.innerText = u.신청서명 ? `첨부: ${u.신청서명}` : '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 사용자 추가/수정 내용 저장 (임시 목록에 반영)
|
|
||||||
*/
|
|
||||||
function saveUserEdit() {
|
function saveUserEdit() {
|
||||||
const idx = parseInt((document.getElementById('edit-user-idx') as HTMLInputElement).value);
|
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.trim();
|
||||||
const 사용기간 = (document.getElementById('new-user-사용기간') as HTMLInputElement).value;
|
if (!이름) { alert('이름을 입력해주세요.'); return; }
|
||||||
|
|
||||||
const fileInput = document.getElementById('new-user-신청서') as HTMLInputElement;
|
const fileInput = document.getElementById('new-user-신청서') as HTMLInputElement;
|
||||||
let 신청서명 = '';
|
let 신청서명 = '';
|
||||||
@@ -154,17 +221,20 @@ function saveUserEdit() {
|
|||||||
신청서명 = tempSwUsers[idx].신청서명;
|
신청서명 = 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) {
|
if (idx === -1) tempSwUsers.push(userData);
|
||||||
tempSwUsers.push({
|
else tempSwUsers[idx] = userData;
|
||||||
id: Math.random().toString(36).substring(2, 9),
|
|
||||||
swId: currentSwUserAssetId,
|
|
||||||
법인, 부서, 팀, 직위, 이름, 사용기간, 신청서명
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
tempSwUsers[idx] = { ...tempSwUsers[idx], 법인, 부서, 팀, 직위, 이름, 사용기간, 신청서명 };
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('sw-user-edit-modal')?.classList.add('hidden');
|
document.getElementById('sw-user-edit-modal')?.classList.add('hidden');
|
||||||
renderUserList();
|
renderUserList();
|
||||||
|
|||||||
@@ -1,45 +1,77 @@
|
|||||||
import { state } from '../../state';
|
import { state } from '../../core/state';
|
||||||
import { HardwareAsset } from '../../excelHandler';
|
import { HardwareAsset } from '../../core/excelHandler';
|
||||||
import { openModal } from './BaseModal';
|
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) {
|
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 storageForm = document.getElementById('storage-asset-form') as HTMLFormElement;
|
||||||
const btnSaveStorage = document.getElementById('btn-save-storage-asset') as HTMLButtonElement;
|
const btnSaveStorage = document.getElementById('btn-save-storage-asset') as HTMLButtonElement;
|
||||||
const btnDeleteStorage = document.getElementById('btn-delete-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) => {
|
btnSaveStorage?.addEventListener('click', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!storageForm.checkValidity()) { storageForm.reportValidity(); return; }
|
if (!storageForm.checkValidity()) { storageForm.reportValidity(); return; }
|
||||||
|
|
||||||
const id = (document.getElementById('storage-asset-id') as HTMLInputElement).value;
|
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 = {
|
const newAsset: HardwareAsset = {
|
||||||
id: id || Math.random().toString(36).substring(2, 9),
|
id: id || Math.random().toString(36).substring(2, 9),
|
||||||
type: '스토리지',
|
type: '스토리지',
|
||||||
법인: (document.getElementById('storage-법인') as HTMLSelectElement).value,
|
법인: (document.getElementById('storage-법인') as HTMLInputElement).value,
|
||||||
storage유형: (document.getElementById('storage-유형') as HTMLSelectElement).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,
|
명칭: (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,
|
||||||
담당자_정: (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,
|
금액: (document.getElementById('storage-금액') as HTMLInputElement).value,
|
||||||
납품업체: (document.getElementById('storage-납품업체') as HTMLInputElement).value,
|
관리자: '', MACaddress: '', HW사양: '', OS: '', 납품업체: '', 품의서명: ''
|
||||||
품의서명
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
@@ -53,7 +85,6 @@ export function initStorageModal(renderContent: () => void, closeModals: () => v
|
|||||||
renderContent();
|
renderContent();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 삭제 버튼 이벤트
|
|
||||||
btnDeleteStorage?.addEventListener('click', (e) => {
|
btnDeleteStorage?.addEventListener('click', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const id = (document.getElementById('storage-asset-id') as HTMLInputElement).value;
|
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) {
|
export function openStorageModal(asset?: HardwareAsset) {
|
||||||
const storageModal = document.getElementById('storage-asset-modal') as HTMLDivElement;
|
|
||||||
const storageForm = document.getElementById('storage-asset-form') as HTMLFormElement;
|
const storageForm = document.getElementById('storage-asset-form') as HTMLFormElement;
|
||||||
const deleteBtn = document.getElementById('btn-delete-storage-asset')!;
|
const deleteBtn = document.getElementById('btn-delete-storage-asset')!;
|
||||||
|
|
||||||
@@ -82,27 +108,20 @@ export function openStorageModal(asset?: HardwareAsset) {
|
|||||||
deleteBtn.style.display = 'block';
|
deleteBtn.style.display = 'block';
|
||||||
|
|
||||||
(document.getElementById('storage-asset-id') as HTMLInputElement).value = asset.id;
|
(document.getElementById('storage-asset-id') as HTMLInputElement).value = asset.id;
|
||||||
(document.getElementById('storage-법인') as HTMLSelectElement).value = asset.법인;
|
(document.getElementById('storage-법인') as HTMLInputElement).value = asset.법인;
|
||||||
(document.getElementById('storage-유형') as HTMLSelectElement).value = asset.storage유형 || 'NAS';
|
(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-모델명') 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-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.구매일 || '';
|
||||||
(document.getElementById('storage-금액') as HTMLInputElement).value = asset.금액 ? Number(asset.금액.replace(/,/g, '')).toLocaleString() : '';
|
(document.getElementById('storage-금액') as HTMLInputElement).value = asset.금액 || '';
|
||||||
(document.getElementById('storage-납품업체') as HTMLInputElement).value = asset.납품업체 || '';
|
|
||||||
(document.getElementById('storage-품의서명') as HTMLElement).innerText = asset.품의서명 ? `📎${asset.품의서명}` : '';
|
|
||||||
} else {
|
} else {
|
||||||
document.getElementById('storage-modal-title')!.textContent = '새 스토리지 자산 추가';
|
document.getElementById('storage-modal-title')!.textContent = '신규 스토리지 자산 추가';
|
||||||
deleteBtn.style.display = 'none';
|
deleteBtn.style.display = 'none';
|
||||||
(document.getElementById('storage-asset-id') as HTMLInputElement).value = '';
|
(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 = '';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { state } from '../state';
|
import { state } from '../core/state';
|
||||||
|
|
||||||
export function renderSidebar(onTabChange: (tab: string) => void) {
|
export function renderSidebar(onTabChange: (tab: string) => void) {
|
||||||
const navItems = document.querySelectorAll('.nav-list li');
|
const navItems = document.querySelectorAll('.nav-list li');
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ export function generateDummyData(): MasterAssetData {
|
|||||||
납품업체: '총판',
|
납품업체: '총판',
|
||||||
비고: '연간구독'
|
비고: '연간구독'
|
||||||
});
|
});
|
||||||
// ... rest unchanged
|
|
||||||
const assignCount = Math.floor(Math.random() * 2) + 1;
|
const assignCount = Math.floor(Math.random() * 2) + 1;
|
||||||
for (let j=0; j<assignCount; j++) {
|
for (let j=0; j<assignCount; j++) {
|
||||||
swUsers.push({
|
swUsers.push({
|
||||||
@@ -482,27 +482,6 @@ export const realServerData = [
|
|||||||
"SSD1": "280GB",
|
"SSD1": "280GB",
|
||||||
"SSD2": "1TB"
|
"SSD2": "1TB"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"법인": "회사",
|
|
||||||
"자산코드": "서버번호",
|
|
||||||
"storage유형": "서버",
|
|
||||||
"용도": "구분",
|
|
||||||
"상세": "비고",
|
|
||||||
"위치": "설치위치",
|
|
||||||
"담당자_정": "담당자\n정",
|
|
||||||
"담당자_부": "담당자\n부",
|
|
||||||
"IP주소": "IP 주소",
|
|
||||||
"IP2": "IP 주소",
|
|
||||||
"원격접속": "원격접속\n접속도구",
|
|
||||||
"서버ID": "원격접속\n아이디",
|
|
||||||
"서버PW": "원격접속\n비빌번호",
|
|
||||||
"모델명": "",
|
|
||||||
"OS": "모델명",
|
|
||||||
"CPU": "용량",
|
|
||||||
"RAM": "",
|
|
||||||
"SSD1": "",
|
|
||||||
"SSD2": ""
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"법인": "삼안",
|
"법인": "삼안",
|
||||||
"자산코드": "sa-das-001",
|
"자산코드": "sa-das-001",
|
||||||
@@ -533,8 +512,8 @@ export const realServerData = [
|
|||||||
"위치": "서관 203번",
|
"위치": "서관 203번",
|
||||||
"담당자_정": "",
|
"담당자_정": "",
|
||||||
"담당자_부": "",
|
"담당자_부": "",
|
||||||
"IP주소": "(IDC) 118.220.172.246",
|
"IP주소": "118.220.172.246",
|
||||||
"IP2": "(IDC) 118.220.172.246",
|
"IP2": "118.220.172.246",
|
||||||
"원격접속": "원격",
|
"원격접속": "원격",
|
||||||
"서버ID": "administrator",
|
"서버ID": "administrator",
|
||||||
"서버PW": "sg11707808",
|
"서버PW": "sg11707808",
|
||||||
@@ -554,8 +533,8 @@ export const realServerData = [
|
|||||||
"위치": "서관 205번",
|
"위치": "서관 205번",
|
||||||
"담당자_정": "",
|
"담당자_정": "",
|
||||||
"담당자_부": "",
|
"담당자_부": "",
|
||||||
"IP주소": "(IDC) 118.220.172.248\n(매니지먼트) 118.220.172.247",
|
"IP주소": "118.220.172.248",
|
||||||
"IP2": "(IDC) 118.220.172.248\n(매니지먼트) 118.220.172.247",
|
"IP2": "118.220.172.247",
|
||||||
"원격접속": "원격",
|
"원격접속": "원격",
|
||||||
"서버ID": "administrator\n-",
|
"서버ID": "administrator\n-",
|
||||||
"서버PW": "sg11707808\n-",
|
"서버PW": "sg11707808\n-",
|
||||||
@@ -575,8 +554,8 @@ export const realServerData = [
|
|||||||
"위치": "서관 202번",
|
"위치": "서관 202번",
|
||||||
"담당자_정": "",
|
"담당자_정": "",
|
||||||
"담당자_부": "",
|
"담당자_부": "",
|
||||||
"IP주소": "(IDC) 118.220.172.241\n(매지니먼트) 118.220.172.240",
|
"IP주소": "118.220.172.241",
|
||||||
"IP2": "(IDC) 118.220.172.241\n(매지니먼트) 118.220.172.240",
|
"IP2": "118.220.172.240",
|
||||||
"원격접속": "원격",
|
"원격접속": "원격",
|
||||||
"서버ID": "administrator\nadmin0",
|
"서버ID": "administrator\nadmin0",
|
||||||
"서버PW": "saman1!\nRoot1234",
|
"서버PW": "saman1!\nRoot1234",
|
||||||
@@ -653,7 +632,7 @@ export const realServerData = [
|
|||||||
"법인": "기술개발센터",
|
"법인": "기술개발센터",
|
||||||
"자산코드": "",
|
"자산코드": "",
|
||||||
"storage유형": "NAS",
|
"storage유형": "NAS",
|
||||||
"용도": "그래픽스개발팀\r\n데이터 백업 NAS",
|
"용도": "그래픽스개발팀 데이터 백업 NAS",
|
||||||
"상세": "그래픽스 개발팀 데이터 백업용 NAS",
|
"상세": "그래픽스 개발팀 데이터 백업용 NAS",
|
||||||
"위치": "마천사무실",
|
"위치": "마천사무실",
|
||||||
"담당자_정": "",
|
"담당자_정": "",
|
||||||
@@ -974,7 +953,7 @@ export const realServerData = [
|
|||||||
"자산코드": "",
|
"자산코드": "",
|
||||||
"storage유형": "서버",
|
"storage유형": "서버",
|
||||||
"용도": "GSIM 서버",
|
"용도": "GSIM 서버",
|
||||||
"상세": "ProjectMaster(이하 PM) 오브젝트 스토리지서버(온프레미스), Terrain 및 Basemap 데이터 저장",
|
"상세": "Basemap 데이터 저장",
|
||||||
"위치": "마천사무실",
|
"위치": "마천사무실",
|
||||||
"담당자_정": "",
|
"담당자_정": "",
|
||||||
"담당자_부": "",
|
"담당자_부": "",
|
||||||
@@ -994,7 +973,7 @@ export const realServerData = [
|
|||||||
"자산코드": "",
|
"자산코드": "",
|
||||||
"storage유형": "스토리지",
|
"storage유형": "스토리지",
|
||||||
"용도": "GSIM 스토리지",
|
"용도": "GSIM 스토리지",
|
||||||
"상세": "ProjectMaster(이하 PM) 오브젝트 스토리지서버(온프레미스)",
|
"상세": "ProjectMaster 오브젝트 스토리지",
|
||||||
"위치": "마천사무실",
|
"위치": "마천사무실",
|
||||||
"담당자_정": "",
|
"담당자_정": "",
|
||||||
"담당자_부": "",
|
"담당자_부": "",
|
||||||
@@ -1034,7 +1013,7 @@ export const realServerData = [
|
|||||||
"자산코드": "",
|
"자산코드": "",
|
||||||
"storage유형": "서버",
|
"storage유형": "서버",
|
||||||
"용도": "HM MapService 2.0 서버",
|
"용도": "HM MapService 2.0 서버",
|
||||||
"상세": "공간데이터(타일맵, 수치지형도 2.0, ...) 다운로드 서비스 등 신규공간정보 서비스",
|
"상세": "공간데이터 다운로드 서비스 등",
|
||||||
"위치": "마천사무실",
|
"위치": "마천사무실",
|
||||||
"담당자_정": "",
|
"담당자_정": "",
|
||||||
"담당자_부": "",
|
"담당자_부": "",
|
||||||
@@ -1054,7 +1033,7 @@ export const realServerData = [
|
|||||||
"자산코드": "",
|
"자산코드": "",
|
||||||
"storage유형": "스토리지",
|
"storage유형": "스토리지",
|
||||||
"용도": "HM MapService 2.0 스토리지",
|
"용도": "HM MapService 2.0 스토리지",
|
||||||
"상세": "공간데이터(타일맵, 수치지형도 2.0, ...) 다운로드 서비스 등",
|
"상세": "공간데이터 저장용",
|
||||||
"위치": "마천사무실",
|
"위치": "마천사무실",
|
||||||
"담당자_정": "",
|
"담당자_정": "",
|
||||||
"담당자_부": "",
|
"담당자_부": "",
|
||||||
@@ -1074,7 +1053,7 @@ export const realServerData = [
|
|||||||
"자산코드": "",
|
"자산코드": "",
|
||||||
"storage유형": "서버",
|
"storage유형": "서버",
|
||||||
"용도": "Gitlab Runner",
|
"용도": "Gitlab Runner",
|
||||||
"상세": "GitLab 운영 서버(단지설계,KnGIL) 추후 문남연 GIT서버로 이전예정",
|
"상세": "GitLab 운영 서버",
|
||||||
"위치": "마천사무실",
|
"위치": "마천사무실",
|
||||||
"담당자_정": "",
|
"담당자_정": "",
|
||||||
"담당자_부": "",
|
"담당자_부": "",
|
||||||
@@ -1130,7 +1109,7 @@ export const realServerData = [
|
|||||||
"자산코드": "2",
|
"자산코드": "2",
|
||||||
"storage유형": "NAS",
|
"storage유형": "NAS",
|
||||||
"용도": "NAS 1",
|
"용도": "NAS 1",
|
||||||
"상세": "한라 사업지원, 경영지원, 업무, 안전품질, 운영 공용 NAS",
|
"상세": "한라 공용 NAS",
|
||||||
"위치": "한맥빌딩(MDF 실)",
|
"위치": "한맥빌딩(MDF 실)",
|
||||||
"IP주소": "192.168.9.32",
|
"IP주소": "192.168.9.32",
|
||||||
"모델명": "DS224+",
|
"모델명": "DS224+",
|
||||||
@@ -1146,7 +1125,7 @@ export const realServerData = [
|
|||||||
"자산코드": "3",
|
"자산코드": "3",
|
||||||
"storage유형": "NAS",
|
"storage유형": "NAS",
|
||||||
"용도": "NAS 4",
|
"용도": "NAS 4",
|
||||||
"상세": "한라 법무 , 기획부서 공용 NAS",
|
"상세": "한라 공용 NAS",
|
||||||
"위치": "한맥빌딩(MDF 실)",
|
"위치": "한맥빌딩(MDF 실)",
|
||||||
"IP주소": "192.168.9.25",
|
"IP주소": "192.168.9.25",
|
||||||
"모델명": "CS407",
|
"모델명": "CS407",
|
||||||
@@ -1178,7 +1157,7 @@ export const realServerData = [
|
|||||||
"자산코드": "5",
|
"자산코드": "5",
|
||||||
"storage유형": "NAS",
|
"storage유형": "NAS",
|
||||||
"용도": "NAS 6",
|
"용도": "NAS 6",
|
||||||
"상세": "한라 기반사업부, 기술영업부 공용 NAS",
|
"상세": "한라 공용 NAS",
|
||||||
"위치": "한맥빌딩(MDF 실)",
|
"위치": "한맥빌딩(MDF 실)",
|
||||||
"IP주소": "192.168.9.27",
|
"IP주소": "192.168.9.27",
|
||||||
"모델명": "DS923+",
|
"모델명": "DS923+",
|
||||||
@@ -1226,7 +1205,7 @@ export const realServerData = [
|
|||||||
"자산코드": "8",
|
"자산코드": "8",
|
||||||
"storage유형": "NAS",
|
"storage유형": "NAS",
|
||||||
"용도": "한맥 NAS 1",
|
"용도": "한맥 NAS 1",
|
||||||
"상세": "한맥 도로부 , 안전진단부 , 환경평가부 공용 NAS",
|
"상세": "한맥 공용 NAS",
|
||||||
"위치": "한맥빌딩(MDF 실)",
|
"위치": "한맥빌딩(MDF 실)",
|
||||||
"IP주소": "192.168.10.3",
|
"IP주소": "192.168.10.3",
|
||||||
"모델명": "DS416j",
|
"모델명": "DS416j",
|
||||||
@@ -1242,7 +1221,7 @@ export const realServerData = [
|
|||||||
"자산코드": "9",
|
"자산코드": "9",
|
||||||
"storage유형": "NAS",
|
"storage유형": "NAS",
|
||||||
"용도": "한맥 NAS 2",
|
"용도": "한맥 NAS 2",
|
||||||
"상세": "한맥 감리부 , 교통부 , 도시계획부 , 수자원부 , 총괄기획실 , 장헌산업 공용 NAS",
|
"상세": "한맥 공용 NAS",
|
||||||
"위치": "한맥빌딩(MDF 실)",
|
"위치": "한맥빌딩(MDF 실)",
|
||||||
"IP주소": "192.168.10.6",
|
"IP주소": "192.168.10.6",
|
||||||
"모델명": "DS416j",
|
"모델명": "DS416j",
|
||||||
@@ -1258,7 +1237,7 @@ export const realServerData = [
|
|||||||
"자산코드": "10",
|
"자산코드": "10",
|
||||||
"storage유형": "NAS",
|
"storage유형": "NAS",
|
||||||
"용도": "한맥 NAS 3",
|
"용도": "한맥 NAS 3",
|
||||||
"상세": "한맥 경영지원부 , 지반터널부 , 구조부 , 상하수도부 공용 NAS",
|
"상세": "한맥 공용 NAS",
|
||||||
"위치": "한맥빌딩(MDF 실)",
|
"위치": "한맥빌딩(MDF 실)",
|
||||||
"IP주소": "192.168.10.7",
|
"IP주소": "192.168.10.7",
|
||||||
"모델명": "DS416j",
|
"모델명": "DS416j",
|
||||||
@@ -1450,7 +1429,7 @@ export const realServerData = [
|
|||||||
"자산코드": "22",
|
"자산코드": "22",
|
||||||
"storage유형": "PC\n서버(랙)",
|
"storage유형": "PC\n서버(랙)",
|
||||||
"용도": "저장소 및 전산모사\n구)스마트건설 서버",
|
"용도": "저장소 및 전산모사\n구)스마트건설 서버",
|
||||||
"상세": "EGBIM, ParaView, CFDCore\n스마트딜리버리, 지능화검색시스템, 디지털화설문, 검색WIKI 웹서비스",
|
"상세": "ParaView, CFDCore\n디지털화설문, 검색WIKI 웹서비스",
|
||||||
"위치": "한맥빌딩(MDF 실)",
|
"위치": "한맥빌딩(MDF 실)",
|
||||||
"IP주소": "172.16.10.213",
|
"IP주소": "172.16.10.213",
|
||||||
"모델명": "조립PC\nProLiant DL360 Gen10",
|
"모델명": "조립PC\nProLiant DL360 Gen10",
|
||||||
@@ -1466,7 +1445,7 @@ export const realServerData = [
|
|||||||
"자산코드": "23",
|
"자산코드": "23",
|
||||||
"storage유형": "서버(랙)",
|
"storage유형": "서버(랙)",
|
||||||
"용도": "IDC 산하ERP서버",
|
"용도": "IDC 산하ERP서버",
|
||||||
"상세": "스마트건설 프로젝트 개발 서버 (XR 가상화 메인 서버) → IDC 산하ERP서버",
|
"상세": "XR 가상화 메인 서버 → IDC 산하ERP서버",
|
||||||
"위치": "한맥빌딩(MDF 실)",
|
"위치": "한맥빌딩(MDF 실)",
|
||||||
"IP주소": "172.16.10.226",
|
"IP주소": "172.16.10.226",
|
||||||
"모델명": "ProLiant DL360 Gen10",
|
"모델명": "ProLiant DL360 Gen10",
|
||||||
39
src/main.ts
39
src/main.ts
@@ -1,18 +1,21 @@
|
|||||||
import { state } from './state';
|
import { state } from './core/state';
|
||||||
import { renderSidebar } from './components/Sidebar';
|
import { renderSidebar } from './components/Sidebar';
|
||||||
import { renderDashboard } from './views/DashboardView';
|
import { renderDashboard } from './views/DashboardView';
|
||||||
import { renderTable } from './views/AssetTableView';
|
import { renderTable } from './views/AssetTableView';
|
||||||
import { downloadTemplate, exportToExcel, parseExcel } from './excelHandler';
|
import { downloadTemplate, exportToExcel, parseExcel, HardwareAsset } from './core/excelHandler';
|
||||||
|
import { initBaseModal } from './components/Modal/BaseModal';
|
||||||
import { initPcModal } from './components/Modal/PCModal';
|
import { initPcModal } from './components/Modal/PCModal';
|
||||||
import { initHwModal, openHwModal } from './components/Modal/HWModal';
|
import { initHwModal, openHwModal } from './components/Modal/HWModal';
|
||||||
import { initStorageModal } from './components/Modal/StorageModal';
|
import { initStorageModal } from './components/Modal/StorageModal';
|
||||||
import { initSwModal } from './components/Modal/SWModal';
|
import { initSwModal } from './components/Modal/SWModal';
|
||||||
import { initSwUserModal } from './components/Modal/SWUserModal';
|
import { initSwUserModal } from './components/Modal/SWUserModal';
|
||||||
|
import { initDashboardDetailModal } from './components/Modal/DashboardDetailModal';
|
||||||
import { createIcons, Download, Upload, FileSpreadsheet, Plus, X, LayoutDashboard, Monitor, Server, Database, Laptop, CalendarClock, Key, Cpu, Layers, Users, Paperclip, Edit2, History, RefreshCcw } from 'lucide';
|
import { createIcons, Download, Upload, FileSpreadsheet, Plus, X, LayoutDashboard, Monitor, Server, Database, Laptop, CalendarClock, Key, Cpu, Layers, Users, Paperclip, Edit2, History, RefreshCcw } from 'lucide';
|
||||||
|
|
||||||
// --- App Initialization ---
|
// --- App Initialization ---
|
||||||
function initApp() {
|
function initApp() {
|
||||||
const mainContent = document.getElementById('main-content')!;
|
const mainContent = document.getElementById('main-content')!;
|
||||||
|
if (!mainContent) return;
|
||||||
|
|
||||||
// 1. 초기 뷰 렌더링 (대시보드)
|
// 1. 초기 뷰 렌더링 (대시보드)
|
||||||
renderDashboard(mainContent);
|
renderDashboard(mainContent);
|
||||||
@@ -28,15 +31,20 @@ function initApp() {
|
|||||||
}
|
}
|
||||||
// 상단 타이틀 업데이트
|
// 상단 타이틀 업데이트
|
||||||
const titleEl = document.getElementById('current-tab-title')!;
|
const titleEl = document.getElementById('current-tab-title')!;
|
||||||
titleEl.textContent = `${state.activeCategory === 'hw' ? '하드웨어' : '소프트웨어'} / ${state.activeSubTab}`;
|
if (titleEl) {
|
||||||
|
const catName = state.activeCategory === 'hw' ? '하드웨어' : '소프트웨어';
|
||||||
|
titleEl.textContent = `${catName} / ${state.activeSubTab}`;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 3. 모달 초기화
|
// 3. 모달 초기화 (HTML 주입 및 개별 로직 바인딩)
|
||||||
initPcModal();
|
const { closeAllModals } = initBaseModal();
|
||||||
initHwModal();
|
|
||||||
initStorageModal();
|
initPcModal(() => renderTable(mainContent), closeAllModals);
|
||||||
initSwModal();
|
initHwModal(); // HW 모달은 내부에서 자체 닫기 로직 포함 중이나 추후 통일 가능
|
||||||
initSwUserModal(() => renderTable(mainContent), () => {});
|
initStorageModal(() => renderTable(mainContent), closeAllModals);
|
||||||
|
initSwModal(() => renderTable(mainContent), closeAllModals);
|
||||||
|
initSwUserModal(() => renderTable(mainContent), closeAllModals);
|
||||||
initDashboardDetailModal();
|
initDashboardDetailModal();
|
||||||
|
|
||||||
// 4. 전역 버튼 이벤트 바인딩
|
// 4. 전역 버튼 이벤트 바인딩
|
||||||
@@ -55,7 +63,7 @@ function initApp() {
|
|||||||
|
|
||||||
document.getElementById('btn-add-asset')?.addEventListener('click', () => {
|
document.getElementById('btn-add-asset')?.addEventListener('click', () => {
|
||||||
if (state.activeSubTab === '서버' || state.activeSubTab === '전산비품' || state.activeSubTab === '스토리지') {
|
if (state.activeSubTab === '서버' || state.activeSubTab === '전산비품' || state.activeSubTab === '스토리지') {
|
||||||
const newAsset = {
|
const newAsset: HardwareAsset = {
|
||||||
id: Math.random().toString(36).substring(2, 9),
|
id: Math.random().toString(36).substring(2, 9),
|
||||||
type: state.activeSubTab,
|
type: state.activeSubTab,
|
||||||
법인: '한맥',
|
법인: '한맥',
|
||||||
@@ -80,16 +88,5 @@ function initApp() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function initDashboardDetailModal() {
|
|
||||||
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(); });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the app
|
// Start the app
|
||||||
document.addEventListener('DOMContentLoaded', initApp);
|
document.addEventListener('DOMContentLoaded', initApp);
|
||||||
|
|||||||
@@ -126,6 +126,19 @@
|
|||||||
font-size: 0.8125rem;
|
font-size: 0.8125rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-security {
|
||||||
|
font-size: 10px;
|
||||||
|
background-color: #fef2f2;
|
||||||
|
color: #ef4444;
|
||||||
|
padding: 1px 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 1px solid #fee2e2;
|
||||||
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group input,
|
.form-group input,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { state } from '../state';
|
import { state } from '../core/state';
|
||||||
import { createIcons, Download, Upload, FileSpreadsheet, Plus, X, LayoutDashboard, Monitor, Server, Database, Laptop, CalendarClock, Key, Cpu, Layers, Users, Paperclip, Edit2, RefreshCcw } from 'lucide';
|
import { createIcons, Download, Upload, FileSpreadsheet, Plus, X, LayoutDashboard, Monitor, Server, Database, Laptop, CalendarClock, Key, Cpu, Layers, Users, Paperclip, Edit2, RefreshCcw } from 'lucide';
|
||||||
import { openPcModal } from '../components/Modal/PCModal';
|
import { openPcModal } from '../components/Modal/PCModal';
|
||||||
import { openHwModal } from '../components/Modal/HWModal';
|
import { openHwModal } from '../components/Modal/HWModal';
|
||||||
@@ -10,7 +10,7 @@ import { openSwUserModal } from '../components/Modal/SWUserModal';
|
|||||||
* 자산 목록 테이블 렌더링 메인 함수
|
* 자산 목록 테이블 렌더링 메인 함수
|
||||||
*/
|
*/
|
||||||
export function renderTable(mainContent: HTMLElement) {
|
export function renderTable(mainContent: HTMLElement) {
|
||||||
mainContent.innerHTML = ''; // 기존 내용 삭제 (중요)
|
mainContent.innerHTML = '';
|
||||||
const container = document.createElement('div');
|
const container = document.createElement('div');
|
||||||
container.className = 'view-container';
|
container.className = 'view-container';
|
||||||
const table = document.createElement('table');
|
const table = document.createElement('table');
|
||||||
@@ -21,7 +21,6 @@ export function renderTable(mainContent: HTMLElement) {
|
|||||||
renderSwTable(table, container, mainContent);
|
renderSwTable(table, container, mainContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 테이블 내 아이콘 초기화
|
|
||||||
createIcons({
|
createIcons({
|
||||||
icons: { Download, Upload, FileSpreadsheet, Plus, X, LayoutDashboard, Monitor, Server, Database, Laptop, CalendarClock, Key, Cpu, Layers, Users, Paperclip, Edit2 }
|
icons: { Download, Upload, FileSpreadsheet, Plus, X, LayoutDashboard, Monitor, Server, Database, Laptop, CalendarClock, Key, Cpu, Layers, Users, Paperclip, Edit2 }
|
||||||
});
|
});
|
||||||
@@ -44,7 +43,6 @@ function renderHwTable(table: HTMLTableElement, container: HTMLElement, mainCont
|
|||||||
tr.style.cursor = 'pointer';
|
tr.style.cursor = 'pointer';
|
||||||
tr.innerHTML = `<td>${idx+1}</td><td>${asset.법인}</td><td>${asset.자산코드}</td><td>${asset.사용자||''}</td><td>${asset.위치||''}</td><td>${asset.CPU||''}</td><td>${asset.GPU||''}</td><td>${asset.RAM||''}</td><td>${asset.SSD1||'-'}</td><td>${asset.SSD2||'-'}</td><td>${asset.HDD1||'-'}</td><td>${asset.HDD2||'-'}</td><td>${asset.구매일||''}</td><td>${asset.금액||''}</td><td>${asset.납품업체||''}</td><td style="text-align:center;">${asset.품의서명 ? '<i data-lucide="paperclip" class="text-primary"></i>' : '-'}</td><td><button class="btn btn-outline btn-sm btn-edit">수정</button></td>`;
|
tr.innerHTML = `<td>${idx+1}</td><td>${asset.법인}</td><td>${asset.자산코드}</td><td>${asset.사용자||''}</td><td>${asset.위치||''}</td><td>${asset.CPU||''}</td><td>${asset.GPU||''}</td><td>${asset.RAM||''}</td><td>${asset.SSD1||'-'}</td><td>${asset.SSD2||'-'}</td><td>${asset.HDD1||'-'}</td><td>${asset.HDD2||'-'}</td><td>${asset.구매일||''}</td><td>${asset.금액||''}</td><td>${asset.납품업체||''}</td><td style="text-align:center;">${asset.품의서명 ? '<i data-lucide="paperclip" class="text-primary"></i>' : '-'}</td><td><button class="btn btn-outline btn-sm btn-edit">수정</button></td>`;
|
||||||
tr.addEventListener('click', (e) => { if (!(e.target as HTMLElement).closest('button')) openPcModal(asset); });
|
tr.addEventListener('click', (e) => { if (!(e.target as HTMLElement).closest('button')) openPcModal(asset); });
|
||||||
tr.querySelector('.btn-edit')?.addEventListener('click', () => openPcModal(asset));
|
|
||||||
tbody.appendChild(tr);
|
tbody.appendChild(tr);
|
||||||
});
|
});
|
||||||
} else if (state.activeSubTab === '스토리지') {
|
} else if (state.activeSubTab === '스토리지') {
|
||||||
@@ -59,33 +57,12 @@ function renderHwTable(table: HTMLTableElement, container: HTMLElement, mainCont
|
|||||||
tr.style.cursor = 'pointer';
|
tr.style.cursor = 'pointer';
|
||||||
tr.innerHTML = `<td>${idx+1}</td><td>${asset.법인}</td><td>${asset.storage유형||''}</td><td>${asset.자산코드}</td><td>${asset.명칭}</td><td>${asset.위치||''}</td><td>${asset.모델명||''}</td><td>${asset.용량||''}</td><td>${asset.담당자_정||''}</td><td>${asset.IP주소||''}</td><td>${asset.구매일||''}</td><td>${asset.금액||''}</td><td><button class="btn btn-outline btn-sm btn-edit">수정</button></td>`;
|
tr.innerHTML = `<td>${idx+1}</td><td>${asset.법인}</td><td>${asset.storage유형||''}</td><td>${asset.자산코드}</td><td>${asset.명칭}</td><td>${asset.위치||''}</td><td>${asset.모델명||''}</td><td>${asset.용량||''}</td><td>${asset.담당자_정||''}</td><td>${asset.IP주소||''}</td><td>${asset.구매일||''}</td><td>${asset.금액||''}</td><td><button class="btn btn-outline btn-sm btn-edit">수정</button></td>`;
|
||||||
tr.addEventListener('click', (e) => { if (!(e.target as HTMLElement).closest('button')) openStorageModal(asset); });
|
tr.addEventListener('click', (e) => { if (!(e.target as HTMLElement).closest('button')) openStorageModal(asset); });
|
||||||
tr.querySelector('.btn-edit')?.addEventListener('click', () => openStorageModal(asset));
|
|
||||||
tbody.appendChild(tr);
|
tbody.appendChild(tr);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// 서버 또는 전산비품
|
// 서버 또는 전산비품
|
||||||
if (state.activeSubTab === '서버') {
|
if (state.activeSubTab === '서버') {
|
||||||
table.innerHTML = `
|
table.innerHTML = `<thead><tr><th>No</th><th>법인</th><th>자산번호</th><th>유형</th><th>용도</th><th>상세</th><th>설치위치</th><th>담당자</th><th>모델명</th><th>OS</th><th>CPU</th><th>RAM</th><th>Storage</th></tr></thead><tbody id="dynamic-tbody"></tbody>`;
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>No</th>
|
|
||||||
<th>법인</th>
|
|
||||||
<th>자산번호</th>
|
|
||||||
<th>유형</th>
|
|
||||||
<th>용도</th>
|
|
||||||
<th>상세</th>
|
|
||||||
<th>설치위치</th>
|
|
||||||
<th>담당자</th>
|
|
||||||
<th>IP 주소</th>
|
|
||||||
<th>원격접속</th>
|
|
||||||
<th>모델명</th>
|
|
||||||
<th>OS</th>
|
|
||||||
<th>CPU</th>
|
|
||||||
<th>RAM</th>
|
|
||||||
<th>Storage</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="dynamic-tbody"></tbody>`;
|
|
||||||
} else {
|
} else {
|
||||||
table.innerHTML = `<thead><tr><th>No</th><th>법인</th>${state.activeSubTab === '전산비품' ? '<th>유형</th>' : ''}<th>자산코드</th><th>명칭</th><th>위치</th><th>관리자</th><th>구매일</th><th>금액</th><th>관리</th></tr></thead><tbody id="dynamic-tbody"></tbody>`;
|
table.innerHTML = `<thead><tr><th>No</th><th>법인</th>${state.activeSubTab === '전산비품' ? '<th>유형</th>' : ''}<th>자산코드</th><th>명칭</th><th>위치</th><th>관리자</th><th>구매일</th><th>금액</th><th>관리</th></tr></thead><tbody id="dynamic-tbody"></tbody>`;
|
||||||
}
|
}
|
||||||
@@ -94,52 +71,27 @@ function renderHwTable(table: HTMLTableElement, container: HTMLElement, mainCont
|
|||||||
container.appendChild(tableWrapper);
|
container.appendChild(tableWrapper);
|
||||||
mainContent.appendChild(container);
|
mainContent.appendChild(container);
|
||||||
const tbody = document.getElementById('dynamic-tbody')!;
|
const tbody = document.getElementById('dynamic-tbody')!;
|
||||||
const colCount = state.activeSubTab === '서버' ? 15 : (state.activeSubTab === '전산비품' ? 11 : 10);
|
const colCount = state.activeSubTab === '서버' ? 13 : (state.activeSubTab === '전산비품' ? 11 : 10);
|
||||||
if (list.length === 0) { tbody.innerHTML = `<tr><td colspan="${colCount}">등록된 자산이 없습니다.</td></tr>`; return; }
|
if (list.length === 0) { tbody.innerHTML = `<tr><td colspan="${colCount}">등록된 자산이 없습니다.</td></tr>`; return; }
|
||||||
|
|
||||||
list.forEach((asset, idx) => {
|
list.forEach((asset, idx) => {
|
||||||
const tr = document.createElement('tr');
|
const tr = document.createElement('tr');
|
||||||
tr.style.cursor = 'pointer';
|
tr.style.cursor = 'pointer';
|
||||||
|
|
||||||
const formatInline = (v: any) => String(v || '').replace(/\n/g, ' / ').trim();
|
const formatInline = (v: any) => String(v || '').replace(/\n/g, ' / ').trim();
|
||||||
const getBadge = (text: string, bgColor: string) =>
|
const getBadge = (text: string, bgColor: string) => `<span style="background:${bgColor}; color:white; font-size:10px; padding:1px 4px; border-radius:3px; font-weight:700; margin-right:4px; display:inline-block; line-height:1.2;">${text}</span>`;
|
||||||
`<span style="background:${bgColor}; color:white; font-size:10px; padding:1px 4px; border-radius:3px; font-weight:700; margin-right:4px; display:inline-block; line-height:1.2;">${text}</span>`;
|
|
||||||
|
|
||||||
if (state.activeSubTab === '서버') {
|
if (state.activeSubTab === '서버') {
|
||||||
const mainManager = asset.담당자_정 || '';
|
const mainManager = asset.담당자_정 || '';
|
||||||
const subManager = asset.담당자_부 || '';
|
const subManager = asset.담당자_부 || '';
|
||||||
|
const managerHtml = [mainManager ? `${getBadge('정', '#1E5149')} ${mainManager}` : '', subManager ? `${getBadge('부', '#9CA3AF')} ${subManager}` : ''].filter(v => v !== '').join(' / ');
|
||||||
// 담당자 배지화
|
|
||||||
const managerHtml = [
|
|
||||||
mainManager ? `${getBadge('정', '#1E5149')} ${mainManager}` : '',
|
|
||||||
subManager ? `${getBadge('부', '#9CA3AF')} ${subManager}` : ''
|
|
||||||
].filter(v => v !== '').join(' / ');
|
|
||||||
|
|
||||||
// 원격접속 배지화
|
|
||||||
const tools = (asset.원격접속 || '').split('\n');
|
|
||||||
const ids = (asset.서버ID || '').split('\n');
|
|
||||||
const pws = (asset.서버PW || '').split('\n');
|
|
||||||
const maxLen = Math.max(tools.length, ids.length, pws.length);
|
|
||||||
|
|
||||||
let remoteItems = [];
|
|
||||||
for(let i=0; i<maxLen; i++) {
|
|
||||||
let toolName = tools[i] || '접속';
|
|
||||||
let badgeColor = '#3B82F6'; // 기본 파랑 (Remote)
|
|
||||||
if (toolName.toLowerCase().includes('any')) badgeColor = '#EF4444'; // Anydesk 빨강
|
|
||||||
if (toolName.toLowerCase().includes('chrome')) badgeColor = '#F59E0B'; // Chrome 노랑
|
|
||||||
|
|
||||||
let item = `${getBadge(toolName, badgeColor)}`;
|
|
||||||
if (ids[i] || pws[i]) {
|
|
||||||
item += ` (${ids[i] || '-'}/${pws[i] || '-'})`;
|
|
||||||
}
|
|
||||||
remoteItems.push(item);
|
|
||||||
}
|
|
||||||
const remoteHtml = remoteItems.join(' / ');
|
|
||||||
|
|
||||||
// IP 및 Storage
|
|
||||||
const ipInfo = [asset.IP주소, asset.IP2].filter(v => v && v !== '').join(' / ');
|
|
||||||
const storageInfo = [asset.SSD1, asset.SSD2].filter(v => v && v !== '').join(' / ');
|
const storageInfo = [asset.SSD1, asset.SSD2].filter(v => v && v !== '').join(' / ');
|
||||||
|
|
||||||
|
// 위치 정보 가공 (서관/동관 -> IDC)
|
||||||
|
let locationHtml = formatInline(asset.위치);
|
||||||
|
if (locationHtml.startsWith('서관') || locationHtml.startsWith('동관')) {
|
||||||
|
locationHtml = `IDC(${locationHtml})`;
|
||||||
|
}
|
||||||
|
|
||||||
tr.innerHTML = `
|
tr.innerHTML = `
|
||||||
<td>${idx+1}</td>
|
<td>${idx+1}</td>
|
||||||
<td class="text-nowrap">${formatInline(asset.법인)}</td>
|
<td class="text-nowrap">${formatInline(asset.법인)}</td>
|
||||||
@@ -147,10 +99,8 @@ function renderHwTable(table: HTMLTableElement, container: HTMLElement, mainCont
|
|||||||
<td class="text-nowrap">${formatInline(asset.storage유형)}</td>
|
<td class="text-nowrap">${formatInline(asset.storage유형)}</td>
|
||||||
<td class="text-nowrap">${formatInline(asset.용도)}</td>
|
<td class="text-nowrap">${formatInline(asset.용도)}</td>
|
||||||
<td class="text-nowrap">${formatInline(asset.상세)}</td>
|
<td class="text-nowrap">${formatInline(asset.상세)}</td>
|
||||||
<td class="text-nowrap">${formatInline(asset.위치)}</td>
|
<td class="text-nowrap">${locationHtml}</td>
|
||||||
<td class="text-nowrap">${managerHtml}</td>
|
<td class="text-nowrap">${managerHtml}</td>
|
||||||
<td class="text-nowrap">${formatInline(ipInfo)}</td>
|
|
||||||
<td class="text-nowrap">${remoteHtml}</td>
|
|
||||||
<td class="text-nowrap">${formatInline(asset.모델명)}</td>
|
<td class="text-nowrap">${formatInline(asset.모델명)}</td>
|
||||||
<td class="text-nowrap">${formatInline(asset.OS)}</td>
|
<td class="text-nowrap">${formatInline(asset.OS)}</td>
|
||||||
<td class="text-nowrap">${formatInline(asset.CPU)}</td>
|
<td class="text-nowrap">${formatInline(asset.CPU)}</td>
|
||||||
@@ -170,111 +120,61 @@ function renderHwTable(table: HTMLTableElement, container: HTMLElement, mainCont
|
|||||||
function renderSwTable(table: HTMLTableElement, container: HTMLElement, mainContent: HTMLElement) {
|
function renderSwTable(table: HTMLTableElement, container: HTMLElement, mainContent: HTMLElement) {
|
||||||
const fullList = state.masterData.sw.filter(a => a.type === state.activeSubTab);
|
const fullList = state.masterData.sw.filter(a => a.type === state.activeSubTab);
|
||||||
const isSub = state.activeSubTab === '구독SW';
|
const isSub = state.activeSubTab === '구독SW';
|
||||||
|
|
||||||
// 0. Container 준비 (조회 바 + 테이블)
|
|
||||||
container.innerHTML = '';
|
container.innerHTML = '';
|
||||||
|
|
||||||
// 1. 조회 바 (Filter Bar) 생성
|
|
||||||
const filterBar = document.createElement('div');
|
const filterBar = document.createElement('div');
|
||||||
filterBar.className = 'search-bar';
|
filterBar.className = 'search-bar';
|
||||||
filterBar.innerHTML = `
|
filterBar.innerHTML = `<div class="search-item flex-1"><label>통합 검색 (제품명/부서)</label><input type="text" id="filter-keyword" placeholder="검색어를 입력하세요..." autocomplete="off"></div><div class="search-item"><label>분야</label><select id="filter-field"><option value="">전체 분야</option><option value="업무공통">업무공통</option><option value="개발S/W">개발S/W</option><option value="디자인">디자인</option><option value="설계S/W">설계S/W</option></select></div><div class="search-item"><label>법인</label><select id="filter-corp"><option value="">전체 법인</option><option value="한맥">한맥</option><option value="삼안">삼안</option><option value="바론">바론</option></select></div><button id="btn-reset-filters" class="btn btn-outline btn-reset" title="검색 조건 초기화"><i data-lucide="refresh-ccw" style="width:14px; height:14px;"></i> 필터 초기화</button>`;
|
||||||
<div class="search-item flex-1">
|
|
||||||
<label>통합 검색 (제품명/부서)</label>
|
|
||||||
<input type="text" id="filter-keyword" placeholder="검색어를 입력하세요..." autocomplete="off">
|
|
||||||
</div>
|
|
||||||
<div class="search-item">
|
|
||||||
<label>분야</label>
|
|
||||||
<select id="filter-field">
|
|
||||||
<option value="">전체 분야</option>
|
|
||||||
<option value="업무공통">업무공통</option>
|
|
||||||
<option value="개발S/W">개발S/W</option>
|
|
||||||
<option value="디자인">디자인</option>
|
|
||||||
<option value="설계S/W">설계S/W</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="search-item">
|
|
||||||
<label>법인</label>
|
|
||||||
<select id="filter-corp">
|
|
||||||
<option value="">전체 법인</option>
|
|
||||||
<option value="한맥">한맥</option>
|
|
||||||
<option value="삼안">삼안</option>
|
|
||||||
<option value="바론">바론</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<button id="btn-reset-filters" class="btn btn-outline btn-reset" title="검색 조건 초기화">
|
|
||||||
<i data-lucide="refresh-ccw" style="width:14px; height:14px;"></i> 필터 초기화
|
|
||||||
</button>
|
|
||||||
`;
|
|
||||||
container.appendChild(filterBar);
|
container.appendChild(filterBar);
|
||||||
|
|
||||||
// 2. 테이블 기본 구조 생성
|
|
||||||
const tableWrapper = document.createElement('div');
|
const tableWrapper = document.createElement('div');
|
||||||
tableWrapper.className = 'table-container';
|
tableWrapper.className = 'table-container';
|
||||||
table.classList.add('sw-table');
|
table.classList.add('sw-table');
|
||||||
table.innerHTML = `<thead><tr><th style="text-align:center;">No.</th><th style="text-align:center;">분야</th><th style="text-align:center;">법인</th><th style="text-align:center;">부서</th><th style="text-align:center;">제품명</th><th style="text-align:center;">구매일</th>${isSub ? '<th style="text-align:center;">구독일</th>' : ''}<th style="text-align:center;">금액</th><th style="text-align:center;">수량</th><th style="text-align:center;">사용가능</th><th style="text-align:center;">관리</th></tr></thead><tbody id="dynamic-tbody"></tbody>`;
|
table.innerHTML = `<thead><tr><th style="text-align:center;">No.</th><th style="text-align:center;">분야</th><th style="text-align:center;">법인</th><th style="text-align:center;">부서</th><th style="text-align:center;">제품명</th><th style="text-align:center;">구매일</th>${isSub ? '<th style="text-align:center;">구독일</th>' : ''}<th style="text-align:center;">금액</th><th style="text-align:center;">수량</th><th style="text-align:center;">사용가능</th><th style="text-align:center;">관리</th></tr></thead><tbody id="dynamic-tbody"></tbody>`;
|
||||||
|
|
||||||
tableWrapper.appendChild(table);
|
tableWrapper.appendChild(table);
|
||||||
container.appendChild(tableWrapper);
|
container.appendChild(tableWrapper);
|
||||||
mainContent.appendChild(container);
|
mainContent.appendChild(container);
|
||||||
|
|
||||||
const tbody = document.getElementById('dynamic-tbody')!;
|
const tbody = document.getElementById('dynamic-tbody')!;
|
||||||
|
|
||||||
// 3. 필터링 및 테이블 업데이트 로직
|
|
||||||
const updateTable = () => {
|
const updateTable = () => {
|
||||||
const keyword = (document.getElementById('filter-keyword') as HTMLInputElement).value.toLowerCase().trim();
|
const keyword = (document.getElementById('filter-keyword') as HTMLInputElement).value.toLowerCase().trim();
|
||||||
const field = (document.getElementById('filter-field') as HTMLSelectElement).value;
|
const field = (document.getElementById('filter-field') as HTMLSelectElement).value;
|
||||||
const corp = (document.getElementById('filter-corp') as HTMLSelectElement).value;
|
const corp = (document.getElementById('filter-corp') as HTMLSelectElement).value;
|
||||||
|
|
||||||
const filtered = fullList.filter(asset => {
|
const filtered = fullList.filter(asset => {
|
||||||
const matchKeyword = !keyword ||
|
const matchKeyword = !keyword || (asset.제품명 || '').toLowerCase().includes(keyword) || (asset.부서 || '').toLowerCase().includes(keyword);
|
||||||
(asset.제품명 || '').toLowerCase().includes(keyword) ||
|
|
||||||
(asset.부서 || '').toLowerCase().includes(keyword);
|
|
||||||
const matchField = !field || asset.분야 === field;
|
const matchField = !field || asset.분야 === field;
|
||||||
const matchCorp = !corp || asset.법인 === corp;
|
const matchCorp = !corp || asset.법인 === corp;
|
||||||
return matchKeyword && matchField && matchCorp;
|
return matchKeyword && matchField && matchCorp;
|
||||||
});
|
});
|
||||||
|
|
||||||
tbody.innerHTML = '';
|
tbody.innerHTML = '';
|
||||||
if (filtered.length === 0) {
|
if (filtered.length === 0) {
|
||||||
tbody.innerHTML = `<tr><td colspan="${isSub ? 11 : 10}" style="text-align:center; padding: 3rem; color: var(--text-muted);">검색 결과가 없습니다.</td></tr>`;
|
tbody.innerHTML = `<tr><td colspan="${isSub ? 11 : 10}" style="text-align:center; padding: 3rem; color: var(--text-muted);">검색 결과가 없습니다.</td></tr>`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
filtered.forEach((asset, idx) => {
|
filtered.forEach((asset, idx) => {
|
||||||
const assigned = state.masterData.swUsers.filter(u => u.swId === asset.id).length;
|
const assigned = state.masterData.swUsers.filter(u => u.swId === asset.id).length;
|
||||||
const avail = (typeof asset.수량 === 'number' ? asset.수량 : parseInt(asset.수량||'0', 10)) - assigned;
|
const qty = typeof asset.수량 === 'number' ? asset.수량 : parseInt(asset.수량||'0', 10);
|
||||||
|
const avail = qty - assigned;
|
||||||
const tr = document.createElement('tr');
|
const tr = document.createElement('tr');
|
||||||
tr.style.cursor = 'pointer';
|
tr.style.cursor = 'pointer';
|
||||||
tr.innerHTML = `<td>${idx+1}</td><td>${asset.분야||''}</td><td>${asset.법인}</td><td>${asset.부서||''}</td><td>${asset.제품명}</td><td>${asset.구매일||''}</td>${isSub ? `<td>${asset.구독일||''}</td>` : ''}<td>${asset.금액||'0'}</td><td>${asset.수량}</td><td><strong style="color: ${avail > 0 ? 'var(--primary-color)' : 'var(--danger)'}">${avail}</strong></td><td style="display:flex; justify-content:center; align-items:center; gap:0.5rem;"><button type="button" class="btn-icon btn-edit" title="수정" style="color: var(--text-muted);"><i data-lucide="edit-2" style="width:18px; height:18px;"></i></button><button type="button" class="btn-icon btn-users" title="사용자 관리" style="color: var(--primary-color);"><i data-lucide="users" style="width:18px; height:18px;"></i></button></td>`;
|
tr.innerHTML = `<td>${idx+1}</td><td>${asset.분야||''}</td><td>${asset.법인}</td><td>${asset.부서||''}</td><td>${asset.제품명}</td><td>${asset.구매일||''}</td>${isSub ? `<td>${asset.구독일||''}</td>` : ''}<td>${asset.금액||'0'}</td><td>${qty}</td><td><strong style="color: ${avail > 0 ? 'var(--primary-color)' : 'var(--danger)'}">${avail}</strong></td><td style="display:flex; justify-content:center; align-items:center; gap:0.5rem;"><button type="button" class="btn-icon btn-edit" title="수정" style="color: var(--text-muted);"><i data-lucide="edit-2" style="width:18px; height:18px;"></i></button><button type="button" class="btn-icon btn-users" title="사용자 관리" style="color: var(--primary-color);"><i data-lucide="users" style="width:18px; height:18px;"></i></button></td>`;
|
||||||
tr.addEventListener('click', (e) => { if (!(e.target as HTMLElement).closest('button')) openSwModal(asset); });
|
tr.addEventListener('click', (e) => { if (!(e.target as HTMLElement).closest('button')) openSwModal(asset); });
|
||||||
tr.querySelector('.btn-edit')?.addEventListener('click', () => openSwModal(asset));
|
tr.querySelector('.btn-edit')?.addEventListener('click', () => openSwModal(asset));
|
||||||
tr.querySelector('.btn-users')?.addEventListener('click', () => openSwUserModal(asset));
|
tr.querySelector('.btn-users')?.addEventListener('click', () => openSwUserModal(asset));
|
||||||
tbody.appendChild(tr);
|
tbody.appendChild(tr);
|
||||||
});
|
});
|
||||||
|
createIcons({ icons: { Edit2, Users, RefreshCcw } });
|
||||||
// 버튼 내 아이콘 다시 그리기
|
|
||||||
createIcons({
|
|
||||||
icons: { Edit2, Users, RefreshCcw }
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 4. 이벤트 바인딩
|
|
||||||
const keywordInput = document.getElementById('filter-keyword') as HTMLInputElement;
|
const keywordInput = document.getElementById('filter-keyword') as HTMLInputElement;
|
||||||
const fieldSelect = document.getElementById('filter-field') as HTMLSelectElement;
|
const fieldSelect = document.getElementById('filter-field') as HTMLSelectElement;
|
||||||
const corpSelect = document.getElementById('filter-corp') as HTMLSelectElement;
|
const corpSelect = document.getElementById('filter-corp') as HTMLSelectElement;
|
||||||
const resetBtn = document.getElementById('btn-reset-filters') as HTMLButtonElement;
|
const resetBtn = document.getElementById('btn-reset-filters') as HTMLButtonElement;
|
||||||
|
|
||||||
keywordInput.addEventListener('input', updateTable);
|
keywordInput.addEventListener('input', updateTable);
|
||||||
fieldSelect.addEventListener('change', updateTable);
|
fieldSelect.addEventListener('change', updateTable);
|
||||||
corpSelect.addEventListener('change', updateTable);
|
corpSelect.addEventListener('change', updateTable);
|
||||||
|
|
||||||
resetBtn.addEventListener('click', () => {
|
resetBtn.addEventListener('click', () => {
|
||||||
keywordInput.value = '';
|
keywordInput.value = ''; fieldSelect.value = ''; corpSelect.value = '';
|
||||||
fieldSelect.value = '';
|
|
||||||
corpSelect.value = '';
|
|
||||||
updateTable();
|
updateTable();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 초기 실행
|
|
||||||
updateTable();
|
updateTable();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { state } from '../state';
|
import { state } from '../core/state';
|
||||||
import { HardwareAsset, SoftwareAsset } from '../excelHandler';
|
import { HardwareAsset, SoftwareAsset } from '../core/excelHandler';
|
||||||
|
import { openDashboardDetail, openSwDashboardDetail, openSwUsageDetail } from '../components/Modal/DashboardDetailModal';
|
||||||
|
|
||||||
declare var Chart: any;
|
declare var Chart: any;
|
||||||
|
|
||||||
@@ -344,71 +345,3 @@ function isSWExpiring(sw: SoftwareAsset) {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
|
|
||||||
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 avail = (typeof sw.수량 === 'number' ? sw.수량 : parseInt(sw.수량||'0', 10)) - assigned;
|
|
||||||
const tr = document.createElement('tr');
|
|
||||||
tr.innerHTML = `<td>${idx+1}</td><td>${sw.법인}</td><td>${sw.제품명}</td><td>${sw.수량}</td><td>${assigned}</td><td>${avail}</td>`;
|
|
||||||
tbody.appendChild(tr);
|
|
||||||
});
|
|
||||||
modal.classList.remove('hidden');
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user