From 164568843bbed933c677df70f59ff40312632d6b Mon Sep 17 00:00:00 2001 From: Taehoon Date: Thu, 11 Jun 2026 09:47:57 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20LocationView=20=EA=B3=A0=EB=8F=84?= =?UTF-8?q?=ED=99=94=20-=20=EC=A7=80=EB=8F=84=20=ED=81=B4=EB=A6=AD=20?= =?UTF-8?q?=EC=8B=9C=20=EC=82=AC=EC=9D=B4=EB=93=9C=EB=B0=94=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=EC=A0=95=EB=B3=B4=20=ED=91=9C=EC=8B=9C=20=EB=B0=8F?= =?UTF-8?q?=20=EA=B5=AC=EC=97=AD=20=ED=95=84=ED=84=B0=EB=A7=81=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/state.ts | 2 + src/core/utils.ts | 2 +- src/main.ts | 36 +++-- src/styles/dashboard.css | 270 ++++++++++++++++++++++++++++++++++++++ src/views/LocationView.ts | 232 ++++++++++++++++++++++++++++++++ vite.config.ts | 10 ++ 6 files changed, 543 insertions(+), 9 deletions(-) create mode 100644 src/views/LocationView.ts diff --git a/src/core/state.ts b/src/core/state.ts index d7189bb..e5ccb5b 100644 --- a/src/core/state.ts +++ b/src/core/state.ts @@ -36,6 +36,7 @@ export interface MasterAssetData { export interface AppState { activeCategory: 'dashboard' | 'hw' | 'sw' | 'ops' | 'vip' | 'fac' | 'users' | 'etc'; activeSubTab: string; + viewMode: 'location' | 'legacy' | 'list'; masterData: MasterAssetData; activeCharts: any[]; currentUserRole: 'admin' | 'user'; @@ -45,6 +46,7 @@ export interface AppState { export const state: AppState = { activeCategory: 'hw', activeSubTab: '서버', // 대시보드 제거됨에 따라 기본값 변경 + viewMode: 'location', activeCharts: [], currentUserRole: 'user', masterData: { diff --git a/src/core/utils.ts b/src/core/utils.ts index 030b2d1..469fce2 100644 --- a/src/core/utils.ts +++ b/src/core/utils.ts @@ -1,6 +1,6 @@ import { PAGE_DESCRIPTIONS } from './schema'; -export const API_BASE_URL = `http://${location.hostname}:3000`; +export const API_BASE_URL = ''; /** * ITAM 공통 유틸리티 함수 diff --git a/src/main.ts b/src/main.ts index 53f549b..a7e52c3 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,6 +2,7 @@ import { state, loadMasterDataFromDB, saveAsset } from './core/state'; import { renderNavigation } from './components/Navigation'; import { renderDashboard } from './views/DashboardView'; import { renderSWTable } from './views/SW_Table'; +import { renderLocationView } from './views/LocationView'; import { initBaseModal } from './components/Modal/BaseModal'; import { initHwModal, openHwModal } from './components/Modal/HWModal'; import { initSwModal, openSwModal } from './components/Modal/SWModal'; @@ -47,10 +48,33 @@ function refreshView() { const mainContent = document.getElementById('main-content')!; if (!mainContent) return; - if (state.activeSubTab === '대시보드') { - renderDashboard(mainContent); + mainContent.innerHTML = ` +
+
+ + + +
+
+
+ `; + + // 이벤트 바인딩 + mainContent.querySelectorAll('.mode-toggle-btn').forEach(btn => { + btn.addEventListener('click', () => { + const mode = (btn as HTMLElement).getAttribute('data-mode') as any; + state.viewMode = mode; + refreshView(); + }); + }); + + const viewBody = document.getElementById('view-body')!; + if (state.viewMode === 'location') { + renderLocationView(viewBody); + } else if (state.viewMode === 'legacy') { + renderDashboard(viewBody); // 통계/차트 } else { - renderSWTable(mainContent); + renderSWTable(viewBody); // 리스트 형식 } } @@ -74,11 +98,7 @@ function initApp() { try { renderNavigation((tab) => { - if (tab === '대시보드') { - renderDashboard(mainContent); - } else { - renderSWTable(mainContent); - } + refreshView(); }); initHwModal(() => saveAllDataToDB(), closeAllModals); diff --git a/src/styles/dashboard.css b/src/styles/dashboard.css index 44ddd6d..5c1bbaf 100644 --- a/src/styles/dashboard.css +++ b/src/styles/dashboard.css @@ -57,3 +57,273 @@ width: 100% !important; max-height: 280px; } + +/* --- Location View Styles --- */ +.location-layout { + display: grid; + grid-template-columns: 1.2fr 1fr; + gap: 2rem; + height: calc(100vh - 180px); +} + +.map-section, .asset-section { + display: flex; + flex-direction: column; +} + +.section-title { + font-size: 1.125rem; + font-weight: 700; + margin-bottom: 1rem; + color: var(--text-main); + display: flex; + align-items: center; +} + +.map-wrapper { + flex: 1; + background: #f8fafc; + box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.05); +} + +.location-box { + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); + user-select: none; +} + +.location-box:hover { + background: rgba(30, 81, 73, 0.2) !important; + transform: scale(1.02); + z-index: 10; +} + +.location-box:active { + transform: scale(0.98); +} + +.asset-section .table-container { + flex: 1; + overflow-y: auto; +} + +.status-tag { + display: inline-block; + padding: 0.25rem 0.625rem; + border-radius: 9999px; + font-size: 0.75rem; + font-weight: 600; + background: #ecfdf5; + color: #059669; + border: 1px solid #d1fae5; +} + +.view-toggle-btn:hover { + border-color: var(--primary-color) !important; + color: var(--primary-color) !important; +} + +.view-toggle-btn.active:hover { + color: white !important; +} + +/* --- View Toggle Header --- */ +.view-header { + padding: 0.5rem 1.5rem; + background: var(--white); + border-bottom: 1px solid var(--border-color); + display: flex; + align-items: center; + justify-content: flex-start; + gap: 1rem; +} + +.view-toggle-container { + display: flex; + background: #f1f5f9; + padding: 0.25rem; + border-radius: 8px; + gap: 0.25rem; +} + +.mode-toggle-btn { + padding: 0.5rem 1rem; + border: none; + background: transparent; + border-radius: 6px; + font-size: 0.8125rem; + font-weight: 600; + color: var(--text-muted); + cursor: pointer; + transition: all 0.2s ease; +} + +.mode-toggle-btn:hover { + color: var(--text-main); +} + +.mode-toggle-btn.active { + background: var(--white); + color: var(--primary-color); + box-shadow: 0 1px 3px rgba(0,0,0,0.1); +} + +/* --- Enhanced Location View --- */ +.location-view-wrapper { + display: flex; + flex-direction: column; + height: calc(100vh - 120px); +} + +.location-filter-bar { + padding: 1rem 1.5rem; + background: var(--white); + border-bottom: 1px solid var(--border-color); + display: flex; + align-items: center; + gap: 2rem; +} + +.filter-group { + display: flex; + align-items: center; + gap: 0.75rem; +} + +.filter-group label { + font-size: 0.8125rem; + font-weight: 700; + color: var(--text-main); +} + +.filter-group select { + padding: 0.4rem 0.75rem; + border: 1px solid var(--border-color); + border-radius: 6px; + font-size: 0.8125rem; + color: var(--text-main); + background: var(--white); + min-width: 140px; +} + +.map-pagination { + margin-left: auto; + display: flex; + align-items: center; + gap: 1rem; +} + +.page-info { + font-size: 0.75rem; + color: var(--text-muted); + font-weight: 600; +} + +.page-btns button { + padding: 0.3rem 0.75rem; + border: 1px solid var(--border-color); + background: var(--white); + border-radius: 4px; + font-size: 0.75rem; + cursor: pointer; +} + +.page-btns button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.location-main-content { + flex: 1; + display: grid; + grid-template-columns: 1.4fr 1fr; + gap: 1.5rem; + padding: 1.5rem; + overflow: hidden; +} + +.map-container-section { + display: flex; + flex-direction: column; + overflow: auto; +} + +.location-box-point { + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s ease; +} + +.box-label-text { + font-size: 0.65rem; + font-weight: 800; + color: var(--primary-color); + pointer-events: none; + text-shadow: 0 0 2px white; +} + +.asset-list-section { + background: var(--white); + border-radius: 12px; + border: 1px solid var(--border-color); + display: flex; + flex-direction: column; + overflow: hidden; +} + +.asset-list-section .section-header { + padding: 1rem 1.25rem; + border-bottom: 1px solid var(--border-color); + background: #f8fafc; +} + +.asset-list-section h4 { + margin: 0; + font-size: 0.9375rem; + color: var(--text-main); +} + +.mini-table-wrapper { + flex: 1; + overflow-y: auto; +} + +.compact-table { + width: 100%; + border-collapse: collapse; +} + +.compact-table th { + position: sticky; + top: 0; + background: var(--white); + padding: 0.75rem 1rem; + text-align: left; + font-size: 0.75rem; + font-weight: 700; + color: var(--text-muted); + border-bottom: 1px solid var(--border-color); +} + +.compact-table td { + padding: 0.75rem 1rem; + font-size: 0.8125rem; + border-bottom: 1px solid #f1f5f9; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 150px; +} + +.compact-table tr.clickable-row:hover { + background: #f1f5f9; + cursor: pointer; +} + +.empty-state { + padding: 4rem 2rem; + text-align: center; + color: var(--text-muted); + font-size: 0.8125rem; +} diff --git a/src/views/LocationView.ts b/src/views/LocationView.ts new file mode 100644 index 0000000..33d919d --- /dev/null +++ b/src/views/LocationView.ts @@ -0,0 +1,232 @@ +import { state } from '../core/state'; +import { openHwModal } from '../components/Modal/HWModal'; +import { ASSET_SCHEMA } from '../core/schema'; +import { LOCATION_DATA, IMAGE_LOCATIONS } from '../components/Modal/SharedData'; + +/** + * 위치 중심 자산 현황 뷰 (Refined) + */ +export async function renderLocationView(container: HTMLElement) { + if (!container) return; + + // 로컬 상태 (UI 제어용) + let currentLoc = '기술개발센터'; + let currentDetail = '서버실'; + let currentPage = 0; + let mapConfig: any = {}; + + try { + const res = await fetch('/api/maps'); + mapConfig = await res.json(); + } catch (err) { console.error('Failed to load map config', err); } + + const render = () => { + const locImages = (IMAGE_LOCATIONS[currentLoc] && IMAGE_LOCATIONS[currentLoc][currentDetail]) + ? IMAGE_LOCATIONS[currentLoc][currentDetail] + : []; + const mapPath = locImages[currentPage] || ''; + + // 자산이 등록된(좌표가 일치하는) 구역만 필터링하여 표시 + 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) + ) + ); + + container.innerHTML = ` +
+ +
+
+ + +
+
+ + +
+ + ${locImages.length > 1 ? ` +
+ 사진: ${currentPage + 1} / ${locImages.length} +
+ + +
+
+ ` : ''} +
+ +
+ +
+
+ ${mapPath ? ` + +
+ ${boxes.map((box: any, idx: number) => { + const name = box.name || `#${idx+1}`; + return ` +
+
+ `}).join('')} +
+ ` : '
해당 위치의 도면이 등록되지 않았습니다.
'} +
+

* 지도 위의 구역을 클릭하면 자산 상세 정보가 표시됩니다.

+
+ + +
+
+

📍 구역을 선택하세요

+
+
+
지도에서 자산 위치를 클릭하세요.
+
+
+
+
+ `; + + // 이미지 로드 후 오버레이 크기 재조정 (좌표 밀림 방지) + const img = container.querySelector('#main-map-img') as HTMLImageElement; + if (img) { + img.onload = () => { + const overlay = container.querySelector('#box-overlay') as HTMLElement; + if (overlay) { + overlay.style.height = img.offsetHeight + 'px'; + } + }; + } + + // 이벤트 바인딩 + const selMain = container.querySelector('#sel-loc-main') as HTMLSelectElement; + selMain?.addEventListener('change', () => { + currentLoc = selMain.value; + currentDetail = LOCATION_DATA[currentLoc][0]; + currentPage = 0; + render(); + }); + + const selDetail = container.querySelector('#sel-loc-detail') as HTMLSelectElement; + selDetail?.addEventListener('change', () => { + currentDetail = selDetail.value; + currentPage = 0; + render(); + }); + + container.querySelector('#btn-prev-page')?.addEventListener('click', () => { currentPage--; render(); }); + container.querySelector('#btn-next-page')?.addEventListener('click', () => { currentPage++; render(); }); + + container.querySelectorAll('.location-box-point').forEach(box => { + box.addEventListener('click', () => { + const x = box.getAttribute('data-x'); + const y = box.getAttribute('data-y'); + + // 좌표 및 위치 정보를 기반으로 정확한 자산 1개 찾기 + 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) + ); + + if (targetAsset) { + renderAssetDetail(targetAsset); + } + + container.querySelectorAll('.location-box-point').forEach(b => (b as HTMLElement).style.background = 'rgba(30, 81, 81, 0.1)'); + (box as HTMLElement).style.background = 'rgba(30, 81, 73, 0.4)'; + }); + }); + }; + + const renderAssetDetail = (asset: any) => { + const title = container.querySelector('#loc-list-title')!; + const tableContainer = container.querySelector('#loc-asset-table-container')!; + title.innerHTML = ` +
+ + 📍 자산 상세 정보 + +
+ `; + + // 섹션별 렌더링 함수 + const renderSection = (title: string, fields: { label: string; value: any }[]) => ` +
+
${title}
+
+ ${fields.map(f => ` +
${f.label}
+
${f.value || '-'}
+ `).join('')} +
+
+ `; + + // 하드웨어 정보 구성 + const sectionsHTML = [ + renderSection('기본 관리 정보', [ + { label: ASSET_SCHEMA.ASSET_CODE.ui, value: asset.asset_code }, + { label: ASSET_SCHEMA.PURCHASE_CORP.ui, value: asset.purchase_corp }, + { label: ASSET_SCHEMA.CATEGORY.ui, value: asset.category }, + { label: ASSET_SCHEMA.ASSET_TYPE.ui, value: asset.asset_type }, + { label: ASSET_SCHEMA.HW_STATUS.ui, value: asset.hw_status } + ]), + renderSection('시스템 사양', [ + { label: ASSET_SCHEMA.MODEL_NAME.ui, value: asset.model_name }, + { label: ASSET_SCHEMA.OS.ui, value: asset.os }, + { label: ASSET_SCHEMA.CPU.ui, value: asset.cpu }, + { label: ASSET_SCHEMA.RAM.ui, value: asset.ram }, + { label: ASSET_SCHEMA.GPU.ui, value: asset.gpu } + ]), + renderSection('네트워크 정보', [ + { label: ASSET_SCHEMA.IP_ADDR.ui, value: asset.ip_address }, + { label: ASSET_SCHEMA.MAC_ADDR.ui, value: asset.mac_address }, + { label: ASSET_SCHEMA.REMOTE_TOOL.ui, value: asset.remote_tool } + ]), + renderSection('구매 및 기타', [ + { label: ASSET_SCHEMA.PURCHASE_DATE.ui, value: asset.purchase_date }, + { label: ASSET_SCHEMA.PURCHASE_AMOUNT.ui, value: asset.purchase_amount ? `${Number(asset.purchase_amount).toLocaleString()}원` : '-' }, + { label: ASSET_SCHEMA.MEMO.ui, value: asset.memo } + ]) + ].join(''); + + tableContainer.innerHTML = ` +
+ ${sectionsHTML} +
+ `; + + // 뒤로가기 버튼: 목록 대신 초기 상태로 리셋 + container.querySelector('#btn-back-to-list')?.addEventListener('click', () => { + title.textContent = `📍 구역을 선택하세요`; + tableContainer.innerHTML = `
지도에서 자산 위치를 클릭하세요.
`; + }); + + // 수정 버튼 (기존 모달 활용) + container.querySelector('#btn-edit-from-loc')?.addEventListener('click', () => { + openHwModal(asset, 'edit'); + }); + }; + + // showAssets 함수 제거 (목록 표시 불필요) + + + render(); +} diff --git a/vite.config.ts b/vite.config.ts index 3d35e2a..56b1c1c 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -4,5 +4,15 @@ export default defineConfig({ server: { port: 8080, host: true, // Listen on all local IPs + proxy: { + '/api': { + target: 'http://localhost:3000', + changeOrigin: true, + }, + '/uploads': { + target: 'http://localhost:3000', + changeOrigin: true, + } + } }, }); From 207acbdecb9985064012dcb4e47fcefe3b7f3166 Mon Sep 17 00:00:00 2001 From: Taehoon Date: Thu, 11 Jun 2026 10:08:43 +0900 Subject: [PATCH 2/5] =?UTF-8?q?fix:=20LocationView=20=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=95=84=EC=9B=83=20=EC=95=88=EC=A0=95=ED=99=94=20=EB=B0=8F=20?= =?UTF-8?q?UI=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 페이지네이션 버튼을 상세위치 옆으로 이동\n- 지도 이미지 밀림 현상 방지를 위한 정렬 방식 수정 및 동기화 로직 보강\n- 상단 메뉴에서 구버전 현황 버튼 제거 --- src/main.ts | 7 +-- src/views/LocationView.ts | 101 +++++++++++++++++++++----------------- 2 files changed, 58 insertions(+), 50 deletions(-) diff --git a/src/main.ts b/src/main.ts index a7e52c3..a8d3667 100644 --- a/src/main.ts +++ b/src/main.ts @@ -52,7 +52,6 @@ function refreshView() {
-
@@ -60,7 +59,7 @@ function refreshView() { `; // 이벤트 바인딩 - mainContent.querySelectorAll('.mode-toggle-btn').forEach(btn => { + mainContent.querySelectorAll('.mode-toggle-btn').forEach(btn => { btn.addEventListener('click', () => { const mode = (btn as HTMLElement).getAttribute('data-mode') as any; state.viewMode = mode; @@ -71,12 +70,10 @@ function refreshView() { const viewBody = document.getElementById('view-body')!; if (state.viewMode === 'location') { renderLocationView(viewBody); - } else if (state.viewMode === 'legacy') { - renderDashboard(viewBody); // 통계/차트 } else { renderSWTable(viewBody); // 리스트 형식 } -} + } // 통합 저장 및 갱신 async function saveAllDataToDB() { diff --git a/src/views/LocationView.ts b/src/views/LocationView.ts index 33d919d..5bb81ff 100644 --- a/src/views/LocationView.ts +++ b/src/views/LocationView.ts @@ -49,29 +49,32 @@ export async function renderLocationView(container: HTMLElement) {
- -
- - ${locImages.length > 1 ? ` -
- 사진: ${currentPage + 1} / ${locImages.length} -
- - +
+ + + + ${locImages.length > 1 ? ` +
+
+ + +
+ (${currentPage + 1} / ${locImages.length}) +
+ ` : ''}
- ` : ''}
-
- -
-
+
+ +
+
${mapPath ? ` - -
+ +
${boxes.map((box: any, idx: number) => { const name = box.name || `#${idx+1}`; return ` @@ -86,32 +89,48 @@ export async function renderLocationView(container: HTMLElement) {
` : '
해당 위치의 도면이 등록되지 않았습니다.
'}
-

* 지도 위의 구역을 클릭하면 자산 상세 정보가 표시됩니다.

- -
-
-

📍 구역을 선택하세요

+ +
+
+

📍 구역을 선택하세요

-
-
지도에서 자산 위치를 클릭하세요.
+
+
지도에서 자산 위치를 클릭하세요.
+
+

* 지도 위의 구역을 클릭하면 자산 상세 정보가 표시됩니다.

+
`; - // 이미지 로드 후 오버레이 크기 재조정 (좌표 밀림 방지) + // 이미지 로드 및 윈도우 리사이즈 시 오버레이 크기와 위치를 이미지에 정확히 맞춤 + const syncOverlaySize = () => { + const img = container.querySelector('#main-map-img') as HTMLImageElement; + const overlay = container.querySelector('#box-overlay') as HTMLElement; + if (img && overlay && img.complete) { + overlay.style.width = img.clientWidth + 'px'; + overlay.style.height = img.clientHeight + 'px'; + overlay.style.left = img.offsetLeft + 'px'; + overlay.style.top = img.offsetTop + 'px'; + } + }; + const img = container.querySelector('#main-map-img') as HTMLImageElement; if (img) { - img.onload = () => { - const overlay = container.querySelector('#box-overlay') as HTMLElement; - if (overlay) { - overlay.style.height = img.offsetHeight + 'px'; - } - }; + if (img.complete) { + syncOverlaySize(); + setTimeout(syncOverlaySize, 50); // 레이아웃 안정화 대기 + } else { + img.onload = syncOverlaySize; + } } + + window.removeEventListener('resize', syncOverlaySize); + window.addEventListener('resize', syncOverlaySize); // 이벤트 바인딩 const selMain = container.querySelector('#sel-loc-main') as HTMLSelectElement; @@ -137,7 +156,6 @@ export async function renderLocationView(container: HTMLElement) { const x = box.getAttribute('data-x'); const y = box.getAttribute('data-y'); - // 좌표 및 위치 정보를 기반으로 정확한 자산 1개 찾기 const targetAsset = state.masterData.hw.find(a => a.location === currentLoc && a.location_detail === currentDetail && @@ -149,7 +167,7 @@ export async function renderLocationView(container: HTMLElement) { renderAssetDetail(targetAsset); } - container.querySelectorAll('.location-box-point').forEach(b => (b as HTMLElement).style.background = 'rgba(30, 81, 81, 0.1)'); + container.querySelectorAll('.location-box-point').forEach(b => (b as HTMLElement).style.background = 'rgba(30, 81, 73, 0.1)'); (box as HTMLElement).style.background = 'rgba(30, 81, 73, 0.4)'; }); }); @@ -166,20 +184,18 @@ export async function renderLocationView(container: HTMLElement) {
`; - // 섹션별 렌더링 함수 const renderSection = (title: string, fields: { label: string; value: any }[]) => ` -
+
${title}
${fields.map(f => `
${f.label}
-
${f.value || '-'}
+
${f.value || '-'}
`).join('')}
`; - // 하드웨어 정보 구성 const sectionsHTML = [ renderSection('기본 관리 정보', [ { label: ASSET_SCHEMA.ASSET_CODE.ui, value: asset.asset_code }, @@ -208,25 +224,20 @@ export async function renderLocationView(container: HTMLElement) { ].join(''); tableContainer.innerHTML = ` -
+
${sectionsHTML}
`; - // 뒤로가기 버튼: 목록 대신 초기 상태로 리셋 container.querySelector('#btn-back-to-list')?.addEventListener('click', () => { title.textContent = `📍 구역을 선택하세요`; - tableContainer.innerHTML = `
지도에서 자산 위치를 클릭하세요.
`; + tableContainer.innerHTML = `
지도에서 자산 위치를 클릭하세요.
`; }); - // 수정 버튼 (기존 모달 활용) container.querySelector('#btn-edit-from-loc')?.addEventListener('click', () => { openHwModal(asset, 'edit'); }); }; - // showAssets 함수 제거 (목록 표시 불필요) - - render(); } From 95fbd3f606cf72b82ac2444747da92049e42540b Mon Sep 17 00:00:00 2001 From: Taehoon Date: Thu, 11 Jun 2026 10:37:33 +0900 Subject: [PATCH 3/5] =?UTF-8?q?fix:=20=EC=9E=90=EC=82=B0=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=EC=A0=95=EB=B3=B4=20=EB=A0=88=EC=9D=B4=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EC=B5=9C=EC=A0=81=ED=99=94=20=EB=B0=8F=20=EC=8A=A4?= =?UTF-8?q?=ED=83=80=EC=9D=BC=20=ED=91=9C=EC=A4=80=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 상세 정보 섹션을 2열 그리드로 변경하여 정보 밀도 향상\n- 항목 제목(12px) 및 내용(14px) 폰트 크기 조정\n- 인라인 스타일을 dashboard.css로 이전 및 중앙 집중화\n- 불필요한 아이콘 제거 및 UI 정돈 --- src/styles/dashboard.css | 61 +++++++++++++++++++++++++++++++++++---- src/views/LocationView.ts | 17 ++++++----- 2 files changed, 65 insertions(+), 13 deletions(-) diff --git a/src/styles/dashboard.css b/src/styles/dashboard.css index 5c1bbaf..85801c4 100644 --- a/src/styles/dashboard.css +++ b/src/styles/dashboard.css @@ -321,9 +321,60 @@ cursor: pointer; } -.empty-state { - padding: 4rem 2rem; - text-align: center; - color: var(--text-muted); - font-size: 0.8125rem; +/* --- Asset Detail Sidebar (LocationView) --- */ +.asset-detail-sidebar { + padding-top: 1rem; + background: var(--white); + height: 100%; + overflow-y: auto; +} + +.detail-section { + margin-bottom: 20px; + padding: 0 1.25rem; +} + +.detail-section-title { + font-size: 13px; + font-weight: 700; + color: var(--primary-color); + border-bottom: 1px solid var(--border-color); + padding-bottom: 6px; + margin-bottom: 12px; +} + +.detail-grid { + display: grid; + grid-template-columns: repeat(2, minmax(80px, auto) 1fr); + gap: 8px 16px; +} + +.detail-label { + font-size: 12px; + color: var(--text-muted); + font-weight: 600; + display: flex; + align-items: center; +} + +.detail-value { + font-size: 14px; + color: var(--text-main); + font-weight: 500; + word-break: break-all; + display: flex; + align-items: center; +} + +.detail-header-actions { + display: flex; + align-items: center; + gap: 8px; + width: 100%; +} + +.detail-header-title { + flex: 1; + font-size: 0.95rem; + font-weight: 700; } diff --git a/src/views/LocationView.ts b/src/views/LocationView.ts index 5bb81ff..9ade3c6 100644 --- a/src/views/LocationView.ts +++ b/src/views/LocationView.ts @@ -176,21 +176,22 @@ export async function renderLocationView(container: HTMLElement) { const renderAssetDetail = (asset: any) => { const title = container.querySelector('#loc-list-title')!; const tableContainer = container.querySelector('#loc-asset-table-container')!; + title.innerHTML = ` -
+
- 📍 자산 상세 정보 + 자산 상세 정보
`; const renderSection = (title: string, fields: { label: string; value: any }[]) => ` -
-
${title}
-
+
+
${title}
+
${fields.map(f => ` -
${f.label}
-
${f.value || '-'}
+
${f.label}
+
${f.value || '-'}
`).join('')}
@@ -224,7 +225,7 @@ export async function renderLocationView(container: HTMLElement) { ].join(''); tableContainer.innerHTML = ` -
+
${sectionsHTML}
`; From 10479aad7ed146fa4f5f07fd9c67420367404f41 Mon Sep 17 00:00:00 2001 From: Taehoon Date: Thu, 11 Jun 2026 11:14:00 +0900 Subject: [PATCH 4/5] =?UTF-8?q?feat:=20HWModal=20=EB=84=A4=ED=8A=B8?= =?UTF-8?q?=EC=9B=8C=ED=81=AC=20=EB=B0=8F=20=EC=9B=90=EA=B2=A9=20=EC=A0=91?= =?UTF-8?q?=EC=86=8D=20=EC=A0=95=EB=B3=B4=20=EB=8F=99=EC=A0=81=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=20=EA=B8=B0=EB=8A=A5=20=ED=86=B5=ED=95=A9=20=EB=B0=8F?= =?UTF-8?q?=20UI=20=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Modal/HWModal.ts | 231 ++++++++++++++++++++++---------- 1 file changed, 157 insertions(+), 74 deletions(-) diff --git a/src/components/Modal/HWModal.ts b/src/components/Modal/HWModal.ts index 9cd7916..efb5521 100644 --- a/src/components/Modal/HWModal.ts +++ b/src/components/Modal/HWModal.ts @@ -35,8 +35,9 @@ class HwAssetModal extends BaseModal {