/* ===== Partner Management ===== */ let partners = []; let partnerWorkers = []; let selectedPartnerId = null; let editingWorkerId = null; async function loadPartners() { try { const isActive = document.getElementById('partnerFilterActive')?.value; const search = document.getElementById('partnerSearch')?.value?.trim() || ''; const params = new URLSearchParams(); if (isActive !== '' && isActive !== undefined) params.set('is_active', isActive); if (search) params.set('search', search); const r = await api('/partners/?' + params.toString()); partners = r.data || []; renderPartnerList(); } catch (e) { showToast('업체 목록 로드 실패: ' + e.message, 'error'); } } function renderPartnerList() { const c = document.getElementById('partnerList'); if (!partners.length) { c.innerHTML = '
등록된 협력업체가 없습니다
'; return; } c.innerHTML = partners.map(p => { const types = tryParseJson(p.business_type) || []; const typeStr = types.length ? types.map(t => `${escapeHtml(t)}`).join(' ') : ''; const insuranceWarning = isInsuranceExpiringSoon(p.insurance_expiry); return `
${escapeHtml(p.company_name)} ${!p.is_active ? '비활성' : ''} ${insuranceWarning ? '보험만료' : ''}
${p.business_number ? `${p.business_number}` : ''} ${p.representative ? `${escapeHtml(p.representative)}` : ''} ${typeStr}
${p.is_active ? `` : ''}
`; }).join(''); } function isInsuranceExpiringSoon(expiry) { if (!expiry) return false; const exp = new Date(expiry); const now = new Date(); const diff = (exp - now) / (1000 * 60 * 60 * 24); return diff <= 30 && diff >= 0; } function tryParseJson(val) { if (!val) return null; if (Array.isArray(val)) return val; try { return JSON.parse(val); } catch { return null; } } /* ===== 업체 상세 + 작업자 ===== */ async function selectPartner(id) { selectedPartnerId = id; renderPartnerList(); // 하이라이트 갱신 try { const r = await api(`/partners/${id}`); const p = r.data; partnerWorkers = p.workers || []; renderPartnerDetail(p); document.getElementById('partnerDetail').classList.remove('hidden'); document.getElementById('partnerEmpty').classList.add('hidden'); } catch (e) { showToast('상세 조회 실패: ' + e.message, 'error'); } } function renderPartnerDetail(p) { const types = tryParseJson(p.business_type) || []; document.getElementById('detailCompanyName').textContent = p.company_name; document.getElementById('detailInfo').innerHTML = `
사업자번호: ${escapeHtml(p.business_number) || '-'}
대표자: ${escapeHtml(p.representative) || '-'}
담당자: ${escapeHtml(p.contact_name) || '-'}
연락처: ${escapeHtml(p.contact_phone) || '-'}
주소: ${escapeHtml(p.address) || '-'}
업종: ${types.map(t => `${escapeHtml(t)}`).join(' ') || '-'}
산재보험: ${escapeHtml(p.insurance_number) || '-'} ${p.insurance_expiry ? `(만료: ${formatDate(p.insurance_expiry)})` : ''}
${p.notes ? `
비고: ${escapeHtml(p.notes)}
` : ''}
`; renderWorkerList(); } function renderWorkerList() { const c = document.getElementById('workerList'); if (!partnerWorkers.length) { c.innerHTML = '
등록된 작업자가 없습니다
'; return; } c.innerHTML = partnerWorkers.map(w => `
${escapeHtml(w.worker_name)} ${w.is_team_leader ? '팀장' : ''} ${!w.is_active ? '비활성' : ''}
${w.position ? `${escapeHtml(w.position)}` : ''} ${w.phone ? `${escapeHtml(w.phone)}` : ''} ${w.safety_training_date ? `안전교육: ${formatDate(w.safety_training_date)}` : ''}
${w.is_active ? `` : ''}
`).join(''); } /* ===== 업체 등록 ===== */ function openAddPartner() { document.getElementById('addPartnerModal').classList.remove('hidden'); } function closeAddPartner() { document.getElementById('addPartnerModal').classList.add('hidden'); document.getElementById('addPartnerForm').reset(); } async function submitAddPartner(e) { e.preventDefault(); const typesRaw = document.getElementById('newBusinessType').value.trim(); const data = { company_name: document.getElementById('newCompanyName').value.trim(), business_number: document.getElementById('newBusinessNumber').value.trim() || null, representative: document.getElementById('newRepresentative').value.trim() || null, contact_name: document.getElementById('newContactName').value.trim() || null, contact_phone: document.getElementById('newContactPhone').value.trim() || null, address: document.getElementById('newAddress').value.trim() || null, business_type: typesRaw ? typesRaw.split(',').map(s => s.trim()).filter(Boolean) : null, insurance_number: document.getElementById('newInsuranceNumber').value.trim() || null, insurance_expiry: document.getElementById('newInsuranceExpiry').value || null, notes: document.getElementById('newPartnerNotes').value.trim() || null, }; if (!data.company_name) { showToast('업체명은 필수입니다', 'error'); return; } try { await api('/partners/', { method: 'POST', body: JSON.stringify(data) }); showToast('업체가 등록되었습니다'); closeAddPartner(); await loadPartners(); } catch (e) { showToast(e.message, 'error'); } } /* ===== 업체 수정 ===== */ function openEditPartner(id) { const p = partners.find(x => x.id === id); if (!p) return; const types = tryParseJson(p.business_type) || []; document.getElementById('editPartnerId').value = p.id; document.getElementById('editCompanyName').value = p.company_name; document.getElementById('editBusinessNumber').value = p.business_number || ''; document.getElementById('editRepresentative').value = p.representative || ''; document.getElementById('editContactName').value = p.contact_name || ''; document.getElementById('editContactPhone').value = p.contact_phone || ''; document.getElementById('editAddress').value = p.address || ''; document.getElementById('editBusinessType').value = types.join(', '); document.getElementById('editInsuranceNumber').value = p.insurance_number || ''; document.getElementById('editInsuranceExpiry').value = p.insurance_expiry ? formatDate(p.insurance_expiry) : ''; document.getElementById('editPartnerNotes').value = p.notes || ''; document.getElementById('editPartnerModal').classList.remove('hidden'); } function closeEditPartner() { document.getElementById('editPartnerModal').classList.add('hidden'); } async function submitEditPartner(e) { e.preventDefault(); const id = document.getElementById('editPartnerId').value; const typesRaw = document.getElementById('editBusinessType').value.trim(); const data = { company_name: document.getElementById('editCompanyName').value.trim(), business_number: document.getElementById('editBusinessNumber').value.trim() || null, representative: document.getElementById('editRepresentative').value.trim() || null, contact_name: document.getElementById('editContactName').value.trim() || null, contact_phone: document.getElementById('editContactPhone').value.trim() || null, address: document.getElementById('editAddress').value.trim() || null, business_type: typesRaw ? typesRaw.split(',').map(s => s.trim()).filter(Boolean) : null, insurance_number: document.getElementById('editInsuranceNumber').value.trim() || null, insurance_expiry: document.getElementById('editInsuranceExpiry').value || null, notes: document.getElementById('editPartnerNotes').value.trim() || null, }; try { await api(`/partners/${id}`, { method: 'PUT', body: JSON.stringify(data) }); showToast('수정되었습니다'); closeEditPartner(); await loadPartners(); if (selectedPartnerId == id) selectPartner(id); } catch (e) { showToast(e.message, 'error'); } } /* ===== 업체 비활성화 ===== */ async function deactivatePartner(id, name) { if (!confirm(`"${name}" 업체를 비활성화하시겠습니까?`)) return; try { await api(`/partners/${id}`, { method: 'DELETE' }); showToast('비활성화 완료'); await loadPartners(); if (selectedPartnerId === id) { document.getElementById('partnerDetail').classList.add('hidden'); document.getElementById('partnerEmpty').classList.remove('hidden'); selectedPartnerId = null; } } catch (e) { showToast(e.message, 'error'); } } /* ===== 작업자 등록 ===== */ function openAddWorker() { if (!selectedPartnerId) { showToast('업체를 먼저 선택해주세요', 'error'); return; } document.getElementById('addWorkerModal').classList.remove('hidden'); } function closeAddWorker() { document.getElementById('addWorkerModal').classList.add('hidden'); document.getElementById('addWorkerForm').reset(); } async function submitAddWorker(e) { e.preventDefault(); const data = { worker_name: document.getElementById('newWorkerName').value.trim(), position: document.getElementById('newWorkerPosition').value.trim() || null, is_team_leader: document.getElementById('newWorkerIsLeader').checked, phone: document.getElementById('newWorkerPhone').value.trim() || null, safety_training_date: document.getElementById('newWorkerSafetyDate').value || null, notes: document.getElementById('newWorkerNotes').value.trim() || null, }; if (!data.worker_name) { showToast('작업자명은 필수입니다', 'error'); return; } try { await api(`/partners/${selectedPartnerId}/workers`, { method: 'POST', body: JSON.stringify(data) }); showToast('작업자가 등록되었습니다'); closeAddWorker(); await selectPartner(selectedPartnerId); } catch (e) { showToast(e.message, 'error'); } } /* ===== 작업자 수정 ===== */ function openEditWorker(id) { const w = partnerWorkers.find(x => x.id === id); if (!w) return; editingWorkerId = id; document.getElementById('editWorkerName').value = w.worker_name; document.getElementById('editWorkerPosition').value = w.position || ''; document.getElementById('editWorkerIsLeader').checked = w.is_team_leader; document.getElementById('editWorkerPhone').value = w.phone || ''; document.getElementById('editWorkerSafetyDate').value = w.safety_training_date ? formatDate(w.safety_training_date) : ''; document.getElementById('editWorkerNotes').value = w.notes || ''; document.getElementById('editWorkerModal').classList.remove('hidden'); } function closeEditWorker() { document.getElementById('editWorkerModal').classList.add('hidden'); editingWorkerId = null; } async function submitEditWorker(e) { e.preventDefault(); if (!editingWorkerId) return; const data = { worker_name: document.getElementById('editWorkerName').value.trim(), position: document.getElementById('editWorkerPosition').value.trim() || null, is_team_leader: document.getElementById('editWorkerIsLeader').checked, phone: document.getElementById('editWorkerPhone').value.trim() || null, safety_training_date: document.getElementById('editWorkerSafetyDate').value || null, notes: document.getElementById('editWorkerNotes').value.trim() || null, }; try { await api(`/partners/workers/${editingWorkerId}`, { method: 'PUT', body: JSON.stringify(data) }); showToast('수정되었습니다'); closeEditWorker(); await selectPartner(selectedPartnerId); } catch (e) { showToast(e.message, 'error'); } } async function doDeactivateWorker(id) { if (!confirm('이 작업자를 비활성화하시겠습니까?')) return; try { await api(`/partners/workers/${id}`, { method: 'DELETE' }); showToast('비활성화 완료'); await selectPartner(selectedPartnerId); } catch (e) { showToast(e.message, 'error'); } } /* ===== Init ===== */ function initPartnerPage() { if (!initAuth()) return; document.getElementById('addPartnerForm').addEventListener('submit', submitAddPartner); document.getElementById('editPartnerForm').addEventListener('submit', submitEditPartner); document.getElementById('addWorkerForm').addEventListener('submit', submitAddWorker); document.getElementById('editWorkerForm').addEventListener('submit', submitEditWorker); document.getElementById('partnerSearch')?.addEventListener('input', debounce(loadPartners, 300)); document.getElementById('partnerFilterActive')?.addEventListener('change', loadPartners); loadPartners(); } function debounce(fn, ms) { let t; return function(...args) { clearTimeout(t); t = setTimeout(() => fn.apply(this, args), ms); }; }