Files
TK-FB-Project/web-ui/js/daily-work-report.js
Hyungi Ahn 33307bb243 enhance: 작업 입력 UI 대폭 개선 - 모던하고 직관적인 인터페이스 구현
🎨 모던한 카드형 레이아웃:
1. 작업 항목 디자인 완전 개편:
   - 그라데이션 배경 (primary → tertiary)
   - 상단 컬러 바 호버 효과
   - 향상된 그림자 및 호버 애니메이션
   - 2xl 테두리 반경으로 부드러운 외관

2. 폼 필드 그룹화:
   - form-field-group 컨테이너 도입
   - 아이콘과 라벨 조합으로 직관성 향상
   - 포커스 상태 시각적 피드백
   - 호버 효과로 상호작용성 증대

 에러 유형 조건부 표시 개선:
1. 스마트한 UI 로직:
   - 업무 상태가 '에러'일 때만 에러 유형 섹션 표시
   - 부드러운 슬라이드 다운 애니메이션 (0.4s cubic-bezier)
   - opacity, max-height, transform 조합 애니메이션

2. 시각적 구분:
   - 에러 섹션: error-50 → warning-50 그라데이션
   - 에러 테두리: error-200 컬러
   - 에러 아이콘 및 라벨: error-500/700 컬러

🚀 빠른 시간 버튼 고도화:
1. 프리미엄 디자인:
   - 그라데이션 배경 (primary-100 → primary-200)
   - 호버 시: primary-500 → primary-600 그라데이션
   - 반짝이는 효과 (::before 슬라이드 애니메이션)
   - 3D 변형 효과 (scale, translateY)

2. 향상된 상호작용:
   - 30분 옵션 추가 (0.5시간)
   - 클릭 시 스케일 애니메이션
   - 중앙 정렬 및 최소 너비 설정
   - cubic-bezier 전환 효과

🎯 사용자 경험 개선:
1. 직관적인 인터페이스:
   - 아이콘으로 필드 구분 (🏗️ 프로젝트, ⚙️ 작업유형, 📊 업무상태, ⚠️ 에러유형,  시간)
   - 명확한 플레이스홀더 텍스트
   - 논리적인 필드 배치 (2열 그리드)

2. 반응형 최적화:
   - 모바일에서 적절한 패딩 및 폰트 크기
   - 터치 친화적 버튼 크기
   - 유연한 그리드 레이아웃

🔧 기술적 개선:
1. JavaScript 로직 강화:
   - setupWorkEntryEvents 함수 완전 재작성
   - 폼 필드 포커스 효과 추가
   - 에러 타입 조건부 표시 로직 개선
   - 버튼 클릭 피드백 애니메이션

2. CSS 아키텍처:
   - 컴포넌트 기반 스타일링
   - CSS 변수 활용한 일관된 디자인
   - 애니메이션 키프레임 정의
   - 계층적 스타일 구조

🎯 결과:
- 허접했던 UI → 프로페셔널한 모던 인터페이스
- 에러 유형 조건부 표시로 사용성 대폭 향상
- 직관적이고 아름다운 작업 입력 경험
- 대시보드와 완벽한 디자인 일관성

테스트: http://localhost:20000/pages/common/daily-work-report.html
2025-11-03 13:02:30 +09:00

964 lines
31 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})">
<i class="fas fa-times"></i>
</button>
</div>
<div class="work-entry-grid">
<div class="form-field-group">
<div class="form-field-label">
<span class="form-field-icon">🏗️</span>
프로젝트
</div>
<select class="form-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-field-group">
<div class="form-field-label">
<span class="form-field-icon">⚙️</span>
작업 유형
</div>
<select class="form-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-full">
<div class="form-field-group">
<div class="form-field-label">
<span class="form-field-icon">📊</span>
업무 상태
</div>
<select class="form-select work-status-select" required>
<option value="">업무 상태를 선택하세요</option>
${workStatusTypes.map(ws => `<option value="${ws.id}">${ws.name}</option>`).join('')}
</select>
</div>
</div>
<div class="error-type-section work-entry-full">
<div class="form-field-label">
<span class="form-field-icon">⚠️</span>
에러 유형
</div>
<select class="form-select error-type-select">
<option value="">에러 유형을 선택하세요</option>
${errorTypes.map(et => `<option value="${et.id}">${et.name}</option>`).join('')}
</select>
</div>
<div class="time-input-section work-entry-full">
<div class="form-field-label">
<span class="form-field-icon">⏰</span>
작업 시간 (시간)
</div>
<input type="number" class="form-select time-input"
placeholder="작업 시간을 입력하세요"
min="0.25"
max="24"
step="0.25"
value="1.00"
required>
<div class="quick-time-buttons">
<button type="button" class="quick-time-btn" data-hours="0.5">30분</button>
<button type="button" class="quick-time-btn" data-hours="1">1시간</button>
<button type="button" class="quick-time-btn" data-hours="2">2시간</button>
<button type="button" class="quick-time-btn" data-hours="4">4시간</button>
<button type="button" class="quick-time-btn" data-hours="8">8시간</button>
</div>
</div>
`;
container.appendChild(entryDiv);
setupWorkEntryEvents(entryDiv);
}
// 작업 항목 이벤트 설정
function setupWorkEntryEvents(entryDiv) {
const timeInput = entryDiv.querySelector('.time-input');
const workStatusSelect = entryDiv.querySelector('.work-status-select');
const errorTypeSection = entryDiv.querySelector('.error-type-section');
const errorTypeSelect = entryDiv.querySelector('.error-type-select');
// 시간 입력 이벤트
timeInput.addEventListener('input', updateTotalHours);
// 빠른 시간 버튼 이벤트
entryDiv.querySelectorAll('.quick-time-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
e.preventDefault();
timeInput.value = btn.dataset.hours;
updateTotalHours();
// 버튼 클릭 효과
btn.style.transform = 'scale(0.95)';
setTimeout(() => {
btn.style.transform = '';
}, 150);
});
});
// 업무 상태 변경 시 에러 유형 섹션 토글
workStatusSelect.addEventListener('change', (e) => {
const isError = e.target.value === '2'; // 에러 상태 ID가 2라고 가정
if (isError) {
errorTypeSection.classList.add('visible');
errorTypeSelect.required = true;
// 에러 상태일 때 시각적 피드백
errorTypeSection.style.animation = 'slideDown 0.4s ease-out';
} else {
errorTypeSection.classList.remove('visible');
errorTypeSelect.required = false;
errorTypeSelect.value = '';
}
});
// 폼 필드 포커스 효과
entryDiv.querySelectorAll('.form-field-group').forEach(group => {
const input = group.querySelector('select, input');
if (input) {
input.addEventListener('focus', () => {
group.classList.add('focused');
});
input.addEventListener('blur', () => {
group.classList.remove('focused');
});
}
});
}
// 작업 항목 제거
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;