+
-
구매 및 계약 (Purchase)
-
+
+
+
`;
+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';
+ }
+ }
+}
+
function fillSwFormData(asset: SoftwareAsset) {
setFieldValue('sw-asset-id', asset.id);
setFieldValue('sw-asset-type', asset.type);
setFieldValue('sw-법인', asset.법인);
- setFieldValue('sw-자산번호', asset.자산번호);
+ setFieldValue('sw-자산번호', asset.자산번호 || '');
setFieldValue('sw-제품명', asset.제품명);
setFieldValue('sw-수량', asset.수량);
setFieldValue('sw-금액', asset.금액);
- setFieldValue('sw-구매일', asset.구매일);
- setFieldValue('sw-납품업체', asset.납품업체);
- setFieldValue('sw-비고', asset.비고);
+ setFieldValue('sw-구매일', asset.구매일 || '');
+ setFieldValue('sw-납품업체', asset.납품업체 || '');
+ setFieldValue('sw-비고', asset.비고 || '');
- const type = asset.type;
- 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 === '구독SW') {
- if (keyGroup) keyGroup.style.display = 'none';
- if (typeGroup) typeGroup.style.display = 'flex';
- if (expiryGroup) expiryGroup.style.display = 'flex';
+ if (asset.type === '클라우드') {
+ setFieldValue('sw-플랫폼명', (asset as any).플랫폼명 || '');
+ setFieldValue('sw-부서', (asset as any).부서 || '');
+ setFieldValue('sw-계정명', (asset as any).계정명 || '');
+ setFieldValue('sw-결제수단', (asset as any).결제수단 || '');
+ setFieldValue('sw-연결카드번호', (asset as any).연결카드번호 || '');
+ setFieldValue('sw-결제일', (asset as any).결제일 || '');
+ setFieldValue('sw-당월청구액', (asset as any).당월청구액 || '');
+ } else if (asset.type === '구독SW') {
setFieldValue('sw-라이선스유형', (asset as any).라이선스유형 || '');
setFieldValue('sw-만료일', (asset as any).만료일 || '');
} else {
- if (keyGroup) keyGroup.style.display = 'flex';
- if (typeGroup) typeGroup.style.display = 'none';
- if (expiryGroup) expiryGroup.style.display = 'none';
setFieldValue('sw-라이선스키', (asset as any).라이선스키 || '');
}
renderUserSummary(asset.id);
+ renderSwHistory(asset.id);
}
function renderUserSummary(swId: string) {
@@ -161,7 +260,7 @@ function renderSwHistory(swId: string) {
if (!container) return;
const logs = (state.masterData.logs || []).filter(l => l.assetId === swId);
if (logs.length === 0) {
- container.innerHTML = '
이력이 없습니다.
';
+ container.innerHTML = '
수정 이력이 없습니다.
';
return;
}
container.innerHTML = logs.map(l => `
@@ -173,32 +272,21 @@ function renderSwHistory(swId: string) {
`).join('');
}
-export function openSwModal(asset: SoftwareAsset) {
+export function openSwModal(asset: SoftwareAsset, mode: 'view' | 'add' = 'view') {
currentSwAsset = asset;
const modal = document.getElementById('sw-asset-modal')!;
- 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')!;
- form.reset();
- const isNew = !asset.자산번호;
-
- if (isNew) {
- isEditMode = true;
- form.classList.remove('is-view-mode');
- form.classList.add('is-edit-mode');
- saveBtn.textContent = '저장';
- revertBtn.classList.add('hidden');
- } else {
- isEditMode = false;
- form.classList.remove('is-edit-mode');
- form.classList.add('is-view-mode');
- saveBtn.textContent = '수정';
- revertBtn.classList.add('hidden');
- }
+ // 수정 잠금 상태 제어
+ setEditLock('sw-asset-form', mode, {
+ saveBtnId: 'btn-save-sw-asset',
+ revertBtnId: 'btn-revert-sw-edit'
+ });
+ isEditMode = (mode === 'add');
+
fillSwFormData(asset);
- renderSwHistory(asset.id);
+ applySwTypeUI(asset.type);
+
modal.classList.remove('hidden');
createIcons({ icons: { X, History, Plus } });
}
@@ -213,26 +301,29 @@ export function initSwModal(onSave: () => void, closeModals: () => void) {
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 closeModalAction = () => { closeModals(); 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'
+ });
isEditMode = false;
- form.classList.replace('is-edit-mode', 'is-view-mode');
- saveBtn.textContent = '수정';
- revertBtn.classList.add('hidden');
if (currentSwAsset) fillSwFormData(currentSwAsset);
});
saveBtn.addEventListener('click', () => {
if (!currentSwAsset) return;
if (!isEditMode) {
+ setEditLock('sw-asset-form', 'edit', {
+ saveBtnId: 'btn-save-sw-asset',
+ revertBtnId: 'btn-revert-sw-edit'
+ });
isEditMode = true;
- form.classList.replace('is-view-mode', 'is-edit-mode');
- saveBtn.textContent = '저장';
- revertBtn.classList.remove('hidden');
return;
}
@@ -250,23 +341,37 @@ export function initSwModal(onSave: () => void, closeModals: () => void) {
type: type
};
- if (type === '구독SW') {
+ if (type === '클라우드') {
+ updated.플랫폼명 = getFieldValue('sw-플랫폼명');
+ updated.부서 = getFieldValue('sw-부서');
+ updated.계정명 = getFieldValue('sw-계정명');
+ updated.결제수단 = getFieldValue('sw-결제수단');
+ updated.연결카드번호 = getFieldValue('sw-연결카드번호');
+ updated.결제일 = getFieldValue('sw-결제일');
+ updated.당월청구액 = getFieldValue('sw-당월청구액');
+ } else if (type === '구독SW') {
updated.라이선스유형 = getFieldValue('sw-라이선스유형');
updated.만료일 = getFieldValue('sw-만료일');
} else {
updated.라이선스키 = getFieldValue('sw-라이선스키');
}
- const targetList = type === '구독SW' ? state.masterData.subSw : state.masterData.permSw;
+ // 데이터 저장 로직 (state 업데이트)
+ let targetList: SoftwareAsset[] = [];
+ if (type === '구독SW') targetList = state.masterData.subSw;
+ else if (type === '영구SW') targetList = state.masterData.permSw;
+ else if (type === '클라우드') targetList = state.masterData.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'
+ });
isEditMode = false;
- form.classList.replace('is-edit-mode', 'is-view-mode');
- saveBtn.textContent = '수정';
- revertBtn.classList.add('hidden');
});
deleteBtn.addEventListener('click', () => {
@@ -274,7 +379,8 @@ export function initSwModal(onSave: () => void, closeModals: () => void) {
if (confirm('삭제하시겠습니까?')) {
const type = currentSwAsset.type;
if (type === '구독SW') state.masterData.subSw = state.masterData.subSw.filter(a => a.id !== currentSwAsset!.id);
- else state.masterData.permSw = state.masterData.permSw.filter(a => a.id !== currentSwAsset!.id);
+ else if (type === '영구SW') state.masterData.permSw = state.masterData.permSw.filter(a => a.id !== currentSwAsset!.id);
+ else if (type === '클라우드') state.masterData.cloud = state.masterData.cloud.filter(a => a.id !== currentSwAsset!.id);
onSave();
closeModalAction();
}
@@ -283,4 +389,36 @@ export function initSwModal(onSave: () => void, closeModals: () => void) {
userUpdateBtn.addEventListener('click', () => {
if (currentSwAsset) openSwUserModal(currentSwAsset);
});
+
+ // 이력 추가 모달 로직
+ const logModal = document.getElementById('sw-log-modal')!;
+ 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) { alert('날짜와 내용을 입력해주세요.'); 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);
+ });
}
+
diff --git a/src/main.ts b/src/main.ts
index f22f246..0953898 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -7,12 +7,11 @@ import { initBaseModal } from './components/Modal/BaseModal';
import { initPcModal } from './components/Modal/PCModal';
import { initHwModal, openHwModal } from './components/Modal/HWModal';
import { initSwModal, openSwModal } from './components/Modal/SWModal';
-import { initCloudModal, openCloudModal } from './components/Modal/CloudModal';
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';
-// --- DB 저장을 위한 세분화된 헬퍼 함수들 (setting 브랜치 기반) ---
+// --- DB 저장을 위한 세분화된 헬퍼 함수들 ---
async function apiBatchSave(url: string, data: any[], label: string) {
try {
const response = await fetch(url, {
@@ -34,6 +33,7 @@ const saveEquipToDB = () => apiBatchSave('http://localhost:3000/api/equip/batch'
const saveMobileToDB = () => apiBatchSave('http://localhost:3000/api/mobile/batch', state.masterData.mobile, '모바일기기');
const saveSubSwToDB = () => apiBatchSave('http://localhost:3000/api/sw/sub/batch', state.masterData.subSw, '구독SW');
const savePermSwToDB = () => apiBatchSave('http://localhost:3000/api/sw/perm/batch', state.masterData.permSw, '영구SW');
+const saveCloudToDB = () => apiBatchSave('http://localhost:3000/api/cloud/batch', state.masterData.cloud, '클라우드');
const saveSwUsersToDB = () => apiBatchSave('http://localhost:3000/api/sw-users/batch', state.masterData.swUsers, 'SW사용자');
// 모든 하드웨어 DB 동기화
@@ -47,6 +47,16 @@ async function saveAllHardwareToDB() {
]);
}
+// 모든 소프트웨어 DB 동기화
+async function saveAllSoftwareToDB() {
+ await Promise.all([
+ saveSubSwToDB(),
+ savePermSwToDB(),
+ saveCloudToDB(),
+ saveSwUsersToDB()
+ ]);
+}
+
// --- App Initialization ---
function initApp() {
console.log('🚀 ITAM Dedicated System Initializing...');
@@ -64,23 +74,20 @@ function initApp() {
}
});
- // 모달 초기화: StorageModal은 HWModal에 통합됨
+ // 모달 초기화
initPcModal(() => { saveAllHardwareToDB(); renderSWTable(mainContent); }, closeAllModals);
initHwModal(() => { saveAllHardwareToDB(); renderSWTable(mainContent); }, closeAllModals);
initSwModal(() => {
- if (state.activeSubTab === '구독SW') saveSubSwToDB();
- else savePermSwToDB();
+ saveAllSoftwareToDB();
renderSWTable(mainContent);
}, closeAllModals);
- initCloudModal(() => {
- // 클라우드 저장 로직 (필요 시 API 추가 구현 가능)
- renderSWTable(mainContent);
+ initSwUserModal(() => {
+ saveSwUsersToDB();
+ renderSWTable(mainContent);
}, closeAllModals);
- initSwUserModal(() => { saveSwUsersToDB(); renderSWTable(mainContent); }, closeAllModals);
-
initDashboardDetailModal();
} catch (e) { console.error('❌ Initialization failed:', e); }
@@ -107,7 +114,7 @@ function initApp() {
state.masterData = data;
await Promise.all([
saveAllHardwareToDB(),
- saveSubSwToDB(), savePermSwToDB(), saveSwUsersToDB()
+ saveAllSoftwareToDB()
]);
renderSWTable(mainContent);
}
@@ -115,16 +122,25 @@ function initApp() {
document.getElementById('btn-add-asset')?.addEventListener('click', () => {
const tab = state.activeSubTab;
- if (['개인PC', '서버', '전산비품', '스토리지', '모바일기기'].includes(tab)) {
+ const cat = state.activeCategory;
+
+ if (cat === 'hw') {
+ // 하드웨어 대시보드 또는 개별 탭에서 추가
+ const defaultType = (tab === '대시보드') ? '' : tab;
openHwModal({
id: Math.random().toString(36).substring(2, 9),
- type: tab,
- 법인: '한맥', 자산코드: '', 명칭: '', 위치: '', 관리자: '', IP주소: '', MACaddress: '', HW사양: '', OS: '', 납품업체: '', 품의서명: ''
+ type: defaultType,
+ 법인: '한맥', 자산코드: '', 명칭: '', 설치위치: '', MACaddress: '', HW사양: '', OS: '', 연락처: '', 담당부서: ''
+ } as any, 'add');
+ } else if (cat === 'sw') {
+ // 소프트웨어 대시보드 또는 개별 탭에서 추가
+ let defaultType = tab;
+ if (tab === '대시보드') defaultType = '구독SW'; // SW는 기본 레이아웃을 위해 하나 지정하되 필드는 빈값
+
+ openSwModal({
+ id: Math.random().toString(36).substring(2, 9),
+ type: defaultType, 제품명: '', 금액: '', 수량: 1, 계정명: '', 납품업체: '', 비고: '', 법인: '한맥'
} as any, 'add');
- } else if (tab === '클라우드') {
- openCloudModal({ type: '클라우드', 플랫폼명: '', 법인: '한맥', 부서: '', 제품명: '', 계정명: '', 결제수단: '', 결제일: '', 연결카드번호: '', 당월청구액: '', 비고: '' } as any);
- } else if (tab === '구독SW' || tab === '영구SW') {
- openSwModal({ type: tab, 제품명: '', 금액: '', 수량: 1, 계정명: '', 납품업체: '', 비고: '', 법인: '한맥' } as any);
}
});
@@ -133,4 +149,4 @@ function initApp() {
});
}
-document.addEventListener('DOMContentLoaded', initApp);
\ No newline at end of file
+document.addEventListener('DOMContentLoaded', initApp);
diff --git a/src/views/List/CloudListView.ts b/src/views/List/CloudListView.ts
index 3af4d1c..8efdc1f 100644
--- a/src/views/List/CloudListView.ts
+++ b/src/views/List/CloudListView.ts
@@ -1,9 +1,10 @@
import { state } from '../../core/state';
-import { openCloudModal } from '../../components/Modal/CloudModal';
+import { openSwModal } from '../../components/Modal/SWModal';
import { createIcons, Cloud, CreditCard, DollarSign } from 'lucide';
export function renderCloudList(container: HTMLElement) {
- const fullList = state.masterData.sw.filter(a => a.type === '클라우드');
+ // DB에서 직접 로드된 전용 배열을 사용하여 데이터 소스를 일원화함
+ const getFullList = () => state.masterData.cloud || [];
const filterBar = document.createElement('div');
filterBar.className = 'search-bar';
@@ -58,7 +59,7 @@ export function renderCloudList(container: HTMLElement) {
const keyword = keywordInput ? keywordInput.value.toLowerCase().trim() : '';
const payment = paymentSelect ? paymentSelect.value : '';
- const filtered = fullList.filter(asset => {
+ const filtered = getFullList().filter(asset => {
const kwMatch = !keyword ||
(asset.제품명 || '').toLowerCase().includes(keyword) ||
(asset.부서 || '').toLowerCase().includes(keyword) ||
@@ -96,7 +97,7 @@ export function renderCloudList(container: HTMLElement) {
${asset.비고||''} |
`;
- tr.addEventListener('click', () => openCloudModal(asset));
+ tr.addEventListener('click', () => openSwModal(asset, 'view'));
tbody.appendChild(tr);
});
createIcons({ icons: { Cloud, CreditCard, DollarSign } });
diff --git a/src/views/List/SwListView.ts b/src/views/List/SwListView.ts
index f45d394..0f7a87e 100644
--- a/src/views/List/SwListView.ts
+++ b/src/views/List/SwListView.ts
@@ -131,8 +131,15 @@ export function renderSwList(container: HTMLElement) {
`;
- tr.addEventListener('click', (e) => { if (!(e.target as HTMLElement).closest('button')) openSwModal(asset); });
- tr.querySelector('.btn-edit')?.addEventListener('click', (e) => { e.stopPropagation(); openSwModal(asset); });
+ tr.addEventListener('click', (e) => {
+ if (!(e.target as HTMLElement).closest('button')) {
+ openSwModal(asset, 'view');
+ }
+ });
+ tr.querySelector('.btn-edit')?.addEventListener('click', (e) => {
+ e.stopPropagation();
+ openSwModal(asset, 'edit');
+ });
tr.querySelector('.btn-users')?.addEventListener('click', (e) => { e.stopPropagation(); openSwUserModal(asset); });
tbody.appendChild(tr);
});