const { useState, useEffect, useMemo } = React; /* ── Modül grupları (permissions checkbox için) ── */ const _MODULE_GROUPS = [ { label: 'İzleme', modules: [{ id: 'wallboard', label: 'Wallboard' }, { id: 'dashboard', label: 'AI Monitör' }] }, { label: 'Çağrı Merkezi', modules: [ { id: 'human_agents', label: 'Temsilciler' }, { id: 'teams', label: 'Takımlar' }, { id: 'queues', label: 'Kuyruklar' }, { id: 'ivr', label: 'IVR Menüleri' }, { id: 'routing', label: 'AI Yönlendirme' }, { id: 'trunks', label: 'Hat Yönetimi' }, { id: 'schedules', label: 'Mesai' }, { id: 'callbacks', label: 'Geri Arama' }, { id: 'appointments', label: 'Randevular' }, ]}, { label: 'AI & Analiz', modules: [{ id: 'agents', label: 'AI Asistanlar' }, { id: 'speech', label: 'Konuşma Analizi' }, { id: 'qc', label: 'Kalite Kontrol' }, { id: 'surveys', label: 'Anketler' }] }, { label: 'Raporlar & Veri', modules: [{ id: 'report_builder', label: 'Raporlar' }, { id: 'recordings', label: 'Kayıtlar' }, { id: 'dialer', label: 'Dialer' }, { id: 'alarms', label: 'Alarmlar' }, { id: 'customers', label: 'Müşteriler' }] }, { label: 'Sistem', modules: [{ id: 'webservices', label: 'Web Servisler' }, { id: 'billing', label: 'Faturalama' }, { id: 'settings', label: 'Ayarlar' }, { id: 'logs', label: 'Sistem Logları' }] }, ]; const _ALL_MOD_IDS = _MODULE_GROUPS.flatMap(g => g.modules.map(m => m.id)); const _ROLE_LABELS = { super_admin: 'Süper Admin', admin: 'Admin', supervisor: 'Supervisor', agent: 'Temsilci', viewer: 'İzleyici' }; const _ROLE_COLORS = { super_admin: { bg: '#fef3c7', color: '#92400e' }, admin: { bg: '#dbeafe', color: '#1e40af' }, supervisor: { bg: '#e0e7ff', color: '#3730a3' }, agent: { bg: '#d1fae5', color: '#065f46' }, viewer: { bg: '#f3f4f6', color: '#374151' }, }; /* ── Inline Permissions Grid ── */ function _PermGrid({ permissions, onChange }) { const ps = new Set(permissions || []); const allSel = _ALL_MOD_IDS.every(id => ps.has(id)); function tog(id) { const n = new Set(ps); n.has(id) ? n.delete(id) : n.add(id); onChange([...n]); } function togGrp(ids) { const n = new Set(ps); const a = ids.every(id => n.has(id)); ids.forEach(id => a ? n.delete(id) : n.add(id)); onChange([...n]); } function togAll() { onChange(allSel ? [] : [..._ALL_MOD_IDS]); } return (
Modül İzinleri
{_MODULE_GROUPS.map(g => { const gids = g.modules.map(m => m.id); return (
{g.modules.map(m => ( ))}
); })}
); } /* ── Human Agent Modal ── */ function HumanAgentModal({ agent, onClose, onSave }) { const isEdit = !!agent; const [form, setForm] = useState({ name: agent?.name || '', extension: agent?.extension || '', sip_password: agent?.sip_password || '', department: agent?.department || '', priority: agent?.priority ?? 1, max_concurrent_calls: agent?.max_concurrent_calls ?? 1, is_active: agent?.is_active ?? true, auto_answer: agent?.auto_answer ?? true, acw_timeout_sec: agent?.acw_timeout_sec ?? 30, team_id: agent?.team_id ?? null, role: agent?.role || '', email: agent?.email || '', password: '', permissions: agent?.permissions || [], }); const [selectedQueues, setSelectedQueues] = useState(agent?.queues ? agent.queues.map(q => typeof q === 'object' ? q.id : q) : []); const [allQueues, setAllQueues] = useState([]); const [allTeams, setAllTeams] = useState([]); const [allDepts, setAllDepts] = useState([]); const [saving, setSaving] = useState(false); const [error, setError] = useState(''); const set = (k, v) =>setForm(f => ({ ...f, [k]: v })); const showPerms = form.role === 'supervisor' || form.role === 'viewer'; useEffect(() => { authFetch('/api/queues').then(r =>r.ok ? r.json() : []).then(setAllQueues).catch(() => {}); authFetch('/api/teams').then(r =>r.ok ? r.json() : []).then(setAllTeams).catch(() => {}); authFetch('/api/departments').then(r =>r.ok ? r.json() : []).then(d => setAllDepts(d.filter(x => x.is_active))).catch(() => {}); }, []); function toggleQueue(qid) { setSelectedQueues(p =>p.includes(qid) ? p.filter(id =>id !== qid) : [...p, qid]); } async function save() { setError(''); if (!form.name.trim()) { setError('Ad Soyad zorunludur.'); return; } if (!form.extension.trim()) { setError('Extension zorunludur.'); return; } // Rol seçildiyse email ve şifre zorunlu (yeni kullanıcı oluştururken) if (form.role && !isEdit && !agent?.user_id) { if (!form.email.trim()) { setError('Rol seçildiğinde e-posta zorunludur.'); return; } if (!form.password || form.password.length < 6) { setError('Panel şifresi en az 6 karakter olmalıdır.'); return; } } setSaving(true); try { const payload = { ...form }; // Permissions sadece supervisor/viewer için gönder payload.permissions = showPerms ? form.permissions : null; // Boş şifre gönderme (düzenleme modunda değiştirmek istemiyorsa) if (isEdit && !payload.password) delete payload.password; // Rol seçilmemişse kullanıcı alanlarını gönderme if (!payload.role) { delete payload.role; delete payload.email; delete payload.password; delete payload.permissions; } const r = await authFetch(isEdit ? `/api/human-agents/${agent.id}` : '/api/human-agents', { method: isEdit ? 'PUT' : 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); if (!r.ok) { const err = await r.json(); throw new Error(err.detail || JSON.stringify(err)); } const saved = await r.json(); await authFetch(`/api/human-agents/${saved.id}/queues`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(selectedQueues) }); onSave(); } catch (e) { setError(e.message); } finally { setSaving(false); } } return (
e.stopPropagation()} style={{ maxWidth: 680, margin: 'auto' }}>
{isEdit ? 'Temsilci Düzenle' : 'Yeni Temsilci'}
set('name', e.target.value)} />
set('extension', e.target.value)} />
set('sip_password', e.target.value)} placeholder="••••••" />
set('acw_timeout_sec', parseInt(e.target.value))} title="Cagri bittikten sonraki bekleme suresi" />
{allQueues.map(q => { const sel = selectedQueues.includes(q.id); return ( ); })}
set('auto_answer', e.target.checked)} style={{ width: 18, height: 18, accentColor: 'var(--green)', cursor: 'pointer' }} />
{/* ── Panel Kullanıcı Bilgileri ── */}
Panel Kullanıcı Bilgileri
set('email', e.target.value)} placeholder="ornek@sirket.com" />
{(!isEdit || !agent?.user_id) && form.role && (
set('password', e.target.value)} placeholder="En az 6 karakter" />
)} {isEdit && agent?.user_id && (
set('password', e.target.value)} placeholder="Boş bırakırsanız değişmez" />
)} {form.role && (
{form.role === 'admin' && 'Admin tüm modüllere tam erişime sahiptir.'} {form.role === 'supervisor' && 'Supervisor sadece aşağıda seçtiğiniz modüllere erişebilir.'} {form.role === 'viewer' && 'İzleyici seçtiğiniz modülleri salt okunur olarak görür.'}
)} {showPerms && ( <_PermGrid permissions={form.permissions} onChange={p => set('permissions', p)} /> )}
{error &&
! {error}
}
); } /* ── Bulk Import Modal ── */ function BulkImportModal({ onClose, onDone, toast }) { const [file, setFile] = useState(null); const [uploading, setUploading] = useState(false); const [result, setResult] = useState(null); const [dragOver, setDragOver] = useState(false); function handleFile(f) { if (!f) return; const name = f.name.toLowerCase(); if (!name.endsWith('.csv') && !name.endsWith('.xlsx') && !name.endsWith('.xls')) { toast('Sadece CSV veya Excel (.xlsx) desteklenir', 'error'); return; } setFile(f); setResult(null); } async function upload() { if (!file) return; setUploading(true); try { const fd = new FormData(); fd.append('file', file); const r = await authFetch('/api/human-agents/bulk-import', { method: 'POST', body: fd }); const data = await r.json(); if (!r.ok) throw new Error(data.detail || 'Yukleme basarisiz'); setResult(data); if (data.inserted > 0) { toast(`${data.inserted} temsilci basariyla eklendi`, 'success'); } } catch (e) { toast(e.message, 'error'); } finally { setUploading(false); } } return (
e.stopPropagation()} style={{ maxWidth: 600 }}>
Toplu Temsilci Yukle
CSV veya Excel dosyasi ile toplu temsilci ekleyin.
Zorunlu sutunlar: adve dahili
Opsiyonel:sifre, departman, acw, oto_cevap
{result && (
0 ? 'has-errors' : 'success'}`}>
{result.inserted > 0 ? `${result.inserted} temsilci eklendi` : 'Hic temsilci eklenemedi'} {result.skipped > 0 && ` — ${result.skipped} satir atlandi`}
{result.errors?.length > 0 && (
{result.errors.map((e, i) => (
Satir {e.row}: {e.reason}
))}
)}
)}
{result?.inserted > 0 ? ( ) : ( <> )}
); } /* ── Bulk Queue Modal ── */ function BulkQueueModal({ selectedIds, onClose, onDone, toast }) { const [allQueues, setAllQueues] = useState([]); const [selectedQueues, setSelectedQueues] = useState([]); const [saving, setSaving] = useState(false); useEffect(() => { authFetch('/api/queues').then(r =>r.ok ? r.json() : []).then(setAllQueues).catch(() => {}); }, []); function toggleQueue(qid) { setSelectedQueues(p =>p.includes(qid) ? p.filter(id =>id !== qid) : [...p, qid]); } async function apply() { setSaving(true); try { const r = await authFetch('/api/human-agents/bulk-queues', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ids: selectedIds, queue_ids: selectedQueues }), }); const data = await r.json(); if (!r.ok) throw new Error(data.detail || 'Hata'); toast(`${data.updated} temsilcinin kuyrulari guncellendi`, 'success'); onDone(); onClose(); } catch (e) { toast(e.message, 'error'); } finally { setSaving(false); } } return (
e.stopPropagation()} style={{ maxWidth: 480 }}>
Toplu Kuyruk Atama

{selectedIds.length}temsilcinin kuyruk uyeligini secin:

{allQueues.map(q => { const sel = selectedQueues.includes(q.id); return ( ); })} {allQueues.length === 0 && Tanimli kuyruk bulunamadi.}
); } /* ═══════════════════════════════════════════════════════════════════════════════ MAIN: HumanAgentsTab — Sadece Tablo Gorunumu ═══════════════════════════════════════════════════════════════════════════════ */ function HumanAgentsTab({ toast }) { // ── Data const { data: agents, reload: reloadAgents } = window.useApi('/api/human-agents', []); // ── Teams list (for display) const [allTeams, setAllTeams] = useState([]); // Load teams list useEffect(() => { authFetch('/api/teams').then(r =>r.ok ? r.json() : []).then(d =>setAllTeams(Array.isArray(d) ? d : [])).catch(() => {}); }, [agents]); // Active agents list const agentsWithStats = useMemo(() => { const activeList = Array.isArray(agents) ? agents.filter(a => a.is_active) : []; return activeList.map(a => { const merged = { ...a }; if (merged.queues && merged.queues.length > 0) { merged.queues = merged.queues.map(q => typeof q === 'string' ? q : q.name); } return merged; }); }, [agents]); // ── Modals const [agentModal, setAgentModal] = useState(null); const [importModal, setImportModal] = useState(false); const [bulkQueueModal, setBulkQueueModal] = useState(false); // ── Search, filter, sort, pagination const [search, setSearch] = useState(''); const [filterDept, setFilterDept] = useState(''); const [sortKey, setSortKey] = useState('name'); const [sortDir, setSortDir] = useState('asc'); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(25); // ── Selection const [selected, setSelected] = useState(new Set()); // ── Active agents const activeAgents = agentsWithStats; // ── Departments from data const departments = useMemo(() => { const depts = new Set(); activeAgents.forEach(a => { if (a.department) depts.add(a.department); }); return [...depts].sort(); }, [activeAgents]); // ── Filtered + sorted const filteredAgents = useMemo(() => { let list = [...activeAgents]; if (search.trim()) { const q = search.toLowerCase(); list = list.filter(a => a.name.toLowerCase().includes(q) || a.extension.toLowerCase().includes(q) || (a.department || '').toLowerCase().includes(q) ); } if (filterDept) list = list.filter(a =>a.department === filterDept); list.sort((a, b) => { let va = a[sortKey] || ''; let vb = b[sortKey] || ''; if (typeof va === 'number' && typeof vb === 'number') { return sortDir === 'asc' ? va - vb : vb - va; } if (typeof va === 'string') va = va.toLowerCase(); if (typeof vb === 'string') vb = vb.toLowerCase(); if (va < vb) return sortDir === 'asc' ? -1 : 1; if (va >vb) return sortDir === 'asc' ? 1 : -1; return 0; }); return list; }, [activeAgents, search, filterDept, sortKey, sortDir]); // ── Paginated const totalPages = Math.max(1, Math.ceil(filteredAgents.length / pageSize)); const currentPage = Math.min(page, totalPages); const paginatedAgents = useMemo(() => { const start = (currentPage - 1) * pageSize; return filteredAgents.slice(start, start + pageSize); }, [filteredAgents, currentPage, pageSize]); useEffect(() => { setPage(1); }, [search, filterDept, pageSize]); useEffect(() => { setSelected(new Set()); }, [agents]); // ── Handlers function toggleSort(key) { if (sortKey === key) setSortDir(d =>d === 'asc' ? 'desc' : 'asc'); else { setSortKey(key); setSortDir('asc'); } } function toggleSelect(id) { setSelected(prev => { const next = new Set(prev); if (next.has(id)) next.delete(id); else next.add(id); return next; }); } function toggleSelectAll() { if (selected.size === paginatedAgents.length) { setSelected(new Set()); } else { setSelected(new Set(paginatedAgents.map(a =>a.id))); } } async function delAgent(id) { if (!confirm('Temsilciyi pasife almak istediginize emin misiniz?')) return; await authFetch(`/api/human-agents/${id}`, { method: 'DELETE' }); toast('Temsilci pasife alindi', 'success'); reloadAgents(); } async function bulkDelete() { const ids = [...selected]; if (!confirm(`${ids.length} temsilciyi pasife almak istediginize emin misiniz?`)) return; try { const r = await authFetch('/api/human-agents/bulk-delete', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ids }), }); const data = await r.json(); toast(`${data.deleted} temsilci pasife alindi`, 'success'); setSelected(new Set()); reloadAgents(); } catch (e) { toast('Hata: ' + e.message, 'error'); } } async function exportCSV() { try { const r = await authFetch('/api/human-agents/export'); const blob = await r.blob(); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'temsilciler.csv'; a.click(); URL.revokeObjectURL(url); toast('CSV indirildi', 'success'); } catch (e) { toast('Indirme hatasi', 'error'); } } // ── Sort arrow helper const SortArrow = ({ col }) => ( {sortKey === col ? (sortDir === 'asc' ? '▲' : '▼') : '↕'} ); // ── Page buttons function pageButtons() { const btns = []; const maxVisible = 7; let start = Math.max(1, currentPage - 3); let end = Math.min(totalPages, start + maxVisible - 1); if (end - start < maxVisible - 1) start = Math.max(1, end - maxVisible + 1); btns.push( ); if (start > 1) { btns.push(); if (start > 2) btns.push(); } for (let i = start; i <= end; i++) { btns.push( ); } if (end < totalPages) { if (end < totalPages - 1) btns.push(); btns.push(); } btns.push( ); return btns; } // ═══════════════════════════ RENDER ═══════════════════════════ return (
{/* ── Header ── */}

Temsilci Tanımları

{activeAgents.length} aktif temsilci
{/* ── Toolbar ── */}
setSearch(e.target.value)} />
{/* ── Bulk Action Bar ── */} {selected.size > 0 && (
{selected.size} temsilci secili
)} {/* ── Table ── */}
{paginatedAgents.length === 0 && ( )} {paginatedAgents.map(a => ( ))}
0 && selected.size === paginatedAgents.length} onChange={toggleSelectAll} /> toggleSort('name')}>Temsilci toggleSort('extension')}>Dahili toggleSort('department')}>Departman Rol Takım Kuyruklar
{search || filterDept ? 'Filtreye uygun temsilci bulunamadı.' : 'Henüz temsilci tanımlanmamış.'}
toggleSelect(a.id)} /> {a.name} {a.extension} {a.department ? {a.department} : } {a.role && _ROLE_LABELS[a.role] ? ( {_ROLE_LABELS[a.role]} ) : } {a.team_id ? ( {(allTeams || []).find(t => t.id === a.team_id)?.name || `Takım #${a.team_id}`} ) : }
{a.queues && a.queues.length > 0 ? a.queues.map((q, i) => ( {typeof q === 'string' ? q : q.name} )) : }
{/* ── Pagination ── */} {filteredAgents.length > 0 && (
Toplam {filteredAgents.length} temsilci {filteredAgents.length !== activeAgents.length && ` (${activeAgents.length} kayittan filtrelendi)`}
{pageButtons()}
)} {/* ══════════════ MODALS ══════════════ */} {agentModal && setAgentModal(null)} onSave={() => { toast('Temsilci kaydedildi', 'success'); setAgentModal(null); reloadAgents(); }} />} {importModal && setImportModal(false)} onDone={reloadAgents} toast={toast} />} {bulkQueueModal && setBulkQueueModal(false)} onDone={() => { setSelected(new Set()); reloadAgents(); }} toast={toast} />}
); } window.HumanAgentsTab = HumanAgentsTab;