// RecordingsTab.jsx - Çağrı Kayıtları // Exported as window.RecordingsTab (function () { const { useState, useRef, useCallback } = React; function formatDuration(seconds) { if (!seconds && seconds !== 0) return '-'; const m = Math.floor(seconds / 60); const s = Math.floor(seconds % 60); if (m === 0) return `${s}sn`; return `${m}dk ${s}sn`; } function formatFileSize(mb) { if (!mb && mb !== 0) return '-'; return `${parseFloat(mb).toFixed(1)} MB`; } function formatDateTime(isoStr) { // parseUTC: backend Z'siz naive UTC döndürse de güvenli yorumlanır. const d = parseUTC(isoStr); if (!d) return '-'; const pad = (n) => String(n).padStart(2, '0'); return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`; } // ─── Audio Player Bar ───────────────────────────────────────────────────── function AudioPlayerBar({ recording, onClose }) { const audioRef = useRef(null); const [playing, setPlaying] = useState(false); const [currentTime, setCurrentTime] = useState(0); const [duration, setDuration] = useState(0); React.useEffect(() => { setPlaying(false); setCurrentTime(0); setDuration(0); if (audioRef.current) { audioRef.current.load(); audioRef.current.play().then(() => setPlaying(true)).catch(() => {}); } }, [recording.id]); function fmt(sec) { if (!sec || !isFinite(sec)) return '--:--'; const s = Math.floor(sec); return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, '0')}`; } function handleSeek(e) { if (!audioRef.current || !duration || !isFinite(duration)) return; audioRef.current.currentTime = parseFloat(e.target.value); } function togglePlay() { if (!audioRef.current) return; if (playing) { audioRef.current.pause(); setPlaying(false); } else { audioRef.current.play(); setPlaying(true); } } return React.createElement('div', { style: { position: 'fixed', bottom: 0, left: 0, right: 0, zIndex: 1000, background: 'var(--surface)', borderTop: '1px solid var(--border)', padding: '10px 24px', display: 'flex', alignItems: 'center', gap: 16, boxShadow: '0 -4px 24px rgba(0,0,0,0.3)', } }, React.createElement('audio', { ref: audioRef, src: `/api/recordings/${recording.id}/stream`, onTimeUpdate: () => audioRef.current && setCurrentTime(audioRef.current.currentTime), onLoadedMetadata: () => audioRef.current && setDuration(audioRef.current.duration), onEnded: () => setPlaying(false), }), React.createElement('button', { onClick: togglePlay, style: { width: 40, height: 40, borderRadius: '50%', border: 'none', cursor: 'pointer', background: 'var(--blue)', color: '#fff', fontSize: 16, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, } }, playing ? '⏸' : '▶'), React.createElement('div', { style: { minWidth: 160, flexShrink: 0 } }, React.createElement('div', { style: { fontWeight: 600, fontSize: 13, color: 'var(--text)' } }, recording.phone_number || '-' ), React.createElement('div', { style: { fontSize: 11, color: 'var(--text-dim)', marginTop: 2 } }, (recording.agent_name || '-') + ' · ' + formatDateTime(recording.start_time) ) ), React.createElement('div', { style: { flex: 1, display: 'flex', flexDirection: 'column', gap: 4 } }, React.createElement('input', { type: 'range', min: 0, max: isFinite(duration) && duration > 0 ? duration : 0, step: 0.1, value: currentTime, onChange: handleSeek, disabled: !isFinite(duration) || duration === 0, style: { width: '100%', accentColor: 'var(--blue)', cursor: 'pointer', height: 4 }, }), React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', fontSize: 11, color: 'var(--text-dim)' } }, React.createElement('span', null, fmt(currentTime)), React.createElement('span', null, fmt(duration)) ) ), React.createElement('button', { onClick: onClose, style: { background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-dim)', fontSize: 20, lineHeight: 1, flexShrink: 0, padding: '4px 8px', borderRadius: 6, } }, '✕') ); } // ─── Confirm Delete ─────────────────────────────────────────────────────── function ConfirmDialog({ message, onConfirm, onCancel }) { return React.createElement('div', { style: { position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.6)', zIndex: 2000, display: 'flex', alignItems: 'center', justifyContent: 'center' } }, React.createElement('div', { className: 'card', style: { padding: 28, minWidth: 320, textAlign: 'center' } }, React.createElement('p', { style: { marginBottom: 20, fontSize: 15 } }, message), React.createElement('div', { style: { display: 'flex', gap: 12, justifyContent: 'center' } }, React.createElement('button', { className: 'btn btn-danger', onClick: onConfirm }, 'Sil'), React.createElement('button', { className: 'btn btn-secondary', onClick: onCancel }, 'İptal') ) ) ); } // ─── Main RecordingsTab ─────────────────────────────────────────────────── function RecordingsTab({ toast }) { const [recordings, setRecordings] = useState([]); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const [perPage] = useState(20); const [loading, setLoading] = useState(false); const [searched, setSearched] = useState(false); // ilk aramadan önce tablo gözükmesin // Filtreler const [dateFrom, setDateFrom] = useState(''); const [dateTo, setDateTo] = useState(''); const [phone, setPhone] = useState(''); const [agentName, setAgentName] = useState(''); const [hasRecording, setHasRecording] = useState(true); // Player / Delete const [activeRecording, setActiveRecording] = useState(null); const [deleteTarget, setDeleteTarget] = useState(null); const [expandedId, setExpandedId] = useState(null); // genişletilmiş satır id'si const doFetch = useCallback(async (pg, filters) => { setLoading(true); try { const params = new URLSearchParams({ page: pg, per_page: perPage }); if (filters.phone) params.set('phone', filters.phone); if (filters.agentName) params.set('agent_name', filters.agentName); if (filters.dateFrom) params.set('date_from', filters.dateFrom); if (filters.dateTo) params.set('date_to', filters.dateTo); if (filters.hasRecording) params.set('has_recording', 'true'); const res = await authFetch(`/api/recordings?${params.toString()}`); const data = await res.json(); setRecordings(data.recordings || []); setTotal(data.total || 0); setSearched(true); } catch (err) { toast && toast('Kayıtlar yüklenemedi: ' + err.message, 'error'); } finally { setLoading(false); } }, [perPage]); // Arama butonuna basılınca function handleSearch() { setPage(1); doFetch(1, { phone, agentName, dateFrom, dateTo, hasRecording }); } // Enter tuşu desteği function handleKeyDown(e) { if (e.key === 'Enter') handleSearch(); } // Temizle function handleClear() { setPhone(''); setAgentName(''); setDateFrom(''); setDateTo(''); setHasRecording(true); setRecordings([]); setTotal(0); setSearched(false); setPage(1); } // Sayfa değişince yeniden fetch (arama yapılmışsa) React.useEffect(() => { if (searched) { doFetch(page, { phone, agentName, dateFrom, dateTo, hasRecording }); } }, [page]); async function handleDelete(rec) { try { const res = await authFetch(`/api/recordings/${rec.id}`, { method: 'DELETE' }); if (res.ok) { toast && toast('Kayıt silindi', 'success'); doFetch(page, { phone, agentName, dateFrom, dateTo, hasRecording }); if (activeRecording && activeRecording.id === rec.id) setActiveRecording(null); } else { const d = await res.json(); toast && toast(d.error || 'Silme hatası', 'error'); } } catch (err) { toast && toast('Silme hatası: ' + err.message, 'error'); } finally { setDeleteTarget(null); } } const totalPages = Math.ceil(total / perPage); const dispositionLabel = (code) => { const map = { ANSWERED: 'Yanıtlandı', 'NO ANSWER': 'Yanıtsız', BUSY: 'Meşgul', FAILED: 'Başarısız' }; return map[code] || code || '-'; }; const dispositionColor = (code) => { const map = { ANSWERED: '#22c55e', 'NO ANSWER': '#f59e0b', BUSY: '#f97316', FAILED: '#ef4444' }; return map[code] || 'var(--text-dim)'; }; return React.createElement('div', { style: { paddingBottom: activeRecording ? 100 : 0 } }, // ── Başlık ────────────────────────────────────────────────────────────── React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 } }, React.createElement('div', null, React.createElement('h2', { style: { margin: 0, fontSize: 22, fontWeight: 600 } }, 'Çağrı Kayıtları'), React.createElement('p', { style: { margin: '4px 0 0', color: 'var(--text-dim)', fontSize: 14 } }, 'Arama kriterlerini girerek kayıtları listeleyin' ) ) ), // ── Arama Paneli ───────────────────────────────────────────────────────── React.createElement('div', { className: 'card', style: { padding: '16px 20px', marginBottom: 20 } }, // Satır 1: tarih + telefon + temsilci React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 12, marginBottom: 12, alignItems: 'flex-end' } }, React.createElement('div', null, React.createElement('label', { style: { fontSize: 12, color: 'var(--text-dim)', display: 'block', marginBottom: 4 } }, 'Başlangıç Tarihi'), React.createElement('input', { type: 'date', className: 'form-input', value: dateFrom, onChange: e => setDateFrom(e.target.value), onKeyDown: handleKeyDown, style: { fontSize: 13 } }) ), React.createElement('div', null, React.createElement('label', { style: { fontSize: 12, color: 'var(--text-dim)', display: 'block', marginBottom: 4 } }, 'Bitiş Tarihi'), React.createElement('input', { type: 'date', className: 'form-input', value: dateTo, onChange: e => setDateTo(e.target.value), onKeyDown: handleKeyDown, style: { fontSize: 13 } }) ), React.createElement('div', null, React.createElement('label', { style: { fontSize: 12, color: 'var(--text-dim)', display: 'block', marginBottom: 4 } }, 'Telefon Numarası'), React.createElement('input', { type: 'text', className: 'form-input', value: phone, placeholder: '05xx...', onChange: e => setPhone(e.target.value), onKeyDown: handleKeyDown, style: { fontSize: 13, width: 160 } }) ), React.createElement('div', null, React.createElement('label', { style: { fontSize: 12, color: 'var(--text-dim)', display: 'block', marginBottom: 4 } }, 'Temsilci / Asistan'), React.createElement('input', { type: 'text', className: 'form-input', value: agentName, placeholder: 'Ad ile ara...', onChange: e => setAgentName(e.target.value), onKeyDown: handleKeyDown, style: { fontSize: 13, width: 180 } }) ), React.createElement('label', { style: { display: 'flex', alignItems: 'center', gap: 6, fontSize: 13, cursor: 'pointer', paddingBottom: 2 } }, React.createElement('input', { type: 'checkbox', checked: hasRecording, onChange: e => setHasRecording(e.target.checked), style: { accentColor: 'var(--blue)' } }), 'Sadece ses kaydı olanlar' ), ), // Satır 2: butonlar React.createElement('div', { style: { display: 'flex', gap: 8 } }, React.createElement('button', { className: 'btn btn-primary', onClick: handleSearch, disabled: loading, style: { fontSize: 13, minWidth: 100 } }, loading ? 'Aranıyor…' : '🔍 Ara'), React.createElement('button', { className: 'btn btn-secondary', onClick: handleClear, style: { fontSize: 13 } }, 'Temizle'), searched && !loading && React.createElement('span', { style: { display: 'flex', alignItems: 'center', fontSize: 13, color: 'var(--text-dim)', marginLeft: 8 } }, `${total} kayıt bulundu`) ) ), // ── Tablo (sadece arama yapılmışsa) ────────────────────────────────────── searched && React.createElement('div', { className: 'card', style: { overflowX: 'auto', padding: 0 } }, React.createElement('table', { style: { width: '100%', borderCollapse: 'collapse', fontSize: 13 } }, React.createElement('thead', null, React.createElement('tr', { style: { background: 'var(--bg-hover)', borderBottom: '1px solid var(--border)' } }, ['Tarih / Saat', 'Telefon', 'Temsilci / Asistan', 'Süre', 'Boyut', 'Durum', 'İşlemler'].map(h => React.createElement('th', { key: h, style: { padding: '10px 14px', textAlign: 'left', fontWeight: 600, color: 'var(--text-dim)', whiteSpace: 'nowrap' } }, h) ) ) ), React.createElement('tbody', null, loading ? React.createElement('tr', null, React.createElement('td', { colSpan: 7, style: { textAlign: 'center', padding: 48, color: 'var(--text-dim)' } }, React.createElement('div', null, '⏳ Yükleniyor...') ) ) : recordings.length === 0 ? React.createElement('tr', null, React.createElement('td', { colSpan: 7, style: { textAlign: 'center', padding: 48, color: 'var(--text-dim)' } }, React.createElement('div', { style: { fontSize: 32, marginBottom: 8 } }, '🔍'), React.createElement('div', null, 'Arama kriterlerinize uygun kayıt bulunamadı.') ) ) : recordings.flatMap(rec => { const hasVars = rec.variables && Object.keys(rec.variables).length > 0; const isExpanded = expandedId === rec.id; const rows = [ React.createElement('tr', { key: rec.id, style: { borderBottom: isExpanded ? 'none' : '1px solid var(--border)', background: activeRecording && activeRecording.id === rec.id ? 'var(--bg-hover)' : 'transparent', transition: 'background 0.2s' } }, React.createElement('td', { style: { padding: '9px 14px', whiteSpace: 'nowrap' } }, hasVars ? React.createElement('button', { onClick: () => setExpandedId(isExpanded ? null : rec.id), style: { background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-dim)', fontSize: 12, marginRight: 4, padding: 0 }, title: isExpanded ? 'Detayı kapat' : 'Müşteri bilgilerini göster', }, isExpanded ? '▾' : '▸') : React.createElement('span', { style: { display: 'inline-block', width: 14, marginRight: 4 } }, ''), formatDateTime(rec.start_time)), React.createElement('td', { style: { padding: '9px 14px', fontFamily: 'monospace' } }, rec.phone_number || '-'), React.createElement('td', { style: { padding: '9px 14px' } }, rec.agent_name || '-'), React.createElement('td', { style: { padding: '9px 14px', whiteSpace: 'nowrap' } }, formatDuration(rec.duration)), React.createElement('td', { style: { padding: '9px 14px', whiteSpace: 'nowrap' } }, formatFileSize(rec.file_size_mb)), React.createElement('td', { style: { padding: '9px 14px' } }, React.createElement('span', { style: { color: dispositionColor(rec.disposition_code), fontWeight: 500, fontSize: 12 } }, dispositionLabel(rec.disposition_code)) ), React.createElement('td', { style: { padding: '9px 14px' } }, React.createElement('div', { style: { display: 'flex', gap: 6 } }, rec.recording_path ? React.createElement('button', { className: 'btn btn-secondary', onClick: () => setActiveRecording(rec), style: { fontSize: 12, padding: '4px 10px' }, title: 'Oynat' }, '▶') : React.createElement('button', { className: 'btn btn-secondary', disabled: true, style: { fontSize: 12, padding: '4px 10px', opacity: 0.4 }, title: 'Kayıt yok' }, '▶'), rec.recording_path ? React.createElement('a', { href: `/api/recordings/download/${rec.id}`, download: rec.recording_filename || true, className: 'btn btn-secondary', style: { fontSize: 12, padding: '4px 10px', textDecoration: 'none' }, title: 'İndir' }, '⬇') : React.createElement('button', { className: 'btn btn-secondary', disabled: true, style: { fontSize: 12, padding: '4px 10px', opacity: 0.4 } }, '⬇'), React.createElement('button', { className: 'btn btn-danger', onClick: () => setDeleteTarget(rec), style: { fontSize: 12, padding: '4px 10px' }, title: 'Kaydı Sil' }, 'Sil') ) ) ) ]; if (isExpanded && hasVars) { rows.push( React.createElement('tr', { key: rec.id + '-meta', style: { borderBottom: '1px solid var(--border)', background: 'var(--surface2, rgba(0,0,0,.03))' } }, React.createElement('td', { colSpan: 7, style: { padding: '8px 14px 12px 32px' } }, React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', gap: '6px 18px', fontSize: 12 } }, Object.entries(rec.variables).map(([k, v]) => React.createElement('div', { key: k }, React.createElement('span', { style: { color: 'var(--text-dim)', marginRight: 6 } }, k + ':'), React.createElement('span', { style: { fontFamily: 'monospace', fontWeight: 500 } }, String(v) || '—') ) ) ) ) ) ); } return rows; }) ) ) ), // ── Sayfalama ───────────────────────────────────────────────────────────── searched && totalPages > 1 && React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 12, fontSize: 13 } }, React.createElement('span', { style: { color: 'var(--text-dim)' } }, `Sayfa ${page} / ${totalPages}` ), React.createElement('div', { style: { display: 'flex', gap: 8 } }, React.createElement('button', { className: 'btn btn-secondary', disabled: page <= 1, onClick: () => setPage(p => Math.max(1, p - 1)), style: { fontSize: 13 } }, '← Önceki'), React.createElement('button', { className: 'btn btn-secondary', disabled: page >= totalPages, onClick: () => setPage(p => Math.min(totalPages, p + 1)), style: { fontSize: 13 } }, 'Sonraki →') ) ), // ── Silme Onayı ─────────────────────────────────────────────────────────── deleteTarget && React.createElement(ConfirmDialog, { message: `"${deleteTarget.phone_number || deleteTarget.id}" numaralı çağrının kaydını silmek istediğinize emin misiniz?`, onConfirm: () => handleDelete(deleteTarget), onCancel: () => setDeleteTarget(null) }), // ── Ses Oynatıcı ────────────────────────────────────────────────────────── activeRecording && React.createElement(AudioPlayerBar, { recording: activeRecording, onClose: () => setActiveRecording(null) }) ); } window.RecordingsTab = RecordingsTab; })();