Files
TK-FB-Project/web-ui/js/daily-work-report.js
Hyungi Ahn bad5584988 enhance: daily-work-report.html 모던 UI 대폭 개선 - 대시보드 일관성 및 사용성 향상
🎨 모던 디자인 시스템 적용:
1. 대시보드와 일관된 디자인 언어:
   - design-system.css 활용한 통일된 색상, 타이포그래피
   - 동일한 카드, 버튼, 애니메이션 스타일
   - 일관된 간격, 그림자, 테두리 반경

2. 새로운 CSS 파일 분리:
   - daily-work-report.css 생성 (673줄)
   - 인라인 스타일 완전 제거 (926줄 → 0줄)
   - 모듈화된 스타일 관리

🚀 사용자 경험 대폭 개선:
1. 모던한 진행 단계 표시:
   - 상단 3단계 진행 바 추가
   - 실시간 단계 상태 표시 (활성/완료)
   - 시각적 진행도 피드백

2. 개선된 레이아웃 구조:
   - 헤더: 그라데이션 배경, 중앙 정렬
   - 메인: 최대 너비 1200px, 중앙 배치
   - 카드: 일관된 패딩, 그림자, 테두리

3. 향상된 작업자 선택 UI:
   - worker-btn → worker-card 클래스 변경
   - 카드형 디자인으로 시각적 개선
   - 호버 효과: 상단 컬러 바, 배경 변화
   - 선택 상태: 그라데이션 배경, 흰색 텍스트

 인터랙션 개선:
1. 버튼 디자인 통일:
   - 일관된 패딩, 높이 (48px)
   - 호버 효과: translateY(-2px), 그림자 확대
   - 색상: primary, success, secondary 통일

2. 폼 요소 개선:
   - form-input 클래스로 통일된 스타일
   - 포커스 상태: 테두리 색상, 그림자 효과
   - 라벨: 명확한 계층 구조

3. 애니메이션 효과:
   - 단계 전환: opacity, transform 애니메이션
   - 카드 호버: translateY, 그림자 변화
   - 부드러운 전환: var(--transition-normal)

📱 반응형 디자인 최적화:
1. 모바일 (768px 이하):
   - 진행 단계: 세로 배치
   - 작업자 그리드: 1열로 변경
   - 패딩, 폰트 크기 조정

2. 태블릿 (1024px 이하):
   - 적절한 그리드 컬럼 수 조정
   - 터치 친화적 버튼 크기

🔧 기술적 개선:
1. JavaScript 업데이트:
   - updateProgressSteps() 함수 추가
   - 진행 단계 실시간 업데이트
   - CSS 클래스명 변경 반영

2. HTML 구조 개선:
   - 시맨틱 태그 활용 (header, main)
   - 접근성 향상 (label, aria 속성)
   - 깔끔한 마크업 구조

🎯 결과:
- 대시보드와 완벽한 디자인 일관성
- 직관적이고 사용하기 쉬운 인터페이스
- 모든 디바이스에서 최적화된 경험
- 프로페셔널한 시각적 품질

테스트: http://localhost:20000/pages/common/daily-work-report.html
2025-11-03 12:56:45 +09:00

917 lines
30 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// daily-work-report.js - 브라우저 호환 버전
// =================================================================
// 🌐 API 설정 (window 객체에서 가져오기)
// =================================================================
// API 설정은 api-config.js에서 window 객체에 설정됨
// 전역 변수
let workTypes = [];
let workStatusTypes = [];
let errorTypes = [];
let workers = [];
let projects = [];
let selectedWorkers = new Set();
let workEntryCounter = 0;
let currentStep = 1;
let editingWorkId = null; // 수정 중인 작업 ID
// 한국 시간 기준 오늘 날짜 가져오기
function getKoreaToday() {
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, '0');
const day = String(today.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
// 현재 로그인한 사용자 정보 가져오기
function getCurrentUser() {
try {
const token = localStorage.getItem('token');
if (!token) return null;
const payloadBase64 = token.split('.')[1];
if (payloadBase64) {
const payload = JSON.parse(atob(payloadBase64));
console.log('토큰에서 추출한 사용자 정보:', payload);
return payload;
}
} catch (error) {
console.log('토큰에서 사용자 정보 추출 실패:', error);
}
try {
const userInfo = localStorage.getItem('user') || localStorage.getItem('userInfo') || localStorage.getItem('currentUser');
if (userInfo) {
const parsed = JSON.parse(userInfo);
console.log('localStorage에서 가져온 사용자 정보:', parsed);
return parsed;
}
} catch (error) {
console.log('localStorage에서 사용자 정보 가져오기 실패:', error);
}
return null;
}
// 메시지 표시
function showMessage(message, type = 'info') {
const container = document.getElementById('message-container');
container.innerHTML = `<div class="message ${type}">${message}</div>`;
if (type === 'success') {
setTimeout(() => {
hideMessage();
}, 5000);
}
}
function hideMessage() {
document.getElementById('message-container').innerHTML = '';
}
// 단계 이동
function goToStep(stepNumber) {
for (let i = 1; i <= 3; i++) {
const step = document.getElementById(`step${i}`);
if (step) {
step.classList.remove('active', 'completed');
if (i < stepNumber) {
step.classList.add('completed');
const stepNum = step.querySelector('.step-number');
if (stepNum) stepNum.classList.add('completed');
} else if (i === stepNumber) {
step.classList.add('active');
}
}
}
// 진행 단계 표시 업데이트
updateProgressSteps(stepNumber);
currentStep = stepNumber;
}
// 진행 단계 표시 업데이트
function updateProgressSteps(currentStepNumber) {
for (let i = 1; i <= 3; i++) {
const progressStep = document.getElementById(`progressStep${i}`);
if (progressStep) {
progressStep.classList.remove('active', 'completed');
if (i < currentStepNumber) {
progressStep.classList.add('completed');
} else if (i === currentStepNumber) {
progressStep.classList.add('active');
}
}
}
}
// 초기 데이터 로드 (통합 API 사용)
async function loadData() {
try {
showMessage('데이터를 불러오는 중...', 'loading');
console.log('🔗 통합 API 설정을 사용한 기본 데이터 로딩 시작...');
await loadWorkers();
await loadProjects();
await loadWorkTypes();
await loadWorkStatusTypes();
await loadErrorTypes();
console.log('로드된 작업자 수:', workers.length);
console.log('로드된 프로젝트 수:', projects.length);
console.log('작업 유형 수:', workTypes.length);
populateWorkerGrid();
hideMessage();
} catch (error) {
console.error('데이터 로드 실패:', error);
showMessage('데이터 로드 중 오류가 발생했습니다: ' + error.message, 'error');
}
}
async function loadWorkers() {
try {
console.log('Workers API 호출 중... (통합 API 사용)');
const data = await window.apiCall(`${window.API}/workers`);
workers = Array.isArray(data) ? data : (data.data || data.workers || []);
console.log('✅ Workers 로드 성공:', workers.length);
} catch (error) {
console.error('작업자 로딩 오류:', error);
throw error;
}
}
async function loadProjects() {
try {
console.log('Projects API 호출 중... (통합 API 사용)');
const data = await window.apiCall(`${window.API}/projects`);
projects = Array.isArray(data) ? data : (data.data || data.projects || []);
console.log('✅ Projects 로드 성공:', projects.length);
} catch (error) {
console.error('프로젝트 로딩 오류:', error);
throw error;
}
}
async function loadWorkTypes() {
try {
const data = await window.apiCall(`${window.API}/daily-work-reports/work-types`);
if (Array.isArray(data) && data.length > 0) {
workTypes = data;
console.log('✅ 작업 유형 API 사용 (통합 설정)');
return;
}
throw new Error('API 실패');
} catch (error) {
console.log('⚠️ 작업 유형 API 사용 불가, 기본값 사용');
workTypes = [
{id: 1, name: 'Base'},
{id: 2, name: 'Vessel'},
{id: 3, name: 'Piping'}
];
}
}
async function loadWorkStatusTypes() {
try {
const data = await window.apiCall(`${window.API}/daily-work-reports/work-status-types`);
if (Array.isArray(data) && data.length > 0) {
workStatusTypes = data;
console.log('✅ 업무 상태 유형 API 사용 (통합 설정)');
return;
}
throw new Error('API 실패');
} catch (error) {
console.log('⚠️ 업무 상태 유형 API 사용 불가, 기본값 사용');
workStatusTypes = [
{id: 1, name: '정규'},
{id: 2, name: '에러'}
];
}
}
async function loadErrorTypes() {
try {
const data = await window.apiCall(`${window.API}/daily-work-reports/error-types`);
if (Array.isArray(data) && data.length > 0) {
errorTypes = data;
console.log('✅ 에러 유형 API 사용 (통합 설정)');
return;
}
throw new Error('API 실패');
} catch (error) {
console.log('⚠️ 에러 유형 API 사용 불가, 기본값 사용');
errorTypes = [
{id: 1, name: '설계미스'},
{id: 2, name: '외주작업 불량'},
{id: 3, name: '입고지연'},
{id: 4, name: '작업 불량'}
];
}
}
// 작업자 그리드 생성
function populateWorkerGrid() {
const grid = document.getElementById('workerGrid');
grid.innerHTML = '';
workers.forEach(worker => {
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'worker-card';
btn.textContent = worker.worker_name;
btn.dataset.id = worker.worker_id;
btn.addEventListener('click', () => {
toggleWorkerSelection(worker.worker_id, btn);
});
grid.appendChild(btn);
});
}
// 작업자 선택 토글
function toggleWorkerSelection(workerId, btnElement) {
if (selectedWorkers.has(workerId)) {
selectedWorkers.delete(workerId);
btnElement.classList.remove('selected');
} else {
selectedWorkers.add(workerId);
btnElement.classList.add('selected');
}
const nextBtn = document.getElementById('nextStep2');
nextBtn.disabled = selectedWorkers.size === 0;
}
// 작업 항목 추가
function addWorkEntry() {
const container = document.getElementById('workEntriesList');
workEntryCounter++;
const entryDiv = document.createElement('div');
entryDiv.className = 'work-entry';
entryDiv.dataset.id = workEntryCounter;
entryDiv.innerHTML = `
<div class="work-entry-header">
<div class="work-entry-title">작업 ${workEntryCounter}</div>
<button type="button" class="remove-work-btn" onclick="removeWorkEntry(${workEntryCounter})">×</button>
</div>
<div class="work-entry-row">
<div class="form-group">
<label>🏗️ 프로젝트</label>
<select class="large-select project-select" required>
<option value="">프로젝트 선택</option>
${projects.map(p => `<option value="${p.project_id}">${p.project_name}</option>`).join('')}
</select>
</div>
<div class="form-group">
<label>⚙️ 작업 유형</label>
<select class="large-select work-type-select" required>
<option value="">작업 유형 선택</option>
${workTypes.map(wt => `<option value="${wt.id}">${wt.name}</option>`).join('')}
</select>
</div>
</div>
<div class="work-entry-row">
<div class="form-group">
<label>📊 업무 상태</label>
<select class="large-select work-status-select" required>
<option value="">업무 상태 선택</option>
${workStatusTypes.map(ws => `<option value="${ws.id}">${ws.name}</option>`).join('')}
</select>
</div>
<div class="form-group error-type-section">
<label>❌ 에러 유형</label>
<select class="large-select error-type-select">
<option value="">에러 유형 선택</option>
${errorTypes.map(et => `<option value="${et.id}">${et.name}</option>`).join('')}
</select>
</div>
</div>
<div class="time-input-row">
<div class="form-group">
<label>⏰ 작업 시간</label>
<input type="number" class="large-select time-input"
placeholder="시간 입력"
min="0"
max="24"
step="0.5"
required>
<div class="quick-time-buttons">
<div class="quick-time-btn" data-hours="1">1시간</div>
<div class="quick-time-btn" data-hours="2">2시간</div>
<div class="quick-time-btn" data-hours="4">4시간</div>
<div class="quick-time-btn" data-hours="8">8시간</div>
</div>
</div>
</div>
`;
container.appendChild(entryDiv);
setupWorkEntryEvents(entryDiv);
}
// 작업 항목 이벤트 설정
function setupWorkEntryEvents(entryDiv) {
const timeInput = entryDiv.querySelector('.time-input');
timeInput.addEventListener('input', updateTotalHours);
entryDiv.querySelectorAll('.quick-time-btn').forEach(btn => {
btn.addEventListener('click', () => {
timeInput.value = btn.dataset.hours;
updateTotalHours();
});
});
const workStatusSelect = entryDiv.querySelector('.work-status-select');
const errorTypeSection = entryDiv.querySelector('.error-type-section');
workStatusSelect.addEventListener('change', (e) => {
if (e.target.value === '2') {
errorTypeSection.classList.add('visible');
errorTypeSection.querySelector('.error-type-select').required = true;
} else {
errorTypeSection.classList.remove('visible');
errorTypeSection.querySelector('.error-type-select').required = false;
errorTypeSection.querySelector('.error-type-select').value = '';
}
});
}
// 작업 항목 제거
function removeWorkEntry(id) {
const entry = document.querySelector(`[data-id="${id}"]`);
if (entry) {
entry.remove();
updateTotalHours();
}
}
// 총 시간 업데이트
function updateTotalHours() {
const timeInputs = document.querySelectorAll('.time-input');
let total = 0;
timeInputs.forEach(input => {
const value = parseFloat(input.value) || 0;
total += value;
});
const display = document.getElementById('totalHoursDisplay');
display.textContent = `총 작업시간: ${total}시간`;
if (total > 24) {
display.style.background = 'linear-gradient(135deg, #e74c3c 0%, #c0392b 100%)';
display.textContent += ' ⚠️ 24시간 초과';
} else {
display.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
}
}
// 저장 함수 (통합 API 사용)
async function saveWorkReport() {
const reportDate = document.getElementById('reportDate').value;
if (!reportDate || selectedWorkers.size === 0) {
showMessage('날짜와 작업자를 선택해주세요.', 'error');
return;
}
const entries = document.querySelectorAll('.work-entry');
if (entries.length === 0) {
showMessage('최소 하나의 작업을 추가해주세요.', 'error');
return;
}
const newWorkEntries = [];
for (const entry of entries) {
const projectId = entry.querySelector('.project-select').value;
const workTypeId = entry.querySelector('.work-type-select').value;
const workStatusId = entry.querySelector('.work-status-select').value;
const errorTypeId = entry.querySelector('.error-type-select').value;
const workHours = entry.querySelector('.time-input').value;
if (!projectId || !workTypeId || !workStatusId || !workHours) {
showMessage('모든 작업 항목을 완성해주세요.', 'error');
return;
}
if (workStatusId === '2' && !errorTypeId) {
showMessage('에러 상태인 경우 에러 유형을 선택해주세요.', 'error');
return;
}
newWorkEntries.push({
project_id: parseInt(projectId),
work_type_id: parseInt(workTypeId),
work_status_id: parseInt(workStatusId),
error_type_id: errorTypeId ? parseInt(errorTypeId) : null,
work_hours: parseFloat(workHours)
});
}
try {
const submitBtn = document.getElementById('submitBtn');
submitBtn.disabled = true;
submitBtn.textContent = '💾 저장 중...';
const currentUser = getCurrentUser();
let totalSaved = 0;
let totalFailed = 0;
const failureDetails = [];
for (const workerId of selectedWorkers) {
const requestData = {
report_date: reportDate,
worker_id: parseInt(workerId),
work_entries: newWorkEntries,
created_by: currentUser?.user_id || currentUser?.id
};
console.log('전송 데이터 (통합 API 사용):', requestData);
try {
const result = await window.apiCall(`${window.API}/daily-work-reports`, {
method: 'POST',
body: JSON.stringify(requestData)
});
console.log('✅ 저장 성공 (통합 API):', result);
totalSaved++;
} catch (error) {
console.error('❌ 저장 실패:', error);
totalFailed++;
const workerName = workers.find(w => w.worker_id == workerId)?.worker_name || '알 수 없음';
failureDetails.push(`${workerName}: ${error.message}`);
}
}
if (totalSaved > 0 && totalFailed === 0) {
showMessage(`${totalSaved}명의 작업보고서가 성공적으로 저장되었습니다!`, 'success');
} else if (totalSaved > 0 && totalFailed > 0) {
showMessage(`⚠️ ${totalSaved}명 성공, ${totalFailed}명 실패. 실패: ${failureDetails.join(', ')}`, 'warning');
} else {
showMessage(`❌ 모든 저장이 실패했습니다. 상세: ${failureDetails.join(', ')}`, 'error');
}
if (totalSaved > 0) {
setTimeout(() => {
refreshTodayWorkers();
resetForm();
}, 2000);
}
} catch (error) {
console.error('저장 오류:', error);
showMessage('저장 중 예기치 못한 오류가 발생했습니다: ' + error.message, 'error');
} finally {
const submitBtn = document.getElementById('submitBtn');
submitBtn.disabled = false;
submitBtn.textContent = '💾 작업보고서 저장';
}
}
// 폼 초기화
function resetForm() {
goToStep(1);
selectedWorkers.clear();
document.querySelectorAll('.worker-card.selected').forEach(btn => {
btn.classList.remove('selected');
});
const container = document.getElementById('workEntriesList');
container.innerHTML = '';
workEntryCounter = 0;
updateTotalHours();
document.getElementById('nextStep2').disabled = true;
}
// 당일 작업자 현황 로드 (본인 입력분만) - 통합 API 사용
async function loadTodayWorkers() {
const section = document.getElementById('dailyWorkersSection');
const content = document.getElementById('dailyWorkersContent');
if (!section || !content) {
console.log('당일 현황 섹션이 HTML에 없습니다.');
return;
}
try {
const today = getKoreaToday();
const currentUser = getCurrentUser();
content.innerHTML = '<div class="loading-spinner">📊 내가 입력한 오늘의 작업 현황을 불러오는 중... (통합 API)</div>';
section.style.display = 'block';
// 본인이 입력한 데이터만 조회 (통합 API 사용)
let queryParams = `date=${today}`;
if (currentUser?.user_id) {
queryParams += `&created_by=${currentUser.user_id}`;
} else if (currentUser?.id) {
queryParams += `&created_by=${currentUser.id}`;
}
console.log(`🔒 본인 입력분만 조회 (통합 API): ${API}/daily-work-reports?${queryParams}`);
const rawData = await window.apiCall(`${window.API}/daily-work-reports?${queryParams}`);
console.log('📊 당일 작업 데이터 (통합 API):', rawData);
let data = [];
if (Array.isArray(rawData)) {
data = rawData;
} else if (rawData?.data) {
data = rawData.data;
}
displayMyDailyWorkers(data, today);
} catch (error) {
console.error('당일 작업자 로드 오류:', error);
content.innerHTML = `
<div class="no-data-message">
❌ 오늘의 작업 현황을 불러올 수 없습니다.<br>
<small>${error.message}</small>
</div>
`;
}
}
// 본인 입력 작업자 현황 표시 (수정/삭제 기능 포함)
function displayMyDailyWorkers(data, date) {
const content = document.getElementById('dailyWorkersContent');
if (!Array.isArray(data) || data.length === 0) {
content.innerHTML = `
<div class="no-data-message">
📝 내가 오늘(${date}) 입력한 작업이 없습니다.<br>
<small>새로운 작업을 추가해보세요!</small>
</div>
`;
return;
}
// 작업자별로 데이터 그룹화
const workerGroups = {};
data.forEach(work => {
const workerName = work.worker_name || '미지정';
if (!workerGroups[workerName]) {
workerGroups[workerName] = [];
}
workerGroups[workerName].push(work);
});
const totalWorkers = Object.keys(workerGroups).length;
const totalWorks = data.length;
const headerHtml = `
<div class="daily-workers-header">
<h4>📊 내가 입력한 오늘(${date}) 작업 현황 - 총 ${totalWorkers}명, ${totalWorks}개 작업</h4>
<button class="refresh-btn" onclick="refreshTodayWorkers()">
🔄 새로고침
</button>
</div>
`;
const workersHtml = Object.entries(workerGroups).map(([workerName, works]) => {
const totalHours = works.reduce((sum, work) => {
return sum + parseFloat(work.work_hours || 0);
}, 0);
// 개별 작업 항목들 (수정/삭제 버튼 포함)
const individualWorksHtml = works.map((work) => {
const projectName = work.project_name || '미지정';
const workTypeName = work.work_type_name || '미지정';
const workStatusName = work.work_status_name || '미지정';
const workHours = work.work_hours || 0;
const errorTypeName = work.error_type_name || null;
const workId = work.id;
return `
<div class="individual-work-item">
<div class="work-details-grid">
<div class="detail-item">
<div class="detail-label">🏗️ 프로젝트</div>
<div class="detail-value">${projectName}</div>
</div>
<div class="detail-item">
<div class="detail-label">⚙️ 작업종류</div>
<div class="detail-value">${workTypeName}</div>
</div>
<div class="detail-item">
<div class="detail-label">📊 작업상태</div>
<div class="detail-value">${workStatusName}</div>
</div>
<div class="detail-item">
<div class="detail-label">⏰ 작업시간</div>
<div class="detail-value">${workHours}시간</div>
</div>
${errorTypeName ? `
<div class="detail-item">
<div class="detail-label">❌ 에러유형</div>
<div class="detail-value">${errorTypeName}</div>
</div>
` : ''}
</div>
<div class="action-buttons">
<button class="edit-btn" onclick="editWorkItem('${workId}')">
✏️ 수정
</button>
<button class="delete-btn" onclick="deleteWorkItem('${workId}')">
🗑️ 삭제
</button>
</div>
</div>
`;
}).join('');
return `
<div class="worker-status-item">
<div class="worker-header">
<div class="worker-name">👤 ${workerName}</div>
<div class="worker-total-hours">총 ${totalHours}시간</div>
</div>
<div class="individual-works-container">
${individualWorksHtml}
</div>
</div>
`;
}).join('');
content.innerHTML = headerHtml + '<div class="worker-status-grid">' + workersHtml + '</div>';
}
// 작업 항목 수정 함수 (통합 API 사용)
async function editWorkItem(workId) {
try {
console.log('수정할 작업 ID:', workId);
// 1. 기존 데이터 조회 (통합 API 사용)
showMessage('작업 정보를 불러오는 중... (통합 API)', 'loading');
const workData = await window.apiCall(`${window.API}/daily-work-reports/${workId}`);
console.log('수정할 작업 데이터 (통합 API):', workData);
// 2. 수정 모달 표시
showEditModal(workData);
hideMessage();
} catch (error) {
console.error('작업 정보 조회 오류:', error);
showMessage('작업 정보를 불러올 수 없습니다: ' + error.message, 'error');
}
}
// 수정 모달 표시
function showEditModal(workData) {
editingWorkId = workData.id;
const modalHtml = `
<div class="edit-modal" id="editModal">
<div class="edit-modal-content">
<div class="edit-modal-header">
<h3>✏️ 작업 수정</h3>
<button class="close-modal-btn" onclick="closeEditModal()">×</button>
</div>
<div class="edit-modal-body">
<div class="edit-form-group">
<label>🏗️ 프로젝트</label>
<select class="edit-select" id="editProject">
<option value="">프로젝트 선택</option>
${projects.map(p => `
<option value="${p.project_id}" ${p.project_id == workData.project_id ? 'selected' : ''}>
${p.project_name}
</option>
`).join('')}
</select>
</div>
<div class="edit-form-group">
<label>⚙️ 작업 유형</label>
<select class="edit-select" id="editWorkType">
<option value="">작업 유형 선택</option>
${workTypes.map(wt => `
<option value="${wt.id}" ${wt.id == workData.work_type_id ? 'selected' : ''}>
${wt.name}
</option>
`).join('')}
</select>
</div>
<div class="edit-form-group">
<label>📊 업무 상태</label>
<select class="edit-select" id="editWorkStatus">
<option value="">업무 상태 선택</option>
${workStatusTypes.map(ws => `
<option value="${ws.id}" ${ws.id == workData.work_status_id ? 'selected' : ''}>
${ws.name}
</option>
`).join('')}
</select>
</div>
<div class="edit-form-group" id="editErrorTypeGroup" style="${workData.work_status_id == 2 ? '' : 'display: none;'}">
<label>❌ 에러 유형</label>
<select class="edit-select" id="editErrorType">
<option value="">에러 유형 선택</option>
${errorTypes.map(et => `
<option value="${et.id}" ${et.id == workData.error_type_id ? 'selected' : ''}>
${et.name}
</option>
`).join('')}
</select>
</div>
<div class="edit-form-group">
<label>⏰ 작업 시간</label>
<input type="number" class="edit-input" id="editWorkHours"
value="${workData.work_hours}"
min="0" max="24" step="0.5">
</div>
</div>
<div class="edit-modal-footer">
<button class="btn btn-secondary" onclick="closeEditModal()">취소</button>
<button class="btn btn-success" onclick="saveEditedWork()">💾 저장</button>
</div>
</div>
</div>
`;
document.body.insertAdjacentHTML('beforeend', modalHtml);
// 업무 상태 변경 이벤트
document.getElementById('editWorkStatus').addEventListener('change', (e) => {
const errorTypeGroup = document.getElementById('editErrorTypeGroup');
if (e.target.value === '2') {
errorTypeGroup.style.display = 'block';
} else {
errorTypeGroup.style.display = 'none';
}
});
}
// 수정 모달 닫기
function closeEditModal() {
const modal = document.getElementById('editModal');
if (modal) {
modal.remove();
}
editingWorkId = null;
}
// 수정된 작업 저장 (통합 API 사용)
async function saveEditedWork() {
try {
const projectId = document.getElementById('editProject').value;
const workTypeId = document.getElementById('editWorkType').value;
const workStatusId = document.getElementById('editWorkStatus').value;
const errorTypeId = document.getElementById('editErrorType').value;
const workHours = document.getElementById('editWorkHours').value;
if (!projectId || !workTypeId || !workStatusId || !workHours) {
showMessage('모든 필수 항목을 입력해주세요.', 'error');
return;
}
if (workStatusId === '2' && !errorTypeId) {
showMessage('에러 상태인 경우 에러 유형을 선택해주세요.', 'error');
return;
}
const updateData = {
project_id: parseInt(projectId),
work_type_id: parseInt(workTypeId),
work_status_id: parseInt(workStatusId),
error_type_id: errorTypeId ? parseInt(errorTypeId) : null,
work_hours: parseFloat(workHours)
};
showMessage('작업을 수정하는 중... (통합 API)', 'loading');
const result = await window.apiCall(`${window.API}/daily-work-reports/${editingWorkId}`, {
method: 'PUT',
body: JSON.stringify(updateData)
});
console.log('✅ 수정 성공 (통합 API):', result);
showMessage('✅ 작업이 성공적으로 수정되었습니다!', 'success');
closeEditModal();
refreshTodayWorkers();
} catch (error) {
console.error('❌ 수정 실패:', error);
showMessage('수정 중 오류가 발생했습니다: ' + error.message, 'error');
}
}
// 작업 항목 삭제 함수 (통합 API 사용)
async function deleteWorkItem(workId) {
if (!confirm('정말로 이 작업을 삭제하시겠습니까?\n삭제된 작업은 복구할 수 없습니다.')) {
return;
}
try {
console.log('삭제할 작업 ID:', workId);
showMessage('작업을 삭제하는 중... (통합 API)', 'loading');
// 개별 항목 삭제 API 호출 (본인 작성분만 삭제 가능) - 통합 API 사용
const result = await window.apiCall(`${window.API}/daily-work-reports/my-entry/${workId}`, {
method: 'DELETE'
});
console.log('✅ 삭제 성공 (통합 API):', result);
showMessage('✅ 작업이 성공적으로 삭제되었습니다!', 'success');
// 화면 새로고침
refreshTodayWorkers();
} catch (error) {
console.error('❌ 삭제 실패:', error);
showMessage('삭제 중 오류가 발생했습니다: ' + error.message, 'error');
}
}
// 오늘 현황 새로고침
function refreshTodayWorkers() {
loadTodayWorkers();
}
// 이벤트 리스너 설정
function setupEventListeners() {
document.getElementById('nextStep1').addEventListener('click', () => {
const dateInput = document.getElementById('reportDate');
if (dateInput && dateInput.value) {
goToStep(2);
} else {
showMessage('날짜를 선택해주세요.', 'error');
}
});
document.getElementById('nextStep2').addEventListener('click', () => {
if (selectedWorkers.size > 0) {
goToStep(3);
addWorkEntry();
} else {
showMessage('작업자를 선택해주세요.', 'error');
}
});
document.getElementById('addWorkBtn').addEventListener('click', addWorkEntry);
document.getElementById('submitBtn').addEventListener('click', saveWorkReport);
}
// 초기화
async function init() {
try {
const token = localStorage.getItem('token');
if (!token || token === 'undefined') {
showMessage('로그인이 필요합니다.', 'error');
localStorage.removeItem('token');
setTimeout(() => {
window.location.href = '/';
}, 2000);
return;
}
document.getElementById('reportDate').value = getKoreaToday();
await loadData();
setupEventListeners();
loadTodayWorkers();
console.log('✅ 시스템 초기화 완료 (통합 API 설정 적용)');
} catch (error) {
console.error('초기화 오류:', error);
showMessage('초기화 중 오류가 발생했습니다.', 'error');
}
}
// 페이지 로드 시 초기화
document.addEventListener('DOMContentLoaded', init);
// 전역 함수로 노출
window.removeWorkEntry = removeWorkEntry;
window.refreshTodayWorkers = refreshTodayWorkers;
window.editWorkItem = editWorkItem;
window.deleteWorkItem = deleteWorkItem;
window.closeEditModal = closeEditModal;
window.saveEditedWork = saveEditedWork;