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:
@@ -1,7 +1,7 @@
|
||||
// daily-work-report.js - 브라우저 호환 버전
|
||||
|
||||
// =================================================================
|
||||
// 🌐 API 설정 (window 객체에서 가져오기)
|
||||
// API 설정 (window 객체에서 가져오기)
|
||||
// =================================================================
|
||||
// API 설정은 api-config.js에서 window 객체에 설정됨
|
||||
|
||||
@@ -183,7 +183,7 @@ function formatDateForApi(date) {
|
||||
*/
|
||||
function getUser() {
|
||||
if (window.getSSOUser) return window.getSSOUser();
|
||||
const raw = localStorage.getItem('sso_user') || localStorage.getItem('user');
|
||||
const raw = localStorage.getItem('sso_user');
|
||||
try { return raw ? JSON.parse(raw) : null; } catch(e) { return null; }
|
||||
}
|
||||
|
||||
@@ -233,7 +233,7 @@ function renderTbmWorkList() {
|
||||
<div style="margin-bottom: 1rem; display: flex; justify-content: space-between; align-items: center;">
|
||||
<h3 style="margin: 0;">작업보고서 목록</h3>
|
||||
<button type="button" class="btn-add-work" onclick="addManualWorkRow()">
|
||||
➕ 작업 추가
|
||||
작업 추가
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
@@ -247,7 +247,7 @@ function renderTbmWorkList() {
|
||||
<span class="tbm-session-info" style="color: white; font-weight: 500;">TBM에 없는 작업을 추가로 입력할 수 있습니다</span>
|
||||
</div>
|
||||
<button type="button" class="btn-batch-submit" onclick="submitAllManualWorkReports()" style="background: #fff; color: #d97706; border: none; padding: 0.4rem 0.8rem; border-radius: 4px; font-weight: 600; cursor: pointer; font-size: 0.8rem;">
|
||||
📤 일괄 제출
|
||||
일괄 제출
|
||||
</button>
|
||||
</div>
|
||||
<div class="tbm-table-container">
|
||||
@@ -326,7 +326,7 @@ function renderTbmWorkList() {
|
||||
html += `
|
||||
<div class="issue-reminder-section">
|
||||
<div class="issue-reminder-header">
|
||||
<span class="issue-reminder-icon">⚠️</span>
|
||||
<span class="issue-reminder-icon"></span>
|
||||
<span class="issue-reminder-title">당일 신고된 문제</span>
|
||||
<span class="issue-reminder-count">${relatedIssues.length}건</span>
|
||||
</div>
|
||||
@@ -349,7 +349,7 @@ function renderTbmWorkList() {
|
||||
${relatedIssues.length > 5 ? `<div class="issue-reminder-more">외 ${relatedIssues.length - 5}건 더 있음</div>` : ''}
|
||||
</div>
|
||||
<div class="issue-reminder-hint">
|
||||
💡 위 문제로 인해 작업이 지연되었다면, 아래에서 부적합 시간을 추가해주세요.
|
||||
위 문제로 인해 작업이 지연되었다면, 아래에서 부적합 시간을 추가해주세요.
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -474,7 +474,7 @@ function renderTbmWorkList() {
|
||||
<button type="button"
|
||||
class="btn-batch-submit"
|
||||
onclick="batchSubmitTbmSession('${key}')">
|
||||
📤 이 세션 일괄제출 (${group.items.length}건)
|
||||
이 세션 일괄제출 (${group.items.length}건)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -572,7 +572,7 @@ window.submitTbmWorkReport = async function(index) {
|
||||
}
|
||||
|
||||
// 부적합 원인 유효성 검사 (issue_report_id 또는 category_id 또는 error_type_id 필요)
|
||||
console.log('🔍 부적합 검증 시작:', defects.map(d => ({
|
||||
console.log(' 부적합 검증 시작:', defects.map(d => ({
|
||||
defect_hours: d.defect_hours,
|
||||
category_id: d.category_id,
|
||||
item_id: d.item_id,
|
||||
@@ -583,7 +583,7 @@ window.submitTbmWorkReport = async function(index) {
|
||||
|
||||
const invalidDefects = defects.filter(d => d.defect_hours > 0 && !d.error_type_id && !d.issue_report_id && !d.category_id && !d.item_id);
|
||||
if (invalidDefects.length > 0) {
|
||||
console.error('❌ 유효하지 않은 부적합:', invalidDefects);
|
||||
console.error(' 유효하지 않은 부적합:', invalidDefects);
|
||||
showMessage('부적합 시간이 있는 항목은 원인을 선택해주세요.', 'error');
|
||||
return;
|
||||
}
|
||||
@@ -610,8 +610,6 @@ window.submitTbmWorkReport = async function(index) {
|
||||
work_status_id: errorHours > 0 ? 2 : 1
|
||||
};
|
||||
|
||||
console.log('🔍 TBM 제출 데이터:', JSON.stringify(reportData, null, 2));
|
||||
console.log('🔍 부적합 원인:', defects);
|
||||
|
||||
try {
|
||||
const response = await window.apiCall('/daily-work-reports/from-tbm', 'POST', reportData);
|
||||
@@ -623,7 +621,7 @@ window.submitTbmWorkReport = async function(index) {
|
||||
// 부적합 원인이 있으면 저장 (이슈 기반 또는 레거시)
|
||||
if (defects.length > 0 && response.data?.report_id) {
|
||||
const validDefects = defects.filter(d => (d.issue_report_id || d.category_id || d.item_id || d.error_type_id) && d.defect_hours > 0);
|
||||
console.log('📋 부적합 원인 필터링:', {
|
||||
console.log(' 부적합 원인 필터링:', {
|
||||
전체: defects.length,
|
||||
유효: validDefects.length,
|
||||
validDefects: validDefects.map(d => ({
|
||||
@@ -645,20 +643,17 @@ window.submitTbmWorkReport = async function(index) {
|
||||
note: d.note || ''
|
||||
}));
|
||||
|
||||
console.log('📤 부적합 저장 요청:', defectsToSend);
|
||||
|
||||
const defectResponse = await window.apiCall(`/daily-work-reports/${response.data.report_id}/defects`, 'PUT', {
|
||||
defects: defectsToSend
|
||||
});
|
||||
|
||||
if (!defectResponse.success) {
|
||||
console.error('❌ 부적합 저장 실패:', defectResponse);
|
||||
console.error(' 부적합 저장 실패:', defectResponse);
|
||||
showMessage('작업보고서는 저장되었으나 부적합 원인 저장에 실패했습니다.', 'warning');
|
||||
} else {
|
||||
console.log('✅ 부적합 저장 성공:', defectResponse);
|
||||
}
|
||||
} else {
|
||||
console.log('⚠️ 유효한 부적합 항목이 없어 저장 건너뜀');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -808,7 +803,7 @@ window.batchSubmitTbmSession = async function(sessionKey) {
|
||||
'success',
|
||||
'일괄제출 완료',
|
||||
`${totalCount}건의 작업보고서가 모두 성공적으로 제출되었습니다.`,
|
||||
results.success.map(name => `✓ ${name}`)
|
||||
results.success.map(name => ` ${name}`)
|
||||
);
|
||||
} else if (successCount === 0) {
|
||||
// 모두 실패
|
||||
@@ -816,13 +811,13 @@ window.batchSubmitTbmSession = async function(sessionKey) {
|
||||
'error',
|
||||
'일괄제출 실패',
|
||||
`${totalCount}건의 작업보고서가 모두 실패했습니다.`,
|
||||
results.failed.map(msg => `✗ ${msg}`)
|
||||
results.failed.map(msg => ` ${msg}`)
|
||||
);
|
||||
} else {
|
||||
// 일부 성공, 일부 실패
|
||||
const details = [
|
||||
...results.success.map(name => `✓ ${name} - 성공`),
|
||||
...results.failed.map(msg => `✗ ${msg}`)
|
||||
...results.success.map(name => ` ${name} - 성공`),
|
||||
...results.failed.map(msg => ` ${msg}`)
|
||||
];
|
||||
showSaveResultModal(
|
||||
'warning',
|
||||
@@ -840,7 +835,7 @@ window.batchSubmitTbmSession = async function(sessionKey) {
|
||||
} finally {
|
||||
submitBtn.classList.remove('is-loading');
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = `📤 이 세션 일괄제출 (${sessionRows.length}건)`;
|
||||
submitBtn.textContent = ` 이 세션 일괄제출 (${sessionRows.length}건)`;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -892,7 +887,7 @@ window.addManualWorkRow = function() {
|
||||
<input type="hidden" id="workplace_${manualIndex}">
|
||||
<div id="workplaceDisplay_${manualIndex}" class="workplace-select-box" style="display: flex; flex-direction: column; gap: 0.25rem; padding: 0.5rem; background: #f9fafb; border: 2px solid #e5e7eb; border-radius: 6px; min-height: 60px; cursor: pointer;" onclick="openWorkplaceMapForManual('${manualIndex}')">
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.75rem; color: #6b7280; font-weight: 500;">
|
||||
<span>🗺️</span>
|
||||
<span></span>
|
||||
<span>작업장소</span>
|
||||
</div>
|
||||
<div id="workplaceText_${manualIndex}" style="font-size: 0.8rem; color: #9ca3af; font-style: italic;">
|
||||
@@ -923,7 +918,7 @@ window.addManualWorkRow = function() {
|
||||
제출
|
||||
</button>
|
||||
<button type="button" class="btn-delete-compact" onclick="removeManualWorkRow('${manualIndex}')" style="margin-left: 4px;">
|
||||
✕
|
||||
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
@@ -1049,13 +1044,13 @@ window.openWorkplaceMapForManual = async function(manualIndex) {
|
||||
const safeImage = escapeHtml(cat.layout_image || '');
|
||||
return `
|
||||
<button type="button" class="btn btn-secondary" style="width: 100%; text-align: left;" onclick='selectWorkplaceCategory(${safeId}, "${safeName.replace(/"/g, '"')}", "${safeImage.replace(/"/g, '"')}")'>
|
||||
<span style="margin-right: 0.5rem;">🏭</span>
|
||||
<span style="margin-right: 0.5rem;"></span>
|
||||
${safeName}
|
||||
</button>
|
||||
`;
|
||||
}).join('') + `
|
||||
<button type="button" class="btn btn-secondary" style="width: 100%; text-align: left; margin-top: 0.5rem; background-color: #f0f9ff; border-color: #0ea5e9;" onclick='selectExternalWorkplace()'>
|
||||
<span style="margin-right: 0.5rem;">🌐</span>
|
||||
<span style="margin-right: 0.5rem;"></span>
|
||||
외부 (외근/연차/휴무 등)
|
||||
</button>
|
||||
`;
|
||||
@@ -1107,7 +1102,7 @@ window.selectWorkplaceCategory = async function(categoryId, categoryName, layout
|
||||
const safeName = escapeHtml(wp.workplace_name);
|
||||
return `
|
||||
<button type="button" id="workplace-${safeId}" class="btn btn-secondary" style="width: 100%; text-align: left;" onclick='selectWorkplaceFromList(${safeId}, "${safeName.replace(/"/g, '"')}")'>
|
||||
<span style="margin-right: 0.5rem;">📍</span>
|
||||
<span style="margin-right: 0.5rem;"></span>
|
||||
${safeName}
|
||||
</button>
|
||||
`;
|
||||
@@ -1136,7 +1131,6 @@ async function loadWorkplaceMap(categoryId, layoutImagePath, workplaces) {
|
||||
? layoutImagePath
|
||||
: `${apiBaseUrl}${layoutImagePath}`;
|
||||
|
||||
console.log('🖼️ 이미지 로드 시도:', fullImageUrl);
|
||||
|
||||
// 지도 영역 데이터 로드
|
||||
const regionsResponse = await window.apiCall(`/workplaces/categories/${categoryId}/map-regions`);
|
||||
@@ -1164,11 +1158,10 @@ async function loadWorkplaceMap(categoryId, layoutImagePath, workplaces) {
|
||||
// 클릭 이벤트 리스너 추가
|
||||
mapCanvas.onclick = handleMapClick;
|
||||
|
||||
console.log(`✅ 작업장 지도 로드 완료: ${mapRegions.length}개 영역`);
|
||||
};
|
||||
|
||||
mapImage.onerror = function() {
|
||||
console.error('❌ 지도 이미지 로드 실패');
|
||||
console.error(' 지도 이미지 로드 실패');
|
||||
document.getElementById('layoutMapArea').style.display = 'none';
|
||||
showMessage('지도를 불러올 수 없어 리스트로 표시합니다.', 'warning');
|
||||
};
|
||||
@@ -1176,7 +1169,7 @@ async function loadWorkplaceMap(categoryId, layoutImagePath, workplaces) {
|
||||
mapImage.src = fullImageUrl;
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 작업장 지도 로드 오류:', error);
|
||||
console.error(' 작업장 지도 로드 오류:', error);
|
||||
document.getElementById('layoutMapArea').style.display = 'none';
|
||||
}
|
||||
}
|
||||
@@ -1301,12 +1294,12 @@ window.confirmWorkplaceSelection = function() {
|
||||
if (displayDiv) {
|
||||
displayDiv.innerHTML = `
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.75rem; color: #059669; font-weight: 600;">
|
||||
<span>✓</span>
|
||||
<span></span>
|
||||
<span>작업장소 선택됨</span>
|
||||
</div>
|
||||
<div style="font-size: 0.8rem; color: #111827; font-weight: 500;">
|
||||
<div style="color: #6b7280; font-size: 0.7rem; margin-bottom: 2px;">🏭 ${escapeHtml(selectedWorkplaceCategoryName)}</div>
|
||||
<div>📍 ${escapeHtml(selectedWorkplaceName)}</div>
|
||||
<div style="color: #6b7280; font-size: 0.7rem; margin-bottom: 2px;"> ${escapeHtml(selectedWorkplaceCategoryName)}</div>
|
||||
<div> ${escapeHtml(selectedWorkplaceName)}</div>
|
||||
</div>
|
||||
`;
|
||||
displayDiv.style.background = '#ecfdf5';
|
||||
@@ -1354,11 +1347,11 @@ window.selectExternalWorkplace = function() {
|
||||
if (displayDiv) {
|
||||
displayDiv.innerHTML = `
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.75rem; color: #0284c7; font-weight: 600;">
|
||||
<span>✓</span>
|
||||
<span></span>
|
||||
<span>외부 선택됨</span>
|
||||
</div>
|
||||
<div style="font-size: 0.8rem; color: #111827; font-weight: 500;">
|
||||
<div>🌐 ${escapeHtml(externalWorkplaceName)}</div>
|
||||
<div> ${escapeHtml(externalWorkplaceName)}</div>
|
||||
</div>
|
||||
`;
|
||||
displayDiv.style.background = '#f0f9ff';
|
||||
@@ -1778,10 +1771,10 @@ function renderCompletedReports(reports) {
|
||||
</div>
|
||||
<div class="report-actions" style="display: flex; gap: 0.5rem; margin-top: 0.75rem; padding-top: 0.75rem; border-top: 1px solid #e5e7eb;">
|
||||
<button type="button" class="btn btn-sm btn-secondary" onclick='openEditReportModal(${JSON.stringify(report).replace(/'/g, "'")})' style="flex: 1; padding: 0.4rem 0.6rem; font-size: 0.75rem;">
|
||||
✏️ 수정
|
||||
수정
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-danger" onclick="deleteWorkReport(${report.id})" style="flex: 1; padding: 0.4rem 0.6rem; font-size: 0.75rem; background: #fee2e2; color: #dc2626; border: 1px solid #fecaca;">
|
||||
🗑️ 삭제
|
||||
삭제
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1997,7 +1990,7 @@ function getCurrentUser() {
|
||||
}
|
||||
|
||||
try {
|
||||
const token = window.getSSOToken ? window.getSSOToken() : (localStorage.getItem('sso_token') || localStorage.getItem('token'));
|
||||
const token = window.getSSOToken ? window.getSSOToken() : (localStorage.getItem('sso_token'));
|
||||
if (token) {
|
||||
const payloadBase64 = token.split('.')[1];
|
||||
if (payloadBase64) {
|
||||
@@ -2009,7 +2002,7 @@ function getCurrentUser() {
|
||||
}
|
||||
|
||||
try {
|
||||
const userInfo = localStorage.getItem('sso_user') || localStorage.getItem('user') || localStorage.getItem('userInfo');
|
||||
const userInfo = localStorage.getItem('sso_user');
|
||||
if (userInfo) return JSON.parse(userInfo);
|
||||
} catch (error) {
|
||||
console.log('localStorage에서 사용자 정보 가져오기 실패:', error);
|
||||
@@ -2044,16 +2037,16 @@ function showSaveResultModal(type, title, message, details = null) {
|
||||
let icon = '';
|
||||
switch (type) {
|
||||
case 'success':
|
||||
icon = '✅';
|
||||
icon = '';
|
||||
break;
|
||||
case 'error':
|
||||
icon = '❌';
|
||||
icon = '';
|
||||
break;
|
||||
case 'warning':
|
||||
icon = '⚠️';
|
||||
icon = '';
|
||||
break;
|
||||
default:
|
||||
icon = 'ℹ️';
|
||||
icon = 'ℹ';
|
||||
}
|
||||
|
||||
// 모달 내용 구성
|
||||
@@ -2157,7 +2150,6 @@ async function loadData() {
|
||||
try {
|
||||
showMessage('데이터를 불러오는 중...', 'loading');
|
||||
|
||||
console.log('🔗 통합 API 설정을 사용한 기본 데이터 로딩 시작...');
|
||||
await loadWorkers();
|
||||
await loadProjects();
|
||||
await loadWorkTypes();
|
||||
@@ -2190,8 +2182,6 @@ async function loadWorkers() {
|
||||
return notResigned;
|
||||
});
|
||||
|
||||
console.log(`✅ Workers 로드 성공: ${workers.length}명 (전체: ${allWorkers.length}명)`);
|
||||
console.log(`📊 필터링 조건: employment_status≠resigned (퇴사자만 제외)`);
|
||||
} catch (error) {
|
||||
console.error('작업자 로딩 오류:', error);
|
||||
throw error;
|
||||
@@ -2203,7 +2193,6 @@ async function loadProjects() {
|
||||
console.log('Projects API 호출 중... (활성 프로젝트만)');
|
||||
const data = await window.apiCall(`/projects/active/list`);
|
||||
projects = Array.isArray(data) ? data : (data.data || data.projects || []);
|
||||
console.log('✅ 활성 프로젝트 로드 성공:', projects.length);
|
||||
} catch (error) {
|
||||
console.error('프로젝트 로딩 오류:', error);
|
||||
throw error;
|
||||
@@ -2216,12 +2205,10 @@ async function loadWorkTypes() {
|
||||
const data = response.data || response;
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
workTypes = data;
|
||||
console.log('✅ 작업 유형 API 사용 (통합 설정):', workTypes.length + '개');
|
||||
return;
|
||||
}
|
||||
throw new Error('API 실패');
|
||||
} catch (error) {
|
||||
console.log('⚠️ 작업 유형 API 사용 불가, 기본값 사용:', error.message);
|
||||
workTypes = [
|
||||
{ id: 1, name: 'Base' },
|
||||
{ id: 2, name: 'Vessel' },
|
||||
@@ -2236,12 +2223,10 @@ async function loadWorkStatusTypes() {
|
||||
const data = response.data || response;
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
workStatusTypes = data;
|
||||
console.log('✅ 업무 상태 유형 API 사용 (통합 설정):', workStatusTypes.length + '개');
|
||||
return;
|
||||
}
|
||||
throw new Error('API 실패');
|
||||
} catch (error) {
|
||||
console.log('⚠️ 업무 상태 유형 API 사용 불가, 기본값 사용');
|
||||
workStatusTypes = [
|
||||
{ id: 1, name: '정규' },
|
||||
{ id: 2, name: '에러' }
|
||||
@@ -2266,7 +2251,6 @@ async function loadErrorTypes() {
|
||||
const catResponse = await window.apiCall('/work-issues/categories/type/nonconformity');
|
||||
if (catResponse && catResponse.success && Array.isArray(catResponse.data)) {
|
||||
issueCategories = catResponse.data;
|
||||
console.log(`✅ 부적합 카테고리 ${issueCategories.length}개 로드`);
|
||||
|
||||
// 모든 아이템 로드
|
||||
const itemResponse = await window.apiCall('/work-issues/items');
|
||||
@@ -2274,11 +2258,9 @@ async function loadErrorTypes() {
|
||||
// 부적합 카테고리의 아이템만 필터링
|
||||
const categoryIds = issueCategories.map(c => c.category_id);
|
||||
issueItems = itemResponse.data.filter(item => categoryIds.includes(item.category_id));
|
||||
console.log(`✅ 부적합 아이템 ${issueItems.length}개 로드`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('⚠️ 신고 카테고리 로드 실패:', error);
|
||||
issueCategories = [];
|
||||
issueItems = [];
|
||||
}
|
||||
@@ -2287,7 +2269,6 @@ async function loadErrorTypes() {
|
||||
// TBM 팀 구성 자동 불러오기
|
||||
async function loadTbmTeamForDate(date) {
|
||||
try {
|
||||
console.log('🛠️ TBM 팀 구성 조회 중:', date);
|
||||
const response = await window.apiCall(`/tbm/sessions/date/${date}`);
|
||||
|
||||
if (response && response.success && response.data && response.data.length > 0) {
|
||||
@@ -2300,16 +2281,14 @@ async function loadTbmTeamForDate(date) {
|
||||
const teamRes = await window.apiCall(`/tbm/sessions/${targetSession.session_id}/team`);
|
||||
if (teamRes && teamRes.success && teamRes.data) {
|
||||
const teamWorkerIds = teamRes.data.map(m => m.user_id);
|
||||
console.log(`✅ TBM 팀 구성 로드 성공: ${teamWorkerIds.length}명`);
|
||||
return teamWorkerIds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('ℹ️ 해당 날짜의 TBM 팀 구성이 없습니다.');
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.error('❌ TBM 팀 구성 조회 오류:', error);
|
||||
console.error(' TBM 팀 구성 조회 오류:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -2340,7 +2319,7 @@ async function populateWorkerGrid() {
|
||||
font-size: 0.875rem;
|
||||
`;
|
||||
infoDiv.innerHTML = `
|
||||
<strong>🛠️ TBM 팀 구성 자동 적용</strong><br>
|
||||
<strong> TBM 팀 구성 자동 적용</strong><br>
|
||||
오늘 TBM에서 구성된 팀원 ${tbmWorkerIds.length}명이 자동으로 선택되었습니다.
|
||||
`;
|
||||
grid.appendChild(infoDiv);
|
||||
@@ -2389,29 +2368,25 @@ function toggleWorkerSelection(workerId, btnElement) {
|
||||
|
||||
// 작업 항목 추가
|
||||
function addWorkEntry() {
|
||||
console.log('🔧 addWorkEntry 함수 호출됨');
|
||||
const container = document.getElementById('workEntriesList');
|
||||
console.log('🔧 컨테이너:', container);
|
||||
workEntryCounter++;
|
||||
console.log('🔧 작업 항목 카운터:', workEntryCounter);
|
||||
|
||||
const entryDiv = document.createElement('div');
|
||||
entryDiv.className = 'work-entry';
|
||||
entryDiv.dataset.id = workEntryCounter;
|
||||
console.log('🔧 생성된 작업 항목 div:', entryDiv);
|
||||
|
||||
entryDiv.innerHTML = `
|
||||
<div class="work-entry-header">
|
||||
<div class="work-entry-title">작업 항목 #${workEntryCounter}</div>
|
||||
<button type="button" class="remove-work-btn" onclick="event.stopPropagation(); removeWorkEntry(${workEntryCounter})" title="이 작업 삭제">
|
||||
🗑️ 삭제
|
||||
삭제
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="work-entry-grid">
|
||||
<div class="form-field-group">
|
||||
<div class="form-field-label">
|
||||
<span class="form-field-icon">🏗️</span>
|
||||
<span class="form-field-icon"></span>
|
||||
프로젝트
|
||||
</div>
|
||||
<select class="form-select project-select" required>
|
||||
@@ -2422,7 +2397,7 @@ function addWorkEntry() {
|
||||
|
||||
<div class="form-field-group">
|
||||
<div class="form-field-label">
|
||||
<span class="form-field-icon">⚙️</span>
|
||||
<span class="form-field-icon"></span>
|
||||
작업 유형
|
||||
</div>
|
||||
<select class="form-select work-type-select" required>
|
||||
@@ -2435,7 +2410,7 @@ function addWorkEntry() {
|
||||
<div class="work-entry-full">
|
||||
<div class="form-field-group">
|
||||
<div class="form-field-label">
|
||||
<span class="form-field-icon">📊</span>
|
||||
<span class="form-field-icon"></span>
|
||||
업무 상태
|
||||
</div>
|
||||
<select class="form-select work-status-select" required>
|
||||
@@ -2447,7 +2422,7 @@ function addWorkEntry() {
|
||||
|
||||
<div class="error-type-section work-entry-full">
|
||||
<div class="form-field-label">
|
||||
<span class="form-field-icon">⚠️</span>
|
||||
<span class="form-field-icon"></span>
|
||||
에러 유형
|
||||
</div>
|
||||
<select class="form-select error-type-select">
|
||||
@@ -2458,7 +2433,7 @@ function addWorkEntry() {
|
||||
|
||||
<div class="time-input-section work-entry-full">
|
||||
<div class="form-field-label">
|
||||
<span class="form-field-icon">⏰</span>
|
||||
<span class="form-field-icon"></span>
|
||||
작업 시간 (시간)
|
||||
</div>
|
||||
<input type="number" class="form-select time-input"
|
||||
@@ -2479,12 +2454,8 @@ function addWorkEntry() {
|
||||
`;
|
||||
|
||||
container.appendChild(entryDiv);
|
||||
console.log('🔧 작업 항목이 컨테이너에 추가됨');
|
||||
console.log('🔧 현재 컨테이너 내용:', container.innerHTML.length, '문자');
|
||||
console.log('🔧 현재 .work-entry 개수:', container.querySelectorAll('.work-entry').length);
|
||||
|
||||
setupWorkEntryEvents(entryDiv);
|
||||
console.log('🔧 이벤트 설정 완료');
|
||||
}
|
||||
|
||||
// 작업 항목 이벤트 설정
|
||||
@@ -2546,15 +2517,11 @@ function setupWorkEntryEvents(entryDiv) {
|
||||
|
||||
// 작업 항목 제거
|
||||
function removeWorkEntry(id) {
|
||||
console.log('🗑️ removeWorkEntry 호출됨, id:', id);
|
||||
const entry = document.querySelector(`.work-entry[data-id="${id}"]`);
|
||||
console.log('🗑️ 찾은 entry:', entry);
|
||||
if (entry) {
|
||||
entry.remove();
|
||||
updateTotalHours();
|
||||
console.log('✅ 작업 항목 삭제 완료');
|
||||
} else {
|
||||
console.log('❌ 작업 항목을 찾을 수 없음');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2573,7 +2540,7 @@ function updateTotalHours() {
|
||||
|
||||
if (total > 24) {
|
||||
display.style.background = 'linear-gradient(135deg, #e74c3c 0%, #c0392b 100%)';
|
||||
display.textContent += ' ⚠️ 24시간 초과';
|
||||
display.textContent += '24시간 초과';
|
||||
} else {
|
||||
display.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
|
||||
}
|
||||
@@ -2593,8 +2560,6 @@ async function saveWorkReport() {
|
||||
}
|
||||
|
||||
const entries = document.querySelectorAll('.work-entry');
|
||||
console.log('🔍 찾은 작업 항목들:', entries);
|
||||
console.log('🔍 작업 항목 개수:', entries.length);
|
||||
|
||||
if (entries.length === 0) {
|
||||
showSaveResultModal(
|
||||
@@ -2606,10 +2571,8 @@ async function saveWorkReport() {
|
||||
}
|
||||
|
||||
const newWorkEntries = [];
|
||||
console.log('🔍 작업 항목 수집 시작...');
|
||||
|
||||
for (const entry of entries) {
|
||||
console.log('🔍 작업 항목 처리 중:', entry);
|
||||
|
||||
const projectSelect = entry.querySelector('.project-select');
|
||||
const workTypeSelect = entry.querySelector('.work-type-select');
|
||||
@@ -2617,7 +2580,7 @@ async function saveWorkReport() {
|
||||
const errorTypeSelect = entry.querySelector('.error-type-select');
|
||||
const timeInput = entry.querySelector('.time-input');
|
||||
|
||||
console.log('🔍 선택된 요소들:', {
|
||||
console.log(' 선택된 요소들:', {
|
||||
projectSelect,
|
||||
workTypeSelect,
|
||||
workStatusSelect,
|
||||
@@ -2631,7 +2594,7 @@ async function saveWorkReport() {
|
||||
const errorTypeId = errorTypeSelect?.value;
|
||||
const workHours = timeInput?.value;
|
||||
|
||||
console.log('🔍 수집된 값들:', {
|
||||
console.log(' 수집된 값들:', {
|
||||
projectId,
|
||||
workTypeId,
|
||||
workStatusId,
|
||||
@@ -2665,8 +2628,7 @@ async function saveWorkReport() {
|
||||
work_hours: parseFloat(workHours)
|
||||
};
|
||||
|
||||
console.log('🔍 생성된 작업 항목:', workEntry);
|
||||
console.log('🔍 작업 항목 상세:', {
|
||||
console.log(' 작업 항목 상세:', {
|
||||
project_id: workEntry.project_id,
|
||||
work_type_id: workEntry.work_type_id,
|
||||
work_status_id: workEntry.work_status_id,
|
||||
@@ -2676,13 +2638,11 @@ async function saveWorkReport() {
|
||||
newWorkEntries.push(workEntry);
|
||||
}
|
||||
|
||||
console.log('🔍 최종 수집된 작업 항목들:', newWorkEntries);
|
||||
console.log('🔍 총 작업 항목 개수:', newWorkEntries.length);
|
||||
|
||||
try {
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = '💾 저장 중...';
|
||||
submitBtn.textContent = ' 저장 중...';
|
||||
|
||||
const currentUser = getCurrentUser();
|
||||
let totalSaved = 0;
|
||||
@@ -2706,18 +2666,13 @@ async function saveWorkReport() {
|
||||
created_by: currentUser?.user_id || currentUser?.id
|
||||
};
|
||||
|
||||
console.log('🔄 배열 형태로 전송:', requestData);
|
||||
console.log('🔄 work_entries:', requestData.work_entries);
|
||||
console.log('🔄 work_entries[0] 상세:', requestData.work_entries[0]);
|
||||
console.log('🔄 전송 데이터 JSON:', JSON.stringify(requestData, null, 2));
|
||||
|
||||
try {
|
||||
const result = await window.apiCall(`/daily-work-reports`, 'POST', requestData);
|
||||
|
||||
console.log('✅ 저장 성공:', result);
|
||||
totalSaved++;
|
||||
} catch (error) {
|
||||
console.error('❌ 저장 실패:', error);
|
||||
console.error(' 저장 실패:', error);
|
||||
totalFailed++;
|
||||
|
||||
failureDetails.push(`${workerName}: ${error.message}`);
|
||||
@@ -2765,7 +2720,7 @@ async function saveWorkReport() {
|
||||
} finally {
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = '💾 작업보고서 저장';
|
||||
submitBtn.textContent = ' 작업보고서 저장';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2801,7 +2756,7 @@ async function loadTodayWorkers() {
|
||||
const today = getKoreaToday();
|
||||
const currentUser = getCurrentUser();
|
||||
|
||||
content.innerHTML = '<div class="loading-spinner">📊 내가 입력한 오늘의 작업 현황을 불러오는 중... (통합 API)</div>';
|
||||
content.innerHTML = '<div class="loading-spinner"> 내가 입력한 오늘의 작업 현황을 불러오는 중... (통합 API)</div>';
|
||||
section.style.display = 'block';
|
||||
|
||||
// 본인이 입력한 데이터만 조회 (통합 API 사용)
|
||||
@@ -2812,10 +2767,8 @@ async function loadTodayWorkers() {
|
||||
queryParams += `&created_by=${currentUser.id}`;
|
||||
}
|
||||
|
||||
console.log(`🔒 본인 입력분만 조회 (통합 API): ${API}/daily-work-reports?${queryParams}`);
|
||||
|
||||
const rawData = await window.apiCall(`/daily-work-reports?${queryParams}`);
|
||||
console.log('📊 당일 작업 데이터 (통합 API):', rawData);
|
||||
|
||||
let data = [];
|
||||
if (Array.isArray(rawData)) {
|
||||
@@ -2830,7 +2783,7 @@ async function loadTodayWorkers() {
|
||||
console.error('당일 작업자 로드 오류:', error);
|
||||
content.innerHTML = `
|
||||
<div class="no-data-message">
|
||||
❌ 오늘의 작업 현황을 불러올 수 없습니다.<br>
|
||||
오늘의 작업 현황을 불러올 수 없습니다.<br>
|
||||
<small>${error.message}</small>
|
||||
</div>
|
||||
`;
|
||||
@@ -2844,7 +2797,7 @@ function displayMyDailyWorkers(data, date) {
|
||||
if (!Array.isArray(data) || data.length === 0) {
|
||||
content.innerHTML = `
|
||||
<div class="no-data-message">
|
||||
📝 내가 오늘(${date}) 입력한 작업이 없습니다.<br>
|
||||
내가 오늘(${date}) 입력한 작업이 없습니다.<br>
|
||||
<small>새로운 작업을 추가해보세요!</small>
|
||||
</div>
|
||||
`;
|
||||
@@ -2866,9 +2819,9 @@ function displayMyDailyWorkers(data, date) {
|
||||
|
||||
const headerHtml = `
|
||||
<div class="daily-workers-header">
|
||||
<h4>📊 내가 입력한 오늘(${escapeHtml(date)}) 작업 현황 - 총 ${parseInt(totalWorkers) || 0}명, ${parseInt(totalWorks) || 0}개 작업</h4>
|
||||
<h4> 내가 입력한 오늘(${escapeHtml(date)}) 작업 현황 - 총 ${parseInt(totalWorkers) || 0}명, ${parseInt(totalWorks) || 0}개 작업</h4>
|
||||
<button class="refresh-btn" onclick="refreshTodayWorkers()">
|
||||
🔄 새로고침
|
||||
새로고침
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
@@ -2891,34 +2844,34 @@ function displayMyDailyWorkers(data, date) {
|
||||
<div class="individual-work-item">
|
||||
<div class="work-details-grid">
|
||||
<div class="detail-item">
|
||||
<div class="detail-label">🏗️ 프로젝트</div>
|
||||
<div class="detail-label"> 프로젝트</div>
|
||||
<div class="detail-value">${projectName}</div>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<div class="detail-label">⚙️ 작업종류</div>
|
||||
<div class="detail-label"> 작업종류</div>
|
||||
<div class="detail-value">${workTypeName}</div>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<div class="detail-label">📊 작업상태</div>
|
||||
<div class="detail-label"> 작업상태</div>
|
||||
<div class="detail-value">${workStatusName}</div>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<div class="detail-label">⏰ 작업시간</div>
|
||||
<div class="detail-label"> 작업시간</div>
|
||||
<div class="detail-value">${workHours}시간</div>
|
||||
</div>
|
||||
${errorTypeName ? `
|
||||
<div class="detail-item">
|
||||
<div class="detail-label">❌ 에러유형</div>
|
||||
<div class="detail-label"> 에러유형</div>
|
||||
<div class="detail-value">${errorTypeName}</div>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<button class="edit-btn" onclick="editWorkItem('${workId}')">
|
||||
✏️ 수정
|
||||
수정
|
||||
</button>
|
||||
<button class="delete-btn" onclick="deleteWorkItem('${workId}')">
|
||||
🗑️ 삭제
|
||||
삭제
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2928,7 +2881,7 @@ function displayMyDailyWorkers(data, date) {
|
||||
return `
|
||||
<div class="worker-status-item">
|
||||
<div class="worker-header">
|
||||
<div class="worker-name">👤 ${escapeHtml(workerName)}</div>
|
||||
<div class="worker-name"> ${escapeHtml(workerName)}</div>
|
||||
<div class="worker-total-hours">총 ${parseFloat(totalHours)}시간</div>
|
||||
</div>
|
||||
<div class="individual-works-container">
|
||||
@@ -2970,12 +2923,12 @@ function showEditModal(workData) {
|
||||
<div class="edit-modal" id="editModal">
|
||||
<div class="edit-modal-content">
|
||||
<div class="edit-modal-header">
|
||||
<h3>✏️ 작업 수정</h3>
|
||||
<h3> 작업 수정</h3>
|
||||
<button class="close-modal-btn" onclick="closeEditModal()">×</button>
|
||||
</div>
|
||||
<div class="edit-modal-body">
|
||||
<div class="edit-form-group">
|
||||
<label>🏗️ 프로젝트</label>
|
||||
<label> 프로젝트</label>
|
||||
<select class="edit-select" id="editProject">
|
||||
<option value="">프로젝트 선택</option>
|
||||
${projects.map(p => `
|
||||
@@ -2987,7 +2940,7 @@ function showEditModal(workData) {
|
||||
</div>
|
||||
|
||||
<div class="edit-form-group">
|
||||
<label>⚙️ 작업 유형</label>
|
||||
<label> 작업 유형</label>
|
||||
<select class="edit-select" id="editWorkType">
|
||||
<option value="">작업 유형 선택</option>
|
||||
${workTypes.map(wt => `
|
||||
@@ -2999,7 +2952,7 @@ function showEditModal(workData) {
|
||||
</div>
|
||||
|
||||
<div class="edit-form-group">
|
||||
<label>📊 업무 상태</label>
|
||||
<label> 업무 상태</label>
|
||||
<select class="edit-select" id="editWorkStatus">
|
||||
<option value="">업무 상태 선택</option>
|
||||
${workStatusTypes.map(ws => `
|
||||
@@ -3011,7 +2964,7 @@ function showEditModal(workData) {
|
||||
</div>
|
||||
|
||||
<div class="edit-form-group" id="editErrorTypeGroup" style="${workData.work_status_id == 2 ? '' : 'display: none;'}">
|
||||
<label>❌ 에러 유형</label>
|
||||
<label> 에러 유형</label>
|
||||
<select class="edit-select" id="editErrorType">
|
||||
<option value="">에러 유형 선택</option>
|
||||
${errorTypes.map(et => `
|
||||
@@ -3023,7 +2976,7 @@ function showEditModal(workData) {
|
||||
</div>
|
||||
|
||||
<div class="edit-form-group">
|
||||
<label>⏰ 작업 시간</label>
|
||||
<label> 작업 시간</label>
|
||||
<input type="number" class="edit-input" id="editWorkHours"
|
||||
value="${workData.work_hours}"
|
||||
min="0" max="24" step="0.5">
|
||||
@@ -3031,7 +2984,7 @@ function showEditModal(workData) {
|
||||
</div>
|
||||
<div class="edit-modal-footer">
|
||||
<button class="btn btn-secondary" onclick="closeEditModal()">취소</button>
|
||||
<button class="btn btn-success" onclick="saveEditedWork()">💾 저장</button>
|
||||
<button class="btn btn-success" onclick="saveEditedWork()"> 저장</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -3093,14 +3046,13 @@ async function saveEditedWork() {
|
||||
body: JSON.stringify(updateData)
|
||||
});
|
||||
|
||||
console.log('✅ 수정 성공 (통합 API):', result);
|
||||
showMessage('✅ 작업이 성공적으로 수정되었습니다!', 'success');
|
||||
showMessage(' 작업이 성공적으로 수정되었습니다!', 'success');
|
||||
|
||||
closeEditModal();
|
||||
refreshTodayWorkers();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 수정 실패:', error);
|
||||
console.error(' 수정 실패:', error);
|
||||
showMessage('수정 중 오류가 발생했습니다: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
@@ -3121,14 +3073,13 @@ async function deleteWorkItem(workId) {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
console.log('✅ 삭제 성공 (통합 API):', result);
|
||||
showMessage('✅ 작업이 성공적으로 삭제되었습니다!', 'success');
|
||||
showMessage(' 작업이 성공적으로 삭제되었습니다!', 'success');
|
||||
|
||||
// 화면 새로고침
|
||||
refreshTodayWorkers();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 삭제 실패:', error);
|
||||
console.error(' 삭제 실패:', error);
|
||||
showMessage('삭제 중 오류가 발생했습니다: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
@@ -3164,7 +3115,6 @@ async function init() {
|
||||
// TBM 작업 목록 로드 (기본 탭)
|
||||
await loadIncompleteTbms();
|
||||
|
||||
console.log('✅ 시스템 초기화 완료 (통합 API 설정 적용)');
|
||||
|
||||
} catch (error) {
|
||||
console.error('초기화 오류:', error);
|
||||
@@ -3422,7 +3372,6 @@ function renderInlineDefectList(index) {
|
||||
|
||||
const defects = tempDefects[index] || [];
|
||||
|
||||
console.log(`📝 [renderInlineDefectList] index=${index}, 부적합 수=${defects.length}`, defects);
|
||||
|
||||
// 이슈가 있으면 이슈 선택 UI, 없으면 레거시 UI
|
||||
if (nonconformityIssues.length > 0) {
|
||||
@@ -3430,7 +3379,7 @@ function renderInlineDefectList(index) {
|
||||
let html = `
|
||||
<div class="defect-issue-section">
|
||||
<div class="defect-issue-header">
|
||||
<span class="defect-issue-title">📋 ${escapeHtml(workerWorkplaceName || '작업장소')} 관련 부적합</span>
|
||||
<span class="defect-issue-title"> ${escapeHtml(workerWorkplaceName || '작업장소')} 관련 부적합</span>
|
||||
<span class="defect-issue-count">${parseInt(nonconformityIssues.length) || 0}건</span>
|
||||
</div>
|
||||
<div class="defect-issue-list">
|
||||
|
||||
Reference in New Issue
Block a user