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.
This commit is contained in:
Hyungi Ahn
2025-12-19 12:42:24 +09:00
parent 8a8307edfc
commit 05843da1c4
19 changed files with 826 additions and 381 deletions

View File

@@ -138,213 +138,6 @@ async function loadMonthlyWorkData(year, month) {
}
}
// 캘린더 렌더링
async function renderCalendar() {
const year = CalendarState.currentDate.getFullYear();
const month = CalendarState.currentDate.getMonth();
// 헤더 업데이트
const monthNames = ['1월', '2월', '3월', '4월', '5월', '6월',
'7월', '8월', '9월', '10월', '11월', '12월'];
const monthText = `${year}${monthNames[month]}`;
elements.monthYearTitle.textContent = monthText;
// 로딩 표시
showLoading(true);
try {
// 월별 데이터 로드
const monthData = await loadMonthlyWorkData(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 = '';
const currentDay = new Date(startDate);
// 6주 * 7일 = 42일 렌더링
for (let i = 0; i < 42; i++) {
// 로컬 시간대로 날짜 문자열 생성 (UTC 변환 문제 방지)
const year = currentDay.getFullYear();
const month_num = String(currentDay.getMonth() + 1).padStart(2, '0');
const day_num = String(currentDay.getDate()).padStart(2, '0');
const dateStr = `${year}-${month_num}-${day_num}`;
const dayNumber = currentDay.getDate();
const isCurrentMonth = currentDay.getMonth() === month;
const isToday = dateStr === todayStr;
const isSunday = currentDay.getDay() === 0;
const isSaturday = currentDay.getDay() === 6;
// 해당 날짜의 작업 데이터 (집계 데이터 구조)
let dayWorkData = monthData[dateStr] || {
hasData: false,
hasIssues: false,
hasErrors: false,
workerCount: 0
};
// 실제 데이터 사용 (테스트 데이터 제거)
const dayStatus = analyzeDayStatus(dayWorkData);
// 디버깅: 상태가 있는 날짜만 로그
if (dayStatus.hasData || dayStatus.hasIssues || dayStatus.hasIncomplete || dayStatus.hasOvertimeWarning) {
let statusText = '이상없음';
if (dayStatus.hasOvertimeWarning) statusText = '확인필요';
else if (dayStatus.hasIncomplete) statusText = '미입력';
else if (dayStatus.hasIssues) statusText = '부분입력';
console.log(`📅 ${dateStr} (${dayNumber}일):`, {
상태: statusText,
작업자수: dayStatus.workerCount,
dayStatus,
원본데이터: dayWorkData
});
}
let dayClasses = ['calendar-day'];
if (!isCurrentMonth) dayClasses.push('other-month');
if (isToday) dayClasses.push('today');
if (isSunday) dayClasses.push('sunday');
if (isSaturday) dayClasses.push('saturday');
if (isSunday || isSaturday) dayClasses.push('weekend');
// 문제가 있는지 확인
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">${dayNumber}</div>
${statusIcons}
</div>
`;
currentDay.setDate(currentDay.getDate() + 1);
}
elements.calendarDays.innerHTML = calendarHTML;
} catch (error) {
console.error('캘린더 렌더링 오류:', error);
showToast('캘린더를 불러오는데 실패했습니다.', 'error');
} finally {
showLoading(false);
}
}
// 일별 상태 분석 (집계 데이터 또는 원본 데이터 처리)
function analyzeDayStatus(dayData) {
// 새로운 집계 데이터 구조인지 확인 (monthly_summary에서 온 데이터)
if (dayData && typeof dayData === 'object' && 'totalWorkers' in dayData) {
// 미입력 판단: allWorkers 배열 길이와 실제 작업한 작업자 수 비교
const totalRegisteredWorkers = CalendarState.allWorkers ? CalendarState.allWorkers.length : 10; // 실제 등록된 작업자 수
const actualIncompleteWorkers = Math.max(0, totalRegisteredWorkers - dayData.workingWorkers);
const result = {
hasData: dayData.totalWorkers > 0,
hasIssues: dayData.partialWorkers > 0, // 부분입력 작업자가 있으면 true
hasIncomplete: actualIncompleteWorkers > 0 || dayData.incompleteWorkers > 0, // 실제 미입력 작업자가 있으면 true
hasOvertimeWarning: dayData.hasOvertimeWarning || dayData.overtimeWarningWorkers > 0, // 12시간 초과
workerCount: dayData.totalWorkers || 0
};
// 디버깅: 모든 데이터 로그 (미입력 문제 해결용)
console.log('📊 analyzeDayStatus 결과:', {
dayData,
result,
actualIncompleteWorkers,
workingWorkers: dayData.workingWorkers,
totalRegisteredWorkers: totalRegisteredWorkers,
allWorkersLength: CalendarState.allWorkers ? CalendarState.allWorkers.length : 'undefined'
});
return result;
}
// 기존 hasData 구조 확인
if (dayData && typeof dayData === 'object' && dayData.hasData !== undefined) {
return {
hasData: dayData.hasData,
hasIssues: dayData.hasIssues,
hasErrors: dayData.hasErrors,
workerCount: dayData.workerCount || 0
};
}
// 폴백: 기존 방식으로 분석 (원본 작업 데이터 배열)
if (!Array.isArray(dayData) || dayData.length === 0) {
return {
hasData: false,
hasIssues: false,
hasErrors: false,
workerCount: 0
};
}
// 작업자별로 그룹화
const workerGroups = {};
dayData.forEach(work => {
if (!workerGroups[work.worker_id]) {
workerGroups[work.worker_id] = [];
}
workerGroups[work.worker_id].push(work);
});
const workerCount = Object.keys(workerGroups).length;
let hasIssues = false;
let hasErrors = false;
// 각 작업자의 상태 분석 - 문제가 있는지만 확인
Object.values(workerGroups).forEach(workerWork => {
const totalHours = workerWork.reduce((sum, w) => sum + parseFloat(w.work_hours || 0), 0);
const hasError = workerWork.some(w => w.work_status_id === 2);
const hasVacation = workerWork.some(w => w.project_id === 13);
// 오류가 있는 경우
if (hasError) {
hasErrors = true;
}
// 휴가가 아닌데 미입력이거나 부분입력인 경우
else if (!hasVacation && (totalHours === 0 || totalHours < 8)) {
hasIssues = true;
}
});
return {
hasData: true,
hasIssues,
hasErrors,
workerCount
};
}
// 일일 작업 현황 모달 열기
async function openDailyWorkModal(dateStr) {
console.log(`🗓️ 클릭된 날짜: ${dateStr}`);