import React, { useState, useEffect, useCallback, useRef } from 'react'; import api from '../services/api'; import assetService from '../services/assetService'; import { toast } from 'react-toastify'; 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 (
{/* ── Constrained header + tabs ─────────────────────────────────── */}
{/* Header */}

βš™οΈ Einstellungen

Ticket-Kategorien, Vorlagen, E-Mail-Texte und E-Mail-Design verwalten

{/* Tabs */}
{[ { key: 'categories', label: 'πŸ“‚ Kategorien' }, { key: 'templates', label: 'πŸ“ Vorlagen' }, { key: 'asset-types', label: 'πŸ–₯️ Asset-Typen' }, { key: 'email-templates', label: 'πŸ“§ E-Mail-Vorlagen' }, { key: 'email-design', label: '🎨 E-Mail-Design' }, { key: 'cronjobs', label: '⏰ Cronjobs' }, ].map(t => ( ))}
{/* end constrained header/tabs */} {/* ── Constrained tab content (all except email-design) ─────────── */}
{/* ── CATEGORIES TAB ────────────────────────────────────────────── */} {tab === 'categories' && (

{categories.length} Kategorien Β· werden beim Ticket-Erstellen angezeigt

{categories.length === 0 && (

Keine Kategorien vorhanden

)} {categories.map(cat => (
{cat.icon} {cat.name}
))}
)} {/* ── TEMPLATES TAB ─────────────────────────────────────────────── */} {tab === 'templates' && (

{templates.length} Vorlagen Β· erscheinen im Ticket-Erstellungs-Dropdown

{templates.length === 0 && (

Keine Vorlagen vorhanden

)} {templates.map(tpl => (
{PRIORITY_ICONS[tpl.priority]}
{tpl.label}
{tpl.category} Β· {tpl.title || Kein Titel}
))}
)} {/* ── ASSET TYPES TAB ──────────────────────────────────────────── */} {tab === 'asset-types' && } {/* ── EMAIL TEMPLATES TAB ───────────────────────────────────────── */} {tab === 'email-templates' && (

Betreff und Einleitungstext der automatischen E-Mails anpassen. Variablen wie {'{{ticket_number}}'} werden beim Versand ersetzt.

{emailTpls.map(tpl => { const meta = EMAIL_TEMPLATE_LABELS[tpl.type] || { label: tpl.type, desc: '' }; return (
{meta.label}
{meta.desc}
); })}
)} {/* ── CRONJOBS TAB ──────────────────────────────────────────────── */} {tab === 'cronjobs' && }
{/* end constrained tab content */} {/* ── EMAIL DESIGN TAB – full width ─────────────────────────────── */} {tab === 'email-design' && designDraft && (
{/* Left: settings (sticky) */}

🏷️ Marke

setDraft('logo_url', e.target.value)} placeholder="https://... (leer = Emoji-Icon)" />

Bild-URL (PNG/SVG/JPG). Wenn leer wird das Emoji-Icon verwendet.

setDraft('brand_icon', e.target.value)} placeholder="πŸ’»" style={{ fontSize: '18px', width: '80px' }} />
setDraft('brand_name', e.target.value)} placeholder="CEREDA SYSTEMS" />

Erstes Wort dunkel, Rest in PrimΓ€rfarbe.

setDraft('brand_subtitle', e.target.value)} placeholder="IT Support" />

🎨 Farben

{[ { key: 'primary_color', label: 'PrimΓ€rfarbe', desc: 'Akzentleiste, Links, Badge' }, { key: 'button_color', label: 'Button-Farbe', desc: 'Hintergrund des Haupt-Buttons' }, { key: 'bg_color', label: 'Hintergrundfarbe', desc: 'Γ„ußerer E-Mail-Hintergrund' }, ].map(({ key, label, desc }) => (
setDraft(key, e.target.value)} style={{ width: '44px', height: '36px', border: '1px solid var(--border-color)', borderRadius: '8px', cursor: 'pointer', padding: '2px' }} /> setDraft(key, e.target.value)} placeholder="#0d9488" style={{ flex: 1, fontFamily: 'monospace', fontSize: '13px' }} />

{desc}

))}

πŸ“ Footer

setDraft('company_name', e.target.value)} placeholder="Cereda Systems GmbH" />