feat: 소프트웨어 자산 관리 기능 고도화 및 대시보드 누적 비용 분석 기능 추가

This commit is contained in:
2026-04-23 19:47:07 +09:00
parent d125de1902
commit 9fcecd4bf5
13 changed files with 649 additions and 324 deletions

View File

@@ -92,6 +92,7 @@ export interface HardwareLog {
date: string;
details: string;
user: string;
cost?: number;
}
export interface MasterAssetData {
@@ -119,7 +120,7 @@ const EQUIP_HEADERS = ['구매법인', '비품유형', '자산코드', '명칭',
const MOBILE_HEADERS = ['구매법인', '자산코드', '명칭', '위치', '관리자', '기기유형', 'OS', '구매연월', '금액', '납품업체', '품의서명', '비고'];
const SUB_SW_HEADERS = ['ID', '분야', '법인', '부서', '제품명', '구매연월', '만료일', '라이선스유형', '금액', '수량', '계정명', '납품업체', '비고'];
const PERM_SW_HEADERS = ['ID', '분야', '법인', '부서', '제품명', '구매연월', '라이선스키', '금액', '수량', '계정명', '납품업체', '비고'];
const PERM_SW_HEADERS = ['ID', '분야', '법인', '부서', '제품명', '구매연월', '만료일', '라이선스키', '금액', '수량', '계정명', '납품업체', '비고'];
const CLOUD_HEADERS = ['ID', '플랫폼명', '법인', '부서', '사용용도(제품명)', '계정명', '결제수단', '결제일', '연결카드번호', '당월청구액', '비고'];
export function downloadTemplate() {
@@ -157,7 +158,7 @@ export function exportToExcel(masterData: MasterAssetData) {
{ tab: '전산비품', list: masterData.equip, headers: EQUIP_HEADERS, map: (a: any) => [a., a., a., a., a., a., a.IP주소, a.MACaddress, a.HW사양, a.OS, a.||a., a., a., a., a.] },
{ tab: '모바일기기', list: masterData.mobile, headers: MOBILE_HEADERS, map: (a: any) => [a., a., a., a., a., a.type, a.OS, a.||a., a., a., a., a.] },
{ tab: '구독SW', list: masterData.subSw, headers: SUB_SW_HEADERS, map: (a: any) => [a.id, a., a., a., a., a.||a., a., a., a., a., a., a., a.] },
{ tab: '영구SW', list: masterData.permSw, headers: PERM_SW_HEADERS, map: (a: any) => [a.id, a., a., a., a., a.||a., a., a., a., a., a., a.] }
{ tab: '영구SW', list: masterData.permSw, headers: PERM_SW_HEADERS, map: (a: any) => [a.id, a., a., a., a., a.||a., a., a., a., a., a., a., a.] }
];
exportMap.forEach(m => {
@@ -189,7 +190,7 @@ export async function parseExcel(file: File): Promise<MasterAssetData> {
} else if (sheetName === '구독SW') {
rows.forEach(r => data.subSw.push({ id: r['ID']||Math.random().toString(36).substring(2, 9), type: '구독SW', 분야: r['분야']||'', 법인: r['법인']||'', 부서: r['부서']||'', 제품명: r['제품명']||'', 구매연월: r['구매연월']||r['구매일']||'', 만료일: r['만료일']||'', 라이선스유형: r['라이선스유형']||'', 금액: r['금액']||'', 수량: parseInt(r['수량']||'1'), 계정명: r['계정명']||'', 납품업체: r['납품업체']||'', 비고: r['비고']||'' }));
} else if (sheetName === '영구SW') {
rows.forEach(r => data.permSw.push({ id: r['ID']||Math.random().toString(36).substring(2, 9), type: '영구SW', 분야: r['분야']||'', 법인: r['법인']||'', 부서: r['부서']||'', 제품명: r['제품명']||'', 구매연월: r['구매연월']||r['구매일']||'', 라이선스키: r['라이선스키']||'', 금액: r['금액']||'', 수량: parseInt(r['수량']||'1'), 계정명: r['계정명']||'', 납품업체: r['납품업체']||'', 비고: r['비고']||'' }));
rows.forEach(r => data.permSw.push({ id: r['ID']||Math.random().toString(36).substring(2, 9), type: '영구SW', 분야: r['분야']||'', 법인: r['법인']||'', 부서: r['부서']||'', 제품명: r['제품명']||'', 구매연월: r['구매연월']||r['구매일']||'', 만료일: r['만료일']||'', 라이선스키: r['라이선스키']||'', 금액: r['금액']||'', 수량: parseInt(r['수량']||'1'), 계정명: r['계정명']||'', 납품업체: r['납품업체']||'', 비고: r['비고']||'' }));
}
});
resolve(data);

View File

@@ -51,16 +51,16 @@ export const state: AppState = {
export async function loadMasterDataFromDB() {
try {
const endpoints = [
{ key: 'pc', url: 'http://172.16.40.100:3000/api/pc' },
{ key: 'server', url: 'http://172.16.40.100:3000/api/server' },
{ key: 'storage', url: 'http://172.16.40.100:3000/api/storage' },
{ key: 'equip', url: 'http://172.16.40.100:3000/api/equip' },
{ key: 'mobile', url: 'http://172.16.40.100:3000/api/mobile' },
{ key: 'subSw', url: 'http://172.16.40.100:3000/api/sw/sub' },
{ key: 'permSw', url: 'http://172.16.40.100:3000/api/sw/perm' },
{ key: 'cloud', url: 'http://172.16.40.100:3000/api/cloud' },
{ key: 'swUsers', url: 'http://172.16.40.100:3000/api/sw-users' },
{ key: 'logs', url: 'http://172.16.40.100:3000/api/logs' }
{ key: 'pc', url: `http://${location.hostname}:3000/api/pc` },
{ key: 'server', url: `http://${location.hostname}:3000/api/server` },
{ 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: 'swUsers', url: `http://${location.hostname}:3000/api/sw-users` },
{ key: 'logs', url: `http://${location.hostname}:3000/api/logs` }
];
const results = await Promise.all(endpoints.map(e => fetch(e.url)));
@@ -195,7 +195,7 @@ export function deleteHardwareAsset(assetId: string) {
*/
export async function saveSoftwareAsset(asset: SoftwareAsset) {
try {
const response = await fetch('http://172.16.40.100:3000/api/software/save', {
const response = await fetch(`http://${location.hostname}:3000/api/software/save`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(asset)
@@ -224,7 +224,7 @@ export async function saveSoftwareAsset(asset: SoftwareAsset) {
*/
export async function deleteSoftwareAsset(type: string, id: string) {
try {
const response = await fetch(`http://172.16.40.100:3000/api/asset/${type}/${id}`, {
const response = await fetch(`http://${location.hostname}:3000/api/asset/${type}/${id}`, {
method: 'DELETE'
});