- Replace credential login with Admin/Practitioner role selection - Add role-switcher toggle in header with automatic reversion for Admin mode - Implement immediate return to role selection via system logo click - Integrate role state management into global app state
220 lines
6.3 KiB
TypeScript
220 lines
6.3 KiB
TypeScript
import { HardwareAsset, SoftwareAsset, SWUser, HardwareLog } from './excelHandler';
|
|
import { API_BASE_URL } from './utils';
|
|
|
|
// --- State Definitions ---
|
|
export interface MasterAssetData {
|
|
users: any[];
|
|
pc: any[];
|
|
server: any[];
|
|
storage: any[];
|
|
network: any[];
|
|
survey: any[];
|
|
pcParts: any[];
|
|
equipment: any[];
|
|
officeSupplies: any[];
|
|
swInternal: any[];
|
|
swExternal: any[];
|
|
cloud: any[];
|
|
domain: any[];
|
|
cost: any[];
|
|
vip: any[];
|
|
mobile?: any[]; // Legacy mobile support
|
|
equip?: any[]; // Backward compat
|
|
|
|
// Backward compatibility
|
|
subSw: any[];
|
|
permSw: any[];
|
|
|
|
swUsers: SWUser[];
|
|
logs: HardwareLog[];
|
|
|
|
// 통합 배열
|
|
hw: any[];
|
|
sw: any[];
|
|
}
|
|
|
|
export interface AppState {
|
|
activeCategory: 'dashboard' | 'hw' | 'sw' | 'ops' | 'vip' | 'fac' | 'users' | 'etc';
|
|
activeSubTab: string;
|
|
masterData: MasterAssetData;
|
|
activeCharts: any[];
|
|
currentUserRole: 'admin' | 'user';
|
|
}
|
|
|
|
// 초기 상태
|
|
export const state: AppState = {
|
|
activeCategory: 'hw',
|
|
activeSubTab: '서버', // 대시보드 제거됨에 따라 기본값 변경
|
|
activeCharts: [],
|
|
currentUserRole: 'user',
|
|
masterData: {
|
|
users: [],
|
|
pc: [], server: [], storage: [], network: [],
|
|
survey: [], pcParts: [], equipment: [], officeSupplies: [],
|
|
swInternal: [], swExternal: [], cloud: [], domain: [],
|
|
cost: [], vip: [],
|
|
subSw: [], permSw: [],
|
|
hw: [], sw: [],
|
|
swUsers: [], logs: []
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 신규 14개 테이블 구조에 맞춘 데이터 로드
|
|
*/
|
|
export async function loadMasterDataFromDB() {
|
|
try {
|
|
const endpoints = [
|
|
{ key: 'users', url: '/api/users' },
|
|
{ key: 'pc', url: '/api/pc' },
|
|
{ key: 'server', url: '/api/server' },
|
|
{ key: 'storage', url: '/api/storage' },
|
|
{ key: 'network', url: '/api/network' },
|
|
{ key: 'survey', url: '/api/survey' },
|
|
{ key: 'pcParts', url: '/api/pc-parts' },
|
|
{ key: 'equipment', url: '/api/equipment' },
|
|
{ key: 'officeSupplies', url: '/api/office-supplies' },
|
|
{ key: 'swInternal', url: '/api/sw/internal' },
|
|
{ key: 'swExternal', url: '/api/sw/external' },
|
|
{ key: 'cloud', url: '/api/cloud' },
|
|
{ key: 'domain', url: '/api/domain' },
|
|
{ key: 'cost', url: '/api/cost' },
|
|
{ key: 'vip', url: '/api/vip' },
|
|
{ key: 'swUsers', url: '/api/asset/software/assignment' },
|
|
{ key: 'logs', url: '/api/asset/history' }
|
|
];
|
|
|
|
const results = await Promise.all(endpoints.map(e => fetch(API_BASE_URL + e.url)));
|
|
|
|
for (let i = 0; i < endpoints.length; i++) {
|
|
if (results[i].ok) {
|
|
const data = await results[i].json();
|
|
const key = endpoints[i].key;
|
|
(state.masterData as any)[key] = Array.isArray(data) ? data : [];
|
|
}
|
|
}
|
|
|
|
// Mapping for backward compatibility
|
|
state.masterData.equip = state.masterData.equipment;
|
|
state.masterData.subSw = state.masterData.swExternal;
|
|
state.masterData.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('✅ All data (including users) loaded and unified');
|
|
return true;
|
|
} catch (err) {
|
|
console.warn('⚠️ 서버 연결 실패:', err);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
export function updateState(newState: Partial<AppState>) {
|
|
Object.assign(state, newState);
|
|
}
|
|
|
|
/**
|
|
* 자산 저장 (Generic API)
|
|
*/
|
|
export async function saveAsset(category: string, asset: any) {
|
|
try {
|
|
const endpointMap: Record<string, string> = {
|
|
'users': '/api/users/batch',
|
|
'pc': '/api/pc/batch',
|
|
'server': '/api/server/batch',
|
|
'storage': '/api/storage/batch',
|
|
'network': '/api/network/batch',
|
|
'survey': '/api/survey/batch',
|
|
'pcParts': '/api/pc-parts/batch',
|
|
'equipment': '/api/equipment/batch',
|
|
'officeSupplies': '/api/office-supplies/batch',
|
|
'swInternal': '/api/sw/internal/batch',
|
|
'swExternal': '/api/sw/external/batch',
|
|
'cloud': '/api/cloud/batch',
|
|
'domain': '/api/domain/batch',
|
|
'cost': '/api/cost/batch',
|
|
'vip': '/api/vip/batch'
|
|
};
|
|
|
|
const url = `${API_BASE_URL}${endpointMap[category]}`;
|
|
const currentList = [...(state.masterData as any)[category]];
|
|
const idx = currentList.findIndex(a => a.id === asset.id);
|
|
|
|
if (idx > -1) currentList[idx] = asset;
|
|
else currentList.push(asset);
|
|
|
|
const response = await fetch(url, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(currentList)
|
|
});
|
|
|
|
if (response.ok) {
|
|
await loadMasterDataFromDB(); // 전역 상태 갱신
|
|
return true;
|
|
}
|
|
} catch (err) {
|
|
console.error('자산 저장 실패:', err);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* 자산 삭제 (Generic API - Batch 방식 활용)
|
|
*/
|
|
export async function deleteAsset(category: string, assetId: string) {
|
|
try {
|
|
const endpointMap: Record<string, string> = {
|
|
'users': '/api/users/batch',
|
|
'pc': '/api/pc/batch',
|
|
'server': '/api/server/batch',
|
|
'storage': '/api/storage/batch',
|
|
'network': '/api/network/batch',
|
|
'survey': '/api/survey/batch',
|
|
'pcParts': '/api/pc-parts/batch',
|
|
'equipment': '/api/equipment/batch',
|
|
'officeSupplies': '/api/office-supplies/batch',
|
|
'swInternal': '/api/sw/internal/batch',
|
|
'swExternal': '/api/sw/external/batch',
|
|
'cloud': '/api/cloud/batch',
|
|
'domain': '/api/domain/batch',
|
|
'cost': '/api/cost/batch',
|
|
'vip': '/api/vip/batch'
|
|
};
|
|
|
|
const url = `${API_BASE_URL}${endpointMap[category]}`;
|
|
const currentList = [...(state.masterData as any)[category]];
|
|
const filteredList = currentList.filter(a => a.id !== assetId);
|
|
|
|
const response = await fetch(url, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(filteredList)
|
|
});
|
|
|
|
if (response.ok) {
|
|
await loadMasterDataFromDB(); // 전역 상태 갱신
|
|
return true;
|
|
}
|
|
} catch (err) {
|
|
console.error('자산 삭제 실패:', err);
|
|
}
|
|
return false;
|
|
}
|