// ✅ modern-dashboard.js - 모던 대시보드 JavaScript
// API 설정 및 함수들은 api-config.js에서 로드됨
// window.API, window.apiCall, window.getAuthHeaders 사용
// 인증 관련 함수들
function getAuthData() {
const token = localStorage.getItem('token');
const user = localStorage.getItem('user');
return {
token,
user: user ? JSON.parse(user) : null
};
}
// 전역 변수
let currentUser = null;
let workersData = [];
let workData = [];
let selectedDate = new Date().toISOString().split('T')[0];
// 모달 관련 변수
let currentModalWorker = null;
let modalWorkTypes = [];
let modalWorkStatusTypes = [];
let modalErrorTypes = [];
let modalProjects = [];
let modalExistingWork = [];
// DOM 요소
const elements = {
currentTime: document.getElementById('currentTime'),
timeValue: document.getElementById('timeValue'),
userName: document.getElementById('userName'),
userRole: document.getElementById('userRole'),
userInitial: document.getElementById('userInitial'),
selectedDate: document.getElementById('selectedDate'),
refreshBtn: document.getElementById('refreshBtn'),
logoutBtn: document.getElementById('logoutBtn'),
// 요약 카드
todayWorkers: document.getElementById('todayWorkers'),
totalHours: document.getElementById('totalHours'),
activeProjects: document.getElementById('activeProjects'),
errorCount: document.getElementById('errorCount'),
// 컨테이너
workStatusContainer: document.getElementById('workStatusContainer'),
workersContainer: document.getElementById('workersContainer'),
toastContainer: document.getElementById('toastContainer')
};
// ========== 초기화 ========== //
document.addEventListener('DOMContentLoaded', async () => {
// API 함수가 로드될 때까지 기다림
let retryCount = 0;
const maxRetries = 50; // 5초 대기
while (!window.apiCall && retryCount < maxRetries) {
await new Promise(resolve => setTimeout(resolve, 100));
retryCount++;
}
if (!window.apiCall) {
console.error('❌ API 함수를 로드할 수 없습니다.');
showToast('시스템을 초기화할 수 없습니다. 페이지를 새로고침해주세요.', 'error');
return;
}
try {
await initializeDashboard();
} catch (error) {
console.error('대시보드 초기화 오류:', error);
showToast('대시보드를 불러오는 중 오류가 발생했습니다.', 'error');
}
});
async function initializeDashboard() {
console.log('🚀 모던 대시보드 초기화 시작');
// 사용자 정보 설정
setupUserInfo();
// 시간 업데이트 시작
updateCurrentTime();
setInterval(updateCurrentTime, 1000);
// 날짜 설정
elements.selectedDate.value = selectedDate;
// 이벤트 리스너 설정
setupEventListeners();
// 데이터 로드
await loadDashboardData();
// 관리자 권한 확인
checkAdminAccess();
console.log('✅ 모던 대시보드 초기화 완료');
}
// ========== 사용자 정보 설정 ========== //
function setupUserInfo() {
const authData = getAuthData();
if (authData && authData.user) {
currentUser = authData.user;
// 사용자 이름 설정
elements.userName.textContent = currentUser.name || currentUser.username;
// 사용자 역할 설정
const roleMap = {
'admin': '관리자',
'system': '시스템 관리자',
'group_leader': '그룹장',
'leader': '그룹장',
'user': '작업자'
};
elements.userRole.textContent = roleMap[currentUser.role] || '작업자';
// 아바타 초기값 설정
const initial = (currentUser.name || currentUser.username).charAt(0);
elements.userInitial.textContent = initial;
console.log('👤 사용자 정보 설정 완료:', currentUser.name);
}
}
// ========== 시간 업데이트 ========== //
function updateCurrentTime() {
const now = new Date();
const timeString = now.toLocaleTimeString('ko-KR', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
elements.timeValue.textContent = timeString;
}
// ========== 이벤트 리스너 ========== //
function setupEventListeners() {
// 날짜 변경
elements.selectedDate.addEventListener('change', (e) => {
selectedDate = e.target.value;
loadDashboardData();
});
// 새로고침 버튼
elements.refreshBtn.addEventListener('click', () => {
loadDashboardData();
showToast('데이터를 새로고침했습니다.', 'success');
});
// 로그아웃 버튼
elements.logoutBtn.addEventListener('click', () => {
if (confirm('로그아웃하시겠습니까?')) {
localStorage.clear();
window.location.href = '/index.html';
}
});
// 뷰 컨트롤 버튼들
const listViewBtn = document.getElementById('listViewBtn');
const cardViewBtn = document.getElementById('cardViewBtn');
if (listViewBtn) {
listViewBtn.addEventListener('click', () => {
displayWorkers(workersData, 'list');
updateViewButtons('list');
});
}
if (cardViewBtn) {
cardViewBtn.addEventListener('click', () => {
displayWorkers(workersData, 'card');
updateViewButtons('card');
});
}
}
// ========== 데이터 로드 ========== //
async function loadDashboardData() {
console.log('📊 대시보드 데이터 로딩 시작');
try {
// 로딩 상태 표시
showLoadingState();
// 병렬로 데이터 로드
const [workersResult, workResult] = await Promise.all([
loadWorkers(),
loadWorkData(selectedDate)
]);
// 요약 데이터 업데이트
updateSummaryCards();
// 작업 현황 표시
displayWorkStatus();
// 작업자 현황 표시
displayWorkers(workersData, 'card');
console.log('✅ 대시보드 데이터 로딩 완료');
} catch (error) {
console.error('❌ 대시보드 데이터 로딩 오류:', error);
showErrorState();
showToast('데이터를 불러오는 중 오류가 발생했습니다.', 'error');
}
}
async function loadWorkers() {
try {
console.log('👥 작업자 데이터 로딩...');
const response = await window.apiCall('/workers');
const allWorkers = Array.isArray(response) ? response : (response.data || []);
// 활성화된 작업자만 필터링
workersData = allWorkers.filter(worker => {
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);
workersData = [];
throw error;
}
}
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);
workData = [];
throw error;
}
}
// ========== 요약 카드 업데이트 ========== //
function updateSummaryCards() {
// 오늘 작업자 수
const todayWorkersCount = new Set(workData.map(w => w.worker_id)).size;
updateSummaryCard(elements.todayWorkers, todayWorkersCount, '명');
// 총 작업 시간
const totalHours = workData.reduce((sum, work) => sum + parseFloat(work.work_hours || 0), 0);
updateSummaryCard(elements.totalHours, totalHours.toFixed(1), '시간');
// 진행 중인 프로젝트
const activeProjectsCount = new Set(workData.map(w => w.project_id)).size;
updateSummaryCard(elements.activeProjects, activeProjectsCount, '개');
// 오류 발생 건수
const errorCount = workData.filter(w => w.work_status_id === 2).length;
updateSummaryCard(elements.errorCount, errorCount, '건');
}
function updateSummaryCard(element, value, unit) {
if (element) {
const numberElement = element.querySelector('.value-number');
const unitElement = element.querySelector('.value-unit');
if (numberElement) numberElement.textContent = value;
if (unitElement) unitElement.textContent = unit;
}
}
// ========== 작업 현황 표시 (작업자 중심) ========== //
function displayWorkStatus() {
if (!elements.workStatusContainer) return;
// 모든 작업자 데이터 가져오기 (작업이 없는 작업자도 포함)
const allWorkers = workersData || [];
if (allWorkers.length === 0) {
elements.workStatusContainer.innerHTML = `
👥
등록된 작업자가 없습니다
시스템에 작업자가 등록되어 있지 않습니다.
`;
return;
}
// 작업자별 상황 분석
const workerStatusList = allWorkers.map(worker => {
const todayWork = workData.filter(w => w.worker_id === worker.worker_id);
const totalHours = todayWork.reduce((sum, w) => sum + parseFloat(w.work_hours || 0), 0);
// 휴가/연차 제외한 실제 작업시간 계산
const actualWorkHours = todayWork
.filter(w => w.project_id !== 13) // 연차/휴무 프로젝트 제외
.reduce((sum, w) => sum + parseFloat(w.work_hours || 0), 0);
const hasError = todayWork.some(w => w.work_status_id === 2);
// 정규 작업과 에러 작업 건수 분리
const regularWorkCount = todayWork.filter(w => w.project_id !== 13 && w.work_status_id !== 2).length;
const errorWorkCount = todayWork.filter(w => w.project_id !== 13 && w.work_status_id === 2).length;
// 상태 판단 로직 (개선된 버전)
let status = 'incomplete';
let statusText = '미입력';
let statusBadge = '미입력';
let vacationType = null;
// 휴가 처리된 경우 확인 (프로젝트 ID 13 = "연차/휴무" 또는 설명에 휴가 키워드)
const hasVacationRecord = todayWork.some(w =>
w.project_id === 13 || // 연차/휴무 프로젝트
(w.description && (
w.description.includes('연차') ||
w.description.includes('반차') ||
w.description.includes('휴가')
))
);
// 연차/휴무 프로젝트의 시간 계산
const vacationHours = todayWork
.filter(w => w.project_id === 13)
.reduce((sum, w) => sum + parseFloat(w.work_hours || 0), 0);
if (totalHours > 12) {
status = 'overtime-warning';
statusText = '초과근무 확인필요';
statusBadge = '확인필요';
} else if (hasVacationRecord && vacationHours > 0) {
// 연차/휴무 시간에 따른 상태 결정
if (vacationHours === 8) {
status = 'vacation-full';
statusText = '연차';
statusBadge = '연차';
} else if (vacationHours === 6) {
status = 'vacation-half-half';
statusText = '조퇴';
statusBadge = '조퇴';
} else if (vacationHours === 4) {
status = 'vacation-half';
statusText = '반차';
statusBadge = '반차';
} else if (vacationHours === 2) {
status = 'vacation-quarter';
statusText = '반반차';
statusBadge = '반반차';
}
} else if (totalHours > 8) {
// 8시간 초과 - 연장근로
status = 'overtime';
statusText = '연장근로';
statusBadge = '연장근로';
} else if (totalHours === 8) {
// 정확히 8시간 - 정시근로
status = 'complete';
statusText = '정시근로';
statusBadge = '정시근로';
} else if (totalHours > 0) {
// 0시간 초과 8시간 미만 - 부분 입력
status = 'partial';
statusText = '부분 입력';
statusBadge = '부분입력';
// 휴가 처리 필요 여부 판단
if (totalHours === 0) {
vacationType = 'full';
} else if (totalHours === 4) {
vacationType = 'half';
} else if (totalHours === 6) {
vacationType = 'half-half'; // 2시간 더 추가해서 조퇴 처리
}
} else {
// 0시간 - 미입력
status = 'incomplete';
statusText = '미입력';
statusBadge = '미입력';
vacationType = 'full';
}
return {
...worker,
todayWork,
totalHours,
actualWorkHours,
regularWorkCount,
errorWorkCount,
hasError,
status,
statusText,
statusBadge,
vacationType
};
});
elements.workStatusContainer.innerHTML = `
${workerStatusList.map(worker => `
${worker.worker_name.charAt(0)}
${worker.worker_name}
${worker.job_type || '작업자'}
${worker.statusBadge}
작업시간
${worker.actualWorkHours.toFixed(1)}h
정규
${worker.regularWorkCount}건
${worker.errorWorkCount > 0 ? `
에러
${worker.errorWorkCount}건
` : ''}
${worker.vacationType ? `
` : ''}
${worker.status === 'overtime-warning' ? `
` : ''}
`).join('')}
`;
}
function groupWorkDataByProject() {
const groups = {};
workData.forEach(work => {
const projectName = work.project_name || '미지정 프로젝트';
if (!groups[projectName]) {
groups[projectName] = [];
}
groups[projectName].push(work);
});
return groups;
}
// ========== 작업자 현황 표시 ========== //
function displayWorkers(workers, viewType = 'card') {
if (!elements.workersContainer) return;
if (workers.length === 0) {
elements.workersContainer.innerHTML = `
👥
작업자 데이터가 없습니다
등록된 작업자가 없습니다.
`;
return;
}
if (viewType === 'list') {
displayWorkersAsList(workers);
} else {
displayWorkersAsCards(workers);
}
}
function displayWorkersAsCards(workers) {
elements.workersContainer.innerHTML = `
${workers.map(worker => {
const todayWork = workData.filter(w => w.worker_id === worker.worker_id);
const totalHours = todayWork.reduce((sum, w) => sum + parseFloat(w.work_hours || 0), 0);
// 휴가/연차 제외한 실제 작업시간 계산
const actualWorkHours = todayWork
.filter(w => w.project_id !== 13) // 연차/휴무 프로젝트 제외
.reduce((sum, w) => sum + parseFloat(w.work_hours || 0), 0);
const hasError = todayWork.some(w => w.work_status_id === 2);
// 정규 작업과 에러 작업 건수 분리
const regularWorkCount = todayWork.filter(w => w.project_id !== 13 && w.work_status_id !== 2).length;
const errorWorkCount = todayWork.filter(w => w.project_id !== 13 && w.work_status_id === 2).length;
return `
작업시간
${actualWorkHours.toFixed(1)}h
정규
${regularWorkCount}건
${errorWorkCount > 0 ? `
에러
${errorWorkCount}건
` : ''}
`;
}).join('')}
`;
}
function displayWorkersAsList(workers) {
elements.workersContainer.innerHTML = `
| 작업자 |
직종 |
오늘 작업 |
작업 시간 |
상태 |
${workers.map(worker => {
const todayWork = workData.filter(w => w.worker_id === worker.worker_id);
const totalHours = todayWork.reduce((sum, w) => sum + parseFloat(w.work_hours || 0), 0);
const hasError = todayWork.some(w => w.work_status_id === 2);
return `
${worker.worker_name.charAt(0)}
${worker.worker_name}
|
${worker.job_type || '작업자'} |
${todayWork.length}건 |
${totalHours.toFixed(1)}시간 |
${todayWork.length > 0 ? '작업 중' : '대기'}
${hasError ? '오류' : ''}
|
`;
}).join('')}
`;
}
// ========== 뷰 버튼 업데이트 ========== //
function updateViewButtons(activeView) {
const listBtn = document.getElementById('listViewBtn');
const cardBtn = document.getElementById('cardViewBtn');
if (listBtn && cardBtn) {
listBtn.classList.toggle('btn-primary', activeView === 'list');
listBtn.classList.toggle('btn-secondary', activeView !== 'list');
cardBtn.classList.toggle('btn-primary', activeView === 'card');
cardBtn.classList.toggle('btn-secondary', activeView !== 'card');
}
}
// ========== 관리자 권한 확인 ========== //
function checkAdminAccess() {
const adminElements = document.querySelectorAll('.admin-only');
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');
// 작업 분석: 전체 관리자만 접근 가능
if (href && href.includes('work-analysis.html')) {
if (isFullAdmin) {
element.style.display = '';
element.classList.add('visible');
} else {
element.style.display = 'none';
element.classList.remove('visible');
}
}
// 작업 관리: 전체 관리자 + 그룹 리더 접근 가능
else if (href && href.includes('work-management.html')) {
if (isFullAdmin || isGroupLeader) {
element.style.display = '';
element.classList.add('visible');
} else {
element.style.display = 'none';
element.classList.remove('visible');
}
}
// 기타 관리자 전용 메뉴: 전체 관리자만 접근 가능
else {
if (isFullAdmin) {
element.style.display = '';
element.classList.add('visible');
} else {
element.style.display = 'none';
element.classList.remove('visible');
}
}
});
}
// ========== 상태 표시 ========== //
function showLoadingState() {
const loadingHTML = `
`;
if (elements.workStatusContainer) {
elements.workStatusContainer.innerHTML = loadingHTML;
}
if (elements.workersContainer) {
elements.workersContainer.innerHTML = loadingHTML;
}
}
function showErrorState() {
const errorHTML = `
⚠️
데이터를 불러올 수 없습니다
네트워크 연결을 확인하고 다시 시도해주세요.
`;
if (elements.workStatusContainer) {
elements.workStatusContainer.innerHTML = errorHTML;
}
if (elements.workersContainer) {
elements.workersContainer.innerHTML = errorHTML;
}
}
// ========== 토스트 알림 ========== //
function showToast(message, type = 'info', duration = 3000) {
if (!elements.toastContainer) return;
const toast = document.createElement('div');
toast.className = `toast ${type}`;
const iconMap = {
success: '✅',
error: '❌',
warning: '⚠️',
info: 'ℹ️'
};
toast.innerHTML = `
${iconMap[type] || 'ℹ️'}
${message}
`;
elements.toastContainer.appendChild(toast);
// 자동 제거
setTimeout(() => {
if (toast.parentElement) {
toast.remove();
}
}, duration);
}
// ========== 작업자 관련 액션 함수들 ========== //
function openWorkerModal(workerId, workerName) {
console.log(`📝 ${workerName}(ID: ${workerId}) 작업 보고서 모달 열기`);
// 모달 데이터 설정
currentModalWorker = {
id: workerId,
name: workerName,
date: selectedDate
};
// 모달 표시
showWorkerModal();
}
function handleVacation(workerId, vacationType) {
console.log(`🏖️ 작업자 ${workerId} 휴가 처리: ${vacationType}`);
const vacationNames = {
'full': '연차',
'half': '반차',
'half-half': '반반차'
};
const vacationHours = {
'full': 8,
'half': 4,
'half-half': 2
};
if (confirm(`${vacationNames[vacationType]} 처리하시겠습니까?\n(${vacationHours[vacationType]}시간으로 자동 입력됩니다)`)) {
// 휴가 처리 API 호출
processVacation(workerId, vacationType, vacationHours[vacationType]);
}
}
async function processVacation(workerId, vacationType, hours) {
try {
showToast(`휴가 처리 중...`, 'info');
// 휴가용 작업 보고서 생성 (특별한 작업 유형으로)
const vacationReport = {
report_date: selectedDate,
worker_id: workerId,
project_id: 1, // 기본 프로젝트 (휴가용)
work_type_id: 999, // 휴가 전용 작업 유형 (DB에 추가 필요)
work_status_id: 1, // 정상 상태
error_type_id: null,
work_hours: hours,
created_by: currentUser?.user_id || 1
};
const response = await window.apiCall('/daily-work-reports', 'POST', vacationReport);
showToast(`휴가 처리가 완료되었습니다.`, 'success');
await loadDashboardData(); // 데이터 새로고침
} catch (error) {
console.error('휴가 처리 오류:', error);
showToast(`휴가 처리 중 오류가 발생했습니다: ${error.message}`, 'error');
}
}
function confirmOvertime(workerId) {
console.log(`⚠️ 작업자 ${workerId} 초과근무 확인`);
if (confirm('12시간을 초과한 작업시간이 정상적인 입력인지 확인하시겠습니까?')) {
// 초과근무 확인 처리
processOvertimeConfirmation(workerId);
}
}
async function processOvertimeConfirmation(workerId) {
try {
showToast('초과근무 승인 처리 중...', 'info');
// 새로운 근태 관리 API 사용
const overtimeData = {
worker_id: workerId,
date: selectedDate
};
const response = await window.apiCall('/attendance/overtime/approve', 'POST', overtimeData);
if (response.success) {
showToast('초과근무가 정상으로 승인되었습니다.', 'success');
await loadDashboardData(); // 데이터 새로고침
} else {
throw new Error(response.message || '초과근무 승인에 실패했습니다.');
}
} catch (error) {
console.error('초과근무 승인 오류:', error);
showToast(`초과근무 승인 중 오류가 발생했습니다: ${error.message}`, 'error');
}
}
// ========== 모달 시스템 ========== //
function showWorkerModal() {
// 모달이 없으면 생성
if (!document.getElementById('workerModal')) {
createWorkerModal();
}
// 모달 데이터 로드 및 표시
loadModalData();
document.getElementById('workerModal').style.display = 'flex';
document.body.style.overflow = 'hidden'; // 배경 스크롤 방지
}
function hideWorkerModal() {
document.getElementById('workerModal').style.display = 'none';
document.body.style.overflow = 'auto'; // 배경 스크롤 복원
resetModalForm();
}
function createWorkerModal() {
const modalHTML = `
`;
document.body.insertAdjacentHTML('beforeend', modalHTML);
setupModalEventListeners();
}
function setupModalEventListeners() {
// 모달 외부 클릭 시 닫기
document.getElementById('workerModal').addEventListener('click', (e) => {
if (e.target.id === 'workerModal') {
hideWorkerModal();
}
});
// 새 작업 추가/취소 버튼
document.getElementById('modalAddWorkBtn').addEventListener('click', showModalNewWorkForm);
document.getElementById('modalCancelWorkBtn').addEventListener('click', hideModalNewWorkForm);
document.getElementById('modalSaveWorkBtn').addEventListener('click', saveModalNewWork);
// 업무 상태 변경 시 에러 유형 토글
document.getElementById('modalWorkStatusSelect').addEventListener('change', toggleModalErrorType);
// 빠른 시간 버튼
document.querySelectorAll('.modal-time-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
document.getElementById('modalWorkHours').value = e.target.dataset.hours;
});
});
// 휴가 처리 버튼
document.querySelectorAll('.modal-vacation-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const vacationType = e.target.dataset.type;
handleModalVacation(vacationType);
});
});
}
async function loadModalData() {
if (!currentModalWorker) return;
try {
// 모달 헤더 업데이트
document.getElementById('modalTitle').textContent = `${currentModalWorker.name} 작업 보고서`;
document.getElementById('modalWorkerName').textContent = currentModalWorker.name;
document.getElementById('modalWorkerDate').textContent = currentModalWorker.date;
document.getElementById('modalWorkerInitial').textContent = currentModalWorker.name.charAt(0);
// 병렬로 데이터 로드
await Promise.all([
loadModalExistingWork(),
loadModalDropdownData()
]);
// UI 업데이트
updateModalSummary();
renderModalExistingWork();
populateModalDropdowns();
} catch (error) {
console.error('모달 데이터 로드 오류:', error);
showToast('데이터 로드 중 오류가 발생했습니다.', 'error');
}
}
async function loadModalExistingWork() {
try {
const response = await window.apiCall(`/daily-work-reports?date=${currentModalWorker.date}&worker_id=${currentModalWorker.id}`);
modalExistingWork = Array.isArray(response) ? response : (response.data || []);
} catch (error) {
console.error('기존 작업 로드 오류:', error);
modalExistingWork = [];
}
}
async function loadModalDropdownData() {
try {
const [projectsRes, workTypesRes, workStatusRes, errorTypesRes] = await Promise.all([
window.apiCall('/projects/active/list'),
window.apiCall('/daily-work-reports/work-types'),
window.apiCall('/daily-work-reports/work-status-types'),
window.apiCall('/daily-work-reports/error-types')
]);
modalProjects = Array.isArray(projectsRes) ? projectsRes : (projectsRes.data || []);
modalWorkTypes = Array.isArray(workTypesRes) ? workTypesRes : (workTypesRes.data || []);
modalWorkStatusTypes = Array.isArray(workStatusRes) ? workStatusRes : (workStatusRes.data || []);
modalErrorTypes = Array.isArray(errorTypesRes) ? errorTypesRes : (errorTypesRes.data || []);
} catch (error) {
console.error('드롭다운 데이터 로드 오류:', error);
}
}
function updateModalSummary() {
const totalHours = modalExistingWork.reduce((sum, work) => sum + parseFloat(work.work_hours || 0), 0);
const workCount = modalExistingWork.length;
document.getElementById('modalTotalHours').textContent = `${totalHours.toFixed(1)}h`;
document.getElementById('modalWorkCount').textContent = `${workCount}건`;
// 12시간 초과 경고
if (totalHours > 12) {
document.getElementById('modalTotalHours').classList.add('warning');
} else {
document.getElementById('modalTotalHours').classList.remove('warning');
}
}
function renderModalExistingWork() {
const container = document.getElementById('modalExistingWork');
if (modalExistingWork.length === 0) {
container.innerHTML = `
`;
return;
}
container.innerHTML = modalExistingWork.map(work => `
${work.project_name || '미지정 프로젝트'}
${work.work_type_name || '미지정 작업'}
${work.work_status_id === 2 && work.error_type_name ? `
${work.error_type_name}
` : ''}
${work.work_hours}h
`).join('');
}
function populateModalDropdowns() {
// 프로젝트 드롭다운
const projectSelect = document.getElementById('modalProjectSelect');
projectSelect.innerHTML = '';
modalProjects.forEach(project => {
projectSelect.innerHTML += ``;
});
// 작업 유형 드롭다운
const workTypeSelect = document.getElementById('modalWorkTypeSelect');
workTypeSelect.innerHTML = '';
modalWorkTypes.forEach(type => {
workTypeSelect.innerHTML += ``;
});
// 작업 상태 드롭다운
const workStatusSelect = document.getElementById('modalWorkStatusSelect');
workStatusSelect.innerHTML = '';
modalWorkStatusTypes.forEach(status => {
workStatusSelect.innerHTML += ``;
});
// 에러 유형 드롭다운
const errorTypeSelect = document.getElementById('modalErrorTypeSelect');
errorTypeSelect.innerHTML = '';
modalErrorTypes.forEach(error => {
errorTypeSelect.innerHTML += ``;
});
}
function showModalNewWorkForm() {
document.getElementById('modalNewWorkSection').style.display = 'block';
document.getElementById('modalAddWorkBtn').style.display = 'none';
}
function hideModalNewWorkForm() {
document.getElementById('modalNewWorkSection').style.display = 'none';
document.getElementById('modalAddWorkBtn').style.display = 'block';
resetModalForm();
}
function resetModalForm() {
document.getElementById('modalProjectSelect').value = '';
document.getElementById('modalWorkTypeSelect').value = '';
document.getElementById('modalWorkStatusSelect').value = '';
document.getElementById('modalErrorTypeSelect').value = '';
document.getElementById('modalWorkHours').value = '1.00';
document.getElementById('modalErrorTypeGroup').style.display = 'none';
}
function toggleModalErrorType() {
const workStatusSelect = document.getElementById('modalWorkStatusSelect');
const errorTypeGroup = document.getElementById('modalErrorTypeGroup');
if (workStatusSelect.value === '2') { // 에러 상태
errorTypeGroup.style.display = 'block';
} else {
errorTypeGroup.style.display = 'none';
document.getElementById('modalErrorTypeSelect').value = '';
}
}
async function saveModalNewWork() {
try {
const projectId = document.getElementById('modalProjectSelect').value;
const workTypeId = document.getElementById('modalWorkTypeSelect').value;
const workStatusId = document.getElementById('modalWorkStatusSelect').value;
const errorTypeId = document.getElementById('modalErrorTypeSelect').value;
const workHours = document.getElementById('modalWorkHours').value;
// 유효성 검사
if (!projectId || !workTypeId || !workStatusId || !workHours) {
showToast('모든 필수 필드를 입력해주세요.', 'error');
return;
}
if (workStatusId === '2' && !errorTypeId) {
showToast('에러 상태일 때는 에러 유형을 선택해야 합니다.', 'error');
return;
}
const workData = {
report_date: currentModalWorker.date,
worker_id: currentModalWorker.id,
work_entries: [{
project_id: parseInt(projectId),
task_id: parseInt(workTypeId), // work_type_id를 task_id로 매핑
work_hours: parseFloat(workHours),
work_status_id: parseInt(workStatusId),
error_type_id: workStatusId === '2' ? parseInt(errorTypeId) : null,
description: '' // 기본 설명
}]
};
console.log('📤 전송할 작업 데이터:', workData);
console.log('📋 현재 사용자:', currentUser);
await window.apiCall('/daily-work-reports', 'POST', workData);
showToast('작업이 성공적으로 저장되었습니다.', 'success');
// 데이터 새로고침
await loadModalExistingWork();
updateModalSummary();
renderModalExistingWork();
hideModalNewWorkForm();
// 대시보드 데이터도 새로고침
await loadDashboardData();
} catch (error) {
console.error('작업 저장 오류:', error);
showToast(`작업 저장 중 오류가 발생했습니다: ${error.message}`, 'error');
}
}
async function deleteModalWork(workId) {
if (!confirm('이 작업을 삭제하시겠습니까?')) {
return;
}
try {
await window.apiCall(`/daily-work-reports/${workId}`, 'DELETE');
showToast('작업이 성공적으로 삭제되었습니다.', 'success');
// 데이터 새로고침
await loadModalExistingWork();
updateModalSummary();
renderModalExistingWork();
// 대시보드 데이터도 새로고침
await loadDashboardData();
} catch (error) {
console.error('작업 삭제 오류:', error);
showToast(`작업 삭제 중 오류가 발생했습니다: ${error.message}`, 'error');
}
}
async function handleModalVacation(vacationType) {
const vacationTypeMap = {
'full': { code: 'ANNUAL_FULL', name: '연차', hours: 8 },
'half': { code: 'ANNUAL_HALF', name: '반차', hours: 4 },
'half-half': { code: 'ANNUAL_QUARTER', name: '반반차', hours: 2 }
};
const vacation = vacationTypeMap[vacationType];
if (!vacation) return;
if (!confirm(`${vacation.name} 처리하시겠습니까?\n(${vacation.hours}시간으로 자동 입력됩니다)`)) {
return;
}
try {
// 새로운 근태 관리 API 사용
const vacationData = {
worker_id: currentModalWorker.id,
date: currentModalWorker.date,
vacation_type: vacation.code
};
const response = await window.apiCall('/attendance/vacation', 'POST', vacationData);
if (response.success) {
showToast(`${vacation.name} 처리가 완료되었습니다.`, 'success');
// 데이터 새로고침
await loadModalExistingWork();
updateModalSummary();
renderModalExistingWork();
// 대시보드 데이터도 새로고침
await loadDashboardData();
} else {
throw new Error(response.message || '휴가 처리에 실패했습니다.');
}
} catch (error) {
console.error('휴가 처리 오류:', error);
showToast(`휴가 처리 중 오류가 발생했습니다: ${error.message}`, 'error');
}
}
// ========== 전역 함수 (HTML에서 호출) ========== //
window.loadDashboardData = loadDashboardData;
window.showToast = showToast;
window.updateSummaryCards = updateSummaryCards;
window.displayWorkers = displayWorkers;
window.openWorkerModal = openWorkerModal;
window.hideWorkerModal = hideWorkerModal;
window.deleteModalWork = deleteModalWork;
window.handleVacation = handleVacation;
window.confirmOvertime = confirmOvertime;