refactor: 프론트엔드 SSO 인증 통합 및 API 경로 정리
- Gateway 로그인/포탈 페이지 SSO 연동 - System1 web/fastapi-bridge API base URL 동적 설정 - SSO 토큰 기반 인증 흐름 통일 - deprecated JS 파일 삭제 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -161,8 +161,8 @@
|
||||
|
||||
// redirect 파라미터가 있으면 해당 URL로, 없으면 포털로
|
||||
var redirect = new URLSearchParams(location.search).get('redirect');
|
||||
// Open redirect 방지: 상대 경로 또는 같은 도메인만 허용
|
||||
if (redirect && (redirect.startsWith('/') && !redirect.startsWith('//')) && !redirect.includes('://')) {
|
||||
// Open redirect 방지: 같은 origin의 상대 경로만 허용
|
||||
if (redirect && /^\/[a-zA-Z0-9]/.test(redirect) && !redirect.includes('://') && !redirect.includes('//')) {
|
||||
window.location.href = redirect;
|
||||
} else {
|
||||
window.location.href = '/';
|
||||
|
||||
@@ -221,9 +221,9 @@
|
||||
ssoCookie.remove('sso_token');
|
||||
ssoCookie.remove('sso_user');
|
||||
ssoCookie.remove('sso_refresh_token');
|
||||
localStorage.removeItem('sso_token');
|
||||
localStorage.removeItem('sso_user');
|
||||
localStorage.removeItem('sso_refresh_token');
|
||||
['sso_token','sso_user','sso_refresh_token','token','user','access_token','currentUser','current_user','userInfo','userPageAccess'].forEach(function(k) {
|
||||
localStorage.removeItem(k);
|
||||
});
|
||||
fetch('/auth/logout', { method: 'POST' }).catch(function(){});
|
||||
location.reload();
|
||||
}
|
||||
|
||||
@@ -48,9 +48,9 @@
|
||||
cookieRemove('sso_token');
|
||||
cookieRemove('sso_user');
|
||||
cookieRemove('sso_refresh_token');
|
||||
localStorage.removeItem('sso_token');
|
||||
localStorage.removeItem('sso_user');
|
||||
localStorage.removeItem('sso_refresh_token');
|
||||
['sso_token','sso_user','sso_refresh_token','token','user','access_token','currentUser','current_user','userInfo','userPageAccess'].forEach(function(k) {
|
||||
localStorage.removeItem(k);
|
||||
});
|
||||
window.location.href = this.getLoginUrl();
|
||||
},
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ function getApiBaseUrl() {
|
||||
const protocol = window.location.protocol;
|
||||
const port = window.location.port;
|
||||
|
||||
console.log('🌐 감지된 환경:', { hostname, protocol, port });
|
||||
|
||||
// 🔗 nginx 프록시를 통한 접근 (권장)
|
||||
// nginx가 /api/ 요청을 백엔드로 프록시하므로 포트 없이 접근
|
||||
@@ -18,12 +17,11 @@ function getApiBaseUrl() {
|
||||
? `${protocol}//${hostname}:${port}/api`
|
||||
: `${protocol}//${hostname}/api`;
|
||||
|
||||
console.log('✅ nginx 프록시 사용:', baseUrl);
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
// 🚨 백업: 직접 접근 (nginx 프록시 실패시에만)
|
||||
console.warn('⚠️ 직접 API 접근 (백업 모드)');
|
||||
console.warn(' 직접 API 접근 (백업 모드)');
|
||||
return `${protocol}//${hostname}:8000/api`;
|
||||
}
|
||||
|
||||
@@ -64,12 +62,11 @@ export async function apiCall(url, options = {}) {
|
||||
};
|
||||
|
||||
try {
|
||||
console.log(`📡 API 호출: ${url}`);
|
||||
const response = await fetch(url, finalOptions);
|
||||
|
||||
// 인증 만료 처리
|
||||
if (response.status === 401) {
|
||||
console.error('❌ 인증 만료');
|
||||
console.error(' 인증 만료');
|
||||
localStorage.removeItem('sso_token');
|
||||
alert('인증이 만료되었습니다. 다시 로그인해주세요.');
|
||||
window.location.href = '/';
|
||||
@@ -89,11 +86,10 @@ export async function apiCall(url, options = {}) {
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log(`✅ API 성공: ${url}`);
|
||||
return result;
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ API 오류 (${url}):`, error);
|
||||
console.error(` API 오류 (${url}):`, error);
|
||||
|
||||
// 네트워크 오류 vs 서버 오류 구분
|
||||
if (error.name === 'TypeError' && error.message.includes('fetch')) {
|
||||
@@ -105,8 +101,6 @@ export async function apiCall(url, options = {}) {
|
||||
}
|
||||
|
||||
// 디버깅 정보
|
||||
console.log('🔗 API Base URL:', API);
|
||||
console.log('🌐 Current Location:', {
|
||||
hostname: window.location.hostname,
|
||||
protocol: window.location.protocol,
|
||||
port: window.location.port,
|
||||
@@ -116,21 +110,17 @@ console.log('🌐 Current Location:', {
|
||||
// 🧪 API 연결 테스트 함수 (개발용)
|
||||
export async function testApiConnection() {
|
||||
try {
|
||||
console.log('🧪 API 연결 테스트 시작...');
|
||||
const response = await fetch(`${API}/health`, {
|
||||
method: 'GET',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
console.log('✅ API 연결 성공!');
|
||||
return true;
|
||||
} else {
|
||||
console.log('❌ API 연결 실패:', response.status);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('❌ API 연결 오류:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,6 @@ function getCacheStatus() {
|
||||
*/
|
||||
function clearCache() {
|
||||
dateStatusCache.clear();
|
||||
console.log('📦 캐시가 클리어되었습니다.');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,7 +76,6 @@ function updatePerformanceUI() {
|
||||
*/
|
||||
function logPerformanceStatus() {
|
||||
const status = getCacheStatus();
|
||||
console.log('📊 성능 상태:', status);
|
||||
updatePerformanceUI();
|
||||
}
|
||||
|
||||
@@ -118,7 +116,7 @@ function getCurrentUser() {
|
||||
}
|
||||
|
||||
try {
|
||||
const userInfo = localStorage.getItem('sso_user') || localStorage.getItem('userInfo');
|
||||
const userInfo = localStorage.getItem('sso_user');
|
||||
if (userInfo) {
|
||||
return JSON.parse(userInfo);
|
||||
}
|
||||
@@ -391,18 +389,14 @@ async function calculateDateStatus(dateStr) {
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`📊 ${dateStr} 상태 계산 시작 - 순차 호출`);
|
||||
|
||||
// 1단계: WorkReports 먼저 가져오기
|
||||
console.log(`📝 1단계: WorkReports 조회 중...`);
|
||||
const workReports = await fetchWorkReports(dateStr);
|
||||
|
||||
// 2초 대기 (서버 부하 방지)
|
||||
console.log(`⏳ 2초 대기 중... (서버 부하 방지)`);
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
// 2단계: DailyWorkReports 가져오기
|
||||
console.log(`📊 2단계: DailyWorkReports 조회 중...`);
|
||||
const dailyReports = await fetchDailyWorkReports(dateStr);
|
||||
|
||||
let status;
|
||||
@@ -423,7 +417,6 @@ async function calculateDateStatus(dateStr) {
|
||||
|
||||
// 캐시에 저장
|
||||
dateStatusCache.set(dateStr, status);
|
||||
console.log(`✅ ${dateStr} 상태 계산 완료: ${status}`);
|
||||
return status;
|
||||
} catch (error) {
|
||||
console.error('날짜 상태 계산 오류:', error);
|
||||
@@ -564,10 +557,9 @@ async function loadAndUpdateDateStatus(dateStr, buttonElement) {
|
||||
}`;
|
||||
}
|
||||
|
||||
console.log(`✅ ${dateStr} 상태 로드 완료: ${status}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ ${dateStr} 상태 로드 실패:`, error);
|
||||
console.error(` ${dateStr} 상태 로드 실패:`, error);
|
||||
buttonElement.classList.remove('loading-state');
|
||||
buttonElement.classList.add('error-state');
|
||||
buttonElement.title = `${dateStr} - 로드 실패: ${error.message}`;
|
||||
@@ -589,18 +581,14 @@ async function loadAndUpdateDateStatus(dateStr, buttonElement) {
|
||||
*/
|
||||
async function getWorkersForDate(dateStr) {
|
||||
try {
|
||||
console.log(`👥 ${dateStr} 작업자 데이터 조합 시작 - 순차 호출`);
|
||||
|
||||
// 1단계: WorkReports 먼저 가져오기
|
||||
console.log(`📝 1단계: WorkReports 조회 중...`);
|
||||
const workReports = await fetchWorkReports(dateStr);
|
||||
|
||||
// 2초 대기 (서버 부하 방지)
|
||||
console.log(`⏳ 2초 대기 중... (서버 부하 방지)`);
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
// 2단계: DailyWorkReports 가져오기
|
||||
console.log(`📊 2단계: DailyWorkReports 조회 중...`);
|
||||
const dailyReports = await fetchDailyWorkReports(dateStr);
|
||||
|
||||
const workerMap = new Map();
|
||||
@@ -645,7 +633,6 @@ async function getWorkersForDate(dateStr) {
|
||||
validationStatus: getValidationStatus(worker)
|
||||
}));
|
||||
|
||||
console.log(`✅ ${dateStr} 작업자 데이터 조합 완료: ${result.length}명`);
|
||||
return result;
|
||||
|
||||
} catch (error) {
|
||||
@@ -1022,11 +1009,6 @@ async function init() {
|
||||
window.saveEditedWork = saveEditedWork;
|
||||
window.deleteWorker = deleteWorker;
|
||||
|
||||
console.log('✅ 근태 검증 관리 시스템 초기화 완료 (API 통합)');
|
||||
console.log(`🔗 API 경로: ${API}`);
|
||||
console.log(`📊 설정: 동시 최대 ${RATE_LIMIT.maxConcurrent}개 요청, ${RATE_LIMIT.delayBetweenRequests}ms 딜레이`);
|
||||
console.log('🔄 API 호출 방식: 통합 설정 + 순차 호출');
|
||||
console.log('🚫 429 에러 방지: 각 날짜당 최소 5초 간격');
|
||||
|
||||
} catch (error) {
|
||||
console.error('초기화 오류:', error);
|
||||
|
||||
@@ -4,7 +4,6 @@ import { isLoggedIn, getUser, clearAuthData } from './auth.js';
|
||||
// 즉시 실행 함수로 스코프를 보호하고 로직을 실행
|
||||
(function() {
|
||||
if (!isLoggedIn()) {
|
||||
console.log('🚨 인증되지 않은 사용자. 로그인 페이지로 이동합니다.');
|
||||
clearAuthData(); // 만약을 위해 한번 더 정리
|
||||
window.location.href = '/login';
|
||||
return; // 이후 코드 실행 방지
|
||||
@@ -14,13 +13,12 @@ import { isLoggedIn, getUser, clearAuthData } from './auth.js';
|
||||
|
||||
// 사용자 정보가 유효한지 확인 (토큰은 있지만 유저 정보가 깨졌을 경우)
|
||||
if (!currentUser || !currentUser.username || !currentUser.role) {
|
||||
console.error('🚨 사용자 정보가 유효하지 않습니다. 강제 로그아웃 처리합니다.');
|
||||
console.error(' 사용자 정보가 유효하지 않습니다. 강제 로그아웃 처리합니다.');
|
||||
clearAuthData();
|
||||
window.location.href = '/login';
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`✅ ${currentUser.username}(${currentUser.role})님 인증 성공.`);
|
||||
|
||||
// 역할 기반 메뉴 제어 로직은 각 컴포넌트 로더(load-navbar.js 등)로 이전함.
|
||||
// 전역 변수 할당(window.currentUser) 제거.
|
||||
|
||||
@@ -206,6 +206,4 @@ form?.addEventListener('submit', async (e) => {
|
||||
// 페이지 로드 시 현재 사용자 정보 표시
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const user = JSON.parse(localStorage.getItem('sso_user') || '{}');
|
||||
console.log('🔐 비밀번호 변경 페이지 로드됨');
|
||||
console.log('👤 현재 사용자:', user.username || 'Unknown');
|
||||
});
|
||||
@@ -42,7 +42,7 @@ function getCurrentUser() {
|
||||
}
|
||||
|
||||
try {
|
||||
const userInfo = localStorage.getItem('sso_user') || localStorage.getItem('userInfo') || localStorage.getItem('currentUser');
|
||||
const userInfo = localStorage.getItem('sso_user');
|
||||
if (userInfo) {
|
||||
const parsed = JSON.parse(userInfo);
|
||||
console.log('localStorage에서 가져온 사용자 정보:', parsed);
|
||||
@@ -94,7 +94,6 @@ async function loadData() {
|
||||
try {
|
||||
showMessage('데이터를 불러오는 중...', 'loading');
|
||||
|
||||
console.log('🔗 통합 API 설정을 사용한 기본 데이터 로딩 시작...');
|
||||
await loadWorkers();
|
||||
await loadProjects();
|
||||
await loadWorkTypes();
|
||||
@@ -119,7 +118,6 @@ async function loadWorkers() {
|
||||
console.log('Workers API 호출 중... (통합 API 사용)');
|
||||
const data = await apiCall(`${API}/workers`);
|
||||
workers = Array.isArray(data) ? data : (data.workers || []);
|
||||
console.log('✅ Workers 로드 성공:', workers.length);
|
||||
} catch (error) {
|
||||
console.error('작업자 로딩 오류:', error);
|
||||
throw error;
|
||||
@@ -131,7 +129,6 @@ async function loadProjects() {
|
||||
console.log('Projects API 호출 중... (통합 API 사용)');
|
||||
const data = await apiCall(`${API}/projects`);
|
||||
projects = Array.isArray(data) ? data : (data.projects || []);
|
||||
console.log('✅ Projects 로드 성공:', projects.length);
|
||||
} catch (error) {
|
||||
console.error('프로젝트 로딩 오류:', error);
|
||||
throw error;
|
||||
@@ -143,12 +140,10 @@ async function loadWorkTypes() {
|
||||
const data = await apiCall(`${API}/daily-work-reports/work-types`);
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
workTypes = data;
|
||||
console.log('✅ 작업 유형 API 사용 (통합 설정)');
|
||||
return;
|
||||
}
|
||||
throw new Error('API 실패');
|
||||
} catch (error) {
|
||||
console.log('⚠️ 작업 유형 API 사용 불가, 기본값 사용');
|
||||
workTypes = [
|
||||
{id: 1, name: 'Base'},
|
||||
{id: 2, name: 'Vessel'},
|
||||
@@ -162,12 +157,10 @@ async function loadWorkStatusTypes() {
|
||||
const data = await apiCall(`${API}/daily-work-reports/work-status-types`);
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
workStatusTypes = data;
|
||||
console.log('✅ 업무 상태 유형 API 사용 (통합 설정)');
|
||||
return;
|
||||
}
|
||||
throw new Error('API 실패');
|
||||
} catch (error) {
|
||||
console.log('⚠️ 업무 상태 유형 API 사용 불가, 기본값 사용');
|
||||
workStatusTypes = [
|
||||
{id: 1, name: '정규'},
|
||||
{id: 2, name: '에러'}
|
||||
@@ -180,12 +173,10 @@ async function loadErrorTypes() {
|
||||
const data = await apiCall(`${API}/daily-work-reports/error-types`);
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
errorTypes = data;
|
||||
console.log('✅ 에러 유형 API 사용 (통합 설정)');
|
||||
return;
|
||||
}
|
||||
throw new Error('API 실패');
|
||||
} catch (error) {
|
||||
console.log('⚠️ 에러 유형 API 사용 불가, 기본값 사용');
|
||||
errorTypes = [
|
||||
{id: 1, name: '설계미스'},
|
||||
{id: 2, name: '외주작업 불량'},
|
||||
@@ -429,10 +420,9 @@ async function saveWorkReport() {
|
||||
body: JSON.stringify(requestData)
|
||||
});
|
||||
|
||||
console.log('✅ 저장 성공 (통합 API):', result);
|
||||
totalSaved++;
|
||||
} catch (error) {
|
||||
console.error('❌ 저장 실패:', error);
|
||||
console.error(' 저장 실패:', error);
|
||||
totalFailed++;
|
||||
|
||||
const workerName = workers.find(w => w.worker_id == workerId)?.worker_name || '알 수 없음';
|
||||
@@ -508,10 +498,8 @@ async function loadTodayWorkers() {
|
||||
queryParams += `&created_by=${currentUser.id}`;
|
||||
}
|
||||
|
||||
console.log(`🔒 본인 입력분만 조회 (통합 API): ${API}/daily-work-reports?${queryParams}`);
|
||||
|
||||
const rawData = await apiCall(`${API}/daily-work-reports?${queryParams}`);
|
||||
console.log('📊 당일 작업 데이터 (통합 API):', rawData);
|
||||
|
||||
let data = [];
|
||||
if (Array.isArray(rawData)) {
|
||||
@@ -789,14 +777,13 @@ async function saveEditedWork() {
|
||||
body: JSON.stringify(updateData)
|
||||
});
|
||||
|
||||
console.log('✅ 수정 성공 (통합 API):', result);
|
||||
showMessage('✅ 작업이 성공적으로 수정되었습니다!', 'success');
|
||||
|
||||
closeEditModal();
|
||||
refreshTodayWorkers();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 수정 실패:', error);
|
||||
console.error(' 수정 실패:', error);
|
||||
showMessage('수정 중 오류가 발생했습니다: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
@@ -817,14 +804,13 @@ async function deleteWorkItem(workId) {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
console.log('✅ 삭제 성공 (통합 API):', result);
|
||||
showMessage('✅ 작업이 성공적으로 삭제되었습니다!', 'success');
|
||||
|
||||
// 화면 새로고침
|
||||
refreshTodayWorkers();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 삭제 실패:', error);
|
||||
console.error(' 삭제 실패:', error);
|
||||
showMessage('삭제 중 오류가 발생했습니다: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
@@ -877,7 +863,6 @@ async function init() {
|
||||
setupEventListeners();
|
||||
loadTodayWorkers();
|
||||
|
||||
console.log('✅ 시스템 초기화 완료 (통합 API 설정 적용)');
|
||||
|
||||
} catch (error) {
|
||||
console.error('초기화 오류:', error);
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
// /js/group-leader-dashboard.js
|
||||
// 그룹장 전용 대시보드 기능
|
||||
|
||||
console.log('📊 그룹장 대시보드 스크립트 로딩');
|
||||
|
||||
// 팀 현황 새로고침
|
||||
async function refreshTeamStatus() {
|
||||
console.log('🔄 팀 현황 새로고침 시작');
|
||||
|
||||
try {
|
||||
// 로딩 상태 표시
|
||||
@@ -24,7 +22,7 @@ async function refreshTeamStatus() {
|
||||
}, 1000);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 팀 현황 로딩 실패:', error);
|
||||
console.error(' 팀 현황 로딩 실패:', error);
|
||||
const teamList = document.getElementById('team-list');
|
||||
if (teamList) {
|
||||
teamList.innerHTML = '<div style="text-align: center; padding: 20px; color: #f44336;">❌ 로딩 실패</div>';
|
||||
@@ -64,7 +62,6 @@ function updateTeamStatusUI() {
|
||||
if (presentEl) presentEl.textContent = presentCount;
|
||||
if (absentEl) absentEl.textContent = absentCount;
|
||||
|
||||
console.log('✅ 팀 현황 업데이트 완료');
|
||||
}
|
||||
|
||||
// 환영 메시지 개인화
|
||||
@@ -74,21 +71,18 @@ function personalizeWelcome() {
|
||||
|
||||
if (user && user.name && welcomeMsg) {
|
||||
welcomeMsg.textContent = `${user.name}님의 실시간 팀 현황 및 작업 모니터링`;
|
||||
console.log('✅ 환영 메시지 개인화 완료');
|
||||
}
|
||||
}
|
||||
|
||||
// 페이지 초기화
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('🚀 그룹장 대시보드 초기화 시작');
|
||||
|
||||
// 사용자 정보 확인
|
||||
const user = JSON.parse(localStorage.getItem('sso_user') || '{}');
|
||||
console.log('👤 현재 사용자:', user);
|
||||
|
||||
// 권한 확인
|
||||
if (user.access_level !== 'group_leader') {
|
||||
console.warn('⚠️ 그룹장 권한 없음:', user.access_level);
|
||||
console.warn(' 그룹장 권한 없음:', user.access_level);
|
||||
// 필요시 다른 페이지로 리다이렉트
|
||||
}
|
||||
|
||||
@@ -96,7 +90,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
personalizeWelcome();
|
||||
updateTeamStatusUI();
|
||||
|
||||
console.log('✅ 그룹장 대시보드 초기화 완료');
|
||||
});
|
||||
|
||||
// 전역 함수로 내보내기 (HTML에서 사용)
|
||||
|
||||
@@ -135,10 +135,9 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
updateTime();
|
||||
setInterval(updateTime, 1000);
|
||||
|
||||
console.log('✅ 네비게이션 바 로딩 완료');
|
||||
|
||||
} catch (error) {
|
||||
console.error('🔴 네비게이션 바 로딩 중 오류 발생:', error);
|
||||
console.error(' 네비게이션 바 로딩 중 오류 발생:', error);
|
||||
navbarContainer.innerHTML = '<p>네비게이션 바를 불러오는 데 실패했습니다.</p>';
|
||||
}
|
||||
});
|
||||
@@ -92,7 +92,6 @@ async function initializeSections() {
|
||||
// 5. 모든 수정이 완료된 HTML을 실제 DOM에 한 번에 삽입
|
||||
mainContainer.innerHTML = doc.body.innerHTML;
|
||||
|
||||
console.log(`✅ ${currentUser.role} 역할의 섹션 로딩 완료.`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('섹션 로딩 중 오류 발생:', error);
|
||||
|
||||
@@ -58,10 +58,9 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
// 3. 수정 완료된 HTML을 실제 DOM에 삽입
|
||||
sidebarContainer.innerHTML = doc.body.innerHTML;
|
||||
|
||||
console.log('✅ 사이드바 로딩 및 필터링 완료');
|
||||
|
||||
} catch (error) {
|
||||
console.error('🔴 사이드바 로딩 실패:', error);
|
||||
console.error(' 사이드바 로딩 실패:', error);
|
||||
sidebarContainer.innerHTML = '<p>메뉴 로딩 실패</p>';
|
||||
}
|
||||
});
|
||||
@@ -47,7 +47,7 @@ function getCurrentUser() {
|
||||
}
|
||||
|
||||
try {
|
||||
const userInfo = localStorage.getItem('sso_user') || localStorage.getItem('userInfo') || localStorage.getItem('currentUser');
|
||||
const userInfo = localStorage.getItem('sso_user');
|
||||
if (userInfo) {
|
||||
const parsed = JSON.parse(userInfo);
|
||||
console.log('localStorage에서 가져온 사용자 정보:', parsed);
|
||||
@@ -128,7 +128,6 @@ async function loadWorkers() {
|
||||
console.log('작업자 데이터 로딩 중... (통합 API)');
|
||||
const data = await apiCall(`${API}/workers`);
|
||||
workers = Array.isArray(data) ? data : (data.workers || []);
|
||||
console.log('✅ 작업자 로드 성공:', workers.length);
|
||||
} catch (error) {
|
||||
console.error('작업자 로딩 오류:', error);
|
||||
throw error;
|
||||
@@ -142,32 +141,27 @@ async function loadWorkData(date) {
|
||||
|
||||
// 1차: view_all=true로 전체 데이터 시도
|
||||
let queryParams = `date=${date}&view_all=true`;
|
||||
console.log(`🔍 1차 시도: ${API}/daily-work-reports?${queryParams}`);
|
||||
|
||||
let data = await apiCall(`${API}/daily-work-reports?${queryParams}`);
|
||||
workData = Array.isArray(data) ? data : (data.data || []);
|
||||
|
||||
// 데이터가 없으면 다른 방법들 시도
|
||||
if (workData.length === 0) {
|
||||
console.log('⚠️ view_all로 데이터 없음, 다른 방법 시도...');
|
||||
|
||||
// 2차: admin=true로 시도
|
||||
queryParams = `date=${date}&admin=true`;
|
||||
console.log(`🔍 2차 시도: ${API}/daily-work-reports?${queryParams}`);
|
||||
|
||||
data = await apiCall(`${API}/daily-work-reports?${queryParams}`);
|
||||
workData = Array.isArray(data) ? data : (data.data || []);
|
||||
|
||||
if (workData.length === 0) {
|
||||
// 3차: 날짜 경로 파라미터로 시도
|
||||
console.log(`🔍 3차 시도: ${API}/daily-work-reports/date/${date}`);
|
||||
|
||||
data = await apiCall(`${API}/daily-work-reports/date/${date}`);
|
||||
workData = Array.isArray(data) ? data : (data.data || []);
|
||||
|
||||
if (workData.length === 0) {
|
||||
// 4차: 기본 파라미터만으로 시도
|
||||
console.log(`🔍 4차 시도: ${API}/daily-work-reports?date=${date}`);
|
||||
|
||||
data = await apiCall(`${API}/daily-work-reports?date=${date}`);
|
||||
workData = Array.isArray(data) ? data : (data.data || []);
|
||||
@@ -175,15 +169,11 @@ async function loadWorkData(date) {
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`✅ 최종 작업 데이터 로드 결과: ${workData.length}개`);
|
||||
|
||||
// 디버깅을 위한 상세 로그
|
||||
if (workData.length > 0) {
|
||||
console.log('📊 로드된 데이터 샘플:', workData.slice(0, 3));
|
||||
const uniqueWorkers = [...new Set(workData.map(w => w.worker_name))];
|
||||
console.log('👥 데이터에 포함된 작업자들:', uniqueWorkers);
|
||||
} else {
|
||||
console.log('❌ 해당 날짜에 작업 데이터가 없거나 접근 권한이 없습니다.');
|
||||
}
|
||||
|
||||
return workData;
|
||||
@@ -195,10 +185,8 @@ async function loadWorkData(date) {
|
||||
|
||||
// 구체적인 에러 정보 표시
|
||||
if (error.message.includes('403')) {
|
||||
console.log('🔒 권한 부족으로 인한 접근 제한');
|
||||
throw new Error('해당 날짜의 데이터에 접근할 권한이 없습니다.');
|
||||
} else if (error.message.includes('404')) {
|
||||
console.log('📭 해당 날짜에 데이터 없음');
|
||||
throw new Error('해당 날짜에 입력된 작업 데이터가 없습니다.');
|
||||
} else {
|
||||
throw error;
|
||||
@@ -339,7 +327,6 @@ function displayDashboard(data) {
|
||||
filteredWorkData = data.workers;
|
||||
setupFiltering();
|
||||
|
||||
console.log('✅ 대시보드 표시 완료');
|
||||
}
|
||||
|
||||
// 요약 섹션 표시
|
||||
@@ -767,7 +754,6 @@ async function saveEditedWork(workId) {
|
||||
body: JSON.stringify(updateData)
|
||||
});
|
||||
|
||||
console.log('✅ 수정 성공 (통합 API):', result);
|
||||
showMessage('✅ 작업이 성공적으로 수정되었습니다!', 'success');
|
||||
|
||||
closeEditModal();
|
||||
@@ -777,7 +763,7 @@ async function saveEditedWork(workId) {
|
||||
await loadDashboardData();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 수정 실패:', error);
|
||||
console.error(' 수정 실패:', error);
|
||||
showMessage('수정 중 오류가 발생했습니다: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
@@ -798,7 +784,6 @@ async function deleteWorkItem(workId) {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
console.log('✅ 삭제 성공 (통합 API):', result);
|
||||
showMessage('✅ 작업이 성공적으로 삭제되었습니다!', 'success');
|
||||
|
||||
closeWorkerDetailModal();
|
||||
@@ -807,7 +792,7 @@ async function deleteWorkItem(workId) {
|
||||
await loadDashboardData();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 삭제 실패:', error);
|
||||
console.error(' 삭제 실패:', error);
|
||||
showMessage('삭제 중 오류가 발생했습니다: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
@@ -912,7 +897,6 @@ async function init() {
|
||||
// 이벤트 리스너 설정
|
||||
setupEventListeners();
|
||||
|
||||
console.log('✅ 관리자 대시보드 초기화 완료 (통합 API 설정 적용)');
|
||||
|
||||
// 자동으로 오늘 데이터 로드
|
||||
loadDashboardData();
|
||||
|
||||
@@ -117,6 +117,5 @@ function showError(message) {
|
||||
|
||||
// 페이지 로드 시 실행
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
console.log('👤 프로필 페이지 로드됨');
|
||||
loadProfile();
|
||||
});
|
||||
@@ -71,7 +71,6 @@ class WorkReportReviewManager {
|
||||
{id: 3, name: '입고지연'}, {id: 4, name: '작업 불량'}
|
||||
];
|
||||
} catch (error) {
|
||||
console.log('⚠️ 일부 API 사용 불가, 기본값 사용');
|
||||
}
|
||||
|
||||
// 휴가 정보 로드
|
||||
@@ -101,13 +100,11 @@ class WorkReportReviewManager {
|
||||
this.attendanceData = await response.json();
|
||||
console.log('휴가 정보 로드 완료:', this.attendanceData.length);
|
||||
} else if (response.status === 404) {
|
||||
console.log('⚠️ 휴가 API 없음, 더미 데이터 생성');
|
||||
this.attendanceData = this.generateDummyAttendance();
|
||||
} else {
|
||||
throw new Error(`휴가 정보 로드 실패: ${response.status}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('⚠️ 휴가 정보 로드 오류, 더미 데이터 사용:', error.message);
|
||||
this.attendanceData = this.generateDummyAttendance();
|
||||
}
|
||||
}
|
||||
@@ -204,7 +201,6 @@ class WorkReportReviewManager {
|
||||
if (response.status === 404 || response.status === 500) {
|
||||
// API가 아직 준비되지 않은 경우 더미 데이터 사용
|
||||
this.reports = this.generateDummyData();
|
||||
console.log('⚠️ API 응답 오류, 더미 데이터 사용:', response.status);
|
||||
if (response.status === 404) {
|
||||
this.showMessage('⚠️ 검토 API가 준비되지 않아 더미 데이터를 표시합니다.', 'warning');
|
||||
} else {
|
||||
@@ -227,7 +223,6 @@ class WorkReportReviewManager {
|
||||
this.updateTable();
|
||||
|
||||
} catch (error) {
|
||||
console.log('⚠️ 네트워크 오류로 더미 데이터 사용:', error.message);
|
||||
// 더미 데이터로 대체
|
||||
this.reports = this.generateDummyData();
|
||||
this.validateWorkHours();
|
||||
|
||||
@@ -499,14 +499,13 @@ async function saveEditedWork(workId) {
|
||||
body: JSON.stringify(updateData)
|
||||
});
|
||||
|
||||
console.log('✅ 수정 성공 (통합 API):', result);
|
||||
showMessage('✅ 작업이 성공적으로 수정되었습니다!', 'success');
|
||||
|
||||
closeEditModal();
|
||||
refreshCurrentDay(); // 현재 날짜 데이터 새로고침
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 수정 실패:', error);
|
||||
console.error(' 수정 실패:', error);
|
||||
showMessage('수정 중 오류가 발생했습니다: ' + error.message, 'error');
|
||||
|
||||
// 버튼 복원
|
||||
@@ -538,13 +537,12 @@ async function deleteWorkItem(workId) {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
console.log('✅ 삭제 성공 (통합 API):', result);
|
||||
showMessage('✅ 작업이 성공적으로 삭제되었습니다!', 'success');
|
||||
|
||||
refreshCurrentDay(); // 현재 날짜 데이터 새로고침
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 삭제 실패:', error);
|
||||
console.error(' 삭제 실패:', error);
|
||||
showMessage('삭제 중 오류가 발생했습니다: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
@@ -597,7 +595,7 @@ async function deleteWorkerAllWorks(date, workerName) {
|
||||
refreshCurrentDay(); // 현재 날짜 데이터 새로고침
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 전체 삭제 실패:', error);
|
||||
console.error(' 전체 삭제 실패:', error);
|
||||
showMessage('작업 삭제 중 오류가 발생했습니다: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
@@ -638,7 +636,6 @@ function showConfirmDialog(title, message, warning) {
|
||||
// 기본 데이터 로드 (통합 API 사용)
|
||||
async function loadBasicData() {
|
||||
try {
|
||||
console.log('🔗 통합 API 설정을 사용한 기본 데이터 로딩...');
|
||||
|
||||
const promises = [
|
||||
// 프로젝트 로드
|
||||
@@ -695,7 +692,6 @@ async function loadBasicData() {
|
||||
errorTypes
|
||||
};
|
||||
|
||||
console.log('✅ 기본 데이터 로드 완료 (통합 API):', basicData);
|
||||
} catch (error) {
|
||||
console.error('기본 데이터 로드 실패:', error);
|
||||
}
|
||||
@@ -764,7 +760,6 @@ async function init() {
|
||||
// 기본 데이터 미리 로드
|
||||
await loadBasicData();
|
||||
|
||||
console.log('✅ 검토 페이지 초기화 완료 (통합 API 설정 적용)');
|
||||
|
||||
} catch (error) {
|
||||
console.error('초기화 오류:', error);
|
||||
|
||||
@@ -1,318 +0,0 @@
|
||||
/**
|
||||
* Daily Work Report - Module Loader
|
||||
* 작업보고서 모듈을 초기화하고 연결하는 메인 진입점
|
||||
*
|
||||
* 로드 순서:
|
||||
* 1. state.js - 전역 상태 관리
|
||||
* 2. utils.js - 유틸리티 함수
|
||||
* 3. api.js - API 클라이언트
|
||||
* 4. index.js - 이 파일 (메인 컨트롤러)
|
||||
*/
|
||||
|
||||
class DailyWorkReportController {
|
||||
constructor() {
|
||||
this.state = window.DailyWorkReportState;
|
||||
this.api = window.DailyWorkReportAPI;
|
||||
this.utils = window.DailyWorkReportUtils;
|
||||
this.initialized = false;
|
||||
|
||||
console.log('[Controller] DailyWorkReportController 생성');
|
||||
}
|
||||
|
||||
/**
|
||||
* 초기화
|
||||
*/
|
||||
async init() {
|
||||
if (this.initialized) {
|
||||
console.log('[Controller] 이미 초기화됨');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[Controller] 초기화 시작...');
|
||||
|
||||
try {
|
||||
// 이벤트 리스너 설정
|
||||
this.setupEventListeners();
|
||||
|
||||
// 기본 데이터 로드
|
||||
await this.api.loadAllData();
|
||||
|
||||
// TBM 탭이 기본
|
||||
await this.switchTab('tbm');
|
||||
|
||||
this.initialized = true;
|
||||
console.log('[Controller] 초기화 완료');
|
||||
|
||||
} catch (error) {
|
||||
console.error('[Controller] 초기화 실패:', error);
|
||||
window.showMessage?.('초기화 중 오류가 발생했습니다: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 이벤트 리스너 설정
|
||||
*/
|
||||
setupEventListeners() {
|
||||
// 탭 버튼
|
||||
const tbmBtn = document.getElementById('tbmReportTab');
|
||||
const completedBtn = document.getElementById('completedReportTab');
|
||||
|
||||
if (tbmBtn) {
|
||||
tbmBtn.addEventListener('click', () => this.switchTab('tbm'));
|
||||
}
|
||||
if (completedBtn) {
|
||||
completedBtn.addEventListener('click', () => this.switchTab('completed'));
|
||||
}
|
||||
|
||||
// 완료 보고서 날짜 변경
|
||||
const completedDateInput = document.getElementById('completedReportDate');
|
||||
if (completedDateInput) {
|
||||
completedDateInput.addEventListener('change', () => this.loadCompletedReports());
|
||||
}
|
||||
|
||||
console.log('[Controller] 이벤트 리스너 설정 완료');
|
||||
}
|
||||
|
||||
/**
|
||||
* 탭 전환
|
||||
*/
|
||||
async switchTab(tab) {
|
||||
this.state.setCurrentTab(tab);
|
||||
|
||||
const tbmBtn = document.getElementById('tbmReportTab');
|
||||
const completedBtn = document.getElementById('completedReportTab');
|
||||
const tbmSection = document.getElementById('tbmReportSection');
|
||||
const completedSection = document.getElementById('completedReportSection');
|
||||
|
||||
// 모든 탭 버튼 비활성화
|
||||
tbmBtn?.classList.remove('active');
|
||||
completedBtn?.classList.remove('active');
|
||||
|
||||
// 모든 섹션 숨기기
|
||||
if (tbmSection) tbmSection.style.display = 'none';
|
||||
if (completedSection) completedSection.style.display = 'none';
|
||||
|
||||
// 선택된 탭 활성화
|
||||
if (tab === 'tbm') {
|
||||
tbmBtn?.classList.add('active');
|
||||
if (tbmSection) tbmSection.style.display = 'block';
|
||||
await this.loadTbmData();
|
||||
} else if (tab === 'completed') {
|
||||
completedBtn?.classList.add('active');
|
||||
if (completedSection) completedSection.style.display = 'block';
|
||||
|
||||
// 오늘 날짜로 초기화
|
||||
const dateInput = document.getElementById('completedReportDate');
|
||||
if (dateInput) {
|
||||
dateInput.value = this.utils.getKoreaToday();
|
||||
}
|
||||
await this.loadCompletedReports();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TBM 데이터 로드
|
||||
*/
|
||||
async loadTbmData() {
|
||||
try {
|
||||
await this.api.loadIncompleteTbms();
|
||||
await this.api.loadDailyIssuesForTbms();
|
||||
|
||||
// 렌더링은 기존 함수 사용 (점진적 마이그레이션)
|
||||
if (typeof window.renderTbmWorkList === 'function') {
|
||||
window.renderTbmWorkList();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Controller] TBM 데이터 로드 오류:', error);
|
||||
window.showMessage?.('TBM 데이터를 불러오는 중 오류가 발생했습니다.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 완료 보고서 로드
|
||||
*/
|
||||
async loadCompletedReports() {
|
||||
try {
|
||||
const dateInput = document.getElementById('completedReportDate');
|
||||
const date = dateInput?.value || this.utils.getKoreaToday();
|
||||
|
||||
const reports = await this.api.loadCompletedReports(date);
|
||||
|
||||
// 렌더링은 기존 함수 사용
|
||||
if (typeof window.renderCompletedReports === 'function') {
|
||||
window.renderCompletedReports(reports);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Controller] 완료 보고서 로드 오류:', error);
|
||||
window.showMessage?.('완료 보고서를 불러오는 중 오류가 발생했습니다.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TBM 작업보고서 제출
|
||||
*/
|
||||
async submitTbmWorkReport(index) {
|
||||
try {
|
||||
const tbm = this.state.incompleteTbms[index];
|
||||
if (!tbm) {
|
||||
throw new Error('TBM 데이터를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
// 유효성 검사
|
||||
const totalHoursInput = document.getElementById(`totalHours_${index}`);
|
||||
const totalHours = parseFloat(totalHoursInput?.value);
|
||||
|
||||
if (!totalHours || totalHours <= 0) {
|
||||
window.showMessage?.('작업시간을 입력해주세요.', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
// 부적합 시간 계산
|
||||
const defects = this.state.tempDefects[index] || [];
|
||||
const errorHours = defects.reduce((sum, d) => sum + (parseFloat(d.defect_hours) || 0), 0);
|
||||
const regularHours = totalHours - errorHours;
|
||||
|
||||
if (regularHours < 0) {
|
||||
window.showMessage?.('부적합 시간이 총 작업시간을 초과할 수 없습니다.', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
// API 데이터 구성
|
||||
const user = this.state.getCurrentUser();
|
||||
const reportData = {
|
||||
tbm_session_id: tbm.session_id,
|
||||
tbm_assignment_id: tbm.assignment_id,
|
||||
user_id: tbm.user_id,
|
||||
project_id: tbm.project_id,
|
||||
work_type_id: tbm.work_type_id,
|
||||
report_date: this.utils.formatDateForApi(tbm.session_date),
|
||||
total_hours: totalHours,
|
||||
regular_hours: regularHours,
|
||||
error_hours: errorHours,
|
||||
work_status_id: errorHours > 0 ? 2 : 1,
|
||||
created_by: user?.user_id || user?.id,
|
||||
defects: defects.map(d => ({
|
||||
category_id: d.category_id,
|
||||
item_id: d.item_id,
|
||||
issue_report_id: d.issue_report_id,
|
||||
defect_hours: d.defect_hours,
|
||||
note: d.note
|
||||
}))
|
||||
};
|
||||
|
||||
const result = await this.api.submitTbmWorkReport(reportData);
|
||||
|
||||
window.showSaveResultModal?.(
|
||||
'success',
|
||||
'제출 완료',
|
||||
`${tbm.worker_name}의 작업보고서가 제출되었습니다.`
|
||||
);
|
||||
|
||||
// 목록 새로고침
|
||||
await this.loadTbmData();
|
||||
|
||||
} catch (error) {
|
||||
console.error('[Controller] 제출 오류:', error);
|
||||
window.showSaveResultModal?.(
|
||||
'error',
|
||||
'제출 실패',
|
||||
error.message || '작업보고서 제출 중 오류가 발생했습니다.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 세션 일괄 제출
|
||||
*/
|
||||
async batchSubmitSession(sessionKey) {
|
||||
const rows = document.querySelectorAll(`tr[data-session-key="${sessionKey}"][data-type="tbm"]`);
|
||||
const indices = [];
|
||||
|
||||
rows.forEach(row => {
|
||||
const index = parseInt(row.dataset.index);
|
||||
const totalHoursInput = document.getElementById(`totalHours_${index}`);
|
||||
if (totalHoursInput?.value && parseFloat(totalHoursInput.value) > 0) {
|
||||
indices.push(index);
|
||||
}
|
||||
});
|
||||
|
||||
if (indices.length === 0) {
|
||||
window.showMessage?.('제출할 항목이 없습니다. 작업시간을 입력해주세요.', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmed = confirm(`${indices.length}건의 작업보고서를 일괄 제출하시겠습니까?`);
|
||||
if (!confirmed) return;
|
||||
|
||||
let successCount = 0;
|
||||
let failCount = 0;
|
||||
|
||||
for (const index of indices) {
|
||||
try {
|
||||
await this.submitTbmWorkReport(index);
|
||||
successCount++;
|
||||
} catch (error) {
|
||||
failCount++;
|
||||
console.error(`[Controller] 일괄 제출 오류 (index: ${index}):`, error);
|
||||
}
|
||||
}
|
||||
|
||||
if (failCount === 0) {
|
||||
window.showSaveResultModal?.('success', '일괄 제출 완료', `${successCount}건이 성공적으로 제출되었습니다.`);
|
||||
} else {
|
||||
window.showSaveResultModal?.('warning', '일괄 제출 부분 완료', `성공: ${successCount}건, 실패: ${failCount}건`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 상태 디버그
|
||||
*/
|
||||
debug() {
|
||||
console.log('[Controller] 상태 디버그:');
|
||||
this.state.debug();
|
||||
}
|
||||
}
|
||||
|
||||
// 전역 인스턴스 생성
|
||||
window.DailyWorkReportController = new DailyWorkReportController();
|
||||
|
||||
// 하위 호환성: 기존 전역 함수들
|
||||
window.switchTab = (tab) => window.DailyWorkReportController.switchTab(tab);
|
||||
window.submitTbmWorkReport = (index) => window.DailyWorkReportController.submitTbmWorkReport(index);
|
||||
window.batchSubmitTbmSession = (sessionKey) => window.DailyWorkReportController.batchSubmitSession(sessionKey);
|
||||
|
||||
// 사용자 정보 함수
|
||||
window.getUser = () => window.DailyWorkReportState.getUser();
|
||||
window.getCurrentUser = () => window.DailyWorkReportState.getCurrentUser();
|
||||
|
||||
// 날짜 그룹 토글 (UI 함수)
|
||||
window.toggleDateGroup = function(dateStr) {
|
||||
const group = document.querySelector(`.date-group[data-date="${dateStr}"]`);
|
||||
if (!group) return;
|
||||
|
||||
const isExpanded = group.classList.contains('expanded');
|
||||
const content = group.querySelector('.date-group-content');
|
||||
const icon = group.querySelector('.date-toggle-icon');
|
||||
|
||||
if (isExpanded) {
|
||||
group.classList.remove('expanded');
|
||||
group.classList.add('collapsed');
|
||||
if (content) content.style.display = 'none';
|
||||
if (icon) icon.textContent = '▶';
|
||||
} else {
|
||||
group.classList.remove('collapsed');
|
||||
group.classList.add('expanded');
|
||||
if (content) content.style.display = 'block';
|
||||
if (icon) icon.textContent = '▼';
|
||||
}
|
||||
};
|
||||
|
||||
// DOMContentLoaded 이벤트에서 초기화
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// 약간의 지연 후 초기화 (다른 스크립트 로드 대기)
|
||||
setTimeout(() => {
|
||||
window.DailyWorkReportController.init();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
console.log('[Module] daily-work-report/index.js 로드 완료');
|
||||
@@ -1,51 +0,0 @@
|
||||
// /js/work-report-api.js
|
||||
import { apiGet, apiPost } from './api-helper.js';
|
||||
|
||||
/**
|
||||
* 작업 보고서 작성을 위해 필요한 초기 데이터(작업자, 프로젝트, 태스크)를 가져옵니다.
|
||||
* Promise.all을 사용하여 병렬로 API를 호출합니다.
|
||||
* @returns {Promise<{workers: Array, projects: Array, tasks: Array}>}
|
||||
*/
|
||||
export async function getInitialData() {
|
||||
try {
|
||||
const [allWorkers, projects, tasks] = await Promise.all([
|
||||
apiGet('/workers'),
|
||||
apiGet('/projects'),
|
||||
apiGet('/tasks')
|
||||
]);
|
||||
|
||||
// 활성화된 작업자만 필터링
|
||||
const workers = allWorkers.filter(worker => {
|
||||
return worker.status === 'active' || worker.is_active === 1 || worker.is_active === true;
|
||||
});
|
||||
|
||||
// 데이터 형식 검증
|
||||
if (!Array.isArray(workers) || !Array.isArray(projects) || !Array.isArray(tasks)) {
|
||||
throw new Error('서버에서 받은 데이터 형식이 올바르지 않습니다.');
|
||||
}
|
||||
|
||||
// 작업자 목록은 ID 기준으로 정렬
|
||||
workers.sort((a, b) => a.user_id - b.user_id);
|
||||
|
||||
return { workers, projects, tasks };
|
||||
} catch (error) {
|
||||
console.error('초기 데이터 로딩 중 오류 발생:', error);
|
||||
// 에러를 다시 던져서 호출한 쪽에서 처리할 수 있도록 함
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 작성된 작업 보고서 데이터를 서버에 전송합니다.
|
||||
* @param {Array<object>} reportData - 전송할 작업 보고서 데이터 배열
|
||||
* @returns {Promise<object>} - 서버의 응답 결과
|
||||
*/
|
||||
export async function createWorkReport(reportData) {
|
||||
try {
|
||||
const result = await apiPost('/workreports', reportData);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('작업 보고서 생성 요청 실패:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
// /js/work-report-create.js
|
||||
import { renderCalendar } from './calendar.js';
|
||||
import { getInitialData, createWorkReport } from './work-report-api.js';
|
||||
import { initializeReportTable, getReportData } from './work-report-ui.js';
|
||||
|
||||
// 전역 상태 변수
|
||||
let selectedDate = '';
|
||||
|
||||
/**
|
||||
* 날짜가 선택되었을 때 실행되는 콜백 함수.
|
||||
* 초기 데이터를 로드하고 테이블을 렌더링합니다.
|
||||
* @param {string} date - 선택된 날짜 (YYYY-MM-DD 형식)
|
||||
*/
|
||||
async function onDateSelect(date) {
|
||||
selectedDate = date;
|
||||
const tableBody = document.getElementById('reportBody');
|
||||
tableBody.innerHTML = '<tr><td colspan="8" class="text-center">데이터를 불러오는 중...</td></tr>';
|
||||
|
||||
try {
|
||||
const initialData = await getInitialData();
|
||||
initializeReportTable(initialData);
|
||||
} catch (error) {
|
||||
alert('데이터를 불러오는 데 실패했습니다: ' + error.message);
|
||||
tableBody.innerHTML = '<tr><td colspan="8" class="text-center error">오류 발생! 데이터를 불러올 수 없습니다.</td></tr>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* '전체 등록' 버튼 클릭 시 실행되는 이벤트 핸들러.
|
||||
* 폼 데이터를 서버에 전송합니다.
|
||||
*/
|
||||
async function handleSubmit() {
|
||||
if (!selectedDate) {
|
||||
alert('먼저 달력에서 날짜를 선택해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
const reportData = getReportData();
|
||||
if (!reportData) {
|
||||
// getReportData 내부에서 이미 alert으로 사용자에게 알림
|
||||
return;
|
||||
}
|
||||
|
||||
// 각 항목에 선택된 날짜 추가
|
||||
const payload = reportData.map(item => ({ ...item, date: selectedDate }));
|
||||
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = '등록 중...';
|
||||
|
||||
try {
|
||||
const result = await createWorkReport(payload);
|
||||
if (result.success) {
|
||||
alert('✅ 작업 보고서가 성공적으로 등록되었습니다!');
|
||||
// 성공 후 폼을 다시 로드하거나, 다른 페이지로 이동 등의 로직 추가 가능
|
||||
onDateSelect(selectedDate); // 현재 날짜의 폼을 다시 로드
|
||||
} else {
|
||||
throw new Error(result.error || '알 수 없는 오류로 등록에 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
alert('❌ 등록 실패: ' + error.message);
|
||||
} finally {
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = '전체 등록';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 페이지 초기화 함수
|
||||
*/
|
||||
function initializePage() {
|
||||
renderCalendar('calendar', onDateSelect);
|
||||
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
submitBtn.addEventListener('click', handleSubmit);
|
||||
}
|
||||
|
||||
// DOM이 로드되면 페이지 초기화를 시작합니다.
|
||||
document.addEventListener('DOMContentLoaded', initializePage);
|
||||
@@ -1,141 +0,0 @@
|
||||
// /js/work-report-ui.js
|
||||
|
||||
const DEFAULT_PROJECT_ID = '13'; // 나중에는 API나 설정에서 받아오는 것이 좋음
|
||||
const DEFAULT_TASK_ID = '15';
|
||||
|
||||
/**
|
||||
* 주어진 데이터를 바탕으로 <select> 요소의 <option>들을 생성합니다.
|
||||
* @param {Array<object>} items - 옵션으로 만들 데이터 배열
|
||||
* @param {string} valueField - <option>의 value 속성에 사용할 필드 이름
|
||||
* @param {string} textField - <option>의 텍스트에 사용할 필드 이름
|
||||
* @returns {string} - 생성된 HTML 옵션 문자열
|
||||
*/
|
||||
function createOptions(items, valueField, textField) {
|
||||
return items.map(item => `<option value="${item[valueField]}">${textField(item)}</option>`).join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* 테이블의 모든 행 번호를 다시 매깁니다.
|
||||
* @param {HTMLTableSectionElement} tableBody - tbody 요소
|
||||
*/
|
||||
function updateRowNumbers(tableBody) {
|
||||
tableBody.querySelectorAll('tr').forEach((tr, index) => {
|
||||
tr.cells[0].textContent = index + 1;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 하나의 작업 보고서 행(tr)을 생성합니다.
|
||||
* @param {object} worker - 작업자 정보
|
||||
* @param {Array} projects - 전체 프로젝트 목록
|
||||
* @param {Array} tasks - 전체 태스크 목록
|
||||
* @param {number} index - 행 번호
|
||||
* @returns {HTMLTableRowElement} - 생성된 tr 요소
|
||||
*/
|
||||
function createReportRow(worker, projects, tasks, index) {
|
||||
const tr = document.createElement('tr');
|
||||
|
||||
tr.innerHTML = `
|
||||
<td>${index + 1}</td>
|
||||
<td>
|
||||
<input type="hidden" name="user_id" value="${worker.user_id}">
|
||||
${worker.worker_name}
|
||||
</td>
|
||||
<td><select name="project_id">${createOptions(projects, 'project_id', p => p.project_name)}</select></td>
|
||||
<td><select name="task_id">${createOptions(tasks, 'task_id', t => `${t.category}:${t.subcategory}`)}</select></td>
|
||||
<td>
|
||||
<select name="overtime">
|
||||
<option value="">없음</option>
|
||||
${[1, 2, 3, 4].map(n => `<option>${n}</option>`).join('')}
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<select name="work_type">
|
||||
${['근무', '연차', '유급', '반차', '반반차', '조퇴', '휴무'].map(t => `<option>${t}</option>`).join('')}
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="text" name="memo" placeholder="메모"></td>
|
||||
<td><button type="button" class="remove-btn">x</button></td>
|
||||
`;
|
||||
|
||||
// 이벤트 리스너 설정
|
||||
const workTypeSelect = tr.querySelector('[name="work_type"]');
|
||||
const projectSelect = tr.querySelector('[name="project_id"]');
|
||||
const taskSelect = tr.querySelector('[name="task_id"]');
|
||||
|
||||
workTypeSelect.addEventListener('change', () => {
|
||||
const isDisabled = ['연차', '휴무', '유급'].includes(workTypeSelect.value);
|
||||
projectSelect.disabled = isDisabled;
|
||||
taskSelect.disabled = isDisabled;
|
||||
if (isDisabled) {
|
||||
projectSelect.value = DEFAULT_PROJECT_ID;
|
||||
taskSelect.value = DEFAULT_TASK_ID;
|
||||
}
|
||||
});
|
||||
|
||||
tr.querySelector('.remove-btn').addEventListener('click', () => {
|
||||
tr.remove();
|
||||
updateRowNumbers(tr.parentElement);
|
||||
});
|
||||
|
||||
return tr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 작업 보고서 테이블을 초기화하고 데이터를 채웁니다.
|
||||
* @param {{workers: Array, projects: Array, tasks: Array}} initialData - 초기 데이터
|
||||
*/
|
||||
export function initializeReportTable(initialData) {
|
||||
const tableBody = document.getElementById('reportBody');
|
||||
if (!tableBody) return;
|
||||
|
||||
tableBody.innerHTML = ''; // 기존 내용 초기화
|
||||
const { workers, projects, tasks } = initialData;
|
||||
|
||||
if (!workers || workers.length === 0) {
|
||||
tableBody.innerHTML = '<tr><td colspan="8" class="text-center">등록할 작업자 정보가 없습니다.</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
workers.forEach((worker, index) => {
|
||||
const row = createReportRow(worker, projects, tasks, index);
|
||||
tableBody.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 테이블에서 폼 데이터를 추출하여 배열로 반환합니다.
|
||||
* @returns {Array<object>|null} - 추출된 데이터 배열 또는 유효성 검사 실패 시 null
|
||||
*/
|
||||
export function getReportData() {
|
||||
const tableBody = document.getElementById('reportBody');
|
||||
const rows = tableBody.querySelectorAll('tr');
|
||||
|
||||
if (rows.length === 0 || (rows.length === 1 && rows[0].cells.length < 2)) {
|
||||
alert('등록할 내용이 없습니다.');
|
||||
return null;
|
||||
}
|
||||
|
||||
const reportData = [];
|
||||
const workerIds = new Set();
|
||||
|
||||
for (const tr of rows) {
|
||||
const workerId = tr.querySelector('[name="user_id"]').value;
|
||||
if (workerIds.has(workerId)) {
|
||||
alert(`오류: 작업자 '${tr.cells[1].textContent.trim()}'가 중복 등록되었습니다.`);
|
||||
return null;
|
||||
}
|
||||
workerIds.add(workerId);
|
||||
|
||||
reportData.push({
|
||||
user_id: workerId,
|
||||
project_id: tr.querySelector('[name="project_id"]').value,
|
||||
task_id: tr.querySelector('[name="task_id"]').value,
|
||||
overtime_hours: tr.querySelector('[name="overtime"]').value || 0,
|
||||
work_details: tr.querySelector('[name="work_type"]').value,
|
||||
memo: tr.querySelector('[name="memo"]').value
|
||||
});
|
||||
}
|
||||
|
||||
return reportData;
|
||||
}
|
||||
@@ -50,13 +50,11 @@ const elements = {
|
||||
|
||||
// ========== 초기화 ========== //
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
console.log('🔧 관리자 설정 페이지 초기화 시작');
|
||||
|
||||
try {
|
||||
await initializePage();
|
||||
console.log('✅ 관리자 설정 페이지 초기화 완료');
|
||||
} catch (error) {
|
||||
console.error('❌ 페이지 초기화 오류:', error);
|
||||
console.error(' 페이지 초기화 오류:', error);
|
||||
showToast('페이지를 불러오는 중 오류가 발생했습니다.', 'error');
|
||||
}
|
||||
});
|
||||
@@ -75,7 +73,6 @@ function setupUserInfo() {
|
||||
const authData = getAuthData();
|
||||
if (authData && authData.user) {
|
||||
currentUser = authData.user;
|
||||
console.log('👤 사용자 정보 로드 완료:', currentUser.name, currentUser.role);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,13 +147,11 @@ function setupEventListeners() {
|
||||
// ========== 사용자 관리 ========== //
|
||||
async function loadUsers() {
|
||||
try {
|
||||
console.log('👥 사용자 목록 로딩...');
|
||||
|
||||
// 실제 API에서 사용자 데이터 가져오기
|
||||
const response = await window.apiCall('/users');
|
||||
users = Array.isArray(response) ? response : (response.data || []);
|
||||
|
||||
console.log(`✅ 사용자 ${users.length}명 로드 완료`);
|
||||
|
||||
// 필터링된 사용자 목록 초기화
|
||||
filteredUsers = [...users];
|
||||
@@ -165,7 +160,7 @@ async function loadUsers() {
|
||||
renderUsersTable();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 사용자 목록 로딩 오류:', error);
|
||||
console.error(' 사용자 목록 로딩 오류:', error);
|
||||
showToast('사용자 목록을 불러오는 중 오류가 발생했습니다.', 'error');
|
||||
users = [];
|
||||
filteredUsers = [];
|
||||
@@ -556,9 +551,8 @@ async function loadAllPages() {
|
||||
try {
|
||||
const response = await apiCall('/pages');
|
||||
allPages = response.data || response || [];
|
||||
console.log('📄 페이지 목록 로드:', allPages.length, '개');
|
||||
} catch (error) {
|
||||
console.error('❌ 페이지 목록 로드 오류:', error);
|
||||
console.error(' 페이지 목록 로드 오류:', error);
|
||||
allPages = [];
|
||||
}
|
||||
}
|
||||
@@ -568,9 +562,8 @@ async function loadUserPageAccess(userId) {
|
||||
try {
|
||||
const response = await apiCall(`/users/${userId}/page-access`);
|
||||
userPageAccess = response.data?.pageAccess || [];
|
||||
console.log(`👤 사용자 ${userId} 페이지 권한 로드:`, userPageAccess.length, '개');
|
||||
} catch (error) {
|
||||
console.error('❌ 사용자 페이지 권한 로드 오류:', error);
|
||||
console.error(' 사용자 페이지 권한 로드 오류:', error);
|
||||
userPageAccess = [];
|
||||
}
|
||||
}
|
||||
@@ -595,15 +588,13 @@ async function savePageAccess(userId, containerId = null) {
|
||||
|
||||
const pageAccessData = Array.from(pageAccessMap.values());
|
||||
|
||||
console.log('📤 페이지 권한 저장:', userId, pageAccessData);
|
||||
|
||||
await apiCall(`/users/${userId}/page-access`, 'PUT', {
|
||||
pageAccess: pageAccessData
|
||||
});
|
||||
|
||||
console.log('✅ 페이지 권한 저장 완료');
|
||||
} catch (error) {
|
||||
console.error('❌ 페이지 권한 저장 오류:', error);
|
||||
console.error(' 페이지 권한 저장 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -647,7 +638,7 @@ async function managePageAccess(userId) {
|
||||
// 모달 표시
|
||||
document.getElementById('pageAccessModal').style.display = 'flex';
|
||||
} catch (error) {
|
||||
console.error('❌ 페이지 권한 관리 모달 오류:', error);
|
||||
console.error(' 페이지 권한 관리 모달 오류:', error);
|
||||
showToast('페이지 권한 관리를 열 수 없습니다.', 'error');
|
||||
}
|
||||
}
|
||||
@@ -775,7 +766,7 @@ async function savePageAccessFromModal() {
|
||||
|
||||
closePageAccessModal();
|
||||
} catch (error) {
|
||||
console.error('❌ 페이지 권한 저장 오류:', error);
|
||||
console.error(' 페이지 권한 저장 오류:', error);
|
||||
showToast('페이지 권한 저장에 실패했습니다.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ if ('caches' in window) {
|
||||
* sso_token이 없으면 기존 token도 확인 (하위 호환)
|
||||
*/
|
||||
window.getSSOToken = function() {
|
||||
return cookieGet('sso_token') || localStorage.getItem('sso_token') || localStorage.getItem('token');
|
||||
return cookieGet('sso_token') || localStorage.getItem('sso_token');
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -43,10 +43,6 @@ if ('caches' in window) {
|
||||
*/
|
||||
window.getSSOUser = function() {
|
||||
var raw = cookieGet('sso_user') || localStorage.getItem('sso_user');
|
||||
if (!raw) {
|
||||
// 기존 user 키도 확인 (하위 호환)
|
||||
raw = localStorage.getItem('user');
|
||||
}
|
||||
try { return raw ? JSON.parse(raw) : null; } catch(e) { return null; }
|
||||
};
|
||||
|
||||
@@ -69,13 +65,9 @@ if ('caches' in window) {
|
||||
cookieRemove('sso_token');
|
||||
cookieRemove('sso_user');
|
||||
cookieRemove('sso_refresh_token');
|
||||
localStorage.removeItem('sso_token');
|
||||
localStorage.removeItem('sso_user');
|
||||
localStorage.removeItem('sso_refresh_token');
|
||||
// 기존 키도 삭제 (하위 호환)
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('user');
|
||||
localStorage.removeItem('userPageAccess');
|
||||
['sso_token','sso_user','sso_refresh_token','token','user','access_token','currentUser','current_user','userInfo','userPageAccess'].forEach(function(k) {
|
||||
localStorage.removeItem(k);
|
||||
});
|
||||
};
|
||||
|
||||
// ==================== 보안 유틸리티 (XSS 방지) ====================
|
||||
|
||||
@@ -7,12 +7,10 @@ function getApiBaseUrl() {
|
||||
const protocol = window.location.protocol;
|
||||
const port = window.location.port;
|
||||
|
||||
console.log('🌐 감지된 환경:', { hostname, protocol, port });
|
||||
|
||||
// 🔗 외부 도메인 (Cloudflare Tunnel) - Gateway nginx가 /api/를 프록시
|
||||
if (hostname.includes('technicalkorea.net')) {
|
||||
const baseUrl = `${protocol}//${hostname}${config.api.path}`;
|
||||
console.log('✅ Gateway 프록시 사용:', baseUrl);
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
@@ -21,27 +19,24 @@ function getApiBaseUrl() {
|
||||
hostname === 'localhost' || hostname === '127.0.0.1' ||
|
||||
hostname.includes('.local') || hostname.includes('hyungi')) {
|
||||
const baseUrl = `${protocol}//${hostname}:${config.api.port}${config.api.path}`;
|
||||
console.log('✅ 로컬 직접 접근:', baseUrl);
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
// 🚨 기타: 포트 없이 상대 경로
|
||||
const baseUrl = `${protocol}//${hostname}${config.api.path}`;
|
||||
console.log('✅ 기본 프록시 사용:', baseUrl);
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
// API 설정
|
||||
const API_URL = getApiBaseUrl();
|
||||
|
||||
// 전역 변수로 설정
|
||||
window.API = API_URL;
|
||||
window.API_BASE_URL = API_URL;
|
||||
// 전역 변수로 설정 (api-base.js가 이미 설정한 경우 유지)
|
||||
if (!window.API) window.API = API_URL;
|
||||
if (!window.API_BASE_URL) window.API_BASE_URL = API_URL;
|
||||
|
||||
function ensureAuthenticated() {
|
||||
const token = localStorage.getItem('sso_token');
|
||||
if (!token || token === 'undefined' || token === 'null') {
|
||||
console.log('🚨 인증되지 않은 사용자. 로그인 페이지로 이동합니다.');
|
||||
clearAuthData(); // 만약을 위해 한번 더 정리
|
||||
redirectToLogin();
|
||||
return false; // 이후 코드 실행 방지
|
||||
@@ -49,7 +44,6 @@ function ensureAuthenticated() {
|
||||
|
||||
// 토큰 만료 확인
|
||||
if (isTokenExpired(token)) {
|
||||
console.log('🚨 토큰이 만료되었습니다. 로그인 페이지로 이동합니다.');
|
||||
clearAuthData();
|
||||
alert('세션이 만료되었습니다. 다시 로그인해주세요.');
|
||||
redirectToLogin();
|
||||
@@ -75,8 +69,6 @@ function isTokenExpired(token) {
|
||||
function clearAuthData() {
|
||||
localStorage.removeItem('sso_token');
|
||||
localStorage.removeItem('sso_user');
|
||||
localStorage.removeItem('userInfo');
|
||||
localStorage.removeItem('currentUser');
|
||||
// SSO 쿠키도 삭제 (로그인 페이지 자동 리다이렉트 방지)
|
||||
var cookieDomain = window.location.hostname.includes('technicalkorea.net')
|
||||
? '; domain=.technicalkorea.net' : '';
|
||||
@@ -112,12 +104,10 @@ async function apiCall(url, method = 'GET', data = null) {
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`📡 API 호출: ${fullUrl} (${method})`);
|
||||
const response = await fetch(fullUrl, options);
|
||||
|
||||
// 인증 만료 처리
|
||||
if (response.status === 401) {
|
||||
console.error('🚨 인증 실패: 토큰이 만료되었거나 유효하지 않습니다.');
|
||||
clearAuthData();
|
||||
alert('세션이 만료되었습니다. 다시 로그인해주세요.');
|
||||
redirectToLogin();
|
||||
@@ -132,8 +122,7 @@ async function apiCall(url, method = 'GET', data = null) {
|
||||
|
||||
if (contentType && contentType.includes('application/json')) {
|
||||
const errorData = await response.json();
|
||||
console.error('📋 서버 에러 상세:', errorData);
|
||||
|
||||
|
||||
// 에러 메시지 추출 (여러 형식 지원)
|
||||
if (typeof errorData === 'string') {
|
||||
errorMessage = errorData;
|
||||
@@ -150,23 +139,18 @@ async function apiCall(url, method = 'GET', data = null) {
|
||||
}
|
||||
} else {
|
||||
const errorText = await response.text();
|
||||
console.error('📋 서버 에러 텍스트:', errorText);
|
||||
errorMessage = errorText || errorMessage;
|
||||
errorMessage = errorText || errorMessage;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('📋 에러 파싱 중 예외 발생:', e.message);
|
||||
// 파싱 실패해도 HTTP 상태 코드는 전달
|
||||
}
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log(`✅ API 성공: ${fullUrl}`);
|
||||
return result;
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ API 오류 (${fullUrl}):`, error);
|
||||
console.error('❌ 에러 전체 내용:', JSON.stringify(error, null, 2));
|
||||
|
||||
// 네트워크 오류 vs 서버 오류 구분
|
||||
if (error.name === 'TypeError' && error.message.includes('fetch')) {
|
||||
@@ -177,36 +161,6 @@ async function apiCall(url, method = 'GET', data = null) {
|
||||
}
|
||||
}
|
||||
|
||||
// 디버깅 정보
|
||||
console.log('🔗 API Base URL:', API);
|
||||
console.log('🌐 Current Location:', {
|
||||
hostname: window.location.hostname,
|
||||
protocol: window.location.protocol,
|
||||
port: window.location.port,
|
||||
href: window.location.href
|
||||
});
|
||||
|
||||
// 🧪 API 연결 테스트 함수 (개발용)
|
||||
async function testApiConnection() {
|
||||
try {
|
||||
console.log('🧪 API 연결 테스트 시작...');
|
||||
const response = await fetch(`${API}/health`, {
|
||||
method: 'GET',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
console.log('✅ API 연결 성공!');
|
||||
return true;
|
||||
} else {
|
||||
console.log('❌ API 연결 실패:', response.status);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('❌ API 연결 오류:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// API 헬퍼 함수들
|
||||
async function apiGet(url) {
|
||||
@@ -225,35 +179,26 @@ async function apiDelete(url) {
|
||||
return apiCall(url, 'DELETE');
|
||||
}
|
||||
|
||||
// 전역 함수로 설정
|
||||
// 전역 함수로 설정 (api-base.js가 이미 등록한 것은 덮어쓰지 않음)
|
||||
window.ensureAuthenticated = ensureAuthenticated;
|
||||
window.getAuthHeaders = getAuthHeaders;
|
||||
window.apiCall = apiCall;
|
||||
if (!window.getAuthHeaders) window.getAuthHeaders = getAuthHeaders;
|
||||
if (!window.apiCall) window.apiCall = apiCall;
|
||||
window.apiGet = apiGet;
|
||||
window.apiPost = apiPost;
|
||||
window.apiPut = apiPut;
|
||||
window.apiDelete = apiDelete;
|
||||
window.testApiConnection = testApiConnection;
|
||||
window.isTokenExpired = isTokenExpired;
|
||||
window.clearAuthData = clearAuthData;
|
||||
|
||||
// 개발 모드에서 자동 테스트
|
||||
if (window.location.hostname === 'localhost' || window.location.hostname.startsWith('192.168.')) {
|
||||
setTimeout(() => {
|
||||
testApiConnection();
|
||||
}, 1000);
|
||||
}
|
||||
if (!window.clearAuthData) window.clearAuthData = clearAuthData;
|
||||
|
||||
// 주기적으로 토큰 만료 확인 (5분마다)
|
||||
setInterval(() => {
|
||||
const token = localStorage.getItem('sso_token');
|
||||
if (token && isTokenExpired(token)) {
|
||||
console.log('🚨 주기적 확인: 토큰이 만료되었습니다.');
|
||||
clearAuthData();
|
||||
alert('세션이 만료되었습니다. 다시 로그인해주세요.');
|
||||
redirectToLogin();
|
||||
}
|
||||
}, config.app.tokenRefreshInterval); // 5분마다 확인
|
||||
}, config.app.tokenRefreshInterval);
|
||||
|
||||
// ES6 모듈 export
|
||||
export { API_URL as API_BASE_URL, API_URL as API, apiCall, getAuthHeaders };
|
||||
@@ -8,7 +8,7 @@ const API_BASE_URL = window.API_BASE_URL || 'http://localhost:30005/api';
|
||||
function getToken() {
|
||||
// SSO 토큰 우선, 기존 token 폴백
|
||||
if (window.getSSOToken) return window.getSSOToken();
|
||||
const token = localStorage.getItem('sso_token') || localStorage.getItem('token');
|
||||
const token = localStorage.getItem('sso_token');
|
||||
return token && token !== 'undefined' && token !== 'null' ? token : null;
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@ function clearAuthData() {
|
||||
if (window.clearSSOAuth) { window.clearSSOAuth(); return; }
|
||||
localStorage.removeItem('sso_token');
|
||||
localStorage.removeItem('sso_user');
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('user');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,26 +12,24 @@
|
||||
|
||||
// ===== 인증 함수 (api-base.js의 SSO 함수 활용) =====
|
||||
function isLoggedIn() {
|
||||
const token = window.getSSOToken ? window.getSSOToken() : (localStorage.getItem('sso_token') || localStorage.getItem('token'));
|
||||
const token = window.getSSOToken ? window.getSSOToken() : localStorage.getItem('sso_token');
|
||||
return token && token !== 'undefined' && token !== 'null';
|
||||
}
|
||||
|
||||
function getUser() {
|
||||
if (window.getSSOUser) return window.getSSOUser();
|
||||
const user = localStorage.getItem('sso_user') || localStorage.getItem('user');
|
||||
const user = localStorage.getItem('sso_user');
|
||||
try { return user ? JSON.parse(user) : null; } catch(e) { return null; }
|
||||
}
|
||||
|
||||
function getToken() {
|
||||
return window.getSSOToken ? window.getSSOToken() : (localStorage.getItem('sso_token') || localStorage.getItem('token'));
|
||||
return window.getSSOToken ? window.getSSOToken() : localStorage.getItem('sso_token');
|
||||
}
|
||||
|
||||
function clearAuthData() {
|
||||
if (window.clearSSOAuth) { window.clearSSOAuth(); return; }
|
||||
localStorage.removeItem('sso_token');
|
||||
localStorage.removeItem('sso_user');
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('user');
|
||||
localStorage.removeItem('userPageAccess');
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,6 @@ function getCacheStatus() {
|
||||
*/
|
||||
function clearCache() {
|
||||
dateStatusCache.clear();
|
||||
console.log('📦 캐시가 클리어되었습니다.');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,7 +76,6 @@ function updatePerformanceUI() {
|
||||
*/
|
||||
function logPerformanceStatus() {
|
||||
const status = getCacheStatus();
|
||||
console.log('📊 성능 상태:', status);
|
||||
updatePerformanceUI();
|
||||
}
|
||||
|
||||
@@ -118,7 +116,7 @@ function getCurrentUser() {
|
||||
}
|
||||
|
||||
try {
|
||||
const userInfo = localStorage.getItem('sso_user') || localStorage.getItem('userInfo');
|
||||
const userInfo = localStorage.getItem('sso_user');
|
||||
if (userInfo) {
|
||||
return JSON.parse(userInfo);
|
||||
}
|
||||
@@ -391,18 +389,14 @@ async function calculateDateStatus(dateStr) {
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`📊 ${dateStr} 상태 계산 시작 - 순차 호출`);
|
||||
|
||||
// 1단계: WorkReports 먼저 가져오기
|
||||
console.log(`📝 1단계: WorkReports 조회 중...`);
|
||||
const workReports = await fetchWorkReports(dateStr);
|
||||
|
||||
// 2초 대기 (서버 부하 방지)
|
||||
console.log(`⏳ 2초 대기 중... (서버 부하 방지)`);
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
// 2단계: DailyWorkReports 가져오기
|
||||
console.log(`📊 2단계: DailyWorkReports 조회 중...`);
|
||||
const dailyReports = await fetchDailyWorkReports(dateStr);
|
||||
|
||||
let status;
|
||||
@@ -423,7 +417,6 @@ async function calculateDateStatus(dateStr) {
|
||||
|
||||
// 캐시에 저장
|
||||
dateStatusCache.set(dateStr, status);
|
||||
console.log(`✅ ${dateStr} 상태 계산 완료: ${status}`);
|
||||
return status;
|
||||
} catch (error) {
|
||||
console.error('날짜 상태 계산 오류:', error);
|
||||
@@ -564,10 +557,9 @@ async function loadAndUpdateDateStatus(dateStr, buttonElement) {
|
||||
}`;
|
||||
}
|
||||
|
||||
console.log(`✅ ${dateStr} 상태 로드 완료: ${status}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ ${dateStr} 상태 로드 실패:`, error);
|
||||
console.error(` ${dateStr} 상태 로드 실패:`, error);
|
||||
buttonElement.classList.remove('loading-state');
|
||||
buttonElement.classList.add('error-state');
|
||||
buttonElement.title = `${dateStr} - 로드 실패: ${error.message}`;
|
||||
@@ -589,18 +581,14 @@ async function loadAndUpdateDateStatus(dateStr, buttonElement) {
|
||||
*/
|
||||
async function getWorkersForDate(dateStr) {
|
||||
try {
|
||||
console.log(`👥 ${dateStr} 작업자 데이터 조합 시작 - 순차 호출`);
|
||||
|
||||
// 1단계: WorkReports 먼저 가져오기
|
||||
console.log(`📝 1단계: WorkReports 조회 중...`);
|
||||
const workReports = await fetchWorkReports(dateStr);
|
||||
|
||||
// 2초 대기 (서버 부하 방지)
|
||||
console.log(`⏳ 2초 대기 중... (서버 부하 방지)`);
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
// 2단계: DailyWorkReports 가져오기
|
||||
console.log(`📊 2단계: DailyWorkReports 조회 중...`);
|
||||
const dailyReports = await fetchDailyWorkReports(dateStr);
|
||||
|
||||
const workerMap = new Map();
|
||||
@@ -645,7 +633,6 @@ async function getWorkersForDate(dateStr) {
|
||||
validationStatus: getValidationStatus(worker)
|
||||
}));
|
||||
|
||||
console.log(`✅ ${dateStr} 작업자 데이터 조합 완료: ${result.length}명`);
|
||||
return result;
|
||||
|
||||
} catch (error) {
|
||||
@@ -1022,11 +1009,6 @@ async function init() {
|
||||
window.saveEditedWork = saveEditedWork;
|
||||
window.deleteWorker = deleteWorker;
|
||||
|
||||
console.log('✅ 근태 검증 관리 시스템 초기화 완료 (API 통합)');
|
||||
console.log(`🔗 API 경로: ${API}`);
|
||||
console.log(`📊 설정: 동시 최대 ${RATE_LIMIT.maxConcurrent}개 요청, ${RATE_LIMIT.delayBetweenRequests}ms 딜레이`);
|
||||
console.log('🔄 API 호출 방식: 통합 설정 + 순차 호출');
|
||||
console.log('🚫 429 에러 방지: 각 날짜당 최소 5초 간격');
|
||||
|
||||
} catch (error) {
|
||||
console.error('초기화 오류:', error);
|
||||
|
||||
@@ -123,7 +123,6 @@ async function checkPageAccess(pageKey) {
|
||||
// 즉시 실행 함수로 스코프를 보호하고 로직을 실행
|
||||
(async function() {
|
||||
if (!isLoggedIn()) {
|
||||
console.log('🚨 인증되지 않은 사용자. 로그인 페이지로 이동합니다.');
|
||||
clearAuthData(); // 만약을 위해 한번 더 정리
|
||||
window.location.href = window.getLoginUrl ? window.getLoginUrl() : '/login';
|
||||
return; // 이후 코드 실행 방지
|
||||
@@ -133,31 +132,28 @@ async function checkPageAccess(pageKey) {
|
||||
|
||||
// 사용자 정보가 유효한지 확인 (토큰은 있지만 유저 정보가 깨졌을 경우)
|
||||
if (!currentUser || !currentUser.username) {
|
||||
console.error('🚨 사용자 정보가 유효하지 않습니다. 강제 로그아웃 처리합니다.');
|
||||
console.error(' 사용자 정보가 유효하지 않습니다. 강제 로그아웃 처리합니다.');
|
||||
clearAuthData();
|
||||
window.location.href = window.getLoginUrl ? window.getLoginUrl() : '/login';
|
||||
return;
|
||||
}
|
||||
|
||||
const userRole = currentUser.role || currentUser.access_level || '사용자';
|
||||
console.log(`✅ ${currentUser.username}(${userRole})님 인증 성공.`);
|
||||
|
||||
// 페이지 접근 권한 체크 (Admin은 건너뛰기)
|
||||
if (currentUser.role !== 'Admin' && currentUser.role !== 'System Admin') {
|
||||
const pageKey = getCurrentPageKey();
|
||||
|
||||
if (pageKey) {
|
||||
console.log(`🔍 페이지 권한 체크: ${pageKey}`);
|
||||
const hasAccess = await checkPageAccess(pageKey);
|
||||
|
||||
if (!hasAccess) {
|
||||
console.error(`🚫 페이지 접근 권한이 없습니다: ${pageKey}`);
|
||||
console.error(` 페이지 접근 권한이 없습니다: ${pageKey}`);
|
||||
alert('이 페이지에 접근할 권한이 없습니다.');
|
||||
window.location.href = '/pages/dashboard.html';
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`✅ 페이지 접근 권한 확인됨: ${pageKey}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ export function parseJwt(token) {
|
||||
*/
|
||||
export function getToken() {
|
||||
if (window.getSSOToken) return window.getSSOToken();
|
||||
return localStorage.getItem('sso_token') || localStorage.getItem('token');
|
||||
return localStorage.getItem('sso_token');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -27,7 +27,7 @@ export function getToken() {
|
||||
*/
|
||||
export function getUser() {
|
||||
if (window.getSSOUser) return window.getSSOUser();
|
||||
const raw = localStorage.getItem('sso_user') || localStorage.getItem('user');
|
||||
const raw = localStorage.getItem('sso_user');
|
||||
try {
|
||||
return raw ? JSON.parse(raw) : null;
|
||||
} catch(e) {
|
||||
@@ -37,16 +37,11 @@ export function getUser() {
|
||||
|
||||
/**
|
||||
* 로그인 성공 후 토큰과 사용자 정보를 저장합니다.
|
||||
* 하위 호환성을 위해 sso_token/sso_user와 token/user 모두에 저장합니다.
|
||||
* sso_token/sso_user 키로 저장합니다.
|
||||
*/
|
||||
export function saveAuthData(token, user) {
|
||||
const userStr = JSON.stringify(user);
|
||||
// SSO 키
|
||||
localStorage.setItem('sso_token', token);
|
||||
localStorage.setItem('sso_user', userStr);
|
||||
// 하위 호환 키 (캐시된 구버전 app-init.js 대응)
|
||||
localStorage.setItem('token', token);
|
||||
localStorage.setItem('user', userStr);
|
||||
localStorage.setItem('sso_user', JSON.stringify(user));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,8 +51,6 @@ export function clearAuthData() {
|
||||
if (window.clearSSOAuth) { window.clearSSOAuth(); return; }
|
||||
localStorage.removeItem('sso_token');
|
||||
localStorage.removeItem('sso_user');
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('user');
|
||||
localStorage.removeItem('userPageAccess');
|
||||
}
|
||||
|
||||
|
||||
@@ -205,6 +205,4 @@ form?.addEventListener('submit', async (e) => {
|
||||
// 페이지 로드 시 현재 사용자 정보 표시
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const user = JSON.parse(localStorage.getItem('sso_user') || '{}');
|
||||
console.log('🔐 비밀번호 변경 페이지 로드됨');
|
||||
console.log('👤 현재 사용자:', user.username || 'Unknown');
|
||||
});
|
||||
@@ -43,14 +43,14 @@ async function getComponentHtml(componentName, componentPath) {
|
||||
export async function loadComponent(componentName, containerSelector, domProcessor = null) {
|
||||
const container = document.querySelector(containerSelector);
|
||||
if (!container) {
|
||||
console.warn(`⚠️ 컴포넌트를 삽입할 컨테이너를 찾을 수 없습니다: ${containerSelector} (선택사항일 수 있음)`);
|
||||
console.warn(` 컴포넌트를 삽입할 컨테이너를 찾을 수 없습니다: ${containerSelector} (선택사항일 수 있음)`);
|
||||
return;
|
||||
}
|
||||
|
||||
const componentPath = config.components[componentName];
|
||||
if (!componentPath) {
|
||||
console.error(`🔴 설정 파일(config.js)에서 '${componentName}' 컴포넌트의 경로를 찾을 수 없습니다.`);
|
||||
container.innerHTML = `<p>${componentName} 로딩 실패</p>`;
|
||||
console.error(` 설정 파일(config.js)에서 '${componentName}' 컴포넌트의 경로를 찾을 수 없습니다.`);
|
||||
container.textContent = `${componentName} 로딩 실패`;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -72,10 +72,9 @@ export async function loadComponent(componentName, containerSelector, domProcess
|
||||
container.innerHTML = htmlText;
|
||||
}
|
||||
|
||||
console.log(`✅ '${componentName}' 컴포넌트 로딩 완료: ${containerSelector}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`🔴 '${componentName}' 컴포넌트 로딩 실패:`, error);
|
||||
container.innerHTML = `<p>${componentName} 로딩에 실패했습니다. 관리자에게 문의하세요.</p>`;
|
||||
console.error(` '${componentName}' 컴포넌트 로딩 실패:`, error);
|
||||
container.textContent = `${componentName} 로딩에 실패했습니다. 관리자에게 문의하세요.`;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// daily-work-report.js - 브라우저 호환 버전
|
||||
|
||||
// =================================================================
|
||||
// 🌐 API 설정 (window 객체에서 가져오기)
|
||||
// API 설정 (window 객체에서 가져오기)
|
||||
// =================================================================
|
||||
// API 설정은 api-config.js에서 window 객체에 설정됨
|
||||
|
||||
@@ -183,7 +183,7 @@ function formatDateForApi(date) {
|
||||
*/
|
||||
function getUser() {
|
||||
if (window.getSSOUser) return window.getSSOUser();
|
||||
const raw = localStorage.getItem('sso_user') || localStorage.getItem('user');
|
||||
const raw = localStorage.getItem('sso_user');
|
||||
try { return raw ? JSON.parse(raw) : null; } catch(e) { return null; }
|
||||
}
|
||||
|
||||
@@ -233,7 +233,7 @@ function renderTbmWorkList() {
|
||||
<div style="margin-bottom: 1rem; display: flex; justify-content: space-between; align-items: center;">
|
||||
<h3 style="margin: 0;">작업보고서 목록</h3>
|
||||
<button type="button" class="btn-add-work" onclick="addManualWorkRow()">
|
||||
➕ 작업 추가
|
||||
작업 추가
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
@@ -247,7 +247,7 @@ function renderTbmWorkList() {
|
||||
<span class="tbm-session-info" style="color: white; font-weight: 500;">TBM에 없는 작업을 추가로 입력할 수 있습니다</span>
|
||||
</div>
|
||||
<button type="button" class="btn-batch-submit" onclick="submitAllManualWorkReports()" style="background: #fff; color: #d97706; border: none; padding: 0.4rem 0.8rem; border-radius: 4px; font-weight: 600; cursor: pointer; font-size: 0.8rem;">
|
||||
📤 일괄 제출
|
||||
일괄 제출
|
||||
</button>
|
||||
</div>
|
||||
<div class="tbm-table-container">
|
||||
@@ -326,7 +326,7 @@ function renderTbmWorkList() {
|
||||
html += `
|
||||
<div class="issue-reminder-section">
|
||||
<div class="issue-reminder-header">
|
||||
<span class="issue-reminder-icon">⚠️</span>
|
||||
<span class="issue-reminder-icon"></span>
|
||||
<span class="issue-reminder-title">당일 신고된 문제</span>
|
||||
<span class="issue-reminder-count">${relatedIssues.length}건</span>
|
||||
</div>
|
||||
@@ -349,7 +349,7 @@ function renderTbmWorkList() {
|
||||
${relatedIssues.length > 5 ? `<div class="issue-reminder-more">외 ${relatedIssues.length - 5}건 더 있음</div>` : ''}
|
||||
</div>
|
||||
<div class="issue-reminder-hint">
|
||||
💡 위 문제로 인해 작업이 지연되었다면, 아래에서 부적합 시간을 추가해주세요.
|
||||
위 문제로 인해 작업이 지연되었다면, 아래에서 부적합 시간을 추가해주세요.
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -474,7 +474,7 @@ function renderTbmWorkList() {
|
||||
<button type="button"
|
||||
class="btn-batch-submit"
|
||||
onclick="batchSubmitTbmSession('${key}')">
|
||||
📤 이 세션 일괄제출 (${group.items.length}건)
|
||||
이 세션 일괄제출 (${group.items.length}건)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -572,7 +572,7 @@ window.submitTbmWorkReport = async function(index) {
|
||||
}
|
||||
|
||||
// 부적합 원인 유효성 검사 (issue_report_id 또는 category_id 또는 error_type_id 필요)
|
||||
console.log('🔍 부적합 검증 시작:', defects.map(d => ({
|
||||
console.log(' 부적합 검증 시작:', defects.map(d => ({
|
||||
defect_hours: d.defect_hours,
|
||||
category_id: d.category_id,
|
||||
item_id: d.item_id,
|
||||
@@ -583,7 +583,7 @@ window.submitTbmWorkReport = async function(index) {
|
||||
|
||||
const invalidDefects = defects.filter(d => d.defect_hours > 0 && !d.error_type_id && !d.issue_report_id && !d.category_id && !d.item_id);
|
||||
if (invalidDefects.length > 0) {
|
||||
console.error('❌ 유효하지 않은 부적합:', invalidDefects);
|
||||
console.error(' 유효하지 않은 부적합:', invalidDefects);
|
||||
showMessage('부적합 시간이 있는 항목은 원인을 선택해주세요.', 'error');
|
||||
return;
|
||||
}
|
||||
@@ -610,8 +610,6 @@ window.submitTbmWorkReport = async function(index) {
|
||||
work_status_id: errorHours > 0 ? 2 : 1
|
||||
};
|
||||
|
||||
console.log('🔍 TBM 제출 데이터:', JSON.stringify(reportData, null, 2));
|
||||
console.log('🔍 부적합 원인:', defects);
|
||||
|
||||
try {
|
||||
const response = await window.apiCall('/daily-work-reports/from-tbm', 'POST', reportData);
|
||||
@@ -623,7 +621,7 @@ window.submitTbmWorkReport = async function(index) {
|
||||
// 부적합 원인이 있으면 저장 (이슈 기반 또는 레거시)
|
||||
if (defects.length > 0 && response.data?.report_id) {
|
||||
const validDefects = defects.filter(d => (d.issue_report_id || d.category_id || d.item_id || d.error_type_id) && d.defect_hours > 0);
|
||||
console.log('📋 부적합 원인 필터링:', {
|
||||
console.log(' 부적합 원인 필터링:', {
|
||||
전체: defects.length,
|
||||
유효: validDefects.length,
|
||||
validDefects: validDefects.map(d => ({
|
||||
@@ -645,20 +643,17 @@ window.submitTbmWorkReport = async function(index) {
|
||||
note: d.note || ''
|
||||
}));
|
||||
|
||||
console.log('📤 부적합 저장 요청:', defectsToSend);
|
||||
|
||||
const defectResponse = await window.apiCall(`/daily-work-reports/${response.data.report_id}/defects`, 'PUT', {
|
||||
defects: defectsToSend
|
||||
});
|
||||
|
||||
if (!defectResponse.success) {
|
||||
console.error('❌ 부적합 저장 실패:', defectResponse);
|
||||
console.error(' 부적합 저장 실패:', defectResponse);
|
||||
showMessage('작업보고서는 저장되었으나 부적합 원인 저장에 실패했습니다.', 'warning');
|
||||
} else {
|
||||
console.log('✅ 부적합 저장 성공:', defectResponse);
|
||||
}
|
||||
} else {
|
||||
console.log('⚠️ 유효한 부적합 항목이 없어 저장 건너뜀');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -808,7 +803,7 @@ window.batchSubmitTbmSession = async function(sessionKey) {
|
||||
'success',
|
||||
'일괄제출 완료',
|
||||
`${totalCount}건의 작업보고서가 모두 성공적으로 제출되었습니다.`,
|
||||
results.success.map(name => `✓ ${name}`)
|
||||
results.success.map(name => ` ${name}`)
|
||||
);
|
||||
} else if (successCount === 0) {
|
||||
// 모두 실패
|
||||
@@ -816,13 +811,13 @@ window.batchSubmitTbmSession = async function(sessionKey) {
|
||||
'error',
|
||||
'일괄제출 실패',
|
||||
`${totalCount}건의 작업보고서가 모두 실패했습니다.`,
|
||||
results.failed.map(msg => `✗ ${msg}`)
|
||||
results.failed.map(msg => ` ${msg}`)
|
||||
);
|
||||
} else {
|
||||
// 일부 성공, 일부 실패
|
||||
const details = [
|
||||
...results.success.map(name => `✓ ${name} - 성공`),
|
||||
...results.failed.map(msg => `✗ ${msg}`)
|
||||
...results.success.map(name => ` ${name} - 성공`),
|
||||
...results.failed.map(msg => ` ${msg}`)
|
||||
];
|
||||
showSaveResultModal(
|
||||
'warning',
|
||||
@@ -840,7 +835,7 @@ window.batchSubmitTbmSession = async function(sessionKey) {
|
||||
} finally {
|
||||
submitBtn.classList.remove('is-loading');
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = `📤 이 세션 일괄제출 (${sessionRows.length}건)`;
|
||||
submitBtn.textContent = ` 이 세션 일괄제출 (${sessionRows.length}건)`;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -892,7 +887,7 @@ window.addManualWorkRow = function() {
|
||||
<input type="hidden" id="workplace_${manualIndex}">
|
||||
<div id="workplaceDisplay_${manualIndex}" class="workplace-select-box" style="display: flex; flex-direction: column; gap: 0.25rem; padding: 0.5rem; background: #f9fafb; border: 2px solid #e5e7eb; border-radius: 6px; min-height: 60px; cursor: pointer;" onclick="openWorkplaceMapForManual('${manualIndex}')">
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.75rem; color: #6b7280; font-weight: 500;">
|
||||
<span>🗺️</span>
|
||||
<span></span>
|
||||
<span>작업장소</span>
|
||||
</div>
|
||||
<div id="workplaceText_${manualIndex}" style="font-size: 0.8rem; color: #9ca3af; font-style: italic;">
|
||||
@@ -923,7 +918,7 @@ window.addManualWorkRow = function() {
|
||||
제출
|
||||
</button>
|
||||
<button type="button" class="btn-delete-compact" onclick="removeManualWorkRow('${manualIndex}')" style="margin-left: 4px;">
|
||||
✕
|
||||
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
@@ -1049,13 +1044,13 @@ window.openWorkplaceMapForManual = async function(manualIndex) {
|
||||
const safeImage = escapeHtml(cat.layout_image || '');
|
||||
return `
|
||||
<button type="button" class="btn btn-secondary" style="width: 100%; text-align: left;" onclick='selectWorkplaceCategory(${safeId}, "${safeName.replace(/"/g, '"')}", "${safeImage.replace(/"/g, '"')}")'>
|
||||
<span style="margin-right: 0.5rem;">🏭</span>
|
||||
<span style="margin-right: 0.5rem;"></span>
|
||||
${safeName}
|
||||
</button>
|
||||
`;
|
||||
}).join('') + `
|
||||
<button type="button" class="btn btn-secondary" style="width: 100%; text-align: left; margin-top: 0.5rem; background-color: #f0f9ff; border-color: #0ea5e9;" onclick='selectExternalWorkplace()'>
|
||||
<span style="margin-right: 0.5rem;">🌐</span>
|
||||
<span style="margin-right: 0.5rem;"></span>
|
||||
외부 (외근/연차/휴무 등)
|
||||
</button>
|
||||
`;
|
||||
@@ -1107,7 +1102,7 @@ window.selectWorkplaceCategory = async function(categoryId, categoryName, layout
|
||||
const safeName = escapeHtml(wp.workplace_name);
|
||||
return `
|
||||
<button type="button" id="workplace-${safeId}" class="btn btn-secondary" style="width: 100%; text-align: left;" onclick='selectWorkplaceFromList(${safeId}, "${safeName.replace(/"/g, '"')}")'>
|
||||
<span style="margin-right: 0.5rem;">📍</span>
|
||||
<span style="margin-right: 0.5rem;"></span>
|
||||
${safeName}
|
||||
</button>
|
||||
`;
|
||||
@@ -1136,7 +1131,6 @@ async function loadWorkplaceMap(categoryId, layoutImagePath, workplaces) {
|
||||
? layoutImagePath
|
||||
: `${apiBaseUrl}${layoutImagePath}`;
|
||||
|
||||
console.log('🖼️ 이미지 로드 시도:', fullImageUrl);
|
||||
|
||||
// 지도 영역 데이터 로드
|
||||
const regionsResponse = await window.apiCall(`/workplaces/categories/${categoryId}/map-regions`);
|
||||
@@ -1164,11 +1158,10 @@ async function loadWorkplaceMap(categoryId, layoutImagePath, workplaces) {
|
||||
// 클릭 이벤트 리스너 추가
|
||||
mapCanvas.onclick = handleMapClick;
|
||||
|
||||
console.log(`✅ 작업장 지도 로드 완료: ${mapRegions.length}개 영역`);
|
||||
};
|
||||
|
||||
mapImage.onerror = function() {
|
||||
console.error('❌ 지도 이미지 로드 실패');
|
||||
console.error(' 지도 이미지 로드 실패');
|
||||
document.getElementById('layoutMapArea').style.display = 'none';
|
||||
showMessage('지도를 불러올 수 없어 리스트로 표시합니다.', 'warning');
|
||||
};
|
||||
@@ -1176,7 +1169,7 @@ async function loadWorkplaceMap(categoryId, layoutImagePath, workplaces) {
|
||||
mapImage.src = fullImageUrl;
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 작업장 지도 로드 오류:', error);
|
||||
console.error(' 작업장 지도 로드 오류:', error);
|
||||
document.getElementById('layoutMapArea').style.display = 'none';
|
||||
}
|
||||
}
|
||||
@@ -1301,12 +1294,12 @@ window.confirmWorkplaceSelection = function() {
|
||||
if (displayDiv) {
|
||||
displayDiv.innerHTML = `
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.75rem; color: #059669; font-weight: 600;">
|
||||
<span>✓</span>
|
||||
<span></span>
|
||||
<span>작업장소 선택됨</span>
|
||||
</div>
|
||||
<div style="font-size: 0.8rem; color: #111827; font-weight: 500;">
|
||||
<div style="color: #6b7280; font-size: 0.7rem; margin-bottom: 2px;">🏭 ${escapeHtml(selectedWorkplaceCategoryName)}</div>
|
||||
<div>📍 ${escapeHtml(selectedWorkplaceName)}</div>
|
||||
<div style="color: #6b7280; font-size: 0.7rem; margin-bottom: 2px;"> ${escapeHtml(selectedWorkplaceCategoryName)}</div>
|
||||
<div> ${escapeHtml(selectedWorkplaceName)}</div>
|
||||
</div>
|
||||
`;
|
||||
displayDiv.style.background = '#ecfdf5';
|
||||
@@ -1354,11 +1347,11 @@ window.selectExternalWorkplace = function() {
|
||||
if (displayDiv) {
|
||||
displayDiv.innerHTML = `
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.75rem; color: #0284c7; font-weight: 600;">
|
||||
<span>✓</span>
|
||||
<span></span>
|
||||
<span>외부 선택됨</span>
|
||||
</div>
|
||||
<div style="font-size: 0.8rem; color: #111827; font-weight: 500;">
|
||||
<div>🌐 ${escapeHtml(externalWorkplaceName)}</div>
|
||||
<div> ${escapeHtml(externalWorkplaceName)}</div>
|
||||
</div>
|
||||
`;
|
||||
displayDiv.style.background = '#f0f9ff';
|
||||
@@ -1778,10 +1771,10 @@ function renderCompletedReports(reports) {
|
||||
</div>
|
||||
<div class="report-actions" style="display: flex; gap: 0.5rem; margin-top: 0.75rem; padding-top: 0.75rem; border-top: 1px solid #e5e7eb;">
|
||||
<button type="button" class="btn btn-sm btn-secondary" onclick='openEditReportModal(${JSON.stringify(report).replace(/'/g, "'")})' style="flex: 1; padding: 0.4rem 0.6rem; font-size: 0.75rem;">
|
||||
✏️ 수정
|
||||
수정
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-danger" onclick="deleteWorkReport(${report.id})" style="flex: 1; padding: 0.4rem 0.6rem; font-size: 0.75rem; background: #fee2e2; color: #dc2626; border: 1px solid #fecaca;">
|
||||
🗑️ 삭제
|
||||
삭제
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1997,7 +1990,7 @@ function getCurrentUser() {
|
||||
}
|
||||
|
||||
try {
|
||||
const token = window.getSSOToken ? window.getSSOToken() : (localStorage.getItem('sso_token') || localStorage.getItem('token'));
|
||||
const token = window.getSSOToken ? window.getSSOToken() : (localStorage.getItem('sso_token'));
|
||||
if (token) {
|
||||
const payloadBase64 = token.split('.')[1];
|
||||
if (payloadBase64) {
|
||||
@@ -2009,7 +2002,7 @@ function getCurrentUser() {
|
||||
}
|
||||
|
||||
try {
|
||||
const userInfo = localStorage.getItem('sso_user') || localStorage.getItem('user') || localStorage.getItem('userInfo');
|
||||
const userInfo = localStorage.getItem('sso_user');
|
||||
if (userInfo) return JSON.parse(userInfo);
|
||||
} catch (error) {
|
||||
console.log('localStorage에서 사용자 정보 가져오기 실패:', error);
|
||||
@@ -2044,16 +2037,16 @@ function showSaveResultModal(type, title, message, details = null) {
|
||||
let icon = '';
|
||||
switch (type) {
|
||||
case 'success':
|
||||
icon = '✅';
|
||||
icon = '';
|
||||
break;
|
||||
case 'error':
|
||||
icon = '❌';
|
||||
icon = '';
|
||||
break;
|
||||
case 'warning':
|
||||
icon = '⚠️';
|
||||
icon = '';
|
||||
break;
|
||||
default:
|
||||
icon = 'ℹ️';
|
||||
icon = 'ℹ';
|
||||
}
|
||||
|
||||
// 모달 내용 구성
|
||||
@@ -2157,7 +2150,6 @@ async function loadData() {
|
||||
try {
|
||||
showMessage('데이터를 불러오는 중...', 'loading');
|
||||
|
||||
console.log('🔗 통합 API 설정을 사용한 기본 데이터 로딩 시작...');
|
||||
await loadWorkers();
|
||||
await loadProjects();
|
||||
await loadWorkTypes();
|
||||
@@ -2190,8 +2182,6 @@ async function loadWorkers() {
|
||||
return notResigned;
|
||||
});
|
||||
|
||||
console.log(`✅ Workers 로드 성공: ${workers.length}명 (전체: ${allWorkers.length}명)`);
|
||||
console.log(`📊 필터링 조건: employment_status≠resigned (퇴사자만 제외)`);
|
||||
} catch (error) {
|
||||
console.error('작업자 로딩 오류:', error);
|
||||
throw error;
|
||||
@@ -2203,7 +2193,6 @@ async function loadProjects() {
|
||||
console.log('Projects API 호출 중... (활성 프로젝트만)');
|
||||
const data = await window.apiCall(`/projects/active/list`);
|
||||
projects = Array.isArray(data) ? data : (data.data || data.projects || []);
|
||||
console.log('✅ 활성 프로젝트 로드 성공:', projects.length);
|
||||
} catch (error) {
|
||||
console.error('프로젝트 로딩 오류:', error);
|
||||
throw error;
|
||||
@@ -2216,12 +2205,10 @@ async function loadWorkTypes() {
|
||||
const data = response.data || response;
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
workTypes = data;
|
||||
console.log('✅ 작업 유형 API 사용 (통합 설정):', workTypes.length + '개');
|
||||
return;
|
||||
}
|
||||
throw new Error('API 실패');
|
||||
} catch (error) {
|
||||
console.log('⚠️ 작업 유형 API 사용 불가, 기본값 사용:', error.message);
|
||||
workTypes = [
|
||||
{ id: 1, name: 'Base' },
|
||||
{ id: 2, name: 'Vessel' },
|
||||
@@ -2236,12 +2223,10 @@ async function loadWorkStatusTypes() {
|
||||
const data = response.data || response;
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
workStatusTypes = data;
|
||||
console.log('✅ 업무 상태 유형 API 사용 (통합 설정):', workStatusTypes.length + '개');
|
||||
return;
|
||||
}
|
||||
throw new Error('API 실패');
|
||||
} catch (error) {
|
||||
console.log('⚠️ 업무 상태 유형 API 사용 불가, 기본값 사용');
|
||||
workStatusTypes = [
|
||||
{ id: 1, name: '정규' },
|
||||
{ id: 2, name: '에러' }
|
||||
@@ -2266,7 +2251,6 @@ async function loadErrorTypes() {
|
||||
const catResponse = await window.apiCall('/work-issues/categories/type/nonconformity');
|
||||
if (catResponse && catResponse.success && Array.isArray(catResponse.data)) {
|
||||
issueCategories = catResponse.data;
|
||||
console.log(`✅ 부적합 카테고리 ${issueCategories.length}개 로드`);
|
||||
|
||||
// 모든 아이템 로드
|
||||
const itemResponse = await window.apiCall('/work-issues/items');
|
||||
@@ -2274,11 +2258,9 @@ async function loadErrorTypes() {
|
||||
// 부적합 카테고리의 아이템만 필터링
|
||||
const categoryIds = issueCategories.map(c => c.category_id);
|
||||
issueItems = itemResponse.data.filter(item => categoryIds.includes(item.category_id));
|
||||
console.log(`✅ 부적합 아이템 ${issueItems.length}개 로드`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('⚠️ 신고 카테고리 로드 실패:', error);
|
||||
issueCategories = [];
|
||||
issueItems = [];
|
||||
}
|
||||
@@ -2287,7 +2269,6 @@ async function loadErrorTypes() {
|
||||
// TBM 팀 구성 자동 불러오기
|
||||
async function loadTbmTeamForDate(date) {
|
||||
try {
|
||||
console.log('🛠️ TBM 팀 구성 조회 중:', date);
|
||||
const response = await window.apiCall(`/tbm/sessions/date/${date}`);
|
||||
|
||||
if (response && response.success && response.data && response.data.length > 0) {
|
||||
@@ -2300,16 +2281,14 @@ async function loadTbmTeamForDate(date) {
|
||||
const teamRes = await window.apiCall(`/tbm/sessions/${targetSession.session_id}/team`);
|
||||
if (teamRes && teamRes.success && teamRes.data) {
|
||||
const teamWorkerIds = teamRes.data.map(m => m.user_id);
|
||||
console.log(`✅ TBM 팀 구성 로드 성공: ${teamWorkerIds.length}명`);
|
||||
return teamWorkerIds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('ℹ️ 해당 날짜의 TBM 팀 구성이 없습니다.');
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.error('❌ TBM 팀 구성 조회 오류:', error);
|
||||
console.error(' TBM 팀 구성 조회 오류:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -2340,7 +2319,7 @@ async function populateWorkerGrid() {
|
||||
font-size: 0.875rem;
|
||||
`;
|
||||
infoDiv.innerHTML = `
|
||||
<strong>🛠️ TBM 팀 구성 자동 적용</strong><br>
|
||||
<strong> TBM 팀 구성 자동 적용</strong><br>
|
||||
오늘 TBM에서 구성된 팀원 ${tbmWorkerIds.length}명이 자동으로 선택되었습니다.
|
||||
`;
|
||||
grid.appendChild(infoDiv);
|
||||
@@ -2389,29 +2368,25 @@ function toggleWorkerSelection(workerId, btnElement) {
|
||||
|
||||
// 작업 항목 추가
|
||||
function addWorkEntry() {
|
||||
console.log('🔧 addWorkEntry 함수 호출됨');
|
||||
const container = document.getElementById('workEntriesList');
|
||||
console.log('🔧 컨테이너:', container);
|
||||
workEntryCounter++;
|
||||
console.log('🔧 작업 항목 카운터:', workEntryCounter);
|
||||
|
||||
const entryDiv = document.createElement('div');
|
||||
entryDiv.className = 'work-entry';
|
||||
entryDiv.dataset.id = workEntryCounter;
|
||||
console.log('🔧 생성된 작업 항목 div:', entryDiv);
|
||||
|
||||
entryDiv.innerHTML = `
|
||||
<div class="work-entry-header">
|
||||
<div class="work-entry-title">작업 항목 #${workEntryCounter}</div>
|
||||
<button type="button" class="remove-work-btn" onclick="event.stopPropagation(); removeWorkEntry(${workEntryCounter})" title="이 작업 삭제">
|
||||
🗑️ 삭제
|
||||
삭제
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="work-entry-grid">
|
||||
<div class="form-field-group">
|
||||
<div class="form-field-label">
|
||||
<span class="form-field-icon">🏗️</span>
|
||||
<span class="form-field-icon"></span>
|
||||
프로젝트
|
||||
</div>
|
||||
<select class="form-select project-select" required>
|
||||
@@ -2422,7 +2397,7 @@ function addWorkEntry() {
|
||||
|
||||
<div class="form-field-group">
|
||||
<div class="form-field-label">
|
||||
<span class="form-field-icon">⚙️</span>
|
||||
<span class="form-field-icon"></span>
|
||||
작업 유형
|
||||
</div>
|
||||
<select class="form-select work-type-select" required>
|
||||
@@ -2435,7 +2410,7 @@ function addWorkEntry() {
|
||||
<div class="work-entry-full">
|
||||
<div class="form-field-group">
|
||||
<div class="form-field-label">
|
||||
<span class="form-field-icon">📊</span>
|
||||
<span class="form-field-icon"></span>
|
||||
업무 상태
|
||||
</div>
|
||||
<select class="form-select work-status-select" required>
|
||||
@@ -2447,7 +2422,7 @@ function addWorkEntry() {
|
||||
|
||||
<div class="error-type-section work-entry-full">
|
||||
<div class="form-field-label">
|
||||
<span class="form-field-icon">⚠️</span>
|
||||
<span class="form-field-icon"></span>
|
||||
에러 유형
|
||||
</div>
|
||||
<select class="form-select error-type-select">
|
||||
@@ -2458,7 +2433,7 @@ function addWorkEntry() {
|
||||
|
||||
<div class="time-input-section work-entry-full">
|
||||
<div class="form-field-label">
|
||||
<span class="form-field-icon">⏰</span>
|
||||
<span class="form-field-icon"></span>
|
||||
작업 시간 (시간)
|
||||
</div>
|
||||
<input type="number" class="form-select time-input"
|
||||
@@ -2479,12 +2454,8 @@ function addWorkEntry() {
|
||||
`;
|
||||
|
||||
container.appendChild(entryDiv);
|
||||
console.log('🔧 작업 항목이 컨테이너에 추가됨');
|
||||
console.log('🔧 현재 컨테이너 내용:', container.innerHTML.length, '문자');
|
||||
console.log('🔧 현재 .work-entry 개수:', container.querySelectorAll('.work-entry').length);
|
||||
|
||||
setupWorkEntryEvents(entryDiv);
|
||||
console.log('🔧 이벤트 설정 완료');
|
||||
}
|
||||
|
||||
// 작업 항목 이벤트 설정
|
||||
@@ -2546,15 +2517,11 @@ function setupWorkEntryEvents(entryDiv) {
|
||||
|
||||
// 작업 항목 제거
|
||||
function removeWorkEntry(id) {
|
||||
console.log('🗑️ removeWorkEntry 호출됨, id:', id);
|
||||
const entry = document.querySelector(`.work-entry[data-id="${id}"]`);
|
||||
console.log('🗑️ 찾은 entry:', entry);
|
||||
if (entry) {
|
||||
entry.remove();
|
||||
updateTotalHours();
|
||||
console.log('✅ 작업 항목 삭제 완료');
|
||||
} else {
|
||||
console.log('❌ 작업 항목을 찾을 수 없음');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2573,7 +2540,7 @@ function updateTotalHours() {
|
||||
|
||||
if (total > 24) {
|
||||
display.style.background = 'linear-gradient(135deg, #e74c3c 0%, #c0392b 100%)';
|
||||
display.textContent += ' ⚠️ 24시간 초과';
|
||||
display.textContent += '24시간 초과';
|
||||
} else {
|
||||
display.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
|
||||
}
|
||||
@@ -2593,8 +2560,6 @@ async function saveWorkReport() {
|
||||
}
|
||||
|
||||
const entries = document.querySelectorAll('.work-entry');
|
||||
console.log('🔍 찾은 작업 항목들:', entries);
|
||||
console.log('🔍 작업 항목 개수:', entries.length);
|
||||
|
||||
if (entries.length === 0) {
|
||||
showSaveResultModal(
|
||||
@@ -2606,10 +2571,8 @@ async function saveWorkReport() {
|
||||
}
|
||||
|
||||
const newWorkEntries = [];
|
||||
console.log('🔍 작업 항목 수집 시작...');
|
||||
|
||||
for (const entry of entries) {
|
||||
console.log('🔍 작업 항목 처리 중:', entry);
|
||||
|
||||
const projectSelect = entry.querySelector('.project-select');
|
||||
const workTypeSelect = entry.querySelector('.work-type-select');
|
||||
@@ -2617,7 +2580,7 @@ async function saveWorkReport() {
|
||||
const errorTypeSelect = entry.querySelector('.error-type-select');
|
||||
const timeInput = entry.querySelector('.time-input');
|
||||
|
||||
console.log('🔍 선택된 요소들:', {
|
||||
console.log(' 선택된 요소들:', {
|
||||
projectSelect,
|
||||
workTypeSelect,
|
||||
workStatusSelect,
|
||||
@@ -2631,7 +2594,7 @@ async function saveWorkReport() {
|
||||
const errorTypeId = errorTypeSelect?.value;
|
||||
const workHours = timeInput?.value;
|
||||
|
||||
console.log('🔍 수집된 값들:', {
|
||||
console.log(' 수집된 값들:', {
|
||||
projectId,
|
||||
workTypeId,
|
||||
workStatusId,
|
||||
@@ -2665,8 +2628,7 @@ async function saveWorkReport() {
|
||||
work_hours: parseFloat(workHours)
|
||||
};
|
||||
|
||||
console.log('🔍 생성된 작업 항목:', workEntry);
|
||||
console.log('🔍 작업 항목 상세:', {
|
||||
console.log(' 작업 항목 상세:', {
|
||||
project_id: workEntry.project_id,
|
||||
work_type_id: workEntry.work_type_id,
|
||||
work_status_id: workEntry.work_status_id,
|
||||
@@ -2676,13 +2638,11 @@ async function saveWorkReport() {
|
||||
newWorkEntries.push(workEntry);
|
||||
}
|
||||
|
||||
console.log('🔍 최종 수집된 작업 항목들:', newWorkEntries);
|
||||
console.log('🔍 총 작업 항목 개수:', newWorkEntries.length);
|
||||
|
||||
try {
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = '💾 저장 중...';
|
||||
submitBtn.textContent = ' 저장 중...';
|
||||
|
||||
const currentUser = getCurrentUser();
|
||||
let totalSaved = 0;
|
||||
@@ -2706,18 +2666,13 @@ async function saveWorkReport() {
|
||||
created_by: currentUser?.user_id || currentUser?.id
|
||||
};
|
||||
|
||||
console.log('🔄 배열 형태로 전송:', requestData);
|
||||
console.log('🔄 work_entries:', requestData.work_entries);
|
||||
console.log('🔄 work_entries[0] 상세:', requestData.work_entries[0]);
|
||||
console.log('🔄 전송 데이터 JSON:', JSON.stringify(requestData, null, 2));
|
||||
|
||||
try {
|
||||
const result = await window.apiCall(`/daily-work-reports`, 'POST', requestData);
|
||||
|
||||
console.log('✅ 저장 성공:', result);
|
||||
totalSaved++;
|
||||
} catch (error) {
|
||||
console.error('❌ 저장 실패:', error);
|
||||
console.error(' 저장 실패:', error);
|
||||
totalFailed++;
|
||||
|
||||
failureDetails.push(`${workerName}: ${error.message}`);
|
||||
@@ -2765,7 +2720,7 @@ async function saveWorkReport() {
|
||||
} finally {
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = '💾 작업보고서 저장';
|
||||
submitBtn.textContent = ' 작업보고서 저장';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2801,7 +2756,7 @@ async function loadTodayWorkers() {
|
||||
const today = getKoreaToday();
|
||||
const currentUser = getCurrentUser();
|
||||
|
||||
content.innerHTML = '<div class="loading-spinner">📊 내가 입력한 오늘의 작업 현황을 불러오는 중... (통합 API)</div>';
|
||||
content.innerHTML = '<div class="loading-spinner"> 내가 입력한 오늘의 작업 현황을 불러오는 중... (통합 API)</div>';
|
||||
section.style.display = 'block';
|
||||
|
||||
// 본인이 입력한 데이터만 조회 (통합 API 사용)
|
||||
@@ -2812,10 +2767,8 @@ async function loadTodayWorkers() {
|
||||
queryParams += `&created_by=${currentUser.id}`;
|
||||
}
|
||||
|
||||
console.log(`🔒 본인 입력분만 조회 (통합 API): ${API}/daily-work-reports?${queryParams}`);
|
||||
|
||||
const rawData = await window.apiCall(`/daily-work-reports?${queryParams}`);
|
||||
console.log('📊 당일 작업 데이터 (통합 API):', rawData);
|
||||
|
||||
let data = [];
|
||||
if (Array.isArray(rawData)) {
|
||||
@@ -2830,7 +2783,7 @@ async function loadTodayWorkers() {
|
||||
console.error('당일 작업자 로드 오류:', error);
|
||||
content.innerHTML = `
|
||||
<div class="no-data-message">
|
||||
❌ 오늘의 작업 현황을 불러올 수 없습니다.<br>
|
||||
오늘의 작업 현황을 불러올 수 없습니다.<br>
|
||||
<small>${error.message}</small>
|
||||
</div>
|
||||
`;
|
||||
@@ -2844,7 +2797,7 @@ function displayMyDailyWorkers(data, date) {
|
||||
if (!Array.isArray(data) || data.length === 0) {
|
||||
content.innerHTML = `
|
||||
<div class="no-data-message">
|
||||
📝 내가 오늘(${date}) 입력한 작업이 없습니다.<br>
|
||||
내가 오늘(${date}) 입력한 작업이 없습니다.<br>
|
||||
<small>새로운 작업을 추가해보세요!</small>
|
||||
</div>
|
||||
`;
|
||||
@@ -2866,9 +2819,9 @@ function displayMyDailyWorkers(data, date) {
|
||||
|
||||
const headerHtml = `
|
||||
<div class="daily-workers-header">
|
||||
<h4>📊 내가 입력한 오늘(${escapeHtml(date)}) 작업 현황 - 총 ${parseInt(totalWorkers) || 0}명, ${parseInt(totalWorks) || 0}개 작업</h4>
|
||||
<h4> 내가 입력한 오늘(${escapeHtml(date)}) 작업 현황 - 총 ${parseInt(totalWorkers) || 0}명, ${parseInt(totalWorks) || 0}개 작업</h4>
|
||||
<button class="refresh-btn" onclick="refreshTodayWorkers()">
|
||||
🔄 새로고침
|
||||
새로고침
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
@@ -2891,34 +2844,34 @@ function displayMyDailyWorkers(data, date) {
|
||||
<div class="individual-work-item">
|
||||
<div class="work-details-grid">
|
||||
<div class="detail-item">
|
||||
<div class="detail-label">🏗️ 프로젝트</div>
|
||||
<div class="detail-label"> 프로젝트</div>
|
||||
<div class="detail-value">${projectName}</div>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<div class="detail-label">⚙️ 작업종류</div>
|
||||
<div class="detail-label"> 작업종류</div>
|
||||
<div class="detail-value">${workTypeName}</div>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<div class="detail-label">📊 작업상태</div>
|
||||
<div class="detail-label"> 작업상태</div>
|
||||
<div class="detail-value">${workStatusName}</div>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<div class="detail-label">⏰ 작업시간</div>
|
||||
<div class="detail-label"> 작업시간</div>
|
||||
<div class="detail-value">${workHours}시간</div>
|
||||
</div>
|
||||
${errorTypeName ? `
|
||||
<div class="detail-item">
|
||||
<div class="detail-label">❌ 에러유형</div>
|
||||
<div class="detail-label"> 에러유형</div>
|
||||
<div class="detail-value">${errorTypeName}</div>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<button class="edit-btn" onclick="editWorkItem('${workId}')">
|
||||
✏️ 수정
|
||||
수정
|
||||
</button>
|
||||
<button class="delete-btn" onclick="deleteWorkItem('${workId}')">
|
||||
🗑️ 삭제
|
||||
삭제
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2928,7 +2881,7 @@ function displayMyDailyWorkers(data, date) {
|
||||
return `
|
||||
<div class="worker-status-item">
|
||||
<div class="worker-header">
|
||||
<div class="worker-name">👤 ${escapeHtml(workerName)}</div>
|
||||
<div class="worker-name"> ${escapeHtml(workerName)}</div>
|
||||
<div class="worker-total-hours">총 ${parseFloat(totalHours)}시간</div>
|
||||
</div>
|
||||
<div class="individual-works-container">
|
||||
@@ -2970,12 +2923,12 @@ function showEditModal(workData) {
|
||||
<div class="edit-modal" id="editModal">
|
||||
<div class="edit-modal-content">
|
||||
<div class="edit-modal-header">
|
||||
<h3>✏️ 작업 수정</h3>
|
||||
<h3> 작업 수정</h3>
|
||||
<button class="close-modal-btn" onclick="closeEditModal()">×</button>
|
||||
</div>
|
||||
<div class="edit-modal-body">
|
||||
<div class="edit-form-group">
|
||||
<label>🏗️ 프로젝트</label>
|
||||
<label> 프로젝트</label>
|
||||
<select class="edit-select" id="editProject">
|
||||
<option value="">프로젝트 선택</option>
|
||||
${projects.map(p => `
|
||||
@@ -2987,7 +2940,7 @@ function showEditModal(workData) {
|
||||
</div>
|
||||
|
||||
<div class="edit-form-group">
|
||||
<label>⚙️ 작업 유형</label>
|
||||
<label> 작업 유형</label>
|
||||
<select class="edit-select" id="editWorkType">
|
||||
<option value="">작업 유형 선택</option>
|
||||
${workTypes.map(wt => `
|
||||
@@ -2999,7 +2952,7 @@ function showEditModal(workData) {
|
||||
</div>
|
||||
|
||||
<div class="edit-form-group">
|
||||
<label>📊 업무 상태</label>
|
||||
<label> 업무 상태</label>
|
||||
<select class="edit-select" id="editWorkStatus">
|
||||
<option value="">업무 상태 선택</option>
|
||||
${workStatusTypes.map(ws => `
|
||||
@@ -3011,7 +2964,7 @@ function showEditModal(workData) {
|
||||
</div>
|
||||
|
||||
<div class="edit-form-group" id="editErrorTypeGroup" style="${workData.work_status_id == 2 ? '' : 'display: none;'}">
|
||||
<label>❌ 에러 유형</label>
|
||||
<label> 에러 유형</label>
|
||||
<select class="edit-select" id="editErrorType">
|
||||
<option value="">에러 유형 선택</option>
|
||||
${errorTypes.map(et => `
|
||||
@@ -3023,7 +2976,7 @@ function showEditModal(workData) {
|
||||
</div>
|
||||
|
||||
<div class="edit-form-group">
|
||||
<label>⏰ 작업 시간</label>
|
||||
<label> 작업 시간</label>
|
||||
<input type="number" class="edit-input" id="editWorkHours"
|
||||
value="${workData.work_hours}"
|
||||
min="0" max="24" step="0.5">
|
||||
@@ -3031,7 +2984,7 @@ function showEditModal(workData) {
|
||||
</div>
|
||||
<div class="edit-modal-footer">
|
||||
<button class="btn btn-secondary" onclick="closeEditModal()">취소</button>
|
||||
<button class="btn btn-success" onclick="saveEditedWork()">💾 저장</button>
|
||||
<button class="btn btn-success" onclick="saveEditedWork()"> 저장</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -3093,14 +3046,13 @@ async function saveEditedWork() {
|
||||
body: JSON.stringify(updateData)
|
||||
});
|
||||
|
||||
console.log('✅ 수정 성공 (통합 API):', result);
|
||||
showMessage('✅ 작업이 성공적으로 수정되었습니다!', 'success');
|
||||
showMessage(' 작업이 성공적으로 수정되었습니다!', 'success');
|
||||
|
||||
closeEditModal();
|
||||
refreshTodayWorkers();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 수정 실패:', error);
|
||||
console.error(' 수정 실패:', error);
|
||||
showMessage('수정 중 오류가 발생했습니다: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
@@ -3121,14 +3073,13 @@ async function deleteWorkItem(workId) {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
console.log('✅ 삭제 성공 (통합 API):', result);
|
||||
showMessage('✅ 작업이 성공적으로 삭제되었습니다!', 'success');
|
||||
showMessage(' 작업이 성공적으로 삭제되었습니다!', 'success');
|
||||
|
||||
// 화면 새로고침
|
||||
refreshTodayWorkers();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 삭제 실패:', error);
|
||||
console.error(' 삭제 실패:', error);
|
||||
showMessage('삭제 중 오류가 발생했습니다: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
@@ -3164,7 +3115,6 @@ async function init() {
|
||||
// TBM 작업 목록 로드 (기본 탭)
|
||||
await loadIncompleteTbms();
|
||||
|
||||
console.log('✅ 시스템 초기화 완료 (통합 API 설정 적용)');
|
||||
|
||||
} catch (error) {
|
||||
console.error('초기화 오류:', error);
|
||||
@@ -3422,7 +3372,6 @@ function renderInlineDefectList(index) {
|
||||
|
||||
const defects = tempDefects[index] || [];
|
||||
|
||||
console.log(`📝 [renderInlineDefectList] index=${index}, 부적합 수=${defects.length}`, defects);
|
||||
|
||||
// 이슈가 있으면 이슈 선택 UI, 없으면 레거시 UI
|
||||
if (nonconformityIssues.length > 0) {
|
||||
@@ -3430,7 +3379,7 @@ function renderInlineDefectList(index) {
|
||||
let html = `
|
||||
<div class="defect-issue-section">
|
||||
<div class="defect-issue-header">
|
||||
<span class="defect-issue-title">📋 ${escapeHtml(workerWorkplaceName || '작업장소')} 관련 부적합</span>
|
||||
<span class="defect-issue-title"> ${escapeHtml(workerWorkplaceName || '작업장소')} 관련 부적합</span>
|
||||
<span class="defect-issue-count">${parseInt(nonconformityIssues.length) || 0}건</span>
|
||||
</div>
|
||||
<div class="defect-issue-list">
|
||||
|
||||
@@ -68,7 +68,7 @@ class DailyWorkReportState extends BaseState {
|
||||
}
|
||||
|
||||
try {
|
||||
const userInfo = localStorage.getItem('sso_user') || localStorage.getItem('userInfo') || localStorage.getItem('currentUser');
|
||||
const userInfo = localStorage.getItem('sso_user');
|
||||
if (userInfo) {
|
||||
return JSON.parse(userInfo);
|
||||
}
|
||||
|
||||
@@ -327,16 +327,13 @@ async function openEquipmentModal(equipmentId = null) {
|
||||
// 다음 관리번호 로드
|
||||
async function loadNextEquipmentCode() {
|
||||
try {
|
||||
console.log('📋 다음 관리번호 조회 중...');
|
||||
const response = await axios.get('/equipments/next-code');
|
||||
console.log('📋 다음 관리번호 응답:', response.data);
|
||||
if (response.data.success) {
|
||||
document.getElementById('equipmentCode').value = response.data.data.next_code;
|
||||
console.log('✅ 다음 관리번호 설정:', response.data.data.next_code);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 다음 관리번호 조회 실패:', error);
|
||||
console.error('❌ 에러 상세:', error.response?.data || error.message);
|
||||
console.error(' 다음 관리번호 조회 실패:', error);
|
||||
console.error(' 에러 상세:', error.response?.data || error.message);
|
||||
// 오류 시 기본값으로 빈 값 유지 (사용자가 직접 입력)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// 그룹장 전용 대시보드 - 실시간 근태 및 작업 현황 (Real Data Version)
|
||||
import { apiCall } from './api-config.js';
|
||||
|
||||
console.log('📊 그룹장 대시보드 스크립트 로딩 (Live Data)');
|
||||
|
||||
// 상태별 스타일/텍스트 매핑
|
||||
const STATUS_MAP = {
|
||||
|
||||
@@ -92,11 +92,10 @@ async function initializeSections() {
|
||||
// 5. 모든 수정이 완료된 HTML을 실제 DOM에 한 번에 삽입
|
||||
mainContainer.innerHTML = doc.body.innerHTML;
|
||||
|
||||
console.log(`✅ ${currentUser.role} 역할의 섹션 로딩 완료.`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('섹션 로딩 중 오류 발생:', error);
|
||||
mainContainer.innerHTML = `<div class="error-state">콘텐츠 로딩에 실패했습니다: ${error.message}</div>`;
|
||||
mainContainer.textContent = '콘텐츠 로딩에 실패했습니다. 페이지를 새로고침해 주세요.';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,22 +39,18 @@ function getCurrentUser() {
|
||||
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('sso_user') || localStorage.getItem('userInfo') || localStorage.getItem('currentUser');
|
||||
const userInfo = localStorage.getItem('sso_user');
|
||||
if (userInfo) {
|
||||
const parsed = JSON.parse(userInfo);
|
||||
console.log('localStorage에서 가져온 사용자 정보:', parsed);
|
||||
return parsed;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('localStorage에서 사용자 정보 가져오기 실패:', error);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -134,7 +130,6 @@ async function loadWorkers() {
|
||||
return worker.status === 'active' || worker.is_active === 1 || worker.is_active === true;
|
||||
});
|
||||
|
||||
console.log(`✅ 작업자 로드 성공: ${workers.length}명 (전체: ${allWorkers.length}명)`);
|
||||
} catch (error) {
|
||||
console.error('작업자 로딩 오류:', error);
|
||||
throw error;
|
||||
@@ -148,32 +143,27 @@ async function loadWorkData(date) {
|
||||
|
||||
// 1차: view_all=true로 전체 데이터 시도
|
||||
let queryParams = `date=${date}&view_all=true`;
|
||||
console.log(`🔍 1차 시도: ${API}/daily-work-reports?${queryParams}`);
|
||||
|
||||
let data = await apiCall(`${API}/daily-work-reports?${queryParams}`);
|
||||
workData = Array.isArray(data) ? data : (data.data || []);
|
||||
|
||||
// 데이터가 없으면 다른 방법들 시도
|
||||
if (workData.length === 0) {
|
||||
console.log('⚠️ view_all로 데이터 없음, 다른 방법 시도...');
|
||||
|
||||
// 2차: admin=true로 시도
|
||||
queryParams = `date=${date}&admin=true`;
|
||||
console.log(`🔍 2차 시도: ${API}/daily-work-reports?${queryParams}`);
|
||||
|
||||
data = await apiCall(`${API}/daily-work-reports?${queryParams}`);
|
||||
workData = Array.isArray(data) ? data : (data.data || []);
|
||||
|
||||
if (workData.length === 0) {
|
||||
// 3차: 날짜 경로 파라미터로 시도
|
||||
console.log(`🔍 3차 시도: ${API}/daily-work-reports/date/${date}`);
|
||||
|
||||
data = await apiCall(`${API}/daily-work-reports/date/${date}`);
|
||||
workData = Array.isArray(data) ? data : (data.data || []);
|
||||
|
||||
if (workData.length === 0) {
|
||||
// 4차: 기본 파라미터만으로 시도
|
||||
console.log(`🔍 4차 시도: ${API}/daily-work-reports?date=${date}`);
|
||||
|
||||
data = await apiCall(`${API}/daily-work-reports?date=${date}`);
|
||||
workData = Array.isArray(data) ? data : (data.data || []);
|
||||
@@ -181,15 +171,11 @@ async function loadWorkData(date) {
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`✅ 최종 작업 데이터 로드 결과: ${workData.length}개`);
|
||||
|
||||
// 디버깅을 위한 상세 로그
|
||||
if (workData.length > 0) {
|
||||
console.log('📊 로드된 데이터 샘플:', workData.slice(0, 3));
|
||||
const uniqueWorkers = [...new Set(workData.map(w => w.worker_name))];
|
||||
console.log('👥 데이터에 포함된 작업자들:', uniqueWorkers);
|
||||
} else {
|
||||
console.log('❌ 해당 날짜에 작업 데이터가 없거나 접근 권한이 없습니다.');
|
||||
}
|
||||
|
||||
return workData;
|
||||
@@ -201,10 +187,8 @@ async function loadWorkData(date) {
|
||||
|
||||
// 구체적인 에러 정보 표시
|
||||
if (error.message.includes('403')) {
|
||||
console.log('🔒 권한 부족으로 인한 접근 제한');
|
||||
throw new Error('해당 날짜의 데이터에 접근할 권한이 없습니다.');
|
||||
} else if (error.message.includes('404')) {
|
||||
console.log('📭 해당 날짜에 데이터 없음');
|
||||
throw new Error('해당 날짜에 입력된 작업 데이터가 없습니다.');
|
||||
} else {
|
||||
throw error;
|
||||
@@ -345,7 +329,6 @@ function displayDashboard(data) {
|
||||
filteredWorkData = data.workers;
|
||||
setupFiltering();
|
||||
|
||||
console.log('✅ 대시보드 표시 완료');
|
||||
}
|
||||
|
||||
// 요약 섹션 표시
|
||||
@@ -773,7 +756,6 @@ async function saveEditedWork(workId) {
|
||||
body: JSON.stringify(updateData)
|
||||
});
|
||||
|
||||
console.log('✅ 수정 성공 (통합 API):', result);
|
||||
showMessage('✅ 작업이 성공적으로 수정되었습니다!', 'success');
|
||||
|
||||
closeEditModal();
|
||||
@@ -783,7 +765,7 @@ async function saveEditedWork(workId) {
|
||||
await loadDashboardData();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 수정 실패:', error);
|
||||
console.error(' 수정 실패:', error);
|
||||
showMessage('수정 중 오류가 발생했습니다: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
@@ -804,7 +786,6 @@ async function deleteWorkItem(workId) {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
console.log('✅ 삭제 성공 (통합 API):', result);
|
||||
showMessage('✅ 작업이 성공적으로 삭제되었습니다!', 'success');
|
||||
|
||||
closeWorkerDetailModal();
|
||||
@@ -813,7 +794,7 @@ async function deleteWorkItem(workId) {
|
||||
await loadDashboardData();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 삭제 실패:', error);
|
||||
console.error(' 삭제 실패:', error);
|
||||
showMessage('삭제 중 오류가 발생했습니다: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
@@ -918,7 +899,6 @@ async function init() {
|
||||
// 이벤트 리스너 설정
|
||||
setupEventListeners();
|
||||
|
||||
console.log('✅ 관리자 대시보드 초기화 완료 (통합 API 설정 적용)');
|
||||
|
||||
// 자동으로 오늘 데이터 로드
|
||||
loadDashboardData();
|
||||
|
||||
@@ -62,7 +62,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
}
|
||||
|
||||
if (!window.apiCall) {
|
||||
console.error('❌ API 함수를 로드할 수 없습니다.');
|
||||
console.error(' API 함수를 로드할 수 없습니다.');
|
||||
showToast('시스템을 초기화할 수 없습니다. 페이지를 새로고침해주세요.', 'error');
|
||||
return;
|
||||
}
|
||||
@@ -76,7 +76,6 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
});
|
||||
|
||||
async function initializeDashboard() {
|
||||
console.log('🚀 모던 대시보드 초기화 시작');
|
||||
|
||||
// 사용자 정보 설정
|
||||
setupUserInfo();
|
||||
@@ -104,7 +103,6 @@ async function initializeDashboard() {
|
||||
// TBM 페이지 접근 권한 확인
|
||||
checkTbmPageAccess();
|
||||
|
||||
console.log('✅ 모던 대시보드 초기화 완료');
|
||||
}
|
||||
|
||||
// ========== 사용자 정보 설정 ========== //
|
||||
@@ -113,7 +111,6 @@ function setupUserInfo() {
|
||||
const authData = getAuthData();
|
||||
if (authData && authData.user) {
|
||||
currentUser = authData.user;
|
||||
console.log('👤 사용자 정보 로드 완료:', currentUser.name, currentUser.role);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +176,6 @@ function setupEventListeners() {
|
||||
|
||||
// ========== 데이터 로드 ========== //
|
||||
async function loadDashboardData() {
|
||||
console.log('📊 대시보드 데이터 로딩 시작');
|
||||
|
||||
try {
|
||||
// 로딩 상태 표시
|
||||
@@ -200,10 +196,9 @@ async function loadDashboardData() {
|
||||
// 작업자 현황 표시
|
||||
displayWorkers(workersData, 'card');
|
||||
|
||||
console.log('✅ 대시보드 데이터 로딩 완료');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 대시보드 데이터 로딩 오류:', error);
|
||||
console.error(' 대시보드 데이터 로딩 오류:', error);
|
||||
showErrorState();
|
||||
showToast('데이터를 불러오는 중 오류가 발생했습니다.', 'error');
|
||||
}
|
||||
@@ -211,7 +206,6 @@ async function loadDashboardData() {
|
||||
|
||||
async function loadWorkers() {
|
||||
try {
|
||||
console.log('👥 작업자 데이터 로딩...');
|
||||
const response = await window.apiCall('/workers');
|
||||
const allWorkers = Array.isArray(response) ? response : (response.data || []);
|
||||
|
||||
@@ -220,7 +214,6 @@ async function loadWorkers() {
|
||||
return worker.status === 'active' || worker.is_active === 1 || worker.is_active === true;
|
||||
});
|
||||
|
||||
console.log(`✅ 작업자 ${workersData.length}명 로드 완료 (전체: ${allWorkers.length}명)`);
|
||||
return workersData;
|
||||
} catch (error) {
|
||||
console.error('작업자 데이터 로딩 오류:', error);
|
||||
@@ -231,10 +224,8 @@ async function loadWorkers() {
|
||||
|
||||
async function loadWorkData(date) {
|
||||
try {
|
||||
console.log(`📋 ${date} 작업 데이터 로딩...`);
|
||||
const response = await window.apiCall(`/daily-work-reports?date=${date}&view_all=true`);
|
||||
workData = Array.isArray(response) ? response : (response.data || []);
|
||||
console.log(`✅ 작업 데이터 ${workData.length}건 로드 완료`);
|
||||
return workData;
|
||||
} catch (error) {
|
||||
console.error('작업 데이터 로딩 오류:', error);
|
||||
@@ -683,7 +674,6 @@ function checkAdminAccess() {
|
||||
const isFullAdmin = currentUser && ['admin', 'system'].includes(currentUser.access_level);
|
||||
const isGroupLeader = currentUser && currentUser.access_level === 'group_leader';
|
||||
|
||||
console.log(`🔐 권한 확인: 사용자=${currentUser?.username}, 역할=${currentUser.access_level}, 전체관리자=${isFullAdmin}, 그룹리더=${isGroupLeader}`);
|
||||
|
||||
adminElements.forEach(element => {
|
||||
const href = element.getAttribute('href');
|
||||
@@ -725,22 +715,18 @@ function checkAdminAccess() {
|
||||
async function checkTbmPageAccess() {
|
||||
try {
|
||||
if (!currentUser || !currentUser.user_id) {
|
||||
console.log('⚠️ TBM 페이지 권한 확인: 사용자 정보 없음');
|
||||
return;
|
||||
}
|
||||
|
||||
const tbmQuickAction = document.getElementById('tbmQuickAction');
|
||||
if (!tbmQuickAction) {
|
||||
console.log('⚠️ TBM 빠른 작업 버튼 요소를 찾을 수 없습니다');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('🛠️ TBM 페이지 권한 확인 중...', { role: currentUser.role, access_level: currentUser.access_level });
|
||||
|
||||
// Admin은 모든 페이지 접근 가능
|
||||
if (currentUser.role === 'Admin' || currentUser.role === 'System Admin' || currentUser.access_level === 'admin' || currentUser.access_level === 'system') {
|
||||
tbmQuickAction.style.display = 'block';
|
||||
console.log('✅ Admin 사용자 - TBM 빠른 작업 버튼 표시');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -755,15 +741,12 @@ async function checkTbmPageAccess() {
|
||||
|
||||
if (tbmPage && tbmPage.can_access) {
|
||||
tbmQuickAction.style.display = 'block';
|
||||
console.log('✅ TBM 페이지 접근 권한 있음 - 빠른 작업 버튼 표시');
|
||||
} else {
|
||||
console.log('❌ TBM 페이지 접근 권한 없음 - 빠른 작업 버튼 숨김');
|
||||
}
|
||||
} else {
|
||||
console.log('⚠️ TBM 페이지 권한 확인 실패');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ TBM 페이지 권한 확인 오류:', error);
|
||||
console.error(' TBM 페이지 권한 확인 오류:', error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -811,7 +794,6 @@ function showErrorState() {
|
||||
|
||||
// ========== 작업자 관련 액션 함수들 ========== //
|
||||
function openWorkerModal(workerId, workerName) {
|
||||
console.log(`📝 ${workerName}(ID: ${workerId}) 작업 보고서 모달 열기`);
|
||||
|
||||
// 모달 데이터 설정
|
||||
currentModalWorker = {
|
||||
@@ -825,7 +807,6 @@ function openWorkerModal(workerId, workerName) {
|
||||
}
|
||||
|
||||
function handleVacation(workerId, vacationType) {
|
||||
console.log(`🏖️ 작업자 ${workerId} 휴가 처리: ${vacationType}`);
|
||||
|
||||
const vacationNames = {
|
||||
'full': '연차',
|
||||
@@ -873,7 +854,6 @@ async function processVacation(workerId, vacationType, hours) {
|
||||
}
|
||||
|
||||
function confirmOvertime(workerId) {
|
||||
console.log(`⚠️ 작업자 ${workerId} 초과근무 확인`);
|
||||
|
||||
if (confirm('12시간을 초과한 작업시간이 정상적인 입력인지 확인하시겠습니까?')) {
|
||||
// 초과근무 확인 처리
|
||||
@@ -1269,8 +1249,6 @@ async function saveModalNewWork() {
|
||||
}]
|
||||
};
|
||||
|
||||
console.log('📤 전송할 작업 데이터:', workData);
|
||||
console.log('📋 현재 사용자:', currentUser);
|
||||
|
||||
await window.apiCall('/daily-work-reports', 'POST', workData);
|
||||
|
||||
|
||||
@@ -381,7 +381,8 @@ function showLoading() {
|
||||
|
||||
function showError(message) {
|
||||
const tbody = document.getElementById('attendanceTableBody');
|
||||
tbody.innerHTML = `<tr><td colspan="7" class="error-cell">${message}</td></tr>`;
|
||||
const safeMsg = (window.escapeHtml ? window.escapeHtml(message) : message.replace(/[&<>"']/g, m => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m])));
|
||||
tbody.innerHTML = `<tr><td colspan="7" class="error-cell">${safeMsg}</td></tr>`;
|
||||
|
||||
// 통계 초기화
|
||||
document.getElementById('totalHours').textContent = '-';
|
||||
|
||||
@@ -8,7 +8,6 @@ let currentMonth = new Date().getMonth() + 1;
|
||||
|
||||
// 페이지 초기화
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
console.log('📊 나의 대시보드 초기화 시작');
|
||||
|
||||
await loadUserInfo();
|
||||
await loadVacationBalance();
|
||||
@@ -16,7 +15,6 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
await loadWorkHoursStats();
|
||||
await loadRecentReports();
|
||||
|
||||
console.log('✅ 나의 대시보드 초기화 완료');
|
||||
});
|
||||
|
||||
// 사용자 정보 로드
|
||||
|
||||
@@ -117,6 +117,5 @@ function showError(message) {
|
||||
|
||||
// 페이지 로드 시 실행
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
console.log('👤 프로필 페이지 로드됨');
|
||||
loadProfile();
|
||||
});
|
||||
@@ -14,7 +14,6 @@ function redirect(url) {
|
||||
*/
|
||||
export function redirectToLogin() {
|
||||
const loginUrl = config.paths.loginPage + '?redirect=' + encodeURIComponent(window.location.href);
|
||||
console.log(`🔄 로그인 페이지로 이동합니다: ${loginUrl}`);
|
||||
redirect(loginUrl);
|
||||
}
|
||||
|
||||
@@ -25,7 +24,6 @@ export function redirectToLogin() {
|
||||
*/
|
||||
export function redirectToDefaultDashboard(backendRedirectUrl = null) {
|
||||
const destination = backendRedirectUrl || config.paths.defaultDashboard;
|
||||
console.log(`🔄 대시보드로 이동합니다: ${destination}`);
|
||||
|
||||
// 부드러운 화면 전환 효과
|
||||
document.body.style.transition = 'opacity 0.3s ease-out';
|
||||
@@ -40,7 +38,6 @@ export function redirectToDefaultDashboard(backendRedirectUrl = null) {
|
||||
* 시스템 대시보드 페이지로 리디렉션합니다.
|
||||
*/
|
||||
export function redirectToSystemDashboard() {
|
||||
console.log(`🔄 시스템 대시보드로 이동합니다: ${config.paths.systemDashboard}`);
|
||||
redirect(config.paths.systemDashboard);
|
||||
}
|
||||
|
||||
@@ -48,7 +45,6 @@ export function redirectToSystemDashboard() {
|
||||
* 그룹 리더 대시보드 페이지로 리디렉션합니다.
|
||||
*/
|
||||
export function redirectToGroupLeaderDashboard() {
|
||||
console.log(`🔄 그룹 리더 대시보드로 이동합니다: ${config.paths.groupLeaderDashboard}`);
|
||||
redirect(config.paths.groupLeaderDashboard);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ let currentStatusFilter = 'all'; // 'all', 'active', 'inactive'
|
||||
|
||||
// 페이지 초기화
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('📁 프로젝트 관리 페이지 초기화 시작');
|
||||
|
||||
initializePage();
|
||||
loadProjects();
|
||||
@@ -103,11 +102,9 @@ function setupSearchInput() {
|
||||
// 프로젝트 목록 로드
|
||||
async function loadProjects() {
|
||||
try {
|
||||
console.log('📊 프로젝트 목록 로딩 시작');
|
||||
|
||||
const response = await apiCall('/projects', 'GET');
|
||||
|
||||
console.log('📊 API 응답 구조:', response);
|
||||
|
||||
// API 응답이 { success: true, data: [...] } 형태인 경우 처리
|
||||
let projectData = [];
|
||||
@@ -122,7 +119,6 @@ async function loadProjects() {
|
||||
|
||||
allProjects = projectData;
|
||||
|
||||
console.log(`✅ 프로젝트 ${allProjects.length}개 로드 완료`);
|
||||
|
||||
// 초기 필터 적용
|
||||
applyAllFilters();
|
||||
@@ -156,10 +152,10 @@ function renderProjects() {
|
||||
const projectsHtml = filteredProjects.map(project => {
|
||||
// 프로젝트 상태 아이콘 및 텍스트
|
||||
const statusMap = {
|
||||
'planning': { icon: '📋', text: '계획', color: '#6b7280' },
|
||||
'active': { icon: '🚀', text: '진행중', color: '#10b981' },
|
||||
'completed': { icon: '✅', text: '완료', color: '#3b82f6' },
|
||||
'cancelled': { icon: '❌', text: '취소', color: '#ef4444' }
|
||||
'planning': { icon: '', text: '계획', color: '#6b7280' },
|
||||
'active': { icon: '', text: '진행중', color: '#10b981' },
|
||||
'completed': { icon: '', text: '완료', color: '#3b82f6' },
|
||||
'cancelled': { icon: '', text: '취소', color: '#ef4444' }
|
||||
};
|
||||
|
||||
const validStatuses = ['planning', 'active', 'completed', 'cancelled'];
|
||||
@@ -175,16 +171,9 @@ function renderProjects() {
|
||||
const safePm = escapeHtml(project.pm || '-');
|
||||
const safeSite = escapeHtml(project.site || '-');
|
||||
|
||||
console.log('🎨 카드 렌더링:', {
|
||||
project_id: project.project_id,
|
||||
project_name: project.project_name,
|
||||
is_active_raw: project.is_active,
|
||||
isInactive: isInactive
|
||||
});
|
||||
|
||||
return `
|
||||
<div class="project-card ${isInactive ? 'inactive' : ''}" onclick="editProject(${safeProjectId})">
|
||||
${isInactive ? '<div class="inactive-overlay"><span class="inactive-badge">🚫 비활성화됨</span></div>' : ''}
|
||||
${isInactive ? '<div class="inactive-overlay"><span class="inactive-badge"> 비활성화됨</span></div>' : ''}
|
||||
<div class="project-header">
|
||||
<div class="project-info">
|
||||
<div class="project-job-no">${safeJobNo}</div>
|
||||
@@ -213,15 +202,15 @@ function renderProjects() {
|
||||
<span class="meta-label">현장</span>
|
||||
<span class="meta-value">${safeSite}</span>
|
||||
</div>
|
||||
${isInactive ? '<div class="inactive-notice">⚠️ 작업보고서에서 숨김</div>' : ''}
|
||||
${isInactive ? '<div class="inactive-notice"> 작업보고서에서 숨김</div>' : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div class="project-actions">
|
||||
<button class="btn-edit" onclick="event.stopPropagation(); editProject(${safeProjectId})" title="수정">
|
||||
✏️ 수정
|
||||
수정
|
||||
</button>
|
||||
<button class="btn-delete" onclick="event.stopPropagation(); confirmDeleteProject(${safeProjectId})" title="삭제">
|
||||
🗑️ 삭제
|
||||
삭제
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -252,12 +241,6 @@ function updateProjectStats() {
|
||||
if (totalProjectsElement) {
|
||||
totalProjectsElement.textContent = filteredProjects.length;
|
||||
}
|
||||
|
||||
console.log('📊 프로젝트 통계:', {
|
||||
전체: filteredProjects.length,
|
||||
활성: activeProjects.length,
|
||||
비활성: inactiveProjects.length
|
||||
});
|
||||
}
|
||||
|
||||
// 날짜 포맷팅
|
||||
@@ -282,7 +265,6 @@ function filterByStatus(status) {
|
||||
// 필터링 적용
|
||||
applyAllFilters();
|
||||
|
||||
console.log(`🔍 상태 필터 적용: ${status}`);
|
||||
}
|
||||
|
||||
// 통계 카드 활성화 상태 업데이트
|
||||
@@ -371,7 +353,7 @@ async function refreshProjectList() {
|
||||
const refreshBtn = document.querySelector('.btn-secondary');
|
||||
if (refreshBtn) {
|
||||
const originalText = refreshBtn.innerHTML;
|
||||
refreshBtn.innerHTML = '<span class="btn-icon">⏳</span>새로고침 중...';
|
||||
refreshBtn.innerHTML = '<span class="btn-icon"></span>새로고침 중...';
|
||||
refreshBtn.disabled = true;
|
||||
|
||||
await loadProjects();
|
||||
@@ -415,12 +397,6 @@ function openProjectModal(project = null) {
|
||||
const isActiveValue = project.is_active === 1 || project.is_active === true || project.is_active === 'true';
|
||||
document.getElementById('isActive').checked = isActiveValue;
|
||||
|
||||
console.log('🔧 프로젝트 로드:', {
|
||||
project_id: project.project_id,
|
||||
project_name: project.project_name,
|
||||
is_active_raw: project.is_active,
|
||||
is_active_processed: isActiveValue
|
||||
});
|
||||
} else {
|
||||
// 신규 등록 모드
|
||||
modalTitle.textContent = '새 프로젝트 등록';
|
||||
@@ -480,7 +456,6 @@ async function saveProject() {
|
||||
is_active: document.getElementById('isActive').checked ? 1 : 0
|
||||
};
|
||||
|
||||
console.log('💾 저장할 프로젝트 데이터:', projectData);
|
||||
|
||||
// 필수 필드 검증
|
||||
if (!projectData.job_no || !projectData.project_name) {
|
||||
@@ -523,7 +498,7 @@ function confirmDeleteProject(projectId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (confirm(`"${project.project_name}" 프로젝트를 정말 삭제하시겠습니까?\n\n⚠️ 삭제된 프로젝트는 복구할 수 없습니다.`)) {
|
||||
if (confirm(`"${project.project_name}" 프로젝트를 정말 삭제하시겠습니까?\n\n 삭제된 프로젝트는 복구할 수 없습니다.`)) {
|
||||
deleteProjectById(projectId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,6 @@ const WEATHER_ICONS = {
|
||||
*/
|
||||
async function initPage() {
|
||||
try {
|
||||
console.log('📋 안전 체크리스트 관리 페이지 초기화...');
|
||||
|
||||
await Promise.all([
|
||||
loadAllChecks(),
|
||||
@@ -55,7 +54,6 @@ async function initPage() {
|
||||
]);
|
||||
|
||||
renderCurrentTab();
|
||||
console.log('✅ 초기화 완료. 체크항목:', allChecks.length, '개');
|
||||
} catch (error) {
|
||||
console.error('초기화 실패:', error);
|
||||
showToast('데이터를 불러오는데 실패했습니다.', 'error');
|
||||
@@ -73,7 +71,6 @@ async function loadAllChecks() {
|
||||
const response = await apiCall('/tbm/safety-checks');
|
||||
if (response && response.success) {
|
||||
allChecks = response.data || [];
|
||||
console.log('✅ 체크 항목 로드:', allChecks.length, '개');
|
||||
} else {
|
||||
console.warn('체크 항목 응답 실패:', response);
|
||||
allChecks = [];
|
||||
@@ -93,7 +90,6 @@ async function loadWeatherConditions() {
|
||||
if (response && response.success) {
|
||||
weatherConditions = response.data || [];
|
||||
populateWeatherSelects();
|
||||
console.log('✅ 날씨 조건 로드:', weatherConditions.length, '개');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('날씨 조건 로드 실패:', error);
|
||||
@@ -110,7 +106,6 @@ async function loadWorkTypes() {
|
||||
if (response && response.success) {
|
||||
workTypes = response.data || [];
|
||||
populateWorkTypeSelects();
|
||||
console.log('✅ 공정 목록 로드:', workTypes.length, '개');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('공정 목록 로드 실패:', error);
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
// System Dashboard JavaScript
|
||||
console.log('🚀 system-dashboard.js loaded');
|
||||
|
||||
import { apiRequest } from './api-helper.js';
|
||||
import { getCurrentUser } from './auth.js';
|
||||
|
||||
console.log('📦 modules imported successfully');
|
||||
|
||||
// 전역 변수
|
||||
let systemData = {
|
||||
@@ -15,7 +13,6 @@ let systemData = {
|
||||
|
||||
// Initialize on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('📄 DOM loaded, starting initialization');
|
||||
initializeSystemDashboard();
|
||||
setupEventListeners();
|
||||
});
|
||||
@@ -31,65 +28,51 @@ function setupEventListeners() {
|
||||
switch(action) {
|
||||
case 'account-management':
|
||||
button.addEventListener('click', openAccountManagement);
|
||||
console.log('✅ Account management button listener added');
|
||||
break;
|
||||
case 'system-logs':
|
||||
button.addEventListener('click', openSystemLogs);
|
||||
console.log('✅ System logs button listener added');
|
||||
break;
|
||||
case 'database-management':
|
||||
button.addEventListener('click', openDatabaseManagement);
|
||||
console.log('✅ Database management button listener added');
|
||||
break;
|
||||
case 'system-settings':
|
||||
button.addEventListener('click', openSystemSettings);
|
||||
console.log('✅ System settings button listener added');
|
||||
break;
|
||||
case 'backup-management':
|
||||
button.addEventListener('click', openBackupManagement);
|
||||
console.log('✅ Backup management button listener added');
|
||||
break;
|
||||
case 'monitoring':
|
||||
button.addEventListener('click', openMonitoring);
|
||||
console.log('✅ Monitoring button listener added');
|
||||
break;
|
||||
case 'close-modal':
|
||||
button.addEventListener('click', () => closeModal('account-modal'));
|
||||
console.log('✅ Modal close button listener added');
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`🎯 Total ${actionButtons.length} event listeners setup completed`);
|
||||
}
|
||||
|
||||
// Initialize system dashboard
|
||||
async function initializeSystemDashboard() {
|
||||
try {
|
||||
console.log('🚀 Starting system dashboard initialization...');
|
||||
|
||||
// Load user info
|
||||
await loadUserInfo();
|
||||
console.log('✅ User info loaded');
|
||||
|
||||
// Load system status
|
||||
await loadSystemStatus();
|
||||
console.log('✅ System status loaded');
|
||||
|
||||
// Load user statistics
|
||||
await loadUserStats();
|
||||
console.log('✅ User statistics loaded');
|
||||
|
||||
// Load recent activities
|
||||
await loadRecentActivities();
|
||||
console.log('✅ Recent activities loaded');
|
||||
|
||||
// Setup auto-refresh (every 30 seconds)
|
||||
setInterval(refreshSystemStatus, 30000);
|
||||
|
||||
console.log('🎉 System dashboard initialization completed');
|
||||
} catch (error) {
|
||||
console.error('❌ System dashboard initialization error:', error);
|
||||
console.error(' System dashboard initialization error:', error);
|
||||
showNotification('Error loading system dashboard', 'error');
|
||||
}
|
||||
}
|
||||
@@ -307,7 +290,6 @@ async function refreshSystemStatus() {
|
||||
|
||||
// Open account management
|
||||
function openAccountManagement() {
|
||||
console.log('🎯 Account management button clicked');
|
||||
const modal = document.getElementById('account-modal');
|
||||
const content = document.getElementById('account-management-content');
|
||||
|
||||
@@ -315,13 +297,11 @@ function openAccountManagement() {
|
||||
console.log('Content element:', content);
|
||||
|
||||
if (modal && content) {
|
||||
console.log('✅ Modal and content elements found, loading content...');
|
||||
// Load account management content
|
||||
loadAccountManagementContent(content);
|
||||
modal.style.display = 'block';
|
||||
console.log('✅ Modal displayed');
|
||||
} else {
|
||||
console.error('❌ Modal or content element not found');
|
||||
console.error(' Modal or content element not found');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -890,11 +870,9 @@ window.filterLogs = filterLogs;
|
||||
|
||||
// 테스트용 전역 함수
|
||||
window.testFunction = function() {
|
||||
console.log('🧪 테스트 함수 호출됨!');
|
||||
alert('테스트 함수가 정상적으로 작동합니다!');
|
||||
};
|
||||
|
||||
console.log('🌐 전역 함수들 노출 완료');
|
||||
|
||||
// 모달 외부 클릭 시 닫기
|
||||
window.onclick = function(event) {
|
||||
|
||||
@@ -8,7 +8,6 @@ let currentEditingTask = null;
|
||||
|
||||
// 페이지 초기화
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
console.log('📋 작업 관리 페이지 초기화');
|
||||
|
||||
// API 함수가 로드될 때까지 대기
|
||||
let retryCount = 0;
|
||||
@@ -33,7 +32,7 @@ async function loadAllData() {
|
||||
// 작업 목록 로드
|
||||
await loadTasks();
|
||||
} catch (error) {
|
||||
console.error('❌ 데이터 로드 오류:', error);
|
||||
console.error(' 데이터 로드 오류:', error);
|
||||
showToast('데이터를 불러오는 중 오류가 발생했습니다.', 'error');
|
||||
}
|
||||
}
|
||||
@@ -51,11 +50,10 @@ async function loadWorkTypes() {
|
||||
workTypes = [];
|
||||
}
|
||||
|
||||
console.log('✅ 공정 목록 로드:', workTypes.length + '개');
|
||||
renderWorkTypeTabs();
|
||||
populateWorkTypeSelect();
|
||||
} catch (error) {
|
||||
console.error('❌ 공정 목록 조회 오류:', error);
|
||||
console.error(' 공정 목록 조회 오류:', error);
|
||||
// API 오류 시에도 빈 배열로 처리
|
||||
workTypes = [];
|
||||
renderWorkTypeTabs();
|
||||
@@ -69,7 +67,6 @@ async function loadTasks() {
|
||||
|
||||
if (response && response.success) {
|
||||
tasks = response.data || [];
|
||||
console.log('✅ 작업 목록 로드:', tasks.length + '개');
|
||||
} else {
|
||||
tasks = [];
|
||||
}
|
||||
@@ -77,7 +74,7 @@ async function loadTasks() {
|
||||
renderTasks();
|
||||
updateStatistics();
|
||||
} catch (error) {
|
||||
console.error('❌ 작업 목록 조회 오류:', error);
|
||||
console.error(' 작업 목록 조회 오류:', error);
|
||||
showToast('작업 목록을 불러오는 중 오류가 발생했습니다.', 'error');
|
||||
tasks = [];
|
||||
renderTasks();
|
||||
@@ -257,7 +254,7 @@ async function editTask(taskId) {
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 작업 조회 오류:', error);
|
||||
console.error(' 작업 조회 오류:', error);
|
||||
showToast('작업 정보를 불러올 수 없습니다.', 'error');
|
||||
}
|
||||
}
|
||||
@@ -314,7 +311,7 @@ async function saveTask() {
|
||||
throw new Error(response.message || '저장에 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 작업 저장 오류:', error);
|
||||
console.error(' 작업 저장 오류:', error);
|
||||
showToast('작업 저장 중 오류가 발생했습니다.', 'error');
|
||||
}
|
||||
}
|
||||
@@ -339,7 +336,7 @@ async function deleteTask() {
|
||||
throw new Error(response.message || '삭제에 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 작업 삭제 오류:', error);
|
||||
console.error(' 작업 삭제 오류:', error);
|
||||
showToast('작업 삭제 중 오류가 발생했습니다.', 'error');
|
||||
}
|
||||
}
|
||||
@@ -399,7 +396,7 @@ async function editWorkType(workTypeId) {
|
||||
document.getElementById('workTypeModal').style.display = 'flex';
|
||||
document.body.style.overflow = 'hidden';
|
||||
} catch (error) {
|
||||
console.error('❌ 공정 조회 오류:', error);
|
||||
console.error(' 공정 조회 오류:', error);
|
||||
showToast('공정 정보를 불러올 수 없습니다.', 'error');
|
||||
}
|
||||
}
|
||||
@@ -445,7 +442,7 @@ async function saveWorkType() {
|
||||
throw new Error(response.message || '저장에 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 공정 저장 오류:', error);
|
||||
console.error(' 공정 저장 오류:', error);
|
||||
showToast('공정 저장 중 오류가 발생했습니다.', 'error');
|
||||
}
|
||||
}
|
||||
@@ -477,7 +474,7 @@ async function deleteWorkType() {
|
||||
throw new Error(response.message || '삭제에 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 공정 삭제 오류:', error);
|
||||
console.error(' 공정 삭제 오류:', error);
|
||||
showToast('공정 삭제 중 오류가 발생했습니다.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,6 @@ function formatDate(d) { return window.CommonUtils.formatDate(d); }
|
||||
|
||||
// 페이지 초기화
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
console.log('🛠️ TBM 관리 페이지 초기화');
|
||||
|
||||
// API 함수가 로드될 때까지 대기
|
||||
let retryCount = 0;
|
||||
@@ -542,7 +541,6 @@ function populateLeaderSelect() {
|
||||
const jobTypeText = worker.job_type ? ` (${escapeHtml(worker.job_type)})` : '';
|
||||
leaderSelect.innerHTML = `<option value="${escapeHtml(worker.user_id)}" selected>${escapeHtml(worker.worker_name)}${jobTypeText}</option>`;
|
||||
leaderSelect.disabled = true;
|
||||
console.log('✅ 입력자 자동 설정:', worker.worker_name);
|
||||
} else {
|
||||
// 작업자를 찾을 수 없는 경우
|
||||
leaderSelect.innerHTML = '<option value="">입력자를 찾을 수 없습니다</option>';
|
||||
@@ -560,7 +558,6 @@ function populateLeaderSelect() {
|
||||
return `<option value="${escapeHtml(w.user_id)}">${escapeHtml(w.worker_name)}${jobTypeText}</option>`;
|
||||
}).join('');
|
||||
leaderSelect.disabled = false;
|
||||
console.log('✅ 관리자: 입력자 선택 가능');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -637,16 +634,14 @@ window.closeTbmModal = closeTbmModal;
|
||||
|
||||
// TBM 세션 저장 (간소화: 프로젝트+공정+작업자, task/workplace=null)
|
||||
async function saveTbmSession() {
|
||||
console.log('💾 TBM 저장 시작...');
|
||||
|
||||
let leaderId = parseInt(document.getElementById('leaderId').value);
|
||||
|
||||
if (!leaderId || isNaN(leaderId)) {
|
||||
if (!currentUser.user_id) {
|
||||
console.log('📝 관리자 계정: leader_user_id를 NULL로 설정');
|
||||
leaderId = null;
|
||||
} else {
|
||||
console.error('❌ 입력자 설정 오류');
|
||||
console.error(' 입력자 설정 오류');
|
||||
showToast('입력자 정보가 올바르지 않습니다.', 'error');
|
||||
return;
|
||||
}
|
||||
@@ -704,7 +699,7 @@ async function saveTbmSession() {
|
||||
throw new Error(teamResponse.message || '팀원 수정에 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ TBM 세션 수정 오류:', error);
|
||||
console.error(' TBM 세션 수정 오류:', error);
|
||||
showToast('TBM 세션 수정 중 오류가 발생했습니다.', 'error');
|
||||
}
|
||||
return;
|
||||
@@ -745,7 +740,6 @@ async function saveTbmSession() {
|
||||
|
||||
if (response && response.success) {
|
||||
createdSessionId = response.data.session_id;
|
||||
console.log('✅ TBM 세션 생성 완료:', createdSessionId);
|
||||
|
||||
const teamResponse = await window.TbmAPI.addTeamMembers(createdSessionId, members);
|
||||
|
||||
@@ -765,7 +759,7 @@ async function saveTbmSession() {
|
||||
throw new Error(response.message || '저장에 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ TBM 세션 저장 오류:', error);
|
||||
console.error(' TBM 세션 저장 오류:', error);
|
||||
|
||||
// 409 중복 배정 에러 처리
|
||||
if (error.duplicates && error.duplicates.length > 0) {
|
||||
@@ -1540,7 +1534,7 @@ async function loadWorkplacesByCategory(categoryId) {
|
||||
</button>
|
||||
`).join('');
|
||||
} catch (error) {
|
||||
console.error('❌ 작업장 로드 오류:', error);
|
||||
console.error(' 작업장 로드 오류:', error);
|
||||
workplaceList.innerHTML = '<div style="color: #ef4444; text-align: center; padding: 2rem;">작업장을 불러오는 중 오류가 발생했습니다</div>';
|
||||
}
|
||||
}
|
||||
@@ -1641,7 +1635,6 @@ async function loadWorkplaceMap(categoryId, layoutImagePath) {
|
||||
? layoutImagePath
|
||||
: `${apiBaseUrl}${layoutImagePath}`;
|
||||
|
||||
console.log('🖼️ 이미지 로드 시도:', fullImageUrl);
|
||||
|
||||
// 지도 영역 데이터 로드
|
||||
mapRegions = await window.TbmAPI.loadMapRegions(categoryId);
|
||||
@@ -1666,11 +1659,10 @@ async function loadWorkplaceMap(categoryId, layoutImagePath) {
|
||||
// 클릭 이벤트 리스너 추가
|
||||
mapCanvas.onclick = handleMapClick;
|
||||
|
||||
console.log(`✅ 작업장 지도 로드 완료: ${mapRegions.length}개 영역`);
|
||||
};
|
||||
|
||||
mapImage.onerror = function() {
|
||||
console.error('❌ 지도 이미지 로드 실패');
|
||||
console.error(' 지도 이미지 로드 실패');
|
||||
document.getElementById('layoutMapArea').style.display = 'none';
|
||||
document.getElementById('workplaceListSection').style.display = 'block';
|
||||
document.getElementById('workplaceList').style.display = 'flex';
|
||||
@@ -1681,7 +1673,7 @@ async function loadWorkplaceMap(categoryId, layoutImagePath) {
|
||||
mapImage.src = fullImageUrl;
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 작업장 지도 로드 오류:', error);
|
||||
console.error(' 작업장 지도 로드 오류:', error);
|
||||
document.getElementById('layoutMapArea').style.display = 'none';
|
||||
document.getElementById('workplaceList').style.display = 'flex';
|
||||
}
|
||||
@@ -2030,7 +2022,7 @@ async function openTeamCompositionModal(sessionId) {
|
||||
lockBodyScroll();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 팀 구성 로드 오류:', error);
|
||||
console.error(' 팀 구성 로드 오류:', error);
|
||||
showToast('팀 구성을 불러오는 중 오류가 발생했습니다.', 'error');
|
||||
}
|
||||
}
|
||||
@@ -2130,7 +2122,7 @@ async function saveTeamComposition() {
|
||||
throw new Error(response.message || '저장에 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 팀 구성 저장 오류:', error);
|
||||
console.error(' 팀 구성 저장 오류:', error);
|
||||
showToast('팀 구성 저장 중 오류가 발생했습니다.', 'error');
|
||||
}
|
||||
}
|
||||
@@ -2222,7 +2214,7 @@ async function openSafetyCheckModal(sessionId) {
|
||||
lockBodyScroll();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 안전 체크 조회 오류:', error);
|
||||
console.error(' 안전 체크 조회 오류:', error);
|
||||
showToast('안전 체크 정보를 불러오는 중 오류가 발생했습니다.', 'error');
|
||||
}
|
||||
}
|
||||
@@ -2328,7 +2320,7 @@ async function saveSafetyChecklist() {
|
||||
showToast('안전 체크가 완료되었습니다.', 'success');
|
||||
closeSafetyModal();
|
||||
} catch (error) {
|
||||
console.error('❌ 안전 체크 저장 오류:', error);
|
||||
console.error(' 안전 체크 저장 오류:', error);
|
||||
showToast('안전 체크 저장 중 오류가 발생했습니다.', 'error');
|
||||
}
|
||||
}
|
||||
@@ -2473,7 +2465,7 @@ async function completeTbmSession() {
|
||||
throw new Error(response.message || '완료 처리에 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ TBM 완료 처리 오류:', error);
|
||||
console.error(' TBM 완료 처리 오류:', error);
|
||||
showToast('TBM 완료 처리 중 오류가 발생했습니다.', 'error');
|
||||
} finally {
|
||||
if (btn) { btn.disabled = false; btn.innerHTML = '<span class="tbm-btn-icon">✓</span> 완료'; }
|
||||
@@ -2622,7 +2614,6 @@ async function viewTbmSession(sessionId) {
|
||||
// 푸터 버튼 동적 생성
|
||||
const footer = document.getElementById('detailModalFooter');
|
||||
const safeId = parseInt(session.session_id) || 0;
|
||||
console.log('📋 TBM 상세 - session_id:', safeId, 'status:', session.status);
|
||||
if (session.status === 'draft') {
|
||||
footer.innerHTML = `
|
||||
<button type="button" class="tbm-btn tbm-btn-danger" onclick="confirmDeleteTbm(${safeId})">
|
||||
@@ -2649,7 +2640,7 @@ async function viewTbmSession(sessionId) {
|
||||
lockBodyScroll();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ TBM 상세 조회 오류:', error);
|
||||
console.error(' TBM 상세 조회 오류:', error);
|
||||
showToast('상세 정보를 불러오는 중 오류가 발생했습니다.', 'error');
|
||||
}
|
||||
}
|
||||
@@ -2674,7 +2665,7 @@ async function deleteTbmSession(sessionId) {
|
||||
await loadRecentTbmGroupedByDate();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ TBM 삭제 오류:', error);
|
||||
console.error(' TBM 삭제 오류:', error);
|
||||
showToast(error?.message || 'TBM 삭제 중 오류가 발생했습니다.', 'error');
|
||||
}
|
||||
}
|
||||
@@ -2747,7 +2738,7 @@ async function openHandoverModal(sessionId) {
|
||||
lockBodyScroll();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 인계 모달 열기 오류:', error);
|
||||
console.error(' 인계 모달 열기 오류:', error);
|
||||
showToast('인계 정보를 불러오는 중 오류가 발생했습니다.', 'error');
|
||||
}
|
||||
}
|
||||
@@ -2815,7 +2806,7 @@ async function saveHandover() {
|
||||
throw new Error(response.message || '인계 요청에 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 작업 인계 저장 오류:', error);
|
||||
console.error(' 작업 인계 저장 오류:', error);
|
||||
showToast('작업 인계 중 오류가 발생했습니다.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ class TbmAPI {
|
||||
// 현재 로그인한 사용자 정보 가져오기
|
||||
const userInfo = JSON.parse(localStorage.getItem('sso_user') || '{}');
|
||||
this.state.currentUser = userInfo;
|
||||
console.log('👤 로그인 사용자:', this.state.currentUser, 'user_id:', this.state.currentUser?.user_id);
|
||||
|
||||
// 병렬로 데이터 로드
|
||||
await Promise.all([
|
||||
@@ -31,9 +30,8 @@ class TbmAPI {
|
||||
this.loadWorkplaceCategories()
|
||||
]);
|
||||
|
||||
console.log('✅ 초기 데이터 로드 완료');
|
||||
} catch (error) {
|
||||
console.error('❌ 초기 데이터 로드 오류:', error);
|
||||
console.error(' 초기 데이터 로드 오류:', error);
|
||||
window.showToast?.('데이터를 불러오는 중 오류가 발생했습니다.', 'error');
|
||||
}
|
||||
}
|
||||
@@ -49,11 +47,10 @@ class TbmAPI {
|
||||
// 활성 상태인 작업자만 필터링
|
||||
workers = workers.filter(w => w.status === 'active' && w.employment_status === 'employed');
|
||||
this.state.allWorkers = workers;
|
||||
console.log('✅ 작업자 목록 로드:', workers.length + '명');
|
||||
return workers;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 작업자 로딩 오류:', error);
|
||||
console.error(' 작업자 로딩 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -69,11 +66,10 @@ class TbmAPI {
|
||||
this.state.allProjects = projects.filter(p =>
|
||||
p.is_active === 1 || p.is_active === true || p.is_active === '1'
|
||||
);
|
||||
console.log('✅ 프로젝트 목록 로드:', this.state.allProjects.length + '개 (활성)');
|
||||
return this.state.allProjects;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 프로젝트 로딩 오류:', error);
|
||||
console.error(' 프로젝트 로딩 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -86,11 +82,10 @@ class TbmAPI {
|
||||
const response = await window.apiCall('/tbm/safety-checks');
|
||||
if (response && response.success) {
|
||||
this.state.allSafetyChecks = response.data;
|
||||
console.log('✅ 안전 체크리스트 로드:', this.state.allSafetyChecks.length + '개');
|
||||
return this.state.allSafetyChecks;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 안전 체크리스트 로딩 오류:', error);
|
||||
console.error(' 안전 체크리스트 로딩 오류:', error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,11 +97,10 @@ class TbmAPI {
|
||||
const response = await window.apiCall('/daily-work-reports/work-types');
|
||||
if (response && response.success) {
|
||||
this.state.allWorkTypes = response.data || [];
|
||||
console.log('✅ 공정 목록 로드:', this.state.allWorkTypes.length + '개');
|
||||
return this.state.allWorkTypes;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 공정 로딩 오류:', error);
|
||||
console.error(' 공정 로딩 오류:', error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,11 +112,10 @@ class TbmAPI {
|
||||
const response = await window.apiCall('/tasks/active/list');
|
||||
if (response && response.success) {
|
||||
this.state.allTasks = response.data || [];
|
||||
console.log('✅ 작업 목록 로드:', this.state.allTasks.length + '개');
|
||||
return this.state.allTasks;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 작업 로딩 오류:', error);
|
||||
console.error(' 작업 로딩 오류:', error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,11 +127,10 @@ class TbmAPI {
|
||||
const response = await window.apiCall('/workplaces?is_active=true');
|
||||
if (response && response.success) {
|
||||
this.state.allWorkplaces = response.data || [];
|
||||
console.log('✅ 작업장 목록 로드:', this.state.allWorkplaces.length + '개');
|
||||
return this.state.allWorkplaces;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 작업장 로딩 오류:', error);
|
||||
console.error(' 작업장 로딩 오류:', error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,11 +142,10 @@ class TbmAPI {
|
||||
const response = await window.apiCall('/workplaces/categories/active/list');
|
||||
if (response && response.success) {
|
||||
this.state.allWorkplaceCategories = response.data || [];
|
||||
console.log('✅ 작업장 카테고리 로드:', this.state.allWorkplaceCategories.length + '개');
|
||||
return this.state.allWorkplaceCategories;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 작업장 카테고리 로딩 오류:', error);
|
||||
console.error(' 작업장 카테고리 로딩 오류:', error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,10 +163,9 @@ class TbmAPI {
|
||||
} else {
|
||||
this.state.todaySessions = [];
|
||||
}
|
||||
console.log('✅ 오늘 TBM 로드:', this.state.todaySessions.length + '건');
|
||||
return this.state.todaySessions;
|
||||
} catch (error) {
|
||||
console.error('❌ 오늘 TBM 조회 오류:', error);
|
||||
console.error(' 오늘 TBM 조회 오류:', error);
|
||||
window.showToast?.('오늘 TBM을 불러오는 중 오류가 발생했습니다.', 'error');
|
||||
this.state.todaySessions = [];
|
||||
return [];
|
||||
@@ -217,11 +207,10 @@ class TbmAPI {
|
||||
}
|
||||
});
|
||||
|
||||
console.log('✅ 날짜별 TBM 로드 완료:', this.state.allLoadedSessions.length + '건');
|
||||
return this.state.dateGroupedSessions;
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ TBM 날짜별 로드 오류:', error);
|
||||
console.error(' TBM 날짜별 로드 오류:', error);
|
||||
window.showToast?.('TBM을 불러오는 중 오류가 발생했습니다.', 'error');
|
||||
this.state.dateGroupedSessions = {};
|
||||
return {};
|
||||
@@ -242,7 +231,7 @@ class TbmAPI {
|
||||
}
|
||||
return this.state.allSessions;
|
||||
} catch (error) {
|
||||
console.error('❌ TBM 세션 조회 오류:', error);
|
||||
console.error(' TBM 세션 조회 오류:', error);
|
||||
window.showToast?.('TBM 세션을 불러오는 중 오류가 발생했습니다.', 'error');
|
||||
this.state.allSessions = [];
|
||||
return [];
|
||||
@@ -258,10 +247,9 @@ class TbmAPI {
|
||||
if (!response || !response.success) {
|
||||
throw new Error(response?.message || '세션 생성 실패');
|
||||
}
|
||||
console.log('✅ TBM 세션 생성 완료:', response.data?.session_id);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('❌ TBM 세션 생성 오류:', error);
|
||||
console.error(' TBM 세션 생성 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -277,7 +265,7 @@ class TbmAPI {
|
||||
}
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('❌ TBM 세션 조회 오류:', error);
|
||||
console.error(' TBM 세션 조회 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -293,7 +281,7 @@ class TbmAPI {
|
||||
}
|
||||
return response.data || [];
|
||||
} catch (error) {
|
||||
console.error('❌ TBM 팀원 조회 오류:', error);
|
||||
console.error(' TBM 팀원 조회 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -313,10 +301,9 @@ class TbmAPI {
|
||||
if (response && response.duplicates) err.duplicates = response.duplicates;
|
||||
throw err;
|
||||
}
|
||||
console.log('✅ TBM 팀원 추가 완료:', members.length + '명');
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('❌ TBM 팀원 추가 오류:', error);
|
||||
console.error(' TBM 팀원 추가 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -329,7 +316,7 @@ class TbmAPI {
|
||||
const response = await window.apiCall(`/tbm/sessions/${sessionId}/team/clear`, 'DELETE');
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('❌ TBM 팀원 삭제 오류:', error);
|
||||
console.error(' TBM 팀원 삭제 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -342,7 +329,7 @@ class TbmAPI {
|
||||
const response = await window.apiCall(`/tbm/sessions/${sessionId}/safety`);
|
||||
return response?.data || [];
|
||||
} catch (error) {
|
||||
console.error('❌ 안전 체크 조회 오류:', error);
|
||||
console.error(' 안전 체크 조회 오류:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -358,7 +345,7 @@ class TbmAPI {
|
||||
}
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('❌ 필터링된 안전 체크 조회 오류:', error);
|
||||
console.error(' 필터링된 안전 체크 조회 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -378,7 +365,7 @@ class TbmAPI {
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('❌ 안전 체크 저장 오류:', error);
|
||||
console.error(' 안전 체크 저장 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -396,10 +383,9 @@ class TbmAPI {
|
||||
if (!response || !response.success) {
|
||||
throw new Error(response?.message || '완료 처리 실패');
|
||||
}
|
||||
console.log('✅ TBM 완료 처리:', sessionId);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('❌ TBM 완료 처리 오류:', error);
|
||||
console.error(' TBM 완료 처리 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -415,7 +401,7 @@ class TbmAPI {
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('❌ 작업 인계 저장 오류:', error);
|
||||
console.error(' 작업 인계 저장 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -431,7 +417,7 @@ class TbmAPI {
|
||||
}
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('❌ 작업장 로드 오류:', error);
|
||||
console.error(' 작업장 로드 오류:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -448,7 +434,7 @@ class TbmAPI {
|
||||
}
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.error('❌ 지도 영역 로드 오류:', error);
|
||||
console.error(' 지도 영역 로드 오류:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -462,10 +448,9 @@ class TbmAPI {
|
||||
if (!response || !response.success) {
|
||||
throw new Error(response?.message || '삭제 실패');
|
||||
}
|
||||
console.log('✅ TBM 세션 삭제:', sessionId);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('❌ TBM 세션 삭제 오류:', error);
|
||||
console.error(' TBM 세션 삭제 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -485,7 +470,7 @@ class TbmAPI {
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('❌ 분할 배정 오류:', error);
|
||||
console.error(' 분할 배정 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -505,7 +490,7 @@ class TbmAPI {
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('❌ 팀원 수정 오류:', error);
|
||||
console.error(' 팀원 수정 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -521,7 +506,7 @@ class TbmAPI {
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('❌ TBM 이동 오류:', error);
|
||||
console.error(' TBM 이동 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -537,7 +522,7 @@ class TbmAPI {
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('❌ 작업 생성 오류:', error);
|
||||
console.error(' 작업 생성 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -553,7 +538,7 @@ class TbmAPI {
|
||||
}
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.error('❌ 활성 작업장 목록 오류:', error);
|
||||
console.error(' 활성 작업장 목록 오류:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -569,7 +554,7 @@ class TbmAPI {
|
||||
}
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.error('❌ 배정 현황 조회 오류:', error);
|
||||
console.error(' 배정 현황 조회 오류:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -585,7 +570,7 @@ class TbmAPI {
|
||||
}
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.error('❌ TBM 세션 조회 오류:', error);
|
||||
console.error(' TBM 세션 조회 오류:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -603,10 +588,9 @@ class TbmAPI {
|
||||
if (!response || !response.success) {
|
||||
throw new Error(response?.message || '완료 처리 실패');
|
||||
}
|
||||
console.log('✅ TBM 완료 처리 (근태 포함):', sessionId);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('❌ TBM 완료 처리 오류:', error);
|
||||
console.error(' TBM 완료 처리 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ let errorTimelineChart = null;
|
||||
|
||||
// 페이지 초기화
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('📈 작업 분석 페이지 초기화 시작');
|
||||
|
||||
initializePage();
|
||||
loadInitialData();
|
||||
@@ -35,7 +34,6 @@ function initializePage() {
|
||||
setupLogoutButton();
|
||||
|
||||
// 기본 날짜 설정은 HTML에서 처리됨 (새로운 UI)
|
||||
console.log('✅ 작업 분석 페이지 초기화 완료');
|
||||
}
|
||||
|
||||
// 현재 시간 업데이트 (시 분 초 형식으로 고정)
|
||||
@@ -92,7 +90,6 @@ function setupLogoutButton() {
|
||||
// 초기 데이터 로드
|
||||
async function loadInitialData() {
|
||||
try {
|
||||
console.log('📊 초기 데이터 로딩 시작');
|
||||
|
||||
// 프로젝트 목록 로드
|
||||
const projects = await apiCall('/projects/active/list', 'GET');
|
||||
@@ -101,7 +98,6 @@ async function loadInitialData() {
|
||||
// 프로젝트 필터 옵션 업데이트
|
||||
updateProjectFilters(projectData);
|
||||
|
||||
console.log('✅ 초기 데이터 로딩 완료');
|
||||
|
||||
} catch (error) {
|
||||
console.error('초기 데이터 로딩 오류:', error);
|
||||
@@ -145,7 +141,6 @@ function switchAnalysisMode(mode) {
|
||||
});
|
||||
document.getElementById(`${mode}-mode`).classList.add('active');
|
||||
|
||||
console.log(`🔄 분석 모드 전환: ${mode}`);
|
||||
}
|
||||
|
||||
// 분석 탭 전환
|
||||
@@ -164,7 +159,6 @@ function switchAnalysisTab(tab) {
|
||||
});
|
||||
document.getElementById(`${tab}-analysis`).classList.add('active');
|
||||
|
||||
console.log(`🔄 분석 탭 전환: ${tab}`);
|
||||
}
|
||||
|
||||
// 기간별 분석 로드
|
||||
@@ -186,7 +180,6 @@ async function loadPeriodAnalysis() {
|
||||
showLoading(true);
|
||||
|
||||
try {
|
||||
console.log('📊 기간별 분석 데이터 로딩 시작');
|
||||
|
||||
// API 호출 파라미터 구성
|
||||
const params = new URLSearchParams({
|
||||
@@ -199,28 +192,26 @@ async function loadPeriodAnalysis() {
|
||||
}
|
||||
|
||||
// 여러 API를 병렬로 호출하여 종합 분석 데이터 구성
|
||||
console.log('📡 API 파라미터:', params.toString());
|
||||
|
||||
const [statsRes, workerStatsRes, projectStatsRes, errorAnalysisRes] = await Promise.all([
|
||||
apiCall(`/work-analysis/stats?${params}`, 'GET').catch(err => {
|
||||
console.error('❌ stats API 오류:', err);
|
||||
console.error(' stats API 오류:', err);
|
||||
return { data: null };
|
||||
}),
|
||||
apiCall(`/work-analysis/worker-stats?${params}`, 'GET').catch(err => {
|
||||
console.error('❌ worker-stats API 오류:', err);
|
||||
console.error(' worker-stats API 오류:', err);
|
||||
return { data: [] };
|
||||
}),
|
||||
apiCall(`/work-analysis/project-stats?${params}`, 'GET').catch(err => {
|
||||
console.error('❌ project-stats API 오류:', err);
|
||||
console.error(' project-stats API 오류:', err);
|
||||
return { data: [] };
|
||||
}),
|
||||
apiCall(`/work-analysis/error-analysis?${params}`, 'GET').catch(err => {
|
||||
console.error('❌ error-analysis API 오류:', err);
|
||||
console.error(' error-analysis API 오류:', err);
|
||||
return { data: {} };
|
||||
})
|
||||
]);
|
||||
|
||||
console.log('📊 개별 API 응답:');
|
||||
console.log(' - stats:', statsRes);
|
||||
console.log(' - worker-stats:', workerStatsRes);
|
||||
console.log(' - project-stats:', projectStatsRes);
|
||||
@@ -234,11 +225,6 @@ async function loadPeriodAnalysis() {
|
||||
errorStats: errorAnalysisRes.data || errorAnalysisRes
|
||||
};
|
||||
|
||||
console.log('📊 분석 데이터:', analysisData);
|
||||
console.log('📊 요약 통계:', analysisData.summary);
|
||||
console.log('👥 작업자 통계:', analysisData.workerStats);
|
||||
console.log('📁 프로젝트 통계:', analysisData.projectStats);
|
||||
console.log('⚠️ 오류 통계:', analysisData.errorStats);
|
||||
|
||||
// 결과 표시
|
||||
displayPeriodAnalysis(analysisData);
|
||||
@@ -284,13 +270,8 @@ function updateSummaryStats(summary) {
|
||||
function displayWorkerAnalysis(workerStats) {
|
||||
const grid = document.getElementById('workerAnalysisGrid');
|
||||
|
||||
console.log('👥 작업자 분석 데이터 확인:', workerStats);
|
||||
console.log('👥 데이터 타입:', typeof workerStats);
|
||||
console.log('👥 배열 여부:', Array.isArray(workerStats));
|
||||
console.log('👥 길이:', workerStats ? workerStats.length : 'undefined');
|
||||
|
||||
if (!workerStats || (Array.isArray(workerStats) && workerStats.length === 0)) {
|
||||
console.log('👥 빈 데이터로 인한 empty-state 표시');
|
||||
grid.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<div class="empty-icon">👥</div>
|
||||
@@ -383,17 +364,11 @@ function displayWorkerAnalysis(workerStats) {
|
||||
function displayProjectAnalysis(projectStats) {
|
||||
const detailsContainer = document.getElementById('projectDetails');
|
||||
|
||||
console.log('📁 프로젝트 분석 데이터 확인:', projectStats);
|
||||
console.log('📁 데이터 타입:', typeof projectStats);
|
||||
console.log('📁 배열 여부:', Array.isArray(projectStats));
|
||||
console.log('📁 길이:', projectStats ? projectStats.length : 'undefined');
|
||||
|
||||
if (projectStats && projectStats.length > 0) {
|
||||
console.log('📁 첫 번째 프로젝트 데이터:', projectStats[0]);
|
||||
}
|
||||
|
||||
if (!projectStats || projectStats.length === 0) {
|
||||
console.log('📁 빈 데이터로 인한 empty-state 표시');
|
||||
detailsContainer.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<div class="empty-icon">📁</div>
|
||||
@@ -412,7 +387,6 @@ function displayProjectAnalysis(projectStats) {
|
||||
}, 0);
|
||||
|
||||
projectStats.forEach(project => {
|
||||
console.log('📁 개별 프로젝트 처리:', project);
|
||||
|
||||
const projectName = project.project_name || project.name || project.projectName || '프로젝트';
|
||||
const totalHours = project.totalHours || project.total_hours || project.hours || 0;
|
||||
@@ -451,8 +425,6 @@ function updateProjectChart(projectStats) {
|
||||
const labels = projectStats.map(p => p.project_name || p.name || p.projectName || '프로젝트');
|
||||
const data = projectStats.map(p => p.totalHours || p.total_hours || p.hours || 0);
|
||||
|
||||
console.log('📊 차트 라벨:', labels);
|
||||
console.log('📊 차트 데이터:', data);
|
||||
const colors = [
|
||||
'#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6',
|
||||
'#06b6d4', '#84cc16', '#f97316', '#ec4899', '#6366f1'
|
||||
@@ -487,15 +459,11 @@ function updateProjectChart(projectStats) {
|
||||
|
||||
// 오류 분석 표시
|
||||
function displayErrorAnalysis(errorStats, allData) {
|
||||
console.log('⚠️ 오류 분석 데이터 확인:', errorStats);
|
||||
console.log('⚠️ 데이터 타입:', typeof errorStats);
|
||||
console.log('⚠️ 배열 여부:', Array.isArray(errorStats));
|
||||
|
||||
// errorStats가 배열인 경우 첫 번째 요소 사용
|
||||
let errorData = errorStats;
|
||||
if (Array.isArray(errorStats) && errorStats.length > 0) {
|
||||
errorData = errorStats[0];
|
||||
console.log('⚠️ 배열에서 첫 번째 요소 사용:', errorData);
|
||||
}
|
||||
|
||||
// 오류 요약 업데이트 - 실제 데이터 구조에 맞게 수정
|
||||
@@ -506,7 +474,6 @@ function displayErrorAnalysis(errorStats, allData) {
|
||||
const totalHours = allData && allData.summary ? allData.summary.totalHours : 0;
|
||||
const normalHours = Math.max(0, totalHours - errorHours);
|
||||
|
||||
console.log('⚠️ 정규 시간:', normalHours, '오류 시간:', errorHours);
|
||||
|
||||
document.getElementById('normalHours').textContent = `${normalHours}h`;
|
||||
document.getElementById('errorHours').textContent = `${errorHours}h`;
|
||||
@@ -671,7 +638,6 @@ async function loadProjectAnalysis() {
|
||||
showLoading(true);
|
||||
|
||||
try {
|
||||
console.log('📁 프로젝트별 분석 데이터 로딩 시작');
|
||||
|
||||
// API 호출 파라미터 구성
|
||||
const params = new URLSearchParams({
|
||||
@@ -685,7 +651,6 @@ async function loadProjectAnalysis() {
|
||||
const response = await apiCall(`/work-analysis/project-worktype-analysis?${params}`, 'GET');
|
||||
const projectAnalysisData = response.data || response;
|
||||
|
||||
console.log('📁 프로젝트 분석 데이터:', projectAnalysisData);
|
||||
|
||||
// 결과 표시
|
||||
displayProjectModeAnalysis(projectAnalysisData);
|
||||
|
||||
@@ -28,7 +28,6 @@ class WorkAnalysisAPIClient {
|
||||
config.body = JSON.stringify(data);
|
||||
}
|
||||
|
||||
console.log(`📡 API 호출: ${this.baseURL}${endpoint} (${method})`);
|
||||
|
||||
const response = await fetch(`${this.baseURL}${endpoint}`, config);
|
||||
const result = await response.json();
|
||||
@@ -37,11 +36,10 @@ class WorkAnalysisAPIClient {
|
||||
throw new Error(result.message || `HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
console.log(`✅ API 성공: ${this.baseURL}${endpoint}`);
|
||||
return result;
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ API 실패: ${this.baseURL}${endpoint}`, error);
|
||||
console.error(` API 실패: ${this.baseURL}${endpoint}`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -68,11 +66,9 @@ class WorkAnalysisAPIClient {
|
||||
* 기본 통계 조회
|
||||
*/
|
||||
async getBasicStats(startDate, endDate, projectId = null) {
|
||||
console.log('🔍 getBasicStats 호출:', startDate, '~', endDate, projectId ? `(프로젝트: ${projectId})` : '');
|
||||
const params = this.createDateParams(startDate, endDate,
|
||||
projectId ? { project_id: projectId } : {}
|
||||
);
|
||||
console.log('🌐 API 요청 URL:', `/work-analysis/stats?${params}`);
|
||||
return await this.apiCall(`/work-analysis/stats?${params}`);
|
||||
}
|
||||
|
||||
@@ -138,20 +134,18 @@ class WorkAnalysisAPIClient {
|
||||
* @returns {Promise<Array>} 결과 배열
|
||||
*/
|
||||
async batchCall(apiCalls) {
|
||||
console.log('🔄 배치 API 호출 시작:', apiCalls.length, '개');
|
||||
|
||||
const promises = apiCalls.map(async ({ name, method, ...args }) => {
|
||||
try {
|
||||
const result = await this[method](...args);
|
||||
return { name, success: true, data: result };
|
||||
} catch (error) {
|
||||
console.warn(`⚠️ ${name} API 오류:`, error);
|
||||
console.warn(` ${name} API 오류:`, error);
|
||||
return { name, success: false, error: error.message, data: null };
|
||||
}
|
||||
});
|
||||
|
||||
const results = await Promise.all(promises);
|
||||
console.log('✅ 배치 API 호출 완료');
|
||||
|
||||
return results.reduce((acc, result) => {
|
||||
acc[result.name] = result;
|
||||
|
||||
@@ -23,7 +23,6 @@ class WorkAnalysisChartRenderer {
|
||||
if (this.charts.has(chartId)) {
|
||||
this.charts.get(chartId).destroy();
|
||||
this.charts.delete(chartId);
|
||||
console.log('🗑️ 차트 제거:', chartId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +32,6 @@ class WorkAnalysisChartRenderer {
|
||||
destroyAllCharts() {
|
||||
this.charts.forEach((chart, id) => {
|
||||
chart.destroy();
|
||||
console.log('🗑️ 차트 제거:', id);
|
||||
});
|
||||
this.charts.clear();
|
||||
}
|
||||
@@ -52,7 +50,6 @@ class WorkAnalysisChartRenderer {
|
||||
const chart = new Chart(canvas, config);
|
||||
this.charts.set(chartId, chart);
|
||||
|
||||
console.log('📊 차트 생성:', chartId);
|
||||
return chart;
|
||||
}
|
||||
|
||||
@@ -65,7 +62,6 @@ class WorkAnalysisChartRenderer {
|
||||
* @param {string} projectId - 프로젝트 ID (선택사항)
|
||||
*/
|
||||
async renderTimeSeriesChart(startDate, endDate, projectId = '') {
|
||||
console.log('📈 시계열 차트 렌더링 시작');
|
||||
|
||||
try {
|
||||
const api = window.WorkAnalysisAPI;
|
||||
@@ -77,7 +73,7 @@ class WorkAnalysisChartRenderer {
|
||||
|
||||
const canvas = document.getElementById('workStatusChart');
|
||||
if (!canvas) {
|
||||
console.error('❌ workStatusChart 캔버스를 찾을 수 없습니다');
|
||||
console.error(' workStatusChart 캔버스를 찾을 수 없습니다');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -125,10 +121,9 @@ class WorkAnalysisChartRenderer {
|
||||
};
|
||||
|
||||
this.createChart('workStatus', canvas, config);
|
||||
console.log('✅ 시계열 차트 렌더링 완료');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 시계열 차트 렌더링 실패:', error);
|
||||
console.error(' 시계열 차트 렌더링 실패:', error);
|
||||
this._showChartError('workStatusChart', '시계열 차트를 불러올 수 없습니다');
|
||||
}
|
||||
}
|
||||
@@ -140,11 +135,10 @@ class WorkAnalysisChartRenderer {
|
||||
* @param {Array} projectData - 프로젝트 데이터
|
||||
*/
|
||||
renderStackedBarChart(projectData) {
|
||||
console.log('📊 스택 바 차트 렌더링 시작');
|
||||
|
||||
const canvas = document.getElementById('projectDistributionChart');
|
||||
if (!canvas) {
|
||||
console.error('❌ projectDistributionChart 캔버스를 찾을 수 없습니다');
|
||||
console.error(' projectDistributionChart 캔버스를 찾을 수 없습니다');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -212,7 +206,6 @@ class WorkAnalysisChartRenderer {
|
||||
};
|
||||
|
||||
this.createChart('projectDistribution', canvas, config);
|
||||
console.log('✅ 스택 바 차트 렌더링 완료');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -256,11 +249,10 @@ class WorkAnalysisChartRenderer {
|
||||
* @param {Array} workerData - 작업자 데이터
|
||||
*/
|
||||
renderWorkerPerformanceChart(workerData) {
|
||||
console.log('👤 작업자별 성과 차트 렌더링 시작');
|
||||
|
||||
const canvas = document.getElementById('workerPerformanceChart');
|
||||
if (!canvas) {
|
||||
console.error('❌ workerPerformanceChart 캔버스를 찾을 수 없습니다');
|
||||
console.error(' workerPerformanceChart 캔버스를 찾을 수 없습니다');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -308,7 +300,6 @@ class WorkAnalysisChartRenderer {
|
||||
};
|
||||
|
||||
this.createChart('workerPerformance', canvas, config);
|
||||
console.log('✅ 작업자별 성과 차트 렌더링 완료');
|
||||
}
|
||||
|
||||
// ========== 오류 분석 차트 ==========
|
||||
@@ -318,11 +309,10 @@ class WorkAnalysisChartRenderer {
|
||||
* @param {Array} errorData - 오류 데이터
|
||||
*/
|
||||
renderErrorAnalysisChart(errorData) {
|
||||
console.log('⚠️ 오류 분석 차트 렌더링 시작');
|
||||
|
||||
const canvas = document.getElementById('errorAnalysisChart');
|
||||
if (!canvas) {
|
||||
console.error('❌ errorAnalysisChart 캔버스를 찾을 수 없습니다');
|
||||
console.error(' errorAnalysisChart 캔버스를 찾을 수 없습니다');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -380,7 +370,6 @@ class WorkAnalysisChartRenderer {
|
||||
};
|
||||
|
||||
this.createChart('errorAnalysis', canvas, config);
|
||||
console.log('✅ 오류 분석 차트 렌더링 완료');
|
||||
}
|
||||
|
||||
// ========== 유틸리티 ==========
|
||||
@@ -421,9 +410,8 @@ class WorkAnalysisChartRenderer {
|
||||
this.charts.forEach((chart, id) => {
|
||||
try {
|
||||
chart.resize();
|
||||
console.log('📏 차트 리사이즈:', id);
|
||||
} catch (error) {
|
||||
console.warn('⚠️ 차트 리사이즈 실패:', id, error);
|
||||
console.warn(' 차트 리사이즈 실패:', id, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -47,7 +47,6 @@ class WorkAnalysisDataProcessor {
|
||||
* @returns {Object} 집계된 프로젝트 데이터
|
||||
*/
|
||||
aggregateProjectData(recentWorkData) {
|
||||
console.log('📊 프로젝트 데이터 집계 시작');
|
||||
|
||||
if (!recentWorkData || recentWorkData.length === 0) {
|
||||
return { projects: [], totalHours: 0 };
|
||||
@@ -62,7 +61,6 @@ class WorkAnalysisDataProcessor {
|
||||
|
||||
// 주말 연차는 제외
|
||||
if (isWeekend && isVacation) {
|
||||
console.log('🏖️ 주말 연차/휴무 제외:', work.report_date, work.project_name);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -118,7 +116,6 @@ class WorkAnalysisDataProcessor {
|
||||
|
||||
const totalHours = projects.reduce((sum, p) => sum + p.totalHours, 0);
|
||||
|
||||
console.log('✅ 프로젝트 데이터 집계 완료:', projects.length, '개 프로젝트');
|
||||
return { projects, totalHours };
|
||||
}
|
||||
|
||||
@@ -151,7 +148,6 @@ class WorkAnalysisDataProcessor {
|
||||
* @returns {Array} 집계된 오류 데이터
|
||||
*/
|
||||
aggregateErrorData(recentWorkData) {
|
||||
console.log('📊 오류 분석 데이터 집계 시작');
|
||||
|
||||
if (!recentWorkData || recentWorkData.length === 0) {
|
||||
return [];
|
||||
@@ -166,7 +162,6 @@ class WorkAnalysisDataProcessor {
|
||||
|
||||
// 주말 연차는 완전히 제외
|
||||
if (isWeekend && isVacation) {
|
||||
console.log('🏖️ 주말 연차/휴무 제외:', work.report_date, work.project_name);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -243,7 +238,6 @@ class WorkAnalysisDataProcessor {
|
||||
return (a.project_name || '').localeCompare(b.project_name || '');
|
||||
});
|
||||
|
||||
console.log('✅ 오류 분석 데이터 집계 완료:', processedResult.length, '개 항목');
|
||||
return processedResult;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,13 +18,11 @@ class WorkAnalysisMainController {
|
||||
* 초기화
|
||||
*/
|
||||
init() {
|
||||
console.log('🚀 작업 분석 메인 컨트롤러 초기화');
|
||||
|
||||
this.setupEventListeners();
|
||||
this.setupStateListeners();
|
||||
this.initializeUI();
|
||||
|
||||
console.log('✅ 작업 분석 메인 컨트롤러 초기화 완료');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,16 +155,14 @@ class WorkAnalysisMainController {
|
||||
const startDate = document.getElementById('startDate').value;
|
||||
const endDate = document.getElementById('endDate').value;
|
||||
|
||||
console.log('🔄 기간 확정 처리 시작:', startDate, '~', endDate);
|
||||
|
||||
this.state.confirmPeriod(startDate, endDate);
|
||||
|
||||
this.showToast('기간이 확정되었습니다', 'success');
|
||||
|
||||
console.log('✅ 기간 확정 완료 - 각 분석 버튼을 눌러서 데이터를 확인하세요');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 기간 확정 처리 오류:', error);
|
||||
console.error(' 기간 확정 처리 오류:', error);
|
||||
this.state.setError(error);
|
||||
this.showToast(error.message, 'error');
|
||||
}
|
||||
@@ -222,13 +218,10 @@ class WorkAnalysisMainController {
|
||||
const { start, end } = currentState.confirmedPeriod;
|
||||
|
||||
try {
|
||||
console.log('📊 기본 통계 로딩 시작 - 기간:', start, '~', end);
|
||||
this.state.startLoading('기본 통계를 로딩 중입니다...');
|
||||
|
||||
console.log('🌐 API 호출 전 - getBasicStats 호출...');
|
||||
const statsResponse = await this.api.getBasicStats(start, end);
|
||||
|
||||
console.log('📊 기본 통계 API 응답:', statsResponse);
|
||||
|
||||
if (statsResponse.success && statsResponse.data) {
|
||||
const stats = statsResponse.data;
|
||||
@@ -250,7 +243,6 @@ class WorkAnalysisMainController {
|
||||
this.state.setCache('basicStats', cardData);
|
||||
this.updateResultCards(cardData);
|
||||
|
||||
console.log('✅ 기본 통계 로딩 완료:', cardData);
|
||||
} else {
|
||||
// 기본값으로 카드 업데이트
|
||||
const defaultData = {
|
||||
@@ -261,11 +253,11 @@ class WorkAnalysisMainController {
|
||||
errorRate: 0
|
||||
};
|
||||
this.updateResultCards(defaultData);
|
||||
console.warn('⚠️ 기본 통계 데이터가 없어서 기본값으로 설정');
|
||||
console.warn(' 기본 통계 데이터가 없어서 기본값으로 설정');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 기본 통계 로드 실패:', error);
|
||||
console.error(' 기본 통계 로드 실패:', error);
|
||||
// 에러 시에도 기본값으로 카드 업데이트
|
||||
const defaultData = {
|
||||
totalHours: 0,
|
||||
@@ -318,7 +310,6 @@ class WorkAnalysisMainController {
|
||||
}
|
||||
]);
|
||||
|
||||
console.log('🔍 기간별 작업 현황 API 응답:', batchData);
|
||||
|
||||
// 데이터 처리
|
||||
const recentWorkData = batchData.recentWork?.success ? batchData.recentWork.data.data : [];
|
||||
@@ -332,7 +323,7 @@ class WorkAnalysisMainController {
|
||||
this.showToast('기간별 작업 현황 분석이 완료되었습니다', 'success');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 기간별 작업 현황 분석 오류:', error);
|
||||
console.error(' 기간별 작업 현황 분석 오류:', error);
|
||||
this.state.setError(error);
|
||||
this.showToast('기간별 작업 현황 분석에 실패했습니다', 'error');
|
||||
} finally {
|
||||
@@ -358,7 +349,6 @@ class WorkAnalysisMainController {
|
||||
// 실제 API 호출
|
||||
const distributionData = await this.api.getProjectDistributionData(start, end);
|
||||
|
||||
console.log('🔍 프로젝트별 분포 API 응답:', distributionData);
|
||||
|
||||
// 데이터 처리
|
||||
const recentWorkData = distributionData.recentWork?.success ? distributionData.recentWork.data.data : [];
|
||||
@@ -366,7 +356,6 @@ class WorkAnalysisMainController {
|
||||
|
||||
const projectData = this.dataProcessor.aggregateProjectData(recentWorkData);
|
||||
|
||||
console.log('📊 취합된 프로젝트 데이터:', projectData);
|
||||
|
||||
// 테이블 렌더링
|
||||
this.tableRenderer.renderProjectDistributionTable(projectData, workerData);
|
||||
@@ -374,7 +363,7 @@ class WorkAnalysisMainController {
|
||||
this.showToast('프로젝트별 분포 분석이 완료되었습니다', 'success');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 프로젝트별 분포 분석 오류:', error);
|
||||
console.error(' 프로젝트별 분포 분석 오류:', error);
|
||||
this.state.setError(error);
|
||||
this.showToast('프로젝트별 분포 분석에 실패했습니다', 'error');
|
||||
} finally {
|
||||
@@ -399,7 +388,6 @@ class WorkAnalysisMainController {
|
||||
|
||||
const workerStatsResponse = await this.api.getWorkerStats(start, end);
|
||||
|
||||
console.log('👤 작업자 통계 API 응답:', workerStatsResponse);
|
||||
|
||||
if (workerStatsResponse.success && workerStatsResponse.data) {
|
||||
this.chartRenderer.renderWorkerPerformanceChart(workerStatsResponse.data);
|
||||
@@ -409,7 +397,7 @@ class WorkAnalysisMainController {
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 작업자별 성과 분석 오류:', error);
|
||||
console.error(' 작업자별 성과 분석 오류:', error);
|
||||
this.state.setError(error);
|
||||
this.showToast('작업자별 성과 분석에 실패했습니다', 'error');
|
||||
} finally {
|
||||
@@ -438,7 +426,6 @@ class WorkAnalysisMainController {
|
||||
this.api.getErrorAnalysis(start, end)
|
||||
]);
|
||||
|
||||
console.log('🔍 오류 분석 API 응답:', recentWorkResponse);
|
||||
|
||||
if (recentWorkResponse.success && recentWorkResponse.data) {
|
||||
this.tableRenderer.renderErrorAnalysisTable(recentWorkResponse.data);
|
||||
@@ -448,7 +435,7 @@ class WorkAnalysisMainController {
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 오류 분석 실패:', error);
|
||||
console.error(' 오류 분석 실패:', error);
|
||||
this.state.setError(error);
|
||||
this.showToast('오류 분석에 실패했습니다', 'error');
|
||||
} finally {
|
||||
@@ -571,13 +558,11 @@ class WorkAnalysisMainController {
|
||||
* 토스트 메시지 표시
|
||||
*/
|
||||
showToast(message, type = 'info') {
|
||||
console.log(`📢 ${type.toUpperCase()}: ${message}`);
|
||||
|
||||
// 간단한 토스트 구현 (실제로는 더 정교한 토스트 라이브러리 사용 권장)
|
||||
if (type === 'error') {
|
||||
alert(`❌ ${message}`);
|
||||
} else if (type === 'success') {
|
||||
console.log(`✅ ${message}`);
|
||||
} else if (type === 'warning') {
|
||||
alert(`⚠️ ${message}`);
|
||||
}
|
||||
@@ -587,7 +572,7 @@ class WorkAnalysisMainController {
|
||||
* 에러 처리
|
||||
*/
|
||||
handleError(errorInfo) {
|
||||
console.error('❌ 에러 발생:', errorInfo);
|
||||
console.error(' 에러 발생:', errorInfo);
|
||||
this.showToast(errorInfo.message, 'error');
|
||||
}
|
||||
|
||||
@@ -597,7 +582,6 @@ class WorkAnalysisMainController {
|
||||
* 컨트롤러 상태 디버그
|
||||
*/
|
||||
debug() {
|
||||
console.log('🔍 메인 컨트롤러 상태:');
|
||||
console.log('- API 클라이언트:', this.api);
|
||||
console.log('- 상태 관리자:', this.state.getState());
|
||||
console.log('- 차트 상태:', this.chartRenderer.getChartStatus());
|
||||
|
||||
@@ -34,7 +34,6 @@ class WorkAnalysisModuleLoader {
|
||||
* 모듈들을 순차적으로 로드
|
||||
*/
|
||||
async _loadModules() {
|
||||
console.log('🚀 작업 분석 모듈 로딩 시작');
|
||||
|
||||
try {
|
||||
// 의존성 순서대로 로드
|
||||
@@ -42,11 +41,10 @@ class WorkAnalysisModuleLoader {
|
||||
await this._loadModule(module);
|
||||
}
|
||||
|
||||
console.log('✅ 모든 작업 분석 모듈 로딩 완료');
|
||||
this._onAllModulesLoaded();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 모듈 로딩 실패:', error);
|
||||
console.error(' 모듈 로딩 실패:', error);
|
||||
this._onLoadingError(error);
|
||||
throw error;
|
||||
}
|
||||
@@ -58,7 +56,6 @@ class WorkAnalysisModuleLoader {
|
||||
*/
|
||||
async _loadModule(module) {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log(`📦 로딩 중: ${module.name}`);
|
||||
|
||||
const script = document.createElement('script');
|
||||
script.src = module.path;
|
||||
@@ -66,12 +63,11 @@ class WorkAnalysisModuleLoader {
|
||||
|
||||
script.onload = () => {
|
||||
module.loaded = true;
|
||||
console.log(`✅ 로딩 완료: ${module.name}`);
|
||||
resolve();
|
||||
};
|
||||
|
||||
script.onerror = (error) => {
|
||||
console.error(`❌ 로딩 실패: ${module.name}`, error);
|
||||
console.error(` 로딩 실패: ${module.name}`, error);
|
||||
reject(new Error(`Failed to load ${module.name}: ${module.path}`));
|
||||
};
|
||||
|
||||
@@ -95,7 +91,7 @@ class WorkAnalysisModuleLoader {
|
||||
const missingGlobals = requiredGlobals.filter(name => !window[name]);
|
||||
|
||||
if (missingGlobals.length > 0) {
|
||||
console.warn('⚠️ 일부 전역 객체가 누락됨:', missingGlobals);
|
||||
console.warn(' 일부 전역 객체가 누락됨:', missingGlobals);
|
||||
}
|
||||
|
||||
// 하위 호환성을 위한 전역 함수들 설정
|
||||
@@ -106,7 +102,6 @@ class WorkAnalysisModuleLoader {
|
||||
detail: { modules: this.modules }
|
||||
}));
|
||||
|
||||
console.log('🎉 작업 분석 시스템 준비 완료');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,7 +160,6 @@ class WorkAnalysisModuleLoader {
|
||||
// 전역 함수로 등록
|
||||
Object.assign(window, legacyFunctions);
|
||||
|
||||
console.log('🔗 하위 호환성 함수 설정 완료');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -39,7 +39,6 @@ class WorkAnalysisStateManager {
|
||||
* 초기화
|
||||
*/
|
||||
init() {
|
||||
console.log('🔧 상태 관리자 초기화');
|
||||
|
||||
// 기본 날짜 설정 (현재 월)
|
||||
const now = new Date();
|
||||
@@ -63,7 +62,6 @@ class WorkAnalysisStateManager {
|
||||
const prevState = { ...this.state };
|
||||
this.state = { ...this.state, ...updates };
|
||||
|
||||
console.log('🔄 상태 업데이트:', updates);
|
||||
|
||||
// 리스너들에게 상태 변경 알림
|
||||
this.notifyListeners(prevState, this.state);
|
||||
@@ -94,7 +92,7 @@ class WorkAnalysisStateManager {
|
||||
try {
|
||||
callback(newState, prevState);
|
||||
} catch (error) {
|
||||
console.error(`❌ 리스너 ${key} 오류:`, error);
|
||||
console.error(` 리스너 ${key} 오류:`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -157,7 +155,6 @@ class WorkAnalysisStateManager {
|
||||
}
|
||||
});
|
||||
|
||||
console.log('✅ 기간 확정:', startDate, '~', endDate);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -177,7 +174,6 @@ class WorkAnalysisStateManager {
|
||||
// DOM 업데이트 직접 수행
|
||||
this.updateTabDOM(tabId);
|
||||
|
||||
console.log('🔄 탭 전환:', tabId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -259,11 +255,9 @@ class WorkAnalysisStateManager {
|
||||
|
||||
const age = Date.now() - cached.timestamp;
|
||||
if (age > maxAge) {
|
||||
console.log('🗑️ 캐시 만료:', key);
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log('📦 캐시 히트:', key);
|
||||
return cached.data;
|
||||
}
|
||||
|
||||
@@ -309,7 +303,7 @@ class WorkAnalysisStateManager {
|
||||
isLoading: false
|
||||
});
|
||||
|
||||
console.error('❌ 에러 발생:', errorInfo);
|
||||
console.error(' 에러 발생:', errorInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -353,8 +347,6 @@ class WorkAnalysisStateManager {
|
||||
* 상태 디버그 정보 출력
|
||||
*/
|
||||
debug() {
|
||||
console.log('🔍 현재 상태:', this.state);
|
||||
console.log('👂 등록된 리스너:', Array.from(this.listeners.keys()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,19 +16,17 @@ class WorkAnalysisTableRenderer {
|
||||
* @param {Array} workerData - 작업자 데이터
|
||||
*/
|
||||
renderProjectDistributionTable(projectData, workerData) {
|
||||
console.log('📋 프로젝트별 분포 테이블 렌더링 시작');
|
||||
|
||||
const tbody = document.getElementById('projectDistributionTableBody');
|
||||
const tfoot = document.getElementById('projectDistributionTableFooter');
|
||||
|
||||
if (!tbody) {
|
||||
console.error('❌ projectDistributionTableBody 요소를 찾을 수 없습니다');
|
||||
console.error(' projectDistributionTableBody 요소를 찾을 수 없습니다');
|
||||
return;
|
||||
}
|
||||
|
||||
// 프로젝트 데이터가 없으면 작업자 데이터로 대체
|
||||
if (!projectData || !projectData.projects || projectData.projects.length === 0) {
|
||||
console.log('⚠️ 프로젝트 데이터가 없어서 작업자 데이터로 대체합니다.');
|
||||
this._renderFallbackTable(workerData, tbody, tfoot);
|
||||
return;
|
||||
}
|
||||
@@ -130,7 +128,6 @@ class WorkAnalysisTableRenderer {
|
||||
tfoot.style.display = 'table-footer-group';
|
||||
}
|
||||
|
||||
console.log('✅ 프로젝트별 분포 테이블 렌더링 완료');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -189,17 +186,14 @@ class WorkAnalysisTableRenderer {
|
||||
* @param {Array} recentWorkData - 최근 작업 데이터
|
||||
*/
|
||||
renderErrorAnalysisTable(recentWorkData) {
|
||||
console.log('📊 오류 분석 테이블 렌더링 시작');
|
||||
console.log('📊 받은 데이터:', recentWorkData);
|
||||
|
||||
const tableBody = document.getElementById('errorAnalysisTableBody');
|
||||
const tableFooter = document.getElementById('errorAnalysisTableFooter');
|
||||
|
||||
console.log('📊 DOM 요소 확인:', { tableBody, tableFooter });
|
||||
|
||||
// DOM 요소 존재 확인
|
||||
if (!tableBody) {
|
||||
console.error('❌ errorAnalysisTableBody 요소를 찾을 수 없습니다');
|
||||
console.error(' errorAnalysisTableBody 요소를 찾을 수 없습니다');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -335,7 +329,6 @@ class WorkAnalysisTableRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
console.log('✅ 오류 분석 테이블 렌더링 완료');
|
||||
}
|
||||
|
||||
// ========== 기간별 작업 현황 테이블 ==========
|
||||
@@ -347,11 +340,10 @@ class WorkAnalysisTableRenderer {
|
||||
* @param {Array} recentWorkData - 최근 작업 데이터
|
||||
*/
|
||||
renderWorkStatusTable(projectData, workerData, recentWorkData) {
|
||||
console.log('📈 기간별 작업 현황 테이블 렌더링 시작');
|
||||
|
||||
const tableContainer = document.querySelector('#work-status-tab .table-container');
|
||||
if (!tableContainer) {
|
||||
console.error('❌ 작업 현황 테이블 컨테이너를 찾을 수 없습니다');
|
||||
console.error(' 작업 현황 테이블 컨테이너를 찾을 수 없습니다');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -431,7 +423,6 @@ class WorkAnalysisTableRenderer {
|
||||
`;
|
||||
|
||||
tableContainer.innerHTML = tableHTML;
|
||||
console.log('✅ 기간별 작업 현황 테이블 렌더링 완료');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,7 +10,6 @@ let statsData = {
|
||||
|
||||
// 페이지 초기화
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('🔧 작업 관리 페이지 초기화 시작');
|
||||
|
||||
initializePage();
|
||||
loadStatistics();
|
||||
@@ -86,7 +85,6 @@ function setupLogoutButton() {
|
||||
// 통계 데이터 로드
|
||||
async function loadStatistics() {
|
||||
try {
|
||||
console.log('📊 통계 데이터 로딩 시작');
|
||||
|
||||
// 프로젝트 수 조회
|
||||
try {
|
||||
@@ -130,7 +128,6 @@ async function loadStatistics() {
|
||||
statsData.codeTypes = 3; // ISSUE_TYPE, ERROR_TYPE, WORK_STATUS
|
||||
updateStatDisplay('codeTypeCount', statsData.codeTypes);
|
||||
|
||||
console.log('✅ 통계 데이터 로딩 완료:', statsData);
|
||||
|
||||
} catch (error) {
|
||||
console.error('통계 데이터 로딩 오류:', error);
|
||||
@@ -155,7 +152,6 @@ function updateStatDisplay(elementId, value) {
|
||||
|
||||
// 페이지 네비게이션
|
||||
function navigateToPage(url) {
|
||||
console.log(`🔗 페이지 이동: ${url}`);
|
||||
|
||||
// 로딩 효과
|
||||
const card = event.currentTarget;
|
||||
|
||||
@@ -77,7 +77,6 @@ class WorkReportReviewManager {
|
||||
{id: 3, name: '입고지연'}, {id: 4, name: '작업 불량'}
|
||||
];
|
||||
} catch (error) {
|
||||
console.log('⚠️ 일부 API 사용 불가, 기본값 사용');
|
||||
}
|
||||
|
||||
// 휴가 정보 로드
|
||||
@@ -107,13 +106,11 @@ class WorkReportReviewManager {
|
||||
this.attendanceData = await response.json();
|
||||
console.log('휴가 정보 로드 완료:', this.attendanceData.length);
|
||||
} else if (response.status === 404) {
|
||||
console.log('⚠️ 휴가 API 없음, 더미 데이터 생성');
|
||||
this.attendanceData = this.generateDummyAttendance();
|
||||
} else {
|
||||
throw new Error(`휴가 정보 로드 실패: ${response.status}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('⚠️ 휴가 정보 로드 오류, 더미 데이터 사용:', error.message);
|
||||
this.attendanceData = this.generateDummyAttendance();
|
||||
}
|
||||
}
|
||||
@@ -210,7 +207,6 @@ class WorkReportReviewManager {
|
||||
if (response.status === 404 || response.status === 500) {
|
||||
// API가 아직 준비되지 않은 경우 더미 데이터 사용
|
||||
this.reports = this.generateDummyData();
|
||||
console.log('⚠️ API 응답 오류, 더미 데이터 사용:', response.status);
|
||||
if (response.status === 404) {
|
||||
this.showMessage('⚠️ 검토 API가 준비되지 않아 더미 데이터를 표시합니다.', 'warning');
|
||||
} else {
|
||||
@@ -233,7 +229,6 @@ class WorkReportReviewManager {
|
||||
this.updateTable();
|
||||
|
||||
} catch (error) {
|
||||
console.log('⚠️ 네트워크 오류로 더미 데이터 사용:', error.message);
|
||||
// 더미 데이터로 대체
|
||||
this.reports = this.generateDummyData();
|
||||
this.validateWorkHours();
|
||||
|
||||
@@ -499,14 +499,13 @@ async function saveEditedWork(workId) {
|
||||
body: JSON.stringify(updateData)
|
||||
});
|
||||
|
||||
console.log('✅ 수정 성공 (통합 API):', result);
|
||||
showMessage('✅ 작업이 성공적으로 수정되었습니다!', 'success');
|
||||
|
||||
closeEditModal();
|
||||
refreshCurrentDay(); // 현재 날짜 데이터 새로고침
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 수정 실패:', error);
|
||||
console.error(' 수정 실패:', error);
|
||||
showMessage('수정 중 오류가 발생했습니다: ' + error.message, 'error');
|
||||
|
||||
// 버튼 복원
|
||||
@@ -538,13 +537,12 @@ async function deleteWorkItem(workId) {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
console.log('✅ 삭제 성공 (통합 API):', result);
|
||||
showMessage('✅ 작업이 성공적으로 삭제되었습니다!', 'success');
|
||||
|
||||
refreshCurrentDay(); // 현재 날짜 데이터 새로고침
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 삭제 실패:', error);
|
||||
console.error(' 삭제 실패:', error);
|
||||
showMessage('삭제 중 오류가 발생했습니다: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
@@ -597,7 +595,7 @@ async function deleteWorkerAllWorks(date, workerName) {
|
||||
refreshCurrentDay(); // 현재 날짜 데이터 새로고침
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 전체 삭제 실패:', error);
|
||||
console.error(' 전체 삭제 실패:', error);
|
||||
showMessage('작업 삭제 중 오류가 발생했습니다: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
@@ -638,7 +636,6 @@ function showConfirmDialog(title, message, warning) {
|
||||
// 기본 데이터 로드 (통합 API 사용)
|
||||
async function loadBasicData() {
|
||||
try {
|
||||
console.log('🔗 통합 API 설정을 사용한 기본 데이터 로딩...');
|
||||
|
||||
const promises = [
|
||||
// 활성 프로젝트 로드
|
||||
@@ -695,7 +692,6 @@ async function loadBasicData() {
|
||||
errorTypes
|
||||
};
|
||||
|
||||
console.log('✅ 기본 데이터 로드 완료 (통합 API):', basicData);
|
||||
} catch (error) {
|
||||
console.error('기본 데이터 로드 실패:', error);
|
||||
}
|
||||
@@ -764,7 +760,6 @@ async function init() {
|
||||
// 기본 데이터 미리 로드
|
||||
await loadBasicData();
|
||||
|
||||
console.log('✅ 검토 페이지 초기화 완료 (통합 API 설정 적용)');
|
||||
|
||||
} catch (error) {
|
||||
console.error('초기화 오류:', error);
|
||||
|
||||
@@ -70,7 +70,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
}
|
||||
|
||||
if (!window.apiCall) {
|
||||
console.error('❌ API 함수를 로드할 수 없습니다.');
|
||||
console.error(' API 함수를 로드할 수 없습니다.');
|
||||
showMessage('시스템을 초기화할 수 없습니다. 페이지를 새로고침해주세요.', 'error');
|
||||
return;
|
||||
}
|
||||
@@ -84,7 +84,6 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
});
|
||||
|
||||
async function initializePage() {
|
||||
console.log('🚀 개별 작업 보고서 페이지 초기화 시작');
|
||||
|
||||
// URL 파라미터 추출
|
||||
const params = getUrlParams();
|
||||
@@ -112,7 +111,6 @@ async function initializePage() {
|
||||
// 초기 데이터 로드
|
||||
await loadInitialData();
|
||||
|
||||
console.log('✅ 개별 작업 보고서 페이지 초기화 완료');
|
||||
}
|
||||
|
||||
function updatePageHeader() {
|
||||
@@ -191,7 +189,6 @@ async function loadExistingWork() {
|
||||
try {
|
||||
const response = await window.apiCall(`/daily-work-reports?date=${selectedDate}&user_id=${currentWorkerId}`);
|
||||
existingWork = Array.isArray(response) ? response : (response.data || []);
|
||||
console.log(`✅ 기존 작업 ${existingWork.length}건 로드 완료`);
|
||||
} catch (error) {
|
||||
console.error('기존 작업 로드 오류:', error);
|
||||
existingWork = [];
|
||||
|
||||
@@ -9,7 +9,6 @@ let currentEditingWorker = null;
|
||||
|
||||
// 페이지 초기화
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
console.log('👥 작업자 관리 페이지 초기화 시작');
|
||||
await waitForApi();
|
||||
await loadDepartments();
|
||||
});
|
||||
@@ -30,7 +29,6 @@ async function loadDepartments() {
|
||||
departments = result.data;
|
||||
renderDepartmentList();
|
||||
updateParentDepartmentSelect();
|
||||
console.log('✅ 부서 목록 로드 완료:', departments.length + '개');
|
||||
} else if (Array.isArray(result)) {
|
||||
departments = result;
|
||||
renderDepartmentList();
|
||||
@@ -268,7 +266,6 @@ async function loadWorkersByDepartment(departmentId) {
|
||||
allWorkers = response.data;
|
||||
filteredWorkers = [...allWorkers];
|
||||
renderWorkerList();
|
||||
console.log(`✅ ${departmentId} 부서 작업자 로드: ${allWorkers.length}명`);
|
||||
} else if (Array.isArray(response)) {
|
||||
allWorkers = response;
|
||||
filteredWorkers = [...allWorkers];
|
||||
|
||||
@@ -22,7 +22,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('🏗️ 작업장 관리 페이지 초기화 시작 (레거시)');
|
||||
loadAllData();
|
||||
});
|
||||
|
||||
@@ -70,7 +69,6 @@ async function loadCategories() {
|
||||
}
|
||||
|
||||
categories = categoryData;
|
||||
console.log(`✅ 카테고리 ${categories.length}개 로드 완료`);
|
||||
} catch (error) {
|
||||
console.error('카테고리 로딩 오류:', error);
|
||||
categories = [];
|
||||
@@ -244,7 +242,6 @@ async function loadImageWithRegions(imageUrl, categoryId) {
|
||||
});
|
||||
|
||||
if (regions.length > 0) {
|
||||
console.log(`✅ 레이아웃 미리보기에 ${regions.length}개 영역 표시 완료`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('영역 로드 오류:', error);
|
||||
@@ -510,7 +507,6 @@ async function saveCategory() {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('💾 저장할 카테고리 데이터:', categoryData);
|
||||
|
||||
let response;
|
||||
if (categoryId) {
|
||||
@@ -576,7 +572,6 @@ async function loadWorkplaces() {
|
||||
}
|
||||
|
||||
workplaces = workplaceData;
|
||||
console.log(`✅ 작업장 ${workplaces.length}개 로드 완료`);
|
||||
} catch (error) {
|
||||
console.error('작업장 로딩 오류:', error);
|
||||
workplaces = [];
|
||||
@@ -758,7 +753,6 @@ async function saveWorkplace() {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('💾 저장할 작업장 데이터:', workplaceData);
|
||||
|
||||
let response;
|
||||
if (workplaceId) {
|
||||
@@ -1018,7 +1012,6 @@ async function loadWorkplaceEquipments(workplaceId) {
|
||||
// 해당 작업장에 할당된 설비 목록 저장
|
||||
existingEquipments = equipments;
|
||||
|
||||
console.log(`✅ 작업장 ${workplaceId}의 설비 ${equipments.length}개 로드 완료 (지도 영역: ${workplaceEquipmentRegions.length}개)`);
|
||||
} catch (error) {
|
||||
console.error('설비 로드 오류:', error);
|
||||
workplaceEquipmentRegions = [];
|
||||
@@ -1039,7 +1032,6 @@ async function loadAllEquipments() {
|
||||
}
|
||||
|
||||
allEquipments = equipments;
|
||||
console.log(`✅ 전체 설비 ${allEquipments.length}개 로드 완료`);
|
||||
} catch (error) {
|
||||
console.error('전체 설비 로드 오류:', error);
|
||||
allEquipments = [];
|
||||
@@ -1442,11 +1434,10 @@ function renderWorkplaceEquipmentList() {
|
||||
function updateEquipmentSelectDropdown() {
|
||||
const selectEl = document.getElementById('equipmentSelectInput');
|
||||
if (!selectEl) {
|
||||
console.warn('⚠️ equipmentSelectInput 요소를 찾을 수 없습니다');
|
||||
console.warn(' equipmentSelectInput 요소를 찾을 수 없습니다');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`📋 드롭다운 업데이트: 전체 설비 ${allEquipments.length}개`);
|
||||
|
||||
// 전체 설비 중에서 아직 어떤 지도에도 배치되지 않은 설비만 표시
|
||||
// map_x_percent가 null이면 지도에 배치되지 않은 설비
|
||||
@@ -1454,13 +1445,11 @@ function updateEquipmentSelectDropdown() {
|
||||
eq.map_x_percent == null || eq.map_x_percent === ''
|
||||
);
|
||||
|
||||
console.log(`📋 지도에 미배치된 설비: ${availableEquipments.length}개`);
|
||||
|
||||
// 이 작업장에 이미 배치된 설비는 제외 (현재 작업장에서 방금 배치한 경우)
|
||||
const registeredIds = workplaceEquipmentRegions.map(r => r.equipment_id);
|
||||
const unregisteredEquipments = availableEquipments.filter(eq => !registeredIds.includes(eq.equipment_id));
|
||||
|
||||
console.log(`📋 선택 가능한 설비: ${unregisteredEquipments.length}개`);
|
||||
|
||||
let options = '<option value="">-- 기존 설비 선택 --</option>';
|
||||
|
||||
@@ -1710,7 +1699,6 @@ function initFullscreenCanvas(imageUrl) {
|
||||
fsCanvas.ontouchmove = handleFsTouchMove;
|
||||
fsCanvas.ontouchend = endFsDraw;
|
||||
|
||||
console.log('✅ 전체화면 캔버스 초기화 완료');
|
||||
};
|
||||
|
||||
img.onerror = function() {
|
||||
|
||||
@@ -19,7 +19,6 @@ class WorkplaceAPI {
|
||||
this.loadCategories(),
|
||||
this.loadWorkplaces()
|
||||
]);
|
||||
console.log('✅ 모든 데이터 로드 완료');
|
||||
} catch (error) {
|
||||
console.error('데이터 로딩 오류:', error);
|
||||
window.showToast?.('데이터를 불러오는데 실패했습니다.', 'error');
|
||||
@@ -41,7 +40,6 @@ class WorkplaceAPI {
|
||||
}
|
||||
|
||||
this.state.categories = categoryData;
|
||||
console.log(`✅ 카테고리 ${this.state.categories.length}개 로드 완료`);
|
||||
return categoryData;
|
||||
} catch (error) {
|
||||
console.error('카테고리 로딩 오류:', error);
|
||||
@@ -103,7 +101,6 @@ class WorkplaceAPI {
|
||||
}
|
||||
|
||||
this.state.workplaces = workplaceData;
|
||||
console.log(`✅ 작업장 ${this.state.workplaces.length}개 로드 완료`);
|
||||
return workplaceData;
|
||||
} catch (error) {
|
||||
console.error('작업장 로딩 오류:', error);
|
||||
@@ -212,7 +209,6 @@ class WorkplaceAPI {
|
||||
|
||||
this.state.existingEquipments = equipments;
|
||||
|
||||
console.log(`✅ 작업장 ${workplaceId}의 설비 ${equipments.length}개 로드 완료`);
|
||||
return equipments;
|
||||
} catch (error) {
|
||||
console.error('설비 로드 오류:', error);
|
||||
@@ -237,7 +233,6 @@ class WorkplaceAPI {
|
||||
}
|
||||
|
||||
this.state.allEquipments = equipments;
|
||||
console.log(`✅ 전체 설비 ${this.state.allEquipments.length}개 로드 완료`);
|
||||
return equipments;
|
||||
} catch (error) {
|
||||
console.error('전체 설비 로드 오류:', error);
|
||||
|
||||
@@ -28,7 +28,6 @@ class WorkplaceController {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('🏗️ 작업장 관리 페이지 초기화 시작');
|
||||
|
||||
// API 함수가 로드될 때까지 대기
|
||||
let retryCount = 0;
|
||||
@@ -216,7 +215,6 @@ class WorkplaceController {
|
||||
});
|
||||
|
||||
if (regions.length > 0) {
|
||||
console.log(`✅ 레이아웃 미리보기에 ${regions.length}개 영역 표시 완료`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('영역 로드 오류:', error);
|
||||
|
||||
Reference in New Issue
Block a user