/* tkpurchase-schedule.js - Schedule management */ let schedulePage = 1; const scheduleLimit = 20; let companySearchTimer = null; let projectList = []; async function loadProjects() { try { const r = await api('/projects/active'); projectList = r.data || []; } catch(e) { console.warn('Load projects error:', e); projectList = []; } } function populateProjectDropdown(selectId, selectedId) { const select = document.getElementById(selectId); select.innerHTML = ''; projectList.forEach(p => { const label = p.job_no ? `[${p.job_no}] ${p.project_name}` : p.project_name; select.innerHTML += ``; }); } function formatDateRange(startDate, endDate) { const s = formatDate(startDate); const e = formatDate(endDate); if (s === e) return s; // 같은 연도이면 월/일만 표시 const sd = new Date(startDate); const ed = new Date(endDate); if (sd.getFullYear() === ed.getFullYear()) { return `${sd.getMonth()+1}/${sd.getDate()} ~ ${ed.getMonth()+1}/${ed.getDate()}`; } return `${s} ~ ${e}`; } async function loadSchedules() { const company = document.getElementById('filterCompany').value.trim(); const dateFrom = document.getElementById('filterDateFrom').value; const dateTo = document.getElementById('filterDateTo').value; const status = document.getElementById('filterStatus').value; let query = `?page=${schedulePage}&limit=${scheduleLimit}`; if (company) query += '&company=' + encodeURIComponent(company); if (dateFrom) query += '&date_from=' + dateFrom; if (dateTo) query += '&date_to=' + dateTo; if (status) query += '&status=' + status; try { const r = await api('/schedules' + query); renderScheduleTable(r.data || [], r.total || 0); } catch(e) { console.warn('Schedule load error:', e); document.getElementById('scheduleTableBody').innerHTML = '로딩 실패'; } } function renderScheduleTable(list, total) { const tbody = document.getElementById('scheduleTableBody'); if (!list.length) { tbody.innerHTML = '일정이 없습니다'; document.getElementById('schedulePagination').innerHTML = ''; return; } const statusMap = { requested: ['badge-orange', '신청'], scheduled: ['badge-amber', '예정'], in_progress: ['badge-green', '진행중'], completed: ['badge-blue', '완료'], cancelled: ['badge-gray', '취소'], rejected: ['badge-red', '반려'] }; tbody.innerHTML = list.map(s => { const [cls, label] = statusMap[s.status] || ['badge-gray', s.status]; const canEdit = s.status === 'scheduled'; const isRequest = s.status === 'requested'; const projectLabel = s.project_name ? (s.job_no ? `[${s.job_no}] ${s.project_name}` : s.project_name) : ''; return ` ${escapeHtml(s.company_name || '')} ${formatDateRange(s.start_date, s.end_date)} ${escapeHtml(projectLabel)} ${escapeHtml(s.work_description || '')} ${escapeHtml(s.workplace_name || '')} ${s.expected_workers || 0}명 ${label} ${isRequest ? `` : ''} ${(s.status === 'in_progress' || s.status === 'completed') ? `` : ''} ${canEdit ? ` ` : ''} `; }).join(''); // Pagination const totalPages = Math.ceil(total / scheduleLimit); renderSchedulePagination(totalPages); } function renderSchedulePagination(totalPages) { const container = document.getElementById('schedulePagination'); if (totalPages <= 1) { container.innerHTML = ''; return; } let html = ''; if (schedulePage > 1) { html += ``; } for (let i = 1; i <= totalPages; i++) { if (i === schedulePage) { html += ``; } else if (Math.abs(i - schedulePage) <= 2 || i === 1 || i === totalPages) { html += ``; } else if (Math.abs(i - schedulePage) === 3) { html += '...'; } } if (schedulePage < totalPages) { html += ``; } container.innerHTML = html; } function goToSchedulePage(p) { schedulePage = p; loadSchedules(); } /* ===== Company Autocomplete ===== */ function setupCompanyAutocomplete(inputId, dropdownId, hiddenId) { const input = document.getElementById(inputId); const dropdown = document.getElementById(dropdownId); const hidden = document.getElementById(hiddenId); input.addEventListener('input', function() { hidden.value = ''; clearTimeout(companySearchTimer); const q = this.value.trim(); if (q.length < 1) { dropdown.classList.add('hidden'); return; } companySearchTimer = setTimeout(async () => { try { const r = await api('/partners/search?q=' + encodeURIComponent(q)); const list = r.data || []; if (!list.length) { dropdown.innerHTML = '
결과 없음
'; } else { dropdown.innerHTML = list.map(c => `
${escapeHtml(c.company_name)}
` ).join(''); } dropdown.classList.remove('hidden'); } catch(e) { dropdown.classList.add('hidden'); } }, 300); }); input.addEventListener('blur', function() { setTimeout(() => dropdown.classList.add('hidden'), 200); }); } function selectCompany(inputId, hiddenId, dropdownId, id, name) { document.getElementById(inputId).value = name; document.getElementById(hiddenId).value = id; document.getElementById(dropdownId).classList.add('hidden'); } /* ===== Add Schedule ===== */ async function openAddSchedule() { document.getElementById('addScheduleForm').reset(); document.getElementById('addCompanyId').value = ''; const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); const tomorrowStr = tomorrow.toISOString().substring(0, 10); document.getElementById('addStartDate').value = tomorrowStr; document.getElementById('addEndDate').value = tomorrowStr; await loadProjects(); populateProjectDropdown('addProject', ''); document.getElementById('addScheduleModal').classList.remove('hidden'); } function closeAddSchedule() { document.getElementById('addScheduleModal').classList.add('hidden'); } async function submitAddSchedule(e) { e.preventDefault(); const companyId = document.getElementById('addCompanyId').value; if (!companyId) { showToast('업체를 선택하세요', 'error'); return; } const startDate = document.getElementById('addStartDate').value; const endDate = document.getElementById('addEndDate').value; if (endDate < startDate) { showToast('종료일은 시작일 이후여야 합니다', 'error'); return; } const projectId = document.getElementById('addProject').value; const body = { company_id: parseInt(companyId), start_date: startDate, end_date: endDate, project_id: projectId ? parseInt(projectId) : null, work_description: document.getElementById('addWorkDescription').value.trim(), workplace_name: document.getElementById('addWorkplaceName').value.trim(), expected_workers: parseInt(document.getElementById('addExpectedWorkers').value) || 0, notes: document.getElementById('addNotes').value.trim() }; try { await api('/schedules', { method: 'POST', body: JSON.stringify(body) }); showToast('일정이 등록되었습니다'); closeAddSchedule(); loadSchedules(); } catch(e) { showToast(e.message || '등록 실패', 'error'); } } /* ===== Edit Schedule ===== */ let scheduleCache = {}; async function openEditSchedule(id) { try { const r = await api('/schedules/' + id); const s = r.data || r; scheduleCache[id] = s; document.getElementById('editScheduleId').value = id; document.getElementById('editCompanySearch').value = s.company_name || ''; document.getElementById('editCompanyId').value = s.company_id || ''; document.getElementById('editStartDate').value = formatDate(s.start_date); document.getElementById('editEndDate').value = formatDate(s.end_date); document.getElementById('editWorkDescription').value = s.work_description || ''; document.getElementById('editWorkplaceName').value = s.workplace_name || ''; document.getElementById('editExpectedWorkers').value = s.expected_workers || 0; document.getElementById('editNotes').value = s.notes || ''; await loadProjects(); populateProjectDropdown('editProject', s.project_id || ''); document.getElementById('editScheduleModal').classList.remove('hidden'); } catch(e) { showToast('일정 정보를 불러올 수 없습니다', 'error'); } } function closeEditSchedule() { document.getElementById('editScheduleModal').classList.add('hidden'); } async function submitEditSchedule(e) { e.preventDefault(); const id = document.getElementById('editScheduleId').value; const companyId = document.getElementById('editCompanyId').value; if (!companyId) { showToast('업체를 선택하세요', 'error'); return; } const startDate = document.getElementById('editStartDate').value; const endDate = document.getElementById('editEndDate').value; if (endDate < startDate) { showToast('종료일은 시작일 이후여야 합니다', 'error'); return; } const projectId = document.getElementById('editProject').value; const body = { company_id: parseInt(companyId), start_date: startDate, end_date: endDate, project_id: projectId ? parseInt(projectId) : null, work_description: document.getElementById('editWorkDescription').value.trim(), workplace_name: document.getElementById('editWorkplaceName').value.trim(), expected_workers: parseInt(document.getElementById('editExpectedWorkers').value) || 0, notes: document.getElementById('editNotes').value.trim() }; try { await api('/schedules/' + id, { method: 'PUT', body: JSON.stringify(body) }); showToast('일정이 수정되었습니다'); closeEditSchedule(); loadSchedules(); } catch(e) { showToast(e.message || '수정 실패', 'error'); } } /* ===== Delete Schedule ===== */ async function deleteSchedule(id) { if (!confirm('이 일정을 삭제하시겠습니까?')) return; try { await api('/schedules/' + id, { method: 'DELETE' }); showToast('일정이 삭제되었습니다'); loadSchedules(); } catch(e) { showToast(e.message || '삭제 실패', 'error'); } } /* ===== Checkin Management ===== */ async function viewCheckins(scheduleId) { document.getElementById('checkinModal').classList.remove('hidden'); const body = document.getElementById('checkinModalBody'); body.innerHTML = '

로딩 중...

'; try { const r = await api('/checkins/schedule/' + scheduleId); const list = r.data || []; if (!list.length) { body.innerHTML = '

체크인 기록이 없습니다

'; return; } body.innerHTML = list.map(c => { let names = ''; if (c.worker_names) { try { const parsed = JSON.parse(c.worker_names); names = Array.isArray(parsed) ? parsed.join(', ') : parsed; } catch { names = c.worker_names; } } const checkinTime = c.check_in_time ? new Date(c.check_in_time).toLocaleString('ko-KR', { month:'numeric', day:'numeric', hour:'2-digit', minute:'2-digit' }) : '-'; const checkoutTime = c.check_out_time ? new Date(c.check_out_time).toLocaleString('ko-KR', { hour:'2-digit', minute:'2-digit' }) : '작업중'; return `
${escapeHtml(c.company_name || '')} ${checkinTime} ~ ${checkoutTime}
인원: ${c.actual_worker_count || 0}명
작업자: ${escapeHtml(names) || '-'}
`; }).join(''); } catch(e) { body.innerHTML = '

로딩 실패

'; } } function closeCheckinModal() { document.getElementById('checkinModal').classList.add('hidden'); } async function deleteCheckin(checkinId, scheduleId) { if (!confirm('이 체크인을 삭제하시겠습니까?\n관련 업무현황도 함께 삭제됩니다.')) return; try { await api('/checkins/' + checkinId, { method: 'DELETE' }); showToast('체크인이 삭제되었습니다'); viewCheckins(scheduleId); loadSchedules(); } catch(e) { showToast(e.message || '삭제 실패', 'error'); } } async function editCheckinWorkers(checkinId) { const el = document.getElementById('checkinNames_' + checkinId); const current = el.textContent.replace('작업자: ', '').trim(); const input = prompt('작업자 명단 (콤마로 구분)', current === '-' ? '' : current); if (input === null) return; const names = input.split(/[,,]\s*/).map(n => n.trim()).filter(Boolean); try { await api('/checkins/' + checkinId, { method: 'PUT', body: JSON.stringify({ worker_names: names.length ? names : null }) }); el.textContent = '작업자: ' + (names.length ? names.join(', ') : '-'); showToast('작업자 명단이 수정되었습니다'); } catch(e) { showToast(e.message || '수정 실패', 'error'); } } /* ===== Approve/Reject Request ===== */ async function openApproveModal(id) { try { const r = await api('/schedules/' + id); const s = r.data || r; scheduleCache[id] = s; document.getElementById('approveScheduleId').value = id; document.getElementById('approveInfo').innerHTML = `
업체: ${escapeHtml(s.company_name || '')}
작업일: ${formatDate(s.start_date)}
예상 인원: ${s.expected_workers || 0}명
작업 내용: ${escapeHtml(s.work_description || '-')}
작업장: ${escapeHtml(s.workplace_name || '-')}
`; document.getElementById('approveWorkplace').value = s.workplace_name || ''; document.getElementById('approveStartDate').value = formatDate(s.start_date); document.getElementById('approveEndDate').value = formatDate(s.end_date); await loadProjects(); populateProjectDropdown('approveProject', ''); document.getElementById('approveModal').classList.remove('hidden'); } catch(e) { showToast('신청 정보를 불러올 수 없습니다', 'error'); } } function closeApproveModal() { document.getElementById('approveModal').classList.add('hidden'); } async function doApprove() { const id = document.getElementById('approveScheduleId').value; if (!confirm('이 작업 신청을 승인하시겠습니까?')) return; const projectId = document.getElementById('approveProject').value; const body = { project_id: projectId ? parseInt(projectId) : null, workplace_name: document.getElementById('approveWorkplace').value.trim(), start_date: document.getElementById('approveStartDate').value, end_date: document.getElementById('approveEndDate').value }; try { await api('/schedules/' + id + '/approve', { method: 'PUT', body: JSON.stringify(body) }); showToast('승인 완료'); closeApproveModal(); loadSchedules(); } catch(e) { showToast(e.message || '승인 실패', 'error'); } } async function doReject() { const id = document.getElementById('approveScheduleId').value; if (!confirm('이 작업 신청을 반려하시겠습니까?')) return; try { await api('/schedules/' + id + '/reject', { method: 'PUT' }); showToast('반려 완료'); closeApproveModal(); loadSchedules(); } catch(e) { showToast(e.message || '반려 실패', 'error'); } } /* ===== Init ===== */ function initSchedulePage() { if (!initAuth()) return; // Set default date range const now = new Date(); const firstDay = new Date(now.getFullYear(), now.getMonth(), 1); const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0); document.getElementById('filterDateFrom').value = firstDay.toISOString().substring(0, 10); document.getElementById('filterDateTo').value = lastDay.toISOString().substring(0, 10); // Setup autocomplete for both modals setupCompanyAutocomplete('addCompanySearch', 'addCompanyDropdown', 'addCompanyId'); setupCompanyAutocomplete('editCompanySearch', 'editCompanyDropdown', 'editCompanyId'); loadSchedules(); }