1 Commits

Author SHA1 Message Date
4b765aba2e feat: 자산 유형별 UI 최적화 및 자산번호 자동 생성 기능 구현
- CPU/GPU/RAM/HDD 등 부품 유형별 필드 라벨 동적 변경 로직 추가\n- 유형별 불필요한 사양 필드 숨김 처리 및 UI 레이아웃 정교화\n- 서버측 자산번호 생성 API (/api/generate-asset-code) 구현\n- 모달 내 자산번호 자동 생성 버튼 이벤트 연동 및 백엔드 동기화
2026-04-22 10:11:45 +09:00
2 changed files with 112 additions and 10 deletions

View File

@@ -323,6 +323,34 @@ app.post('/api/sw-users/batch', async (req, res) => {
} 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(() => {
app.listen(PORT, () => {

View File

@@ -249,7 +249,15 @@ function applyTypeSpecificUI(type: string) {
detailPurpose: document.getElementById('hw-상세용도-group'),
networkTitle: document.getElementById('hw-network-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');
@@ -257,42 +265,83 @@ function applyTypeSpecificUI(type: string) {
const opOnly = document.querySelectorAll('.op-only');
const standardLoc = document.querySelectorAll('.loc-standard');
// 1. 초기화
// 1. 초기화 (모두 숨김 및 라벨 원복)
serverOnly.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');
standardLoc.forEach(el => (el as HTMLElement).style.display = 'flex');
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. 분류 판별
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 isOpType = isMobileGroup || isEquipGroup;
const isPcType = upperType === 'PC' || upperType === '개인PC' || upperType === '노트북';
// 3. 레이아웃 적용
if (groups.opTitle) groups.opTitle.style.display = 'flex';
if (isOpType) {
opOnly.forEach(el => (el as HTMLElement).style.display = 'flex');
standardLoc.forEach(el => (el as HTMLElement).style.display = 'none');
if (groups.specTitle) groups.specTitle.style.display = 'flex';
}
if (groups.model) groups.model.style.display = 'flex';
if (isPcType) {
// 특정 부품 유형에 따른 라벨 및 필드 제어
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.specTitle) groups.specTitle.style.display = 'flex';
if (detailPurpose === '서버') {
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 {
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');
if (groups.networkTitle) groups.networkTitle.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') {
@@ -377,6 +426,31 @@ export function initHwModal(onSave: () => void, closeModals: () => void) {
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', () => {
if (!currentAsset) return;
if (!isEditMode) {