+
diff --git a/src/views/List/PcListView.ts b/src/views/List/PcListView.ts
index 83c6fa0..e51d260 100644
--- a/src/views/List/PcListView.ts
+++ b/src/views/List/PcListView.ts
@@ -5,43 +5,10 @@ import { ASSET_SCHEMA } from '../../core/schema';
import { createListView } from './ListFactory';
export function renderPcList(container: HTMLElement) {
- createListView(container, {
- title: 'PC',
- dataSource: () => sortAssets((state.masterData.pc || []).filter((a: any) => a.asset_type !== '서버PC')),
- searchKeys: ['CURRENT_DEPT', 'CURRENT_USER', 'MODEL_NAME', 'MAC_ADDR', 'MANAGER_MAIN', 'ASSET_TYPE'],
- filterOptions: {
- keywordLabel: `통합 검색 (${ASSET_SCHEMA.MODEL_NAME.ui}/${ASSET_SCHEMA.MANAGER_MAIN.ui}/${ASSET_SCHEMA.CURRENT_USER.ui})`,
- showLoc: true,
- showDept: true,
- showType: true
- },
- onRowClick: (asset) => openHwModal(asset, 'view'),
- columns: [
- { header: ASSET_SCHEMA.CURRENT_USER.ui, sortKey: ASSET_SCHEMA.CURRENT_USER.key, align: 'center', render: a => a[ASSET_SCHEMA.CURRENT_USER.key] || '-' },
- { header: ASSET_SCHEMA.ASSET_TYPE.ui, sortKey: ASSET_SCHEMA.ASSET_TYPE.key, align: 'center', width: '10%', render: a => a[ASSET_SCHEMA.ASSET_TYPE.key] || '-' },
- { header: ASSET_SCHEMA.CPU.ui, sortKey: ASSET_SCHEMA.CPU.key, align: 'center', render: a => a[ASSET_SCHEMA.CPU.key] || '' },
- { header: ASSET_SCHEMA.MAINBOARD.ui, sortKey: ASSET_SCHEMA.MAINBOARD.key, align: 'center', render: a => a[ASSET_SCHEMA.MAINBOARD.key] || '-' },
- { header: ASSET_SCHEMA.RAM.ui, sortKey: ASSET_SCHEMA.RAM.key, align: 'center', render: a => a[ASSET_SCHEMA.RAM.key] || '' },
- { header: ASSET_SCHEMA.GPU.ui, sortKey: ASSET_SCHEMA.GPU.key, align: 'center', render: a => a[ASSET_SCHEMA.GPU.key] || '-' },
- {
- header: 'SSD',
- align: 'center',
- width: '8%',
- render: a => [a[ASSET_SCHEMA.SSD1.key], a[ASSET_SCHEMA.SSD2.key]].filter(Boolean).join(' / ') || '-'
- },
- {
- header: 'HDD',
- align: 'center',
- width: '12%',
- render: a => [a[ASSET_SCHEMA.HDD1.key], a[ASSET_SCHEMA.HDD2.key], a[ASSET_SCHEMA.HDD3.key], a[ASSET_SCHEMA.HDD4.key]].filter(Boolean).join(' / ') || '-'
- },
- {
- header: ASSET_SCHEMA.MAC_ADDR.ui,
- sortKey: ASSET_SCHEMA.MAC_ADDR.key,
- align: 'center',
- render: a => `
${a[ASSET_SCHEMA.MAC_ADDR.key] || '-'}`
- },
- { header: ASSET_SCHEMA.MEMO.ui, sortKey: ASSET_SCHEMA.MEMO.key, className: 'col-memo', width: '30%', render: a => formatInline(a[ASSET_SCHEMA.MEMO.key] || '-') }
- ]
- });
+ container.innerHTML = `
+
+
PC 관리
+
해당 페이지는 다른 작업자에 의해 개발 중입니다.
+
+ `;
}
diff --git a/src/views/LocationView.ts b/src/views/LocationView.ts
new file mode 100644
index 0000000..9ade3c6
--- /dev/null
+++ b/src/views/LocationView.ts
@@ -0,0 +1,244 @@
+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 ? `
+
+ ` : ''}
+
+
+
+
+
+
+
+
+ ${mapPath ? `
+

+
+ ${boxes.map((box: any, idx: number) => {
+ const name = box.name || `#${idx+1}`;
+ return `
+
+
+ `}).join('')}
+
+ ` : '
해당 위치의 도면이 등록되지 않았습니다.
'}
+
+
+
+
+
+
+
+
* 지도 위의 구역을 클릭하면 자산 상세 정보가 표시됩니다.
+
+
+ `;
+
+ // 이미지 로드 및 윈도우 리사이즈 시 오버레이 크기와 위치를 이미지에 정확히 맞춤
+ 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) {
+ 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;
+ 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');
+
+ 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, 73, 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 = `
+
+ `;
+
+ container.querySelector('#btn-back-to-list')?.addEventListener('click', () => {
+ title.textContent = `📍 구역을 선택하세요`;
+ tableContainer.innerHTML = `
지도에서 자산 위치를 클릭하세요.
`;
+ });
+
+ container.querySelector('#btn-edit-from-loc')?.addEventListener('click', () => {
+ openHwModal(asset, 'edit');
+ });
+ };
+
+ 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,
+ }
+ }
},
});