From b9d28736e20a77af2a138f189074017febf10a2b Mon Sep 17 00:00:00 2001 From: Taehoon Date: Mon, 15 Jun 2026 11:47:46 +0900 Subject: [PATCH 01/14] docs: add work log for 2026-06-15 and update DB deletion policy in README --- README.md | 3 + WORK_LOG_20260615.md | 30 +++++ asset_pc (2026.06.15).xlsx | Bin 0 -> 204865 bytes scratch/analyze_codes.cjs | 24 ++++ scratch/check_backup_excel.cjs | 11 ++ scratch/check_codes.cjs | 24 ++++ scratch/check_public_pcs.cjs | 40 +++++++ scratch/compare_and_cleanup.cjs | 77 ++++++++++++ scratch/debug_public.cjs | 25 ++++ scratch/deep_audit.cjs | 69 +++++++++++ scratch/extract_pc_failures.cjs | 61 ++++++++++ scratch/find_public.cjs | 29 +++++ scratch/fix_asset_types_final.cjs | 47 ++++++++ scratch/import_pc_assets.cjs | 122 +++++++++++++++++++ scratch/import_pc_assets_v2.cjs | 164 ++++++++++++++++++++++++++ scratch/import_system_users.cjs | 61 ++++++++++ scratch/peek_asset_pc.cjs | 7 ++ scratch/peek_excel.cjs | 6 + scratch/raw_check.cjs | 18 +++ scratch/rebuild_asset_codes_final.cjs | 85 +++++++++++++ scratch/reexamine_full.cjs | 85 +++++++++++++ scratch/restore_and_merge_users.cjs | 92 +++++++++++++++ scratch/update_dept_saman.cjs | 32 +++++ system_User (20260615).xlsx | Bin 0 -> 47930 bytes ~$backupDB_20260602.xlsx | Bin 0 -> 165 bytes ~$system_User (20260615).xlsx | Bin 0 -> 165 bytes 26 files changed, 1112 insertions(+) create mode 100644 WORK_LOG_20260615.md create mode 100644 asset_pc (2026.06.15).xlsx create mode 100644 scratch/analyze_codes.cjs create mode 100644 scratch/check_backup_excel.cjs create mode 100644 scratch/check_codes.cjs create mode 100644 scratch/check_public_pcs.cjs create mode 100644 scratch/compare_and_cleanup.cjs create mode 100644 scratch/debug_public.cjs create mode 100644 scratch/deep_audit.cjs create mode 100644 scratch/extract_pc_failures.cjs create mode 100644 scratch/find_public.cjs create mode 100644 scratch/fix_asset_types_final.cjs create mode 100644 scratch/import_pc_assets.cjs create mode 100644 scratch/import_pc_assets_v2.cjs create mode 100644 scratch/import_system_users.cjs create mode 100644 scratch/peek_asset_pc.cjs create mode 100644 scratch/peek_excel.cjs create mode 100644 scratch/raw_check.cjs create mode 100644 scratch/rebuild_asset_codes_final.cjs create mode 100644 scratch/reexamine_full.cjs create mode 100644 scratch/restore_and_merge_users.cjs create mode 100644 scratch/update_dept_saman.cjs create mode 100644 system_User (20260615).xlsx create mode 100644 ~$backupDB_20260602.xlsx create mode 100644 ~$system_User (20260615).xlsx diff --git a/README.md b/README.md index a858ee5..21a71f3 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,9 @@ - 기존 동작 방식과 성능을 기준(Baseline)으로 삼고, 수정 후에도 **기존의 모든 기능이 무결하게 유지되는지 반드시 테스트하여 입증**한다. - 검증 결과를 바탕으로 "무엇을, 왜, 어떻게" 바꿀지 상세 보고 후, 사용자로부터 **'진행시켜'** 승인을 얻은 뒤에만 집행한다. 4. **선보고 후승인**: 모든 기능 수정 및 코드 변경 전에는 예상 방안을 먼저 보고하고 승인 절차를 거친다. +5. **DB 삭제 및 초기화 절대 엄금 (Strict DB Deletion Policy)**: + - 어떠한 경우에도 `DELETE`, `DROP`, `TRUNCATE` 등 데이터를 삭제하거나 테이블을 초기화하는 작업은 사전에 사용자에게 상세 사유를 보고하고 **명시적 승인**을 얻은 후에만 시행한다. + - 기존 데이터의 가치를 최우선으로 하며, 작업 전 백업 여부를 반드시 확인한다. --- diff --git a/WORK_LOG_20260615.md b/WORK_LOG_20260615.md new file mode 100644 index 0000000..38b2d23 --- /dev/null +++ b/WORK_LOG_20260615.md @@ -0,0 +1,30 @@ +# 📝 작업 보고서 (2026-06-15) + +## 1. 서버 및 개발 환경 설정 +- **백엔드 서버 구동**: 3000번 포트(DB 서버) 정상 구동 완료. +- **프론트엔드 서버 구동**: 8080번 포트 정상 구동 완료. +- **브랜치 전환**: \`db_setting\` 브랜치로 전환 및 최신 코드 Pull 완료. + +## 2. 데이터베이스 정제 및 보강 (Surgical Update) +- **사용자 정보(system_users) 업데이트**: + - 엑셀(\`system_User (20260615).xlsx\`) 기반 987건 신규 입력. + - 기존 백업 데이터(212건)와 병합하여 총 1,199건의 사용자 DB 구축. +- **PC 자산(asset_pc) 데이터 입력**: + - 엑셀(\`asset_pc (2026.06.15).xlsx\`) 기반 1,030건 입력 완료. + - **용량 정제**: 괄호 제거 및 4자리 GB 단위를 TB로 자동 변환 (예: 1863GB -> 1.86TB). + - **구매일 보강**: 연도 데이터에 월/일 추가 (\`YYYY-12-01\` 형식으로 통일). + - **자산번호 재매핑**: \`PC-YYYY12-NNNN\` 형식으로 전수 재부여 및 기존 번호와의 연속성 유지. + +## 3. 부서 및 자산 유형 정상화 +- **부서명 통합**: '총괄기획실', '기술개발센터', '한맥', '장헌', 'PTC', '현타' 등을 제외한 1,045건의 부서명을 **'삼안'**으로 일괄 통합. +- **자산 유형 교정 (핵심)**: + - 엑셀의 오기입과 상관없이 **사번(emp_no) 존재 여부**를 기준으로 자산 유형을 재분류. + - 사번이 있는 991건 -> **개인PC**로 정상화. + - 사번이 없는 39건 -> **공용PC**로 지정 및 사용자명 '공용'으로 정리. + +## 4. 운영 규칙 업데이트 +- **README.md 수정**: 'DB 삭제 및 초기화 절대 엄금 (Rule 5)' 항목 추가. + +--- +**보고자**: Gemini CLI +**상태**: 소스 코드 수정 없음, 데이터베이스 정제 완료. diff --git a/asset_pc (2026.06.15).xlsx b/asset_pc (2026.06.15).xlsx new file mode 100644 index 0000000000000000000000000000000000000000..e443941bf8008f263ff7f519397f8489e07dcb0a GIT binary patch literal 204865 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 literal 0 HcmV?d00001 diff --git a/scratch/analyze_codes.cjs b/scratch/analyze_codes.cjs new file mode 100644 index 0000000..642b56f --- /dev/null +++ b/scratch/analyze_codes.cjs @@ -0,0 +1,24 @@ +const mysql = require('mysql2/promise'); +require('dotenv').config(); + +async function analyzeCodes() { + const connection = await mysql.createConnection({ + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASS, + database: process.env.DB_NAME, + port: parseInt(process.env.DB_PORT || '3306') + }); + + // 새 자산들의 연도 분포 확인 + const [years] = await connection.query('SELECT DISTINCT purchase_date FROM asset_core WHERE id LIKE "PC_20260615_%"'); + console.log('New assets years:', years.map(y => y.purchase_date)); + + // 기존 자산 코드 패턴 확인 + const [existing] = await connection.query('SELECT asset_code FROM asset_core WHERE asset_code LIKE "PC-%" LIMIT 5'); + console.log('Existing code sample:', existing); + + await connection.end(); +} + +analyzeCodes().catch(console.error); diff --git a/scratch/check_backup_excel.cjs b/scratch/check_backup_excel.cjs new file mode 100644 index 0000000..039b179 --- /dev/null +++ b/scratch/check_backup_excel.cjs @@ -0,0 +1,11 @@ +const XLSX = require('xlsx'); +const workbook = XLSX.readFile('backupDB_20260602.xlsx'); +console.log('Sheet Names:', workbook.SheetNames); +if (workbook.SheetNames.includes('system_users')) { + const sheet = workbook.Sheets['system_users']; + const data = XLSX.utils.sheet_to_json(sheet); + console.log('system_users found! Count:', data.length); + console.log('Sample:', data.slice(0, 2)); +} else { + console.log('system_users sheet not found in backupDB_20260602.xlsx'); +} diff --git a/scratch/check_codes.cjs b/scratch/check_codes.cjs new file mode 100644 index 0000000..b910768 --- /dev/null +++ b/scratch/check_codes.cjs @@ -0,0 +1,24 @@ +const mysql = require('mysql2/promise'); +require('dotenv').config(); + +async function checkCodes() { + const connection = await mysql.createConnection({ + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASS, + database: process.env.DB_NAME, + port: parseInt(process.env.DB_PORT || '3306') + }); + + console.log('--- Asset Codes Sample ---'); + const [rows] = await connection.query('SELECT id, asset_code, purchase_date FROM asset_core WHERE id LIKE "PC_20260615_%" LIMIT 10'); + console.log(rows); + + console.log('\n--- Other Asset Codes Sample ---'); + const [rows2] = await connection.query('SELECT id, asset_code, purchase_date FROM asset_core WHERE id NOT LIKE "PC_20260615_%" AND asset_code IS NOT NULL LIMIT 5'); + console.log(rows2); + + await connection.end(); +} + +checkCodes().catch(console.error); diff --git a/scratch/check_public_pcs.cjs b/scratch/check_public_pcs.cjs new file mode 100644 index 0000000..37e6af0 --- /dev/null +++ b/scratch/check_public_pcs.cjs @@ -0,0 +1,40 @@ +const mysql = require('mysql2/promise'); +require('dotenv').config(); + +async function checkPublicPCs() { + const connection = await mysql.createConnection({ + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASS, + database: process.env.DB_NAME, + port: parseInt(process.env.DB_PORT || '3306') + }); + + console.log('🔍 공용 PC(Public PC)로 추정되는 자산 조회 중...'); + + // 사번이 없거나, 사용자명에 '공용'이 포함된 데이터 조회 + const [rows] = await connection.query(` + SELECT id, asset_code, user_current, emp_no, current_dept, asset_type + FROM asset_core + WHERE (emp_no IS NULL OR emp_no = '' OR user_current LIKE '%공용%') + AND id LIKE 'PC_20260615_%' + `); + + console.log(`📊 발견된 공용 PC 후보: ${rows.length}건`); + + if (rows.length > 0) { + console.table(rows.slice(0, 20)); // 상위 20개 샘플 출력 + + // 요약 통계 + const summary = { + only_no_emp: rows.filter(r => (!r.emp_no) && !r.user_current.includes('공용')).length, + only_public_name: rows.filter(r => r.emp_no && r.user_current.includes('공용')).length, + both: rows.filter(r => (!r.emp_no) && r.user_current.includes('공용')).length + }; + console.log('\n📈 요약 통계:', summary); + } + + await connection.end(); +} + +checkPublicPCs().catch(console.error); diff --git a/scratch/compare_and_cleanup.cjs b/scratch/compare_and_cleanup.cjs new file mode 100644 index 0000000..deee2af --- /dev/null +++ b/scratch/compare_and_cleanup.cjs @@ -0,0 +1,77 @@ +const mysql = require('mysql2/promise'); +require('dotenv').config(); + +async function updateAndCompare() { + const connection = await mysql.createConnection({ + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASS, + database: process.env.DB_NAME, + port: parseInt(process.env.DB_PORT || '3306') + }); + + console.log('🚀 [Step 1 & 2] "undefined" 사번 및 빈 사용자명 정리 중...'); + const [updateResult] = await connection.query(` + UPDATE asset_core + SET user_current = '공용', emp_no = NULL + WHERE id LIKE "PC_20260615_%" AND (emp_no = 'undefined' OR emp_no IS NULL OR emp_no = '') + `); + console.log(`✅ 업데이트 완료: ${updateResult.affectedRows}건`); + + console.log('\n🔍 [Step 3] 엑셀 데이터와 DB asset_type 비교 분석 중...'); + const XLSX = require('xlsx'); + const workbook = XLSX.readFile('asset_pc (2026.06.15).xlsx'); + const sheet = workbook.Sheets[workbook.SheetNames[0]]; + const excelData = XLSX.utils.sheet_to_json(sheet); + + // DB 데이터 로드 + const [dbRows] = await connection.query('SELECT id, asset_type, user_current, emp_no FROM asset_core WHERE id LIKE "PC_20260615_%"'); + const dbMap = new Map(); + dbRows.forEach(r => dbMap.set(r.id, r)); + + const mismatches = []; + const publicButExcelPersonal = []; + + for (let i = 0; i < excelData.length; i++) { + const excelRow = excelData[i]; + const assetId = `PC_20260615_${String(i + 1).padStart(4, '0')}`; + const dbRow = dbMap.get(assetId); + + if (!dbRow) continue; + + const excelType = excelRow.asset_type || '개인PC'; + + // 1. 단순 타입 불일치 체크 + if (dbRow.asset_type !== excelType) { + mismatches.push({ + id: assetId, + excel_type: excelType, + db_type: dbRow.asset_type, + user: dbRow.user_current + }); + } + + // 2. 엑셀은 '개인PC'인데 데이터는 공용(사번없음)인 경우 탐색 + if (excelType === '개인PC' && (!dbRow.emp_no || dbRow.user_current === '공용')) { + publicButExcelPersonal.push({ + id: assetId, + excel_user: excelRow.user_current, + excel_dept: excelRow.current_dept, + db_user: dbRow.user_current + }); + } + } + + console.log(`\n📊 분석 결과:`); + console.log(`- 엑셀과 DB의 asset_type 불일치: ${mismatches.length}건`); + console.log(`- 엑셀은 '개인PC'이나 사번이 없어 '공용'으로 잡힌 항목: ${publicButExcelPersonal.length}건`); + + if (publicButExcelPersonal.length > 0) { + console.log('\n⚠️ 엑셀은 개인PC이나 데이터가 미비한 항목 (상위 10개):'); + console.table(publicButExcelPersonal.slice(0, 10)); + } + + await connection.end(); +} + +updateAndCompare().catch(console.error); diff --git a/scratch/debug_public.cjs b/scratch/debug_public.cjs new file mode 100644 index 0000000..24c3762 --- /dev/null +++ b/scratch/debug_public.cjs @@ -0,0 +1,25 @@ +const mysql = require('mysql2/promise'); +require('dotenv').config(); + +async function debugPublic() { + const connection = await mysql.createConnection({ + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASS, + database: process.env.DB_NAME, + port: parseInt(process.env.DB_PORT || '3306') + }); + + const [rows] = await connection.query(` + SELECT user_current, emp_no, COUNT(*) as count + FROM asset_core + WHERE id LIKE "PC_20260615_%" + GROUP BY user_current, emp_no + HAVING emp_no IS NULL OR emp_no = '' OR user_current LIKE '%공용%' OR user_current = '' + `); + + console.table(rows); + await connection.end(); +} + +debugPublic().catch(console.error); diff --git a/scratch/deep_audit.cjs b/scratch/deep_audit.cjs new file mode 100644 index 0000000..8f45221 --- /dev/null +++ b/scratch/deep_audit.cjs @@ -0,0 +1,69 @@ +const XLSX = require('xlsx'); +const mysql = require('mysql2/promise'); +require('dotenv').config(); + +async function deepAudit() { + const workbook = XLSX.readFile('asset_pc (2026.06.15).xlsx'); + const sheet = workbook.Sheets[workbook.SheetNames[0]]; + const excelData = XLSX.utils.sheet_to_json(sheet); + + console.log('📊 [Excel Audit] Total Rows:', excelData.length); + + // 1. 엑셀 내 asset_type 종류 확인 + const excelTypes = new Set(); + excelData.forEach(r => excelTypes.add(r.asset_type)); + console.log('Excel Asset Types:', Array.from(excelTypes)); + + // 2. '공용' 키워드가 들어간 모든 행 추출 + const publicKeywords = ['공용', '공통', '테스트', 'TEST']; + const potentialPublicInExcel = excelData.filter(r => { + const name = String(r.user_current || ''); + const type = String(r.asset_type || ''); + const memo = String(r.memo || ''); + return publicKeywords.some(k => name.includes(k) || type.includes(k) || memo.includes(k)) || !r.emp_no; + }); + + console.log(`\n🔍 [Potential Public/Issue Rows in Excel]: ${potentialPublicInExcel.length}건`); + console.table(potentialPublicInExcel.slice(0, 30).map(r => ({ + emp_no: r.emp_no, + user: r.user_current, + dept: r.current_dept, + type: r.asset_type, + memo: r.memo + }))); + + // 3. DB와 대조 (특히 엑셀엔 사번이 있는데 DB엔 공용으로 된 게 있는지) + const connection = await mysql.createConnection({ + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASS, + database: process.env.DB_NAME, + port: parseInt(process.env.DB_PORT || '3306') + }); + + const [dbRows] = await connection.query('SELECT id, user_current, emp_no, asset_type FROM asset_core WHERE id LIKE "PC_20260615_%"'); + + // 엑셀은 개인PC인데 DB는 공용인 경우 (또는 그 반대) + const issues = []; + for (let i = 0; i < excelData.length; i++) { + const ex = excelData[i]; + const id = `PC_20260615_${String(i + 1).padStart(4, '0')}`; + const db = dbRows.find(r => r.id === id); + + if (!db) continue; + + const isExcelPublic = !ex.emp_no || String(ex.user_current).includes('공용'); + const isDbPublic = !db.emp_no || String(db.user_current).includes('공용'); + + if (isExcelPublic !== isDbPublic) { + issues.push({ id, excel_user: ex.user_current, db_user: db.user_current, excel_emp: ex.emp_no, db_emp: db.emp_no }); + } + } + + console.log(`\n⚠️ [Consistency Issues]: ${issues.length}건`); + if (issues.length > 0) console.table(issues); + + await connection.end(); +} + +deepAudit().catch(console.error); diff --git a/scratch/extract_pc_failures.cjs b/scratch/extract_pc_failures.cjs new file mode 100644 index 0000000..2cafac9 --- /dev/null +++ b/scratch/extract_pc_failures.cjs @@ -0,0 +1,61 @@ +const XLSX = require('xlsx'); +const mysql = require('mysql2/promise'); +const dotenv = require('dotenv'); +const path = require('path'); + +dotenv.config({ path: path.join(__dirname, '../.env') }); + +const { DB_HOST, DB_USER, DB_PASS, DB_NAME, DB_PORT } = process.env; + +async function extractFailures() { + const connection = await mysql.createConnection({ + host: DB_HOST, + user: DB_USER, + password: DB_PASS, + database: DB_NAME, + port: parseInt(DB_PORT || '3306') + }); + + console.log('🔍 실패 데이터 추출 중...'); + + const workbook = XLSX.readFile('asset_pc (2026.06.15).xlsx'); + const sheet = workbook.Sheets[workbook.SheetNames[0]]; + const rawData = XLSX.utils.sheet_to_json(sheet); + + // 현재 DB에 존재하는 모든 asset_core ID 조회 + const [existingRows] = await connection.query('SELECT id FROM asset_core'); + const existingIds = new Set(existingRows.map(r => r.id)); + + const failures = []; + + for (let i = 0; i < rawData.length; i++) { + const row = rawData[i]; + const assetId = `PC_20260615_${String(i + 1).padStart(4, '0')}`; + + // DB에 해당 ID가 없는 경우 = 실패(충돌 등의 이유로 입력되지 않음) 또는 스킵된 데이터 + // 하지만 이전 로그에서 'Duplicate entry'로 에러가 났던 항목들을 찾는 것이 목적 + // 로직상 ID 생성 규칙에 따라 해당 ID가 DB에 없으면 입력에 실패한 행임 + if (!existingIds.has(assetId)) { + failures.push({ + excel_row: i + 2, + generated_id: assetId, + ...row + }); + } + } + + if (failures.length > 0) { + const newWb = XLSX.utils.book_new(); + const newWs = XLSX.utils.json_to_sheet(failures); + XLSX.utils.book_append_sheet(newWb, newWs, 'Failures'); + const fileName = 'asset_pc_failures_20260615.xlsx'; + XLSX.writeFile(newWb, fileName); + console.log(`✅ 추출 완료: ${failures.length}건의 실패 데이터를 ${fileName}에 저장했습니다.`); + } else { + console.log('입력되지 않은 데이터가 없습니다.'); + } + + await connection.end(); +} + +extractFailures().catch(console.error); diff --git a/scratch/find_public.cjs b/scratch/find_public.cjs new file mode 100644 index 0000000..df02962 --- /dev/null +++ b/scratch/find_public.cjs @@ -0,0 +1,29 @@ +const mysql = require('mysql2/promise'); +require('dotenv').config(); + +async function findPotentialPublic() { + const connection = await mysql.createConnection({ + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASS, + database: process.env.DB_NAME, + port: parseInt(process.env.DB_PORT || '3306') + }); + + console.log('--- Searching for rows with no emp_no or "공용" in user_current ---'); + + // 사번이 'undefined', 'null', 빈값, 또는 사용자명에 '공용'이 들어간 데이터 + const [rows] = await connection.query(` + SELECT id, user_current, emp_no + FROM asset_core + WHERE id LIKE "PC_20260615_%" + AND (emp_no IS NULL OR emp_no = '' OR emp_no = 'undefined' OR user_current LIKE '%공용%') + `); + + console.log('Count:', rows.length); + if (rows.length > 0) console.table(rows); + + await connection.end(); +} + +findPotentialPublic().catch(console.error); diff --git a/scratch/fix_asset_types_final.cjs b/scratch/fix_asset_types_final.cjs new file mode 100644 index 0000000..dff0802 --- /dev/null +++ b/scratch/fix_asset_types_final.cjs @@ -0,0 +1,47 @@ +const mysql = require('mysql2/promise'); +require('dotenv').config(); + +async function fixAssetTypes() { + const connection = await mysql.createConnection({ + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASS, + database: process.env.DB_NAME, + port: parseInt(process.env.DB_PORT || '3306') + }); + + console.log('🚀 [데이터 정상화] 사번 기준 자산 유형 재설정 시작...'); + + // 1. 사번이 있는 모든 신규 자산을 '개인PC'로 강제 전환 + const [personalResult] = await connection.query(` + UPDATE asset_core + SET asset_type = '개인PC' + WHERE id LIKE "PC_20260615_%" + AND emp_no IS NOT NULL + AND emp_no != '' + `); + console.log(`✅ 개인PC 정상화 완료: ${personalResult.affectedRows}건 (사번 존재 항목)`); + + // 2. 사번이 없는 모든 신규 자산을 '공용PC'로 강제 전환 + const [publicResult] = await connection.query(` + UPDATE asset_core + SET asset_type = '공용PC', user_current = '공용' + WHERE id LIKE "PC_20260615_%" + AND (emp_no IS NULL OR emp_no = '') + `); + console.log(`✅ 공용PC 정상화 완료: ${publicResult.affectedRows}건 (사번 부재 항목)`); + + // 3. 최종 결과 확인 + const [rows] = await connection.query(` + SELECT asset_type, COUNT(*) as count + FROM asset_core + WHERE id LIKE "PC_20260615_%" + GROUP BY asset_type + `); + console.log('\n📊 최종 자산 유형 분포:'); + console.table(rows); + + await connection.end(); +} + +fixAssetTypes().catch(console.error); diff --git a/scratch/import_pc_assets.cjs b/scratch/import_pc_assets.cjs new file mode 100644 index 0000000..5c49a65 --- /dev/null +++ b/scratch/import_pc_assets.cjs @@ -0,0 +1,122 @@ +const XLSX = require('xlsx'); +const mysql = require('mysql2/promise'); +const dotenv = require('dotenv'); +const path = require('path'); + +dotenv.config({ path: path.join(__dirname, '../.env') }); + +const { DB_HOST, DB_USER, DB_PASS, DB_NAME, DB_PORT } = process.env; + +async function importAssets() { + const connection = await mysql.createConnection({ + host: DB_HOST, + user: DB_USER, + password: DB_PASS, + database: DB_NAME, + port: parseInt(DB_PORT || '3306') + }); + + console.log('🚀 [Step 1] 데이터 로드 및 사전 준비...'); + + // 1. 엑셀 파일 로드 + const workbook = XLSX.readFile('asset_pc (2026.06.15).xlsx'); + const sheet = workbook.Sheets[workbook.SheetNames[0]]; + const rawData = XLSX.utils.sheet_to_json(sheet); + + // 2. system_users 데이터 맵 생성 (사번 기준 빠른 조회를 위함) + const [userRows] = await connection.query('SELECT emp_no, user_name, dept_name, position, status FROM system_users'); + const userMap = new Map(); + userRows.forEach(u => userMap.set(String(u.emp_no), u)); + + // 3. 기존 자산 중복 체크용 맵 생성 (emp_no + asset_type + category) + const [existingAssets] = await connection.query('SELECT emp_no, asset_type, category FROM asset_core'); + const existingSet = new Set(); + existingAssets.forEach(a => { + existingSet.add(`${a.emp_no}|${a.asset_type}|${a.category}`); + }); + + console.log(`📊 처리 대상 데이터: ${rawData.length}건`); + + let skipCount = 0; + let insertCount = 0; + + for (let i = 0; i < rawData.length; i++) { + const row = rawData[i]; + const empNo = String(row.emp_no); + const assetType = row.asset_type || '개인PC'; + const category = row.category || 'PC'; + + // 중복 체크 + if (existingSet.has(`${empNo}|${assetType}|${category}`)) { + skipCount++; + continue; + } + + // [Step 2] 데이터 정제 + // 1. 사용자 정보 매칭 + const matchedUser = userMap.get(empNo); + const userName = matchedUser ? matchedUser.user_name : row.user_current; + const deptName = matchedUser ? matchedUser.dept_name : row.current_dept; + const position = matchedUser ? matchedUser.position : ''; + + // 2. 날짜 최적화 (purchase_date_1, purchase_date_2 중 최신값) + const d1 = parseInt(row.purchase_date_1) || 0; + const d2 = parseInt(row.purchase_date_2) || 0; + const latestDate = Math.max(d1, d2); + const purchaseDate = latestDate > 0 ? String(latestDate) : ''; + + // 3. 고유 ID 생성 + const assetId = `PC_20260615_${String(i + 1).padStart(4, '0')}`; + const now = new Date().toISOString().replace('T', ' ').substring(0, 19); + + try { + // [Step 3] DB 입력 + // A. asset_core 입력 + await connection.query( + `INSERT INTO asset_core (id, asset_code, category, asset_type, current_role, asset_purpose, service_type, + purchase_corp, purchase_date, memo, manager_primary, current_dept, user_current, emp_no, user_position, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [assetId, assetId, category, assetType, row.current_role, row.asset_purpose, row.service_type, + '', purchaseDate, row.memo || '', '', deptName, userName, empNo, position, now, now] + ); + + // B. asset_spec 입력 + await connection.query( + `INSERT INTO asset_spec (asset_id, model_name, mainboard, cpu, ram, gpu) VALUES (?, ?, ?, ?, ?, ?)`, + [assetId, '', row.mainboard || '', row.cpu || '', row.ram || '', row.gpu || ''] + ); + + // C. asset_volume 입력 (SSD1, SSD2, HDD1~4) + const volumes = [ + { type: 'SSD', cap: row.SDD1, slot: 1 }, + { type: 'SSD', cap: row.SDD2, slot: 2 }, + { type: 'HDD', cap: row.HDD1, slot: 3 }, + { type: 'HDD', cap: row.HDD2, slot: 4 }, + { type: 'HDD', cap: row.HDD3, slot: 5 }, + { type: 'HDD', cap: row.HDD4, slot: 6 } + ]; + + for (const vol of volumes) { + if (vol.cap && vol.cap !== '0' && vol.cap !== 0) { + await connection.query( + `INSERT INTO asset_volume (asset_id, disk_type, capacity, slot_no) VALUES (?, ?, ?, ?)`, + [assetId, vol.type, String(vol.cap), vol.slot] + ); + } + } + + insertCount++; + existingSet.add(`${empNo}|${assetType}|${category}`); // 실시간 중복 방지 추가 + } catch (err) { + console.error(`❌ [${empNo}] 처리 중 오류:`, err.message); + } + } + + console.log(`\n✨ 작업 완료!`); + console.log(`- 신규 입력: ${insertCount}건`); + console.log(`- 중복 스킵: ${skipCount}건`); + + await connection.end(); +} + +importAssets().catch(console.error); diff --git a/scratch/import_pc_assets_v2.cjs b/scratch/import_pc_assets_v2.cjs new file mode 100644 index 0000000..e3dd69b --- /dev/null +++ b/scratch/import_pc_assets_v2.cjs @@ -0,0 +1,164 @@ +const XLSX = require('xlsx'); +const mysql = require('mysql2/promise'); +const dotenv = require('dotenv'); +const path = require('path'); + +dotenv.config({ path: path.join(__dirname, '../.env') }); + +const { DB_HOST, DB_USER, DB_PASS, DB_NAME, DB_PORT } = process.env; + +// 용량 정제 함수 +function parseCapacity(val) { + if (!val || val === '0' || val === 0) return null; + + let str = String(val).toUpperCase(); + + // 1. 괄호와 그 안의 내용 제거 + str = str.replace(/\(.*\)/g, '').trim(); + + // 2. 숫자와 단위 분리 + const numMatch = str.match(/[\d.]+/); + if (!numMatch) return null; + + let num = parseFloat(numMatch[0]); + let unit = 'GB'; // 기본 단위 + + if (str.includes('TB')) { + unit = 'TB'; + } else if (str.includes('GB')) { + // 4자리수 GB인 경우 TB로 전환 (지시사항 1번) + if (num >= 1000) { + num = num / 1000; + unit = 'TB'; + } else { + unit = 'GB'; + } + } else { + // 단위가 명시되지 않은 경우 숫자의 크기로 판단 + if (num >= 1000) { + num = num / 1000; + unit = 'TB'; + } + } + + return { + capacity: parseFloat(num.toFixed(2)), + unit: unit + }; +} + +async function importAssets() { + const connection = await mysql.createConnection({ + host: DB_HOST, + user: DB_USER, + password: DB_PASS, + database: DB_NAME, + port: parseInt(DB_PORT || '3306') + }); + + console.log('🚀 [Step 1] 데이터 로드 및 사전 준비 (정제 로직 강화)...'); + + const workbook = XLSX.readFile('asset_pc (2026.06.15).xlsx'); + const sheet = workbook.Sheets[workbook.SheetNames[0]]; + const rawData = XLSX.utils.sheet_to_json(sheet); + + // system_users 데이터 맵 + const [userRows] = await connection.query('SELECT emp_no, user_name, dept_name, position, status FROM system_users'); + const userMap = new Map(); + userRows.forEach(u => userMap.set(String(u.emp_no), u)); + + // 기존 자산 중복 체크용 (emp_no + asset_type + category + user_current) + const [existingAssets] = await connection.query('SELECT emp_no, asset_type, category, user_current FROM asset_core'); + const existingSet = new Set(); + existingAssets.forEach(a => { + existingSet.add(`${a.emp_no || ''}|${a.asset_type}|${a.category}|${a.user_current}`); + }); + + console.log(`📊 처리 대상 데이터: ${rawData.length}건`); + + let skipCount = 0; + let insertCount = 0; + let errorCount = 0; + + for (let i = 0; i < rawData.length; i++) { + const row = rawData[i]; + const empNo = row.emp_no ? String(row.emp_no) : ''; // 사번 없는 행 처리 (지시사항 3번) + const assetType = row.asset_type || '개인PC'; + const category = row.category || 'PC'; + const userCurrent = row.user_current || ''; + + // 중복 체크 + const dupKey = `${empNo}|${assetType}|${category}|${userCurrent}`; + if (existingSet.has(dupKey)) { + skipCount++; + continue; + } + + // [Step 2] 데이터 정제 + const matchedUser = empNo ? userMap.get(empNo) : null; + const userName = matchedUser ? matchedUser.user_name : userCurrent; + const deptName = matchedUser ? matchedUser.dept_name : (row.current_dept || ''); + const position = matchedUser ? matchedUser.position : ''; + + const d1 = parseInt(row.purchase_date_1) || 0; + const d2 = parseInt(row.purchase_date_2) || 0; + const purchaseDate = Math.max(d1, d2) > 0 ? String(Math.max(d1, d2)) : ''; + + const assetId = `PC_20260615_${String(i + 1).padStart(4, '0')}`; + const now = new Date().toISOString().replace('T', ' ').substring(0, 19); + + try { + // [Step 3] DB 입력 + // A. asset_core + await connection.query( + `INSERT INTO asset_core (id, asset_code, category, asset_type, current_role, asset_purpose, service_type, + purchase_date, memo, current_dept, user_current, emp_no, user_position, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [assetId, assetId, category, assetType, row.current_role || '', row.asset_purpose || '', row.service_type || '', + purchaseDate, row.memo || '', deptName, userName, empNo, position, now, now] + ); + + // B. asset_spec + await connection.query( + `INSERT INTO asset_spec (asset_id, mainboard, cpu, ram, gpu) VALUES (?, ?, ?, ?, ?)`, + [assetId, row.mainboard || '', row.cpu || '', row.ram || '', row.gpu || ''] + ); + + // C. asset_volume + const volCols = [ + { key: 'SDD1', type: 'SSD', slot: 1 }, + { key: 'SDD2', type: 'SSD', slot: 2 }, + { key: 'HDD1', type: 'HDD', slot: 3 }, + { key: 'HDD2', type: 'HDD', slot: 4 }, + { key: 'HDD3', type: 'HDD', slot: 5 }, + { key: 'HDD4', type: 'HDD', slot: 6 } + ]; + + for (const col of volCols) { + const rawVol = row[col.key]; + const parsed = parseCapacity(rawVol); + if (parsed) { + await connection.query( + `INSERT INTO asset_volume (asset_id, disk_type, capacity, unit, slot_no) VALUES (?, ?, ?, ?, ?)`, + [assetId, col.type, parsed.capacity, parsed.unit, col.slot] + ); + } + } + + insertCount++; + existingSet.add(dupKey); + } catch (err) { + errorCount++; + console.error(`❌ [Row ${i + 2}] ${empNo || 'Public'}: ${err.message}`); + } + } + + console.log(`\n✨ 작업 완료!`); + console.log(`- 신규 입력: ${insertCount}건`); + console.log(`- 중복 스킵: ${skipCount}건`); + console.log(`- 오류 실패: ${errorCount}건`); + + await connection.end(); +} + +importAssets().catch(console.error); diff --git a/scratch/import_system_users.cjs b/scratch/import_system_users.cjs new file mode 100644 index 0000000..a9fb46c --- /dev/null +++ b/scratch/import_system_users.cjs @@ -0,0 +1,61 @@ +const XLSX = require('xlsx'); +const mysql = require('mysql2/promise'); +const dotenv = require('dotenv'); +const path = require('path'); + +dotenv.config({ path: path.join(__dirname, '../.env') }); + +const { DB_HOST, DB_USER, DB_PASS, DB_NAME, DB_PORT } = process.env; + +async function importUsers() { + const connection = await mysql.createConnection({ + host: DB_HOST, + user: DB_USER, + password: DB_PASS, + database: DB_NAME, + port: parseInt(DB_PORT || '3306') + }); + + console.log('🚀 Excel 데이터 로드 중...'); + const workbook = XLSX.readFile('system_User (20260615).xlsx'); + const sheetName = workbook.SheetNames[0]; + const sheet = workbook.Sheets[sheetName]; + const data = XLSX.utils.sheet_to_json(sheet); + + console.log(`📊 총 ${data.length}개의 데이터를 찾았습니다.`); + + // 기존 데이터 삭제 여부 (사용자 요구사항에 따라 결정 가능하지만, 보통 초기화 후 재입입) + // 여기서는 중복 방지를 위해 기존 데이터를 삭제하고 새로 넣는 방식을 취하겠습니다. + console.log('🧹 기존 system_users 데이터 삭제 중...'); + await connection.query('DELETE FROM system_users'); + + console.log('📥 데이터 삽입 중...'); + let successCount = 0; + + for (let i = 0; i < data.length; i++) { + const row = data[i]; + const { emp_no, user_name, dept_name, position, status } = row; + + // ID 생성 (USR_ + 인덱스 001 형식) + const id = `USR_${String(i + 1).padStart(3, '0')}`; + const createdAt = new Date().toISOString().replace('T', ' ').substring(0, 19); + + try { + await connection.query( + 'INSERT INTO system_users (id, emp_no, user_name, dept_name, position, status, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)', + [id, String(emp_no), user_name, dept_name, position, status, createdAt] + ); + successCount++; + } catch (err) { + console.error(`❌ 삽입 실패 (Row ${i + 2}):`, err.message); + } + } + + console.log(`✅ 완료: ${successCount}개의 사용자가 성공적으로 등록되었습니다.`); + await connection.end(); +} + +importUsers().catch(err => { + console.error('❌ 작업 중 오류 발생:', err); + process.exit(1); +}); diff --git a/scratch/peek_asset_pc.cjs b/scratch/peek_asset_pc.cjs new file mode 100644 index 0000000..0ee0962 --- /dev/null +++ b/scratch/peek_asset_pc.cjs @@ -0,0 +1,7 @@ +const XLSX = require('xlsx'); +const workbook = XLSX.readFile('asset_pc (2026.06.15).xlsx'); +const sheetName = workbook.SheetNames[0]; +const sheet = workbook.Sheets[sheetName]; +const data = XLSX.utils.sheet_to_json(sheet, { header: 1 }); +console.log('Headers:', JSON.stringify(data[0], null, 2)); +console.log('Sample Row 1:', JSON.stringify(data[1], null, 2)); diff --git a/scratch/peek_excel.cjs b/scratch/peek_excel.cjs new file mode 100644 index 0000000..fdd32ae --- /dev/null +++ b/scratch/peek_excel.cjs @@ -0,0 +1,6 @@ +const XLSX = require('xlsx'); +const workbook = XLSX.readFile('system_User (20260615).xlsx'); +const sheetName = workbook.SheetNames[0]; +const sheet = workbook.Sheets[sheetName]; +const data = XLSX.utils.sheet_to_json(sheet, { header: 1 }); +console.log(JSON.stringify(data.slice(0, 5), null, 2)); diff --git a/scratch/raw_check.cjs b/scratch/raw_check.cjs new file mode 100644 index 0000000..85eec69 --- /dev/null +++ b/scratch/raw_check.cjs @@ -0,0 +1,18 @@ +const mysql = require('mysql2/promise'); +require('dotenv').config(); + +async function rawCheck() { + const connection = await mysql.createConnection({ + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASS, + database: process.env.DB_NAME, + port: parseInt(process.env.DB_PORT || '3306') + }); + + const [rows] = await connection.query('SELECT user_current, emp_no FROM asset_core WHERE id LIKE "PC_20260615_%" LIMIT 10'); + console.log(rows); + await connection.end(); +} + +rawCheck().catch(console.error); diff --git a/scratch/rebuild_asset_codes_final.cjs b/scratch/rebuild_asset_codes_final.cjs new file mode 100644 index 0000000..9c1fec2 --- /dev/null +++ b/scratch/rebuild_asset_codes_final.cjs @@ -0,0 +1,85 @@ +const mysql = require('mysql2/promise'); +require('dotenv').config(); + +async function rebuildAssetCodes() { + const connection = await mysql.createConnection({ + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASS, + database: process.env.DB_NAME, + port: parseInt(process.env.DB_PORT || '3306') + }); + + console.log('🚀 [Step 1] 신규 자산 구매일 업데이트 (YYYY-12-01)...'); + + // 1. 오늘 입력한 자산들 조회 + const [rows] = await connection.query( + 'SELECT id, purchase_date FROM asset_core WHERE id LIKE "PC_20260615_%"' + ); + console.log(`대상 자산: ${rows.length}건`); + + // 2. 구매일자 업데이트 (연도만 있는 경우 -12-01 추가) + for (const row of rows) { + if (row.purchase_date && row.purchase_date.length === 4) { + const newDate = `${row.purchase_date}-12-01`; + await connection.query( + 'UPDATE asset_core SET purchase_date = ? WHERE id = ?', + [newDate, row.id] + ); + } + } + console.log('✅ 구매일 업데이트 완료.'); + + console.log('\n🚀 [Step 2] 자산번호(asset_code) 재매핑 시작...'); + + // 3. 연도별로 그룹화하여 자산번호 부여 + // 연도 목록 추출 + const [yearRows] = await connection.query( + 'SELECT DISTINCT LEFT(purchase_date, 4) as year FROM asset_core WHERE id LIKE "PC_20260615_%" ORDER BY year' + ); + + for (const yRow of yearRows) { + const year = yRow.year; + const yearMonth = `${year}12`; + const pattern = `PC-${yearMonth}-%`; + + console.log(`--- [${year}년] 처리 중 ---`); + + // 해당 연도/월의 기존 최대 순번 조회 + const [maxRows] = await connection.query( + 'SELECT asset_code FROM asset_core WHERE asset_code LIKE ? AND id NOT LIKE "PC_20260615_%"', + [pattern] + ); + + let maxSeq = 0; + maxRows.forEach(r => { + const parts = r.asset_code.split('-'); + const seq = parseInt(parts[2]); + if (seq > maxSeq) maxSeq = seq; + }); + + console.log(`기존 최대 순번: ${maxSeq}`); + + // 해당 연도 자산들 순차적으로 번호 부여 + const [assetsOfYear] = await connection.query( + 'SELECT id FROM asset_core WHERE id LIKE "PC_20260615_%" AND purchase_date LIKE ? ORDER BY id', + [`${year}-12%`] + ); + + let currentSeq = maxSeq + 1; + for (const asset of assetsOfYear) { + const newCode = `PC-${yearMonth}-${String(currentSeq).padStart(4, '0')}`; + await connection.query( + 'UPDATE asset_core SET asset_code = ? WHERE id = ?', + [newCode, asset.id] + ); + currentSeq++; + } + console.log(`신규 부여 완료: ${assetsOfYear.length}건 (순번 ${maxSeq + 1} ~ ${currentSeq - 1})`); + } + + console.log('\n✨ 모든 작업이 완료되었습니다.'); + await connection.end(); +} + +rebuildAssetCodes().catch(console.error); diff --git a/scratch/reexamine_full.cjs b/scratch/reexamine_full.cjs new file mode 100644 index 0000000..10472c4 --- /dev/null +++ b/scratch/reexamine_full.cjs @@ -0,0 +1,85 @@ +const XLSX = require('xlsx'); +const mysql = require('mysql2/promise'); +require('dotenv').config(); + +async function reexamineData() { + const connection = await mysql.createConnection({ + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASS, + database: process.env.DB_NAME, + port: parseInt(process.env.DB_PORT || '3306') + }); + + console.log('🧐 [전수 조사] 엑셀 vs DB 데이터 비교 분석...'); + + // 1. 엑셀 데이터 로드 + const workbook = XLSX.readFile('asset_pc (2026.06.15).xlsx'); + const sheet = workbook.Sheets[workbook.SheetNames[0]]; + const excelRows = XLSX.utils.sheet_to_json(sheet); + + // 2. DB 데이터 로드 + const [dbRows] = await connection.query(` + SELECT id, asset_code, asset_type, user_current, emp_no, current_dept + FROM asset_core + WHERE id LIKE "PC_20260615_%" + `); + const dbMap = new Map(); + dbRows.forEach(r => dbMap.set(r.id, r)); + + const report = { + total: excelRows.length, + publicInExcelWithEmpNo: [], // 엑셀은 공용PC인데 사번이 있는 경우 + personalInExcelNoEmpNo: [], // 엑셀은 개인PC인데 사번이 없는 경우 + typeMismatch: [], // 엑셀과 DB의 asset_type이 다른 경우 + userMismatch: [] // 사용자명이 크게 다른 경우 + }; + + for (let i = 0; i < excelRows.length; i++) { + const ex = excelRows[i]; + const id = `PC_20260615_${String(i + 1).padStart(4, '0')}`; + const db = dbMap.get(id); + + if (!db) continue; + + const exType = ex.asset_type || '개인PC'; + const exEmpNo = ex.emp_no ? String(ex.emp_no) : null; + const exUser = ex.user_current || ''; + + // A. 공용PC인데 사번이 있는 경우 (가장 큰 혼란 포인트) + if (exType === '공용PC' && exEmpNo) { + report.publicInExcelWithEmpNo.push({ id, exUser, exEmpNo, exDept: ex.current_dept }); + } + + // B. 개인PC인데 사번이 없는 경우 + if (exType === '개인PC' && !exEmpNo) { + report.personalInExcelNoEmpNo.push({ id, exUser, exDept: ex.current_dept }); + } + + // C. DB와의 타입 불일치 (현재 DB 상태 체크) + if (db.asset_type !== exType) { + report.typeMismatch.push({ id, exType, dbType: db.asset_type, user: db.user_current }); + } + } + + console.log('\n================================================'); + console.log(`📊 전수 조사 요약 (총 ${report.total}건)`); + console.log(`1. 엑셀은 '공용PC'이나 '사번'이 있는 항목: ${report.publicInExcelWithEmpNo.length}건`); + console.log(`2. 엑셀은 '개인PC'이나 '사번'이 없는 항목: ${report.personalInExcelNoEmpNo.length}건`); + console.log(`3. 현재 DB와 엑셀의 '자산유형' 불일치: ${report.typeMismatch.length}건`); + console.log('================================================\n'); + + if (report.publicInExcelWithEmpNo.length > 0) { + console.log('⚠️ [그룹 1] 공용PC인데 실사용자/관리자가 지정된 사례 (샘플 15건):'); + console.table(report.publicInExcelWithEmpNo.slice(0, 15)); + } + + if (report.personalInExcelNoEmpNo.length > 0) { + console.log('\n⚠️ [그룹 2] 개인PC인데 사번 정보가 누락된 사례 (샘플 15건):'); + console.table(report.personalInExcelNoEmpNo.slice(0, 15)); + } + + await connection.end(); +} + +reexamineData().catch(console.error); diff --git a/scratch/restore_and_merge_users.cjs b/scratch/restore_and_merge_users.cjs new file mode 100644 index 0000000..e2a1fed --- /dev/null +++ b/scratch/restore_and_merge_users.cjs @@ -0,0 +1,92 @@ +const XLSX = require('xlsx'); +const mysql = require('mysql2/promise'); +const dotenv = require('dotenv'); +const path = require('path'); + +dotenv.config({ path: path.join(__dirname, '../.env') }); + +const { DB_HOST, DB_USER, DB_PASS, DB_NAME, DB_PORT } = process.env; + +async function restoreAndMerge() { + const connection = await mysql.createConnection({ + host: DB_HOST, + user: DB_USER, + password: DB_PASS, + database: DB_NAME, + port: parseInt(DB_PORT || '3306') + }); + + console.log('🔄 데이터 복구 및 병합 시작...'); + + // 1. 백업 파일에서 기존 데이터(212건) 로드 + const workbookBackup = XLSX.readFile('backupDB_20260602.xlsx'); + const oldUsers = XLSX.utils.sheet_to_json(workbookBackup.Sheets['system_users']); + + // 2. 신규 파일에서 데이터(987건) 로드 + const workbookNew = XLSX.readFile('system_User (20260615).xlsx'); + const newUsers = XLSX.utils.sheet_to_json(workbookNew.Sheets[workbookNew.SheetNames[0]]); + + console.log(`기본 백업 데이터: ${oldUsers.length}건`); + console.log(`신규 추가 데이터: ${newUsers.length}건`); + + // 테이블 비우기 (실수를 바로잡기 위해 다시 시작) + await connection.query('DELETE FROM system_users'); + + const insertedEmpNos = new Set(); + let restoreCount = 0; + let addCount = 0; + + // 3. 기존 데이터 복구 (ID 보존 시도) + for (const user of oldUsers) { + const { id, emp_no, user_name, dept_name, position, status, created_at } = user; + + // 엑셀 날짜 처리 (숫자로 되어 있을 경우) + let finalCreatedAt = created_at; + if (typeof created_at === 'number') { + const date = new Date((created_at - 25569) * 86400 * 1000); + finalCreatedAt = date.toISOString().replace('T', ' ').substring(0, 19); + } + + try { + await connection.query( + 'INSERT INTO system_users (id, emp_no, user_name, dept_name, position, status, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)', + [id, String(emp_no), user_name, dept_name, position, status, finalCreatedAt] + ); + insertedEmpNos.add(String(emp_no)); + restoreCount++; + } catch (err) { + console.error(`❌ 복구 실패 (emp_no: ${emp_no}):`, err.message); + } + } + + // 4. 신규 데이터 추가 (중복 제외) + for (let i = 0; i < newUsers.length; i++) { + const user = newUsers[i]; + const { emp_no, user_name, dept_name, position, status } = user; + const strEmpNo = String(emp_no); + + if (insertedEmpNos.has(strEmpNo)) { + continue; // 이미 복구된 데이터는 스킵 + } + + // 신규 데이터용 ID 생성 (기존 ID와 겹치지 않게 'NEW_' 접두어 또는 시퀀스 사용) + // 여기서는 단순히 시퀀스로 처리 (최대 ID 확인 후 +1 하는 방식이 좋으나 여기선 간단히) + const id = `USR_N_${String(i + 1).padStart(4, '0')}`; + const createdAt = new Date().toISOString().replace('T', ' ').substring(0, 19); + + try { + await connection.query( + 'INSERT INTO system_users (id, emp_no, user_name, dept_name, position, status, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)', + [id, strEmpNo, user_name, dept_name, position, status, createdAt] + ); + addCount++; + } catch (err) { + console.error(`❌ 추가 실패 (emp_no: ${emp_no}):`, err.message); + } + } + + console.log(`✅ 복구 완료: 기존 ${restoreCount}건 복구, 신규 ${addCount}건 추가 (총 ${restoreCount + addCount}건)`); + await connection.end(); +} + +restoreAndMerge().catch(console.error); diff --git a/scratch/update_dept_saman.cjs b/scratch/update_dept_saman.cjs new file mode 100644 index 0000000..a12d079 --- /dev/null +++ b/scratch/update_dept_saman.cjs @@ -0,0 +1,32 @@ +const mysql = require('mysql2/promise'); +require('dotenv').config(); + +async function updateDepartments() { + const connection = await mysql.createConnection({ + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASS, + database: process.env.DB_NAME, + port: parseInt(process.env.DB_PORT || '3306') + }); + + console.log("🚀 부서명 '삼안' 통합 업데이트 시작..."); + + const [result] = await connection.query(` + UPDATE asset_core + SET current_dept = '삼안' + WHERE current_dept NOT IN ('총괄기획실', '기술개발센터', '현타', '장헌', '한맥', 'PTC', '', '삼안') + AND current_dept IS NOT NULL + `); + + console.log(`✅ 업데이트 완료: ${result.affectedRows}건의 부서명이 '삼안'으로 변경되었습니다.`); + + // 최종 확인용 카운트 + const [rows] = await connection.query('SELECT current_dept, COUNT(*) as count FROM asset_core GROUP BY current_dept'); + console.log('\n📊 최종 부서 분포:'); + console.table(rows); + + await connection.end(); +} + +updateDepartments().catch(console.error); diff --git a/system_User (20260615).xlsx b/system_User (20260615).xlsx new file mode 100644 index 0000000000000000000000000000000000000000..b58a23ea380ececee1e786aefcf600c244504e9c GIT binary patch literal 47930 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 literal 0 HcmV?d00001 diff --git a/~$backupDB_20260602.xlsx b/~$backupDB_20260602.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..975f9aa1d6dd2b15bada070d9f1ab1bbc1a5241e GIT binary patch literal 165 ccmZPxElw>`AQ`YQgfbK}q%ssSD9`}{07N1W#{d8T literal 0 HcmV?d00001 diff --git a/~$system_User (20260615).xlsx b/~$system_User (20260615).xlsx new file mode 100644 index 0000000000000000000000000000000000000000..975f9aa1d6dd2b15bada070d9f1ab1bbc1a5241e GIT binary patch literal 165 ccmZPxElw>`AQ`YQgfbK}q%ssSD9`}{07N1W#{d8T literal 0 HcmV?d00001 From 3e69e74bc96791c4dabbbd26f9a1c088c9cd91cc Mon Sep 17 00:00:00 2001 From: JooWangi Date: Wed, 17 Jun 2026 09:22:31 +0900 Subject: [PATCH 02/14] =?UTF-8?q?Feat:=20=ED=86=B5=ED=95=A9=20=EC=82=AC?= =?UTF-8?q?=EC=96=91=20=EC=A0=81=EC=A0=95=EC=84=B1=20=EC=9D=B8=EB=9D=BC?= =?UTF-8?q?=EC=9D=B8=20=EB=B0=94=20=EA=B7=B8=EB=9E=98=ED=94=84=20=EB=B0=8F?= =?UTF-8?q?=20=EB=8C=80=EC=8B=9C=EB=B3=B4=EB=93=9C=20=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=95=84=EC=9B=83=20=EA=B0=9C=ED=8E=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/Dashboard/HwDashboard.ts | 576 ++++++++++++----------------- 1 file changed, 238 insertions(+), 338 deletions(-) diff --git a/src/views/Dashboard/HwDashboard.ts b/src/views/Dashboard/HwDashboard.ts index 2c91be7..72c2317 100644 --- a/src/views/Dashboard/HwDashboard.ts +++ b/src/views/Dashboard/HwDashboard.ts @@ -6,7 +6,6 @@ import { createIcons, Laptop, Cpu, Shield, Zap, Monitor, AlertTriangle, ChevronR declare var Chart: any; -let jobChartInstance: any = null; let donutChartInstance: any = null; export function renderHwDashboard(container: HTMLElement) { @@ -43,181 +42,149 @@ export function renderHwDashboard(container: HTMLElement) { - -
+ +
- -
+ +
- -
- - -
-
- 보유 자산 수량 -
-
-
-
0대
- 전사 보유 개인용 PC -
-
+ +
+
+ 보유 자산 수량
- - -
-
- 사양 부족 -
-
-
-
0대
- 사양 교체 권고 자산 -
-
+
+
0대
- - -
-
- 오버 스펙 -
-
-
-
0대
- 사양 회수 권고 자산 -
-
-
- - -
-
- 윈도우 11 불가 PC -
-
-
-
0대
- 업데이트 미지원 하드웨어 -
-
-
-
- - -
- -
- -
- 등급별 자산 종합 현황 -
- - -
- - - - - - - - - - - - - -
구분 (등급)보유량운영중재고구매 필요
-
+ +
+
+ 사양 부족 +
+
+
0대
-
- + + +
+
+ 오버 스펙 +
+
+
0대
+
+
+ + +
+
+ 윈도우 11 불가 PC +
+
+
0대
+
+
+
- -
+ +
- -
-
- 직무별 사양 적정성 분석 + +
+ +
+ 등급별 보유 비율
-
- + + +
+
+ +
+ +
+
+ + 최상급 +
+
+ + 상급 +
+
+ + 중급 +
+
+ + 보급 +
+
+ + 교체 대상 +
+
- - -
- - -
- -
- 등급별 보유 비율 -
- - -
-
- -
- -
-
- - 최상급 -
-
- - 상급 -
-
- - 중급 -
-
- - 보급 -
-
- - 교체 대상 -
-
-
-
- -
-
- 연도별 PC 노후도 및 예측 -
-
- - - - - - - - - - - -
구분 (연한)보유권장 조치
-
+ +
+
+ 연도별 PC 노후도 및 예측 +
+
+ + + + + + + + + + + +
구분 (연한)보유권장 조치
+ + +
+
+ +
+ 등급별 자산 종합 현황 및 사양 적정성 분석 +
+ + +
+ + + + + + + + + + + + + + +
구분 (등급)보유량운영중재고구매 필요사양 적정성 분석 (직무 기준)
+
+
+
+
`; @@ -402,21 +369,64 @@ function updateDashboardData(pcs: any[], selectedDept: string) { // 6. 종합 매트릭스 테이블 렌더링 및 바인딩 const matrixTbody = document.getElementById('pc-grade-matrix-tbody')!; + + const getSpecStatusCounts = (activePcsList: any[]) => { + let under = 0; + let normal = 0; + let over = 0; + activePcsList.forEach(p => { + if (p._spec_status === '사양 부족') under++; + else if (p._spec_status === '오버스펙') over++; + else normal++; + }); + return { under, normal, over }; + }; const renderMatrixRow = (gradeKey: keyof typeof matrix, label: string, color: string, shortage: number) => { const data = matrix[gradeKey]; const totalRate = filtered.length > 0 ? Math.round((data.total / filtered.length) * 100) : 0; - const cellStyle = `padding: 14px 12px; text-align: center; font-weight: 700; cursor: pointer; transition: background 0.2s; font-size: 1.25rem;`; + const cellStyle = `padding: 10px 8px; text-align: center; font-weight: 700; cursor: pointer; transition: background 0.2s; font-size: 1.1rem;`; const hoverEvents = `onmouseover="this.style.background='#F1F5F9'" onmouseout="this.style.background='none'"`; + // 사양 적정성 분석 데이터 계산 (운영중인 자산만) + const { under, normal, over } = getSpecStatusCounts(data.activePcs); + const activeCount = data.active; + + const underPct = activeCount > 0 ? (under / activeCount) * 100 : 0; + const normalPct = activeCount > 0 ? (normal / activeCount) * 100 : 0; + const overPct = activeCount > 0 ? (over / activeCount) * 100 : 0; + + let barGraphHtml = ''; + if (activeCount > 0) { + barGraphHtml = ` +
+
+ ${under > 0 ? `
` : ''} + ${normal > 0 ? `
` : ''} + ${over > 0 ? `
` : ''} +
+
+ ${under > 0 ? `부족 ${under}` : ''} + ${normal > 0 ? `적정 ${normal}` : ''} + ${over > 0 ? `오버 ${over}` : ''} +
+
+ `; + } else { + barGraphHtml = `운영중 자산 없음`; + } + return ` - ${label} - ${data.total}대 (${totalRate}%) + ${label} + ${data.total}대 (${totalRate}%) ${data.active}대 ${data.stock}대 ${shortage}대 + + ${barGraphHtml} + `; }; @@ -437,7 +447,33 @@ function updateDashboardData(pcs: any[], selectedDept: string) { const totalShortage = premiumShortage + highShortage + normalShortage + entryShortage + replaceShortage; - const cellStyleHeader = `padding: 14px 12px; text-align: center; font-weight: 800; cursor: pointer; transition: background 0.2s; background: #F8FAFC; font-size: 1.25rem;`; + const totalActivePcs = filtered.filter(p => !isStock(p)); + const { under: totUnder, normal: totNormal, over: totOver } = getSpecStatusCounts(totalActivePcs); + const totUnderPct = totalActive > 0 ? (totUnder / totalActive) * 100 : 0; + const totNormalPct = totalActive > 0 ? (totNormal / totalActive) * 100 : 0; + const totOverPct = totalActive > 0 ? (totOver / totalActive) * 100 : 0; + + let totBarGraphHtml = ''; + if (totalActive > 0) { + totBarGraphHtml = ` +
+
+ ${totUnder > 0 ? `
` : ''} + ${totNormal > 0 ? `
` : ''} + ${totOver > 0 ? `
` : ''} +
+
+ ${totUnder > 0 ? `부족 ${totUnder}` : ''} + ${totNormal > 0 ? `적정 ${totNormal}` : ''} + ${totOver > 0 ? `오버 ${totOver}` : ''} +
+
+ `; + } else { + totBarGraphHtml = `운영중 자산 없음`; + } + + const cellStyleHeader = `padding: 10px 8px; text-align: center; font-weight: 800; cursor: pointer; transition: background 0.2s; background: #F8FAFC; font-size: 1.1rem;`; const hoverEventsHeader = `onmouseover="this.style.background='#EEF2F6'" onmouseout="this.style.background='#F8FAFC'"`; matrixTbody.innerHTML = ` @@ -447,11 +483,14 @@ function updateDashboardData(pcs: any[], selectedDept: string) { ${renderMatrixRow('entry', '보급 PC (20점 ~ 40점)', '#F59E0B', entryShortage)} ${renderMatrixRow('replace', '교체 대상 PC (20점 미만 또는 Win11 불가)', '#EF4444', replaceShortage)} - 합계 (Total) - ${totalPcs}대 (100%) + 합계 (Total) + ${totalPcs}대 (100%) ${totalActive}대 ${totalStock}대 ${totalShortage}대 + + ${totBarGraphHtml} + `; @@ -509,6 +548,38 @@ function updateDashboardData(pcs: any[], selectedDept: string) { }); }); + // 바그래프 세그먼트 또는 텍스트 클릭 리스너 설정 + const handleSpecClick = (e: Event) => { + e.stopPropagation(); + const target = e.currentTarget as HTMLElement; + const grade = target.getAttribute('data-grade')!; + const status = target.getAttribute('data-spec-status')!; + + let targetPcs: any[] = []; + if (grade === 'all') { + targetPcs = filtered.filter(p => !isStock(p) && p._spec_status === status); + } else { + const data = matrix[grade as keyof typeof matrix]; + targetPcs = data.activePcs.filter(p => p._spec_status === status); + } + + const getGradeLabel = (g: string) => { + if (g === 'premium') return '최상급 PC'; + if (g === 'high') return '상급 PC'; + if (g === 'normal') return '중급 PC'; + if (g === 'entry') return '보급 PC'; + if (g === 'replace') return '교체 대상 PC'; + return '전체 PC'; + }; + + const title = `${getGradeLabel(grade)} - ${status} 자산 목록`; + showMiniListModal(title, targetPcs); + }; + + matrixTbody.querySelectorAll('.spec-segment-btn, .spec-text-btn').forEach(btn => { + btn.addEventListener('click', handleSpecClick); + }); + // 7. 연도별 PC 노후도 집계 및 렌더링 const agingCounts = { immediate: [] as any[], // 7년 이상 @@ -535,10 +606,10 @@ function updateDashboardData(pcs: any[], selectedDept: string) { const renderAgingRow = (label: string, list: any[], badgeText: string, badgeStyle: string, ageGroupKey: string) => { return ` - ${label} - ${list.length}대 - - ${badgeText} + ${label} + ${list.length}대 + + ${badgeText} `; @@ -586,50 +657,11 @@ function updateDashboardData(pcs: any[], selectedDept: string) { bindCardClick('card-over-spec', '오버 스펙 대상', p => p._spec_status === '오버스펙'); bindCardClick('card-win11-incompatible', '윈도우 11 업그레이드 불가 PC', p => isWindows11Incompatible(p.cpu, p.ram)); - // 9. 직무별 사양 적정성 대수 연산 및 차트 데이터 셋 구성 (누적 막대 그래프화) - const activeJobs = Array.from( - new Set(filtered.map((p: any) => p[ASSET_SCHEMA.USER_POSITION.key] || '미분류').filter(j => j !== '재고PC')) - ).sort(); - - const underData: number[] = []; - const normalData: number[] = []; - const overData: number[] = []; - - activeJobs.forEach(job => { - const jobPcs = filtered.filter((p: any) => (p[ASSET_SCHEMA.USER_POSITION.key] || '미분류') === job); - const totalCount = jobPcs.length; - if (totalCount === 0) { - underData.push(0); - normalData.push(0); - overData.push(0); - return; - } - let under = 0; - let normal = 0; - let over = 0; - - jobPcs.forEach(p => { - const stockYn = isStock(p); - if (!stockYn) { - if (p._spec_status === '사양 부족') { under++; } - else if (p._spec_status === '오버스펙') { over++; } - else { normal++; } - } else { - normal++; // 예외 폴백 - } - }); - - underData.push(under); - normalData.push(normal); - overData.push(over); - }); - - // 10. 차트들 렌더링 호출 - renderChart(activeJobs, underData, normalData, overData, filtered); + // 10. 도넛 차트 렌더링 호출 renderDonutChart(matrix.premium.total, matrix.high.total, matrix.normal.total, matrix.entry.total, matrix.replace.total); // 전역 상태 등록 - state.activeCharts = [jobChartInstance, donutChartInstance]; + state.activeCharts = [donutChartInstance]; } /** @@ -746,139 +778,7 @@ function showMiniListModal(title: string, list: any[]) { }); } -/** - * Chart.js 가로형 100% 스택 막대 차트 (라이트 테마 튜닝) - */ -function renderChart(labels: string[], underData: number[], normalData: number[], overData: number[], currentFiltered: any[]) { - const ctx = document.getElementById('chart-job-scores') as HTMLCanvasElement; - if (!ctx || typeof Chart === 'undefined') return; - if (jobChartInstance) { - jobChartInstance.destroy(); - jobChartInstance = null; - } - - jobChartInstance = new Chart(ctx, { - type: 'bar', - data: { - labels: labels, - datasets: [ - { - label: '사양 부족', - data: underData, - backgroundColor: 'rgba(239, 68, 68, 0.85)', // Rose Red - borderColor: 'rgb(239, 68, 68)', - borderWidth: 1, - borderRadius: 4, - barPercentage: 0.45, - categoryPercentage: 0.8 - }, - { - label: '적정 사양', - data: normalData, - backgroundColor: 'rgba(30, 81, 73, 0.85)', // Hanmac Green - borderColor: 'rgb(30, 81, 73)', - borderWidth: 1, - borderRadius: 4, - barPercentage: 0.45, - categoryPercentage: 0.8 - }, - { - label: '오버 스펙', - data: overData, - backgroundColor: 'rgba(217, 119, 6, 0.85)', // Amber Orange - borderColor: 'rgb(217, 119, 6)', - borderWidth: 1, - borderRadius: 4, - barPercentage: 0.45, - categoryPercentage: 0.8 - } - ] - }, - options: { - indexAxis: 'y', - responsive: true, - maintainAspectRatio: false, - onHover: (event: any, activeElements: any[]) => { - event.chart.canvas.style.cursor = activeElements.length ? 'pointer' : 'default'; - }, - onClick: (event: any, activeElements: any[]) => { - if (activeElements && activeElements.length > 0) { - const activeElement = activeElements[0]; - const datasetIndex = activeElement.datasetIndex; // 0: 사양 부족, 1: 적정 사양, 2: 오버스펙 - const index = activeElement.index; // 직무군 인덱스 - - const clickedJob = labels[index]; - const statusLabels = ['사양 부족', '적정', '오버스펙']; - const clickedStatus = statusLabels[datasetIndex] || '적정'; - - // 해당 직무군과 사양 상태가 매칭되는 자산 목록 필터링 - const matchedPcs = currentFiltered.filter((p: any) => { - const job = p[ASSET_SCHEMA.USER_POSITION.key] || '미분류'; - if (job !== clickedJob) return false; - - const stockYn = p.hw_status === '재고' || - p.hw_status === '대기' || - !(p.user_current || '').trim(); - - let specStatus = '적정'; - if (!stockYn) { - specStatus = p._spec_status || '적정'; - } - return specStatus === clickedStatus; - }); - - showMiniListModal(`${clickedJob} - ${clickedStatus === '적정' ? '적정 사양' : (clickedStatus === '오버스펙' ? '오버 스펙' : clickedStatus)} 자산`, matchedPcs); - } - }, - plugins: { - legend: { - position: 'top', - align: 'end', - labels: { - font: { family: 'Pretendard', size: 16, weight: '700' }, - color: '#475569', - boxWidth: 12, - boxHeight: 12, - usePointStyle: true - } - }, - tooltip: { - titleFont: { family: 'Pretendard', size: 12, weight: '700' }, - bodyFont: { family: 'Pretendard', size: 12 }, - callbacks: { - label: function (context: any) { - const datasetLabel = context.dataset.label; - const value = context.raw; // 실제 대수 - const total = context.chart.data.datasets.reduce((sum: number, dataset: any) => sum + dataset.data[context.dataIndex], 0); - const percentage = total > 0 ? Math.round((value / total) * 100) : 0; - return `${datasetLabel}: ${value}대 (${percentage}%)`; - } - } - } - }, - scales: { - x: { - stacked: true, - ticks: { - callback: (val: any) => `${val}대`, - font: { family: 'Pretendard', size: 14, weight: '600' }, - color: '#64748B' - }, - grid: { color: '#EEF2F6' } - }, - y: { - stacked: true, - ticks: { - font: { family: 'Pretendard', size: 16, weight: '700' }, - color: '#475569' - }, - grid: { display: false } - } - } - } - }); -} /** * 실시간 사양 적정률 원형 도넛 그래프 (Active Spec Rate) From 84511013251bae52718cea4a59966d6b63fb1fd5 Mon Sep 17 00:00:00 2001 From: JooWangi Date: Wed, 17 Jun 2026 09:25:16 +0900 Subject: [PATCH 03/14] =?UTF-8?q?Style:=20=EB=8C=80=EC=8B=9C=EB=B3=B4?= =?UTF-8?q?=EB=93=9C=20UI=20=ED=94=84=EB=A6=AC=EB=AF=B8=EC=97=84=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=83=80=EC=9D=BC=EB=A7=81=20=EB=B0=8F=20?= =?UTF-8?q?=EC=B9=B4=EB=93=9C=20=EA=B5=AC=EC=A1=B0=20=EB=8F=84=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/Dashboard/HwDashboard.ts | 183 ++++++++++++++++------------- 1 file changed, 101 insertions(+), 82 deletions(-) diff --git a/src/views/Dashboard/HwDashboard.ts b/src/views/Dashboard/HwDashboard.ts index 72c2317..dc451ae 100644 --- a/src/views/Dashboard/HwDashboard.ts +++ b/src/views/Dashboard/HwDashboard.ts @@ -43,70 +43,87 @@ export function renderHwDashboard(container: HTMLElement) {
-
+
- -
+ +
-
-
- 보유 자산 수량 +
+
+
-
-
0대
+
+ + 보유 자산 수량
+
0대
-
-
- 사양 부족 +
+
+
-
-
0대
+
+ + 사양 부족
+
0대
-
-
- 오버 스펙 +
+
+
-
-
0대
+
+ + 오버 스펙
+
0대
-
-
- 윈도우 11 불가 PC +
+
+
-
-
0대
+
+ + 윈도우 11 불가 PC
+
0대
- -
+ +
- -
+ +
-
- 등급별 보유 비율 +
+ 등급별 보유 비율
-
+
-
+
최상급 @@ -125,24 +142,25 @@ export function renderHwDashboard(container: HTMLElement) {
- 교체 대상 + 교체
- -
-
- 연도별 PC 노후도 및 예측 + +
+
+ 연도별 PC 노후도 및 예측
-
+
- - - + + + @@ -156,25 +174,26 @@ export function renderHwDashboard(container: HTMLElement) { - -
-
+ +
+
-
+
등급별 자산 종합 현황 및 사양 적정성 분석
-
-
구분 (연한)보유권장 조치구분 (연한)보유권장 조치
+
+
- - - - - - - + + + + + + + @@ -386,7 +405,7 @@ function updateDashboardData(pcs: any[], selectedDept: string) { const data = matrix[gradeKey]; const totalRate = filtered.length > 0 ? Math.round((data.total / filtered.length) * 100) : 0; - const cellStyle = `padding: 10px 8px; text-align: center; font-weight: 700; cursor: pointer; transition: background 0.2s; font-size: 1.1rem;`; + const cellStyle = `padding: 10px 8px; text-align: center; font-weight: 700; cursor: pointer; transition: background 0.2s; font-size: 1.05rem;`; const hoverEvents = `onmouseover="this.style.background='#F1F5F9'" onmouseout="this.style.background='none'"`; // 사양 적정성 분석 데이터 계산 (운영중인 자산만) @@ -400,31 +419,31 @@ function updateDashboardData(pcs: any[], selectedDept: string) { let barGraphHtml = ''; if (activeCount > 0) { barGraphHtml = ` -
-
- ${under > 0 ? `
` : ''} - ${normal > 0 ? `
` : ''} - ${over > 0 ? `
` : ''} +
+
+ ${under > 0 ? `
` : ''} + ${normal > 0 ? `
` : ''} + ${over > 0 ? `
` : ''}
-
- ${under > 0 ? `부족 ${under}` : ''} - ${normal > 0 ? `적정 ${normal}` : ''} - ${over > 0 ? `오버 ${over}` : ''} +
+ ${under > 0 ? `부족 ${under}` : ''} + ${normal > 0 ? `적정 ${normal}` : ''} + ${over > 0 ? `오버 ${over}` : ''}
`; } else { - barGraphHtml = `운영중 자산 없음`; + barGraphHtml = `운영중 자산 없음`; } return ` -
- - + + + - @@ -456,24 +475,24 @@ function updateDashboardData(pcs: any[], selectedDept: string) { let totBarGraphHtml = ''; if (totalActive > 0) { totBarGraphHtml = ` -
-
- ${totUnder > 0 ? `
` : ''} - ${totNormal > 0 ? `
` : ''} - ${totOver > 0 ? `
` : ''} +
+
+ ${totUnder > 0 ? `
` : ''} + ${totNormal > 0 ? `
` : ''} + ${totOver > 0 ? `
` : ''}
-
- ${totUnder > 0 ? `부족 ${totUnder}` : ''} - ${totNormal > 0 ? `적정 ${totNormal}` : ''} - ${totOver > 0 ? `오버 ${totOver}` : ''} +
+ ${totUnder > 0 ? `부족 ${totUnder}` : ''} + ${totNormal > 0 ? `적정 ${totNormal}` : ''} + ${totOver > 0 ? `오버 ${totOver}` : ''}
`; } else { - totBarGraphHtml = `운영중 자산 없음`; + totBarGraphHtml = `운영중 자산 없음`; } - const cellStyleHeader = `padding: 10px 8px; text-align: center; font-weight: 800; cursor: pointer; transition: background 0.2s; background: #F8FAFC; font-size: 1.1rem;`; + const cellStyleHeader = `padding: 12px 10px; text-align: center; font-weight: 800; cursor: pointer; transition: background 0.2s; background: #F8FAFC; font-size: 1.05rem;`; const hoverEventsHeader = `onmouseover="this.style.background='#EEF2F6'" onmouseout="this.style.background='#F8FAFC'"`; matrixTbody.innerHTML = ` @@ -483,12 +502,12 @@ function updateDashboardData(pcs: any[], selectedDept: string) { ${renderMatrixRow('entry', '보급 PC (20점 ~ 40점)', '#F59E0B', entryShortage)} ${renderMatrixRow('replace', '교체 대상 PC (20점 미만 또는 Win11 불가)', '#EF4444', replaceShortage)}
- - + + - From abc531a41e29bfb32ecd0ba088fa218eee772d83 Mon Sep 17 00:00:00 2001 From: JooWangi Date: Wed, 17 Jun 2026 09:28:06 +0900 Subject: [PATCH 04/14] =?UTF-8?q?Design:=20=EB=8C=80=EC=8B=9C=EB=B3=B4?= =?UTF-8?q?=EB=93=9C=20=ED=95=98=EB=8B=A8=20=ED=91=9C=20=EC=84=B8=EB=A1=9C?= =?UTF-8?q?=EB=B9=84=EC=9C=A8=20=ED=99=95=EC=9E=A5=20=EB=B0=8F=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A1=A4=EB=B0=94=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/Dashboard/HwDashboard.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/views/Dashboard/HwDashboard.ts b/src/views/Dashboard/HwDashboard.ts index dc451ae..cb5ab72 100644 --- a/src/views/Dashboard/HwDashboard.ts +++ b/src/views/Dashboard/HwDashboard.ts @@ -42,8 +42,8 @@ export function renderHwDashboard(container: HTMLElement) { - -
+ +
@@ -175,7 +175,7 @@ export function renderHwDashboard(container: HTMLElement) {
-
From 1d32a0350bef9a873f205ef1530f8864113fa0de Mon Sep 17 00:00:00 2001 From: JooWangi Date: Thu, 18 Jun 2026 15:56:51 +0900 Subject: [PATCH 05/14] =?UTF-8?q?feat:=20=EB=93=B1=EA=B8=89=EB=B3=84=20?= =?UTF-8?q?=EC=9E=90=EC=82=B0=20=EC=A2=85=ED=95=A9=20=ED=98=84=ED=99=A9=20?= =?UTF-8?q?=EB=B0=8F=20=EC=82=AC=EC=96=91=20=EC=A0=81=EC=A0=95=EC=84=B1=20?= =?UTF-8?q?=EB=B6=84=EC=84=9D=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=205:5?= =?UTF-8?q?=20=EC=BD=A4=ED=8C=A9=ED=8A=B8=20=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/Dashboard/HwDashboard.ts | 66 +++++++++++++++--------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/views/Dashboard/HwDashboard.ts b/src/views/Dashboard/HwDashboard.ts index cb5ab72..3dcff22 100644 --- a/src/views/Dashboard/HwDashboard.ts +++ b/src/views/Dashboard/HwDashboard.ts @@ -42,8 +42,8 @@ export function renderHwDashboard(container: HTMLElement) {
- -
+ +
@@ -175,25 +175,25 @@ export function renderHwDashboard(container: HTMLElement) {
-
-
+
-
- 등급별 자산 종합 현황 및 사양 적정성 분석 +
+ 등급별 자산 종합 현황 및 사양 적정성 분석
-
구분 (등급)보유량운영중재고구매 필요사양 적정성 분석 (직무 기준)
구분 (등급)보유량운영중재고구매 필요사양 적정성 분석 (직무 기준)
${label}${data.total}대 (${totalRate}%)
${label}${data.total}대 (${totalRate}%) ${data.active}대 ${data.stock}대 ${shortage}대 + ${barGraphHtml}
합계 (Total)${totalPcs}대 (100%)합계 (Total)${totalPcs}대 (100%) ${totalActive}대 ${totalStock}대 ${totalShortage}대 + ${totBarGraphHtml}
+
- - - - - - + + + + + + @@ -405,7 +405,7 @@ function updateDashboardData(pcs: any[], selectedDept: string) { const data = matrix[gradeKey]; const totalRate = filtered.length > 0 ? Math.round((data.total / filtered.length) * 100) : 0; - const cellStyle = `padding: 10px 8px; text-align: center; font-weight: 700; cursor: pointer; transition: background 0.2s; font-size: 1.05rem;`; + const cellStyle = `padding: 6px 8px; text-align: center; font-weight: 700; cursor: pointer; transition: background 0.2s; font-size: 0.95rem;`; const hoverEvents = `onmouseover="this.style.background='#F1F5F9'" onmouseout="this.style.background='none'"`; // 사양 적정성 분석 데이터 계산 (운영중인 자산만) @@ -419,31 +419,31 @@ function updateDashboardData(pcs: any[], selectedDept: string) { let barGraphHtml = ''; if (activeCount > 0) { barGraphHtml = ` -
-
+
+
${under > 0 ? `
` : ''} ${normal > 0 ? `
` : ''} ${over > 0 ? `
` : ''}
-
- ${under > 0 ? `부족 ${under}` : ''} - ${normal > 0 ? `적정 ${normal}` : ''} - ${over > 0 ? `오버 ${over}` : ''} +
+ ${under > 0 ? `부족 ${under}` : ''} + ${normal > 0 ? `적정 ${normal}` : ''} + ${over > 0 ? `오버 ${over}` : ''}
`; } else { - barGraphHtml = `운영중 자산 없음`; + barGraphHtml = `운영중 자산 없음`; } return `
- - + + - @@ -475,24 +475,24 @@ function updateDashboardData(pcs: any[], selectedDept: string) { let totBarGraphHtml = ''; if (totalActive > 0) { totBarGraphHtml = ` -
-
+
+
${totUnder > 0 ? `
` : ''} ${totNormal > 0 ? `
` : ''} - ${totOver > 0 ? `
` : ''} + ${totOver > 0 ? `
` : ''}
-
- ${totUnder > 0 ? `부족 ${totUnder}` : ''} - ${totNormal > 0 ? `적정 ${totNormal}` : ''} - ${totOver > 0 ? `오버 ${totOver}` : ''} +
+ ${totUnder > 0 ? `부족 ${totUnder}` : ''} + ${totNormal > 0 ? `적정 ${totNormal}` : ''} + ${totOver > 0 ? `오버 ${totOver}` : ''}
`; } else { - totBarGraphHtml = `운영중 자산 없음`; + totBarGraphHtml = `운영중 자산 없음`; } - const cellStyleHeader = `padding: 12px 10px; text-align: center; font-weight: 800; cursor: pointer; transition: background 0.2s; background: #F8FAFC; font-size: 1.05rem;`; + const cellStyleHeader = `padding: 6px 8px; text-align: center; font-weight: 800; cursor: pointer; transition: background 0.2s; background: #F8FAFC; font-size: 0.95rem;`; const hoverEventsHeader = `onmouseover="this.style.background='#EEF2F6'" onmouseout="this.style.background='#F8FAFC'"`; matrixTbody.innerHTML = ` From e77c4854cb9e3f95aee1ea6c520ba1b1f7c90dc7 Mon Sep 17 00:00:00 2001 From: Taehoon Date: Thu, 18 Jun 2026 17:04:25 +0900 Subject: [PATCH 06/14] fix: restore exact matching logic for map locations --- map_config.json | 546 +++++++++++++++++++------------------ server.js | 42 ++- src/main.ts | 20 +- src/styles/map-editor.css | 47 +++- src/views/DashboardView.ts | 21 +- src/views/LocationView.ts | 2 +- src/views/MapEditor.ts | 39 ++- src/views/SW_Table.ts | 20 +- 8 files changed, 422 insertions(+), 315 deletions(-) diff --git a/map_config.json b/map_config.json index 7b4bbdc..b56bc15 100644 --- a/map_config.json +++ b/map_config.json @@ -3,55 +3,55 @@ { "x": "50.78", "y": "1.53", - "w": "45.83", - "h": "6.10" + "w": "46.10", + "h": "6.27" }, { - "x": "50.67", + "x": "50.78", "y": "10.35", - "w": "45.95", - "h": "5.99" + "w": "46.10", + "h": "6.27" }, { "x": "50.78", "y": "19.06", - "w": "45.83", - "h": "6.32" + "w": "46.10", + "h": "6.50" }, { - "x": "50.67", + "x": "50.78", "y": "27.89", - "w": "46.06", - "h": "6.32" + "w": "46.10", + "h": "6.50" }, { "x": "50.78", "y": "36.71", - "w": "45.95", - "h": "6.21" + "w": "46.10", + "h": "6.50" }, { "x": "50.78", "y": "45.64", - "w": "45.83", + "w": "46.10", "h": "6.32" }, { - "x": "50.67", + "x": "50.78", "y": "54.25", - "w": "46.06", + "w": "46.10", "h": "6.54" }, { - "x": "50.90", + "x": "50.78", "y": "63.29", - "w": "45.72", - "h": "5.99" + "w": "46.10", + "h": "6.50" }, { - "x": "50.90", + "x": "50.78", "y": "72.00", - "w": "45.72", + "w": "46.10", "h": "6.32" }, { @@ -61,212 +61,218 @@ "h": "15.58" }, { - "x": "78.67", - "y": "82.03", - "w": "17.94", - "h": "15.25" + "x": "78.62", + "y": "81.92", + "w": "18.31", + "h": "15.58" } ], "img/location_photo/IDC/서관202.png": [ { - "x": "56.35", + "x": "58.35", "y": "64.02", - "w": "40.41", - "h": "5.89" + "w": "40.87", + "h": "6.24" }, { "x": "56.35", "y": "71.57", - "w": "40.66", - "h": "5.89" + "w": "40.87", + "h": "6.24" }, { - "x": "56.23", - "y": "79.25", - "w": "40.53", - "h": "5.76" + "x": "56.35", + "y": "79.17", + "w": "40.87", + "h": "6.24" }, { - "x": "55.98", - "y": "86.42", - "w": "41.15", - "h": "6.27" + "x": "56.35", + "y": "86.66", + "w": "40.87", + "h": "6.24" + }, + { + "x": "56.35", + "y": "32.01", + "w": "40.87", + "h": "6.24" } ], "img/location_photo/IDC/서관203.png": [ { "x": "56.07", - "y": "2.44", - "w": "40.91", - "h": "6.40" + "y": "2.54", + "w": "41.11", + "h": "6.52" }, { "x": "56.07", "y": "10.12", - "w": "40.79", - "h": "6.27" + "w": "41.11", + "h": "6.52" }, { - "x": "55.95", + "x": "56.07", "y": "17.80", - "w": "41.04", - "h": "6.14" + "w": "41.11", + "h": "6.52" }, { - "x": "55.95", + "x": "56.07", "y": "63.51", - "w": "40.91", - "h": "6.14" + "w": "41.11", + "h": "6.52" }, { - "x": "55.95", + "x": "56.07", "y": "71.19", - "w": "41.04", - "h": "6.14" + "w": "41.11", + "h": "6.52" }, { "x": "56.07", "y": "87.70", - "w": "40.91", - "h": "6.02" + "w": "41.11", + "h": "6.52" } ], "img/location_photo/IDC/서관204.png": [ { "x": "48.87", - "y": "2.57", - "w": "47.40", - "h": "6.14" + "y": "2.73", + "w": "47.80", + "h": "6.27" }, { - "x": "49.01", + "x": "48.87", "y": "10.38", - "w": "47.40", - "h": "5.89" + "w": "47.80", + "h": "6.27" }, { "x": "48.87", "y": "17.93", - "w": "47.40", - "h": "5.89" + "w": "47.80", + "h": "6.50" }, { - "x": "48.73", + "x": "48.87", "y": "25.49", - "w": "47.69", - "h": "6.27" + "w": "47.80", + "h": "6.50" }, { "x": "48.87", "y": "33.17", - "w": "47.40", - "h": "6.02" + "w": "47.80", + "h": "6.50" }, { "x": "48.87", "y": "40.59", - "w": "47.54", - "h": "6.40" + "w": "47.80", + "h": "6.50" }, { "x": "48.87", "y": "48.40", - "w": "47.54", - "h": "6.14" + "w": "47.80", + "h": "6.50" }, { - "x": "48.73", + "x": "48.87", "y": "55.95", - "w": "47.69", - "h": "6.14" + "w": "47.80", + "h": "6.50" }, { - "x": "49.01", + "x": "48.87", "y": "63.63", - "w": "47.40", - "h": "6.14" + "w": "47.80", + "h": "6.50" }, { - "x": "48.73", + "x": "48.87", "y": "71.06", - "w": "47.54", - "h": "6.27" + "w": "47.80", + "h": "6.50" }, { "x": "48.87", "y": "78.74", - "w": "47.40", - "h": "6.27" + "w": "47.80", + "h": "6.50" }, { - "x": "49.01", + "x": "48.87", "y": "86.68", - "w": "18.76", - "h": "12.29" + "w": "18.99", + "h": "12.62" } ], "img/location_photo/IDC/동관53.png": [ { "x": "61.62", "y": "3.08", - "w": "35.63", - "h": "7.55" + "w": "35.96", + "h": "7.90" }, { - "x": "61.53", + "x": "61.62", "y": "12.68", - "w": "35.80", - "h": "7.30" + "w": "35.96", + "h": "7.90" }, { - "x": "61.70", - "y": "21.65", - "w": "35.63", - "h": "7.68" + "x": "61.62", + "y": "21.75", + "w": "35.96", + "h": "7.90" } ], "img/location_photo/IDC/동관54.png": [ { "x": "54.71", "y": "2.57", - "w": "42.21", - "h": "6.27" + "w": "42.42", + "h": "6.50" }, { "x": "54.71", "y": "10.38", - "w": "42.21", - "h": "6.14" + "w": "42.42", + "h": "6.50" }, { "x": "54.71", "y": "27.15", - "w": "41.97", - "h": "6.27" + "w": "42.42", + "h": "6.62" }, { "x": "54.71", "y": "43.54", - "w": "42.09", - "h": "6.02" + "w": "42.42", + "h": "6.50" }, { "x": "54.71", "y": "54.93", - "w": "42.09", - "h": "6.40" + "w": "42.42", + "h": "6.50" }, { - "x": "54.83", + "x": "54.71", "y": "70.16", - "w": "42.09", - "h": "6.27" + "w": "42.42", + "h": "6.50" }, { "x": "54.71", "y": "79.51", - "w": "42.09", - "h": "6.14" + "w": "42.42", + "h": "6.50" } ], "img/location_photo/기술개발센터/서버실_1.png": [ @@ -421,286 +427,286 @@ { "x": "49.33", "y": "14.99", - "w": "7.13", - "h": "11.01" + "w": "7.35", + "h": "11.22" }, { "x": "59.23", - "y": "14.73", - "w": "7.13", - "h": "11.14" + "y": "14.99", + "w": "7.35", + "h": "11.22" }, { "x": "69.22", - "y": "14.86", - "w": "7.13", - "h": "11.14" + "y": "14.99", + "w": "7.35", + "h": "11.22" }, { "x": "78.96", "y": "14.99", - "w": "7.30", - "h": "11.01" + "w": "7.35", + "h": "11.22" }, { "x": "89.03", "y": "14.99", - "w": "7.05", - "h": "11.14" + "w": "7.35", + "h": "11.22" }, { "x": "48.57", - "y": "34.19", - "w": "7.39", - "h": "11.14" + "y": "34.11", + "w": "7.52", + "h": "11.44" }, { "x": "56.80", - "y": "34.06", - "w": "7.22", - "h": "11.27" + "y": "34.11", + "w": "7.52", + "h": "11.44" }, { "x": "64.94", - "y": "34.19", - "w": "7.30", - "h": "11.01" + "y": "34.11", + "w": "7.52", + "h": "11.44" }, { - "x": "72.83", - "y": "34.19", - "w": "7.47", - "h": "10.88" + "x": "72.89", + "y": "34.11", + "w": "7.56", + "h": "11.44" }, { "x": "81.22", "y": "34.06", - "w": "7.22", - "h": "11.14" + "w": "7.52", + "h": "11.44" }, { "x": "89.36", - "y": "34.19", - "w": "7.13", - "h": "11.01" + "y": "34.06", + "w": "7.52", + "h": "11.44" }, { - "x": "48.66", - "y": "53.52", + "x": "48.57", + "y": "53.06", "w": "9.06", "h": "20.99" }, { "x": "58.48", - "y": "53.27", - "w": "9.15", - "h": "21.12" + "y": "53.06", + "w": "9.06", + "h": "20.99" }, { "x": "68.55", - "y": "53.27", + "y": "53.06", "w": "9.06", - "h": "21.12" + "h": "20.99" }, { "x": "78.54", - "y": "53.39", - "w": "8.90", - "h": "21.25" + "y": "53.06", + "w": "9.01", + "h": "20.99" }, { "x": "89.36", - "y": "53.27", - "w": "7.39", - "h": "9.99" + "y": "53.22", + "w": "7.45", + "h": "10.11" }, { "x": "89.36", "y": "64.92", - "w": "7.39", - "h": "9.60" + "w": "7.45", + "h": "9.81" }, { "x": "48.57", - "y": "77.08", - "w": "9.40", - "h": "21.38" + "y": "77.41", + "w": "9.18", + "h": "21.45" }, { "x": "58.56", - "y": "77.20", + "y": "77.41", "w": "9.23", - "h": "21.12" + "h": "21.45" }, { "x": "68.63", - "y": "77.33", + "y": "77.41", "w": "9.06", - "h": "21.12" + "h": "21.45" }, { "x": "78.71", - "y": "77.46", + "y": "77.41", "w": "8.98", - "h": "20.99" + "h": "21.45" } ], "img/location_photo/한맥빌딩/MDF실/MDF_2.png": [ { "x": "56.59", - "y": "44.43", - "w": "40.35", - "h": "6.78" + "y": "44.53", + "w": "40.65", + "h": "6.90" }, { - "x": "56.71", + "x": "56.59", "y": "54.80", - "w": "40.24", - "h": "6.53" + "w": "40.65", + "h": "6.90" }, { - "x": "56.71", + "x": "56.59", "y": "65.94", - "w": "40.24", - "h": "6.40" + "w": "40.65", + "h": "6.90" } ], "img/location_photo/한맥빌딩/MDF실/MDF_3.png": [ { "x": "56.71", "y": "13.20", - "w": "40.24", - "h": "6.78" + "w": "40.58", + "h": "6.90" }, { - "x": "56.48", + "x": "56.71", "y": "23.57", "w": "40.58", - "h": "6.53" + "h": "6.90" }, { - "x": "56.59", + "x": "56.71", "y": "34.57", "w": "40.58", - "h": "6.27" + "h": "6.90" }, { - "x": "56.59", + "x": "56.71", "y": "44.69", - "w": "40.46", - "h": "6.66" + "w": "40.58", + "h": "6.90" }, { "x": "56.71", "y": "54.80", - "w": "40.24", - "h": "6.66" + "w": "40.58", + "h": "6.90" }, { "x": "56.71", "y": "65.81", - "w": "40.24", - "h": "6.53" + "w": "40.58", + "h": "6.90" }, { - "x": "56.59", + "x": "56.71", "y": "76.05", - "w": "40.35", - "h": "6.53" + "w": "40.58", + "h": "6.90" } ], "img/location_photo/한맥빌딩/MDF실/MDF_4.png": [ { "x": "52.36", "y": "64.02", - "w": "44.38", - "h": "6.53" + "w": "44.60", + "h": "6.73" } ], "img/location_photo/기술개발센터/서버실/서버실_1.png": [ { - "x": "69.53", - "y": "1.42", - "w": "8.58", - "h": "11.45" + "x": "69.40", + "y": "3.06", + "w": "8.61", + "h": "11.57" }, { - "x": "79.21", - "y": "1.55", - "w": "11.97", - "h": "11.32" + "x": "79.00", + "y": "3.06", + "w": "12.05", + "h": "11.57" }, { - "x": "90.24", - "y": "23.30", + "x": "90.19", + "y": "25.69", "w": "8.50", - "h": "21.49" + "h": "21.48" }, { "x": "53.07", - "y": "53.28", - "w": "8.74", - "h": "21.62" + "y": "52.94", + "w": "8.50", + "h": "21.25" }, { "x": "62.28", - "y": "53.41", - "w": "8.82", - "h": "21.49" + "y": "52.94", + "w": "8.50", + "h": "21.25" }, { "x": "71.50", - "y": "53.28", - "w": "8.90", - "h": "21.75" + "y": "52.94", + "w": "8.50", + "h": "21.25" }, { "x": "80.87", - "y": "53.15", - "w": "8.66", - "h": "21.75" + "y": "52.94", + "w": "8.50", + "h": "21.25" }, { "x": "90.08", - "y": "53.54", - "w": "8.90", - "h": "21.49" + "y": "52.94", + "w": "8.50", + "h": "21.25" }, { "x": "43.86", - "y": "76.32", - "w": "8.82", - "h": "21.75" + "y": "78.30", + "w": "8.50", + "h": "21.20" }, { "x": "53.15", - "y": "76.45", - "w": "8.66", - "h": "21.49" - }, - { - "x": "62.52", - "y": "76.57", - "w": "8.58", - "h": "21.62" - }, - { - "x": "71.65", - "y": "76.45", - "w": "8.66", - "h": "21.62" - }, - { - "x": "80.94", - "y": "76.57", - "w": "8.74", - "h": "21.49" - }, - { - "x": "90.24", - "y": "76.57", + "y": "78.30", "w": "8.50", - "h": "21.36" + "h": "21.20" + }, + { + "x": "62.32", + "y": "78.30", + "w": "8.50", + "h": "21.20" + }, + { + "x": "71.35", + "y": "78.30", + "w": "8.50", + "h": "21.20" + }, + { + "x": "80.58", + "y": "78.30", + "w": "8.50", + "h": "21.20" + }, + { + "x": "89.79", + "y": "78.30", + "w": "8.50", + "h": "21.20" } ], "img/location_photo/기술개발센터/서버실/서버실_2.png": [ @@ -714,13 +720,13 @@ "x": "49.47", "y": "12.04", "w": "47.49", - "h": "6.91" + "h": "7.04" }, { - "x": "49.60", + "x": "49.47", "y": "21.52", - "w": "47.35", - "h": "6.91" + "w": "47.49", + "h": "6.97" }, { "x": "49.47", @@ -729,40 +735,40 @@ "h": "7.04" }, { - "x": "49.60", + "x": "49.47", "y": "39.82", "w": "47.49", - "h": "6.91" + "h": "7.04" }, { "x": "49.47", "y": "50.06", - "w": "47.62", - "h": "6.91" - }, - { - "x": "49.74", - "y": "59.28", - "w": "47.22", - "h": "6.91" - }, - { - "x": "49.34", - "y": "68.37", - "w": "47.75", + "w": "47.49", "h": "7.04" }, { - "x": "49.60", - "y": "77.97", - "w": "47.22", - "h": "6.91" + "x": "49.47", + "y": "59.28", + "w": "47.49", + "h": "7.04" }, { - "x": "49.60", + "x": "49.47", + "y": "68.37", + "w": "47.49", + "h": "7.04" + }, + { + "x": "49.47", + "y": "77.97", + "w": "47.49", + "h": "6.97" + }, + { + "x": "49.47", "y": "86.93", - "w": "47.35", - "h": "7.17" + "w": "47.49", + "h": "7.04" } ] } \ No newline at end of file diff --git a/server.js b/server.js index 18a5d95..5585f16 100644 --- a/server.js +++ b/server.js @@ -675,16 +675,44 @@ app.delete('/api/system-users/:id', async (req, res) => { } }); -app.post('/api/maps/save', (req, res) => { +app.post('/api/maps/save', async (req, res) => { + let connection; try { const { path, boxes } = req.body; if (!path) return res.status(400).json({ error: 'Path is required' }); - let config = {}; - if (fs.existsSync('map_config.json')) config = JSON.parse(fs.readFileSync('map_config.json', 'utf8') || '{}'); - config[path] = boxes; - fs.writeFileSync('map_config.json', JSON.stringify(config, null, 2)); - res.json({ success: true }); - } catch (err) { handleError(res, err, 'SAVE MAPS'); } + + // 1. Get old config to track movements + let oldConfig = {}; + if (fs.existsSync('map_config.json')) { + oldConfig = JSON.parse(fs.readFileSync('map_config.json', 'utf8') || '{}'); + } + const oldBoxes = oldConfig[path] || []; + + // 2. Save new config to file + oldConfig[path] = boxes; + fs.writeFileSync('map_config.json', JSON.stringify(oldConfig, null, 2)); + + // 3. Sync Database Assets (asset_location table) + connection = await pool.getConnection(); + for (let i = 0; i < boxes.length; i++) { + const newBox = boxes[i]; + const oldBox = oldBoxes[i]; + + if (oldBox && (String(oldBox.x) !== String(newBox.x) || String(oldBox.y) !== String(newBox.y))) { + console.log(`Syncing moved box #${i+1} on ${path}: [${oldBox.x}, ${oldBox.y}] -> [${newBox.x}, ${newBox.y}]`); + await connection.query( + 'UPDATE asset_location SET loc_x = ?, loc_y = ? WHERE loc_x = ? AND loc_y = ? AND is_active = 1', + [newBox.x, newBox.y, oldBox.x, oldBox.y] + ); + } + } + + res.json({ success: true, message: 'Map and Database synced successfully' }); + } catch (err) { + handleError(res, err, 'SAVE MAPS SYNC'); + } finally { + if (connection) connection.release(); + } }); // 7. File Upload API (Base64) diff --git a/src/main.ts b/src/main.ts index 59a6375..563f2d8 100644 --- a/src/main.ts +++ b/src/main.ts @@ -156,7 +156,6 @@ function initRoleSwitcher() { if (!checkbox || !userLabel || !adminLabel) return; checkbox.addEventListener('change', () => { - const mainContent = document.getElementById('main-content')!; if (checkbox.checked) { state.currentUserRole = 'admin'; userLabel.classList.remove('active'); @@ -166,14 +165,6 @@ function initRoleSwitcher() { // 관리자 모드 전환 시 대시보드로 이동 state.activeCategory = 'hw'; state.activeSubTab = '대시보드'; - refreshView(); - renderNavigation((tab) => { - if (tab === '대시보드') { - renderDashboard(mainContent); - } else { - renderSWTable(mainContent); - } - }); } else { state.currentUserRole = 'user'; adminLabel.classList.remove('active'); @@ -183,15 +174,10 @@ function initRoleSwitcher() { // 실무자 모드 전환 시 서버 목록으로 이동 state.activeCategory = 'hw'; state.activeSubTab = '서버'; - refreshView(); - renderNavigation((tab) => { - if (tab === '대시보드') { - renderDashboard(mainContent); - } else { - renderSWTable(mainContent); - } - }); } + // 모든 렌더링을 refreshView 하나로 통합하여 규격 유지 + renderNavigation(() => refreshView()); + refreshView(); }); } diff --git a/src/styles/map-editor.css b/src/styles/map-editor.css index ca8d35d..5e4fdd6 100644 --- a/src/styles/map-editor.css +++ b/src/styles/map-editor.css @@ -88,11 +88,52 @@ .box-item { font-family: monospace; font-size: 11px; - padding: 6px; + padding: 10px 6px; border-bottom: 1px solid var(--border-color); display: flex; - justify-content: space-between; - align-items: center; + flex-direction: column; + gap: 8px; +} + +.box-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.box-index { + font-weight: bold; + color: var(--primary-color); +} + +.box-inputs { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 6px; +} + +.input-group { + display: flex; + align-items: center; + gap: 4px; +} + +.input-group label { + color: var(--text-muted); + width: 12px; +} + +.input-group input { + width: 100%; + padding: 2px 4px; + border: 1px solid var(--border-color); + border-radius: 2px; + font-size: 10px; + outline: none; +} + +.input-group input:focus { + border-color: var(--primary-color); } .box-item:hover { background: var(--white); } diff --git a/src/views/DashboardView.ts b/src/views/DashboardView.ts index ffca55d..0e8135f 100644 --- a/src/views/DashboardView.ts +++ b/src/views/DashboardView.ts @@ -3,12 +3,11 @@ import { renderHwDashboard } from './Dashboard/HwDashboard'; import { renderSwDashboard } from './Dashboard/SwDashboard'; /** - * 대시보드 렌더링 통합 허브 + * 대시보드 렌더링 통합 허브 (Vercel Style Normalized) */ export function renderDashboard(mainContent: HTMLElement) { if (!mainContent) return; - mainContent.innerHTML = ''; - + // 기존 차트 리소스 해제 if (state.activeCharts) { state.activeCharts.forEach((c: any) => { @@ -17,11 +16,21 @@ export function renderDashboard(mainContent: HTMLElement) { } state.activeCharts = []; + mainContent.innerHTML = ` +
+
+
+
+
+ `; + + const innerContent = document.getElementById('dashboard-inner-content')!; + if (state.activeCategory === 'hw') { - renderHwDashboard(mainContent); + renderHwDashboard(innerContent); } else if (state.activeCategory === 'sw') { - renderSwDashboard(mainContent); + renderSwDashboard(innerContent); } else { - mainContent.innerHTML = `
운영 서비스 대시보드는 준비 중입니다.
`; + innerContent.innerHTML = `
해당 카테고리의 대시보드는 준비 중입니다.
`; } } diff --git a/src/views/LocationView.ts b/src/views/LocationView.ts index 7fb3f8e..5554972 100644 --- a/src/views/LocationView.ts +++ b/src/views/LocationView.ts @@ -25,7 +25,7 @@ export async function renderLocationView(container: HTMLElement) { : []; const mapPath = locImages[currentPage] || ''; - // 자산이 등록된 구역만 필터링 + // 조회 모드: 자산이 등록된 구역만 필터링하여 노출 const allBoxes = mapConfig[mapPath] || []; const boxes = allBoxes.filter((box: any) => state.masterData.hw.some(a => diff --git a/src/views/MapEditor.ts b/src/views/MapEditor.ts index 7e815d1..78eeb1d 100644 --- a/src/views/MapEditor.ts +++ b/src/views/MapEditor.ts @@ -213,10 +213,45 @@ export class MapEditor { const item = document.createElement('div'); item.className = 'box-item'; item.innerHTML = ` - #${i+1}: [${box.x}, ${box.y}] - +
+ #${i+1} + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
`; this.boxListEl.appendChild(item); }); + + // Add events to new inputs + this.boxListEl.querySelectorAll('input').forEach(input => { + input.addEventListener('change', (e) => { + const target = e.target as HTMLInputElement; + const index = parseInt(target.dataset.index!); + const prop = target.dataset.prop!; + const val = parseFloat(target.value).toFixed(2); + + if (this.boxes[index]) { + this.boxes[index][prop] = val; + this.render(); // Re-render to update the map and sync other inputs + } + }); + }); } } diff --git a/src/views/SW_Table.ts b/src/views/SW_Table.ts index aed2659..efa0ae1 100644 --- a/src/views/SW_Table.ts +++ b/src/views/SW_Table.ts @@ -15,18 +15,22 @@ import { renderGiftList } from './List/GiftListView'; import { renderFacilityList } from './List/FacilityListView'; import { renderCostList } from './List/CostListView'; import { renderUserList } from './List/UserListView'; -import { createIcons, Plus, X, LayoutDashboard, Monitor, Server, Database, Laptop, CalendarClock, Key, Cpu, Layers, Users, Paperclip, Edit2, RefreshCcw, Settings } from 'lucide'; +import { createIcons, Plus, X, LayoutDashboard, Monitor, Server, Database, Laptop, CalendarClock, Key, Cpu, Layers, Users, Paperclip, Edit2, RefreshCcw, BookOpen, Settings } from 'lucide'; /** - * 자산 목록 테이블 렌더링 통합 허브 + * 자산 목록 테이블 렌더링 통합 허브 (Vercel Style Normalized) */ export function renderSWTable(mainContent: HTMLElement) { if (!mainContent) return; console.log(`📂 Rendering Table for: ${state.activeCategory} / ${state.activeSubTab}`); - mainContent.innerHTML = ''; - const container = document.createElement('div'); - container.className = 'view-container'; + mainContent.innerHTML = ` +
+
+
+ `; + + const container = document.getElementById('list-view-container')!; try { const tab = state.activeSubTab; @@ -69,11 +73,9 @@ export function renderSWTable(mainContent: HTMLElement) { } } - mainContent.appendChild(container); - - // 전역 아이콘 초기화 (한 번 더 실행하여 누락 방지) + // 전역 아이콘 초기화 createIcons({ - icons: { Plus, X, LayoutDashboard, Monitor, Server, Database, Laptop, CalendarClock, Key, Cpu, Layers, Users, Paperclip, Edit2, RefreshCcw, Settings } + icons: { Plus, X, LayoutDashboard, Monitor, Server, Database, Laptop, CalendarClock, Key, Cpu, Layers, Users, Paperclip, Edit2, RefreshCcw, BookOpen, Settings } }); } catch (err: any) { console.error('❌ Error rendering table view:', err); From f656f0a43979b53e9a887ada3b3dda7295874f39 Mon Sep 17 00:00:00 2001 From: JooWangi Date: Thu, 18 Jun 2026 19:48:23 +0900 Subject: [PATCH 07/14] =?UTF-8?q?fix:=20=EB=8C=80=EC=8B=9C=EB=B3=B4?= =?UTF-8?q?=EB=93=9C=20=EC=82=AC=EC=96=91=20=EC=A0=81=EC=A0=95=EC=84=B1=20?= =?UTF-8?q?=EC=A7=81=EB=AC=B4=20=EB=A7=A4=ED=95=91=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?(system=5Fusers.position=20=EC=9A=B0=EC=84=A0=20=EC=B0=B8?= =?UTF-8?q?=EC=A1=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - HwDashboard: asset_core.user_position 대신 system_users.user_name -> position 으로 세부 직무 조회 - ListFactory: 동일하게 세부 직무명 우선 참조 - 미니 모달 조직(직무) 컬럼: _resolved_position 사용으로 정확한 직무명 표시 - 수정된 필드명: u.name -> u.user_name (system_users 실제 컬럼명 반영) - 예) 디자이너(3D, 영상) 직군이 최상급 기준으로 올바르게 판정됨 --- src/views/Dashboard/HwDashboard.ts | 480 +++++++----- src/views/List/ListFactory.ts | 1114 +++++++++++----------------- 2 files changed, 708 insertions(+), 886 deletions(-) diff --git a/src/views/Dashboard/HwDashboard.ts b/src/views/Dashboard/HwDashboard.ts index 3dcff22..be7ac87 100644 --- a/src/views/Dashboard/HwDashboard.ts +++ b/src/views/Dashboard/HwDashboard.ts @@ -9,6 +9,55 @@ declare var Chart: any; let donutChartInstance: any = null; export function renderHwDashboard(container: HTMLElement) { + // 전역 툴팁 헬퍼 함수 등록 + (window as any).showSpecTooltip = function(event: MouseEvent, element: HTMLElement, type: string, count: number) { + const container = element.closest('.spec-bar-container'); + if (!container) return; + const tooltip = container.querySelector('.spec-tooltip') as HTMLElement; + if (!tooltip) return; + const textSpan = tooltip.querySelector('.tooltip-text') as HTMLElement; + if (textSpan) { + let color = ''; + let label = ''; + if (type === 'under') { + color = '#EF4444'; + label = '부족'; + } else if (type === 'normal') { + color = '#10B981'; + label = '적정'; + } else if (type === 'over') { + color = '#F59E0B'; + label = '오버'; + } else if (type === 'win11') { + color = '#7928ca'; + label = '윈도우 11 불가'; + } + textSpan.innerHTML = `${label} ${count}대`; + } + tooltip.style.left = event.clientX + 'px'; + tooltip.style.top = event.clientY + 'px'; + tooltip.style.opacity = '1'; + }; + + (window as any).updateSpecTooltipPos = function(event: MouseEvent, element: HTMLElement) { + const container = element.closest('.spec-bar-container'); + if (!container) return; + const tooltip = container.querySelector('.spec-tooltip') as HTMLElement; + if (tooltip) { + tooltip.style.left = event.clientX + 'px'; + tooltip.style.top = event.clientY + 'px'; + } + }; + + (window as any).hideSpecTooltip = function(element: HTMLElement) { + const container = element.closest('.spec-bar-container'); + if (!container) return; + const tooltip = container.querySelector('.spec-tooltip') as HTMLElement; + if (tooltip) { + tooltip.style.opacity = '0'; + } + }; + // 1. 개인용 PC 데이터 추출 (유형이 '개인PC'이거나 상태가 '재고' 또는 '대기' 상태인 PC 집계) const pcs = (state.masterData.pc || []).filter((a: any) => a.asset_type === '개인PC' || @@ -17,19 +66,11 @@ export function renderHwDashboard(container: HTMLElement) { // 2. 1페이지 매거진 리포트(제목바 제거, '| 제목' 미니멀리즘 스타일) HTML 빌드 container.innerHTML = ` -
+
- -
-
-

- 개인 PC 자산 대시보드 -

-
- - -
- 조직 필터: + +
+
@@ -42,125 +83,108 @@ export function renderHwDashboard(container: HTMLElement) {
- -
+ +
- -
+ +
-
-
- +
+
+ 보유 자산 수량
-
- - 보유 자산 수량 -
-
0대
+
0대
-
-
- +
+
+ 사양 부족
-
- - 사양 부족 -
-
0대
+
0대
-
-
- +
+
+ 오버 스펙
-
- - 오버 스펙 -
-
0대
+
0대
-
-
- +
+
+ 윈도우 11 불가
-
- - 윈도우 11 불가 PC -
-
0대
+
0대
- -
+ +
- -
+ +
- 등급별 보유 비율 + 조직별 사용 비율
- +
-
+
- - 최상급 + + 한맥
- - 상급 + + 삼안 +
+
+ + 장헌 +
+
+ + 한라
- 중급 + 기술개발센터
- - 보급 + + 총괄기획실
- - 교체 + + 기타
- -
+ +
- 연도별 PC 노후도 및 예측 + PC 노후도
-
구분 (등급)보유량운영중재고구매 필요사양 적정성 분석 (직무 기준)구분 (등급)보유량운영중재고구매 필요사양 적정성 분석 (직무 기준)
${label}${data.total}대 (${totalRate}%)${label}${data.total}대 (${totalRate}%) ${data.active}대 ${data.stock}대 ${shortage}대 + ${barGraphHtml}
+
- - - + + @@ -174,26 +198,25 @@ export function renderHwDashboard(container: HTMLElement) { - -
-
+ +
+
-
- 등급별 자산 종합 현황 및 사양 적정성 분석 +
+ 등급별 자산 종합현황
-
-
구분 (연한)보유권장 조치구분 (연한)보유
+
+
- - - - - - + + + + + + @@ -226,7 +249,16 @@ export function renderHwDashboard(container: HTMLElement) { }); btn.classList.add('active'); - btn.style.background = '#1E5149'; + const dept = btn.getAttribute('data-dept') || ''; + let bgColor = '#1E5149'; + if (dept === '한맥') bgColor = '#D02121'; + else if (dept === '삼안') bgColor = '#F58120'; + else if (dept === '장헌') bgColor = '#3889C7'; + else if (dept === '한라') bgColor = '#79B2D9'; + else if (dept === '기술개발센터') bgColor = '#10B981'; + else if (dept === '총괄기획실') bgColor = '#133D84'; + + btn.style.background = bgColor; btn.style.color = 'white'; const selectedDept = btn.getAttribute('data-dept') || ''; @@ -252,13 +284,31 @@ function updateDashboardData(pcs: any[], selectedDept: string) { }); // 3. DB 기준 사양 데이터 맵핑 (state.masterData.jobSpecs 이용) - const jobSpecsMap: Record = {}; + const jobSpecsMap: Record = {}; if (state.masterData.jobSpecs) { state.masterData.jobSpecs.forEach((s: any) => { - jobSpecsMap[s.job_name] = s.min_score; + jobSpecsMap[s.job_name] = s.required_grade || '중급'; }); } + // 사용자 이름 → 세부 직무 맵 생성 (system_users.position 기준, 더 정확한 직무 구분) + const userPositionMap: Record = {}; + if (state.masterData.users) { + state.masterData.users.forEach((u: any) => { + if (u.user_name && u.position) { + userPositionMap[u.user_name.trim()] = u.position.trim(); + } + }); + } + + const GRADE_RANK: Record = { + 'premium': 4, '최상급': 4, + 'high': 3, '상급': 3, + 'normal': 2, '중급': 2, + 'entry': 1, '보급': 1, + 'replace': 0, '교체 대상': 0 + }; + const jobScores: Record = {}; pcs.forEach((p: any) => { const score = calculatePcScoreDeductive(p.cpu, p.ram, p.gpu, p.purchase_date); @@ -306,7 +356,7 @@ function updateDashboardData(pcs: any[], selectedDept: string) { currentGradeKey = 'high'; } else if (score >= 40) { currentGradeKey = 'normal'; - } else if (score >= 20 && !win11Incompatible) { + } else if (score >= 20) { currentGradeKey = 'entry'; } else { currentGradeKey = 'replace'; @@ -323,23 +373,32 @@ function updateDashboardData(pcs: any[], selectedDept: string) { currentTarget.active++; currentTarget.activePcs.push(p); - // 직무 적정성 계산 (재직 중이고 실 사용자 매핑 자산만 검토 대상) - const job = p[ASSET_SCHEMA.USER_POSITION.key] || '미분류'; - const standardScore = jobSpecsMap[job] !== undefined ? jobSpecsMap[job] : (jobScores[job]?.avg || 0); + // 직무 적정성 계산: system_users.position 우선 조회 → asset_core.user_position fallback + const userName = (p[ASSET_SCHEMA.CURRENT_USER.key] || '').trim(); + const job = userPositionMap[userName] || p[ASSET_SCHEMA.USER_POSITION.key] || '미분류'; + const requiredGrade = jobSpecsMap[job] || jobSpecsMap[p[ASSET_SCHEMA.USER_POSITION.key]] || '중급'; // 세부 직무 우선, 없으면 일반 직무, 없으면 기본 중급 + + // 미니 모달 표시용으로 해석된 세부 직무명 저장 + p._resolved_position = job; + + const actualGrade = currentGradeKey; // premium, high, normal, entry, replace 중 하나 + + const reqRank = GRADE_RANK[requiredGrade] !== undefined ? GRADE_RANK[requiredGrade] : 2; // '중급' rank + const actRank = GRADE_RANK[actualGrade] !== undefined ? GRADE_RANK[actualGrade] : 0; let isUnder = false; - if (standardScore > 0 && job !== '재고PC') { - if (score < standardScore * 0.6) { + if (job !== '재고PC') { + if (win11Incompatible) { isUnder = true; p._spec_status = '사양 부족'; - } else if (score > standardScore * 1.5 && !win11Incompatible) { + } else if (actRank < reqRank) { + isUnder = true; + p._spec_status = '사양 부족'; + } else if (actRank > reqRank) { p._spec_status = '오버스펙'; criticalList.push(p); overSpecCount++; - } else if (win11Incompatible) { - isUnder = true; - p._spec_status = '사양 부족'; } else { p._spec_status = '적정'; } @@ -357,16 +416,11 @@ function updateDashboardData(pcs: any[], selectedDept: string) { underSpecCount++; // 2. 사양 부족 시 교체받아야 할 직무별 권장 목표 등급 판정 - let targetGradeKey: keyof typeof matrix; - if (standardScore >= 85) { - targetGradeKey = 'premium'; - } else if (standardScore >= 70) { - targetGradeKey = 'high'; - } else if (standardScore >= 40) { - targetGradeKey = 'normal'; - } else { - targetGradeKey = 'entry'; // 교체 대상은 최소 보급형 사양으로 교체 - } + let targetGradeKey: keyof typeof matrix = 'normal'; + if (requiredGrade === '최상급') targetGradeKey = 'premium'; + else if (requiredGrade === '상급') targetGradeKey = 'high'; + else if (requiredGrade === '중급') targetGradeKey = 'normal'; + else if (requiredGrade === '보급') targetGradeKey = 'entry'; const targetGrade = matrix[targetGradeKey]; targetGrade.under++; @@ -390,60 +444,76 @@ function updateDashboardData(pcs: any[], selectedDept: string) { const matrixTbody = document.getElementById('pc-grade-matrix-tbody')!; const getSpecStatusCounts = (activePcsList: any[]) => { + let win11 = 0; let under = 0; let normal = 0; let over = 0; activePcsList.forEach(p => { - if (p._spec_status === '사양 부족') under++; + if (isWindows11Incompatible(p.cpu, p.ram)) win11++; + else if (p._spec_status === '사양 부족') under++; else if (p._spec_status === '오버스펙') over++; else normal++; }); - return { under, normal, over }; + return { win11, under, normal, over }; }; + const maxTotal = Math.max( + matrix.premium.total, + matrix.high.total, + matrix.normal.total, + matrix.entry.total, + matrix.replace.total + ); + const renderMatrixRow = (gradeKey: keyof typeof matrix, label: string, color: string, shortage: number) => { const data = matrix[gradeKey]; const totalRate = filtered.length > 0 ? Math.round((data.total / filtered.length) * 100) : 0; - const cellStyle = `padding: 6px 8px; text-align: center; font-weight: 700; cursor: pointer; transition: background 0.2s; font-size: 0.95rem;`; + const cellStyle = `padding: 22px 8px; text-align: center; font-weight: 700; cursor: pointer; transition: background 0.2s; font-size: 1.05rem;`; const hoverEvents = `onmouseover="this.style.background='#F1F5F9'" onmouseout="this.style.background='none'"`; // 사양 적정성 분석 데이터 계산 (운영중인 자산만) - const { under, normal, over } = getSpecStatusCounts(data.activePcs); + const { win11, under, normal, over } = getSpecStatusCounts(data.activePcs); const activeCount = data.active; + const win11Pct = activeCount > 0 ? (win11 / activeCount) * 100 : 0; const underPct = activeCount > 0 ? (under / activeCount) * 100 : 0; const normalPct = activeCount > 0 ? (normal / activeCount) * 100 : 0; const overPct = activeCount > 0 ? (over / activeCount) * 100 : 0; + const rowTotal = data.total; + const barWidthPct = maxTotal > 0 ? (rowTotal / maxTotal) * 100 : 0; + let barGraphHtml = ''; if (activeCount > 0) { barGraphHtml = ` -
-
- ${under > 0 ? `
` : ''} - ${normal > 0 ? `
` : ''} - ${over > 0 ? `
` : ''} +
+ +
+ ${win11 > 0 ? `
` : ''} + ${under > 0 ? `
` : ''} + ${normal > 0 ? `
` : ''} + ${over > 0 ? `
` : ''}
-
- ${under > 0 ? `부족 ${under}` : ''} - ${normal > 0 ? `적정 ${normal}` : ''} - ${over > 0 ? `오버 ${over}` : ''} + +
+ +
`; } else { - barGraphHtml = `운영중 자산 없음`; + barGraphHtml = `운영중 자산 없음`; } return `
- - + + - @@ -467,7 +537,7 @@ function updateDashboardData(pcs: any[], selectedDept: string) { const totalShortage = premiumShortage + highShortage + normalShortage + entryShortage + replaceShortage; const totalActivePcs = filtered.filter(p => !isStock(p)); - const { under: totUnder, normal: totNormal, over: totOver } = getSpecStatusCounts(totalActivePcs); + const { win11: totWin11, under: totUnder, normal: totNormal, over: totOver } = getSpecStatusCounts(totalActivePcs); const totUnderPct = totalActive > 0 ? (totUnder / totalActive) * 100 : 0; const totNormalPct = totalActive > 0 ? (totNormal / totalActive) * 100 : 0; const totOverPct = totalActive > 0 ? (totOver / totalActive) * 100 : 0; @@ -475,24 +545,25 @@ function updateDashboardData(pcs: any[], selectedDept: string) { let totBarGraphHtml = ''; if (totalActive > 0) { totBarGraphHtml = ` -
-
- ${totUnder > 0 ? `
` : ''} - ${totNormal > 0 ? `
` : ''} - ${totOver > 0 ? `
` : ''} +
+ +
+ ${totUnder > 0 ? `
` : ''} + ${totNormal > 0 ? `
` : ''} + ${totOver > 0 ? `
` : ''}
-
- ${totUnder > 0 ? `부족 ${totUnder}` : ''} - ${totNormal > 0 ? `적정 ${totNormal}` : ''} - ${totOver > 0 ? `오버 ${totOver}` : ''} + +
+ +
`; } else { - totBarGraphHtml = `운영중 자산 없음`; + totBarGraphHtml = `운영중 자산 없음`; } - const cellStyleHeader = `padding: 6px 8px; text-align: center; font-weight: 800; cursor: pointer; transition: background 0.2s; background: #F8FAFC; font-size: 0.95rem;`; + const cellStyleHeader = `padding: 12px 10px; text-align: center; font-weight: 800; cursor: pointer; transition: background 0.2s; background: #F8FAFC; font-size: 1.05rem;`; const hoverEventsHeader = `onmouseover="this.style.background='#EEF2F6'" onmouseout="this.style.background='#F8FAFC'"`; matrixTbody.innerHTML = ` @@ -500,17 +571,7 @@ function updateDashboardData(pcs: any[], selectedDept: string) { ${renderMatrixRow('high', '상급 PC (70점 ~ 85점)', '#1E8E7C', highShortage)} ${renderMatrixRow('normal', '중급 PC (40점 ~ 70점)', '#10B981', normalShortage)} ${renderMatrixRow('entry', '보급 PC (20점 ~ 40점)', '#F59E0B', entryShortage)} - ${renderMatrixRow('replace', '교체 대상 PC (20점 미만 또는 Win11 불가)', '#EF4444', replaceShortage)} -
- - - - - - - + ${renderMatrixRow('replace', '교체 대상 PC (20점 미만)', '#EF4444', replaceShortage)} `; // 셀별 동적 클릭 리스너 바인딩 @@ -535,7 +596,7 @@ function updateDashboardData(pcs: any[], selectedDept: string) { if (t === 'total') return '보유'; if (t === 'active') return '운영중'; if (t === 'stock') return '재고'; - if (t === 'under') return '구매 필요'; + if (t === 'under') return '부족분'; return ''; }; @@ -575,11 +636,21 @@ function updateDashboardData(pcs: any[], selectedDept: string) { const status = target.getAttribute('data-spec-status')!; let targetPcs: any[] = []; + const filterFn = (p: any) => { + if (status === '윈도우 11 불가') { + return isWindows11Incompatible(p.cpu, p.ram); + } else if (status === '사양 부족') { + return !isWindows11Incompatible(p.cpu, p.ram) && p._spec_status === '사양 부족'; + } else { + return p._spec_status === status; + } + }; + if (grade === 'all') { - targetPcs = filtered.filter(p => !isStock(p) && p._spec_status === status); + targetPcs = filtered.filter(p => !isStock(p) && filterFn(p)); } else { const data = matrix[grade as keyof typeof matrix]; - targetPcs = data.activePcs.filter(p => p._spec_status === status); + targetPcs = data.activePcs.filter(filterFn); } const getGradeLabel = (g: string) => { @@ -622,23 +693,20 @@ function updateDashboardData(pcs: any[], selectedDept: string) { const agingTbody = document.getElementById('pc-aging-tbody')!; - const renderAgingRow = (label: string, list: any[], badgeText: string, badgeStyle: string, ageGroupKey: string) => { + const renderAgingRow = (label: string, list: any[], ageGroupKey: string) => { return ` - - - + + `; }; agingTbody.innerHTML = ` - ${renderAgingRow('즉시 교체 (7년 이상)', agingCounts.immediate, '즉시 교체', 'background:#FFF1F2; color:#EF4444; border:1px solid #FCA5A5;', 'immediate')} - ${renderAgingRow('교체 검토 (3년 ~ 7년)', agingCounts.review, '교체 검토', 'background:#FFF7ED; color:#D97706; border:1px solid #FCD34D;', 'review')} - ${renderAgingRow('정상 운용 (1년 ~ 3년)', agingCounts.normal, '정상 운용', 'background:#ECFDF5; color:#059669; border:1px solid #A7F3D0;', 'normal')} - ${renderAgingRow('최신 도입 (1년 미만)', agingCounts.fresh, '최신 도입', 'background:#F0FDF4; color:#16A34A; border:1px solid #BBF7D0;', 'fresh')} + ${renderAgingRow('즉시 교체 (7년 이상)', agingCounts.immediate, 'immediate')} + ${renderAgingRow('교체 검토 (3년 ~ 7년)', agingCounts.review, 'review')} + ${renderAgingRow('정상 운용 (1년 ~ 3년)', agingCounts.normal, 'normal')} + ${renderAgingRow('최신 도입 (1년 미만)', agingCounts.fresh, 'fresh')} `; agingTbody.querySelectorAll('.aging-row').forEach(row => { @@ -656,14 +724,14 @@ function updateDashboardData(pcs: any[], selectedDept: string) { }); // 8. 요약 지표 카드 클릭 리스너 설정 - const bindCardClick = (id: string, gradeTitle: string, filterFn: (p: any) => boolean) => { + const bindCardClick = (id: string, gradeTitle: string, filterFn: (p: any) => boolean, hoverBgColor: string) => { const card = document.getElementById(id)!; if (!card) return; card.style.cursor = 'pointer'; - card.style.transition = 'opacity 0.2s'; + card.style.transition = 'background-color 0.15s ease'; - card.onmouseover = () => { card.style.opacity = '0.7'; }; - card.onmouseout = () => { card.style.opacity = '1'; }; + card.onmouseover = () => { card.style.backgroundColor = hoverBgColor; }; + card.onmouseout = () => { card.style.backgroundColor = '#ffffff'; }; card.onclick = () => { const pcsInGrade = filtered.filter(filterFn); @@ -672,12 +740,48 @@ function updateDashboardData(pcs: any[], selectedDept: string) { }; // 사양 부족 / 오버 스펙 / 윈도우 11 불가 클릭 리스너 설정 - bindCardClick('card-under-spec', '사양 부족 대상', p => p._spec_status === '사양 부족'); - bindCardClick('card-over-spec', '오버 스펙 대상', p => p._spec_status === '오버스펙'); - bindCardClick('card-win11-incompatible', '윈도우 11 업그레이드 불가 PC', p => isWindows11Incompatible(p.cpu, p.ram)); + bindCardClick('card-under-spec', '사양 부족 대상', p => p._spec_status === '사양 부족', '#FEF2F2'); + bindCardClick('card-over-spec', '오버 스펙 대상', p => p._spec_status === '오버스펙', '#FFFBEB'); + bindCardClick('card-win11-incompatible', '윈도우 11 업그레이드 불가 PC', p => isWindows11Incompatible(p.cpu, p.ram), '#F5F3FF'); + + // 9. 조직별 사용 비율 집계 (전체 개인용 PC 기준) + const deptCounts: Record = { + '한맥': 0, + '삼안': 0, + '장헌': 0, + '한라': 0, + '기술개발센터': 0, + '총괄기획실': 0, + '기타': 0 + }; + + pcs.forEach((p: any) => { + const dept = String(p[ASSET_SCHEMA.CURRENT_DEPT.key] || '').trim(); + let matched = false; + for (const key of Object.keys(deptCounts)) { + if (key !== '기타' && dept.includes(key)) { + deptCounts[key]++; + matched = true; + break; + } + } + if (!matched) { + deptCounts['기타']++; + } + }); + + const deptChartData = [ + { label: '한맥', count: deptCounts['한맥'], color: '#D02121' }, + { label: '삼안', count: deptCounts['삼안'], color: '#F58120' }, + { label: '장헌', count: deptCounts['장헌'], color: '#3889C7' }, + { label: '한라', count: deptCounts['한라'], color: '#79B2D9' }, + { label: '기술개발센터', count: deptCounts['기술개발센터'], color: '#10B981' }, + { label: '총괄기획실', count: deptCounts['총괄기획실'], color: '#133D84' }, + { label: '기타', count: deptCounts['기타'], color: '#94A3B8' } + ]; // 10. 도넛 차트 렌더링 호출 - renderDonutChart(matrix.premium.total, matrix.high.total, matrix.normal.total, matrix.entry.total, matrix.replace.total); + renderDonutChart(deptChartData); // 전역 상태 등록 state.activeCharts = [donutChartInstance]; @@ -746,7 +850,7 @@ function showMiniListModal(title: string, list: any[]) { return ` - + @@ -802,7 +906,7 @@ function showMiniListModal(title: string, list: any[]) { /** * 실시간 사양 적정률 원형 도넛 그래프 (Active Spec Rate) */ -function renderDonutChart(premium: number, high: number, normal: number, entry: number, replace: number) { +function renderDonutChart(deptData: { label: string; count: number; color: string }[]) { const ctx = document.getElementById('chart-overall-donut') as HTMLCanvasElement; if (!ctx || typeof Chart === 'undefined') return; @@ -811,21 +915,15 @@ function renderDonutChart(premium: number, high: number, normal: number, entry: donutChartInstance = null; } - const total = premium + high + normal + entry + replace; + const total = deptData.reduce((sum, d) => sum + d.count, 0); donutChartInstance = new Chart(ctx, { type: 'doughnut', data: { - labels: ['최상급', '상급', '중급', '보급', '교체 대상'], + labels: deptData.map(d => d.label), datasets: [{ - data: [premium, high, normal, entry, replace], - backgroundColor: [ - '#11302B', // premium (Hanmac Dark Green) - '#1E8E7C', // high (Hanmac Teal) - '#10B981', // normal (Hanmac Mint) - '#F59E0B', // entry (Yellow-Orange) - '#EF4444' // replace (Red) - ], + data: deptData.map(d => d.count), + backgroundColor: deptData.map(d => d.color), borderColor: '#ffffff', borderWidth: 2 }] diff --git a/src/views/List/ListFactory.ts b/src/views/List/ListFactory.ts index ae7a26b..28bcd40 100644 --- a/src/views/List/ListFactory.ts +++ b/src/views/List/ListFactory.ts @@ -153,6 +153,7 @@ export interface ListViewConfig { showField?: boolean; showType?: boolean; showStatus?: boolean; + showPosition?: boolean; }; columns: ColumnDef[]; onRowClick?: (asset: any) => void; @@ -161,9 +162,8 @@ export interface ListViewConfig { } export function createListView(container: HTMLElement, config: ListViewConfig) { - // 1. 컨테이너 초기화 및 헤더 렌더링 + // 1. 컨테이너 초기화 container.innerHTML = ''; - renderPageHeader(container, config.title); const fullList = config.dataSource(); let sortState: SortState = config.persistentSortState || { key: '', direction: 'asc' }; @@ -181,46 +181,20 @@ export function createListView(container: HTMLElement, config: ListViewConfig) { const isServer = config.title === '서버'; if (!isServer) { (state as any).currentViewMode = 'asset'; - } else if (!(state as any).currentViewMode) { - (state as any).currentViewMode = 'system'; } - // 2. 뷰 전환 토글 버튼 생성 (명칭 변경) - const toggleWrapper = document.createElement('div'); - toggleWrapper.className = 'view-toggle-container'; - - const showPcFlowBtn = config.title === 'PC'; - toggleWrapper.innerHTML = ` -
-
- - -
-
- ${showPcFlowBtn ? ` - - - ` : ''} - -
-
- `; - container.appendChild(toggleWrapper); - - // 3. 필터 바 생성 (자산 목록에서만 사용) - const filterBar = document.createElement('div'); - filterBar.className = 'search-bar'; - container.appendChild(filterBar); - - // 4. 컨텐츠 영역 생성 + // 1. 컨텐츠 영역 생성 (먼저 생성하여 참조 가능하게 함) const contentWrapper = document.createElement('div'); contentWrapper.className = 'view-content-wrapper'; + + // 2. 필터 바 생성 (자산 목록에서만 사용) + const filterBar = document.createElement('div'); + filterBar.className = 'search-bar'; + + // 자산 추가 버튼 및 목록 보기 체크박스 추가 로직 + const showPcFlowBtn = config.title === 'PC'; + + container.appendChild(filterBar); container.appendChild(contentWrapper); // --- 내부 상태 --- @@ -228,7 +202,6 @@ export function createListView(container: HTMLElement, config: ListViewConfig) { let selectedDetailLocation: string | null = null; let dynamicMapConfig: Record = {}; - // 맵 설정 미리 로드 const fetchMapConfig = async () => { try { const res = await fetch(`http://${location.hostname}:3000/api/maps`); @@ -254,15 +227,12 @@ export function createListView(container: HTMLElement, config: ListViewConfig) { selectedLocation = validLocations[0] || ''; } - const locationCounts: Record = {}; - const pcTypeCounts = { public: 0, server: 0, personal: 0 }; - - // 동적 통계 수집 객체 (Hardcoding 제거) + // 동적 통계 수집 객체 const extStats = { total: 0, locCounts: {} as Record, typeCounts: {} as Record, - typeLocMap: {} as Record>, // 유형별 위치 분포 + typeLocMap: {} as Record>, locWarning: 0, typeWarning: 0 }; @@ -273,41 +243,23 @@ export function createListView(container: HTMLElement, config: ListViewConfig) { typeLocMap: {} as Record> }; - // 중앙화된 경고 감지 로직 const checkAnomaly = (serviceType: string, loc: string, type: string) => { - if (serviceType !== '외부') return { isWarning: false, isLocWarning: false, isTypeWarning: false, reason: '' }; + if (serviceType !== '외부') return { isWarning: false, isLocWarning: false, isTypeWarning: false }; const isLocWarning = loc !== 'IDC' && loc !== '미지정' && loc !== ''; const isTypeWarning = type.toLowerCase().replace(/\s/g, '').includes('서버pc'); - const isWarning = isLocWarning || isTypeWarning; - - let reason = ''; - if (isLocWarning && isTypeWarning) reason = '위치/형식 부적절'; - else if (isLocWarning) reason = '위치 부적절'; - else if (isTypeWarning) reason = '형식 부적절'; - - return { isWarning, isLocWarning, isTypeWarning, reason }; + return { isWarning: isLocWarning || isTypeWarning, isLocWarning, isTypeWarning }; }; fullList.forEach(asset => { const loc = asset[ASSET_SCHEMA.LOCATION.key] || '미지정'; - const serviceTypeKey = (ASSET_SCHEMA as any).SERVICE_TYPE?.key || 'service_type'; - const serviceType = asset[serviceTypeKey] || '외부'; + const serviceType = asset.service_type || '외부'; const type = asset[ASSET_SCHEMA.ASSET_TYPE.key] || ''; - locationCounts[loc] = (locationCounts[loc] || 0) + 1; - - if (isPcView) { - if (type.includes('공용')) pcTypeCounts.public++; - else if (type.includes('서버')) pcTypeCounts.server++; - else pcTypeCounts.personal++; - } - const targetStat = serviceType === '내부' ? intStats : extStats; targetStat.total++; if (loc) targetStat.locCounts[loc] = (targetStat.locCounts[loc] || 0) + 1; if (type) { targetStat.typeCounts[type] = (targetStat.typeCounts[type] || 0) + 1; - // 유형별 위치 분포 수집 if (!targetStat.typeLocMap[type]) targetStat.typeLocMap[type] = {}; targetStat.typeLocMap[type][loc] = (targetStat.typeLocMap[type][loc] || 0) + 1; } @@ -319,180 +271,139 @@ export function createListView(container: HTMLElement, config: ListViewConfig) { } }); - // 템플릿 제너레이터 함수 (HTML 중복 제거) const generateDetailStatHTML = (title: string, stats: any) => ` -
- ${title} -
- ${stats.locWarning ? `위치부적절: ${stats.locWarning}` : ''} - ${stats.typeWarning ? `형식부적절: ${stats.typeWarning}` : ''} +
+ ${title} +
+ ${stats.locWarning ? `위치부적절: ${stats.locWarning}` : ''} + ${stats.typeWarning ? `형식부적절: ${stats.typeWarning}` : ''}
-
-
- ${Object.entries(stats.locCounts as Record).sort((a, b) => b[1] - a[1]).slice(0, 4).map(([l, c]) => `${l}: ${c}`).join('')} +
+
+ ${Object.entries(stats.locCounts as Record).sort((a, b) => b[1] - a[1]).slice(0, 4).map(([l, c]) => `${l}: ${c}`).join('')}
-
+
${Object.entries(stats.typeCounts as Record).sort((a, b) => b[1] - a[1]).slice(0, 6).map(([t, c]) => { - const isTypeWarning = title.includes('외부') && t.toLowerCase().replace(/\s/g, '').includes('서버pc'); - - // 위치별 상세 정보 생성 (툴팁용) const locDist = stats.typeLocMap[t] || {}; - const locHint = Object.entries(locDist) - .sort((a: any, b: any) => b[1] - a[1]) - .map(([l, count]) => `${l}: ${count}대`) - .join('\n'); - - return `${t}: ${c}`; + const locHint = Object.entries(locDist).sort((a: any, b: any) => b[1] - a[1]).map(([l, count]) => `${l}: ${count}대`).join('\n'); + return `${t}: ${c}`; }).join('')}
`; contentWrapper.innerHTML = ` -
- - -
-
-
총 보유 자산
-
${fullList.length}
-
- 외부: ${extStats.total} - 내부: ${intStats.total} +
+
+
+
총 보유 자산
+
${fullList.length}
+
+ 외부: ${extStats.total} + 내부: ${intStats.total}
- -
- ${isPcView ? ` -
PC 유형별 현황
-
- 공용: ${pcTypeCounts.public} - 서버: ${pcTypeCounts.server} - 개인: ${pcTypeCounts.personal} -
- ` : generateDetailStatHTML('외부 (운영) 상세', extStats)} -
- -
- ${isPcView ? '' : generateDetailStatHTML('내부 (테스트) 상세', intStats as any)} -
+
${generateDetailStatHTML('외부 (운영) 상세', extStats)}
+
${generateDetailStatHTML('내부 (테스트) 상세', intStats)}
-
- -
-
-

+
+ +
+
+ ${!isPcView ? ` -
- 위치: - ${validLocations.map(l => ``).join('')} - 상세: - +
` : ''}
-
-

구분 (등급)보유량운영중재고구매 필요사양 적정성 분석 (직무 기준)구분 (등급)보유량운영중재고부족분사양 적정성
${label}${data.total}대 (${totalRate}%)${label}${data.total}대 (${totalRate}%) ${data.active}대 ${data.stock}대 ${shortage}대 + ${barGraphHtml}
합계 (Total)${totalPcs}대 (100%)${totalActive}대${totalStock}대${totalShortage}대 - ${totBarGraphHtml} -
${label}${list.length}대 - ${badgeText} - ${label}${list.length}대
${user}${pc.current_dept || '-'} (${pc.user_position || '-'})${pc.current_dept || '-'} (${pc._resolved_position || pc.user_position || '-'}) ${spec} ${badgeHTML}${scoreHTML} ${pc.asset_code || '-'}
- +
+
+ ${isPcView ? ` - - - - - - - - + + + + + + + + ` : ` - - - - - - + + + + + + `} - +
일자담당자구분사용자인수자자산번호상세
일자담당자구분사용자인수자자산번호상세
분류용도/자산명관리자(정)관리자(부)상세위치
분류용도/자산명관리자(정)관리자(부)상세위치
- -
-
+ +
+
${isPcView ? ` -
-
-

+
+
+
-
- - - - - - - +
+
사용자부서 (직무)상태자산코드
+ + + + + + - - + +
사용자부서 (직무)상태자산코드
사양 주의 자산이 없습니다.
사양 주의 자산이 없습니다.
` : ` -

목록에서 자산을 선택하면
상세 정보와 배치도가 표시됩니다.

+

목록에서 자산을 선택하면
상세 정보와 배치도가 표시됩니다.

`}

-