Compare commits
2 Commits
feature/do
...
aab1f91d3d
| Author | SHA1 | Date | |
|---|---|---|---|
| aab1f91d3d | |||
| e77c4854cb |
858
map_config.json
858
map_config.json
File diff suppressed because it is too large
Load Diff
39
server.js
39
server.js
@@ -675,16 +675,41 @@ app.delete('/api/system-users/:id', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/api/maps/save', (req, res) => {
|
app.post('/api/maps/save', async (req, res) => {
|
||||||
|
let connection;
|
||||||
try {
|
try {
|
||||||
const { path, boxes } = req.body;
|
const { path, boxes } = req.body;
|
||||||
if (!path) return res.status(400).json({ error: 'Path is required' });
|
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') || '{}');
|
// 1. Get old config to track movements
|
||||||
config[path] = boxes;
|
let oldConfig = {};
|
||||||
fs.writeFileSync('map_config.json', JSON.stringify(config, null, 2));
|
if (fs.existsSync('map_config.json')) {
|
||||||
res.json({ success: true });
|
oldConfig = JSON.parse(fs.readFileSync('map_config.json', 'utf8') || '{}');
|
||||||
} catch (err) { handleError(res, err, 'SAVE MAPS'); }
|
}
|
||||||
|
const oldBoxes = oldConfig[path] || [];
|
||||||
|
|
||||||
|
// 2. Save new config to file
|
||||||
|
oldConfig[path] = boxes;
|
||||||
|
fs.writeFileSync('map_config.json', JSON.stringify(oldConfig, null, 2));
|
||||||
|
|
||||||
|
// 3. Sync Database Assets (asset_location table)
|
||||||
|
connection = await pool.getConnection();
|
||||||
|
for (const box of boxes) {
|
||||||
|
if (box.asset_id) {
|
||||||
|
console.log(`Syncing asset ${box.asset_id} to new position: [${box.x}, ${box.y}]`);
|
||||||
|
await connection.query(
|
||||||
|
'UPDATE asset_location SET loc_x = ?, loc_y = ? WHERE asset_id = ? AND is_active = 1',
|
||||||
|
[box.x, box.y, box.asset_id]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({ success: true, message: 'Map and Database synced successfully' });
|
||||||
|
} catch (err) {
|
||||||
|
handleError(res, err, 'SAVE MAPS SYNC');
|
||||||
|
} finally {
|
||||||
|
if (connection) connection.release();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 7. File Upload API (Base64)
|
// 7. File Upload API (Base64)
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ export const state: AppState = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
(window as any).__itam_state = state;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 통합 V2 스키마에 맞춘 데이터 로드
|
* 통합 V2 스키마에 맞춘 데이터 로드
|
||||||
*/
|
*/
|
||||||
|
|||||||
20
src/main.ts
20
src/main.ts
@@ -156,7 +156,6 @@ function initRoleSwitcher() {
|
|||||||
if (!checkbox || !userLabel || !adminLabel) return;
|
if (!checkbox || !userLabel || !adminLabel) return;
|
||||||
|
|
||||||
checkbox.addEventListener('change', () => {
|
checkbox.addEventListener('change', () => {
|
||||||
const mainContent = document.getElementById('main-content')!;
|
|
||||||
if (checkbox.checked) {
|
if (checkbox.checked) {
|
||||||
state.currentUserRole = 'admin';
|
state.currentUserRole = 'admin';
|
||||||
userLabel.classList.remove('active');
|
userLabel.classList.remove('active');
|
||||||
@@ -166,14 +165,6 @@ function initRoleSwitcher() {
|
|||||||
// 관리자 모드 전환 시 대시보드로 이동
|
// 관리자 모드 전환 시 대시보드로 이동
|
||||||
state.activeCategory = 'hw';
|
state.activeCategory = 'hw';
|
||||||
state.activeSubTab = '대시보드';
|
state.activeSubTab = '대시보드';
|
||||||
refreshView();
|
|
||||||
renderNavigation((tab) => {
|
|
||||||
if (tab === '대시보드') {
|
|
||||||
renderDashboard(mainContent);
|
|
||||||
} else {
|
|
||||||
renderSWTable(mainContent);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
state.currentUserRole = 'user';
|
state.currentUserRole = 'user';
|
||||||
adminLabel.classList.remove('active');
|
adminLabel.classList.remove('active');
|
||||||
@@ -183,15 +174,10 @@ function initRoleSwitcher() {
|
|||||||
// 실무자 모드 전환 시 서버 목록으로 이동
|
// 실무자 모드 전환 시 서버 목록으로 이동
|
||||||
state.activeCategory = 'hw';
|
state.activeCategory = 'hw';
|
||||||
state.activeSubTab = '서버';
|
state.activeSubTab = '서버';
|
||||||
|
}
|
||||||
|
// 모든 렌더링을 refreshView 하나로 통합하여 규격 유지
|
||||||
|
renderNavigation(() => refreshView());
|
||||||
refreshView();
|
refreshView();
|
||||||
renderNavigation((tab) => {
|
|
||||||
if (tab === '대시보드') {
|
|
||||||
renderDashboard(mainContent);
|
|
||||||
} else {
|
|
||||||
renderSWTable(mainContent);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -88,13 +88,54 @@
|
|||||||
.box-item {
|
.box-item {
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
padding: 6px;
|
padding: 10px 6px;
|
||||||
border-bottom: 1px solid var(--border-color);
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.box-index {
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-inputs {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group label {
|
||||||
|
color: var(--text-muted);
|
||||||
|
width: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 2px 4px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 2px;
|
||||||
|
font-size: 10px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group input:focus {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
.box-item:hover { background: var(--white); }
|
.box-item:hover { background: var(--white); }
|
||||||
.btn-del { cursor: pointer; color: var(--danger); border: none; background: none; font-size: 16px; padding: 0 5px; }
|
.btn-del { cursor: pointer; color: var(--danger); border: none; background: none; font-size: 16px; padding: 0 5px; }
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,10 @@ import { renderHwDashboard } from './Dashboard/HwDashboard';
|
|||||||
import { renderSwDashboard } from './Dashboard/SwDashboard';
|
import { renderSwDashboard } from './Dashboard/SwDashboard';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 대시보드 렌더링 통합 허브
|
* 대시보드 렌더링 통합 허브 (Vercel Style Normalized)
|
||||||
*/
|
*/
|
||||||
export function renderDashboard(mainContent: HTMLElement) {
|
export function renderDashboard(mainContent: HTMLElement) {
|
||||||
if (!mainContent) return;
|
if (!mainContent) return;
|
||||||
mainContent.innerHTML = '';
|
|
||||||
|
|
||||||
// 기존 차트 리소스 해제
|
// 기존 차트 리소스 해제
|
||||||
if (state.activeCharts) {
|
if (state.activeCharts) {
|
||||||
@@ -17,11 +16,21 @@ export function renderDashboard(mainContent: HTMLElement) {
|
|||||||
}
|
}
|
||||||
state.activeCharts = [];
|
state.activeCharts = [];
|
||||||
|
|
||||||
|
mainContent.innerHTML = `
|
||||||
|
<div class="view-content-wrapper">
|
||||||
|
<div id="dashboard-scroll-container" class="table-container" style="padding: 0;">
|
||||||
|
<div id="dashboard-inner-content"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const innerContent = document.getElementById('dashboard-inner-content')!;
|
||||||
|
|
||||||
if (state.activeCategory === 'hw') {
|
if (state.activeCategory === 'hw') {
|
||||||
renderHwDashboard(mainContent);
|
renderHwDashboard(innerContent);
|
||||||
} else if (state.activeCategory === 'sw') {
|
} else if (state.activeCategory === 'sw') {
|
||||||
renderSwDashboard(mainContent);
|
renderSwDashboard(innerContent);
|
||||||
} else {
|
} else {
|
||||||
mainContent.innerHTML = `<div class="dashboard-section-title" style="padding:2rem;">운영 서비스 대시보드는 준비 중입니다.</div>`;
|
innerContent.innerHTML = `<div class="dashboard-section-title" style="padding:2rem;">해당 카테고리의 대시보드는 준비 중입니다.</div>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,16 +25,21 @@ export async function renderLocationView(container: HTMLElement) {
|
|||||||
: [];
|
: [];
|
||||||
const mapPath = locImages[currentPage] || '';
|
const mapPath = locImages[currentPage] || '';
|
||||||
|
|
||||||
// 자산이 등록된 구역만 필터링
|
// 조회 모드: 설정 파일에 정의된 asset_id를 기준으로 자산 데이터 매핑
|
||||||
const allBoxes = mapConfig[mapPath] || [];
|
const allBoxes = mapConfig[mapPath] || [];
|
||||||
const boxes = allBoxes.filter((box: any) =>
|
const boxes = allBoxes.filter((box: any) => box.asset_id != null);
|
||||||
state.masterData.hw.some(a =>
|
|
||||||
a.location === currentLoc &&
|
// 모든 하드웨어 카테고리에서 자산 검색
|
||||||
a.location_detail === currentDetail &&
|
const allHwAssets = [
|
||||||
String(a.loc_x) === String(box.x) &&
|
...state.masterData.pc,
|
||||||
String(a.loc_y) === String(box.y)
|
...state.masterData.server,
|
||||||
)
|
...state.masterData.storage,
|
||||||
);
|
...state.masterData.network,
|
||||||
|
...state.masterData.equipment,
|
||||||
|
...state.masterData.survey,
|
||||||
|
...state.masterData.officeSupplies,
|
||||||
|
...state.masterData.pcParts
|
||||||
|
];
|
||||||
|
|
||||||
container.innerHTML = `
|
container.innerHTML = `
|
||||||
<div class="location-view-wrapper">
|
<div class="location-view-wrapper">
|
||||||
@@ -81,14 +86,17 @@ export async function renderLocationView(container: HTMLElement) {
|
|||||||
<img src="${mapPath}" id="main-map-img" class="map-image">
|
<img src="${mapPath}" id="main-map-img" class="map-image">
|
||||||
<div id="box-overlay" class="map-overlay">
|
<div id="box-overlay" class="map-overlay">
|
||||||
${boxes.map((box: any, idx: number) => {
|
${boxes.map((box: any, idx: number) => {
|
||||||
const name = box.name || `#${idx+1}`;
|
const asset = allHwAssets.find(a => a.id === box.asset_id);
|
||||||
|
const name = asset ? (asset.asset_purpose || asset.asset_code) : (box.name || `#${idx+1}`);
|
||||||
|
// w, h가 없거나 너무 작으면 최소 크기(3%) 보장하여 영역으로 표시
|
||||||
|
const width = Math.max(parseFloat(box.w || '3'), 3);
|
||||||
|
const height = Math.max(parseFloat(box.h || '3'), 3);
|
||||||
return `
|
return `
|
||||||
<div class="location-box-point"
|
<div class="location-box-area"
|
||||||
|
data-asset-id="${box.asset_id}"
|
||||||
data-name="${name}"
|
data-name="${name}"
|
||||||
data-x="${box.x}"
|
style="left:${box.x}%; top:${box.y}%; width:${width}%; height:${height}%;
|
||||||
data-y="${box.y}"
|
border: 2px solid var(--primary-color); background: rgba(30, 81, 73, 0.1); cursor:pointer; pointer-events: auto; position: absolute;">
|
||||||
style="left:${box.x}%; top:${box.y}%; width:${box.w}%; height:${box.h}%;
|
|
||||||
border: 2px solid var(--primary-color); background: rgba(30, 81, 73, 0.1); cursor:pointer; pointer-events: auto;">
|
|
||||||
</div>
|
</div>
|
||||||
`}).join('')}
|
`}).join('')}
|
||||||
</div>
|
</div>
|
||||||
@@ -170,20 +178,15 @@ export async function renderLocationView(container: HTMLElement) {
|
|||||||
chkBox.addEventListener('change', handleToggle);
|
chkBox.addEventListener('change', handleToggle);
|
||||||
}
|
}
|
||||||
|
|
||||||
container.querySelectorAll('.location-box-point').forEach(box => {
|
container.querySelectorAll('.location-box-area').forEach(box => {
|
||||||
box.addEventListener('click', () => {
|
box.addEventListener('click', () => {
|
||||||
const x = box.getAttribute('data-x');
|
const assetId = box.getAttribute('data-asset-id');
|
||||||
const y = box.getAttribute('data-y');
|
if (!assetId) return;
|
||||||
|
|
||||||
const targetAsset = state.masterData.hw.find(a =>
|
const targetAsset = allHwAssets.find(a => a.id === assetId);
|
||||||
a.location === currentLoc &&
|
|
||||||
a.location_detail === currentDetail &&
|
|
||||||
String(a.loc_x) === String(x) &&
|
|
||||||
String(a.loc_y) === String(y)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (targetAsset) renderAssetDetail(targetAsset);
|
if (targetAsset) renderAssetDetail(targetAsset);
|
||||||
container.querySelectorAll('.location-box-point').forEach(b => (b as HTMLElement).style.background = 'rgba(30, 81, 73, 0.1)');
|
container.querySelectorAll('.location-box-area').forEach(b => (b as HTMLElement).style.background = 'rgba(30, 81, 73, 0.1)');
|
||||||
(box as HTMLElement).style.background = 'rgba(30, 81, 73, 0.4)';
|
(box as HTMLElement).style.background = 'rgba(30, 81, 73, 0.4)';
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export class MapEditor {
|
|||||||
private startY: number = 0;
|
private startY: number = 0;
|
||||||
private currentBox: HTMLElement | null = null;
|
private currentBox: HTMLElement | null = null;
|
||||||
private currentPath: string = '';
|
private currentPath: string = '';
|
||||||
|
private assetOptions: {id: string, name: string}[] = [];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.container = document.getElementById('container')!;
|
this.container = document.getElementById('container')!;
|
||||||
@@ -33,11 +34,35 @@ export class MapEditor {
|
|||||||
public async init() {
|
public async init() {
|
||||||
this.renderFileSidebar();
|
this.renderFileSidebar();
|
||||||
await this.loadConfig();
|
await this.loadConfig();
|
||||||
|
await this.loadAssets();
|
||||||
this.bindEvents();
|
this.bindEvents();
|
||||||
this.selectFirstFile();
|
this.selectFirstFile();
|
||||||
createIcons({ icons: { X, Save, Trash2, ChevronLeft, ChevronRight } });
|
createIcons({ icons: { X, Save, Trash2, ChevronLeft, ChevronRight } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async loadAssets() {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`http://${location.hostname}:3000/api/assets/master`);
|
||||||
|
const masterData = await res.json();
|
||||||
|
const allHw = [
|
||||||
|
...(masterData.pc || []),
|
||||||
|
...(masterData.server || []),
|
||||||
|
...(masterData.storage || []),
|
||||||
|
...(masterData.network || []),
|
||||||
|
...(masterData.equipment || []),
|
||||||
|
...(masterData.survey || []),
|
||||||
|
...(masterData.officeSupplies || []),
|
||||||
|
...(masterData.pcParts || [])
|
||||||
|
];
|
||||||
|
this.assetOptions = allHw.map(a => ({
|
||||||
|
id: a.id,
|
||||||
|
name: `[${a.asset_code}] ${a.asset_purpose || a.model_name || a.category}`
|
||||||
|
}));
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to load assets for mapping', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private renderFileSidebar() {
|
private renderFileSidebar() {
|
||||||
let html = '';
|
let html = '';
|
||||||
Object.entries(IMAGE_LOCATIONS).forEach(([bldg, details]) => {
|
Object.entries(IMAGE_LOCATIONS).forEach(([bldg, details]) => {
|
||||||
@@ -137,7 +162,8 @@ export class MapEditor {
|
|||||||
x: (parseFloat(this.currentBox.style.left) / rect.width * 100).toFixed(2),
|
x: (parseFloat(this.currentBox.style.left) / rect.width * 100).toFixed(2),
|
||||||
y: (parseFloat(this.currentBox.style.top) / rect.height * 100).toFixed(2),
|
y: (parseFloat(this.currentBox.style.top) / rect.height * 100).toFixed(2),
|
||||||
w: (width / rect.width * 100).toFixed(2),
|
w: (width / rect.width * 100).toFixed(2),
|
||||||
h: (height / rect.height * 100).toFixed(2)
|
h: (height / rect.height * 100).toFixed(2),
|
||||||
|
asset_id: null
|
||||||
};
|
};
|
||||||
this.boxes.push(boxData);
|
this.boxes.push(boxData);
|
||||||
this.render();
|
this.render();
|
||||||
@@ -210,13 +236,63 @@ export class MapEditor {
|
|||||||
|
|
||||||
this.wrapper.appendChild(div);
|
this.wrapper.appendChild(div);
|
||||||
|
|
||||||
|
// Create asset options dropdown
|
||||||
|
let optionsHtml = '<option value="">-- 자산 매핑 안 됨 --</option>';
|
||||||
|
this.assetOptions.forEach(opt => {
|
||||||
|
const selected = box.asset_id === opt.id ? 'selected' : '';
|
||||||
|
optionsHtml += `<option value="${opt.id}" ${selected}>${opt.name}</option>`;
|
||||||
|
});
|
||||||
|
|
||||||
const item = document.createElement('div');
|
const item = document.createElement('div');
|
||||||
item.className = 'box-item';
|
item.className = 'box-item';
|
||||||
item.innerHTML = `
|
item.innerHTML = `
|
||||||
<span>#${i+1}: [${box.x}, ${box.y}]</span>
|
<div class="box-header">
|
||||||
|
<span class="box-index">#${i+1}</span>
|
||||||
<button class="btn-del" onclick="removeBox(${i})">×</button>
|
<button class="btn-del" onclick="removeBox(${i})">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="box-inputs" style="margin-bottom: 8px;">
|
||||||
|
<select data-index="${i}" data-prop="asset_id" style="width: 100%; padding: 4px;">
|
||||||
|
${optionsHtml}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="box-inputs">
|
||||||
|
<div class="input-group">
|
||||||
|
<label>X</label>
|
||||||
|
<input type="number" step="0.01" value="${box.x}" data-index="${i}" data-prop="x">
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<label>Y</label>
|
||||||
|
<input type="number" step="0.01" value="${box.y}" data-index="${i}" data-prop="y">
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<label>W</label>
|
||||||
|
<input type="number" step="0.01" value="${box.w}" data-index="${i}" data-prop="w">
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<label>H</label>
|
||||||
|
<input type="number" step="0.01" value="${box.h}" data-index="${i}" data-prop="h">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
this.boxListEl.appendChild(item);
|
this.boxListEl.appendChild(item);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add events to new inputs and selects
|
||||||
|
this.boxListEl.querySelectorAll('input, select').forEach(input => {
|
||||||
|
input.addEventListener('change', (e) => {
|
||||||
|
const target = e.target as HTMLInputElement | HTMLSelectElement;
|
||||||
|
const index = parseInt(target.dataset.index!);
|
||||||
|
const prop = target.dataset.prop!;
|
||||||
|
|
||||||
|
if (this.boxes[index]) {
|
||||||
|
if (prop === 'asset_id') {
|
||||||
|
this.boxes[index][prop] = target.value || null;
|
||||||
|
} else {
|
||||||
|
this.boxes[index][prop] = parseFloat(target.value).toFixed(2);
|
||||||
|
this.render(); // Re-render to update the map visual size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,18 +15,22 @@ import { renderGiftList } from './List/GiftListView';
|
|||||||
import { renderFacilityList } from './List/FacilityListView';
|
import { renderFacilityList } from './List/FacilityListView';
|
||||||
import { renderCostList } from './List/CostListView';
|
import { renderCostList } from './List/CostListView';
|
||||||
import { renderUserList } from './List/UserListView';
|
import { renderUserList } from './List/UserListView';
|
||||||
import { createIcons, Plus, X, LayoutDashboard, Monitor, Server, Database, Laptop, CalendarClock, Key, Cpu, Layers, Users, Paperclip, Edit2, RefreshCcw, Settings } from 'lucide';
|
import { createIcons, Plus, X, LayoutDashboard, Monitor, Server, Database, Laptop, CalendarClock, Key, Cpu, Layers, Users, Paperclip, Edit2, RefreshCcw, BookOpen, Settings } from 'lucide';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 자산 목록 테이블 렌더링 통합 허브
|
* 자산 목록 테이블 렌더링 통합 허브 (Vercel Style Normalized)
|
||||||
*/
|
*/
|
||||||
export function renderSWTable(mainContent: HTMLElement) {
|
export function renderSWTable(mainContent: HTMLElement) {
|
||||||
if (!mainContent) return;
|
if (!mainContent) return;
|
||||||
console.log(`📂 Rendering Table for: ${state.activeCategory} / ${state.activeSubTab}`);
|
console.log(`📂 Rendering Table for: ${state.activeCategory} / ${state.activeSubTab}`);
|
||||||
|
|
||||||
mainContent.innerHTML = '';
|
mainContent.innerHTML = `
|
||||||
const container = document.createElement('div');
|
<div class="view-content-wrapper">
|
||||||
container.className = 'view-container';
|
<div id="list-view-container" style="flex: 1; display: flex; flex-direction: column; overflow: hidden;"></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const container = document.getElementById('list-view-container')!;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const tab = state.activeSubTab;
|
const tab = state.activeSubTab;
|
||||||
@@ -69,11 +73,9 @@ export function renderSWTable(mainContent: HTMLElement) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mainContent.appendChild(container);
|
// 전역 아이콘 초기화
|
||||||
|
|
||||||
// 전역 아이콘 초기화 (한 번 더 실행하여 누락 방지)
|
|
||||||
createIcons({
|
createIcons({
|
||||||
icons: { Plus, X, LayoutDashboard, Monitor, Server, Database, Laptop, CalendarClock, Key, Cpu, Layers, Users, Paperclip, Edit2, RefreshCcw, Settings }
|
icons: { Plus, X, LayoutDashboard, Monitor, Server, Database, Laptop, CalendarClock, Key, Cpu, Layers, Users, Paperclip, Edit2, RefreshCcw, BookOpen, Settings }
|
||||||
});
|
});
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('❌ Error rendering table view:', err);
|
console.error('❌ Error rendering table view:', err);
|
||||||
|
|||||||
Reference in New Issue
Block a user