From f22e27b14afd447c8d67b56d282b4d76b7892d55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=83=9C=ED=9B=88?= Date: Thu, 2 Jul 2026 15:51:53 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20AppState=20=ED=83=80=EC=9E=85=EC=97=90?= =?UTF-8?q?=20currentUserMobile=20=EC=84=A0=EC=96=B8=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20=EB=B0=8F=20initializeAppDirectly=20=EC=9D=B8=EC=9E=90=20?= =?UTF-8?q?=EB=88=84=EB=9D=BD=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/state.ts | 582 +++++++++++++++++++++++----------------------- src/main.ts | 2 +- 2 files changed, 293 insertions(+), 291 deletions(-) diff --git a/src/core/state.ts b/src/core/state.ts index b222eb4..74cdbe2 100644 --- a/src/core/state.ts +++ b/src/core/state.ts @@ -1,293 +1,295 @@ -import { HardwareAsset, SoftwareAsset, SWUser, HardwareLog, MasterAssetData, SystemUser } from './types'; -import { API_BASE_URL } from './utils'; +import { HardwareAsset, SoftwareAsset, SWUser, HardwareLog, MasterAssetData, SystemUser } from './types'; +import { API_BASE_URL } from './utils'; -// --- State Definitions --- -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'; - listFilters?: Record; -} - -// 초기 상태 -export const state: AppState = { - activeCategory: 'hw', - activeSubTab: '대시보드', - viewMode: 'location', - activeCharts: [], - currentUserRole: 'user', - listFilters: {}, - masterData: { - users: [], - pc: [], server: [], storage: [], network: [], - survey: [], pcParts: [], partsMaster: [], equipment: [], officeSupplies: [], - swInternal: [], swExternal: [], cloud: [], domain: [], - cost: [], vip: [], - hw: [], sw: [], - swUsers: [], logs: [], - jobSpecs: [], - mobile: [] - } -}; - -(window as any).__itam_state = state; - -/** - * 통합 V2 스키마에 맞춘 데이터 로드 - */ -export async function loadMasterDataFromDB() { - try { - const response = await fetch(`${API_BASE_URL}/api/assets/master`); - if (!response.ok) throw new Error('Failed to fetch master data'); - - const data = await response.json(); - - // DB의 쪼개진 asset_remote 데이터로부터 가상 대표 속성(IP, MAC, 원격도구)을 주입해주는 전처리 함수 - const preprocessAssets = (assets: any[]) => { - if (!Array.isArray(assets)) return; - assets.forEach((asset: any) => { - let ip = ''; - let mac = ''; - let remoteTool = ''; - let remoteId = ''; +// --- State Definitions --- +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'; + currentUserMobile?: string; + listFilters?: Record; +} + +// 초기 상태 +export const state: AppState = { + activeCategory: 'hw', + activeSubTab: '대시보드', + viewMode: 'location', + activeCharts: [], + currentUserRole: 'user', + currentUserMobile: '', + listFilters: {}, + masterData: { + users: [], + pc: [], server: [], storage: [], network: [], + survey: [], pcParts: [], partsMaster: [], equipment: [], officeSupplies: [], + swInternal: [], swExternal: [], cloud: [], domain: [], + cost: [], vip: [], + hw: [], sw: [], + swUsers: [], logs: [], + jobSpecs: [], + mobile: [] + } +}; + +(window as any).__itam_state = state; + +/** + * 통합 V2 스키마에 맞춘 데이터 로드 + */ +export async function loadMasterDataFromDB() { + try { + const response = await fetch(`${API_BASE_URL}/api/assets/master`); + if (!response.ok) throw new Error('Failed to fetch master data'); + + const data = await response.json(); + + // DB의 쪼개진 asset_remote 데이터로부터 가상 대표 속성(IP, MAC, 원격도구)을 주입해주는 전처리 함수 + const preprocessAssets = (assets: any[]) => { + if (!Array.isArray(assets)) return; + assets.forEach((asset: any) => { + let ip = ''; + let mac = ''; + let remoteTool = ''; + let remoteId = ''; let remotePw = ''; - let rems: any[] = []; - try { - rems = asset.remotes ? (typeof asset.remotes === 'string' ? JSON.parse(asset.remotes) : asset.remotes) : []; - } catch(e) {} - - if (Array.isArray(rems)) { - rems.forEach((r: any) => { - if (r.type === 'IP') { - if (!ip) ip = r.val1 || ''; - if (r.val2) { - if (String(r.val2).trim().startsWith('{')) { - try { - const parsed = JSON.parse(r.val2); - remoteTool = r.name || '원격접속'; - remoteId = parsed.id || ''; - remotePw = parsed.pw || ''; - } catch(e) {} - } else { - if (!mac) mac = r.val2 || ''; - } - } - } else if (r.type === 'MAC') { - if (!mac) mac = r.val1 || ''; - } else if (r.type === 'REMOTE') { - if (!remoteTool) remoteTool = r.name || ''; - if (!remoteId) remoteId = r.val1 || ''; - if (!remotePw) remotePw = r.val2 || ''; - } - }); - } - - // 최상위 가상 속성 바인딩 (목록 및 위치보기 뷰어 매핑용) - asset.ip_address = ip; - asset.mac_address = mac; - asset.remote_tool = remoteTool; - asset.remote_id = remoteId; - asset.remote_pw = remotePw; - }); - }; - - if (data) { - const keys = ['pc', 'server', 'storage', 'network', 'survey', 'equipment', 'officeSupplies']; - keys.forEach(k => { - if (data[k]) preprocessAssets(data[k]); - }); - } - - // 전역 상태 업데이트 - state.masterData = { - ...state.masterData, - ...data, - jobSpecs: data.jobSpecs || [], - logs: (data.logs || []).map((l: any) => ({ - ...l, - assetId: l.asset_id || l.assetId, - date: l.log_date || l.date, - user: l.log_user || l.user, - log_date: l.log_date || l.date, - log_user: l.log_user || l.user - })) - }; - - // Mapping for backward compatibility - (state.masterData as any).equip = state.masterData.equipment; - (state.masterData as any).subSw = state.masterData.swExternal; - (state.masterData as any).permSw = state.masterData.swInternal; - - // 하드웨어 통합 (대시보드 호환용) - state.masterData.hw = [ - ...state.masterData.pc, - ...state.masterData.server, - ...state.masterData.storage, - ...state.masterData.network, - ...state.masterData.survey, - ...state.masterData.equipment, - ...state.masterData.officeSupplies - ]; - - // 소프트웨어 통합 - state.masterData.sw = [ - ...state.masterData.swInternal, - ...state.masterData.swExternal, - ...(state.masterData.cloud || []) - ]; - - console.log('✅ V2 Normalized data loaded successfully'); - return true; - } catch (err) { - console.warn('⚠️ Dummy 로드 실패:', err); - } - return false; -} - -export function updateState(newState: Partial) { - Object.assign(state, newState); -} - -/** - * 자산 저장 (V2 Normalized API) - */ -export async function saveAsset(category: string, asset: any) { - try { - const url = `${API_BASE_URL}/api/asset/${category}/save`; - const response = await fetch(url, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(asset) - }); - - if (response.ok) { - await loadMasterDataFromDB(); // 전역 상태 갱신 - return true; - } - } catch (err) { - console.error('자산 저장 실패:', err); - } - return false; -} - -/** - * 자산 삭제 (V2 API) - */ -export async function deleteAsset(category: string, assetId: string) { - try { - const url = `${API_BASE_URL}/api/asset/${category}/${assetId}`; - const response = await fetch(url, { method: 'DELETE' }); - - if (response.ok) { - await loadMasterDataFromDB(); // 전역 상태 갱신 - return true; - } - } catch (err) { - console.error('자산 삭제 실패:', err); - } - return false; -} - -export async function savePartsMaster(component: any) { - try { - const url = `${API_BASE_URL}/api/hardware-components/save`; - const response = await fetch(url, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(component) - }); - - if (response.ok) { - await loadMasterDataFromDB(); // 전역 상태 갱신 - return true; - } - } catch (err) { - console.error('부품 마스터 저장 실패:', err); - } - return false; -} - -export async function deletePartsMaster(id: number) { - try { - const url = `${API_BASE_URL}/api/hardware-components/${id}`; - const response = await fetch(url, { method: 'DELETE' }); - - if (response.ok) { - await loadMasterDataFromDB(); // 전역 상태 갱신 - return true; - } - } catch (err) { - console.error('부품 마스터 삭제 실패:', err); - } - return false; -} - -export async function saveSystemUser(user: any) { - try { - const url = `${API_BASE_URL}/api/system-users/save`; - const response = await fetch(url, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(user) - }); - if (response.ok) { - await loadMasterDataFromDB(); // 전역 상태 갱신 - return true; - } - } catch (err) { - console.error('사용자 정보 저장 실패:', err); - } - return false; -} - -export async function deleteSystemUser(id: string) { - try { - const url = `${API_BASE_URL}/api/system-users/${id}`; - const response = await fetch(url, { method: 'DELETE' }); - if (response.ok) { - await loadMasterDataFromDB(); // 전역 상태 갱신 - return true; - } - } catch (err) { - console.error('사용자 정보 삭제 실패:', err); - } - return false; -} - -export async function saveJobSpec(spec: any) { - try { - const url = `${API_BASE_URL}/api/job-specs/save`; - const response = await fetch(url, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(spec) - }); - - if (response.ok) { - await loadMasterDataFromDB(); // 전역 상태 갱신 - return true; - } - } catch (err) { - console.error('직무별 기준 사양 저장 실패:', err); - } - return false; -} - -export async function deleteJobSpec(id: number) { - try { - const url = `${API_BASE_URL}/api/job-specs/${id}`; - const response = await fetch(url, { method: 'DELETE' }); - - if (response.ok) { - await loadMasterDataFromDB(); // 전역 상태 갱신 - return true; - } - } catch (err) { - console.error('직무별 기준 사양 삭제 실패:', err); - } - return false; -} + let rems: any[] = []; + try { + rems = asset.remotes ? (typeof asset.remotes === 'string' ? JSON.parse(asset.remotes) : asset.remotes) : []; + } catch(e) {} + + if (Array.isArray(rems)) { + rems.forEach((r: any) => { + if (r.type === 'IP') { + if (!ip) ip = r.val1 || ''; + if (r.val2) { + if (String(r.val2).trim().startsWith('{')) { + try { + const parsed = JSON.parse(r.val2); + remoteTool = r.name || '원격접속'; + remoteId = parsed.id || ''; + remotePw = parsed.pw || ''; + } catch(e) {} + } else { + if (!mac) mac = r.val2 || ''; + } + } + } else if (r.type === 'MAC') { + if (!mac) mac = r.val1 || ''; + } else if (r.type === 'REMOTE') { + if (!remoteTool) remoteTool = r.name || ''; + if (!remoteId) remoteId = r.val1 || ''; + if (!remotePw) remotePw = r.val2 || ''; + } + }); + } + + // 최상위 가상 속성 바인딩 (목록 및 위치보기 뷰어 매핑용) + asset.ip_address = ip; + asset.mac_address = mac; + asset.remote_tool = remoteTool; + asset.remote_id = remoteId; + asset.remote_pw = remotePw; + }); + }; + + if (data) { + const keys = ['pc', 'server', 'storage', 'network', 'survey', 'equipment', 'officeSupplies']; + keys.forEach(k => { + if (data[k]) preprocessAssets(data[k]); + }); + } + + // 전역 상태 업데이트 + state.masterData = { + ...state.masterData, + ...data, + jobSpecs: data.jobSpecs || [], + logs: (data.logs || []).map((l: any) => ({ + ...l, + assetId: l.asset_id || l.assetId, + date: l.log_date || l.date, + user: l.log_user || l.user, + log_date: l.log_date || l.date, + log_user: l.log_user || l.user + })) + }; + + // Mapping for backward compatibility + (state.masterData as any).equip = state.masterData.equipment; + (state.masterData as any).subSw = state.masterData.swExternal; + (state.masterData as any).permSw = state.masterData.swInternal; + + // 하드웨어 통합 (대시보드 호환용) + state.masterData.hw = [ + ...state.masterData.pc, + ...state.masterData.server, + ...state.masterData.storage, + ...state.masterData.network, + ...state.masterData.survey, + ...state.masterData.equipment, + ...state.masterData.officeSupplies + ]; + + // 소프트웨어 통합 + state.masterData.sw = [ + ...state.masterData.swInternal, + ...state.masterData.swExternal, + ...(state.masterData.cloud || []) + ]; + + console.log('✅ V2 Normalized data loaded successfully'); + return true; + } catch (err) { + console.warn('⚠️ Dummy 로드 실패:', err); + } + return false; +} + +export function updateState(newState: Partial) { + Object.assign(state, newState); +} + +/** + * 자산 저장 (V2 Normalized API) + */ +export async function saveAsset(category: string, asset: any) { + try { + const url = `${API_BASE_URL}/api/asset/${category}/save`; + const response = await fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(asset) + }); + + if (response.ok) { + await loadMasterDataFromDB(); // 전역 상태 갱신 + return true; + } + } catch (err) { + console.error('자산 저장 실패:', err); + } + return false; +} + +/** + * 자산 삭제 (V2 API) + */ +export async function deleteAsset(category: string, assetId: string) { + try { + const url = `${API_BASE_URL}/api/asset/${category}/${assetId}`; + const response = await fetch(url, { method: 'DELETE' }); + + if (response.ok) { + await loadMasterDataFromDB(); // 전역 상태 갱신 + return true; + } + } catch (err) { + console.error('자산 삭제 실패:', err); + } + return false; +} + +export async function savePartsMaster(component: any) { + try { + const url = `${API_BASE_URL}/api/hardware-components/save`; + const response = await fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(component) + }); + + if (response.ok) { + await loadMasterDataFromDB(); // 전역 상태 갱신 + return true; + } + } catch (err) { + console.error('부품 마스터 저장 실패:', err); + } + return false; +} + +export async function deletePartsMaster(id: number) { + try { + const url = `${API_BASE_URL}/api/hardware-components/${id}`; + const response = await fetch(url, { method: 'DELETE' }); + + if (response.ok) { + await loadMasterDataFromDB(); // 전역 상태 갱신 + return true; + } + } catch (err) { + console.error('부품 마스터 삭제 실패:', err); + } + return false; +} + +export async function saveSystemUser(user: any) { + try { + const url = `${API_BASE_URL}/api/system-users/save`; + const response = await fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(user) + }); + if (response.ok) { + await loadMasterDataFromDB(); // 전역 상태 갱신 + return true; + } + } catch (err) { + console.error('사용자 정보 저장 실패:', err); + } + return false; +} + +export async function deleteSystemUser(id: string) { + try { + const url = `${API_BASE_URL}/api/system-users/${id}`; + const response = await fetch(url, { method: 'DELETE' }); + if (response.ok) { + await loadMasterDataFromDB(); // 전역 상태 갱신 + return true; + } + } catch (err) { + console.error('사용자 정보 삭제 실패:', err); + } + return false; +} + +export async function saveJobSpec(spec: any) { + try { + const url = `${API_BASE_URL}/api/job-specs/save`; + const response = await fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(spec) + }); + + if (response.ok) { + await loadMasterDataFromDB(); // 전역 상태 갱신 + return true; + } + } catch (err) { + console.error('직무별 기준 사양 저장 실패:', err); + } + return false; +} + +export async function deleteJobSpec(id: number) { + try { + const url = `${API_BASE_URL}/api/job-specs/${id}`; + const response = await fetch(url, { method: 'DELETE' }); + + if (response.ok) { + await loadMasterDataFromDB(); // 전역 상태 갱신 + return true; + } + } catch (err) { + console.error('직무별 기준 사양 삭제 실패:', err); + } + return false; +} diff --git a/src/main.ts b/src/main.ts index 88ccc3d..30e8761 100644 --- a/src/main.ts +++ b/src/main.ts @@ -385,7 +385,7 @@ function showLoginScreen(errorMessage?: string) { if (payload.status === 'authenticated') { clearPhonePollTimer(); - initializeAppDirectly(); + initializeAppDirectly(loginId); return; }