import React, { useEffect, useState } from 'react'; import { fieldClass } from '../acquisition/acquisitionUi'; import { labelClass } from '../leads/leadUi'; import { ConfirmDialog } from '../ui/Modal'; import { useToast } from '../Toast'; import { acquisitionOptionsApi } from '../../lib/api'; import { ACQUISITION_FIELD_HINTS, ACQUISITION_FIELD_LABELS, ACQUISITION_OPTION_FIELDS, EMPTY_ACQUISITION_OPTIONS, type AcquisitionOption, type AcquisitionOptionField, type AcquisitionOptions, } from '../../types'; /* * Inhalte der Akquise-Dropdowns pflegen: "Von", "Kanal", "Berater". * * "Anbieter" steht bewusst nicht dabei - das Feld ist Freitext mit Vorschlaegen * aus den bisherigen Eintraegen. * * Gleich aufgebaute Listen statt Sonderfaelle - die Felder verhalten sich * gleich, also sieht ihre Pflege auch gleich aus. * * Umbenennen zieht die vorhandenen Akquise-Eintraege mit, Loeschen nicht: eine * Liste aufzuraeumen darf keine Daten aendern. Beide Antworten sagen, wie viele * Eintraege betroffen sind, und das steht dann auch in der Meldung. */ const PlusIcon = ( ); const OptionRow: React.FC<{ option: AcquisitionOption; onRenamed: (value: string) => Promise; onDelete: () => void; }> = ({ option, onRenamed, onDelete }) => { const [isEditing, setIsEditing] = useState(false); const [draft, setDraft] = useState(option.value); const [isSaving, setIsSaving] = useState(false); // Wird der Wert von aussen umbenannt (oder die Liste neu geladen), soll der // Entwurf nicht auf dem alten Stand stehen bleiben. useEffect(() => { setDraft(option.value); }, [option.value]); const save = async () => { const value = draft.trim(); if (value === '' || value === option.value) { setDraft(option.value); setIsEditing(false); return; } setIsSaving(true); try { await onRenamed(value); setIsEditing(false); } finally { setIsSaving(false); } }; return (
  • {isEditing ? ( setDraft(event.target.value)} onKeyDown={(event) => { if (event.key === 'Enter') { event.preventDefault(); save(); } if (event.key === 'Escape') { setDraft(option.value); setIsEditing(false); } }} onBlur={save} aria-label={`${option.value} umbenennen`} className={`${fieldClass} py-1.5`} /> ) : ( <> {option.value} )}
  • ); }; export const AcquisitionSettings: React.FC = () => { const { addToast } = useToast(); const [options, setOptions] = useState(EMPTY_ACQUISITION_OPTIONS); const [isLoading, setIsLoading] = useState(true); const [drafts, setDrafts] = useState>({ sender: '', channel: '', consultant: '', }); const [toDelete, setToDelete] = useState<{ field: AcquisitionOptionField; option: AcquisitionOption; } | null>(null); const load = async (silent = false) => { if (!silent) setIsLoading(true); try { setOptions(await acquisitionOptionsApi.list()); } catch (err) { addToast(err instanceof Error ? err.message : 'Auswahllisten nicht abrufbar.', 'error'); } finally { setIsLoading(false); } }; useEffect(() => { load(); // Einmal beim Oeffnen des Reiters. // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const add = async (field: AcquisitionOptionField) => { const value = drafts[field].trim(); if (!value) return; try { await acquisitionOptionsApi.create(field, value); setDrafts((prev) => ({ ...prev, [field]: '' })); addToast(`„${value}" zu ${ACQUISITION_FIELD_LABELS[field]} hinzugefügt.`, 'success'); await load(true); } catch (err) { addToast(err instanceof Error ? err.message : 'Hinzufügen fehlgeschlagen.', 'error'); } }; const rename = async (option: AcquisitionOption, value: string) => { try { const result = await acquisitionOptionsApi.update(option.id, value); addToast( result.updated > 0 ? `Umbenannt - ${result.updated} Eintrag/Einträge in der Akquise mitgeändert.` : 'Umbenannt.', 'success' ); await load(true); } catch (err) { addToast(err instanceof Error ? err.message : 'Umbenennen fehlgeschlagen.', 'error'); throw err; } }; const confirmDelete = async () => { if (!toDelete) return; try { const result = await acquisitionOptionsApi.remove(toDelete.option.id); addToast( result.still_in_use > 0 ? `Aus der Liste entfernt. ${result.still_in_use} Eintrag/Einträge behalten den Wert.` : 'Aus der Liste entfernt.', 'success' ); await load(true); } catch (err) { addToast(err instanceof Error ? err.message : 'Entfernen fehlgeschlagen.', 'error'); } finally { setToDelete(null); } }; if (isLoading) { return (
    ); } return (

    Auswahllisten der Akquise

    Diese drei Listen füllen die Dropdowns Von, Kanal und Berater im Formular unter Akquise. Pflegen darf jeder angemeldete Benutzer - wer beim Eintragen einen fehlenden Wert braucht, soll weiterarbeiten können. Das Feld Anbieter steht bewusst nicht hier: dort wird frei getippt, mit Vorschlägen aus den bisherigen Einträgen.

    Umbenennen ändert die vorhandenen Akquise-Einträge mit. Entfernen nimmt den Wert nur aus der Auswahl - Einträge, die ihn tragen, behalten ihn und zeigen ihn als „nicht in der Liste".

    {ACQUISITION_OPTION_FIELDS.map((field) => (

    {ACQUISITION_FIELD_LABELS[field]}

    {options[field].length} {options[field].length === 1 ? 'Eintrag' : 'Einträge'}

    {ACQUISITION_FIELD_HINTS[field]}

    setDrafts((prev) => ({ ...prev, [field]: event.target.value })) } onKeyDown={(event) => { if (event.key === 'Enter') { event.preventDefault(); add(field); } }} placeholder={`Neuer Wert für ${ACQUISITION_FIELD_LABELS[field]}`} className={fieldClass} />
    {options[field].length === 0 ? (

    Noch nichts hinterlegt - das Dropdown ist dann leer.

    ) : (
      {options[field].map((option) => ( rename(option, value)} onDelete={() => setToDelete({ field, option })} /> ))}
    )}
    ))}
    {toDelete && ( setToDelete(null)} /> )}
    ); };