From 26f9a4dea2ba640ead891de1f0e4ceac9b32e128 Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Tue, 4 Nov 2025 17:52:24 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8?= =?UTF-8?q?=EB=B3=84=20=EC=9E=91=EC=97=85=20=EB=B6=84=ED=8F=AC=20Productio?= =?UTF-8?q?n=20Report=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=99=84=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✨ 주요 기능 - 프로젝트별 → 작업유형별 데이터 취합 및 표시 - Production Report 스타일 테이블 구현 - 연차/휴무 별도 처리 (주말 제외, 녹색 테마) - Job No. 정확한 표시 (중복 제거) 🔧 API 개선 - recent-work API에 job_no 필드 추가 - MySQL 쿼리 결과 처리 수정 (results[0] 사용) - Projects 테이블 대소문자 조인 문제 해결 🎨 UI/UX 개선 - 탭 기반 분석 인터페이스 - 색상 팔레트 개선 (파란색/녹색/노란색 계열) - 텍스트 방향 수정 (가로 표시) - 프로젝트별 합계 행 추가 📊 계산 로직 - 공수: 시간 ÷ 8 - 부하율: (개별 시간 ÷ 전체 시간) × 100% - 인건비: 공수 × 350,000원 - 주말 연차 자동 제외 --- .../controllers/workAnalysisController.js | 32 +- api.hyungi.net/models/WorkAnalysis.js | 4 +- web-ui/css/work-analysis.css | 216 ++++++ web-ui/pages/analysis/work-analysis.html | 652 ++++++++++++++++-- 4 files changed, 828 insertions(+), 76 deletions(-) diff --git a/api.hyungi.net/controllers/workAnalysisController.js b/api.hyungi.net/controllers/workAnalysisController.js index ab88598..d1f3765 100644 --- a/api.hyungi.net/controllers/workAnalysisController.js +++ b/api.hyungi.net/controllers/workAnalysisController.js @@ -391,14 +391,24 @@ class WorkAnalysisController { const testResults = await db.query(testQuery, [start, end]); console.log('📊 데이터 확인:', testResults[0]); - // 프로젝트별-작업별 시간 분석 쿼리 (간단한 버전으로 테스트) + // 먼저 간단한 테스트 쿼리로 데이터 확인 + const simpleQuery = ` + SELECT COUNT(*) as count, MIN(report_date) as min_date, MAX(report_date) as max_date + FROM daily_work_reports + WHERE report_date BETWEEN ? AND ? + `; + + const simpleResult = await db.query(simpleQuery, [start, end]); + console.log('📊 기간 내 데이터 확인:', simpleResult[0][0]); + + // 프로젝트별-작업별 시간 분석 쿼리 (work_types 테이블과 조인) const query = ` SELECT - COALESCE(p.project_id, 0) as project_id, - COALESCE(p.project_name, 'Unknown Project') as project_name, + COALESCE(p.project_id, dwr.project_id) as project_id, + COALESCE(p.project_name, CONCAT('프로젝트 ', dwr.project_id)) as project_name, COALESCE(p.job_no, 'N/A') as job_no, dwr.work_type_id, - CONCAT('Work Type ', dwr.work_type_id) as work_type_name, + COALESCE(wt.name, CONCAT('작업유형 ', dwr.work_type_id)) as work_type_name, -- 총 시간 SUM(dwr.work_hours) as total_hours, @@ -421,18 +431,22 @@ class WorkAnalysisController { ) as error_rate_percent FROM daily_work_reports dwr - LEFT JOIN projects p ON dwr.project_id = p.project_id + LEFT JOIN Projects p ON dwr.project_id = p.project_id + LEFT JOIN work_types wt ON dwr.work_type_id = wt.id WHERE dwr.report_date BETWEEN ? AND ? - GROUP BY p.project_id, p.project_name, p.job_no, dwr.work_type_id - ORDER BY p.project_name, dwr.work_type_id + GROUP BY dwr.project_id, p.project_name, p.job_no, dwr.work_type_id, wt.name + ORDER BY p.project_name, wt.name `; const results = await db.query(query, [start, end]); + console.log('📊 쿼리 결과 개수:', results[0].length); + console.log('📊 첫 번째 결과:', results[0][0]); + console.log('📊 모든 결과:', JSON.stringify(results[0], null, 2)); // 데이터를 프로젝트별로 그룹화 const groupedData = {}; - results.forEach(row => { + results[0].forEach(row => { const projectKey = `${row.project_id}_${row.project_name}`; if (!groupedData[projectKey]) { @@ -476,7 +490,7 @@ class WorkAnalysisController { // 전체 요약 통계 const totalStats = { total_projects: Object.keys(groupedData).length, - total_work_types: new Set(results.map(r => r.work_type_id)).size, + total_work_types: new Set(results[0].map(r => r.work_type_id)).size, grand_total_hours: Object.values(groupedData).reduce((sum, p) => sum + p.total_project_hours, 0), grand_regular_hours: Object.values(groupedData).reduce((sum, p) => sum + p.total_regular_hours, 0), grand_error_hours: Object.values(groupedData).reduce((sum, p) => sum + p.total_error_hours, 0) diff --git a/api.hyungi.net/models/WorkAnalysis.js b/api.hyungi.net/models/WorkAnalysis.js index e12ca5a..8cca359 100644 --- a/api.hyungi.net/models/WorkAnalysis.js +++ b/api.hyungi.net/models/WorkAnalysis.js @@ -190,6 +190,7 @@ class WorkAnalysis { w.worker_name, dwr.project_id, p.project_name, + p.job_no, dwr.work_type_id, wt.name as work_type_name, dwr.work_status_id, @@ -202,7 +203,7 @@ class WorkAnalysis { dwr.created_at FROM daily_work_reports dwr LEFT JOIN workers w ON dwr.worker_id = w.worker_id - LEFT JOIN projects p ON dwr.project_id = p.project_id + LEFT JOIN Projects p ON dwr.project_id = p.project_id LEFT JOIN work_types wt ON dwr.work_type_id = wt.id LEFT JOIN work_status_types wst ON dwr.work_status_id = wst.id LEFT JOIN error_types et ON dwr.error_type_id = et.id @@ -221,6 +222,7 @@ class WorkAnalysis { worker_name: row.worker_name || `작업자 ${row.worker_id}`, project_id: row.project_id, project_name: row.project_name || `프로젝트 ${row.project_id}`, + job_no: row.job_no || 'N/A', work_type_id: row.work_type_id, work_type_name: row.work_type_name || `작업유형 ${row.work_type_id}`, work_status_id: row.work_status_id, diff --git a/web-ui/css/work-analysis.css b/web-ui/css/work-analysis.css index b892b97..9eeea8e 100644 --- a/web-ui/css/work-analysis.css +++ b/web-ui/css/work-analysis.css @@ -609,6 +609,152 @@ body { color: var(--gray-600); } +/* ========== Production Report 테이블 ========== */ +.production-report-table { + width: 100%; + border-collapse: collapse; + font-size: 0.9rem; + background: var(--white); + border: 2px solid #000; +} + +.production-report-table th { + background: #4472C4; + color: var(--white); + font-weight: 600; + padding: 1rem 0.75rem; + text-align: center; + border: 1px solid #000; + vertical-align: middle; + line-height: 1.3; +} + +.production-report-table td { + padding: 0.75rem; + border: 1px solid #000; + text-align: center; + vertical-align: middle; +} + +/* 프로젝트명 (Job No.) 스타일 */ +.production-report-table .project-name { + background: #5D9CEC; + color: var(--white); + font-weight: 600; + text-align: center; + writing-mode: horizontal-tb; + text-orientation: mixed; + width: 150px; + min-width: 150px; + padding: 0.5rem; + line-height: 1.3; +} + +/* 작업 내용 스타일 */ +.production-report-table .work-content { + background: var(--white); + color: var(--gray-800); + font-weight: 500; + text-align: center; +} + +/* 공수 스타일 */ +.production-report-table .man-days { + background: var(--white); + color: var(--gray-800); + font-weight: 600; + text-align: center; +} + +/* 부하율 스타일 */ +.production-report-table .load-rate { + background: var(--white); + color: var(--gray-800); + font-weight: 600; + text-align: center; +} + +/* 인건비 스타일 */ +.production-report-table .labor-cost { + background: var(--white); + color: var(--gray-800); + font-weight: 600; + text-align: center; +} + +/* 합계 행 스타일 */ +.production-report-table .total-row { + background: #FFF3CD !important; + font-weight: 700; + border-top: 2px solid #F39C12; +} + +.production-report-table .total-row td { + background: #FFF3CD !important; + color: #856404; + font-weight: 700; +} + +/* 프로젝트 그룹 스타일 */ +.production-report-table .project-group { + border-top: 2px solid #000; +} + +.production-report-table .project-group:first-child { + border-top: none; +} + +/* 연차/휴무 스타일 */ +.production-report-table .vacation-project { + background: #E8F5E8 !important; + border-top: 3px solid #4CAF50 !important; +} + +.production-report-table .vacation-project .project-name { + background: #4CAF50 !important; + color: var(--white) !important; + font-weight: 700; + text-align: center; + font-size: 1rem; + writing-mode: horizontal-tb !important; + text-orientation: mixed !important; +} + +.production-report-table .vacation-project .work-content { + background: #E8F5E8 !important; + color: #2E7D32 !important; + font-weight: 600; + text-align: center; +} + +.production-report-table .vacation-project td { + background: #E8F5E8 !important; + color: #2E7D32 !important; + font-weight: 500; +} + +.production-report-table .vacation-project .man-days, +.production-report-table .vacation-project .load-rate, +.production-report-table .vacation-project .labor-cost { + background: #E8F5E8 !important; + color: #2E7D32 !important; + font-weight: 600; +} + +/* 프로젝트별 합계 행 스타일 */ +.production-report-table .project-subtotal { + background: #E8F4FD !important; + font-weight: 600; + border-bottom: 2px solid #5D9CEC; +} + +.production-report-table .project-subtotal td { + background: #E8F4FD !important; + color: #2E5BBA; + font-weight: 600; + border-bottom: 2px solid #5D9CEC; +} + .work-report-table .input-hours { font-weight: 600; color: var(--success); @@ -720,6 +866,66 @@ body { transform: none; } +/* ========== 탭 네비게이션 ========== */ +.tab-navigation { + background: var(--white); + border-radius: var(--radius-xl); + padding: var(--space-6); + box-shadow: var(--shadow-lg); + border: 1px solid var(--gray-200); + margin-bottom: var(--space-8); +} + +.tab-buttons { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: var(--space-4); +} + +.tab-button { + background: var(--gray-50); + border: 2px solid var(--gray-200); + border-radius: var(--radius-lg); + padding: var(--space-4) var(--space-6); + font-size: 0.9rem; + font-weight: 600; + color: var(--gray-700); + cursor: pointer; + transition: all 0.3s ease; + display: flex; + align-items: center; + justify-content: center; + gap: var(--space-2); + text-align: center; +} + +.tab-button:hover { + background: var(--gray-100); + border-color: var(--primary); + transform: translateY(-2px); + box-shadow: var(--shadow-md); +} + +.tab-button.active { + background: var(--gradient-primary); + border-color: var(--primary); + color: var(--white); + box-shadow: var(--shadow-lg); +} + +.tab-button .icon { + font-size: 1.2rem; +} + +/* ========== 탭 컨텐츠 ========== */ +.tab-content { + display: none; +} + +.tab-content.active { + display: block; +} + .chart-title { font-size: 1.5rem; font-weight: 700; @@ -986,6 +1192,16 @@ body { font-size: 0.85rem; } + .tab-buttons { + grid-template-columns: 1fr; + gap: var(--space-3); + } + + .tab-button { + padding: var(--space-3) var(--space-4); + font-size: 0.85rem; + } + .result-card { padding: var(--space-4); } diff --git a/web-ui/pages/analysis/work-analysis.html b/web-ui/pages/analysis/work-analysis.html index 5995f92..b73a74c 100644 --- a/web-ui/pages/analysis/work-analysis.html +++ b/web-ui/pages/analysis/work-analysis.html @@ -7,7 +7,7 @@ - + @@ -83,14 +83,26 @@

분석 중입니다...

- -
-
📊
-

분석을 시작해보세요

-

- 기간을 설정하고 분석 실행 버튼을 클릭하여
- 상세한 작업 현황 분석을 확인할 수 있습니다. -

+ + @@ -172,20 +184,21 @@
- -