- Replaced SELECT* queries in 8 models with explicit columns. - Began modularizing work-report-calendar.js by creating CalendarAPI.js, CalendarState.js, and CalendarView.js. - Refactored manage-project.js to use global API helpers. - Fixed API container crash by adding missing volume mounts to docker-compose.yml. - Added new migration for missing columns in the projects table. - Documented current DB schema and deployment notes.
121 lines
5.7 KiB
JavaScript
121 lines
5.7 KiB
JavaScript
// web-ui/js/modules/calendar/CalendarView.js
|
|
|
|
/**
|
|
* 캘린더 UI 렌더링 및 DOM 조작을 담당하는 전역 객체입니다.
|
|
*/
|
|
(function(window) {
|
|
'use strict';
|
|
|
|
const CalendarView = {
|
|
elements: {},
|
|
|
|
initializeElements: function() {
|
|
this.elements.monthYearTitle = document.getElementById('monthYearTitle');
|
|
this.elements.calendarDays = document.getElementById('calendarDays');
|
|
this.elements.prevMonthBtn = document.getElementById('prevMonthBtn');
|
|
this.elements.nextMonthBtn = document.getElementById('nextMonthBtn');
|
|
this.elements.todayBtn = document.getElementById('todayBtn');
|
|
this.elements.dailyWorkModal = document.getElementById('dailyWorkModal');
|
|
this.elements.modalTitle = document.getElementById('modalTitle');
|
|
this.elements.modalSummary = document.querySelector('.daily-summary');
|
|
this.elements.modalTotalWorkers = document.getElementById('modalTotalWorkers');
|
|
this.elements.modalTotalHours = document.getElementById('modalTotalHours');
|
|
this.elements.modalTotalTasks = document.getElementById('modalTotalTasks');
|
|
this.elements.modalErrorCount = document.getElementById('modalErrorCount');
|
|
this.elements.modalWorkersList = document.getElementById('modalWorkersList');
|
|
this.elements.modalNoData = document.getElementById('modalNoData');
|
|
this.elements.statusFilter = document.getElementById('statusFilter');
|
|
this.elements.loadingSpinner = document.getElementById('loadingSpinner');
|
|
},
|
|
|
|
showLoading: function(show) {
|
|
if (this.elements.loadingSpinner) {
|
|
this.elements.loadingSpinner.style.display = show ? 'flex' : 'none';
|
|
}
|
|
},
|
|
|
|
showToast: function(message, type = 'info') {
|
|
const existingToast = document.querySelector('.toast-message');
|
|
if (existingToast) existingToast.remove();
|
|
|
|
const toast = document.createElement('div');
|
|
toast.className = `toast-message toast-${type}`;
|
|
toast.textContent = message;
|
|
document.body.appendChild(toast);
|
|
|
|
setTimeout(() => toast.remove(), 3000);
|
|
},
|
|
|
|
renderCalendar: async function() {
|
|
const year = CalendarState.currentDate.getFullYear();
|
|
const month = CalendarState.currentDate.getMonth();
|
|
|
|
const monthNames = ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'];
|
|
this.elements.monthYearTitle.textContent = `${year}년 ${monthNames[month]}`;
|
|
|
|
this.showLoading(true);
|
|
try {
|
|
const monthData = await CalendarAPI.getMonthlyCalendarData(year, month);
|
|
const firstDay = new Date(year, month, 1);
|
|
const lastDay = new Date(year, month + 1, 0);
|
|
const startDate = new Date(firstDay);
|
|
startDate.setDate(startDate.getDate() - firstDay.getDay());
|
|
|
|
const today = new Date();
|
|
const todayStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
|
|
|
|
let calendarHTML = '';
|
|
let currentDay = new Date(startDate);
|
|
|
|
for (let i = 0; i < 42; i++) {
|
|
const dateStr = `${currentDay.getFullYear()}-${String(currentDay.getMonth() + 1).padStart(2, '0')}-${String(currentDay.getDate()).padStart(2, '0')}`;
|
|
const dayWorkData = monthData[dateStr] || { hasData: false, hasIssues: false, hasErrors: false, workerCount: 0 };
|
|
const dayStatus = this.analyzeDayStatus(dayWorkData);
|
|
|
|
let dayClasses = ['calendar-day'];
|
|
if (currentDay.getMonth() !== month) dayClasses.push('other-month');
|
|
if (dateStr === todayStr) dayClasses.push('today');
|
|
if (currentDay.getDay() === 0) dayClasses.push('sunday');
|
|
if (currentDay.getDay() === 6) dayClasses.push('saturday');
|
|
|
|
const hasAnyProblem = dayStatus.hasOvertimeWarning || dayStatus.hasIncomplete || dayStatus.hasIssues;
|
|
if (dayStatus.hasData && !hasAnyProblem) dayClasses.push('has-normal');
|
|
|
|
let statusIcons = '';
|
|
if (hasAnyProblem) {
|
|
if (dayStatus.hasOvertimeWarning) statusIcons += '<div class="legend-icon purple">●</div>';
|
|
if (dayStatus.hasIncomplete) statusIcons += '<div class="legend-icon red">●</div>';
|
|
if (dayStatus.hasIssues) statusIcons += '<div class="legend-icon orange">●</div>';
|
|
}
|
|
|
|
calendarHTML += `<div class="${dayClasses.join(' ')}" onclick="openDailyWorkModal('${dateStr}')"><div class="day-number">${currentDay.getDate()}</div>${statusIcons}</div>`;
|
|
currentDay.setDate(currentDay.getDate() + 1);
|
|
}
|
|
this.elements.calendarDays.innerHTML = calendarHTML;
|
|
} catch (error) {
|
|
console.error('캘린더 렌더링 오류:', error);
|
|
this.showToast('캘린더를 불러오는데 실패했습니다.', 'error');
|
|
} finally {
|
|
this.showLoading(false);
|
|
}
|
|
},
|
|
|
|
analyzeDayStatus: function(dayData) {
|
|
if (dayData && typeof dayData === 'object' && 'totalWorkers' in dayData) {
|
|
const totalRegisteredWorkers = CalendarState.allWorkers ? CalendarState.allWorkers.length : 0;
|
|
const actualIncompleteWorkers = Math.max(0, totalRegisteredWorkers - dayData.workingWorkers);
|
|
return {
|
|
hasData: dayData.totalWorkers > 0,
|
|
hasIssues: dayData.partialWorkers > 0,
|
|
hasIncomplete: actualIncompleteWorkers > 0 || dayData.incompleteWorkers > 0,
|
|
hasOvertimeWarning: dayData.hasOvertimeWarning || dayData.overtimeWarningWorkers > 0,
|
|
workerCount: dayData.totalWorkers || 0
|
|
};
|
|
}
|
|
return { hasData: false, hasIssues: false, hasErrors: false, workerCount: 0 };
|
|
}
|
|
};
|
|
|
|
window.CalendarView = CalendarView;
|
|
|
|
})(window); |