Files
TK-FB-Project/web-ui/js/modules/calendar/CalendarView.js
Hyungi Ahn 05843da1c4 refactor(db,frontend): Improve queries and modularize frontend
- 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.
2025-12-19 12:42:24 +09:00

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