/* ══════════════════════════════════════════════════ QueuesTab.jsx — Kuyruk Tanımları Yönetim Paneli Kuyruk adı, extension, açıklama, aktif/pasif ══════════════════════════════════════════════════ */ const { useState, useEffect, useMemo } = React; /* ── Kuyruk Formu Modal ─────────────────────────── */ function QueueModal({ queue, onClose, onSave }) { const empty = { name: '', extension: '', description: '', is_active: true }; const [form, setForm] = useState(queue ? { ...queue } : empty); const [saving, setSaving] = useState(false); const [error, setError] = useState(''); const set = (k, v) => { setForm(f => ({ ...f, [k]: v })); setError(''); }; async function handleSave() { if (!form.name.trim()) { setError('Kuyruk adı zorunludur.'); return; } if (!form.extension.trim()) { setError('Extension zorunludur.'); return; } setSaving(true); try { const url = queue ? `/api/queues/${queue.id}` : '/api/queues'; const method = queue ? 'PUT' : 'POST'; const r = await authFetch(url, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(form), }); if (r.ok) { onSave(); } else { const err = await r.json(); setError(err.detail || JSON.stringify(err)); } } catch (e) { setError('Bağlantı hatası: ' + e.message); } finally { setSaving(false); } } return (
e.stopPropagation()} style={{ maxWidth: 540, margin: 'auto' }}>
{queue ? 'Kuyruğu Düzenle' : 'Yeni Kuyruk'}
set('name', e.target.value)} placeholder="Örn: Satış Kuyruğu" />
set('extension', e.target.value)} placeholder="Örn: 8001" style={{ letterSpacing: 1 }} />
IVR yönlendirmesi ve FreeSWITCH dialplan'da kullanılır.
set('description', e.target.value)} placeholder="Bu kuyruk ne için kullanılıyor?" />
set('is_active', e.target.checked)} style={{ width: 16, height: 16, cursor: 'pointer' }} />
{error && (
{error}
)}
); } /* ── Temsilci Yönetimi Modal ─────────────────────── */ const MEMBERS_PAGE_SIZE = 20; function QueueMembersModal({ queue, onClose, toast, onMemberChange }) { const [agents, setAgents] = useState(null); const [allAgents, setAllAgents] = useState([]); const [search, setSearch] = useState(''); const [tab, setTab] = useState('members'); // 'members' | 'add' const [removing, setRemoving] = useState(null); const [memberPage, setMemberPage] = useState(1); const [addPage, setAddPage] = useState(1); function loadMembers() { authFetch(`/api/queues/${queue.id}/agents`) .then(r => r.ok ? r.json() : []) .then(setAgents) .catch(() => setAgents([])); } function loadAllAgents() { authFetch('/api/human-agents') .then(r => r.ok ? r.json() : []) .then(list => setAllAgents(Array.isArray(list) ? list.filter(a => a.is_active) : [])) .catch(() => setAllAgents([])); } useEffect(() => { loadMembers(); loadAllAgents(); }, [queue.id]); // Reset pages when search or tab changes useEffect(() => { setMemberPage(1); setAddPage(1); }, [search, tab]); async function addAgent(agentId) { try { const r = await authFetch(`/api/queues/${queue.id}/agents/${agentId}`, { method: 'POST' }); if (r.ok) { toast('Temsilci kuyruğa eklendi', 'success'); loadMembers(); if (onMemberChange) onMemberChange(); } else { const err = await r.json(); toast(err.detail || 'Ekleme hatası', 'error'); } } catch (e) { toast('Bağlantı hatası', 'error'); } } async function removeAgent(agentId) { setRemoving(agentId); try { const r = await authFetch(`/api/queues/${queue.id}/agents/${agentId}`, { method: 'DELETE' }); if (r.ok) { toast('Temsilci kuyruktan çıkarıldı', 'success'); loadMembers(); if (onMemberChange) onMemberChange(); } else { toast('Çıkarma hatası', 'error'); } } catch (e) { toast('Bağlantı hatası', 'error'); } finally { setRemoving(null); } } const memberIds = new Set((agents || []).map(a => a.id)); const availableAgents = allAgents.filter(a => !memberIds.has(a.id)); const filterFn = (a) => { if (!search.trim()) return true; const s = search.toLowerCase(); return a.name.toLowerCase().includes(s) || (a.extension || '').toLowerCase().includes(s) || (a.department || '').toLowerCase().includes(s); }; const filteredMembers = (agents || []).filter(filterFn); const filteredAvailable = availableAgents.filter(filterFn); const memberTotalPages = Math.max(1, Math.ceil(filteredMembers.length / MEMBERS_PAGE_SIZE)); const addTotalPages = Math.max(1, Math.ceil(filteredAvailable.length / MEMBERS_PAGE_SIZE)); const pagedMembers = filteredMembers.slice((memberPage - 1) * MEMBERS_PAGE_SIZE, memberPage * MEMBERS_PAGE_SIZE); const pagedAvailable = filteredAvailable.slice((addPage - 1) * MEMBERS_PAGE_SIZE, addPage * MEMBERS_PAGE_SIZE); function Pagination({ current, total, onChange }) { if (total <= 1) return null; return React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8, padding: '12px 0', borderTop: '1px solid var(--border)', marginTop: 8 } }, React.createElement('button', { onClick: () => onChange(Math.max(1, current - 1)), disabled: current <= 1, style: { padding: '4px 10px', borderRadius: 6, border: '1px solid var(--border)', background: 'var(--surface2)', color: 'var(--text-dim)', cursor: current <= 1 ? 'default' : 'pointer', fontSize: 12, opacity: current <= 1 ? 0.4 : 1 } }, '‹ Önceki'), React.createElement('span', { style: { fontSize: 12, color: 'var(--text-dim)' } }, `${current} / ${total}`), React.createElement('button', { onClick: () => onChange(Math.min(total, current + 1)), disabled: current >= total, style: { padding: '4px 10px', borderRadius: 6, border: '1px solid var(--border)', background: 'var(--surface2)', color: 'var(--text-dim)', cursor: current >= total ? 'default' : 'pointer', fontSize: 12, opacity: current >= total ? 0.4 : 1 } }, 'Sonraki ›') ); } return (
e.stopPropagation()} style={{ maxWidth: 640, margin: 'auto', maxHeight: '80vh', display: 'flex', flexDirection: 'column' }}>
{queue.name} Temsilci Yönetimi
{/* Tab switcher + Search */}
{[ { key: 'members', label: `Atanmış (${(agents || []).length})` }, { key: 'add', label: `Ekle (${availableAgents.length})` }, ].map(t => ( ))}
setSearch(e.target.value)} style={{ width: '100%', padding: '8px 12px', borderRadius: 6, border: '1px solid var(--border)', background: 'var(--surface2)', fontSize: 13, color: 'var(--text)', outline: 'none', boxSizing: 'border-box', }} />
{/* Content */}
{agents === null ? (
Yükleniyor...
) : tab === 'members' ? ( <> {filteredMembers.length === 0 ? (
{search.trim() ? 'Aramayla eşleşen temsilci bulunamadı.' : 'Bu kuyruğa henüz temsilci atanmamış.'}
) : ( {pagedMembers.map(a => ( ))}
Ad Dahili Departman
{a.name} {a.extension} {a.department || '—'}
)} ) : ( <> {filteredAvailable.length === 0 ? (
{search.trim() ? 'Aramayla eşleşen temsilci bulunamadı.' : 'Tüm temsilciler zaten bu kuyrukta.'}
) : ( {pagedAvailable.map(a => ( e.currentTarget.style.background = 'rgba(88,166,255,.04)'} onMouseLeave={e => e.currentTarget.style.background = 'transparent'} > ))}
Ad Dahili Departman
{a.name} {a.extension} {a.department || '—'}
)} )}
); } /* ── KALDIRILDI: LiveQueueMonitor Wallboard'a taşındı ── */ function _REMOVED_LiveQueueMonitor() { const [calls, setCalls] = useState(null); // null = ilk yükleme const [lastUpdate, setLastUpdate] = useState(null); const [tick, setTick] = useState(0); // bekleme sürelerini yenilemek için function fetchQueue() { authFetch('/api/live-calls') .then(r => r.ok ? r.json() : []) .then(data => { setCalls(Array.isArray(data) ? data : []); setLastUpdate(new Date()); }) .catch(() => setCalls(prev => prev === null ? [] : prev)); } useEffect(() => { fetchQueue(); const poll = setInterval(fetchQueue, 5000); return () => clearInterval(poll); }, []); // Bekleme sürelerini her saniye yenile useEffect(() => { const t = setInterval(() => setTick(n => n + 1), 1000); return () => clearInterval(t); }, []); const ringingCalls = (calls || []).filter(c => c.status === 'ringing'); const ivrCalls = (calls || []).filter(c => c.status === 'ivr'); const queueCalls = (calls || []).filter(c => c.status === 'queue'); const activeCalls = (calls || []).filter(c => c.status === 'active'); const visibleCalls = calls || []; const fmtTime = (d) => d ? d.toLocaleTimeString('tr-TR', { hour: '2-digit', minute: '2-digit', second: '2-digit' }) : ''; const queueOrAgent = (c) => { if (c.status === 'active') return c.agent_name || c.queue_name || '—'; if (c.status === 'queue') return c.queue_name || '—'; return '—'; }; return (
{/* Panel başlık çubuğu */}
Canlı Çağrı İzleme {/* Canlı yeşil nokta — CSS animasyonu inline keyframes yerine style tag ile */} Canlı
{lastUpdate ? `Son güncelleme: ${fmtTime(lastUpdate)}` : ''}
{/* Özet sayaçlar */}
🔔
{calls === null ? '–' : ringingCalls.length}
Çalıyor
🤖
{calls === null ? '–' : ivrCalls.length}
IVR / AI
{calls === null ? '–' : queueCalls.length}
Kuyrukta
🟢
{calls === null ? '–' : activeCalls.length}
Aktif
{/* İçerik */}
{calls === null ? (
Yükleniyor...
) : visibleCalls.length === 0 ? (
Aktif çağrı bulunmuyor.
) : ( {['Arayan', 'Aranan', 'Kuyruk/Temsilci', 'Süre', 'Durum'].map((h, i) => ( ))} {visibleCalls.map((c, i) => ( e.currentTarget.style.background = 'rgba(88,166,255,.03)'} onMouseLeave={e => e.currentTarget.style.background = 'transparent'} > {/* Arayan */} {/* Aranan */} {/* Kuyruk/Temsilci */} {/* Süre */} {/* Durum badge */} ))}
{h}
{c.caller_number || '—'} {c.callee || '—'} {queueOrAgent(c)} {fmtWait(c.started_at)} {statusBadge(c.status)}
)}
); } /* ── Ana Tab ────────────────────────────────────── */ function QueuesTab({ toast }) { const { data: queues, reload } = window.useApi('/api/queues', []); const [modal, setModal] = useState(null); const [membersModal, setMembersModal] = useState(null); async function handleDelete(q) { if (!confirm(`"${q.name}" kuyruğunu silmek istediğinize emin misiniz?\nIVR menülerinde bu kuyruğa yapılan yönlendirmeler etkilenebilir.`)) return; const r = await authFetch(`/api/queues/${q.id}`, { method: 'DELETE' }); if (r.ok) { toast(`"${q.name}" silindi.`, 'success'); reload(); } else { toast('Silme işlemi başarısız.', 'error'); } } async function handleToggle(q) { const r = await authFetch(`/api/queues/${q.id}/toggle`, { method: 'PATCH' }); if (r.ok) { toast(`"${q.name}" ${q.is_active ? 'pasif' : 'aktif'} yapıldı.`, 'success'); reload(); } } const totalCount = queues ? queues.length : 0; const activeCount = queues ? queues.filter(q => q.is_active).length : 0; const passiveCount = totalCount - activeCount; return (
{/* Başlık */}

Kuyruk Tanımları

{totalCount > 0 && (
{totalCount} toplam · {activeCount} aktif · {passiveCount} pasif
)}
{/* Liste */} {(!queues || queues.length === 0) ? (
Henüz kuyruk tanımlanmamış
IVR menülerinde kullanmak için çağrı kuyruklarınızı oluşturun.
) : (
{['Kuyruk Adı', 'Extension', 'Açıklama', 'Temsilci', 'Durum', ''].map((h, idx) => ( ))} {queues.map((q, i) => ( e.currentTarget.style.background = 'rgba(88,166,255,.03)'} onMouseLeave={e => e.currentTarget.style.background = 'transparent'} > {/* Ad */} {/* Extension */} {/* Açıklama */} {/* Temsilci Sayısı */} {/* Durum */} {/* İşlemler */} ))}
{h}
{q.name} {q.extension} {q.description || } setMembersModal(q)} style={{ cursor: 'pointer', fontSize: 12, color: 'var(--text-dim)', textDecoration: 'underline', textDecorationColor: 'rgba(139,148,158,.3)', textUnderlineOffset: 2, }} title="Temsilcileri yönet" > {q.agent_count || 0} temsilci {q.is_active ? 'Aktif' : 'Pasif'}
)} {/* Kuyruk Formu Modal */} {modal && ( setModal(null)} onSave={() => { setModal(null); reload(); toast('Kuyruk başarıyla kaydedildi.', 'success'); }} /> )} {/* Temsilci Yönetimi Modal */} {membersModal && ( setMembersModal(null)} toast={toast} onMemberChange={reload} /> )}
); } window.QueuesTab = QueuesTab;