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:
@@ -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)}
|
||||
|
||||
@@ -10,6 +10,7 @@ import Input from '../../components/ui/Input';
|
||||
import Select from '../../components/ui/Select';
|
||||
import type { ContractType } from '../../types';
|
||||
import { formatDate } from '../../utils/dateFormat';
|
||||
import { useProviderSettings } from '../../hooks/useProviderSettings';
|
||||
import { Plus, Trash2, Eye, EyeOff, Info, X, ArrowLeft } from 'lucide-react';
|
||||
|
||||
// Contract types are now loaded dynamically from the database
|
||||
@@ -67,6 +68,7 @@ export default function ContractForm() {
|
||||
const queryClient = useQueryClient();
|
||||
const isEdit = !!id;
|
||||
const back = popHistory(location.state, isEdit ? `/contracts/${id}` : '/contracts');
|
||||
const { customerEmailLabel } = useProviderSettings();
|
||||
|
||||
const preselectedCustomerId = searchParams.get('customerId');
|
||||
|
||||
@@ -914,7 +916,7 @@ export default function ContractForm() {
|
||||
}}
|
||||
className="text-blue-600"
|
||||
/>
|
||||
<span className="text-sm">Stressfrei-Wechseln Adresse</span>
|
||||
<span className="text-sm">{customerEmailLabel} Adresse</span>
|
||||
</label>
|
||||
{usernameType === 'stressfrei' && (
|
||||
<Select
|
||||
@@ -929,7 +931,7 @@ export default function ContractForm() {
|
||||
)}
|
||||
{usernameType === 'stressfrei' && stressfreiEmails.length === 0 && (
|
||||
<p className="text-xs text-amber-600">
|
||||
Keine Stressfrei-Wechseln Adressen für diesen Kunden vorhanden. Bitte zuerst beim Kunden anlegen.
|
||||
Keine {customerEmailLabel} Adressen für diesen Kunden vorhanden. Bitte zuerst beim Kunden anlegen.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user