fix: AppState 타입에 currentUserMobile 선언 추가 및 initializeAppDirectly 인자 누락 오류 수정
This commit is contained in:
@@ -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<string, any>;
|
||||
}
|
||||
|
||||
// 초기 상태
|
||||
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<string, any>;
|
||||
}
|
||||
|
||||
// 초기 상태
|
||||
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<AppState>) {
|
||||
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<AppState>) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -385,7 +385,7 @@ function showLoginScreen(errorMessage?: string) {
|
||||
|
||||
if (payload.status === 'authenticated') {
|
||||
clearPhonePollTimer();
|
||||
initializeAppDirectly();
|
||||
initializeAppDirectly(loginId);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user