merge: main 브랜치의 최신 변경 사항 병합 및 충돌 해결

This commit is contained in:
2026-04-17 15:37:57 +09:00
50 changed files with 8265 additions and 1187 deletions

View File

@@ -0,0 +1,241 @@
import { state } from '../../core/state';
import { SoftwareAsset, SWUser } from '../../core/excelHandler';
import { openModal } from './BaseModal';
import { createIcons, Edit2, X, Paperclip } from 'lucide';
let currentSwUserAssetId: string = '';
let tempSwUsers: SWUser[] = [];
const SW_USER_MODAL_HTML = `
<!-- S/W 할당 사용자 목록 모달 -->
<div id="sw-user-modal" class="modal-overlay hidden">
<div class="modal-content" style="max-width: 800px;">
<div class="modal-header">
<h2 id="sw-user-modal-title">S/W 할당 사용자 목록</h2>
<button id="btn-close-sw-user-modal" class="btn-icon" aria-label="닫기"><i data-lucide="x"></i></button>
</div>
<div class="modal-body">
<input type="hidden" id="sw-user-asset-id" />
<div style="text-align: right; margin-bottom: 0.75rem;">
<button type="button" id="btn-open-add-user" class="btn btn-primary btn-sm"><i data-lucide="plus"></i> 사용자 추가</button>
</div>
<div class="table-container">
<table style="width:100%;">
<thead>
<tr>
<th>법인</th>
<th>부서/팀</th>
<th>직위</th>
<th>이름</th>
<th>사용기간</th>
<th>증빙</th>
<th style="text-align:center;">관리</th>
</tr>
</thead>
<tbody id="user-list-body"></tbody>
</table>
</div>
</div>
<div class="modal-footer">
<div></div>
<div class="footer-actions">
<button id="btn-save-sw-user-mapping" class="btn btn-primary">변경사항 저장</button>
<button id="btn-cancel-sw-user-modal" class="btn btn-outline">닫기</button>
</div>
</div>
</div>
</div>
<!-- 사용자 추가/수정 서브 모달 -->
<div id="sw-user-edit-modal" class="modal-overlay hidden" style="z-index: 1100;">
<div class="modal-content" style="max-width: 500px;">
<div class="modal-header">
<h2 id="sw-user-edit-modal-title">사용자 정보</h2>
<button id="btn-close-sw-user-edit" class="btn-icon"><i data-lucide="x"></i></button>
</div>
<div class="modal-body">
<input type="hidden" id="edit-user-idx" />
<div class="grid-form" style="grid-template-columns: 1fr;">
<div class="form-group">
<label>법인</label>
<select id="new-user-법인">
<option value="한맥">한맥</option><option value="삼안">삼안</option><option value="바론">바론</option>
</select>
</div>
<div class="form-group">
<label>부서</label>
<input type="text" id="new-user-부서" />
</div>
<div class="form-group">
<label>팀</label>
<input type="text" id="new-user-팀" />
</div>
<div class="form-group">
<label>직위</label>
<input type="text" id="new-user-직위" />
</div>
<div class="form-group">
<label>이름</label>
<input type="text" id="new-user-이름" required />
</div>
<div class="form-group">
<label>사용기간</label>
<input type="text" id="new-user-사용기간" placeholder="ex) 2024.01 ~ 2024.12" />
</div>
<div class="form-group">
<label>신청서 (증빙파일)</label>
<input type="file" id="new-user-신청서" />
<span id="new-user-신청서명" style="font-size:0.75rem; color:var(--text-muted);"></span>
</div>
</div>
</div>
<div class="modal-footer">
<button id="btn-cancel-sw-user-edit" class="btn btn-outline">취소</button>
<button id="btn-save-edit-user" class="btn btn-primary">확인</button>
</div>
</div>
</div>
`;
export function initSwUserModal(renderContent: () => void, closeModals: () => void) {
if (!document.getElementById('sw-user-modal')) {
document.body.insertAdjacentHTML('beforeend', SW_USER_MODAL_HTML);
}
const btnOpenAddUser = document.getElementById('btn-open-add-user');
const btnSaveEditUser = document.getElementById('btn-save-edit-user');
const btnSaveSwUserMapping = document.getElementById('btn-save-sw-user-mapping');
const btnCancelUserEdit = document.getElementById('btn-cancel-sw-user-edit');
const btnCloseUserEdit = document.getElementById('btn-close-sw-user-edit');
const btnCancelUserModal = document.getElementById('btn-cancel-sw-user-modal');
const btnCloseUserModal = document.getElementById('btn-close-sw-user-modal');
btnOpenAddUser?.addEventListener('click', () => openUserEditModal(-1));
btnSaveEditUser?.addEventListener('click', () => saveUserEdit());
btnSaveSwUserMapping?.addEventListener('click', () => {
state.masterData.swUsers = state.masterData.swUsers.filter(u => u.swId !== currentSwUserAssetId);
state.masterData.swUsers.push(...tempSwUsers);
document.getElementById('sw-user-modal')?.classList.add('hidden');
renderContent();
});
btnCancelUserEdit?.addEventListener('click', () => document.getElementById('sw-user-edit-modal')?.classList.add('hidden'));
btnCloseUserEdit?.addEventListener('click', () => document.getElementById('sw-user-edit-modal')?.classList.add('hidden'));
btnCancelUserModal?.addEventListener('click', () => document.getElementById('sw-user-modal')?.classList.add('hidden'));
btnCloseUserModal?.addEventListener('click', () => document.getElementById('sw-user-modal')?.classList.add('hidden'));
}
function renderUserList() {
const tbody = document.getElementById('user-list-body')!;
tbody.innerHTML = '';
if (tempSwUsers.length === 0) {
tbody.innerHTML = '<tr><td colspan="7" style="padding: 2rem; text-align: center; color: var(--text-muted);">할당된 사용자가 없습니다.</td></tr>';
return;
}
tempSwUsers.forEach((user, idx) => {
const tr = document.createElement('tr');
const deptTeam = [user., user.].filter(Boolean).join(' / ') || '-';
const attachIcon = user. ? `<i data-lucide="paperclip" class="text-primary" style="width:16px; height:16px;" title="${user.}"></i>` : '-';
tr.innerHTML = `
<td>${user.}</td>
<td>${deptTeam}</td>
<td>${user. || '-'}</td>
<td><strong>${user.}</strong></td>
<td style="text-align:center;">${user. || '-'}</td>
<td style="text-align:center;">${attachIcon}</td>
<td style="text-align:center;">
<button type="button" class="btn-icon btn-edit-user" data-idx="${idx}" style="color: var(--primary-color);"><i data-lucide="edit-2" style="width:14px; height:14px;"></i></button>
<button type="button" class="btn-icon btn-remove-user" data-idx="${idx}" style="color: var(--danger);"><i data-lucide="x" style="width:14px; height:14px;"></i></button>
</td>
`;
tbody.appendChild(tr);
});
createIcons({ icons: { Edit2, X, Paperclip } });
tbody.querySelectorAll('.btn-edit-user').forEach(btn => {
btn.addEventListener('click', (e) => {
const idx = parseInt((e.currentTarget as HTMLElement).getAttribute('data-idx')!);
openUserEditModal(idx);
});
});
tbody.querySelectorAll('.btn-remove-user').forEach(btn => {
btn.addEventListener('click', (e) => {
const idx = parseInt((e.currentTarget as HTMLButtonElement).getAttribute('data-idx')!);
tempSwUsers.splice(idx, 1);
renderUserList();
});
});
}
export function openSwUserModal(asset: SoftwareAsset) {
openModal('sw-user-modal');
currentSwUserAssetId = asset.id;
tempSwUsers = state.masterData.swUsers.filter(u => u.swId === asset.id).map(u => ({...u}));
renderUserList();
}
function openUserEditModal(idx: number) {
const editModal = document.getElementById('sw-user-edit-modal')!;
editModal.classList.remove('hidden');
(document.getElementById('edit-user-idx') as HTMLInputElement).value = String(idx);
if (idx === -1) {
document.getElementById('sw-user-edit-modal-title')!.innerText = '새 사용자 추가';
(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 = '';
(document.getElementById('new-user-사용기간') as HTMLInputElement).value = '';
(document.getElementById('new-user-신청서') as HTMLInputElement).value = '';
document.getElementById('new-user-신청서명')!.innerText = '';
} else {
document.getElementById('sw-user-edit-modal-title')!.innerText = '사용자 정보 수정';
const u = tempSwUsers[idx];
(document.getElementById('new-user-법인') as HTMLSelectElement).value = u.;
(document.getElementById('new-user-부서') as HTMLInputElement).value = u.;
(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-신청서명')!.innerText = u. ? `첨부: ${u.}` : '';
}
}
function saveUserEdit() {
const idx = parseInt((document.getElementById('edit-user-idx') as HTMLInputElement).value);
const = (document.getElementById('new-user-이름') as HTMLInputElement).value.trim();
if (!) { alert('이름을 입력해주세요.'); return; }
const fileInput = document.getElementById('new-user-신청서') as HTMLInputElement;
let = '';
if (fileInput.files && fileInput.files.length > 0) {
= fileInput.files[0].name;
} else if (idx !== -1) {
= tempSwUsers[idx].;
}
const userData: SWUser = {
id: idx === -1 ? Math.random().toString(36).substring(2, 9) : tempSwUsers[idx].id,
swId: currentSwUserAssetId,
: (document.getElementById('new-user-법인') as HTMLSelectElement).value,
: (document.getElementById('new-user-부서') as HTMLInputElement).value,
: (document.getElementById('new-user-팀') as HTMLInputElement).value,
: (document.getElementById('new-user-직위') as HTMLInputElement).value,
,
: (document.getElementById('new-user-사용기간') as HTMLInputElement).value,
};
if (idx === -1) tempSwUsers.push(userData);
else tempSwUsers[idx] = userData;
document.getElementById('sw-user-edit-modal')?.classList.add('hidden');
renderUserList();
}