// daily-report-viewer.js - 통합 API 설정 적용 버전 // ================================================================= // 🌐 통합 API 설정 import // ================================================================= import { API, getAuthHeaders, apiCall } from '/js/api-config.js'; // ================================================================= // 🌐 전역 변수 및 기본 설정 // ================================================================= let currentReportData = null; let workTypes = []; let workStatusTypes = []; let errorTypes = []; // ================================================================= // 🔧 유틸리티 함수들 (입력 페이지와 동일) // ================================================================= // 현재 로그인한 사용자 정보 가져오기 function getCurrentUser() { try { const token = localStorage.getItem('token'); if (!token) return null; const payloadBase64 = token.split('.')[1]; if (payloadBase64) { const payload = JSON.parse(atob(payloadBase64)); console.log('토큰에서 추출한 사용자 정보:', payload); return payload; } } catch (error) { console.log('토큰에서 사용자 정보 추출 실패:', error); } try { const userInfo = localStorage.getItem('user') || localStorage.getItem('userInfo') || localStorage.getItem('currentUser'); if (userInfo) { const parsed = JSON.parse(userInfo); console.log('localStorage에서 가져온 사용자 정보:', parsed); return parsed; } } catch (error) { console.log('localStorage에서 사용자 정보 가져오기 실패:', error); } return null; } // 한국 시간 기준 오늘 날짜 가져기기 function getKoreaToday() { const today = new Date(); const year = today.getFullYear(); const month = String(today.getMonth() + 1).padStart(2, '0'); const day = String(today.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; } // 권한 확인 함수 (수정된 버전) function checkUserPermission(user) { if (!user || !user.access_level) { return { level: 'none', canViewAll: false, description: '권한 없음' }; } const accessLevel = user.access_level.toLowerCase(); // 🎯 권한 레벨 정의 (더 유연하게) if (accessLevel === 'system' || accessLevel === 'admin') { return { level: 'admin', canViewAll: true, description: '시스템/관리자 (전체 조회 시도 → 실패 시 본인 데이터)' }; } else if (accessLevel === 'manager' || accessLevel === 'group_leader' || accessLevel === '그룹장') { return { level: 'manager', canViewAll: false, description: '그룹장 (본인 입력 데이터만)' }; } else { return { level: 'user', canViewAll: false, description: '일반 사용자 (본인 입력 데이터만)' }; } } // ================================================================= // 🚀 초기화 및 이벤트 설정 // ================================================================= document.addEventListener('DOMContentLoaded', async function() { console.log('🔥 ===== 통합 API 설정 적용 일일보고서 뷰어 시작 ====='); // 사용자 정보 및 권한 확인 const userInfo = getCurrentUser(); const permission = checkUserPermission(userInfo); console.log('👤 사용자 정보:', userInfo); console.log('🔐 권한 정보:', permission); // 토큰 확인 const mainToken = localStorage.getItem('token'); if (!mainToken) { console.error('❌ 토큰이 없습니다.'); alert('로그인이 필요합니다.'); setTimeout(() => window.location.href = '/index.html', 2000); return; } try { showMessage('시스템을 초기화하는 중...', 'loading'); // 기본 설정 setupEventListeners(); setTodayDate(); // 마스터 데이터 로드 await loadMasterData(); // 권한 표시 displayUserPermission(permission); hideMessage(); console.log('✅ 초기화 완료!'); } catch (error) { console.error('❌ 초기화 실패:', error); showError(`초기화 오류: ${error.message}`); } }); function setupEventListeners() { document.getElementById('searchBtn')?.addEventListener('click', searchReports); document.getElementById('todayBtn')?.addEventListener('click', setTodayDate); document.getElementById('reportDate')?.addEventListener('keypress', function(e) { if (e.key === 'Enter') { searchReports(); } }); document.getElementById('exportExcelBtn')?.addEventListener('click', exportToExcel); document.getElementById('printBtn')?.addEventListener('click', printReport); } function setTodayDate() { const today = getKoreaToday(); const dateInput = document.getElementById('reportDate'); if (dateInput) { dateInput.value = today; searchReports(); } } // 권한 표시 함수 (더 상세하게) function displayUserPermission(permission) { // 권한 정보를 UI에 표시 const headerElement = document.querySelector('h1'); if (headerElement) { headerElement.innerHTML += ` (${permission.description})`; } console.log(`🔐 현재 권한: ${permission.description}`); } // ================================================================= // 📊 마스터 데이터 로드 (통합 API 사용) // ================================================================= async function loadMasterData() { try { console.log('📋 마스터 데이터 로딩...'); await loadWorkTypes(); await loadWorkStatusTypes(); await loadErrorTypes(); console.log('✅ 마스터 데이터 로드 완료'); } catch (error) { console.error('❌ 마스터 데이터 로드 실패:', error); } } async function loadWorkTypes() { try { const data = await apiCall(`${API}/daily-work-reports/work-types`); if (Array.isArray(data) && data.length > 0) { workTypes = data; return; } throw new Error('API 실패'); } catch (error) { workTypes = [ {id: 1, name: 'Base'}, {id: 2, name: 'Vessel'}, {id: 3, name: 'Piping'} ]; } } async function loadWorkStatusTypes() { try { const data = await apiCall(`${API}/daily-work-reports/work-status-types`); if (Array.isArray(data) && data.length > 0) { workStatusTypes = data; return; } throw new Error('API 실패'); } catch (error) { workStatusTypes = [ {id: 1, name: '정규'}, {id: 2, name: '에러'} ]; } } async function loadErrorTypes() { try { const data = await apiCall(`${API}/daily-work-reports/error-types`); if (Array.isArray(data) && data.length > 0) { errorTypes = data; return; } throw new Error('API 실패'); } catch (error) { errorTypes = [ {id: 1, name: '설계미스'}, {id: 2, name: '외주작업 불량'}, {id: 3, name: '입고지연'}, {id: 4, name: '작업 불량'} ]; } } // ================================================================= // 🔍 스마트 권한별 데이터 조회 시스템 (통합 API 사용) // ================================================================= async function searchReports() { const selectedDate = document.getElementById('reportDate')?.value; if (!selectedDate) { showError('날짜를 선택해 주세요.'); return; } console.log(`\n🔍 ===== ${selectedDate} 스마트 권한별 조회 시작 =====`); try { hideAllMessages(); showLoading(true); const currentUser = getCurrentUser(); const permission = checkUserPermission(currentUser); console.log('🔐 권한 확인:', permission); let data = []; let queryMethod = ''; if (permission.canViewAll) { // 🌍 관리자/시스템: 전체 데이터 조회 시도 → 실패 시 본인 데이터로 폴백 console.log('🌍 관리자 권한으로 전체 데이터 조회 시도'); data = await fetchAllDataWithFallback(selectedDate, currentUser); queryMethod = '관리자 권한 (폴백 포함)'; } else { // 🔒 일반 사용자/그룹장: 처음부터 본인 데이터만 조회 console.log('🔒 제한 권한으로 본인 데이터만 조회'); data = await fetchMyData(selectedDate, currentUser); queryMethod = '제한 권한 (본인 데이터만)'; } console.log(`📊 최종 조회된 데이터: ${data.length}개`); if (data.length > 0) { const processedData = processRawData(data, selectedDate); currentReportData = processedData; displayReportData(processedData); showExportSection(true); showMessage(`${queryMethod}으로 ${data.length}개 데이터를 표시했습니다.`, 'success'); } else { const helpMessage = permission.canViewAll ? '전체 조회 및 본인 데이터 조회 모두 실패했습니다.' : '해당 날짜에 본인이 입력한 데이터가 없습니다.'; showNoDataWithHelp(selectedDate, helpMessage); showExportSection(false); } } catch (error) { console.error('❌ 조회 오류:', error); showError(`데이터 조회 오류: ${error.message}`); showExportSection(false); } finally { showLoading(false); console.log('🔍 ===== 조회 완료 =====\n'); } } // 전체 데이터 조회 + 본인 데이터 폴백 (시스템/관리자용) - 통합 API 사용 async function fetchAllDataWithFallback(selectedDate, currentUser) { console.log('📡 전체 데이터 조회 시도 (폴백 지원)'); // 1단계: 전체 데이터 조회 시도 const allData = await fetchAllData(selectedDate); if (allData.length > 0) { console.log(`✅ 전체 데이터 조회 성공: ${allData.length}개`); return allData; } // 2단계: 전체 조회 실패 시 본인 데이터로 폴백 console.log('⚠️ 전체 조회 실패, 본인 데이터로 폴백'); const myData = await fetchMyData(selectedDate, currentUser); if (myData.length > 0) { console.log(`✅ 폴백 성공: 본인 데이터 ${myData.length}개`); showMessage('⚠️ 전체 조회 권한이 없어 본인 입력 데이터만 표시합니다.', 'warning'); return myData; } console.log('❌ 전체 조회 및 폴백 모두 실패'); return []; } // 전체 데이터 조회 (시스템/관리자용) - 통합 API 사용 async function fetchAllData(selectedDate) { console.log('📡 전체 데이터 API 호출'); // 여러 방법으로 시도 const endpoints = [ `/daily-work-reports?date=${selectedDate}`, `/daily-work-reports/date/${selectedDate}` ]; for (const endpoint of endpoints) { try { console.log(`🔍 시도: ${API}${endpoint}`); const rawData = await apiCall(`${API}${endpoint}`); let data = Array.isArray(rawData) ? rawData : (rawData?.data || []); if (data.length > 0) { console.log(`✅ 전체 조회 성공: ${data.length}개 데이터`); return data; } } catch (error) { console.log(`❌ 오류: ${error.message}`); continue; } } console.log('❌ 모든 전체 조회 방법 실패'); return []; } // 본인 데이터 조회 (모든 사용자 공통) - 통합 API 사용 async function fetchMyData(selectedDate, currentUser) { console.log('📡 본인 데이터 API 호출'); if (!currentUser?.user_id && !currentUser?.id) { console.error('❌ 사용자 ID가 없습니다'); return []; } const userId = currentUser.user_id || currentUser.id; console.log(`🔍 본인 데이터 URL: ${API}/daily-work-reports?date=${selectedDate}&created_by=${userId}`); try { const rawData = await apiCall(`${API}/daily-work-reports?date=${selectedDate}&created_by=${userId}`); let data = Array.isArray(rawData) ? rawData : (rawData?.data || []); console.log(`✅ 본인 데이터: ${data.length}개`); return data; } catch (error) { console.error('❌ 본인 데이터 조회 오류:', error); return []; } } // 원시 데이터를 구조화된 형태로 변환 function processRawData(rawData, selectedDate) { console.log('🔄 데이터 구조 변환 시작'); if (!Array.isArray(rawData) || rawData.length === 0) { return { summary: { date: selectedDate, total_workers: 0, total_hours: 0, total_entries: 0, error_count: 0 }, workers: [] }; } // 작업자별로 그룹화 const workerGroups = {}; let totalHours = 0; let errorCount = 0; rawData.forEach(item => { const workerName = item.worker_name || '미지정'; const workHours = parseFloat(item.work_hours || 0); totalHours += workHours; if (item.work_status_id === 2) { errorCount++; } if (!workerGroups[workerName]) { workerGroups[workerName] = { worker_name: workerName, worker_id: item.worker_id, total_hours: 0, work_entries: [] }; } workerGroups[workerName].total_hours += workHours; workerGroups[workerName].work_entries.push({ project_name: item.project_name, work_type_name: item.work_type_name, work_status_name: item.work_status_name, error_type_name: item.error_type_name, work_hours: workHours, work_status_id: item.work_status_id, created_by_name: item.created_by_name || '입력자 미지정' }); }); const processedData = { summary: { date: selectedDate, total_workers: Object.keys(workerGroups).length, total_hours: totalHours, total_entries: rawData.length, error_count: errorCount }, workers: Object.values(workerGroups) }; console.log('✅ 데이터 변환 완료:', { 작업자수: processedData.workers.length, 총항목수: rawData.length, 총시간: totalHours, 에러수: errorCount }); return processedData; } // ================================================================= // 🎨 UI 표시 함수들 (기존과 동일) // ================================================================= function displayReportData(data) { console.log('🎨 리포트 데이터 표시'); displaySummary(data.summary); displayWorkersDetails(data.workers); document.getElementById('reportSummary').style.display = 'block'; document.getElementById('workersReport').style.display = 'block'; } function displaySummary(summary) { const elements = { totalWorkers: summary?.total_workers || 0, totalHours: `${summary?.total_hours || 0}시간`, totalEntries: `${summary?.total_entries || 0}개`, errorCount: `${summary?.error_count || 0}개` }; Object.entries(elements).forEach(([id, value]) => { const element = document.getElementById(id); if (element) element.textContent = value; }); // 에러 카드 스타일링 const errorCard = document.querySelector('.summary-card.error-card'); if (errorCard) { const hasErrors = (summary?.error_count || 0) > 0; errorCard.style.borderLeftColor = hasErrors ? '#e74c3c' : '#28a745'; errorCard.style.backgroundColor = hasErrors ? '#fff5f5' : '#f8fff9'; } } function displayWorkersDetails(workers) { const workersList = document.getElementById('workersList'); if (!workersList) return; workersList.innerHTML = ''; workers.forEach(worker => { const workerCard = createWorkerCard(worker); workersList.appendChild(workerCard); }); } function createWorkerCard(worker) { const workerDiv = document.createElement('div'); workerDiv.className = 'worker-card'; const workerHeader = document.createElement('div'); workerHeader.className = 'worker-header'; workerHeader.innerHTML = `
💡 ${helpMessage}