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 @@
- -