/* SettingsTab.jsx — Tenant Ayarları: API Keys + Kullanım & Limitler */
const { useState, useEffect, useCallback } = React;
function SettingsTab() {
const user = Auth.getUser();
const isAdmin = user && (user.role === 'admin' || user.role === 'super_admin');
/* ── State ── */
const [keys, setKeys] = useState(null);
const [usage, setUsage] = useState(null);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [msg, setMsg] = useState(null); // { type, text }
const [editKeys, setEditKeys] = useState({}); // Only changed fields
const [visibleFields, setVisibleFields] = useState({});
/* ── Load ── */
const load = useCallback(async () => {
setLoading(true);
try {
const [kRes, uRes] = await Promise.all([
authFetch(API + '/api/tenant/api-keys'),
authFetch(API + '/api/tenant/usage'),
]);
if (kRes.ok) setKeys(await kRes.json());
if (uRes.ok) setUsage(await uRes.json());
} catch (e) {
console.error('Settings load error:', e);
}
setLoading(false);
}, []);
useEffect(() => { load(); }, []);
/* ── Save API Keys ── */
const saveKeys = async () => {
if (!Object.keys(editKeys).length) return;
setSaving(true);
setMsg(null);
try {
const r = await authFetch(API + '/api/tenant/api-keys', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(editKeys),
});
if (r.ok) {
setKeys(await r.json());
setEditKeys({});
setMsg({ type: 'success', text: 'API anahtarları güncellendi.' });
} else {
const err = await r.json().catch(() => ({}));
setMsg({ type: 'error', text: err.detail || 'Kaydetme başarısız.' });
}
} catch (e) {
setMsg({ type: 'error', text: 'Bağlantı hatası.' });
}
setSaving(false);
};
/* ── Helpers ── */
const handleKeyChange = (field, value) => {
setEditKeys(prev => ({ ...prev, [field]: value }));
};
const toggleVisible = (field) => {
setVisibleFields(prev => ({ ...prev, [field]: !prev[field] }));
};
const usagePercent = (u) => {
if (!u || !u.limit) return 0;
return Math.min(100, Math.round((u.current / u.limit) * 100));
};
const usageBarClass = (pct) => {
if (pct >= 90) return 'danger';
if (pct >= 70) return 'warn';
return '';
};
/* ── Loading ── */
if (loading) {
return (
);
}
/* ── Render ── */
const keyField = (label, field, placeholder) => {
const masked = keys ? keys[field] : '';
const editing = field in editKeys;
const val = editing ? editKeys[field] : (masked || '');
const isSecret = field.includes('key');
return (
);
};
const usageRow = (label, u) => {
if (!u) return null;
const pct = usagePercent(u);
return (
{label}
{u.current} / {u.limit}
);
};
return (
{/* Message banner */}
{msg && (
{msg.text}
)}
{/* ── API Keys ── */}
API Anahtarları
Azure Speech
{keyField('Speech Key', 'azure_speech_key', 'Azure Speech API Key')}
{keyField('Speech Region', 'azure_speech_region', 'eastus')}
Azure OpenAI
{keyField('Endpoint', 'azure_openai_endpoint', 'https://xxx.openai.azure.com/')}
{keyField('API Key', 'azure_openai_key', 'Azure OpenAI Key')}
{keyField('Deployment', 'azure_openai_deploy', 'gpt-4o')}
{keyField('Embedding Deployment', 'azure_openai_embed_deploy', 'text-embedding-3-small')}
{keyField('API Version', 'azure_openai_api_version', '2025-01-01-preview')}
ElevenLabs / MiniMax TTS
{keyField('ElevenLabs Key', 'elevenlabs_key', 'ElevenLabs API Key')}
{keyField('MiniMax Key', 'minimax_key', 'MiniMax API Key')}
{keyField('MiniMax Group ID', 'minimax_group_id', 'MiniMax Group ID')}
{isAdmin && (
{saving ? 'Kaydediliyor…' : 'Kaydet'}
{Object.keys(editKeys).length > 0 && (
setEditKeys({})}>
İptal
)}
)}
{/* ── Usage & Limits ── */}
Kullanım & Limitler
{usage && (
<>
{usage.plan?.toUpperCase() || 'STARTER'}
{usageRow('AI Agent', usage.agents)}
{usageRow('İnsan Agent', usage.human_agents)}
{usageRow('Kampanya', usage.campaigns)}
{usageRow('Kuyruk', usage.queues)}
>
)}
{!usage && (
Kullanım bilgisi yüklenemedi.
)}
{/* ── Departman Yonetimi ── */}
{isAdmin &&
}
{/* ── Sonuc Kodlari Yonetimi ── */}
{isAdmin &&
}
);
}
/* ═══════════════════════════════════════════════════════════════════════════
DepartmentManager — Departman Yönetimi
═══════════════════════════════════════════════════════════════════════════ */
function DepartmentManager() {
const [depts, setDepts] = useState([]);
const [loading, setLoading] = useState(true);
const [showForm, setShowForm] = useState(false);
const [editingId, setEditingId] = useState(null);
const [form, setForm] = useState({ key: '', label: '', sort_order: 0 });
const [msg, setMsg] = useState(null);
const loadDepts = async () => {
try {
const r = await authFetch(API + '/api/departments');
if (r.ok) setDepts(await r.json());
} catch (e) {}
setLoading(false);
};
useEffect(() => { loadDepts(); }, []);
const seedDefaults = async () => {
try {
const r = await authFetch(API + '/api/departments/seed', { method: 'POST' });
const data = await r.json();
setMsg({ type: 'success', text: data.message || 'Varsayilan departmanlar yuklendi' });
loadDepts();
} catch (e) {
setMsg({ type: 'error', text: 'Hata' });
}
};
const handleSave = async () => {
if (!form.key || !form.label) return;
try {
const url = editingId
? API + `/api/departments/${editingId}`
: API + '/api/departments';
const method = editingId ? 'PUT' : 'POST';
const r = await authFetch(url, {
method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(form),
});
if (r.ok) {
setMsg({ type: 'success', text: editingId ? 'Guncellendi' : 'Eklendi' });
setShowForm(false);
setEditingId(null);
setForm({ key: '', label: '', sort_order: 0 });
loadDepts();
} else {
const err = await r.json().catch(() => ({}));
setMsg({ type: 'error', text: err.detail || 'Hata' });
}
} catch (e) {
setMsg({ type: 'error', text: 'Baglanti hatasi' });
}
};
const handleEdit = (d) => {
setEditingId(d.id);
setForm({ key: d.key, label: d.label, sort_order: d.sort_order || 0 });
setShowForm(true);
};
const handleDelete = async (id) => {
if (!confirm('Bu departmani silmek istiyor musunuz?')) return;
try {
const r = await authFetch(API + `/api/departments/${id}`, { method: 'DELETE' });
if (r.ok) {
setMsg({ type: 'success', text: 'Silindi' });
loadDepts();
} else {
const err = await r.json().catch(() => ({}));
setMsg({ type: 'error', text: err.detail || 'Silinemedi' });
}
} catch (e) {}
};
const handleToggle = async (d) => {
try {
await authFetch(API + `/api/departments/${d.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ is_active: !d.is_active }),
});
loadDepts();
} catch (e) {}
};
return (
Departmanlar
{depts.length === 0 && (
Varsayilanlari Yukle
)}
{
setEditingId(null);
setForm({ key: '', label: '', sort_order: 0 });
setShowForm(true);
}} style={{ fontSize: 12 }}>
+ Yeni Departman
{msg && (
{msg.text}
)}
{showForm && (
{editingId ? 'Departman Duzenle' : 'Yeni Departman'}
{ setShowForm(false); setEditingId(null); }} style={{ fontSize: 12 }}>Iptal
{editingId ? 'Guncelle' : 'Ekle'}
)}
{loading ? (
) : depts.length === 0 ? (
Henuz departman tanimlanmamis.
"Varsayilanlari Yukle" ile standart departmanlari ekleyebilir veya kendiniz tanimlayabilirsiniz.
) : (
{['Kod', 'Etiket', 'Sira', 'Durum', ''].map((h, i) => (
{h}
))}
{depts.map(d => (
{d.key}
{d.label}
{d.sort_order}
handleToggle(d)} style={{
padding: '2px 10px', borderRadius: 8, fontSize: 10, fontWeight: 600, border: 'none', cursor: 'pointer',
background: d.is_active ? 'rgba(34,197,94,.1)' : 'rgba(239,68,68,.1)',
color: d.is_active ? 'var(--green)' : 'var(--red)',
}}>
{d.is_active ? 'Aktif' : 'Pasif'}
handleEdit(d)} style={{
padding: '4px 10px', borderRadius: 6, fontSize: 11, border: '1px solid var(--border)',
background: 'var(--surface2)', color: 'var(--text)', cursor: 'pointer', marginRight: 4,
}}>Duzenle
handleDelete(d.id)} style={{
padding: '4px 10px', borderRadius: 6, fontSize: 11, border: '1px solid rgba(239,68,68,.2)',
background: 'rgba(239,68,68,.05)', color: 'var(--red)', cursor: 'pointer',
}}>Sil
))}
)}
);
}
/* ═══════════════════════════════════════════════════════════════════════════
DispositionManager — Sonuc Kodu Yonetim Paneli
═══════════════════════════════════════════════════════════════════════════ */
function DispositionManager() {
const [codes, setCodes] = useState([]);
const [loading, setLoading] = useState(true);
const [showForm, setShowForm] = useState(false);
const [editingId, setEditingId] = useState(null);
const [form, setForm] = useState({ code: '', label: '', color: '#9ca3af', icon: '', category: 'general', sort_order: 0 });
const [msg, setMsg] = useState(null);
const loadCodes = async () => {
try {
const r = await authFetch(API + '/api/dispositions?active_only=false');
if (r.ok) setCodes(await r.json());
} catch (e) {}
setLoading(false);
};
useEffect(() => { loadCodes(); }, []);
const seedDefaults = async () => {
try {
const r = await authFetch(API + '/api/dispositions/seed', { method: 'POST' });
const data = await r.json();
setMsg({ type: 'success', text: data.message || 'Varsayilan kodlar yuklendi' });
loadCodes();
} catch (e) {
setMsg({ type: 'error', text: 'Hata' });
}
};
const handleSave = async () => {
if (!form.code || !form.label) return;
try {
const url = editingId
? API + `/api/dispositions/${editingId}`
: API + '/api/dispositions';
const method = editingId ? 'PUT' : 'POST';
const r = await authFetch(url, {
method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(form),
});
if (r.ok) {
setMsg({ type: 'success', text: editingId ? 'Guncellendi' : 'Eklendi' });
setShowForm(false);
setEditingId(null);
setForm({ code: '', label: '', color: '#9ca3af', icon: '', category: 'general', sort_order: 0 });
loadCodes();
} else {
const err = await r.json().catch(() => ({}));
setMsg({ type: 'error', text: err.detail || 'Hata' });
}
} catch (e) {
setMsg({ type: 'error', text: 'Baglanti hatasi' });
}
};
const handleEdit = (c) => {
setEditingId(c.id);
setForm({ code: c.code, label: c.label, color: c.color || '#9ca3af', icon: c.icon || '', category: c.category || 'general', sort_order: c.sort_order || 0 });
setShowForm(true);
};
const handleDelete = async (id) => {
if (!confirm('Bu sonuc kodunu silmek istiyor musunuz?')) return;
try {
const r = await authFetch(API + `/api/dispositions/${id}`, { method: 'DELETE' });
if (r.ok) {
setMsg({ type: 'success', text: 'Silindi' });
loadCodes();
} else {
const err = await r.json().catch(() => ({}));
setMsg({ type: 'error', text: err.detail || 'Silinemedi' });
}
} catch (e) {}
};
const handleToggle = async (c) => {
try {
await authFetch(API + `/api/dispositions/${c.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ is_active: !c.is_active }),
});
loadCodes();
} catch (e) {}
};
const categoryLabel = (cat) => {
return { general: 'Genel', dialer: 'Dialer', inbound: 'Inbound' }[cat] || cat;
};
const categoryColor = (cat) => {
return { general: '#6366f1', dialer: '#f59e0b', inbound: '#3b82f6' }[cat] || '#9ca3af';
};
return (
Sonuc Kodlari
{codes.length === 0 && (
Varsayilanlari Yukle
)}
{
setEditingId(null);
setForm({ code: '', label: '', color: '#9ca3af', icon: '', category: 'general', sort_order: 0 });
setShowForm(true);
}} style={{ fontSize: 12 }}>
+ Yeni Kod
{msg && (
{msg.text}
)}
{/* Yeni/Duzenle formu */}
{showForm && (
{editingId ? 'Sonuc Kodu Duzenle' : 'Yeni Sonuc Kodu'}
Renk
setForm({ ...form, color: e.target.value })}
style={{ width: '100%', height: 34, border: '1px solid var(--border)', borderRadius: 6, marginTop: 4, cursor: 'pointer' }} />
Kategori
setForm({ ...form, category: e.target.value })}
style={{ fontSize: 12, marginTop: 4 }}>
Genel (Hepsi)
Dialer
Inbound
Sira
setForm({ ...form, sort_order: parseInt(e.target.value) || 0 })}
style={{ fontSize: 12, marginTop: 4 }} />
{ setShowForm(false); setEditingId(null); }} style={{ fontSize: 12 }}>Iptal
{editingId ? 'Guncelle' : 'Ekle'}
)}
{/* Kod listesi */}
{loading ? (
) : codes.length === 0 ? (
Henuz sonuc kodu tanimlanmamis.
"Varsayilanlari Yukle" butonuna tiklayarak standart kodlari ekleyebilirsiniz.
) : (
{['', 'Kod', 'Etiket', 'Kategori', 'Durum', ''].map((h, i) => (
{h}
))}
{codes.map(c => (
{c.icon || ''}
{c.code}
{c.label}
{categoryLabel(c.category)}
handleToggle(c)} style={{
padding: '2px 10px', borderRadius: 8, fontSize: 10, fontWeight:600, border: 'none', cursor: 'pointer',
background: c.is_active ? 'rgba(34,197,94,.1)' : 'rgba(239,68,68,.1)',
color: c.is_active ? 'var(--green)' : 'var(--red)',
}}>
{c.is_active ? 'Aktif' : 'Pasif'}
handleEdit(c)} style={{
padding: '4px 10px', borderRadius: 6, fontSize: 11, border: '1px solid var(--border)',
background: 'var(--surface2)', color: 'var(--text)', cursor: 'pointer', marginRight: 4,
}}>Duzenle
{!c.is_system && (
handleDelete(c.id)} style={{
padding: '4px 10px', borderRadius: 6, fontSize: 11, border: '1px solid rgba(239,68,68,.2)',
background: 'rgba(239,68,68,.05)', color: 'var(--red)', cursor: 'pointer',
}}>Sil
)}
))}
)}
);
}
window.SettingsTab = SettingsTab;