import { state } from '../../core/state'; import { SoftwareAsset } from '../../core/excelHandler'; import { closeModals } from './BaseModal'; import { openSwUserModal } from './SWUserModal'; import { createIcons, History, Plus, X, Save, Edit2, RotateCcw } from 'lucide'; import { CORP_LIST, TYPE_PREFIX_MAP } from './SharedData'; import { generateOptionsHTML, setFieldValue, getFieldValue, setEditLock, createModalFrameHTML, autoFillForm, autoExtractForm } from './ModalUtils'; let currentSwAsset: SoftwareAsset | null = null; let isEditMode = false; const SW_FIELD_MAP: Record = { '법인': '법인', '자산번호': '자산번호', '제품명': '제품명', '수량': '수량', '금액': '금액', '구매일': '구매일', '납품업체': '납품업체', '비고': '비고', '플랫폼명': '플랫폼명', '부서': '부서', '계정명': '계정명', '결제수단': '결제수단', '연결카드번호': '연결카드번호', '결제일': '결제일', '당월청구액': '당월청구액', '라이선스유형': '라이선스유형', '만료일': '만료일', '라이선스키': '라이선스키' }; const SW_FORM_HTML = `
기본 정보 (Identity)
라이선스 및 계약 정보
관리 및 비고

사용자 할당 현황

`; function renderSwHistory(swId: string) { const container = document.getElementById('sw-history-list'); if (!container) return; const logs = (state.masterData.logs || []).filter(l => l.assetId === swId).sort((a,b) => new Date(b.date).getTime() - new Date(a.date).getTime()); if (logs.length === 0) { container.innerHTML = '
수정 이력이 없습니다.
'; return; } container.innerHTML = logs.map(l => `
${l.date}
${l.user}
${l.details.replace(/\n/g, '
')}
`).join(''); } function renderUserSummary(swId: string) { const container = document.getElementById('sw-assigned-users-summary'); if (!container) return; const userMapping = state.masterData.swUsers.find(u => u.sw_id === swId); if (!userMapping || !userMapping.userData || userMapping.userData.length === 0) { container.innerHTML = '
할당된 사용자가 없습니다.
'; return; } container.innerHTML = userMapping.userData.map(u => `
${u[3] || '이름없음'}${u[1] || '부서없음'}
`).join(''); } function applySwTypeUI(type: string) { const cloudFields = document.querySelectorAll('.cloud-only'); const swFields = document.querySelectorAll('.sw-standard-field'); const userSection = document.getElementById('sw-user-section'); const keyGroup = document.getElementById('sw-license-key-group'); const typeGroup = document.getElementById('sw-license-type-group'); const expiryGroup = document.getElementById('sw-expiry-group'); if (type === '클라우드') { cloudFields.forEach(el => (el as HTMLElement).style.display = 'flex'); swFields.forEach(el => (el as HTMLElement).style.display = 'none'); if (userSection) userSection.style.display = 'none'; } else { cloudFields.forEach(el => (el as HTMLElement).style.display = 'none'); swFields.forEach(el => (el as HTMLElement).style.display = 'flex'); if (userSection) userSection.style.display = 'block'; if (type === '구독SW') { if (keyGroup) keyGroup.style.display = 'none'; if (typeGroup) typeGroup.style.display = 'flex'; if (expiryGroup) expiryGroup.style.display = 'flex'; } else { if (keyGroup) keyGroup.style.display = 'flex'; if (typeGroup) typeGroup.style.display = 'none'; if (expiryGroup) expiryGroup.style.display = 'none'; } } } export function openSwModal(asset: SoftwareAsset, mode: 'view' | 'add' = 'view') { currentSwAsset = asset; const modal = document.getElementById('sw-asset-modal')!; setEditLock('sw-asset-form', mode, { saveBtnId: 'btn-save-sw-asset', revertBtnId: 'btn-revert-sw-edit', generateBtnId: 'btn-generate-sw-code', addLogBtnId: 'btn-add-sw-log' }); isEditMode = (mode === 'add'); autoFillForm('sw', asset, SW_FIELD_MAP); applySwTypeUI(asset.type); renderUserSummary(asset.id); renderSwHistory(asset.id); modal.classList.remove('hidden'); createIcons({ icons: { X, History, Plus } }); } export function initSwModal(onSave: () => void, closeModalsCb: () => void) { if (!document.getElementById('sw-asset-modal')) { const html = createModalFrameHTML('sw', '소프트웨어 상세 정보', SW_FORM_HTML, { historyTitle: '업데이트 내역', addLogBtnId: 'btn-add-sw-log' }); document.body.insertAdjacentHTML('beforeend', html); const logModalHTML = ` `; document.body.insertAdjacentHTML('beforeend', logModalHTML); } const form = document.getElementById('sw-asset-form') as HTMLFormElement; const saveBtn = document.getElementById('btn-save-sw-asset')!; const revertBtn = document.getElementById('btn-revert-sw-edit')!; const deleteBtn = document.getElementById('btn-delete-sw-asset')!; const userUpdateBtn = document.getElementById('btn-open-sw-update')!; const logAddBtn = document.getElementById('btn-add-sw-log')!; const logModal = document.getElementById('sw-log-modal')!; const closeModalAction = () => { closeModalsCb(); isEditMode = false; }; document.getElementById('btn-close-sw-modal')?.addEventListener('click', closeModalAction); document.getElementById('btn-cancel-sw-modal')?.addEventListener('click', closeModalAction); revertBtn.addEventListener('click', () => { setEditLock('sw-asset-form', 'view', { saveBtnId: 'btn-save-sw-asset', revertBtnId: 'btn-revert-sw-edit', generateBtnId: 'btn-generate-sw-code', addLogBtnId: 'btn-add-sw-log' }); isEditMode = false; if (currentSwAsset) openSwModal(currentSwAsset, 'view'); }); document.getElementById('btn-generate-sw-code')?.addEventListener('click', async () => { const typeValue = getFieldValue('sw-asset-type'); const purchaseDate = getFieldValue('sw-구매일'); const typeCode = TYPE_PREFIX_MAP[typeValue] || 'SW'; const dateStr = purchaseDate.replace(/[^0-9]/g, ''); if (dateStr.length < 6) { alert('올바른 구매연월(YYYYMM)을 입력해주세요.'); return; } const prefix = `${typeCode}-${dateStr.substring(0, 6)}-`; try { const res = await fetch(`http://localhost:3000/api/generate-asset-code?prefix=${prefix}`); const data = await res.json(); if (data.nextCode) setFieldValue('sw-자산번호', data.nextCode); } catch (err) { alert('자산번호 생성에 실패했습니다.'); } }); // YYYYMM 입력 제한 로직 (숫자 6자리) document.getElementById('sw-구매일')?.addEventListener('input', (e) => { const target = e.target as HTMLInputElement; target.value = target.value.replace(/[^0-9]/g, '').substring(0, 6); }); saveBtn.addEventListener('click', () => { if (!currentSwAsset) return; if (!isEditMode) { setEditLock('sw-asset-form', 'edit', { saveBtnId: 'btn-save-sw-asset', revertBtnId: 'btn-revert-sw-edit', generateBtnId: 'btn-generate-sw-code', addLogBtnId: 'btn-add-sw-log' }); isEditMode = true; return; } const extracted = autoExtractForm('sw', SW_FIELD_MAP); const updated = { ...currentSwAsset, ...extracted, 수량: parseInt(extracted.수량 || '0') }; let targetList: SoftwareAsset[] = []; if (updated.type === '구독SW') targetList = state.masterData.subSw; else if (updated.type === '영구SW') targetList = state.masterData.permSw; else targetList = (state.masterData as any).cloud || []; const idx = targetList.findIndex(a => a.id === updated.id); if (idx > -1) targetList[idx] = updated; else targetList.push(updated); onSave(); setEditLock('sw-asset-form', 'view', { saveBtnId: 'btn-save-sw-asset', revertBtnId: 'btn-revert-sw-edit', generateBtnId: 'btn-generate-sw-code', addLogBtnId: 'btn-add-sw-log' }); isEditMode = false; }); deleteBtn.addEventListener('click', () => { if (currentSwAsset && confirm('삭제하시겠습니까?')) { const type = currentSwAsset.type; if (type === '구독SW') state.masterData.subSw = state.masterData.subSw.filter(a => a.id !== currentSwAsset!.id); else if (type === '영구SW') state.masterData.permSw = state.masterData.permSw.filter(a => a.id !== currentSwAsset!.id); onSave(); closeModalAction(); } }); userUpdateBtn.addEventListener('click', () => { if (currentSwAsset) openSwUserModal(currentSwAsset); }); logAddBtn.addEventListener('click', () => { logModal.classList.remove('hidden'); (document.getElementById('new-log-date') as HTMLInputElement).value = new Date().toISOString().split('T')[0]; (document.getElementById('new-log-details') as HTMLTextAreaElement).value = ''; }); document.getElementById('btn-close-sw-log')?.addEventListener('click', () => logModal.classList.add('hidden')); document.getElementById('btn-cancel-sw-log')?.addEventListener('click', () => logModal.classList.add('hidden')); document.getElementById('btn-confirm-sw-log')?.addEventListener('click', () => { if (!currentSwAsset) return; const date = (document.getElementById('new-log-date') as HTMLInputElement).value; const details = (document.getElementById('new-log-details') as HTMLTextAreaElement).value; if (!date || !details) return; state.masterData.logs = state.masterData.logs || []; state.masterData.logs.push({ id: Math.random().toString(36).substring(2, 9), assetId: currentSwAsset.id, date, user: '관리자', details }); logModal.classList.add('hidden'); renderSwHistory(currentSwAsset.id); }); }