210 lines
7.8 KiB
JavaScript
210 lines
7.8 KiB
JavaScript
import { renderCalendar } from '/js/calendar.js';
|
|
import { API, getAuthHeaders, ensureAuthenticated } from '/js/api-config.js';
|
|
|
|
// 인증 확인
|
|
ensureAuthenticated();
|
|
|
|
const calendarEl = document.getElementById('calendar');
|
|
const reportBody = document.getElementById('reportBody');
|
|
let selectedDate = '';
|
|
|
|
// 캘린더 렌더링
|
|
renderCalendar('calendar', (dateStr) => {
|
|
selectedDate = dateStr;
|
|
loadReports();
|
|
});
|
|
|
|
// 보고서 로딩
|
|
async function loadReports() {
|
|
if (!selectedDate) return;
|
|
reportBody.innerHTML = '<tr><td colspan="8">불러오는 중...</td></tr>';
|
|
|
|
try {
|
|
const [wRes, pRes, tRes, rRes] = await Promise.all([
|
|
fetch(`${API}/workers`, { headers: getAuthHeaders() }),
|
|
fetch(`${API}/projects`, { headers: getAuthHeaders() }),
|
|
fetch(`${API}/tasks`, { headers: getAuthHeaders() }),
|
|
fetch(`${API}/workreports?start=${selectedDate}&end=${selectedDate}`, { headers: getAuthHeaders() })
|
|
]);
|
|
|
|
if (![wRes, pRes, tRes, rRes].every(res => res.ok)) throw new Error('불러오기 실패');
|
|
|
|
const [workers, projects, tasks, reports] = await Promise.all([
|
|
wRes.json(), pRes.json(), tRes.json(), rRes.json()
|
|
]);
|
|
|
|
// 배열 체크
|
|
if (!Array.isArray(workers) || !Array.isArray(projects) || !Array.isArray(tasks) || !Array.isArray(reports)) {
|
|
throw new Error('잘못된 데이터 형식');
|
|
}
|
|
|
|
if (!reports.length) {
|
|
reportBody.innerHTML = '<tr><td colspan="8">등록된 보고서가 없습니다.</td></tr>';
|
|
return;
|
|
}
|
|
|
|
const nameMap = Object.fromEntries(workers.map(w => [w.worker_id, w.worker_name]));
|
|
const projMap = Object.fromEntries(projects.map(p => [p.project_id, p.project_name]));
|
|
const taskMap = Object.fromEntries(tasks.map(t => [t.task_id, `${t.category}:${t.subcategory}`]));
|
|
|
|
reportBody.innerHTML = '';
|
|
reports.forEach((r, i) => {
|
|
const tr = document.createElement('tr');
|
|
tr.innerHTML = `
|
|
<td>${i + 1}</td>
|
|
<td>${nameMap[r.worker_id] || r.worker_id}</td>
|
|
<td><select data-id="project">
|
|
${projects.map(p =>
|
|
`<option value="${p.project_id}" ${p.project_id === r.project_id ? 'selected' : ''}>${p.project_name}</option>`
|
|
).join('')}</select></td>
|
|
<td><select data-id="task">
|
|
${tasks.map(t =>
|
|
`<option value="${t.task_id}" ${t.task_id === r.task_id ? 'selected' : ''}>${t.category}:${t.subcategory}</option>`
|
|
).join('')}</select></td>
|
|
<td><input type="number" min="0" step="0.5" value="${r.overtime_hours || ''}" data-id="overtime"></td>
|
|
<td><select data-id="work_details">
|
|
${['근무', '연차', '유급', '반차', '반반차', '조퇴', '휴무'].map(opt =>
|
|
`<option value="${opt}" ${r.work_details === opt ? 'selected' : ''}>${opt}</option>`
|
|
).join('')}</select></td>
|
|
<td><input type="text" value="${r.memo || ''}" data-id="memo"></td>
|
|
<td>
|
|
<button class="action-btn save-btn">저장</button>
|
|
<button class="action-btn delete-btn">삭제</button>
|
|
</td>`;
|
|
|
|
// 저장 버튼
|
|
tr.querySelector('.save-btn').onclick = async () => {
|
|
// 입력값 검증
|
|
const projectId = tr.querySelector('[data-id="project"]').value;
|
|
const taskId = tr.querySelector('[data-id="task"]').value;
|
|
const overtimeHours = tr.querySelector('[data-id="overtime"]').value;
|
|
|
|
if (!projectId || !taskId) {
|
|
alert('❌ 프로젝트와 작업을 선택해주세요.');
|
|
return;
|
|
}
|
|
|
|
// 날짜 형식 처리 - MySQL DATE 형식으로 변환
|
|
const formatDate = (dateStr) => {
|
|
if (!dateStr) return selectedDate;
|
|
|
|
// 이미 YYYY-MM-DD 형식인지 확인
|
|
if (/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
|
|
return dateStr;
|
|
}
|
|
|
|
// ISO 형식이나 다른 형식을 YYYY-MM-DD로 변환
|
|
const date = new Date(dateStr);
|
|
if (isNaN(date.getTime())) {
|
|
return selectedDate; // 잘못된 날짜면 선택된 날짜 사용
|
|
}
|
|
|
|
return date.toISOString().split('T')[0]; // YYYY-MM-DD 형식으로 변환
|
|
};
|
|
|
|
const payload = {
|
|
date: formatDate(r.date), // 날짜 형식 변환
|
|
worker_id: r.worker_id, // 기존 작업자 ID 유지
|
|
project_id: Number(projectId),
|
|
task_id: Number(taskId),
|
|
overtime_hours: overtimeHours ? Number(overtimeHours) : null,
|
|
work_details: tr.querySelector('[data-id="work_details"]').value,
|
|
memo: tr.querySelector('[data-id="memo"]').value.trim() || null
|
|
};
|
|
|
|
// 저장 버튼 상태 변경 (로딩 중)
|
|
const saveBtn = tr.querySelector('.save-btn');
|
|
const originalText = saveBtn.textContent;
|
|
const originalColor = saveBtn.style.backgroundColor;
|
|
|
|
saveBtn.textContent = '저장 중...';
|
|
saveBtn.style.backgroundColor = '#ffc107';
|
|
saveBtn.disabled = true;
|
|
|
|
try {
|
|
const res = await fetch(`${API}/workreports/${r.id}`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
...getAuthHeaders(),
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(payload)
|
|
});
|
|
|
|
const result = await res.json();
|
|
|
|
if (res.ok && result.success) {
|
|
// 성공 상태 표시
|
|
saveBtn.textContent = '✅ 완료';
|
|
saveBtn.style.backgroundColor = '#28a745';
|
|
saveBtn.style.color = 'white';
|
|
|
|
setTimeout(() => {
|
|
saveBtn.textContent = originalText;
|
|
saveBtn.style.backgroundColor = originalColor;
|
|
saveBtn.style.color = '';
|
|
saveBtn.disabled = false;
|
|
}, 2000);
|
|
|
|
// alert 대신 조용한 알림
|
|
console.log('저장 완료:', result);
|
|
} else {
|
|
console.error('저장 실패:', result);
|
|
alert(`❌ 저장 실패: ${result.error || result.message || '알 수 없는 오류'}`);
|
|
|
|
// 실패 시 버튼 복원
|
|
saveBtn.textContent = originalText;
|
|
saveBtn.style.backgroundColor = originalColor;
|
|
saveBtn.disabled = false;
|
|
}
|
|
} catch (err) {
|
|
console.error('저장 요청 에러:', err);
|
|
alert('❌ 저장 요청 실패: ' + err.message);
|
|
|
|
// 에러 시 버튼 복원
|
|
saveBtn.textContent = originalText;
|
|
saveBtn.style.backgroundColor = originalColor;
|
|
saveBtn.disabled = false;
|
|
}
|
|
};
|
|
|
|
// 삭제 버튼
|
|
tr.querySelector('.delete-btn').onclick = async () => {
|
|
if (!confirm('정말 삭제하시겠습니까?')) return;
|
|
|
|
try {
|
|
const res = await fetch(`${API}/workreports/${r.id}`, {
|
|
method: 'DELETE',
|
|
headers: getAuthHeaders()
|
|
});
|
|
|
|
if (res.ok) {
|
|
tr.remove();
|
|
// 행 번호 다시 매기기
|
|
updateRowNumbers();
|
|
alert('✅ 삭제 완료');
|
|
} else {
|
|
const result = await res.json();
|
|
alert(`❌ 삭제 실패: ${result.error || result.message || '알 수 없는 오류'}`);
|
|
}
|
|
} catch (err) {
|
|
console.error('삭제 요청 에러:', err);
|
|
alert('❌ 삭제 요청 실패: ' + err.message);
|
|
}
|
|
};
|
|
|
|
reportBody.appendChild(tr);
|
|
});
|
|
} catch (err) {
|
|
console.error('데이터 로딩 에러:', err);
|
|
reportBody.innerHTML = '<tr><td colspan="8">❌ 불러오기 실패: ' + err.message + '</td></tr>';
|
|
}
|
|
}
|
|
|
|
// 행 번호 다시 매기기
|
|
function updateRowNumbers() {
|
|
reportBody.querySelectorAll('tr').forEach((tr, i) => {
|
|
const firstTd = tr.querySelector('td:first-child');
|
|
if (firstTd) firstTd.textContent = i + 1;
|
|
});
|
|
} |