Merge origin/main into HW_Dashboard and resolve conflicts
This commit is contained in:
@@ -14,18 +14,26 @@ export interface FilterOptions {
|
||||
showDept?: boolean;
|
||||
showLoc?: boolean;
|
||||
showField?: boolean;
|
||||
showType?: boolean;
|
||||
extraHTML?: string;
|
||||
onFilterChange: (filters: any) => void;
|
||||
}
|
||||
|
||||
export function renderFilterBar(container: HTMLElement, options: FilterOptions) {
|
||||
const { keywordLabel = '통합 검색', showCorp = false, showDept = false, showLoc = false, showField = false, extraHTML = '', onFilterChange } = options;
|
||||
const { keywordLabel = '통합 검색', showCorp = false, showDept = false, showLoc = false, showField = false, showType = false, extraHTML = '', onFilterChange } = options;
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="search-item flex-1">
|
||||
<label>${keywordLabel}</label>
|
||||
<input type="text" id="filter-keyword" placeholder="검색어를 입력하세요..." autocomplete="off">
|
||||
</div>
|
||||
${showType ? `
|
||||
<div class="search-item">
|
||||
<label>${ASSET_SCHEMA.ASSET_TYPE.ui}</label>
|
||||
<select id="filter-type">
|
||||
<option value="">전체 유형</option>
|
||||
</select>
|
||||
</div>` : ''}
|
||||
${showField ? `
|
||||
<div class="search-item">
|
||||
<label>${ASSET_SCHEMA.SW_FIELD.ui}</label>
|
||||
@@ -66,7 +74,8 @@ export function renderFilterBar(container: HTMLElement, options: FilterOptions)
|
||||
corp: (container.querySelector('#filter-corp') as HTMLSelectElement)?.value || '',
|
||||
dept: (container.querySelector('#filter-dept') as HTMLSelectElement)?.value || '',
|
||||
loc: (container.querySelector('#filter-loc') as HTMLSelectElement)?.value || '',
|
||||
field: (container.querySelector('#filter-field') as HTMLSelectElement)?.value || ''
|
||||
field: (container.querySelector('#filter-field') as HTMLSelectElement)?.value || '',
|
||||
type: (container.querySelector('#filter-type') as HTMLSelectElement)?.value || ''
|
||||
};
|
||||
onFilterChange(filters);
|
||||
};
|
||||
@@ -76,9 +85,10 @@ export function renderFilterBar(container: HTMLElement, options: FilterOptions)
|
||||
container.querySelector('#filter-dept')?.addEventListener('change', triggerChange);
|
||||
container.querySelector('#filter-loc')?.addEventListener('change', triggerChange);
|
||||
container.querySelector('#filter-field')?.addEventListener('change', triggerChange);
|
||||
container.querySelector('#filter-type')?.addEventListener('change', triggerChange);
|
||||
|
||||
container.querySelector('#btn-reset-filters')?.addEventListener('click', () => {
|
||||
['filter-keyword', 'filter-corp', 'filter-dept', 'filter-loc', 'filter-field'].forEach(id => {
|
||||
['filter-keyword', 'filter-corp', 'filter-dept', 'filter-loc', 'filter-field', 'filter-type'].forEach(id => {
|
||||
const el = container.querySelector(`#${id}`);
|
||||
if (el) (el as any).value = '';
|
||||
});
|
||||
@@ -98,7 +108,8 @@ export function applyCommonFilters(list: any[], filters: any, searchKeys: (keyof
|
||||
const matchDept = !filters.dept || (item[ASSET_SCHEMA.CURRENT_DEPT.key] || item[ASSET_SCHEMA.CURRENT_DEPT.db]) === filters.dept;
|
||||
const matchLoc = !filters.loc || (item[ASSET_SCHEMA.LOCATION.key] || item[ASSET_SCHEMA.LOCATION.db]) === filters.loc;
|
||||
const matchField = !filters.field || (item[ASSET_SCHEMA.SW_FIELD.key] || item[ASSET_SCHEMA.SW_FIELD.db]) === filters.field;
|
||||
const matchType = !filters.type || (item[ASSET_SCHEMA.ASSET_TYPE.key] || item[ASSET_SCHEMA.ASSET_TYPE.db]) === filters.type;
|
||||
|
||||
return matchKeyword && matchCorp && matchDept && matchLoc && matchField;
|
||||
return matchKeyword && matchCorp && matchDept && matchLoc && matchField && matchType;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -484,7 +484,7 @@ export const realServerData = [
|
||||
},
|
||||
{
|
||||
"법인": "삼안",
|
||||
"자산코드": "sa-das-001",
|
||||
"자산코드": "DSS020",
|
||||
"storage유형": "서버",
|
||||
"용도": "",
|
||||
"상세": "Satis01, Satis02 광케이블 연결 (물리연결)",
|
||||
@@ -505,7 +505,7 @@ export const realServerData = [
|
||||
},
|
||||
{
|
||||
"법인": "삼안",
|
||||
"자산코드": "sa-nas-001",
|
||||
"자산코드": "DSS019",
|
||||
"storage유형": "서버",
|
||||
"용도": "인트라넷 백업 스토리지",
|
||||
"상세": "",
|
||||
@@ -526,7 +526,7 @@ export const realServerData = [
|
||||
},
|
||||
{
|
||||
"법인": "삼안",
|
||||
"자산코드": "sa-nas-002",
|
||||
"자산코드": "DSS018",
|
||||
"storage유형": "서버",
|
||||
"용도": "성과품 스토리지",
|
||||
"상세": "매니지먼트 접속 확인 불가 (콘솔 연결 후 페이지 오픈 필요)",
|
||||
@@ -547,7 +547,7 @@ export const realServerData = [
|
||||
},
|
||||
{
|
||||
"법인": "삼안",
|
||||
"자산코드": "sa-nas-003",
|
||||
"자산코드": "DSS017",
|
||||
"storage유형": "서버",
|
||||
"용도": "성과품 백업 스토리지",
|
||||
"상세": "",
|
||||
@@ -568,7 +568,7 @@ export const realServerData = [
|
||||
},
|
||||
{
|
||||
"법인": "한라",
|
||||
"자산코드": "hl-das-001",
|
||||
"자산코드": "DSS016",
|
||||
"storage유형": "서버",
|
||||
"용도": "",
|
||||
"상세": "파일서버 정보 없음(접속 불가)",
|
||||
@@ -589,7 +589,7 @@ export const realServerData = [
|
||||
},
|
||||
{
|
||||
"법인": "한라",
|
||||
"자산코드": "hl-das-002",
|
||||
"자산코드": "DSS015",
|
||||
"storage유형": "서버",
|
||||
"용도": "",
|
||||
"상세": "파일서버 정보 없음(접속 불가)",
|
||||
@@ -611,7 +611,7 @@ export const realServerData = [
|
||||
{
|
||||
"법인": "",
|
||||
"자산코드": "",
|
||||
"storage유형": "NAS",
|
||||
"storage유형": "저장시스템_렉(DAS)",
|
||||
"용도": "GSIM NAS",
|
||||
"상세": "팀 내부 자료 저장 , 정사영상 및 지도 데이터 저장 , Gitea 및 Git 내장 NAS",
|
||||
"위치": "마천사무실",
|
||||
@@ -631,7 +631,7 @@ export const realServerData = [
|
||||
{
|
||||
"법인": "",
|
||||
"자산코드": "",
|
||||
"storage유형": "NAS",
|
||||
"storage유형": "저장시스템_렉(DAS)",
|
||||
"용도": "그래픽스개발팀 데이터 백업 NAS",
|
||||
"상세": "그래픽스 개발팀 데이터 백업용 NAS",
|
||||
"위치": "마천사무실",
|
||||
@@ -1091,7 +1091,7 @@ export const realServerData = [
|
||||
{
|
||||
"법인": "",
|
||||
"자산코드": "1",
|
||||
"storage유형": "NAS",
|
||||
"storage유형": "저장시스템_렉(DAS)",
|
||||
"용도": "NAS 2",
|
||||
"상세": "한라 기업부설연구소 공용 NAS",
|
||||
"위치": "한맥빌딩(MDF 실)",
|
||||
@@ -1107,7 +1107,7 @@ export const realServerData = [
|
||||
{
|
||||
"법인": "",
|
||||
"자산코드": "2",
|
||||
"storage유형": "NAS",
|
||||
"storage유형": "저장시스템_렉(DAS)",
|
||||
"용도": "NAS 1",
|
||||
"상세": "한라 공용 NAS",
|
||||
"위치": "한맥빌딩(MDF 실)",
|
||||
@@ -1123,7 +1123,7 @@ export const realServerData = [
|
||||
{
|
||||
"법인": "",
|
||||
"자산코드": "3",
|
||||
"storage유형": "NAS",
|
||||
"storage유형": "저장시스템_렉(DAS)",
|
||||
"용도": "NAS 4",
|
||||
"상세": "한라 공용 NAS",
|
||||
"위치": "한맥빌딩(MDF 실)",
|
||||
@@ -1139,7 +1139,7 @@ export const realServerData = [
|
||||
{
|
||||
"법인": "",
|
||||
"자산코드": "4",
|
||||
"storage유형": "NAS",
|
||||
"storage유형": "저장시스템_렉(DAS)",
|
||||
"용도": "NAS 5",
|
||||
"상세": "한라 환경플랜트사업부 NAS",
|
||||
"위치": "한맥빌딩(MDF 실)",
|
||||
@@ -1155,7 +1155,7 @@ export const realServerData = [
|
||||
{
|
||||
"법인": "",
|
||||
"자산코드": "5",
|
||||
"storage유형": "NAS",
|
||||
"storage유형": "저장시스템_렉(DAS)",
|
||||
"용도": "NAS 6",
|
||||
"상세": "한라 공용 NAS",
|
||||
"위치": "한맥빌딩(MDF 실)",
|
||||
@@ -1171,7 +1171,7 @@ export const realServerData = [
|
||||
{
|
||||
"법인": "",
|
||||
"자산코드": "6",
|
||||
"storage유형": "NAS",
|
||||
"storage유형": "저장시스템_렉(DAS)",
|
||||
"용도": "NAS7",
|
||||
"상세": "한라 원주바이오 NAS",
|
||||
"위치": "한맥빌딩(MDF 실)",
|
||||
@@ -1187,7 +1187,7 @@ export const realServerData = [
|
||||
{
|
||||
"법인": "",
|
||||
"자산코드": "7",
|
||||
"storage유형": "NAS",
|
||||
"storage유형": "저장시스템_렉(DAS)",
|
||||
"용도": "총괄기획실 NAS",
|
||||
"상세": "총괄기획실 공용 NAS",
|
||||
"위치": "한맥빌딩(MDF 실)",
|
||||
@@ -1203,7 +1203,7 @@ export const realServerData = [
|
||||
{
|
||||
"법인": "",
|
||||
"자산코드": "8",
|
||||
"storage유형": "NAS",
|
||||
"storage유형": "저장시스템_렉(DAS)",
|
||||
"용도": "한맥 NAS 1",
|
||||
"상세": "한맥 공용 NAS",
|
||||
"위치": "한맥빌딩(MDF 실)",
|
||||
@@ -1219,7 +1219,7 @@ export const realServerData = [
|
||||
{
|
||||
"법인": "",
|
||||
"자산코드": "9",
|
||||
"storage유형": "NAS",
|
||||
"storage유형": "저장시스템_렉(DAS)",
|
||||
"용도": "한맥 NAS 2",
|
||||
"상세": "한맥 공용 NAS",
|
||||
"위치": "한맥빌딩(MDF 실)",
|
||||
@@ -1235,7 +1235,7 @@ export const realServerData = [
|
||||
{
|
||||
"법인": "",
|
||||
"자산코드": "10",
|
||||
"storage유형": "NAS",
|
||||
"storage유형": "저장시스템_렉(DAS)",
|
||||
"용도": "한맥 NAS 3",
|
||||
"상세": "한맥 공용 NAS",
|
||||
"위치": "한맥빌딩(MDF 실)",
|
||||
@@ -1251,7 +1251,7 @@ export const realServerData = [
|
||||
{
|
||||
"법인": "",
|
||||
"자산코드": "11",
|
||||
"storage유형": "NAS",
|
||||
"storage유형": "저장시스템_렉(DAS)",
|
||||
"용도": "NAS 13",
|
||||
"상세": "환경플랜트사업",
|
||||
"위치": "한맥빌딩(MDF 실)",
|
||||
@@ -1331,7 +1331,7 @@ export const realServerData = [
|
||||
{
|
||||
"법인": "",
|
||||
"자산코드": "16",
|
||||
"storage유형": "NAS",
|
||||
"storage유형": "저장시스템_렉(DAS)",
|
||||
"용도": "디자인팀1 NAS",
|
||||
"상세": "",
|
||||
"위치": "한맥빌딩(MDF 실)",
|
||||
@@ -1347,7 +1347,7 @@ export const realServerData = [
|
||||
{
|
||||
"법인": "",
|
||||
"자산코드": "17",
|
||||
"storage유형": "NAS",
|
||||
"storage유형": "저장시스템_렉(DAS)",
|
||||
"용도": "디자인팀2 NAS",
|
||||
"상세": "",
|
||||
"위치": "한맥빌딩(MDF 실)",
|
||||
@@ -1507,7 +1507,7 @@ export const realServerData = [
|
||||
{
|
||||
"법인": "",
|
||||
"자산코드": "27",
|
||||
"storage유형": "NAS",
|
||||
"storage유형": "저장시스템_렉(DAS)",
|
||||
"용도": "기술개발센터 NAS",
|
||||
"상세": "",
|
||||
"위치": "한맥빌딩(MDF 실)",
|
||||
@@ -1523,7 +1523,7 @@ export const realServerData = [
|
||||
{
|
||||
"법인": "",
|
||||
"자산코드": "28",
|
||||
"storage유형": "NAS",
|
||||
"storage유형": "저장시스템_렉(DAS)",
|
||||
"용도": "-",
|
||||
"상세": "",
|
||||
"위치": "한맥빌딩(MDF 실)",
|
||||
|
||||
@@ -21,6 +21,9 @@ export const ASSET_SCHEMA = {
|
||||
MANAGER_SUB: { key: 'manager_secondary', db: 'manager_secondary', ui: '담당자(부)' },
|
||||
LOCATION: { key: 'location', db: 'location', ui: '자산위치' },
|
||||
LOC_DETAIL: { key: 'location_detail', db: 'location_detail', ui: '상세위치' },
|
||||
LOCATION_PHOTO: { key: 'location_photo', db: 'location_photo', ui: '배치도이미지' },
|
||||
LOC_X: { key: 'loc_x', db: 'loc_x', ui: '위치X' },
|
||||
LOC_Y: { key: 'loc_y', db: 'loc_y', ui: '위치Y' },
|
||||
MEMO: { key: 'memo', db: 'memo', ui: '메모' },
|
||||
|
||||
// ─── 하드웨어 상세 (Hardware) ───
|
||||
@@ -117,12 +120,12 @@ export const PAGE_DESCRIPTIONS: Record<string, { title: string; description: str
|
||||
description: '측량 및 공간 정보 수집에 사용되는 특수 정밀 장비들의 이력과 상태를 관리합니다.',
|
||||
icon: 'map'
|
||||
},
|
||||
'내부': {
|
||||
'내부SW': {
|
||||
title: '사내 개발 S/W 관리',
|
||||
description: '사내에서 자체 개발하거나 운영 중인 시스템 및 소프트웨어 서비스 현황을 관리합니다.',
|
||||
icon: 'code'
|
||||
},
|
||||
'외부': {
|
||||
'외부SW': {
|
||||
title: '외부 상용 S/W 관리',
|
||||
description: '상용 소프트웨어의 라이선스 보유 현황, 사용자 할당 및 만료 일정을 관리합니다.',
|
||||
icon: 'package'
|
||||
|
||||
@@ -39,6 +39,7 @@ export interface AppState {
|
||||
activeSubTab: string;
|
||||
masterData: MasterAssetData;
|
||||
activeCharts: any[];
|
||||
currentUserRole: 'admin' | 'user';
|
||||
}
|
||||
|
||||
// 초기 상태
|
||||
@@ -46,6 +47,7 @@ export const state: AppState = {
|
||||
activeCategory: 'hw',
|
||||
activeSubTab: '대시보드',
|
||||
activeCharts: [],
|
||||
currentUserRole: 'user',
|
||||
masterData: {
|
||||
users: [],
|
||||
pc: [], server: [], storage: [], network: [],
|
||||
@@ -59,27 +61,20 @@ export const state: AppState = {
|
||||
};
|
||||
|
||||
/**
|
||||
* 신규 14개 테이블 구조에 맞춘 데이터 로드 (Dummy Data)
|
||||
* 통합 V2 스키마에 맞춘 데이터 로드
|
||||
*/
|
||||
export async function loadMasterDataFromDB() {
|
||||
try {
|
||||
state.masterData.pc = dummyPCs || [];
|
||||
state.masterData.server = dummyServers || [];
|
||||
state.masterData.storage = dummyStorages || [];
|
||||
state.masterData.network = dummyEquips || []; // dummy fallback
|
||||
state.masterData.survey = [];
|
||||
state.masterData.pcParts = [];
|
||||
state.masterData.equipment = dummyEquips || [];
|
||||
state.masterData.officeSupplies = [];
|
||||
state.masterData.swInternal = dummyPermSw || [];
|
||||
state.masterData.swExternal = dummySubSw || [];
|
||||
state.masterData.cloud = dummyCloud || [];
|
||||
state.masterData.domain = dummyDomain || [];
|
||||
state.masterData.cost = [];
|
||||
state.masterData.vip = [];
|
||||
state.masterData.swUsers = dummySwUsers || [];
|
||||
state.masterData.logs = dummyLogs || [];
|
||||
state.masterData.users = [];
|
||||
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();
|
||||
|
||||
// 전역 상태 업데이트
|
||||
state.masterData = {
|
||||
...state.masterData,
|
||||
...data
|
||||
};
|
||||
|
||||
// Mapping for backward compatibility
|
||||
state.masterData.equip = state.masterData.equipment;
|
||||
@@ -101,10 +96,10 @@ export async function loadMasterDataFromDB() {
|
||||
state.masterData.sw = [
|
||||
...state.masterData.swInternal,
|
||||
...state.masterData.swExternal,
|
||||
...state.masterData.cloud
|
||||
...(state.masterData.cloud || [])
|
||||
];
|
||||
|
||||
console.log('✅ All dummy data loaded and unified');
|
||||
console.log('✅ V2 Normalized data loaded successfully');
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.warn('⚠️ Dummy 로드 실패:', err);
|
||||
@@ -117,18 +112,21 @@ export function updateState(newState: Partial<AppState>) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 자산 저장 (Dummy API)
|
||||
* 자산 저장 (V2 Normalized API)
|
||||
*/
|
||||
export async function saveAsset(category: string, asset: any) {
|
||||
try {
|
||||
const currentList = [...(state.masterData as any)[category]];
|
||||
const idx = currentList.findIndex(a => a.id === asset.id);
|
||||
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 (idx > -1) currentList[idx] = asset;
|
||||
else currentList.push(asset);
|
||||
|
||||
(state.masterData as any)[category] = currentList;
|
||||
return true;
|
||||
if (response.ok) {
|
||||
await loadMasterDataFromDB(); // 전역 상태 갱신
|
||||
return true;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('자산 저장 실패:', err);
|
||||
}
|
||||
@@ -136,14 +134,17 @@ export async function saveAsset(category: string, asset: any) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 자산 삭제 (Dummy API)
|
||||
* 자산 삭제 (V2 API)
|
||||
*/
|
||||
export async function deleteAsset(category: string, assetId: string) {
|
||||
try {
|
||||
const currentList = [...(state.masterData as any)[category]];
|
||||
const filteredList = currentList.filter(a => a.id !== assetId);
|
||||
(state.masterData as any)[category] = filteredList;
|
||||
return true;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -153,14 +153,8 @@ export function dynamicSort<T>(list: T[], key: string, direction: 'asc' | 'desc'
|
||||
}
|
||||
|
||||
/**
|
||||
* 목록 뷰용 액션 버튼 HTML 생성 (자산추가)
|
||||
* 목록 뷰용 액션 버튼 HTML 생성 (중복 제거를 위해 비워둠)
|
||||
*/
|
||||
export function getActionButtonsHTML(): string {
|
||||
return `
|
||||
<div class="search-actions">
|
||||
<button id="btn-add-asset" class="btn btn-primary">
|
||||
<i data-lucide="plus"></i> 자산추가
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
return '';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user