feat(map): implement robust ID-based asset mapping and fix UI rendering inconsistencies

- Migrated map mapping from fuzzy coordinates to precise asset_id tracking
- Updated MapEditor to allow explicit asset assignment via dropdown
- Fixed LocationView rendering logic to search across all hardware categories
- Standardized map indicators to always render as areas (boxes) with minimum size
- Restored stable CSS max-height for detail modal photos to prevent clipping
- Synced MapEditor saves directly to database via asset_id
This commit is contained in:
2026-06-18 19:49:15 +09:00
parent e77c4854cb
commit aab1f91d3d
5 changed files with 350 additions and 355 deletions

View File

@@ -25,16 +25,21 @@ export async function renderLocationView(container: HTMLElement) {
: [];
const mapPath = locImages[currentPage] || '';
// 조회 모드: 자산이 등록된 구역만 필터링하여 노출
// 조회 모드: 설정 파일에 정의된 asset_id를 기준으로 자산 데이터 매핑
const allBoxes = mapConfig[mapPath] || [];
const boxes = allBoxes.filter((box: any) =>
state.masterData.hw.some(a =>
a.location === currentLoc &&
a.location_detail === currentDetail &&
String(a.loc_x) === String(box.x) &&
String(a.loc_y) === String(box.y)
)
);
const boxes = allBoxes.filter((box: any) => box.asset_id != null);
// 모든 하드웨어 카테고리에서 자산 검색
const allHwAssets = [
...state.masterData.pc,
...state.masterData.server,
...state.masterData.storage,
...state.masterData.network,
...state.masterData.equipment,
...state.masterData.survey,
...state.masterData.officeSupplies,
...state.masterData.pcParts
];
container.innerHTML = `
<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">
<div id="box-overlay" class="map-overlay">
${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 `
<div class="location-box-point"
<div class="location-box-area"
data-asset-id="${box.asset_id}"
data-name="${name}"
data-x="${box.x}"
data-y="${box.y}"
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;">
style="left:${box.x}%; top:${box.y}%; width:${width}%; height:${height}%;
border: 2px solid var(--primary-color); background: rgba(30, 81, 73, 0.1); cursor:pointer; pointer-events: auto; position: absolute;">
</div>
`}).join('')}
</div>
@@ -170,20 +178,15 @@ export async function renderLocationView(container: HTMLElement) {
chkBox.addEventListener('change', handleToggle);
}
container.querySelectorAll('.location-box-point').forEach(box => {
container.querySelectorAll('.location-box-area').forEach(box => {
box.addEventListener('click', () => {
const x = box.getAttribute('data-x');
const y = box.getAttribute('data-y');
const assetId = box.getAttribute('data-asset-id');
if (!assetId) return;
const targetAsset = state.masterData.hw.find(a =>
a.location === currentLoc &&
a.location_detail === currentDetail &&
String(a.loc_x) === String(x) &&
String(a.loc_y) === String(y)
);
const targetAsset = allHwAssets.find(a => a.id === assetId);
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)';
});
});