/* tkpurchase-workreport.js - Work report monitoring */ let reportPage = 1; const reportLimit = 20; async function loadCompaniesForFilter() { try { const r = await api('/partners?limit=100'); const list = r.data || []; const sel = document.getElementById('filterCompany'); list.forEach(c => { const opt = document.createElement('option'); opt.value = c.id; opt.textContent = c.company_name; sel.appendChild(opt); }); } catch(e) { console.warn('Load companies error:', e); } } async function loadReports() { const companyId = document.getElementById('filterCompany').value; const dateFrom = document.getElementById('filterDateFrom').value; const dateTo = document.getElementById('filterDateTo').value; const confirmed = document.getElementById('filterConfirmed').value; let query = `?page=${reportPage}&limit=${reportLimit}`; if (companyId) query += '&company_id=' + companyId; if (dateFrom) query += '&date_from=' + dateFrom; if (dateTo) query += '&date_to=' + dateTo; if (confirmed) query += '&confirmed=' + confirmed; try { const r = await api('/work-reports' + query); renderReportTable(r.data || [], r.total || 0); } catch(e) { console.warn('Report load error:', e); document.getElementById('reportTableBody').innerHTML = '로딩 실패'; } } function renderReportTable(list, total) { const tbody = document.getElementById('reportTableBody'); if (!list.length) { tbody.innerHTML = '업무현황이 없습니다'; document.getElementById('reportPagination').innerHTML = ''; return; } tbody.innerHTML = list.map((r, idx) => { const displaySeq = idx + 1; const progressColor = r.progress_rate >= 80 ? 'bg-emerald-500' : r.progress_rate >= 50 ? 'bg-blue-500' : r.progress_rate >= 20 ? 'bg-amber-500' : 'bg-red-500'; const confirmedBadge = r.confirmed_at ? '확인' : r.rejected_by ? '반려' : ''; return ` ${formatDate(r.report_date || r.created_at)} ${displaySeq} ${escapeHtml(r.company_name || '')} ${escapeHtml(r.work_content || '')} ${r.actual_workers || 0}명
${r.progress_rate || 0}%
${escapeHtml(r.issues || '')} ${confirmedBadge} `; }).join(''); // Pagination const totalPages = Math.ceil(total / reportLimit); renderReportPagination(totalPages); } function renderReportPagination(totalPages) { const container = document.getElementById('reportPagination'); if (totalPages <= 1) { container.innerHTML = ''; return; } let html = ''; if (reportPage > 1) { html += ``; } for (let i = 1; i <= totalPages; i++) { if (i === reportPage) { html += ``; } else if (Math.abs(i - reportPage) <= 2 || i === 1 || i === totalPages) { html += ``; } else if (Math.abs(i - reportPage) === 3) { html += '...'; } } if (reportPage < totalPages) { html += ``; } container.innerHTML = html; } function goToReportPage(p) { reportPage = p; loadReports(); } async function confirmReport(id) { if (!confirm('이 업무현황을 확인 처리하시겠습니까?')) return; try { await api('/work-reports/' + id + '/confirm', { method: 'PUT' }); showToast('확인 처리되었습니다'); loadReports(); } catch(e) { showToast(e.message || '확인 처리 실패', 'error'); } } async function viewReportDetail(id) { try { const r = await api('/work-reports/' + id); const d = r.data || r; const progressColor = d.progress_rate >= 80 ? 'bg-emerald-500' : d.progress_rate >= 50 ? 'bg-blue-500' : d.progress_rate >= 20 ? 'bg-amber-500' : 'bg-red-500'; // 작업자 목록 테이블 let workersHtml = ''; if (d.workers && d.workers.length > 0) { const totalHours = d.workers.reduce((sum, w) => sum + Number(w.hours_worked || 0), 0); workersHtml = `
작업자 목록
${d.workers.map(w => ``).join('')}
작업자직위투입시간
${escapeHtml(w.worker_name)}${escapeHtml(w.position || '')}${w.hours_worked || 0}h
합계 (${d.workers.length}명)${totalHours}h
`; } let confirmBtn; if (d.confirmed_at) { confirmBtn = ``; } else if (d.rejected_by) { confirmBtn = `반려됨`; } else { confirmBtn = ` `; } const html = `
업체
${escapeHtml(d.company_name || '')}
보고일
${formatDateTime(d.report_date || d.created_at)}
실투입 인원
${d.actual_workers || 0}명
진행률
${d.progress_rate || 0}%
${workersHtml}
작업내용
${escapeHtml(d.work_content || '-')}
${d.issues ? `
이슈사항
${escapeHtml(d.issues)}
` : ''} ${d.next_plan ? `
향후 계획
${escapeHtml(d.next_plan)}
` : ''}
확인 상태
${d.confirmed_at ? '확인완료 ' + formatDateTime(d.confirmed_at) : d.rejected_by ? '반려 ' + formatDateTime(d.rejected_at) : '미확인'}
${d.confirmed_by_name ? `
확인자
${escapeHtml(d.confirmed_by_name)}
` : ''} ${d.rejected_by ? `
반려 정보
반려 사유: ${escapeHtml(d.rejection_reason || '')}
반려자: ${escapeHtml(d.rejected_by_name || '')} · ${formatDateTime(d.rejected_at)}
` : ''}
${confirmBtn}
`; document.getElementById('reportDetailContent').innerHTML = html; document.getElementById('reportDetailPanel').classList.remove('hidden'); document.getElementById('reportDetailPanel').scrollIntoView({ behavior: 'smooth', block: 'start' }); } catch(e) { showToast('상세 정보를 불러올 수 없습니다', 'error'); } } function closeReportDetail() { document.getElementById('reportDetailPanel').classList.add('hidden'); } async function deleteReport(id) { if (!confirm('이 업무현황을 삭제하시겠습니까? 삭제 후 복구할 수 없습니다.')) return; try { await api('/work-reports/' + id, { method: 'DELETE' }); showToast('삭제되었습니다'); closeReportDetail(); loadReports(); } catch(e) { showToast(e.message || '삭제 실패', 'error'); } } async function rejectReport(id) { const reason = prompt('반려 사유를 입력하세요:'); if (!reason || !reason.trim()) return; try { await api('/work-reports/' + id + '/reject', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ reason: reason.trim() }) }); showToast('반려 처리되었습니다'); viewReportDetail(id); loadReports(); } catch(e) { showToast(e.message || '반려 처리 실패', 'error'); } } async function unconfirmReport(id) { if (!confirm('확인 처리를 취소하시겠습니까?')) return; try { await api('/work-reports/' + id + '/unconfirm', { method: 'PUT' }); showToast('확인이 취소되었습니다'); viewReportDetail(id); loadReports(); } catch(e) { showToast(e.message || '확인 취소 실패', 'error'); } } async function openEditMode(id) { try { const r = await api('/work-reports/' + id); const d = r.data || r; let workersRowsHtml = ''; if (d.workers && d.workers.length > 0) { workersRowsHtml = d.workers.map((w, i) => editWorkerRowHtml(i, w)).join(''); } else { workersRowsHtml = editWorkerRowHtml(0, {}); } const html = `
${escapeHtml(d.company_name || '')}
${formatDateTime(d.report_date || d.created_at)}
${workersRowsHtml}
작업자명투입시간
`; document.getElementById('reportDetailContent').innerHTML = html; document.getElementById('reportDetailPanel').classList.remove('hidden'); } catch(e) { showToast('보고 정보를 불러올 수 없습니다', 'error'); } } function editWorkerRowHtml(idx, w) { return ` `; } function addEditWorkerRow() { const tbody = document.getElementById('editWorkersBody'); const idx = tbody.querySelectorAll('.edit-worker-row').length; tbody.insertAdjacentHTML('beforeend', editWorkerRowHtml(idx, {})); } function removeEditWorkerRow(btn) { const tbody = document.getElementById('editWorkersBody'); if (tbody.querySelectorAll('.edit-worker-row').length <= 1) { showToast('작업자는 최소 1명 필요합니다', 'error'); return; } btn.closest('tr').remove(); } async function saveEditReport(e, id) { e.preventDefault(); const form = document.getElementById('editReportForm'); const data = { actual_workers: parseInt(form.querySelector('[name="actual_workers"]').value) || 0, progress_rate: parseInt(form.querySelector('[name="progress_rate"]').value) || 0, work_content: form.querySelector('[name="work_content"]').value, issues: form.querySelector('[name="issues"]').value, next_plan: form.querySelector('[name="next_plan"]').value, workers: [] }; const rows = document.querySelectorAll('#editWorkersBody .edit-worker-row'); rows.forEach((row, i) => { const nameInput = row.querySelector('input[type="text"]'); const hoursInput = row.querySelector('input[type="number"]'); if (nameInput && nameInput.value.trim()) { data.workers.push({ worker_name: nameInput.value.trim(), hours_worked: parseFloat(hoursInput.value) || 8 }); } }); try { await api('/work-reports/' + id, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); showToast('수정되었습니다'); viewReportDetail(id); loadReports(); } catch(e) { showToast(e.message || '수정 실패', 'error'); } } function initWorkReportPage() { if (!initAuth()) return; // Set default date range to this month const now = new Date(); const firstDay = new Date(now.getFullYear(), now.getMonth(), 1); document.getElementById('filterDateFrom').value = firstDay.toISOString().substring(0, 10); document.getElementById('filterDateTo').value = now.toISOString().substring(0, 10); loadCompaniesForFilter(); loadReports(); }