From 4b408b06404a576548b4081f5ad96593be3b76eb Mon Sep 17 00:00:00 2001 From: Taehoon Date: Tue, 9 Jun 2026 13:56:05 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20HW=20=EB=AA=A8=EB=8B=AC=20UI=20?= =?UTF-8?q?=EA=B3=A0=EB=8F=84=ED=99=94=20=EB=B0=8F=20=EC=9E=90=EC=82=B0=20?= =?UTF-8?q?=EB=B6=84=EB=A5=98=20=EC=B2=B4=EA=B3=84=20=EA=B0=9C=ED=8E=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server.js | 30 ++ src/components/Modal/HWModal.ts | 452 +++++++++++------------------ src/components/Modal/SharedData.ts | 2 +- src/core/utils.ts | 10 +- src/views/List/ListFactory.ts | 50 +++- 5 files changed, 241 insertions(+), 303 deletions(-) diff --git a/server.js b/server.js index 8f666c9..60f04f7 100644 --- a/server.js +++ b/server.js @@ -9,6 +9,12 @@ dotenv.config(); const app = express(); app.use(cors()); app.use(express.json({ limit: '50mb' })); +app.use('/uploads', express.static('uploads')); // 업로드 파일 정적 서빙 + +// uploads 폴더가 없으면 생성 +if (!fs.existsSync('uploads')) { + fs.mkdirSync('uploads'); +} // MySQL Pool Configuration const pool = mysql.createPool({ @@ -327,6 +333,30 @@ app.post('/api/maps/save', (req, res) => { } catch (err) { handleError(res, err, 'SAVE MAPS'); } }); +// 7. File Upload API (Base64) +app.post('/api/upload', (req, res) => { + try { + const { fileName, fileData } = req.body; + if (!fileName || !fileData) return res.status(400).json({ error: 'FileName and FileData are required' }); + + // base64 데이터에서 실제 바이너리 추출 + const base64Data = fileData.replace(/^data:.*;base64,/, ""); + const buffer = Buffer.from(base64Data, 'base64'); + + // 고유한 파일명 생성 (타임스탬프 결합) + const timestamp = Date.now(); + const safeFileName = `${timestamp}_${fileName.replace(/[^a-zA-Z0-9._-]/g, '_')}`; + const filePath = `uploads/${safeFileName}`; + + fs.writeFileSync(filePath, buffer); + + console.log(`파일 업로드 성공: ${filePath}`); + res.json({ success: true, filePath: `/${filePath}`, fileName: safeFileName }); + } catch (err) { + handleError(res, err, 'FILE UPLOAD'); + } +}); + app.listen(3000, '0.0.0.0', () => { console.log('📡 ITAM BACKEND SERVER RUNNING ON PORT 3000 (V3 Normalized)'); }); diff --git a/src/components/Modal/HWModal.ts b/src/components/Modal/HWModal.ts index b06fd96..29a22f4 100644 --- a/src/components/Modal/HWModal.ts +++ b/src/components/Modal/HWModal.ts @@ -10,7 +10,6 @@ import { } from './ModalUtils'; import { CORP_LIST, LOCATION_DATA, CATEGORY_TYPE_MAP, HW_STATUS_LIST, ORG_LIST, IMAGE_LOCATIONS, TYPE_PREFIX_MAP } from './SharedData'; import { BaseModal } from './BaseModal'; -import { createIcons, X, History, Plus, Save, Paperclip, Calendar, Monitor, Cpu, Network, ShieldCheck } from 'lucide'; class HwAssetModal extends BaseModal { private dynamicMapConfig: Record = {}; @@ -20,12 +19,15 @@ class HwAssetModal extends BaseModal { } protected renderFrameHTML(): string { + // CSS 명세(modal.css)의 input 패딩(0.625rem)과 일치시켜 정렬을 완벽하게 잡는 스타일 + const standardBtnStyle = 'height: auto !important; padding: 0.625rem 1.25rem; font-size: 0.875rem; line-height: 1.2; display: inline-flex; align-items: center; justify-content: center;'; + return `