/* ===== Visit Management ===== */ let todayVisits = []; let editingVisitId = null; async function loadTodayVisits() { try { const r = await api('/daily-visits/today'); const { visits, stats } = r.data; todayVisits = visits; renderStats(stats); renderVisitTable(visits); } catch (e) { showToast('데이터 로드 실패: ' + e.message, 'error'); } } function renderStats(s) { document.getElementById('statTotal').textContent = s.total || 0; document.getElementById('statCheckedIn').textContent = s.checked_in || 0; document.getElementById('statCheckedOut').textContent = s.checked_out || 0; document.getElementById('statVisitors').textContent = s.total_visitors || 0; } function renderVisitTable(visits) { const tbody = document.getElementById('visitTableBody'); if (!visits.length) { tbody.innerHTML = '오늘 방문 기록이 없습니다'; return; } tbody.innerHTML = visits.map(v => { const companyName = v.partner_company_name || v.company_name || '-'; const safetyIcon = v.safety_education_yn ? '' : ''; const actions = v.status === 'checked_in' ? `` : ''; return ` ${escapeHtml(companyName)} ${escapeHtml(v.visitor_name)} ${v.visitor_count} ${purposeBadge(v.purpose)} ${safetyIcon} ${formatTime(v.check_in_time)} ${statusBadge(v.status)} ${actions} `; }).join(''); } /* ===== 업체 자동완성 ===== */ let companySearchTimeout = null; let selectedCompanyId = null; function initCompanySearch() { const input = document.getElementById('companySearch'); const dropdown = document.getElementById('companyDropdown'); const manualToggle = document.getElementById('manualCompanyToggle'); const manualInput = document.getElementById('manualCompanyName'); input.addEventListener('input', () => { clearTimeout(companySearchTimeout); selectedCompanyId = null; const q = input.value.trim(); if (q.length < 1) { dropdown.classList.add('hidden'); return; } companySearchTimeout = setTimeout(async () => { try { const r = await api('/partners/search?q=' + encodeURIComponent(q)); const items = r.data || []; if (items.length === 0) { dropdown.innerHTML = '
검색 결과 없음
'; } else { dropdown.innerHTML = items.map(c => `
${escapeHtml(c.company_name)} ${c.business_number ? `${c.business_number}` : ''}
` ).join(''); } dropdown.classList.remove('hidden'); } catch (e) { dropdown.classList.add('hidden'); } }, 300); }); input.addEventListener('blur', () => setTimeout(() => dropdown.classList.add('hidden'), 200)); manualToggle.addEventListener('change', () => { if (manualToggle.checked) { input.parentElement.classList.add('hidden'); manualInput.parentElement.classList.remove('hidden'); selectedCompanyId = null; input.value = ''; } else { input.parentElement.classList.remove('hidden'); manualInput.parentElement.classList.add('hidden'); manualInput.value = ''; } }); } function selectCompany(id, name) { selectedCompanyId = id; document.getElementById('companySearch').value = name; document.getElementById('companyDropdown').classList.add('hidden'); } /* ===== 인원수 +- ===== */ function initCounterButtons() { document.getElementById('countMinus').addEventListener('click', () => { const el = document.getElementById('visitorCount'); const v = parseInt(el.value) || 1; if (v > 1) el.value = v - 1; }); document.getElementById('countPlus').addEventListener('click', () => { const el = document.getElementById('visitorCount'); el.value = (parseInt(el.value) || 0) + 1; }); } /* ===== 추가정보 접이식 ===== */ function toggleExtra() { document.getElementById('extraFields').classList.toggle('open'); const icon = document.getElementById('extraToggleIcon'); icon.classList.toggle('fa-chevron-down'); icon.classList.toggle('fa-chevron-up'); } /* ===== 방문 등록 ===== */ async function submitVisit(e) { e.preventDefault(); const manualMode = document.getElementById('manualCompanyToggle').checked; const company_id = manualMode ? null : selectedCompanyId; const company_name = manualMode ? document.getElementById('manualCompanyName').value.trim() : null; if (!company_id && !company_name) { showToast('업체를 선택하거나 입력해주세요', 'error'); return; } const data = { company_id, company_name: company_name || document.getElementById('companySearch').value.trim(), visitor_name: document.getElementById('visitorName').value.trim(), visitor_count: parseInt(document.getElementById('visitorCount').value) || 1, purpose: document.getElementById('visitPurpose').value, purpose_detail: document.getElementById('purposeDetail').value.trim() || null, workplace_name: document.getElementById('workplaceName').value.trim() || null, safety_education_yn: document.getElementById('safetyCheck').checked, vehicle_number: document.getElementById('vehicleNumber').value.trim() || null, notes: document.getElementById('visitNotes').value.trim() || null, managing_department: document.getElementById('managingDept').value || null, }; if (!data.visitor_name) { showToast('방문자명을 입력해주세요', 'error'); return; } if (!data.purpose) { showToast('방문 목적을 선택해주세요', 'error'); return; } try { await api('/daily-visits/', { method: 'POST', body: JSON.stringify(data) }); showToast('방문이 등록되었습니다'); document.getElementById('visitForm').reset(); selectedCompanyId = null; document.getElementById('manualCompanyToggle').checked = false; document.getElementById('companySearch').parentElement.classList.remove('hidden'); document.getElementById('manualCompanyName').parentElement.classList.add('hidden'); document.getElementById('visitorCount').value = '1'; await loadTodayVisits(); } catch (e) { showToast(e.message, 'error'); } } /* ===== 체크아웃 ===== */ async function doCheckout(id) { try { await api(`/daily-visits/${id}/checkout`, { method: 'PUT', body: JSON.stringify({}) }); showToast('체크아웃 완료'); await loadTodayVisits(); } catch (e) { showToast(e.message, 'error'); } } async function doBulkCheckout() { const checkedIn = todayVisits.filter(v => v.status === 'checked_in'); if (checkedIn.length === 0) { showToast('체크인 중인 방문이 없습니다', 'error'); return; } if (!confirm(`체크인 중인 ${checkedIn.length}건을 모두 체크아웃 하시겠습니까?`)) return; try { const r = await api('/daily-visits/bulk-checkout', { method: 'POST', body: JSON.stringify({}) }); showToast(`${r.data.affected}건 체크아웃 완료`); await loadTodayVisits(); } catch (e) { showToast(e.message, 'error'); } } /* ===== 수정 ===== */ function openEditVisit(id) { const v = todayVisits.find(x => x.id === id); if (!v) return; editingVisitId = id; document.getElementById('editVisitorName').value = v.visitor_name; document.getElementById('editVisitorCount').value = v.visitor_count; document.getElementById('editPurpose').value = v.purpose; document.getElementById('editPurposeDetail').value = v.purpose_detail || ''; document.getElementById('editWorkplace').value = v.workplace_name || ''; document.getElementById('editSafetyCheck').checked = v.safety_education_yn; document.getElementById('editVehicle').value = v.vehicle_number || ''; document.getElementById('editNotes').value = v.notes || ''; document.getElementById('editVisitModal').classList.remove('hidden'); } function closeEditVisit() { document.getElementById('editVisitModal').classList.add('hidden'); editingVisitId = null; } async function submitEditVisit(e) { e.preventDefault(); if (!editingVisitId) return; const data = { visitor_name: document.getElementById('editVisitorName').value.trim(), visitor_count: parseInt(document.getElementById('editVisitorCount').value) || 1, purpose: document.getElementById('editPurpose').value, purpose_detail: document.getElementById('editPurposeDetail').value.trim() || null, workplace_name: document.getElementById('editWorkplace').value.trim() || null, safety_education_yn: document.getElementById('editSafetyCheck').checked, vehicle_number: document.getElementById('editVehicle').value.trim() || null, notes: document.getElementById('editNotes').value.trim() || null, }; try { await api(`/daily-visits/${editingVisitId}`, { method: 'PUT', body: JSON.stringify(data) }); showToast('수정되었습니다'); closeEditVisit(); await loadTodayVisits(); } catch (e) { showToast(e.message, 'error'); } } /* ===== 삭제 ===== */ async function doDeleteVisit(id) { if (!confirm('이 방문 기록을 삭제하시겠습니까?')) return; try { await api(`/daily-visits/${id}`, { method: 'DELETE' }); showToast('삭제되었습니다'); await loadTodayVisits(); } catch (e) { showToast(e.message, 'error'); } } /* ===== CSV 내보내기 ===== */ async function exportVisits() { const token = getToken(); const dateFrom = document.getElementById('exportDateFrom')?.value || ''; const dateTo = document.getElementById('exportDateTo')?.value || ''; let url = API_BASE + '/daily-visits/export?'; if (dateFrom) url += 'date_from=' + dateFrom + '&'; if (dateTo) url += 'date_to=' + dateTo + '&'; const res = await fetch(url, { headers: { 'Authorization': `Bearer ${token}` } }); if (!res.ok) { showToast('내보내기 실패', 'error'); return; } const blob = await res.blob(); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `visits_${dateFrom || 'all'}_${dateTo || 'all'}.csv`; a.click(); } /* ===== Init ===== */ function initVisitPage() { if (!initAuth()) return; initCompanySearch(); initCounterButtons(); document.getElementById('visitForm').addEventListener('submit', submitVisit); document.getElementById('editVisitForm').addEventListener('submit', submitEditVisit); loadTodayVisits(); }