refactor: integrate software assets into unified schema and optimize backend API

This commit is contained in:
2026-04-30 09:34:29 +09:00
parent 68cb5f9767
commit 2af79cdad3
6 changed files with 363 additions and 451 deletions

View File

@@ -48,7 +48,7 @@ export const state: AppState = {
};
/**
* 전용 API 엔드포인트들로부터 데이터 로드
* 전용 API 엔드포인트들로부터 데이터 로드 (Modernized Paths)
*/
export async function loadMasterDataFromDB() {
try {
@@ -58,12 +58,12 @@ export async function loadMasterDataFromDB() {
{ key: 'storage', url: `http://${location.hostname}:3000/api/storage` },
{ key: 'equip', url: `http://${location.hostname}:3000/api/equip` },
{ key: 'mobile', url: `http://${location.hostname}:3000/api/mobile` },
{ key: 'subSw', url: `http://${location.hostname}:3000/api/sw/sub` },
{ key: 'permSw', url: `http://${location.hostname}:3000/api/sw/perm` },
{ key: 'cloud', url: `http://${location.hostname}:3000/api/cloud` },
{ key: 'domain', url: `http://${location.hostname}:3000/api/ops/domain` },
{ key: 'swUsers', url: `http://${location.hostname}:3000/api/sw-users` },
{ key: 'logs', url: `http://${location.hostname}:3000/api/logs` }
{ key: 'subSw', url: `http://${location.hostname}:3000/api/asset/software/subscription` },
{ key: 'permSw', url: `http://${location.hostname}:3000/api/asset/software/perpetual` },
{ key: 'cloud', url: `http://${location.hostname}:3000/api/asset/cloud` },
{ key: 'domain', url: `http://${location.hostname}:3000/api/asset/domain` },
{ key: 'swUsers', url: `http://${location.hostname}:3000/api/asset/software/assignment` },
{ key: 'logs', url: `http://${location.hostname}:3000/api/asset/history` }
];
const results = await Promise.all(endpoints.map(e => fetch(e.url)));
@@ -79,13 +79,15 @@ export async function loadMasterDataFromDB() {
if (results[i].ok) {
const data = await results[i].json();
const key = endpoints[i].key;
console.log(`📡 Loaded ${key}: ${Array.isArray(data) ? data.length : 'not an array'} items`);
if (['pc', 'server', 'storage', 'equip', 'mobile'].includes(key)) {
// 하드웨어 데이터는 자동 재분류 로직 통과
(data as HardwareAsset[]).forEach(asset => saveHardwareAsset(asset));
(Array.isArray(data) ? data : []).forEach(asset => saveHardwareAsset(asset));
} else {
(state.masterData as any)[key] = data || [];
(state.masterData as any)[key] = Array.isArray(data) ? data : [];
}
} else {
console.error(`❌ Failed to load ${endpoints[i].key}: ${results[i].status} ${results[i].statusText}`);
}
}
@@ -194,25 +196,37 @@ export function deleteHardwareAsset(assetId: string) {
}
/**
* 소프트웨어 자산 저장 (API 연동)
* 소프트웨어 자산 저장 (API 연동 - 개선된 일괄 저장 경로)
*/
export async function saveSoftwareAsset(asset: SoftwareAsset) {
try {
const response = await fetch(`http://${location.hostname}:3000/api/software/save`, {
const type = asset.type;
let url = '';
let categoryKey: keyof MasterAssetData = 'subSw';
if (type === '구독SW') {
url = `http://${location.hostname}:3000/api/asset/software/subscription/batch`;
categoryKey = 'subSw';
} else if (type === '영구SW') {
url = `http://${location.hostname}:3000/api/asset/software/perpetual/batch`;
categoryKey = 'permSw';
} else {
url = `http://${location.hostname}:3000/api/asset/cloud/batch`;
categoryKey = 'cloud';
}
const arr = state.masterData[categoryKey] as SoftwareAsset[];
const idx = arr.findIndex(a => a.id === asset.id);
if (idx > -1) arr[idx] = asset;
else arr.push(asset);
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(asset)
body: JSON.stringify(arr)
});
if (response.ok) {
// 로컬 상태 업데이트
const key = asset.type === '구독SW' ? 'subSw' : (asset.type === '영구SW' ? 'permSw' : 'cloud');
const arr = state.masterData[key] as SoftwareAsset[];
const idx = arr.findIndex(a => a.id === asset.id);
if (idx > -1) arr[idx] = asset;
else arr.push(asset);
// 통합 sw 배열 동기화
state.masterData.sw = [...state.masterData.subSw, ...state.masterData.permSw, ...state.masterData.cloud];
return true;
}
@@ -223,21 +237,24 @@ export async function saveSoftwareAsset(asset: SoftwareAsset) {
}
/**
* 소프트웨어 자산 삭제 (API 연동)
* 소프트웨어 자산 삭제 (API 연동 - 개선된 일괄 저장 경로)
*/
export async function deleteSoftwareAsset(type: string, id: string) {
try {
const response = await fetch(`http://${location.hostname}:3000/api/asset/${type}/${id}`, {
method: 'DELETE'
const key = type === '구독SW' ? 'subSw' : (type === '영구SW' ? 'permSw' : 'cloud');
const path = type === '구독SW' ? 'subscription' : (type === '영구SW' ? 'perpetual' : 'cloud');
const arr = state.masterData[key] as SoftwareAsset[];
const filtered = arr.filter(a => a.id !== id);
const response = await fetch(`http://${location.hostname}:3000/api/asset/software/${path}/batch`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(filtered)
});
if (response.ok) {
const key = type === '구독SW' ? 'subSw' : (type === '영구SW' ? 'permSw' : 'cloud');
const arr = state.masterData[key] as SoftwareAsset[];
const idx = arr.findIndex(a => a.id === id);
if (idx > -1) arr.splice(idx, 1);
// 통합 sw 배열 동기화
(state.masterData as any)[key] = filtered;
state.masterData.sw = [...state.masterData.subSw, ...state.masterData.permSw, ...state.masterData.cloud];
return true;
}

View File

@@ -30,7 +30,13 @@ export function formatInline(value: any): string {
* 날짜 문자열 포맷팅 (YYYY.MM.DD -> YYYY-MM-DD)
*/
export function normalizeDate(dateStr: string): string {
return (dateStr || '').replace(/\./g, '-').trim();
if (!dateStr) return '';
let str = String(dateStr).replace(/\./g, '-').trim();
// YYYYMM 형식 처리 (6자리 숫자)
if (/^\d{6}$/.test(str)) {
return `${str.substring(0, 4)}-${str.substring(4, 6)}`;
}
return str;
}
/**