feat: 자산 유형별 UI 최적화 및 자산번호 자동 생성 기능 구현
- CPU/GPU/RAM/HDD 등 부품 유형별 필드 라벨 동적 변경 로직 추가\n- 유형별 불필요한 사양 필드 숨김 처리 및 UI 레이아웃 정교화\n- 서버측 자산번호 생성 API (/api/generate-asset-code) 구현\n- 모달 내 자산번호 자동 생성 버튼 이벤트 연동 및 백엔드 동기화
This commit is contained in:
28
server.js
28
server.js
@@ -323,6 +323,34 @@ app.post('/api/sw-users/batch', async (req, res) => {
|
|||||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 자산번호 자동 생성 API
|
||||||
|
app.get('/api/generate-asset-code', async (req, res) => {
|
||||||
|
const { prefix } = req.query;
|
||||||
|
if (!prefix) return res.status(400).json({ error: 'Prefix is required' });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const tables = ['pc_assets', 'server_assets', 'storage_assets', 'equip_assets', 'mobile_assets'];
|
||||||
|
let maxNum = 0;
|
||||||
|
|
||||||
|
for (const table of tables) {
|
||||||
|
const [rows] = await pool.query(
|
||||||
|
`SELECT asset_code FROM ${table} WHERE asset_code LIKE ?`,
|
||||||
|
[`${prefix}%`]
|
||||||
|
);
|
||||||
|
rows.forEach(r => {
|
||||||
|
const numPart = r.asset_code.replace(prefix, '');
|
||||||
|
const num = parseInt(numPart);
|
||||||
|
if (!isNaN(num) && num > maxNum) maxNum = num;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextNum = (maxNum + 1).toString().padStart(3, '0');
|
||||||
|
res.json({ nextCode: `${prefix}${nextNum}` });
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 초기화 및 서버 기동
|
// 초기화 및 서버 기동
|
||||||
ensureTables().then(() => {
|
ensureTables().then(() => {
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
|
|||||||
@@ -249,7 +249,15 @@ function applyTypeSpecificUI(type: string) {
|
|||||||
detailPurpose: document.getElementById('hw-상세용도-group'),
|
detailPurpose: document.getElementById('hw-상세용도-group'),
|
||||||
networkTitle: document.getElementById('hw-network-title'),
|
networkTitle: document.getElementById('hw-network-title'),
|
||||||
specTitle: document.getElementById('hw-spec-title'),
|
specTitle: document.getElementById('hw-spec-title'),
|
||||||
opTitle: document.getElementById('hw-op-title')
|
opTitle: document.getElementById('hw-op-title'),
|
||||||
|
model: document.getElementById('hw-model-group'),
|
||||||
|
os: document.getElementById('hw-os-group'),
|
||||||
|
cpu: document.getElementById('hw-cpu-group'),
|
||||||
|
ram: document.getElementById('hw-ram-group'),
|
||||||
|
ssd1: document.getElementById('hw-ssd1-group'),
|
||||||
|
ssd2: document.getElementById('hw-ssd2-group'),
|
||||||
|
hwSpec: document.getElementById('hw-hwspec-group'),
|
||||||
|
monitoring: document.getElementById('hw-monitoring-group')
|
||||||
};
|
};
|
||||||
|
|
||||||
const serverOnly = document.querySelectorAll('.server-only');
|
const serverOnly = document.querySelectorAll('.server-only');
|
||||||
@@ -257,42 +265,83 @@ function applyTypeSpecificUI(type: string) {
|
|||||||
const opOnly = document.querySelectorAll('.op-only');
|
const opOnly = document.querySelectorAll('.op-only');
|
||||||
const standardLoc = document.querySelectorAll('.loc-standard');
|
const standardLoc = document.querySelectorAll('.loc-standard');
|
||||||
|
|
||||||
// 1. 초기화
|
// 1. 초기화 (모두 숨김 및 라벨 원복)
|
||||||
serverOnly.forEach(el => (el as HTMLElement).style.display = 'none');
|
serverOnly.forEach(el => (el as HTMLElement).style.display = 'none');
|
||||||
nonServer.forEach(el => (el as HTMLElement).style.display = 'none');
|
nonServer.forEach(el => (el as HTMLElement).style.display = 'none');
|
||||||
opOnly.forEach(el => (el as HTMLElement).style.display = 'none');
|
opOnly.forEach(el => (el as HTMLElement).style.display = 'none');
|
||||||
standardLoc.forEach(el => (el as HTMLElement).style.display = 'flex');
|
standardLoc.forEach(el => (el as HTMLElement).style.display = 'flex');
|
||||||
Object.values(groups).forEach(g => { if (g) g.style.display = 'none'; });
|
Object.values(groups).forEach(g => { if (g) g.style.display = 'none'; });
|
||||||
|
|
||||||
|
const osLabel = document.querySelector('label[for="hw-OS"]') as HTMLElement;
|
||||||
|
const ramLabel = document.querySelector('label[for="hw-RAM"]') as HTMLElement;
|
||||||
|
const modelLabel = document.querySelector('label[for="hw-모델명"]') as HTMLElement;
|
||||||
|
if (osLabel) osLabel.innerText = '운영체제 (OS)';
|
||||||
|
if (ramLabel) ramLabel.innerText = 'RAM 용량';
|
||||||
|
if (modelLabel) modelLabel.innerText = '모델명';
|
||||||
|
|
||||||
// 2. 분류 판별
|
// 2. 분류 판별
|
||||||
const isMobileGroup = ['모바일', '태블릿', '노트북', '휴대폰'].some(t => upperType.includes(t));
|
const isMobileGroup = ['모바일', '태블릿', '휴대폰'].some(t => upperType.includes(t));
|
||||||
const isEquipGroup = ['CPU', 'RAM', 'HDD', 'GPU'].some(t => upperType.includes(t)) || upperType.includes('비품');
|
const isEquipGroup = ['CPU', 'RAM', 'HDD', 'GPU'].some(t => upperType.includes(t)) || upperType.includes('비품');
|
||||||
const isOpType = isMobileGroup || isEquipGroup;
|
const isOpType = isMobileGroup || isEquipGroup;
|
||||||
const isPcType = upperType === 'PC' || upperType === '개인PC' || upperType === '노트북';
|
const isPcType = upperType === 'PC' || upperType === '개인PC' || upperType === '노트북';
|
||||||
|
|
||||||
// 3. 레이아웃 적용
|
// 3. 레이아웃 적용
|
||||||
|
if (groups.opTitle) groups.opTitle.style.display = 'flex';
|
||||||
|
|
||||||
if (isOpType) {
|
if (isOpType) {
|
||||||
opOnly.forEach(el => (el as HTMLElement).style.display = 'flex');
|
opOnly.forEach(el => (el as HTMLElement).style.display = 'flex');
|
||||||
standardLoc.forEach(el => (el as HTMLElement).style.display = 'none');
|
standardLoc.forEach(el => (el as HTMLElement).style.display = 'none');
|
||||||
if (groups.specTitle) groups.specTitle.style.display = 'flex';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPcType) {
|
if (groups.specTitle) groups.specTitle.style.display = 'flex';
|
||||||
|
if (groups.model) groups.model.style.display = 'flex';
|
||||||
|
|
||||||
|
// 특정 부품 유형에 따른 라벨 및 필드 제어
|
||||||
|
const isCpuGpu = ['CPU', 'GPU'].some(t => upperType.includes(t));
|
||||||
|
const isRamHdd = ['RAM', 'HDD'].some(t => upperType.includes(t));
|
||||||
|
|
||||||
|
if (isCpuGpu) {
|
||||||
|
if (groups.os && osLabel) {
|
||||||
|
osLabel.innerText = '출시연월';
|
||||||
|
groups.os.style.display = 'flex';
|
||||||
|
}
|
||||||
|
} else if (isRamHdd) {
|
||||||
|
if (groups.ram && ramLabel) {
|
||||||
|
ramLabel.innerText = '용량';
|
||||||
|
groups.ram.style.display = 'flex';
|
||||||
|
}
|
||||||
|
// HDD인 경우 모델명 라벨을 S/N으로 변경
|
||||||
|
if (upperType.includes('HDD') && modelLabel) {
|
||||||
|
modelLabel.innerText = 'S/N';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (groups.hwSpec) groups.hwSpec.style.display = 'flex';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (isPcType) {
|
||||||
if (groups.detailPurpose) groups.detailPurpose.style.display = 'flex';
|
if (groups.detailPurpose) groups.detailPurpose.style.display = 'flex';
|
||||||
|
if (groups.specTitle) groups.specTitle.style.display = 'flex';
|
||||||
|
|
||||||
if (detailPurpose === '서버') {
|
if (detailPurpose === '서버') {
|
||||||
serverOnly.forEach(el => (el as HTMLElement).style.display = 'flex');
|
serverOnly.forEach(el => (el as HTMLElement).style.display = 'flex');
|
||||||
|
if (groups.networkTitle) groups.networkTitle.style.display = 'flex';
|
||||||
|
['model', 'os', 'cpu', 'ram', 'ssd1', 'ssd2', 'monitoring'].forEach(k => {
|
||||||
|
if (groups[k]) groups[k]!.style.display = 'flex';
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
nonServer.forEach(el => (el as HTMLElement).style.display = 'flex');
|
nonServer.forEach(el => (el as HTMLElement).style.display = 'flex');
|
||||||
|
['model', 'os', 'cpu', 'ram', 'ssd1', 'ssd2', 'hwSpec'].forEach(k => {
|
||||||
|
if (groups[k]) groups[k]!.style.display = 'flex';
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (groups.specTitle) groups.specTitle.style.display = 'flex';
|
}
|
||||||
if (groups.networkTitle) groups.networkTitle.style.display = detailPurpose === '서버' ? 'flex' : 'none';
|
else if (upperType.includes('서버') || ['스토리지', 'NAS', 'DAS'].includes(upperType)) {
|
||||||
} else if (upperType.includes('서버') || ['스토리지', 'NAS', 'DAS'].includes(upperType)) {
|
|
||||||
serverOnly.forEach(el => (el as HTMLElement).style.display = 'flex');
|
serverOnly.forEach(el => (el as HTMLElement).style.display = 'flex');
|
||||||
if (groups.networkTitle) groups.networkTitle.style.display = 'flex';
|
if (groups.networkTitle) groups.networkTitle.style.display = 'flex';
|
||||||
if (groups.specTitle) groups.specTitle.style.display = 'flex';
|
if (groups.specTitle) groups.specTitle.style.display = 'flex';
|
||||||
|
['model', 'os', 'cpu', 'ram', 'ssd1', 'ssd2', 'monitoring'].forEach(k => {
|
||||||
|
if (groups[k]) groups[k]!.style.display = 'flex';
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (groups.opTitle) groups.opTitle.style.display = 'flex';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function openHwModal(asset: HardwareAsset, mode: 'view' | 'add' = 'view') {
|
export function openHwModal(asset: HardwareAsset, mode: 'view' | 'add' = 'view') {
|
||||||
@@ -377,6 +426,31 @@ export function initHwModal(onSave: () => void, closeModals: () => void) {
|
|||||||
if (currentAsset) fillHwFormData(currentAsset);
|
if (currentAsset) fillHwFormData(currentAsset);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById('btn-generate-hw-code')?.addEventListener('click', async () => {
|
||||||
|
const typeValue = typeSelect.value;
|
||||||
|
const purchaseDate = getFieldValue('hw-구매일');
|
||||||
|
const typeCode = TYPE_PREFIX_MAP[typeValue] || 'ETC';
|
||||||
|
|
||||||
|
// 구매일에서 연월(YYMM) 추출 (예: 2026-04-21 -> 2604)
|
||||||
|
const dateStr = purchaseDate.replace(/[^0-9]/g, '');
|
||||||
|
if (dateStr.length < 4) {
|
||||||
|
alert('올바른 구매일(연월)을 입력해주세요. (예: 2026-04-21)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const prefix = `${typeCode}-${dateStr.substring(2, 6)}-`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`http://localhost:3000/api/generate-asset-code?prefix=${prefix}`);
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.nextCode) {
|
||||||
|
setFieldValue('hw-자산코드', data.nextCode);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('❌ 자산번호 생성 실패:', err);
|
||||||
|
alert('자산번호 생성에 실패했습니다.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
saveBtn.addEventListener('click', () => {
|
saveBtn.addEventListener('click', () => {
|
||||||
if (!currentAsset) return;
|
if (!currentAsset) return;
|
||||||
if (!isEditMode) {
|
if (!isEditMode) {
|
||||||
|
|||||||
Reference in New Issue
Block a user