import React, { useState, useEffect, useCallback, useRef } from 'react'; import api from '../services/api'; import assetService from '../services/assetService'; const EMAIL_TEMPLATE_LABELS = { comment_added: { label: 'π¬ Neuer Kommentar', desc: 'Gesendet an den Ersteller wenn Support antwortet' }, escalation: { label: 'β οΈ Eskalation', desc: 'An Admins bei ΓΌberfΓ€lligen kritischen/hohen Tickets' }, satisfaction: { label: 'β Zufriedenheits-Feedback', desc: 'Gesendet nach TicketschlieΓung' }, status_changed: { label: 'π StatusΓ€nderung', desc: 'An den Ersteller bei Statuswechsel' }, ticket_assigned: { label: 'π€ Ticket-Zuweisung', desc: 'An den Bearbeiter bei Ticket-Zuweisung' }, ticket_created: { label: 'β Ticket-BestΓ€tigung', desc: 'An den Ersteller nach Ticket-Erstellung' }, }; const EMAIL_TEMPLATE_VARS = { ticket_created: ['ticket_number', 'ticket_title', 'requester_name', 'category', 'priority'], ticket_assigned: ['ticket_number', 'ticket_title', 'assignee_name', 'requester_name', 'requester_email', 'category', 'priority'], comment_added: ['ticket_number', 'ticket_title', 'author_name'], status_changed: ['ticket_number', 'ticket_title', 'old_status', 'new_status'], escalation: ['ticket_number', 'ticket_title', 'priority', 'requester_name', 'age_hours'], satisfaction: ['ticket_number', 'ticket_title', 'requester_name'], }; const PRIORITIES = ['niedrig', 'mittel', 'hoch', 'kritisch']; const PRIORITY_ICONS = { niedrig: 'π’', mittel: 'π΅', hoch: 'π ', kritisch: 'π΄' }; const COMMON_ICONS = ['π', 'π»', 'π₯οΈ', 'π', 'π', 'π¨οΈ', 'π§', 'π', 'π±', 'π§', 'β‘', 'π’', 'π€', 'ποΈ', 'π']; export default function SettingsPage() { const [tab, setTab] = useState('categories'); // ββ Categories state ββββββββββββββββββββββββββββββββββββββββββββββββββββββ const [categories, setCategories] = useState([]); const [catModal, setCatModal] = useState(null); // null | { id?, name, icon } const [catLoading, setCatLoading] = useState(false); // ββ Templates state βββββββββββββββββββββββββββββββββββββββββββββββββββββββ const [templates, setTemplates] = useState([]); const [tplModal, setTplModal] = useState(null); // null | template object const [tplLoading, setTplLoading] = useState(false); // ββ Email templates state βββββββββββββββββββββββββββββββββββββββββββββββββ const [emailTpls, setEmailTpls] = useState([]); const [emailModal, setEmailModal] = useState(null); // null | { type, label, subject, intro } const [emailLoading, setEmailLoading] = useState(false); const [lastFocused, setLastFocused] = useState('intro'); const emailSubjectRef = useRef(null); const emailIntroRef = useRef(null); // ββ Email design state ββββββββββββββββββββββββββββββββββββββββββββββββββββ const [emailDesign, setEmailDesign] = useState(null); const [designDraft, setDesignDraft] = useState(null); const [designSaving, setDesignSaving] = useState(false); const [previewType, setPreviewType] = useState('ticket_created'); const [previewDark, setPreviewDark] = useState(false); const [previewHtml, setPreviewHtml] = useState(''); const [previewLoading, setPreviewLoading] = useState(false); const previewDebounce = useRef(null); const loadCategories = useCallback(async () => { const res = await api.get('/settings/categories'); setCategories(res.data); }, []); const loadTemplates = useCallback(async () => { const res = await api.get('/settings/templates'); setTemplates(res.data); }, []); const loadEmailTpls = useCallback(async () => { const res = await api.get('/settings/email-templates'); setEmailTpls(res.data); }, []); const loadEmailDesign = useCallback(async () => { const res = await api.get('/settings/email-design'); setEmailDesign(res.data); setDesignDraft(res.data); }, []); useEffect(() => { loadCategories(); loadTemplates(); loadEmailTpls(); loadEmailDesign(); }, [loadCategories, loadTemplates, loadEmailTpls, loadEmailDesign]); // Fetch preview when designDraft or previewType changes useEffect(() => { if (!designDraft) return; clearTimeout(previewDebounce.current); previewDebounce.current = setTimeout(async () => { setPreviewLoading(true); try { const apiUrl = process.env.REACT_APP_API_URL || '/api'; const token = localStorage.getItem('token'); const res = await fetch(`${apiUrl}/settings/email-preview/${previewType}`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}), }, body: JSON.stringify({ design: designDraft, darkMode: previewDark }), }); const html = await res.text(); setPreviewHtml(html); } catch (_) {} setPreviewLoading(false); }, 600); }, [designDraft, previewType, previewDark]); // eslint-disable-line // ββ Category CRUD ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ const saveCategory = async () => { if (!catModal?.name?.trim()) return; setCatLoading(true); try { if (catModal.id) { await api.put(`/settings/categories/${catModal.id}`, { name: catModal.name, icon: catModal.icon }); } else { await api.post('/settings/categories', { name: catModal.name, icon: catModal.icon }); } await loadCategories(); setCatModal(null); } catch (e) { alert(e.response?.data?.message || 'Fehler beim Speichern'); } finally { setCatLoading(false); } }; const deleteCategory = async (cat) => { if (!window.confirm(`Kategorie β${cat.name}" wirklich lΓΆschen?`)) return; await api.delete(`/settings/categories/${cat.id}`); await loadCategories(); }; // ββ Template CRUD ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ const saveTemplate = async () => { if (!tplModal?.label?.trim()) return; setTplLoading(true); try { if (tplModal.id) { await api.put(`/settings/templates/${tplModal.id}`, tplModal); } else { await api.post('/settings/templates', tplModal); } await loadTemplates(); setTplModal(null); } catch (e) { alert(e.response?.data?.message || 'Fehler beim Speichern'); } finally { setTplLoading(false); } }; const deleteTemplate = async (tpl) => { if (!window.confirm(`Vorlage β${tpl.label}" wirklich lΓΆschen?`)) return; await api.delete(`/settings/templates/${tpl.id}`); await loadTemplates(); }; // ββ Email design CRUD βββββββββββββββββββββββββββββββββββββββββββββββββββββ const saveDesign = async () => { setDesignSaving(true); try { const res = await api.put('/settings/email-design', designDraft); setEmailDesign(res.data); setDesignDraft(res.data); } catch (e) { alert(e.response?.data?.message || 'Fehler beim Speichern'); } finally { setDesignSaving(false); } }; const resetDesign = async () => { if (!window.confirm('Design auf Standardwerte zurΓΌcksetzen?')) return; try { const res = await api.post('/settings/email-design/reset'); setEmailDesign(res.data); setDesignDraft(res.data); } catch (e) { alert(e.response?.data?.message || 'Fehler'); } }; const setDraft = (key, val) => setDesignDraft(d => ({ ...d, [key]: val })); // ββ Email template CRUD βββββββββββββββββββββββββββββββββββββββββββββββββββ const saveEmailTpl = async () => { if (!emailModal?.subject?.trim() || !emailModal?.intro?.trim()) return; setEmailLoading(true); try { await api.put(`/settings/email-templates/${emailModal.type}`, { subject: emailModal.subject, intro: emailModal.intro, }); await loadEmailTpls(); setEmailModal(null); } catch (e) { alert(e.response?.data?.message || 'Fehler beim Speichern'); } finally { setEmailLoading(false); } }; const resetEmailTpl = async (type) => { if (!window.confirm('Vorlage auf Standardwerte zurΓΌcksetzen?')) return; try { await api.post(`/settings/email-templates/${type}/reset`); await loadEmailTpls(); if (emailModal?.type === type) { const res = await api.get('/settings/email-templates'); const updated = res.data.find(t => t.type === type); if (updated) setEmailModal(m => ({ ...m, subject: updated.subject, intro: updated.intro })); } } catch (e) { alert(e.response?.data?.message || 'Fehler beim ZurΓΌcksetzen'); } }; const insertVar = (varName) => { const isSubject = lastFocused === 'subject'; const el = isSubject ? emailSubjectRef.current : emailIntroRef.current; const field = isSubject ? 'subject' : 'intro'; const token = `{{${varName}}}`; if (el) { const start = el.selectionStart ?? el.value.length; const end = el.selectionEnd ?? el.value.length; const newVal = el.value.substring(0, start) + token + el.value.substring(end); setEmailModal(m => ({ ...m, [field]: newVal })); setTimeout(() => { el.focus(); el.selectionStart = el.selectionEnd = start + token.length; }, 0); } else { setEmailModal(m => ({ ...m, [field]: (m[field] || '') + token })); } }; // ββ Styles βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ const card = { background: 'var(--bg-card)', border: '1px solid var(--border-color)', borderRadius: 'var(--radius-xl)', padding: '24px', marginBottom: '16px', }; const rowStyle = { display: 'flex', alignItems: 'center', gap: '12px', padding: '10px 14px', borderRadius: 'var(--radius-md)', background: 'var(--bg-secondary)', marginBottom: '8px', }; const btnPrimary = { background: 'var(--cereda-primary)', color: '#fff', border: 'none', borderRadius: 'var(--radius-md)', padding: '8px 16px', cursor: 'pointer', fontSize: '14px', fontWeight: 600, }; const btnOutline = { background: 'transparent', color: 'var(--text-secondary)', border: '1px solid var(--border-color)', borderRadius: 'var(--radius-md)', padding: '6px 12px', cursor: 'pointer', fontSize: '13px', }; const btnDanger = { background: 'transparent', color: 'var(--danger)', border: '1px solid var(--danger)', borderRadius: 'var(--radius-md)', padding: '6px 12px', cursor: 'pointer', fontSize: '13px', }; return (
Ticket-Kategorien, Vorlagen, E-Mail-Texte und E-Mail-Design verwalten
{categories.length} Kategorien Β· werden beim Ticket-Erstellen angezeigt
Keine Kategorien vorhanden
)} {categories.map(cat => ({templates.length} Vorlagen Β· erscheinen im Ticket-Erstellungs-Dropdown
Keine Vorlagen vorhanden
)} {templates.map(tpl => (
Betreff und Einleitungstext der automatischen E-Mails anpassen. Variablen wie {'{{ticket_number}}'} werden beim Versand ersetzt.
Bild-URL (PNG/SVG/JPG). Wenn leer wird das Emoji-Icon verwendet.
Erstes Wort dunkel, Rest in PrimΓ€rfarbe.
{desc}
Asset-Typen verwalten β werden in allen Dropdowns angezeigt.