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

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