refactor(frontend): Begin modularizing work-report-calendar
Initiated the process of refactoring the monolithic `work-report-calendar.js` file as outlined in the Phase 2 frontend modernization plan. - Created `CalendarAPI.js` to encapsulate all API calls related to the calendar, centralizing data fetching logic. - Created `CalendarState.js` to manage the component's state, removing global variables from the main script. - Refactored `work-report-calendar.js` to use the new state and API modules. - Refactored `manage-project.js` to use the existing global API helpers, providing a consistent example for API usage.
This commit is contained in:
@@ -1,9 +1,6 @@
|
||||
// /js/manage-project.js
|
||||
|
||||
import { API, getAuthHeaders, ensureAuthenticated } from '/js/api-config.js';
|
||||
|
||||
// 인증 확인
|
||||
ensureAuthenticated();
|
||||
// The ensureAuthenticated, API, and getAuthHeaders functions are now handled by the global api-helper.js
|
||||
|
||||
function createRow(item, cols, delHandler) {
|
||||
const tr = document.createElement('tr');
|
||||
@@ -40,13 +37,8 @@ projectForm?.addEventListener('submit', async e => {
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API}/projects`, {
|
||||
method: 'POST',
|
||||
headers: getAuthHeaders(),
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
const result = await res.json();
|
||||
if (res.ok && result.success) {
|
||||
const result = await apiPost('/projects', body);
|
||||
if (result.success) {
|
||||
alert('✅ 등록 완료');
|
||||
projectForm.reset();
|
||||
loadProjects();
|
||||
@@ -62,34 +54,24 @@ async function loadProjects() {
|
||||
const tbody = document.getElementById('projectTableBody');
|
||||
tbody.innerHTML = '<tr><td colspan="9">불러오는 중...</td></tr>';
|
||||
try {
|
||||
const res = await fetch(`${API}/projects`, {
|
||||
headers: getAuthHeaders()
|
||||
});
|
||||
const result = await apiGet('/projects');
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`HTTP error! status: ${res.status}`);
|
||||
}
|
||||
|
||||
const list = await res.json();
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if (Array.isArray(list)) {
|
||||
list.forEach(item => {
|
||||
if (result.success && Array.isArray(result.data)) {
|
||||
result.data.forEach(item => {
|
||||
const row = createRow(item, [
|
||||
'project_id', 'job_no', 'project_name', 'contract_date',
|
||||
'due_date', 'delivery_method', 'site', 'pm'
|
||||
], async p => {
|
||||
if (!confirm('삭제하시겠습니까?')) return;
|
||||
try {
|
||||
const delRes = await fetch(`${API}/projects/${p.project_id}`, {
|
||||
method: 'DELETE',
|
||||
headers: getAuthHeaders()
|
||||
});
|
||||
if (delRes.ok) {
|
||||
const delRes = await apiDelete(`/projects/${p.project_id}`);
|
||||
if (delRes.success) {
|
||||
alert('✅ 삭제 완료');
|
||||
loadProjects();
|
||||
} else {
|
||||
alert('❌ 삭제 실패');
|
||||
alert('❌ 삭제 실패: ' + (delRes.error || '알 수 없는 오류'));
|
||||
}
|
||||
} catch (err) {
|
||||
alert('🚨 삭제 중 오류: ' + err.message);
|
||||
|
||||
131
web-ui/js/modules/calendar/CalendarAPI.js
Normal file
131
web-ui/js/modules/calendar/CalendarAPI.js
Normal file
@@ -0,0 +1,131 @@
|
||||
// web-ui/js/modules/calendar/CalendarAPI.js
|
||||
|
||||
/**
|
||||
* 캘린더와 관련된 모든 API 호출을 관리하는 전역 객체입니다.
|
||||
*/
|
||||
(function(window) {
|
||||
'use strict';
|
||||
|
||||
const CalendarAPI = {};
|
||||
|
||||
/**
|
||||
* 활성화된 모든 작업자 목록을 가져옵니다.
|
||||
* @returns {Promise<Array>} 작업자 객체 배열
|
||||
*/
|
||||
CalendarAPI.getWorkers = async function() {
|
||||
try {
|
||||
// api-helper.js 에 정의된 전역 apiGet 함수를 사용합니다.
|
||||
const response = await window.apiGet('/workers');
|
||||
if (response.success && Array.isArray(response.data)) {
|
||||
// 활성화된 작업자만 필터링
|
||||
const activeWorkers = response.data.filter(worker =>
|
||||
worker.status === 'active' || worker.is_active === 1 || worker.is_active === true
|
||||
);
|
||||
return activeWorkers;
|
||||
}
|
||||
console.warn('API 응답 형식이 올바르지 않거나 데이터가 없습니다:', response);
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.error('작업자 데이터 로딩 중 API 오류 발생:', error);
|
||||
// 에러를 다시 던져서 호출부에서 처리할 수 있도록 함
|
||||
throw new Error('작업자 데이터를 불러오는 데 실패했습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 월별 작업 데이터 로드 (집계 테이블 사용으로 최적화)
|
||||
* @param {number} year
|
||||
* @param {number} month (0-indexed)
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
CalendarAPI.getMonthlyCalendarData = async function(year, month) {
|
||||
const monthKey = `${year}-${String(month + 1).padStart(2, '0')}`;
|
||||
try {
|
||||
const response = await window.apiGet(`/monthly-status/calendar?year=${year}&month=${month + 1}`);
|
||||
if (response.success) {
|
||||
return response.data;
|
||||
} else {
|
||||
throw new Error(response.message || '집계 데이터 조회 실패');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`${monthKey} 집계 데이터 로딩 오류:`, error);
|
||||
console.log(`📋 폴백: ${monthKey} 기존 방식 로딩 시작...`);
|
||||
return await _getMonthlyWorkDataFallback(year, month);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 일일 상세 데이터 조회 (모달용)
|
||||
* @param {string} dateStr (YYYY-MM-DD)
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
CalendarAPI.getDailyDetails = async function(dateStr) {
|
||||
try {
|
||||
const response = await window.apiGet(`/monthly-status/daily-details?date=${dateStr}`);
|
||||
if (response.success) {
|
||||
return response.data;
|
||||
}
|
||||
// Fallback to old API if new one fails
|
||||
const fallbackResponse = await window.apiGet(`/daily-work-reports?date=${dateStr}&view_all=true`);
|
||||
return {
|
||||
workers: fallbackResponse.data, // Assuming structure is different
|
||||
summary: {} // No summary in fallback
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('일일 작업 데이터 로딩 오류:', error);
|
||||
throw new Error('해당 날짜의 작업 데이터를 불러오는 데 실패했습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 특정 작업자의 하루치 작업을 모두 삭제합니다.
|
||||
* @param {number} workerId
|
||||
* @param {string} date (YYYY-MM-DD)
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
CalendarAPI.deleteWorkerDayWork = async function(workerId, date) {
|
||||
return await window.apiDelete(`/daily-work-reports/date/${date}/worker/${workerId}`);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 폴백: 순차적 로딩 (지연 시간 포함) - Private helper
|
||||
* @param {number} year
|
||||
* @param {number} month (0-indexed)
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
async function _getMonthlyWorkDataFallback(year, month) {
|
||||
const monthKey = `${year}-${String(month + 1).padStart(2, '0')}`;
|
||||
const monthData = {};
|
||||
try {
|
||||
const firstDay = new Date(year, month, 1);
|
||||
const lastDay = new Date(year, month + 1, 0);
|
||||
const currentDay = new Date(firstDay);
|
||||
|
||||
const promises = [];
|
||||
while (currentDay <= lastDay) {
|
||||
const dateStr = currentDay.toISOString().split('T')[0];
|
||||
promises.push(window.apiGet(`/daily-work-reports?date=${dateStr}&view_all=true`));
|
||||
currentDay.setDate(currentDay.getDate() + 1);
|
||||
}
|
||||
|
||||
const results = await Promise.all(promises);
|
||||
|
||||
let day = 1;
|
||||
for (const result of results) {
|
||||
const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
||||
monthData[dateStr] = result.success && Array.isArray(result.data) ? result.data : [];
|
||||
day++;
|
||||
}
|
||||
return monthData;
|
||||
} catch (error) {
|
||||
console.error(`${monthKey} 순차 로딩 오류:`, error);
|
||||
throw new Error('작업 데이터를 불러오는 데 실패했습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 전역 스코프에 CalendarAPI 객체 할당
|
||||
window.CalendarAPI = CalendarAPI;
|
||||
|
||||
})(window);
|
||||
34
web-ui/js/modules/calendar/CalendarState.js
Normal file
34
web-ui/js/modules/calendar/CalendarState.js
Normal file
@@ -0,0 +1,34 @@
|
||||
// web-ui/js/modules/calendar/CalendarState.js
|
||||
|
||||
/**
|
||||
* 캘린더 페이지의 모든 상태를 관리하는 전역 객체입니다.
|
||||
*/
|
||||
(function(window) {
|
||||
'use strict';
|
||||
|
||||
const CalendarState = {
|
||||
// 캘린더 상태
|
||||
currentDate: new Date(),
|
||||
monthlyData: {}, // 월별 데이터 캐시
|
||||
allWorkers: [], // 전체 작업자 목록 캐시
|
||||
|
||||
// 모달 상태
|
||||
currentModalDate: null,
|
||||
currentEditingWork: null,
|
||||
existingWorks: [],
|
||||
|
||||
// 상태 초기화
|
||||
reset: function() {
|
||||
this.currentDate = new Date();
|
||||
this.monthlyData = {};
|
||||
// allWorkers는 유지
|
||||
this.currentModalDate = null;
|
||||
this.currentEditingWork = null;
|
||||
this.existingWorks = [];
|
||||
}
|
||||
};
|
||||
|
||||
// 전역 스코프에 CalendarState 객체 할당
|
||||
window.CalendarState = CalendarState;
|
||||
|
||||
})(window);
|
||||
@@ -1,12 +1,12 @@
|
||||
// 작업 현황 캘린더 JavaScript
|
||||
|
||||
// 전역 변수
|
||||
let currentDate = new Date();
|
||||
let monthlyData = {}; // 월별 데이터 캐시
|
||||
// 작업자 데이터는 allWorkers 변수 사용
|
||||
let currentModalDate = null;
|
||||
let currentEditingWork = null;
|
||||
let existingWorks = [];
|
||||
// 전역 변수 대신 CalendarState 사용
|
||||
// let currentDate = new Date();
|
||||
// let monthlyData = {}; // 월별 데이터 캐시
|
||||
// let allWorkers = []; // 작업자 데이터는 allWorkers 변수 사용
|
||||
// let currentModalDate = null;
|
||||
// let currentEditingWork = null;
|
||||
// let existingWorks = [];
|
||||
|
||||
// DOM 요소
|
||||
const elements = {
|
||||
@@ -68,17 +68,17 @@ function initializeElements() {
|
||||
// 이벤트 리스너 설정
|
||||
function setupEventListeners() {
|
||||
elements.prevMonthBtn.addEventListener('click', () => {
|
||||
currentDate.setMonth(currentDate.getMonth() - 1);
|
||||
CalendarState.currentDate.setMonth(CalendarState.currentDate.getMonth() - 1);
|
||||
renderCalendar();
|
||||
});
|
||||
|
||||
elements.nextMonthBtn.addEventListener('click', () => {
|
||||
currentDate.setMonth(currentDate.getMonth() + 1);
|
||||
CalendarState.currentDate.setMonth(CalendarState.currentDate.getMonth() + 1);
|
||||
renderCalendar();
|
||||
});
|
||||
|
||||
elements.todayBtn.addEventListener('click', () => {
|
||||
currentDate = new Date();
|
||||
CalendarState.currentDate = new Date();
|
||||
renderCalendar();
|
||||
});
|
||||
|
||||
@@ -101,23 +101,19 @@ function setupEventListeners() {
|
||||
|
||||
// 작업자 데이터 로드 (캐시)
|
||||
async function loadWorkersData() {
|
||||
if (allWorkers.length > 0) return allWorkers;
|
||||
if (CalendarState.allWorkers.length > 0) return CalendarState.allWorkers;
|
||||
|
||||
try {
|
||||
console.log('👥 작업자 데이터 로딩...');
|
||||
const response = await window.apiCall('/workers');
|
||||
const workers = Array.isArray(response) ? response : (response.data || []);
|
||||
console.log('👥 작업자 데이터 로딩 (from CalendarAPI)...');
|
||||
// The new API function already filters for active workers
|
||||
const activeWorkers = await CalendarAPI.getWorkers();
|
||||
CalendarState.allWorkers = activeWorkers;
|
||||
|
||||
// 활성화된 작업자만 필터링
|
||||
allWorkers = workers.filter(worker => {
|
||||
return worker.status === 'active' || worker.is_active === 1 || worker.is_active === true;
|
||||
});
|
||||
|
||||
console.log(`✅ 작업자 ${allWorkers.length}명 로드 완료 (전체: ${workers.length}명)`);
|
||||
return allWorkers;
|
||||
console.log(`✅ 작업자 ${CalendarState.allWorkers.length}명 로드 완료`);
|
||||
return CalendarState.allWorkers;
|
||||
} catch (error) {
|
||||
console.error('작업자 데이터 로딩 오류:', error);
|
||||
showToast('작업자 데이터를 불러오는데 실패했습니다.', 'error');
|
||||
showToast(error.message, 'error');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -126,146 +122,26 @@ async function loadWorkersData() {
|
||||
async function loadMonthlyWorkData(year, month) {
|
||||
const monthKey = `${year}-${String(month + 1).padStart(2, '0')}`;
|
||||
|
||||
if (monthlyData[monthKey]) {
|
||||
if (CalendarState.monthlyData[monthKey]) {
|
||||
console.log(`📋 캐시된 ${monthKey} 데이터 사용`);
|
||||
return monthlyData[monthKey];
|
||||
return CalendarState.monthlyData[monthKey];
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`📋 ${monthKey} 집계 데이터 로딩...`);
|
||||
|
||||
// 새로운 월별 집계 API 사용 (단일 호출)
|
||||
const response = await window.apiCall(`/monthly-status/calendar?year=${year}&month=${month + 1}`);
|
||||
|
||||
if (response.success) {
|
||||
const calendarData = response.data;
|
||||
|
||||
console.log(`📊 ${monthKey} 집계 데이터:`, Object.keys(calendarData).length, '일');
|
||||
|
||||
// 날짜별 상태 데이터로 변환
|
||||
const monthData = {};
|
||||
|
||||
// 해당 월의 모든 날짜 초기화
|
||||
const firstDay = new Date(year, month, 1);
|
||||
const lastDay = new Date(year, month + 1, 0);
|
||||
const currentDay = new Date(firstDay);
|
||||
|
||||
while (currentDay <= lastDay) {
|
||||
const dateStr = currentDay.toISOString().split('T')[0];
|
||||
|
||||
if (calendarData[dateStr]) {
|
||||
// 집계 데이터가 있는 경우
|
||||
const dayData = calendarData[dateStr];
|
||||
monthData[dateStr] = {
|
||||
hasData: dayData.workingWorkers > 0,
|
||||
hasIssues: dayData.hasIssues,
|
||||
hasErrors: dayData.hasErrors,
|
||||
hasOvertimeWarning: dayData.hasOvertimeWarning,
|
||||
totalWorkers: dayData.totalWorkers,
|
||||
workerCount: dayData.totalWorkers,
|
||||
workingWorkers: dayData.workingWorkers,
|
||||
incompleteWorkers: dayData.incompleteWorkers,
|
||||
partialWorkers: dayData.partialWorkers,
|
||||
errorWorkers: dayData.errorWorkers,
|
||||
overtimeWarningWorkers: dayData.overtimeWarningWorkers,
|
||||
totalHours: dayData.totalHours,
|
||||
totalTasks: dayData.totalTasks,
|
||||
errorCount: dayData.errorCount,
|
||||
lastUpdated: dayData.lastUpdated
|
||||
};
|
||||
} else {
|
||||
// 집계 데이터가 없는 경우 (작업 없음)
|
||||
monthData[dateStr] = {
|
||||
hasData: false,
|
||||
hasIssues: false,
|
||||
hasErrors: false,
|
||||
workerCount: 0,
|
||||
workingWorkers: 0,
|
||||
incompleteWorkers: 0,
|
||||
partialWorkers: 0,
|
||||
errorWorkers: 0,
|
||||
totalHours: 0,
|
||||
totalTasks: 0,
|
||||
errorCount: 0
|
||||
};
|
||||
}
|
||||
|
||||
currentDay.setDate(currentDay.getDate() + 1);
|
||||
}
|
||||
|
||||
// 캐시에 저장
|
||||
monthlyData[monthKey] = monthData;
|
||||
|
||||
console.log(`✅ ${monthKey} 집계 데이터 로드 완료 (${Object.keys(monthData).length}일 데이터)`);
|
||||
console.log('📊 월별 데이터 샘플:', Object.entries(monthData).slice(0, 5));
|
||||
return monthData;
|
||||
} else {
|
||||
throw new Error(response.message || '집계 데이터 조회 실패');
|
||||
}
|
||||
|
||||
const data = await CalendarAPI.getMonthlyCalendarData(year, month);
|
||||
CalendarState.monthlyData[monthKey] = data; // Cache the data
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error(`${monthKey} 집계 데이터 로딩 오류:`, error);
|
||||
|
||||
// 폴백: 기존 방식으로 순차 로딩
|
||||
console.log(`📋 폴백: ${monthKey} 기존 방식 로딩 시작...`);
|
||||
return await loadMonthlyWorkDataFallback(year, month);
|
||||
}
|
||||
}
|
||||
|
||||
// 폴백: 순차적 로딩 (지연 시간 포함)
|
||||
async function loadMonthlyWorkDataFallback(year, month) {
|
||||
const monthKey = `${year}-${String(month + 1).padStart(2, '0')}`;
|
||||
const monthData = {};
|
||||
|
||||
try {
|
||||
const firstDay = new Date(year, month, 1);
|
||||
const lastDay = new Date(year, month + 1, 0);
|
||||
const currentDay = new Date(firstDay);
|
||||
|
||||
let loadedCount = 0;
|
||||
const totalDays = lastDay.getDate();
|
||||
|
||||
while (currentDay <= lastDay) {
|
||||
const dateStr = currentDay.toISOString().split('T')[0];
|
||||
|
||||
try {
|
||||
const response = await window.apiCall(`/daily-work-reports?date=${dateStr}&view_all=true`);
|
||||
monthData[dateStr] = Array.isArray(response) ? response : (response.data || []);
|
||||
loadedCount++;
|
||||
|
||||
// 진행률 표시
|
||||
if (loadedCount % 5 === 0) {
|
||||
console.log(`📋 ${monthKey} 로딩 진행률: ${loadedCount}/${totalDays}`);
|
||||
}
|
||||
|
||||
// API 부하 방지를 위한 지연 (500ms)
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
} catch (error) {
|
||||
console.warn(`${dateStr} 데이터 로딩 실패:`, error.message);
|
||||
monthData[dateStr] = [];
|
||||
}
|
||||
|
||||
currentDay.setDate(currentDay.getDate() + 1);
|
||||
}
|
||||
|
||||
// 캐시에 저장
|
||||
monthlyData[monthKey] = monthData;
|
||||
|
||||
console.log(`✅ ${monthKey} 순차 로딩 완료 (${loadedCount}/${totalDays}일)`);
|
||||
return monthData;
|
||||
|
||||
} catch (error) {
|
||||
console.error(`${monthKey} 순차 로딩 오류:`, error);
|
||||
showToast('작업 데이터를 불러오는데 실패했습니다.', 'error');
|
||||
return {};
|
||||
console.error(`${monthKey} 데이터 로딩 오류:`, error);
|
||||
showToast(error.message, 'error');
|
||||
return {}; // Return empty object on failure
|
||||
}
|
||||
}
|
||||
|
||||
// 캘린더 렌더링
|
||||
async function renderCalendar() {
|
||||
const year = currentDate.getFullYear();
|
||||
const month = currentDate.getMonth();
|
||||
const year = CalendarState.currentDate.getFullYear();
|
||||
const month = CalendarState.currentDate.getMonth();
|
||||
|
||||
// 헤더 업데이트
|
||||
const monthNames = ['1월', '2월', '3월', '4월', '5월', '6월',
|
||||
@@ -388,7 +264,7 @@ function analyzeDayStatus(dayData) {
|
||||
// 새로운 집계 데이터 구조인지 확인 (monthly_summary에서 온 데이터)
|
||||
if (dayData && typeof dayData === 'object' && 'totalWorkers' in dayData) {
|
||||
// 미입력 판단: allWorkers 배열 길이와 실제 작업한 작업자 수 비교
|
||||
const totalRegisteredWorkers = allWorkers ? allWorkers.length : 10; // 실제 등록된 작업자 수
|
||||
const totalRegisteredWorkers = CalendarState.allWorkers ? CalendarState.allWorkers.length : 10; // 실제 등록된 작업자 수
|
||||
const actualIncompleteWorkers = Math.max(0, totalRegisteredWorkers - dayData.workingWorkers);
|
||||
|
||||
const result = {
|
||||
@@ -406,7 +282,7 @@ function analyzeDayStatus(dayData) {
|
||||
actualIncompleteWorkers,
|
||||
workingWorkers: dayData.workingWorkers,
|
||||
totalRegisteredWorkers: totalRegisteredWorkers,
|
||||
allWorkersLength: allWorkers ? allWorkers.length : 'undefined'
|
||||
allWorkersLength: CalendarState.allWorkers ? CalendarState.allWorkers.length : 'undefined'
|
||||
});
|
||||
|
||||
return result;
|
||||
@@ -472,7 +348,7 @@ function analyzeDayStatus(dayData) {
|
||||
// 일일 작업 현황 모달 열기
|
||||
async function openDailyWorkModal(dateStr) {
|
||||
console.log(`🗓️ 클릭된 날짜: ${dateStr}`);
|
||||
currentModalDate = dateStr;
|
||||
CalendarState.currentModalDate = dateStr;
|
||||
|
||||
// 날짜 포맷팅
|
||||
const date = new Date(dateStr + 'T00:00:00');
|
||||
@@ -487,18 +363,12 @@ async function openDailyWorkModal(dateStr) {
|
||||
elements.modalTitle.textContent = `${year}년 ${month}월 ${day}일 (${dayName}) 작업 현황`;
|
||||
|
||||
try {
|
||||
// 새로운 집계 API로 작업자별 상세 정보 조회
|
||||
const response = await window.apiCall(`/monthly-status/daily-details?date=${dateStr}`);
|
||||
const response = await CalendarAPI.getDailyDetails(dateStr);
|
||||
|
||||
if (response.success) {
|
||||
const { workers, summary } = response.data;
|
||||
renderModalDataFromSummary(workers, summary);
|
||||
} else {
|
||||
// 폴백: 기존 API 사용
|
||||
console.log('집계 API 실패, 기존 API로 폴백');
|
||||
const fallbackResponse = await window.apiCall(`/daily-work-reports?date=${dateStr}&view_all=true`);
|
||||
const workData = Array.isArray(fallbackResponse) ? fallbackResponse : (fallbackResponse.data || []);
|
||||
renderModalData(workData);
|
||||
if (response.workers) { // New API structure
|
||||
renderModalDataFromSummary(response.workers, response.summary);
|
||||
} else { // Fallback structure
|
||||
renderModalData(response);
|
||||
}
|
||||
|
||||
// 모달 표시
|
||||
@@ -514,13 +384,13 @@ async function openDailyWorkModal(dateStr) {
|
||||
// 집계 데이터로 모달 렌더링 (최적화된 버전)
|
||||
async function renderModalDataFromSummary(workers, summary) {
|
||||
// 전체 작업자 목록 가져오기
|
||||
const allWorkers = await loadWorkersData();
|
||||
const allWorkersList = await loadWorkersData();
|
||||
|
||||
// 작업한 작업자 ID 목록
|
||||
const workedWorkerIds = new Set(workers.map(w => w.workerId));
|
||||
|
||||
// 미기입 작업자 추가 (대시보드와 동일한 상태 판단 로직 적용)
|
||||
const missingWorkers = allWorkers
|
||||
const missingWorkers = allWorkersList
|
||||
.filter(worker => !workedWorkerIds.has(worker.worker_id))
|
||||
.map(worker => {
|
||||
return {
|
||||
@@ -541,11 +411,11 @@ async function renderModalDataFromSummary(workers, summary) {
|
||||
});
|
||||
|
||||
// 전체 작업자 목록 (작업한 사람 + 미기입 사람)
|
||||
const allWorkersList = [...workers, ...missingWorkers];
|
||||
const allModalWorkers = [...workers, ...missingWorkers];
|
||||
|
||||
// 요약 정보 업데이트 (전체 작업자 수 포함)
|
||||
if (elements.modalTotalWorkers) {
|
||||
elements.modalTotalWorkers.textContent = `${allWorkersList.length}명`;
|
||||
elements.modalTotalWorkers.textContent = `${allModalWorkers.length}명`;
|
||||
}
|
||||
if (elements.modalTotalHours) {
|
||||
elements.modalTotalHours.textContent = `${summary.totalHours.toFixed(1)}h`;
|
||||
@@ -559,12 +429,12 @@ async function renderModalDataFromSummary(workers, summary) {
|
||||
}
|
||||
|
||||
// 작업자 리스트 렌더링
|
||||
if (allWorkersList.length === 0) {
|
||||
if (allModalWorkers.length === 0) {
|
||||
elements.modalWorkersList.innerHTML = '<div class="empty-state">등록된 작업자가 없습니다.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
const workersHtml = allWorkersList.map(worker => {
|
||||
const workersHtml = allModalWorkers.map(worker => {
|
||||
// 상태 텍스트 및 색상 결정 (에러가 있어도 작업시간 기준으로 판단)
|
||||
let statusText = '미입력';
|
||||
let statusClass = 'incomplete';
|
||||
@@ -603,13 +473,13 @@ async function renderModalDataFromSummary(workers, summary) {
|
||||
|
||||
// 삭제 버튼 (관리자/그룹장만 표시, 작업이 있는 경우에만)
|
||||
const deleteBtn = isAdmin && worker.totalWorkCount > 0 ? `
|
||||
<button class="btn-delete-worker-work" onclick="event.stopPropagation(); deleteWorkerDayWork(${worker.workerId}, '${currentModalDate}', '${worker.workerName}')" title="이 작업자의 해당 날짜 작업 전체 삭제">
|
||||
<button class="btn-delete-worker-work" onclick="event.stopPropagation(); deleteWorkerDayWork(${worker.workerId}, '${CalendarState.currentModalDate}', '${worker.workerName}')" title="이 작업자의 해당 날짜 작업 전체 삭제">
|
||||
🗑️
|
||||
</button>
|
||||
` : '';
|
||||
|
||||
return `
|
||||
<div class="worker-card ${statusClass}" onclick="openWorkerModal(${worker.workerId}, '${currentModalDate}')">
|
||||
<div class="worker-card ${statusClass}" onclick="openWorkerModal(${worker.workerId}, '${CalendarState.currentModalDate}')">
|
||||
<div class="worker-avatar">
|
||||
<div class="avatar-circle">
|
||||
<span class="avatar-text">${initial}</span>
|
||||
@@ -636,7 +506,7 @@ async function renderModalDataFromSummary(workers, summary) {
|
||||
</div>
|
||||
<div class="worker-actions">
|
||||
${deleteBtn}
|
||||
<button class="btn-work-entry" onclick="event.stopPropagation(); openWorkerModal(${worker.workerId}, '${currentModalDate}')" title="작업입력">
|
||||
<button class="btn-work-entry" onclick="event.stopPropagation(); openWorkerModal(${worker.workerId}, '${CalendarState.currentModalDate}')" title="작업입력">
|
||||
작업입력
|
||||
</button>
|
||||
</div>
|
||||
@@ -789,7 +659,7 @@ function filterWorkersList() {
|
||||
function closeDailyWorkModal() {
|
||||
elements.dailyWorkModal.style.display = 'none';
|
||||
document.body.style.overflow = '';
|
||||
currentModalDate = null;
|
||||
CalendarState.currentModalDate = null;
|
||||
}
|
||||
|
||||
// 로딩 표시
|
||||
@@ -848,16 +718,16 @@ async function deleteWorkerDayWork(workerId, date, workerName) {
|
||||
showToast('작업을 삭제하는 중...', 'info');
|
||||
|
||||
// 날짜+작업자별 전체 삭제 API 호출
|
||||
const result = await window.apiCall(`/daily-work-reports/date/${date}/worker/${workerId}`, 'DELETE');
|
||||
const result = await CalendarAPI.deleteWorkerDayWork(workerId, date);
|
||||
|
||||
console.log('✅ 작업 삭제 성공:', result);
|
||||
showToast(`${workerName}의 ${date} 작업이 삭제되었습니다.`, 'success');
|
||||
|
||||
// 모달 데이터 새로고침
|
||||
await openDailyWorkModal(currentModalDate);
|
||||
await openDailyWorkModal(CalendarState.currentModalDate);
|
||||
|
||||
// 캘린더도 새로고침
|
||||
await loadCalendarData();
|
||||
await renderCalendar();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 작업 삭제 실패:', error);
|
||||
@@ -869,7 +739,7 @@ async function deleteWorkerDayWork(workerId, date, workerName) {
|
||||
async function openWorkerModal(workerId, date) {
|
||||
try {
|
||||
// 작업자 정보 찾기
|
||||
const worker = allWorkers.find(w => w.worker_id === workerId);
|
||||
const worker = CalendarState.allWorkers.find(w => w.worker_id === workerId);
|
||||
if (!worker) {
|
||||
showToast('작업자 정보를 찾을 수 없습니다.', 'error');
|
||||
return;
|
||||
@@ -1083,8 +953,8 @@ async function saveWorkEntry() {
|
||||
await renderCalendar();
|
||||
|
||||
// 현재 열린 모달이 있다면 새로고침
|
||||
if (currentModalDate) {
|
||||
await openDailyWorkModal(currentModalDate);
|
||||
if (CalendarState.currentModalDate) {
|
||||
await openDailyWorkModal(CalendarState.currentModalDate);
|
||||
}
|
||||
} else {
|
||||
const action = editingWorkId ? '수정' : '저장';
|
||||
@@ -1106,7 +976,7 @@ function closeDailyWorkModal() {
|
||||
}
|
||||
|
||||
// 전역 변수로 작업자 목록 저장
|
||||
let allWorkers = [];
|
||||
// let allWorkers = []; // Now in CalendarState
|
||||
|
||||
// 시간 업데이트 함수
|
||||
function updateCurrentTime() {
|
||||
@@ -1299,13 +1169,13 @@ async function loadExistingWorks(workerId, date) {
|
||||
}
|
||||
}
|
||||
|
||||
existingWorks = workerWorks;
|
||||
CalendarState.existingWorks = workerWorks;
|
||||
renderExistingWorks();
|
||||
updateTabCounter();
|
||||
|
||||
} catch (error) {
|
||||
console.error('기존 작업 로드 오류:', error);
|
||||
existingWorks = [];
|
||||
CalendarState.existingWorks = [];
|
||||
renderExistingWorks();
|
||||
updateTabCounter();
|
||||
}
|
||||
@@ -1313,7 +1183,7 @@ async function loadExistingWorks(workerId, date) {
|
||||
|
||||
// 기존 작업 목록 렌더링
|
||||
function renderExistingWorks() {
|
||||
console.log('🎨 작업 목록 렌더링 시작:', existingWorks);
|
||||
console.log('🎨 작업 목록 렌더링 시작:', CalendarState.existingWorks);
|
||||
|
||||
const existingWorkList = document.getElementById('existingWorkList');
|
||||
const noExistingWork = document.getElementById('noExistingWork');
|
||||
@@ -1326,15 +1196,15 @@ function renderExistingWorks() {
|
||||
}
|
||||
|
||||
// 총 작업 시간 계산
|
||||
const totalHours = existingWorks.reduce((sum, work) => sum + parseFloat(work.work_hours || 0), 0);
|
||||
const totalHours = CalendarState.existingWorks.reduce((sum, work) => sum + parseFloat(work.work_hours || 0), 0);
|
||||
|
||||
console.log(`📊 작업 통계: ${existingWorks.length}건, 총 ${totalHours}시간`);
|
||||
console.log(`📊 작업 통계: ${CalendarState.existingWorks.length}건, 총 ${totalHours}시간`);
|
||||
|
||||
// 요약 정보 업데이트
|
||||
if (totalWorkCount) totalWorkCount.textContent = existingWorks.length;
|
||||
if (totalWorkCount) totalWorkCount.textContent = CalendarState.existingWorks.length;
|
||||
if (totalWorkHours) totalWorkHours.textContent = totalHours.toFixed(1);
|
||||
|
||||
if (existingWorks.length === 0) {
|
||||
if (CalendarState.existingWorks.length === 0) {
|
||||
existingWorkList.style.display = 'none';
|
||||
if (noExistingWork) noExistingWork.style.display = 'block';
|
||||
console.log('ℹ️ 작업이 없어서 빈 상태 표시');
|
||||
@@ -1345,7 +1215,7 @@ function renderExistingWorks() {
|
||||
if (noExistingWork) noExistingWork.style.display = 'none';
|
||||
|
||||
// 각 작업 데이터 상세 로그
|
||||
existingWorks.forEach((work, index) => {
|
||||
CalendarState.existingWorks.forEach((work, index) => {
|
||||
console.log(`📋 작업 ${index + 1}:`, {
|
||||
id: work.id,
|
||||
project_name: work.project_name,
|
||||
@@ -1357,7 +1227,7 @@ function renderExistingWorks() {
|
||||
});
|
||||
|
||||
// 작업 목록 HTML 생성
|
||||
const worksHtml = existingWorks.map((work, index) => {
|
||||
const worksHtml = CalendarState.existingWorks.map((work, index) => {
|
||||
const workItemHtml = `
|
||||
<div class="work-item" data-work-id="${work.id}">
|
||||
<div class="work-item-header">
|
||||
@@ -1394,8 +1264,8 @@ function renderExistingWorks() {
|
||||
const renderedItems = existingWorkList.querySelectorAll('.work-item');
|
||||
console.log(`✅ 렌더링 완료: ${renderedItems.length}개 작업 아이템이 DOM에 추가됨`);
|
||||
|
||||
if (renderedItems.length !== existingWorks.length) {
|
||||
console.error(`⚠️ 렌더링 불일치: 데이터 ${existingWorks.length}건 vs DOM ${renderedItems.length}개`);
|
||||
if (renderedItems.length !== CalendarState.existingWorks.length) {
|
||||
console.error(`⚠️ 렌더링 불일치: 데이터 ${CalendarState.existingWorks.length}건 vs DOM ${renderedItems.length}개`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1403,20 +1273,20 @@ function renderExistingWorks() {
|
||||
function updateTabCounter() {
|
||||
const existingTabBtn = document.querySelector('[data-tab="existing"]');
|
||||
if (existingTabBtn) {
|
||||
existingTabBtn.innerHTML = `📋 기존 작업 (${existingWorks.length}건)`;
|
||||
existingTabBtn.innerHTML = `📋 기존 작업 (${CalendarState.existingWorks.length}건)`;
|
||||
}
|
||||
}
|
||||
|
||||
// 작업 수정
|
||||
function editWork(workId) {
|
||||
const work = existingWorks.find(w => w.id === workId);
|
||||
const work = CalendarState.existingWorks.find(w => w.id === workId);
|
||||
if (!work) {
|
||||
showToast('작업 정보를 찾을 수 없습니다.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// 수정 모드로 전환
|
||||
currentEditingWork = work;
|
||||
CalendarState.currentEditingWork = work;
|
||||
|
||||
// 새 작업 탭으로 전환
|
||||
switchTab('new');
|
||||
@@ -1439,7 +1309,7 @@ function editWork(workId) {
|
||||
|
||||
// 작업 삭제 확인
|
||||
function confirmDeleteWork(workId) {
|
||||
const work = existingWorks.find(w => w.id === workId);
|
||||
const work = CalendarState.existingWorks.find(w => w.id === workId);
|
||||
if (!work) {
|
||||
showToast('작업 정보를 찾을 수 없습니다.', 'error');
|
||||
return;
|
||||
@@ -1464,8 +1334,8 @@ async function deleteWorkById(workId) {
|
||||
await loadExistingWorks(workerId, date);
|
||||
|
||||
// 현재 열린 모달이 있다면 새로고침
|
||||
if (currentModalDate) {
|
||||
await openDailyWorkModal(currentModalDate);
|
||||
if (CalendarState.currentModalDate) {
|
||||
await openDailyWorkModal(CalendarState.currentModalDate);
|
||||
}
|
||||
} else {
|
||||
showToast(response.message || '작업 삭제에 실패했습니다.', 'error');
|
||||
@@ -1478,7 +1348,7 @@ async function deleteWorkById(workId) {
|
||||
|
||||
// 작업 폼 초기화
|
||||
function resetWorkForm() {
|
||||
currentEditingWork = null;
|
||||
CalendarState.currentEditingWork = null;
|
||||
|
||||
// 폼 필드 초기화
|
||||
document.getElementById('editingWorkId').value = '';
|
||||
@@ -1496,8 +1366,8 @@ function resetWorkForm() {
|
||||
|
||||
// 작업 삭제 (수정 모드에서)
|
||||
function deleteWork() {
|
||||
if (currentEditingWork) {
|
||||
confirmDeleteWork(currentEditingWork.id);
|
||||
if (CalendarState.currentEditingWork) {
|
||||
confirmDeleteWork(CalendarState.currentEditingWork.id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -337,6 +337,8 @@
|
||||
<script src="/js/api-config.js?v=13"></script>
|
||||
<script src="/js/auth-check.js?v=13"></script>
|
||||
<script src="/js/load-navbar.js?v=4"></script>
|
||||
<script src="/js/modules/calendar/CalendarState.js?v=1"></script>
|
||||
<script src="/js/modules/calendar/CalendarAPI.js?v=1"></script>
|
||||
<script src="/js/work-report-calendar.js?v=41"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user