Mandantenfähigkeit: Domain + Kunden-E-Mail-Label dynamisch pro Provider

Alle hardcoded Referenzen auf 'stressfrei-wechseln.de' und 'Stressfrei-Wechseln'
durch dynamische Werte aus der EmailProviderConfig ersetzt. Notwendig für
Multi-Mandanten-Betrieb, wenn das CRM an Dritte vermietet wird.

Schema:
- Neues Feld EmailProviderConfig.customerEmailLabel (String?)
- Wenn leer, wird Label aus Domain abgeleitet ('stressfrei-wechseln.de' → 'Stressfrei-Wechseln')

Backend:
- Neuer Endpoint GET /api/email-providers/public-settings liefert { domain, customerEmailLabel }
- Neue Service-Funktionen: getProviderPublicSettings(), deriveLabelFromDomain()
- create/updateProviderConfig erweitert um customerEmailLabel

Frontend:
- Neuer Hook useProviderSettings() mit Auto-Caching
- Neues Eingabefeld 'Bezeichnung für Kunden-E-Mails' im Provider-Modal
- Dynamische Domain-Suffix im Adress-Hinzufügen-Dialog (@<domain>)
- Tab-Label 'Stressfrei-Wechseln' im Kunden-Detail → dynamisch
- 'Stressfrei-Wechseln Adresse' in ContractForm → dynamisch
- '(Stressfrei-Wechseln)' Badge in ContractDetail → dynamisch
- 'Stressfrei-Wechseln E-Mail' im Generate-Modal → dynamisch
- Leere-Zustand-Meldungen in Tab und E-Mail-Client → dynamisch

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-23 15:43:19 +02:00
parent cfcdf088df
commit 1290cdad10
13 changed files with 156 additions and 19 deletions
@@ -17,6 +17,7 @@ import { Edit, Trash2, Copy, Eye, EyeOff, ArrowLeft, ArrowRight, Download, Exter
import { calculateConsumption, calculateCosts, calculateMultiMeterConsumption } from '../../utils/energyCalculations';
import CopyButton, { CopyableBlock } from '../../components/ui/CopyButton';
import { formatDate } from '../../utils/dateFormat';
import { useProviderSettings } from '../../hooks/useProviderSettings';
import type { ContractType, ContractStatus, SimCard, MeterReading, ContractTask, ContractTaskSubtask, ContractMeter, ContractDocument } from '../../types';
const typeLabels: Record<ContractType, string> = {
@@ -1444,6 +1445,7 @@ export default function ContractDetail() {
const currentPath = `/contracts/${id}`;
const { hasPermission, isCustomer, isCustomerPortal } = useAuth();
const contractId = parseInt(id!);
const { customerEmailLabel } = useProviderSettings();
const [showPassword, setShowPassword] = useState(false);
const [decryptedPassword, setDecryptedPassword] = useState<string | null>(null);
@@ -2310,7 +2312,7 @@ export default function ContractDetail() {
<dt className="text-sm text-gray-500">
Benutzername
{c.stressfreiEmail && (
<span className="ml-2 text-xs text-blue-600">(Stressfrei-Wechseln)</span>
<span className="ml-2 text-xs text-blue-600">({customerEmailLabel})</span>
)}
</dt>
<dd className="font-mono flex items-center gap-1">
@@ -3363,6 +3365,7 @@ function GenerateInputModal({ templateId, templateName, contractId, onClose }: {
const [stressfreiEmailId, setStressfreiEmailId] = useState('');
const [manualValues, setManualValues] = useState<Record<string, string>>({});
const [generating] = useState(false);
const { customerEmailLabel } = useProviderSettings();
const { data: inputsData, isLoading } = useQuery({
queryKey: ['pdf-inputs', templateId, contractId],
@@ -3390,7 +3393,7 @@ function GenerateInputModal({ templateId, templateName, contractId, onClose }: {
<div className="space-y-4">
{inputs?.needsStressfreiEmail && (
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Stressfrei-Wechseln E-Mail</label>
<label className="block text-sm font-medium text-gray-700 mb-1">{customerEmailLabel} E-Mail</label>
<select
value={stressfreiEmailId}
onChange={(e) => setStressfreiEmailId(e.target.value)}