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:
Hyungi Ahn
2026-03-06 23:18:09 +09:00
parent ec755ed52f
commit 61c810bd47
63 changed files with 255 additions and 1357 deletions

View File

@@ -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 = '/';

View File

@@ -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();
}

View File

@@ -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();
},

View File

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

View File

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

View File

@@ -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) 제거.

View File

@@ -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');
});

View File

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

View File

@@ -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에서 사용)

View File

@@ -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>';
}
});

View File

@@ -92,7 +92,6 @@ async function initializeSections() {
// 5. 모든 수정이 완료된 HTML을 실제 DOM에 한 번에 삽입
mainContainer.innerHTML = doc.body.innerHTML;
console.log(`${currentUser.role} 역할의 섹션 로딩 완료.`);
} catch (error) {
console.error('섹션 로딩 중 오류 발생:', error);

View File

@@ -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>';
}
});

View File

@@ -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();

View File

@@ -117,6 +117,5 @@ function showError(message) {
// 페이지 로드 시 실행
document.addEventListener('DOMContentLoaded', () => {
console.log('👤 프로필 페이지 로드됨');
loadProfile();
});

View File

@@ -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();

View File

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

View File

@@ -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 로드 완료');

View File

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

View File

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

View File

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

View File

@@ -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');
}
}

View File

@@ -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 방지) ====================

View File

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

View File

@@ -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');
}
/**

View File

@@ -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');
}

View File

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

View File

@@ -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}`);
}
}

View File

@@ -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');
}

View File

@@ -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');
});

View File

@@ -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} 로딩에 실패했습니다. 관리자에게 문의하세요.`;
}
}

View File

@@ -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, '&quot;')}", "${safeImage.replace(/"/g, '&quot;')}")'>
<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, '&quot;')}")'>
<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, "&#39;")})' 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">

View File

@@ -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);
}

View File

@@ -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);
// 오류 시 기본값으로 빈 값 유지 (사용자가 직접 입력)
}
}

View File

@@ -2,7 +2,6 @@
// 그룹장 전용 대시보드 - 실시간 근태 및 작업 현황 (Real Data Version)
import { apiCall } from './api-config.js';
console.log('📊 그룹장 대시보드 스크립트 로딩 (Live Data)');
// 상태별 스타일/텍스트 매핑
const STATUS_MAP = {

View File

@@ -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 = '콘텐츠 로딩에 실패했습니다. 페이지를 새로고침해 주세요.';
}
}

View File

@@ -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();

View File

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

View File

@@ -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 => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m])));
tbody.innerHTML = `<tr><td colspan="7" class="error-cell">${safeMsg}</td></tr>`;
// 통계 초기화
document.getElementById('totalHours').textContent = '-';

View File

@@ -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('✅ 나의 대시보드 초기화 완료');
});
// 사용자 정보 로드

View File

@@ -117,6 +117,5 @@ function showError(message) {
// 페이지 로드 시 실행
document.addEventListener('DOMContentLoaded', () => {
console.log('👤 프로필 페이지 로드됨');
loadProfile();
});

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

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

View File

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

View File

@@ -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');
}
}

View File

@@ -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">&#10003;</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');
}
}

View File

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

View File

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

View File

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

View File

@@ -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);
}
});
}

View File

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

View File

@@ -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());

View File

@@ -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('🔗 하위 호환성 함수 설정 완료');
}
/**

View File

@@ -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()));
}
}

View File

@@ -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('✅ 기간별 작업 현황 테이블 렌더링 완료');
}
/**

View File

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

View File

@@ -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();

View File

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

View File

@@ -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 = [];

View File

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

View File

@@ -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() {

View File

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

View File

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