Files
TK-FB-Project/web-ui/js/daily-report-viewer.js

765 lines
26 KiB
JavaScript

// 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 += ` <small style="color: #666; font-size: 0.6em;">(${permission.description})</small>`;
}
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 = `
<div class="worker-name">👤 ${worker.worker_name || '미지정'}</div>
<div class="worker-total-hours">총 ${worker.total_hours || 0}시간</div>
`;
const workEntries = document.createElement('div');
workEntries.className = 'work-entries';
if (worker.work_entries && Array.isArray(worker.work_entries)) {
worker.work_entries.forEach(entry => {
const entryDiv = createWorkEntryCard(entry);
workEntries.appendChild(entryDiv);
});
}
workerDiv.appendChild(workerHeader);
workerDiv.appendChild(workEntries);
return workerDiv;
}
function createWorkEntryCard(entry) {
const entryDiv = document.createElement('div');
entryDiv.className = 'work-entry';
if (entry.work_status_id === 2) {
entryDiv.classList.add('error-entry');
}
const entryHeader = document.createElement('div');
entryHeader.className = 'entry-header';
entryHeader.innerHTML = `
<div class="project-name">${entry.project_name || '프로젝트 미지정'}</div>
<div class="work-hours">${entry.work_hours || 0}시간</div>
`;
const entryDetails = document.createElement('div');
entryDetails.className = 'entry-details';
const details = [
['작업 유형', entry.work_type_name || '-'],
['작업 상태', entry.work_status_name || '정상'],
['입력자', entry.created_by_name || '미지정']
];
if (entry.work_status_id === 2 && entry.error_type_name) {
details.push(['에러 유형', entry.error_type_name, 'error-type']);
}
details.forEach(([label, value, valueClass]) => {
const detailRow = createDetailRow(label, value, valueClass);
entryDetails.appendChild(detailRow);
});
entryDiv.appendChild(entryHeader);
entryDiv.appendChild(entryDetails);
return entryDiv;
}
function createDetailRow(label, value, valueClass = '') {
const detailDiv = document.createElement('div');
detailDiv.className = 'entry-detail';
detailDiv.innerHTML = `
<span class="detail-label">${label}:</span>
<span class="detail-value ${valueClass}">${value}</span>
`;
return detailDiv;
}
// =================================================================
// 🎭 UI 상태 관리
// =================================================================
function showLoading(show) {
const spinner = document.getElementById('loadingSpinner');
if (spinner) {
spinner.style.display = show ? 'flex' : 'none';
}
}
function showError(message) {
const errorDiv = document.getElementById('errorMessage');
if (errorDiv) {
const errorText = errorDiv.querySelector('.error-text');
if (errorText) errorText.textContent = message;
errorDiv.style.display = 'block';
}
}
function showMessage(message, type = 'info') {
const messageContainer = document.getElementById('message-container');
if (messageContainer) {
messageContainer.innerHTML = `<div class="message ${type}">${message}</div>`;
if (type === 'success' || type === 'info') {
setTimeout(() => {
messageContainer.innerHTML = '';
}, 5000);
}
} else {
console.log(`📢 ${type.toUpperCase()}: ${message}`);
}
}
function hideMessage() {
const messageContainer = document.getElementById('message-container');
if (messageContainer) {
messageContainer.innerHTML = '';
}
}
function showNoDataWithHelp(selectedDate, helpMessage = '해당 날짜에 데이터가 없습니다.') {
const noDataDiv = document.getElementById('noDataMessage');
if (noDataDiv) {
noDataDiv.innerHTML = `
<div class="no-data-content">
<span class="no-data-icon">📭</span>
<h3>${selectedDate} 작업보고서가 없습니다</h3>
<div class="help-section">
<p><strong>💡 ${helpMessage}</strong></p>
<ul style="text-align: left; margin: 10px 0;">
<li>다른 날짜를 선택해보세요 (예: ${getKoreaToday()})</li>
<li><a href="/pages/common/daily-work-report.html" target="_blank" style="color: #3498db;">📝 작업보고서 입력 페이지</a>에서 데이터를 먼저 입력해보세요</li>
<li>입력 후 잠시 기다린 다음 다시 시도해보세요</li>
</ul>
<p style="margin-top: 15px;">
<button onclick="window.location.reload()" style="padding: 8px 16px; background: #3498db; color: white; border: none; border-radius: 4px; cursor: pointer;">
🔄 새로고침
</button>
</p>
</div>
</div>
`;
noDataDiv.style.display = 'block';
}
}
function showExportSection(show) {
const exportSection = document.getElementById('exportSection');
if (exportSection) {
exportSection.style.display = show ? 'block' : 'none';
}
}
function hideAllMessages() {
const elements = [
'errorMessage',
'noDataMessage',
'reportSummary',
'workersReport'
];
elements.forEach(id => {
const element = document.getElementById(id);
if (element) element.style.display = 'none';
});
}
// =================================================================
// 📤 내보내기 기능
// =================================================================
function exportToExcel() {
if (!currentReportData?.workers?.length) {
alert('내보낼 데이터가 없습니다.');
return;
}
console.log('📊 Excel 내보내기 시작');
try {
let csvContent = "\uFEFF작업자명,프로젝트명,작업유형,작업상태,에러유형,작업시간,입력자\n";
currentReportData.workers.forEach(worker => {
if (worker.work_entries && Array.isArray(worker.work_entries)) {
worker.work_entries.forEach(entry => {
const row = [
worker.worker_name || '',
entry.project_name || '',
entry.work_type_name || '',
entry.work_status_name || '',
entry.error_type_name || '',
entry.work_hours || 0,
entry.created_by_name || ''
].map(field => `"${String(field).replace(/"/g, '""')}"`).join(',');
csvContent += row + "\n";
});
}
});
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
const fileName = `작업보고서_${currentReportData.summary?.date || '날짜미지정'}.csv`;
link.setAttribute('href', url);
link.setAttribute('download', fileName);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
console.log('✅ Excel 내보내기 완료');
showMessage('Excel 파일이 다운로드되었습니다.', 'success');
} catch (error) {
console.error('❌ Excel 내보내기 실패:', error);
showError('Excel 내보내기 중 오류가 발생했습니다.');
}
}
function printReport() {
console.log('🖨️ 인쇄 시작');
if (!currentReportData?.workers?.length) {
alert('인쇄할 데이터가 없습니다.');
return;
}
try {
window.print();
console.log('✅ 인쇄 대화상자 표시');
} catch (error) {
console.error('❌ 인쇄 실패:', error);
showError('인쇄 중 오류가 발생했습니다.');
}
}
// =================================================================
// 🔄 전역 함수 및 디버깅
// =================================================================
// 개발 모드 디버깅
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
console.log('🐛 개발 모드 활성화');
window.DEBUG = {
currentReportData,
getCurrentUser,
checkUserPermission,
fetchAllData,
fetchMyData,
fetchAllDataWithFallback,
searchReports
};
}
// 전역 함수 노출
window.searchReports = searchReports;
window.exportToExcel = exportToExcel;
window.printReport = printReport;
// 페이지 정리
window.addEventListener('beforeunload', function() {
console.log('📋 페이지 종료');
});