feat: enhance map editor, refine location view, and update image assets

- Map Editor: Add box numbering (drawing/placed) and set default file
- Location View: Refine mouse interaction in view mode (readonly)
- Assets: Add MDF room support and update server room directory structure
- Backend: Add map configuration API for real-time saving
This commit is contained in:
2026-06-01 14:00:45 +09:00
parent bf7fb0ffe6
commit 590ddd0e85
19 changed files with 1456 additions and 109 deletions

View File

@@ -2,6 +2,7 @@ import express from 'express';
import mysql from 'mysql2/promise';
import cors from 'cors';
import dotenv from 'dotenv';
import fs from 'fs';
dotenv.config();
@@ -57,22 +58,18 @@ const saveAssetsBatch = async (tableName, items, res, context) => {
const [cols] = await connection.query(`DESCRIBE ${tableName}`);
const validColumns = cols.map(c => c.Field);
// 1. Clear existing (or we could use UPSERT logic, but existing code used DELETE-INSERT pattern)
// 1. Clear existing
await connection.query(`DELETE FROM ${tableName}`);
// 2. Insert new items
for (const item of items) {
const filteredRow = {};
validColumns.forEach(col => {
// Exclude auto-managed timestamps from manual insertion
if (col === 'created_at' || col === 'updated_at') return;
if (item[col] !== undefined) filteredRow[col] = item[col];
});
// Auto-generate ID if missing
if (!filteredRow.id) filteredRow.id = Math.random().toString(36).substring(2, 9);
await connection.query(`INSERT INTO ${tableName} SET ?`, [filteredRow]);
}
@@ -107,16 +104,13 @@ const routeMap = {
'/api/asset/software/assignment': { table: 'asset_software_assignment', context: 'SW ASSIGN' }
};
// 동적 라우팅 생성 (Dynamic Routing)
Object.entries(routeMap).forEach(([route, { table, context }]) => {
app.get(route, (req, res) => fetchAssets(table, res, context));
app.post(`${route}/batch`, (req, res) => saveAssetsBatch(table, req.body, res, `${context} BATCH`));
});
// 4. Legacy/Auxiliary (History & Assignment)
app.get('/api/asset/history', (req, res) => fetchAssets('asset_history', res, 'HISTORY'));
app.post('/api/asset/history/batch', async (req, res) => {
// Custom logic for history as it might not follow the random-id pattern
const connection = await pool.getConnection();
try {
await connection.beginTransaction();
@@ -136,23 +130,16 @@ app.post('/api/asset/history/batch', async (req, res) => {
} catch (err) { await connection.rollback(); handleError(res, err, 'BATCH HISTORY'); } finally { connection.release(); }
});
// 5. Utility
app.get('/api/generate-asset-code', async (req, res) => {
try {
const { prefix } = req.query;
if (!prefix) return res.status(400).json({ error: 'Prefix is required' });
// Search in multiple tables if necessary, but typically prefix-based tables are known
const tables = ['asset_pc', 'asset_server', 'asset_storage', 'asset_network', 'asset_survey', 'asset_pc_parts', 'asset_equipment', 'asset_office_supplies', 'asset_vip'];
let lastCode = '';
for (const table of tables) {
const [rows] = await pool.query(`SELECT asset_code FROM ${table} WHERE asset_code LIKE ? ORDER BY asset_code DESC LIMIT 1`, [`${prefix}%`]);
if (rows.length > 0 && rows[0].asset_code > lastCode) {
lastCode = rows[0].asset_code;
}
if (rows.length > 0 && rows[0].asset_code > lastCode) lastCode = rows[0].asset_code;
}
let nextNum = 1;
if (lastCode) {
const lastNum = parseInt(lastCode.split('-').pop() || '0');
@@ -162,6 +149,38 @@ app.get('/api/generate-asset-code', async (req, res) => {
} catch (err) { handleError(res, err, 'GENERATE CODE'); }
});
// 6. Map Config API (Real-time Save)
app.get('/api/maps', (req, res) => {
try {
if (!fs.existsSync('map_config.json')) {
return res.json({});
}
const data = fs.readFileSync('map_config.json', 'utf8');
res.json(JSON.parse(data || '{}'));
} catch (err) {
handleError(res, err, 'GET MAPS');
}
});
app.post('/api/maps/save', (req, res) => {
try {
const { path, boxes } = req.body;
if (!path) return res.status(400).json({ error: 'Path is required' });
let config = {};
if (fs.existsSync('map_config.json')) {
config = JSON.parse(fs.readFileSync('map_config.json', 'utf8') || '{}');
}
config[path] = boxes;
fs.writeFileSync('map_config.json', JSON.stringify(config, null, 2));
console.log(`💾 [MAP SAVE] Updated config for: ${path}`);
res.json({ success: true });
} catch (err) {
handleError(res, err, 'SAVE MAPS');
}
});
app.listen(3000, '0.0.0.0', () => {
console.log('📡 ITAM BACKEND SERVER RUNNING ON PORT 3000 (Multi-Table Optimized)');
});