diff --git a/db_init.js b/db_init.js
index 0aa41e8..86ac2a3 100644
--- a/db_init.js
+++ b/db_init.js
@@ -47,6 +47,7 @@ async function initDB() {
server_id VARCHAR(100),
server_pw VARCHAR(100),
model_name VARCHAR(255),
+ mainboard VARCHAR(255) COMMENT '메인보드',
os VARCHAR(100),
cpu VARCHAR(255),
ram VARCHAR(100),
@@ -73,11 +74,14 @@ async function initDB() {
id VARCHAR(50) PRIMARY KEY,
corp VARCHAR(100) COMMENT '구매법인',
asset_code VARCHAR(100) COMMENT '자산번호',
+ category VARCHAR(100) COMMENT '분야',
+ dept VARCHAR(100) COMMENT '부서',
product_name VARCHAR(255) COMMENT '제품명',
license_type VARCHAR(100) COMMENT '라이선스 유형',
quantity INT COMMENT '수량',
price VARCHAR(100) COMMENT '금액',
purchase_date VARCHAR(50) COMMENT '구매일',
+ start_date VARCHAR(50) COMMENT '시작일',
expiry_date VARCHAR(50) COMMENT '만료일',
vendor VARCHAR(255) COMMENT '납품업체',
remarks TEXT COMMENT '비고',
@@ -91,11 +95,14 @@ async function initDB() {
id VARCHAR(50) PRIMARY KEY,
corp VARCHAR(100) COMMENT '구매법인',
asset_code VARCHAR(100) COMMENT '자산번호',
+ category VARCHAR(100) COMMENT '분야',
+ dept VARCHAR(100) COMMENT '부서',
product_name VARCHAR(255) COMMENT '제품명',
license_key VARCHAR(255) COMMENT '라이선스 키',
quantity INT COMMENT '수량',
price VARCHAR(100) COMMENT '금액',
purchase_date VARCHAR(50) COMMENT '구매일',
+ start_date VARCHAR(50) COMMENT '시작일',
vendor VARCHAR(255) COMMENT '납품업체',
remarks TEXT COMMENT '비고',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
diff --git a/server.js b/server.js
index 7944877..dc0ca86 100644
--- a/server.js
+++ b/server.js
@@ -59,7 +59,7 @@ async function ensureTables() {
current_org VARCHAR(100), prev_org VARCHAR(100), location VARCHAR(255),
manager_main VARCHAR(100), manager_sub VARCHAR(100), ip_address VARCHAR(50),
remote_tool VARCHAR(100), server_id VARCHAR(100), server_pw VARCHAR(100),
- model_name VARCHAR(255), os VARCHAR(100), cpu VARCHAR(100), ram VARCHAR(100), gpu VARCHAR(100),
+ model_name VARCHAR(255), mainboard VARCHAR(255), os VARCHAR(100), cpu VARCHAR(100), ram VARCHAR(100), gpu VARCHAR(100),
storage1 VARCHAR(100), storage2 VARCHAR(100), storage3 VARCHAR(100), monitoring VARCHAR(100), price VARCHAR(100), remarks TEXT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`);
@@ -70,16 +70,18 @@ async function ensureTables() {
await connection.query(`
CREATE TABLE IF NOT EXISTS sw_sub_assets (
- id VARCHAR(50) PRIMARY KEY, corp VARCHAR(100), asset_code VARCHAR(100), product_name VARCHAR(255),
+ id VARCHAR(50) PRIMARY KEY, corp VARCHAR(100), asset_code VARCHAR(100),
+ category VARCHAR(100), dept VARCHAR(100), product_name VARCHAR(255),
license_type VARCHAR(100), quantity INT, price VARCHAR(100), purchase_date VARCHAR(50),
- expiry_date VARCHAR(50), vendor VARCHAR(100), remarks TEXT
+ start_date VARCHAR(50), expiry_date VARCHAR(50), vendor VARCHAR(100), remarks TEXT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`);
await connection.query(`
CREATE TABLE IF NOT EXISTS sw_perm_assets (
- id VARCHAR(50) PRIMARY KEY, corp VARCHAR(100), asset_code VARCHAR(100), product_name VARCHAR(255),
+ id VARCHAR(50) PRIMARY KEY, corp VARCHAR(100), asset_code VARCHAR(100),
+ category VARCHAR(100), dept VARCHAR(100), product_name VARCHAR(255),
license_key VARCHAR(255), quantity INT, price VARCHAR(100), purchase_date VARCHAR(50),
- vendor VARCHAR(100), remarks TEXT
+ start_date VARCHAR(50), vendor VARCHAR(100), remarks TEXT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`);
await connection.query(`
@@ -120,7 +122,7 @@ const hardwareInsertSQL = (table) => `
INSERT INTO ${table} (
id, corp, asset_code, purchase_date, type, detail_purpose, purpose, details,
current_org, prev_org, location, manager_main, manager_sub, ip_address,
- remote_tool, server_id, server_pw, model_name, os, cpu, ram, gpu,
+ remote_tool, server_id, server_pw, model_name, mainboard, os, cpu, ram, gpu,
storage1, storage2, storage3, monitoring, price, remarks
) VALUES ?
`;
@@ -128,7 +130,7 @@ const hardwareInsertSQL = (table) => `
const getHardwareValues = (a) => [
a.id, a.법인||'', a.자산코드||'', a.구매일||'', a.type||'', a.상세용도||'', a.용도||'', a.상세||'',
a.현사용조직||'', a.이전사용조직||'', a.위치||'', a.담당자_정||'', a.담당자_부||'', a.IP주소||'',
- a.원격접속||'', a.서버ID||'', a.서버PW||'', a.모델명||'', a.OS||'', a.CPU||'', a.RAM||'', a.GPU||'',
+ a.원격접속||'', a.서버ID||'', a.서버PW||'', a.모델명||'', a.메인보드||'', a.OS||'', a.CPU||'', a.RAM||'', a.GPU||'',
a.SSD1||'', a.SSD2||'', a.HDD1||'', a.모니터링||'', a.금액||'', a.비고||''
];
@@ -137,7 +139,7 @@ const mapHardware = (r, defaultType) => ({
상세용도: r.detail_purpose, 용도: r.purpose, 상세: r.details, 현사용조직: r.current_org,
이전사용조직: r.prev_org, 위치: r.location, 담당자_정: r.manager_main, 담당자_부: r.manager_sub,
IP주소: r.ip_address, 원격접속: r.remote_tool, 서버ID: r.server_id, 서버PW: r.server_pw,
- 모델명: r.model_name, OS: r.os, CPU: r.cpu, RAM: r.ram, GPU: r.gpu, SSD1: r.storage1,
+ 모델명: r.model_name, 메인보드: r.mainboard, OS: r.os, CPU: r.cpu, RAM: r.ram, GPU: r.gpu, SSD1: r.storage1,
SSD2: r.storage2, HDD1: r.storage3, 모니터링: r.monitoring, 금액: r.price, 비고: r.remarks
});
@@ -238,9 +240,11 @@ app.get('/api/sw/sub', async (req, res) => {
try {
const [rows] = await pool.query('SELECT * FROM sw_sub_assets');
res.json(rows.map(r => ({
- id: r.id, type: '구독SW', 법인: r.corp, 자산번호: r.asset_code, 제품명: r.product_name,
- 라이선스유형: r.license_type, 수량: r.quantity, 금액: r.price, 구매일: r.purchase_date,
- 만료일: r.expiry_date, 납품업체: r.vendor, 비고: r.remarks
+ id: r.id, type: '구독SW', 법인: r.corp, 자산번호: r.asset_code,
+ 분야: r.category, 부서: r.dept, 제품명: r.product_name,
+ 라이선스유형: r.license_type, 수량: r.quantity, 금액: r.price,
+ 구매일: r.purchase_date, 시작일: r.start_date, 만료일: r.expiry_date,
+ 납품업체: r.vendor, 비고: r.remarks
})));
} catch (err) { res.status(500).json({ error: err.message }); }
});
@@ -248,8 +252,11 @@ app.get('/api/sw/sub', async (req, res) => {
app.post('/api/sw/sub/batch', async (req, res) => {
try {
const result = await batchSave('sw_sub_assets', req.body, (assets) => ({
- sql: `INSERT INTO sw_sub_assets (id, corp, asset_code, product_name, license_type, quantity, price, purchase_date, expiry_date, vendor, remarks) VALUES ?`,
- values: assets.map(a => [a.id, a.법인||'', a.자산번호||'', a.제품명||'', a.라이선스유형||'', a.수량||0, a.금액||'', a.구매일||'', a.만료일||'', a.납품업체||'', a.비고||''])
+ sql: `INSERT INTO sw_sub_assets (id, corp, asset_code, category, dept, product_name, license_type, quantity, price, purchase_date, start_date, expiry_date, vendor, remarks) VALUES ?`,
+ values: assets.map(a => [
+ a.id, a.법인||'', a.자산번호||'', a.분야||'', a.부서||'', a.제품명||'',
+ a.라이선스유형||'', a.수량||0, a.금액||'', a.구매일||'', a.시작일||'', a.만료일||'', a.납품업체||'', a.비고||''
+ ])
}));
res.json(result);
} catch (err) { res.status(500).json({ error: err.message }); }
@@ -260,8 +267,10 @@ app.get('/api/sw/perm', async (req, res) => {
try {
const [rows] = await pool.query('SELECT * FROM sw_perm_assets');
res.json(rows.map(r => ({
- id: r.id, type: '영구SW', 법인: r.corp, 자산번호: r.asset_code, 제품명: r.product_name,
- 라이선스키: r.license_key, 수량: r.quantity, 금액: r.price, 구매일: r.purchase_date,
+ id: r.id, type: '영구SW', 법인: r.corp, 자산번호: r.asset_code,
+ 분야: r.category, 부서: r.dept, 제품명: r.product_name,
+ 라이선스키: r.license_key, 수량: r.quantity, 금액: r.price,
+ 구매일: r.purchase_date, 시작일: r.start_date,
납품업체: r.vendor, 비고: r.remarks
})));
} catch (err) { res.status(500).json({ error: err.message }); }
@@ -270,8 +279,11 @@ app.get('/api/sw/perm', async (req, res) => {
app.post('/api/sw/perm/batch', async (req, res) => {
try {
const result = await batchSave('sw_perm_assets', req.body, (assets) => ({
- sql: `INSERT INTO sw_perm_assets (id, corp, asset_code, product_name, license_key, quantity, price, purchase_date, vendor, remarks) VALUES ?`,
- values: assets.map(a => [a.id, a.법인||'', a.자산번호||'', a.제품명||'', a.라이선스키||'', a.수량||0, a.금액||'', a.구매일||'', a.납품업체||'', a.비고||''])
+ sql: `INSERT INTO sw_perm_assets (id, corp, asset_code, category, dept, product_name, license_key, quantity, price, purchase_date, start_date, vendor, remarks) VALUES ?`,
+ values: assets.map(a => [
+ a.id, a.법인||'', a.자산번호||'', a.분야||'', a.부서||'', a.제품명||'',
+ a.라이선스키||'', a.수량||0, a.금액||'', a.구매일||'', a.시작일||'', a.납품업체||'', a.비고||''
+ ])
}));
res.json(result);
} catch (err) { res.status(500).json({ error: err.message }); }
diff --git a/src/components/Modal/CloudModal.ts b/src/components/Modal/CloudModal.ts
deleted file mode 100644
index eba98e1..0000000
--- a/src/components/Modal/CloudModal.ts
+++ /dev/null
@@ -1,317 +0,0 @@
-import { state } from '../../core/state';
-import { SoftwareAsset } from '../../core/excelHandler';
-import { openModal } from './BaseModal';
-import { createIcons, Save, X, Edit2, RotateCcw, History, Plus } from 'lucide';
-
-const CLOUD_MODAL_HTML = `
-
-
-
-`;
-
-export let currentCloudAsset: SoftwareAsset | null = null;
-export let isCloudEditMode = false;
-
-export function setCloudEditMode(edit: boolean) {
- isCloudEditMode = edit;
- const form = document.getElementById('cloud-asset-form') as HTMLFormElement;
- const btnSave = document.getElementById('btn-save-cloud-asset') as HTMLButtonElement;
- const btnRevert = document.getElementById('btn-revert-cloud-edit') as HTMLButtonElement;
- const btnClose = document.getElementById('btn-close-cloud-footer') as HTMLButtonElement;
-
- if (edit) {
- form.classList.add('is-edit-mode');
- form.classList.remove('is-view-mode');
- btnSave.textContent = '저장';
- btnRevert.classList.remove('hidden');
- btnClose.classList.add('hidden');
- Array.from(form.elements).forEach((el: any) => el.disabled = false);
- } else {
- form.classList.add('is-view-mode');
- form.classList.remove('is-edit-mode');
- btnSave.textContent = '수정';
- btnRevert.classList.add('hidden');
- btnClose.classList.remove('hidden');
- Array.from(form.elements).forEach((el: any) => el.disabled = true);
- if (currentCloudAsset) fillCloudFormData(currentCloudAsset);
- }
-}
-
-export function fillCloudFormData(asset: SoftwareAsset) {
- (document.getElementById('cloud-asset-id') as HTMLInputElement).value = asset.id;
- (document.getElementById('cloud-플랫폼명') as HTMLInputElement).value = asset.플랫폼명 || '';
- (document.getElementById('cloud-법인') as HTMLSelectElement).value = asset.법인 || '한맥';
- (document.getElementById('cloud-제품명') as HTMLInputElement).value = asset.제품명 || '';
- (document.getElementById('cloud-부서') as HTMLInputElement).value = asset.부서 || '';
- (document.getElementById('cloud-계정명') as HTMLInputElement).value = asset.계정명 || '';
- (document.getElementById('cloud-결제수단') as HTMLSelectElement).value = asset.결제수단 || '';
- (document.getElementById('cloud-연결카드번호') as HTMLInputElement).value = asset.연결카드번호 || '';
- (document.getElementById('cloud-결제일') as HTMLInputElement).value = asset.결제일 || '';
-
- const billing = asset.당월청구액 ? asset.당월청구액.replace(/[^0-9]/g, '') : '';
- (document.getElementById('cloud-당월청구액') as HTMLInputElement).value = billing ? Number(billing).toLocaleString() : '';
- (document.getElementById('cloud-비고') as HTMLInputElement).value = asset.비고 || '';
-
- document.getElementById('btn-open-cloud-update')!.style.display = 'flex';
- renderCloudHistory(asset.id);
-}
-
-function renderCloudHistory(assetId: string) {
- const historyList = document.getElementById('cloud-history-list');
- if (!historyList) return;
- if (!state.masterData.logs) state.masterData.logs = [];
-
- const logs = state.masterData.logs
- .filter(l => l.assetId === assetId)
- .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
-
- if (logs.length === 0) {
- historyList.innerHTML = '업데이트 내역이 없습니다.
';
- return;
- }
-
- historyList.innerHTML = logs.map(log => `
-
-
${log.date}
-
작업자: ${log.user}
-
${log.details.replace(/\n/g, '
')}
-
- `).join('');
- createIcons({ icons: { X, History, Plus } });
-}
-
-export function initCloudModal(renderContent: () => void, closeModals: () => void) {
- if (!document.getElementById('cloud-asset-modal')) {
- document.body.insertAdjacentHTML('beforeend', CLOUD_MODAL_HTML);
- }
-
- const form = document.getElementById('cloud-asset-form') as HTMLFormElement;
- const btnRevert = document.getElementById('btn-revert-cloud-edit');
- const btnSave = document.getElementById('btn-save-cloud-asset');
- const btnDelete = document.getElementById('btn-delete-cloud-asset');
-
- document.getElementById('btn-close-cloud-modal')?.addEventListener('click', closeModals);
- document.getElementById('btn-close-cloud-footer')?.addEventListener('click', closeModals);
-
- btnRevert?.addEventListener('click', (e) => {
- e.preventDefault();
- setCloudEditMode(false);
- });
-
- btnSave?.addEventListener('click', (e) => {
- e.preventDefault();
- if (!isCloudEditMode) {
- setCloudEditMode(true);
- return;
- }
- if (!form.checkValidity()) { form.reportValidity(); return; }
-
- const id = (document.getElementById('cloud-asset-id') as HTMLInputElement).value;
- const billingRaw = (document.getElementById('cloud-당월청구액') as HTMLInputElement).value.replace(/[^0-9]/g, '');
-
- const newAsset: SoftwareAsset = {
- id: id || Math.random().toString(36).substring(2, 9),
- type: '클라우드',
- 플랫폼명: (document.getElementById('cloud-플랫폼명') as HTMLInputElement).value,
- 법인: (document.getElementById('cloud-법인') as HTMLSelectElement).value,
- 제품명: (document.getElementById('cloud-제품명') as HTMLInputElement).value,
- 부서: (document.getElementById('cloud-부서') as HTMLInputElement).value,
- 계정명: (document.getElementById('cloud-계정명') as HTMLInputElement).value,
- 결제수단: (document.getElementById('cloud-결제수단') as HTMLSelectElement).value,
- 연결카드번호: (document.getElementById('cloud-연결카드번호') as HTMLInputElement).value,
- 결제일: (document.getElementById('cloud-결제일') as HTMLInputElement).value,
- 당월청구액: billingRaw,
- 비고: (document.getElementById('cloud-비고') as HTMLInputElement).value,
- 구매일: '', 금액: '', 수량: 1, 납품업체: ''
- };
-
- if (id) {
- const idx = state.masterData.sw.findIndex(a => a.id === id);
- if (idx !== -1) state.masterData.sw[idx] = newAsset;
- } else {
- state.masterData.sw.push(newAsset);
- const now = new Date();
- state.masterData.logs = state.masterData.logs || [];
- state.masterData.logs.push({
- id: Math.random().toString(36).substring(2, 9),
- assetId: newAsset.id,
- date: `${now.getFullYear()}-${String(now.getMonth()+1).padStart(2,'0')}-${String(now.getDate()).padStart(2,'0')}`,
- user: '관리자',
- details: '신규 등록'
- });
- }
- closeModals();
- renderContent();
- });
-
- btnDelete?.addEventListener('click', (e) => {
- e.preventDefault();
- const id = (document.getElementById('cloud-asset-id') as HTMLInputElement).value;
- if (confirm('클라우드 자산을 삭제하시겠습니까?')) {
- state.masterData.sw = state.masterData.sw.filter(a => a.id !== id);
- closeModals();
- renderContent();
- }
- });
-
- // 클라우드 업데이트 (이력) 모달 로직
- const updateModal = document.getElementById('cloud-update-modal')!;
- document.getElementById('btn-open-cloud-update')?.addEventListener('click', () => {
- updateModal.classList.remove('hidden');
- (document.getElementById('cloud-update-date') as HTMLInputElement).value = new Date().toISOString().split('T')[0];
- (document.getElementById('cloud-update-cost') as HTMLInputElement).value = '';
- (document.getElementById('cloud-update-note') as HTMLInputElement).value = '';
- });
-
- const closeUpdateModal = () => updateModal.classList.add('hidden');
- document.getElementById('btn-close-cloud-update')?.addEventListener('click', closeUpdateModal);
- document.getElementById('btn-cancel-cloud-update')?.addEventListener('click', closeUpdateModal);
-
- document.getElementById('btn-save-cloud-update')?.addEventListener('click', () => {
- const id = (document.getElementById('cloud-asset-id') as HTMLInputElement).value;
- if (!id) return;
-
- const date = (document.getElementById('cloud-update-date') as HTMLInputElement).value;
- const costRaw = (document.getElementById('cloud-update-cost') as HTMLInputElement).value.replace(/[^0-9]/g, '');
- const note = (document.getElementById('cloud-update-note') as HTMLInputElement).value;
-
- if (!date) return alert('업데이트 일자를 입력하세요.');
-
- let details = '결제/상태 업데이트';
- if (costRaw) details += ` (비용: ₩ ${Number(costRaw).toLocaleString()})`;
- if (note) details += `\n메모: ${note}`;
-
- state.masterData.logs = state.masterData.logs || [];
- state.masterData.logs.push({
- id: Math.random().toString(36).substring(2, 9),
- assetId: id,
- date,
- user: '관리자',
- details
- });
-
- // 금액 업데이트 반영
- if (costRaw) {
- const idx = state.masterData.sw.findIndex(a => a.id === id);
- if (idx !== -1) {
- state.masterData.sw[idx].당월청구액 = costRaw;
- (document.getElementById('cloud-당월청구액') as HTMLInputElement).value = Number(costRaw).toLocaleString();
- }
- }
-
- closeUpdateModal();
- renderCloudHistory(id);
- renderContent();
- });
-
- createIcons({ icons: { Save, X, Edit2, RotateCcw, History, Plus } });
-}
-
-export function openCloudModal(asset?: SoftwareAsset) {
- currentCloudAsset = asset || null;
- const form = document.getElementById('cloud-asset-form') as HTMLFormElement;
- const deleteBtn = document.getElementById('btn-delete-cloud-asset')!;
-
- openModal('cloud-asset-modal');
- form.reset();
-
- if (asset) {
- document.getElementById('cloud-modal-title')!.textContent = '클라우드 서비스 상세';
- deleteBtn.style.display = 'block';
- fillCloudFormData(asset);
- setCloudEditMode(false);
- } else {
- document.getElementById('cloud-modal-title')!.textContent = '신규 클라우드 서비스 등록';
- deleteBtn.style.display = 'none';
- (document.getElementById('cloud-asset-id') as HTMLInputElement).value = '';
- document.getElementById('btn-open-cloud-update')!.style.display = 'none';
- renderCloudHistory('');
- setCloudEditMode(true);
- }
- createIcons({ icons: { History, Plus } });
-}
diff --git a/src/components/Modal/HWModal.ts b/src/components/Modal/HWModal.ts
index db4bdd5..a2d0a23 100644
--- a/src/components/Modal/HWModal.ts
+++ b/src/components/Modal/HWModal.ts
@@ -449,11 +449,7 @@ export function initHwModal(onSave: () => void, closeModals: () => void) {
saveHardwareAsset(updated);
onSave();
- setEditLock('hw-asset-form', 'view', {
- saveBtnId: 'btn-save-hw-asset',
- revertBtnId: 'btn-revert-hw-edit'
- });
- isEditMode = false;
+ closeModalAction();
});
deleteBtn.addEventListener('click', () => {
diff --git a/src/components/Modal/PCModal.ts b/src/components/Modal/PCModal.ts
index e492582..dac0861 100644
--- a/src/components/Modal/PCModal.ts
+++ b/src/components/Modal/PCModal.ts
@@ -1,7 +1,7 @@
import { state, saveHardwareAsset, deleteHardwareAsset } from '../../core/state';
import { HardwareAsset } from '../../core/excelHandler';
import { openModal, closeModals } from './BaseModal';
-import { createIcons, History, X, Paperclip } from 'lucide';
+import { createIcons, History, X, Paperclip, Calendar } from 'lucide';
import { CORP_LIST, ORG_LIST, HW_TYPE_LIST, LOCATION_DATA } from './SharedData';
import {
generateOptionsHTML,
@@ -9,7 +9,8 @@ import {
getFieldValue,
parseAndSetLocation,
bindLocationEvents,
- getCombinedLocation
+ getCombinedLocation,
+ applyDateMask
} from './ModalUtils';
let currentAsset: HardwareAsset | null = null;
@@ -67,6 +68,10 @@ const PC_MODAL_HTML = `
+
+
+
+
@@ -105,7 +110,13 @@ const PC_MODAL_HTML = `
@@ -184,7 +195,7 @@ export function openPcModal(asset: HardwareAsset, mode: 'view' | 'add' | 'edit'
modal.classList.remove('hidden');
applyPcTypeSpecificUI();
- createIcons({ icons: { X, History, Paperclip } });
+ createIcons({ icons: { X, History, Paperclip, Calendar } });
}
function applyPcTypeSpecificUI() {
@@ -199,9 +210,10 @@ function applyPcTypeSpecificUI() {
const ssd2Group = document.getElementById('pc-SSD2')?.closest('.form-group') as HTMLElement;
const locationFields = document.querySelectorAll('.pc-location-field');
const etcGroup = document.getElementById('pc-위치-기타-group');
+ const mainboardGroup = document.getElementById('pc-메인보드')?.closest('.form-group') as HTMLElement;
// 초기화 (숨김)
- [modelGroup, osGroup, cpuGroup, ramGroup, ssd1Group, ssd2Group].forEach(g => { if(g) g.style.display = 'none'; });
+ [modelGroup, osGroup, cpuGroup, ramGroup, ssd1Group, ssd2Group, mainboardGroup].forEach(g => { if(g) g.style.display = 'none'; });
locationFields.forEach(el => (el as HTMLElement).style.display = 'none');
if (etcGroup) etcGroup.style.display = 'none';
@@ -214,7 +226,7 @@ function applyPcTypeSpecificUI() {
locationFields.forEach(el => (el as HTMLElement).style.display = 'flex');
}
else if (type === 'PC' || type === '노트북') {
- [modelGroup, osGroup, cpuGroup, ramGroup, ssd1Group, ssd2Group].forEach(g => { if(g) g.style.display = 'flex'; });
+ [modelGroup, mainboardGroup, osGroup, cpuGroup, ramGroup, ssd1Group, ssd2Group].forEach(g => { if(g) g.style.display = 'flex'; });
if (detailPurpose === '서버') {
locationFields.forEach(el => (el as HTMLElement).style.display = 'flex');
}
@@ -243,6 +255,7 @@ function fillFormData(asset: HardwareAsset) {
setFieldValue('pc-현사용조직', asset.현사용조직);
setFieldValue('pc-이전사용조직', asset.이전사용조직);
setFieldValue('pc-상세용도', (asset as any).상세용도);
+ setFieldValue('pc-메인보드', (asset as any).메인보드 || '');
parseAndSetLocation(asset.위치, 'pc-위치-빌딩', 'pc-위치-상세', 'pc-위치-기타-group', 'pc-위치-기타');
@@ -278,6 +291,9 @@ export function initPcModal(onSave: () => void, closeModalsCb: () => void) {
bindLocationEvents('pc-위치-빌딩', 'pc-위치-상세', 'pc-위치-기타-group', 'pc-위치-기타');
+ // 날짜 마스킹 적용
+ applyDateMask(document.getElementById('pc-구매일') as HTMLInputElement);
+
const handleClose = () => { closeModalsCb(); isEditMode = false; };
document.getElementById('btn-close-pc-modal')?.addEventListener('click', handleClose);
document.getElementById('btn-cancel-pc-modal')?.addEventListener('click', handleClose);
@@ -317,6 +333,7 @@ export function initPcModal(onSave: () => void, closeModalsCb: () => void) {
RAM: getFieldValue('pc-RAM'),
SSD1: getFieldValue('pc-SSD1'),
SSD2: getFieldValue('pc-SSD2'),
+ 메인보드: getFieldValue('pc-메인보드'),
구매일: getFieldValue('pc-구매일'),
금액: getFieldValue('pc-금액'),
납품업체: getFieldValue('pc-납품업체'),
@@ -325,10 +342,7 @@ export function initPcModal(onSave: () => void, closeModalsCb: () => void) {
saveHardwareAsset(updated);
onSave();
- isEditMode = false;
- pcForm.classList.replace('is-edit-mode', 'is-view-mode');
- saveBtn.textContent = '수정';
- revertBtn?.classList.add('hidden');
+ handleClose();
});
deleteBtn?.addEventListener('click', () => {
diff --git a/src/components/Modal/SWModal.ts b/src/components/Modal/SWModal.ts
index d4ab284..cedd895 100644
--- a/src/components/Modal/SWModal.ts
+++ b/src/components/Modal/SWModal.ts
@@ -27,10 +27,17 @@ const SW_MODAL_HTML = `
-
@@ -250,10 +250,10 @@ function applySwTypeUI(type: string) {
if (keyGroup) keyGroup.style.display = 'none';
if (typeGroup) typeGroup.style.display = 'flex';
if (expiryGroup) expiryGroup.style.display = 'flex';
- } else {
+ } else if (type === '영구SW') {
if (keyGroup) keyGroup.style.display = 'flex';
if (typeGroup) typeGroup.style.display = 'none';
- if (expiryGroup) expiryGroup.style.display = 'flex';
+ if (expiryGroup) expiryGroup.style.display = 'none'; // 영구는 유지보수 기간이 비고에 들어가는 경우가 많아 만료일 숨김 처리
}
}
}
@@ -263,7 +263,7 @@ function fillSwFormData(asset: SoftwareAsset) {
setFieldValue('sw-asset-type', asset.type);
setFieldValue('sw-분야', asset.분야 || '업무공통');
setFieldValue('sw-법인', asset.법인);
- setFieldValue('sw-자산번호', asset.자산번호 || '');
+
setFieldValue('sw-부서', asset.부서 || '');
setFieldValue('sw-제품명', asset.제품명);
setFieldValue('sw-수량', asset.수량);
@@ -287,25 +287,10 @@ function fillSwFormData(asset: SoftwareAsset) {
setFieldValue('sw-라이선스키', (asset as any).라이선스키 || '');
}
- renderUserSummary(asset.id);
renderSwHistory(asset.id);
}
-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 renderSwHistory(swId: string) {
const container = document.getElementById('sw-history-list');
@@ -354,6 +339,11 @@ export function initSwModal(onSave: () => void, closeModals: () => void) {
const deleteBtn = document.getElementById('btn-delete-sw-asset')!;
const userAssignBtn = document.getElementById('btn-open-sw-user')!;
const btnOpenUpdate = document.getElementById('btn-open-sw-update')!;
+ const typeSelect = document.getElementById('sw-asset-type') as HTMLSelectElement;
+
+ typeSelect?.addEventListener('change', () => {
+ applySwTypeUI(typeSelect.value);
+ });
// 날짜 스마트 마스킹 적용
['sw-구매일', 'sw-시작일', 'sw-만료일', 'sw-update-start', 'sw-update-end'].forEach(id => {
@@ -392,7 +382,7 @@ export function initSwModal(onSave: () => void, closeModals: () => void) {
분야: getFieldValue('sw-분야'),
법인: getFieldValue('sw-법인'),
부서: getFieldValue('sw-부서'),
- 자산번호: getFieldValue('sw-자산번호'),
+
제품명: getFieldValue('sw-제품명'),
수량: parseInt(getFieldValue('sw-수량') || '0'),
금액: getFieldValue('sw-금액'),
@@ -418,21 +408,27 @@ export function initSwModal(onSave: () => void, closeModals: () => void) {
}
// 데이터 저장 로직 (state 업데이트)
+ const oldType = currentSwAsset.type;
+ const newType = updated.type;
+
+ // 유형이 변경된 경우 기존 리스트에서 삭제
+ if (oldType !== newType) {
+ if (oldType === '구독SW') state.masterData.subSw = state.masterData.subSw.filter(a => a.id !== updated.id);
+ else if (oldType === '영구SW') state.masterData.permSw = state.masterData.permSw.filter(a => a.id !== updated.id);
+ else if (oldType === '클라우드') state.masterData.cloud = state.masterData.cloud.filter(a => a.id !== updated.id);
+ }
+
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;
+ if (newType === '구독SW') targetList = state.masterData.subSw;
+ else if (newType === '영구SW') targetList = state.masterData.permSw;
+ else if (newType === '클라우드') 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;
+ closeModalAction();
});
deleteBtn.addEventListener('click', () => {
diff --git a/src/components/Modal/SWUserModal.ts b/src/components/Modal/SWUserModal.ts
index 834a6ef..2dee76b 100644
--- a/src/components/Modal/SWUserModal.ts
+++ b/src/components/Modal/SWUserModal.ts
@@ -27,8 +27,7 @@ const SW_USER_MODAL_HTML = `
- | 구매법인 |
- 부서/팀 |
+ 조직 |
직위 |
이름 |
사용기간 |
@@ -58,11 +57,7 @@ const SW_USER_MODAL_HTML = `
@@ -83,7 +82,7 @@ export function renderSwList(container: HTMLElement) {
tbody.innerHTML = '';
if (filtered.length === 0) {
- tbody.innerHTML = `| 검색 결과가 없습니다. |
`;
+ tbody.innerHTML = `| 검색 결과가 없습니다. |
`;
return;
}
@@ -126,22 +125,11 @@ export function renderSwList(container: HTMLElement) {
${formatPrice(asset.금액)} |
${qty} |
${avail} |
-
-
-
- |
`;
tr.addEventListener('click', (e) => {
- if (!(e.target as HTMLElement).closest('button')) {
- openSwModal(asset, 'view');
- }
+ 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);
});
createIcons({ icons: { Edit2, Users, RefreshCcw } });