From f41f2378d71e683bdd1c82d945ee29446957946f Mon Sep 17 00:00:00 2001 From: Taehoon Date: Fri, 19 Jun 2026 16:25:28 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EC=9E=90=EC=82=B0=EB=B2=88=ED=98=B8=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EB=88=84=EB=9D=BD=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EC=9C=84=EC=B9=98=EB=B3=B4?= =?UTF-8?q?=EA=B8=B0=20=EB=8F=84=EB=A9=B4=20=EB=B0=B0=EC=B9=98=20=EB=B3=B4?= =?UTF-8?q?=EC=99=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Modal/HWModal.ts | 24 +++++++++++++++ src/components/Modal/ModalUtils.ts | 5 +++- src/views/LocationView.ts | 48 +++++++++++++++++++++++++++--- 3 files changed, 72 insertions(+), 5 deletions(-) diff --git a/src/components/Modal/HWModal.ts b/src/components/Modal/HWModal.ts index 44ca249..382cf21 100644 --- a/src/components/Modal/HWModal.ts +++ b/src/components/Modal/HWModal.ts @@ -377,6 +377,30 @@ class HwAssetModal extends BaseModal { return; } + // 자산코드가 비어있는 경우 자동 생성 처리 + let assetCode = getFieldValue('hw-asset_code').trim(); + if (!assetCode) { + const cat = categorySelect.value; + if (!cat) { alert('구분을 먼저 선택해주세요.'); return; } + const prefix = TYPE_PREFIX_MAP[cat] || 'ETC'; + const purchaseDate = (document.getElementById('hw-purchase_date') as HTMLInputElement)?.value || ''; + try { + const res = await fetch(`http://${location.hostname}:3000/api/generate-asset-code?prefix=${prefix}&purchaseDate=${purchaseDate}`); + const data = await res.json(); + if (data.nextCode) { + setFieldValue('hw-asset_code', data.nextCode); + assetCode = data.nextCode; + } else { + alert('자산코드 자동 생성에 실패했습니다. 수동으로 생성 버튼을 눌러주세요.'); + return; + } + } catch (err) { + console.error('코드 자동 생성 실패:', err); + alert('자산코드 자동 생성 중 오류가 발생했습니다.'); + return; + } + } + // 동적 볼륨 데이터 수집 const vols: any[] = []; document.querySelectorAll('#hw-volume-container .volume-row').forEach((row, idx) => { diff --git a/src/components/Modal/ModalUtils.ts b/src/components/Modal/ModalUtils.ts index c81f125..e27b519 100644 --- a/src/components/Modal/ModalUtils.ts +++ b/src/components/Modal/ModalUtils.ts @@ -125,10 +125,13 @@ export function setEditLock( const inputs = form.querySelectorAll('input, select, textarea'); inputs.forEach(input => { const el = input as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement; - // 자산번호 및 ID 필드는 편집 모드에서도 잠금 유지 + // 자산번호 및 ID 필드는 편집 모드에서도 잠금 유지 (disabled는 해제하되 readOnly를 적용하여 폼 데이터 수집 가능하게 함) if (el.name !== 'asset_code' && !el.id.includes('asset-id') && !el.id.includes('id-hidden')) { el.disabled = false; if ('readOnly' in el) (el as HTMLInputElement).readOnly = false; + } else { + el.disabled = false; + if ('readOnly' in el) (el as HTMLInputElement).readOnly = true; } }); diff --git a/src/views/LocationView.ts b/src/views/LocationView.ts index 1d6711b..05af2be 100644 --- a/src/views/LocationView.ts +++ b/src/views/LocationView.ts @@ -25,10 +25,6 @@ export async function renderLocationView(container: HTMLElement) { : []; const mapPath = locImages[currentPage] || ''; - // 조회 모드: 설정 파일에 정의된 asset_id를 기준으로 자산 데이터 매핑 - const allBoxes = mapConfig[mapPath] || []; - const boxes = allBoxes.filter((box: any) => box.asset_id != null); - // 모든 하드웨어 카테고리에서 자산 검색 const allHwAssets = [ ...state.masterData.pc, @@ -41,6 +37,50 @@ export async function renderLocationView(container: HTMLElement) { ...state.masterData.pcParts ]; + // map_config.json에 설정된 모든 박스를 복사해서 작업용으로 사용 + const tempBoxes = (mapConfig[mapPath] || []).map((b: any) => ({ ...b })); + + // DB 데이터에서 현재 지도(mapPath) 및 위치와 좌표 정보(loc_x, loc_y)가 일치하는 자산 추출 + allHwAssets.forEach((asset: any) => { + const photoPath = asset.location_photo || asset.loc_img || ''; + const hasCoords = asset.loc_x != null && asset.loc_y != null && asset.loc_x !== '' && asset.loc_y !== '' && asset.loc_x !== 'null' && asset.loc_y !== 'null'; + + if (hasCoords && photoPath.trim() === mapPath.trim()) { + const ax = parseFloat(asset.loc_x); + const ay = parseFloat(asset.loc_y); + + // map_config.json에서 읽어온 박스들 중 x, y 좌표가 일치하는 빈 박스가 있는지 찾음 (오차범위 0.1 고려) + const matchedBox = tempBoxes.find((b: any) => { + const bx = parseFloat(b.x); + const by = parseFloat(b.y); + return Math.abs(bx - ax) < 0.1 && Math.abs(by - ay) < 0.1; + }); + + if (matchedBox) { + // 이미 매칭된 박스가 존재하고 asset_id가 비어있다면 해당 박스에 asset_id를 주입 + if (matchedBox.asset_id == null) { + matchedBox.asset_id = asset.id; + } + } else { + // 일치하는 기존 박스가 없을 때만 4x4 크기의 임시 박스로 동적 생성 + const alreadyMatched = tempBoxes.some((b: any) => b.asset_id === asset.id); + if (!alreadyMatched) { + tempBoxes.push({ + asset_id: asset.id, + x: asset.loc_x, + y: asset.loc_y, + w: '4', + h: '4', + name: asset.asset_purpose || asset.asset_code || '미지정 자산' + }); + } + } + } + }); + + // 최종적으로 asset_id가 null이 아닌(자산이 정상 매핑되거나 갱신된) 박스들만 남겨서 렌더링 + const boxes = tempBoxes.filter((b: any) => b.asset_id != null); + container.innerHTML = `