added backup and email client
This commit is contained in:
@@ -2,6 +2,7 @@ import { useState, useEffect } from 'react';
|
||||
import { useParams, Link, useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { customerApi, addressApi, bankCardApi, documentApi, meterApi, uploadApi, contractApi, stressfreiEmailApi, emailProviderApi, StressfreiEmail } from '../../services/api';
|
||||
import { EmailClientTab } from '../../components/email';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import Card from '../../components/ui/Card';
|
||||
import Button from '../../components/ui/Button';
|
||||
@@ -132,6 +133,13 @@ export default function CustomerDetail() {
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'emails',
|
||||
label: 'E-Mail-Postfach',
|
||||
content: (
|
||||
<EmailClientTab customerId={customerId} />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'contracts',
|
||||
label: 'Verträge',
|
||||
@@ -2793,9 +2801,21 @@ function StressfreiEmailModal({
|
||||
const [localPart, setLocalPart] = useState('');
|
||||
const [notes, setNotes] = useState('');
|
||||
const [provisionAtProvider, setProvisionAtProvider] = useState(false);
|
||||
const [createMailbox, setCreateMailbox] = useState(false);
|
||||
const [provisionError, setProvisionError] = useState<string | null>(null);
|
||||
const [providerStatus, setProviderStatus] = useState<'idle' | 'checking' | 'exists' | 'not_exists' | 'error'>('idle');
|
||||
const [isProvisioning, setIsProvisioning] = useState(false);
|
||||
const [isEnablingMailbox, setIsEnablingMailbox] = useState(false);
|
||||
const [mailboxEnabled, setMailboxEnabled] = useState(false);
|
||||
const [showCredentials, setShowCredentials] = useState(false);
|
||||
const [credentials, setCredentials] = useState<{
|
||||
email: string;
|
||||
password: string;
|
||||
imap: { server: string; port: number; encryption: string } | null;
|
||||
smtp: { server: string; port: number; encryption: string } | null;
|
||||
} | null>(null);
|
||||
const [isLoadingCredentials, setIsLoadingCredentials] = useState(false);
|
||||
const [isResettingPassword, setIsResettingPassword] = useState(false);
|
||||
const queryClient = useQueryClient();
|
||||
const isEditing = !!email;
|
||||
|
||||
@@ -2845,6 +2865,86 @@ function StressfreiEmailModal({
|
||||
}
|
||||
};
|
||||
|
||||
// Mailbox nachträglich aktivieren
|
||||
const handleEnableMailbox = async () => {
|
||||
if (!email) return;
|
||||
setIsEnablingMailbox(true);
|
||||
setProvisionError(null);
|
||||
try {
|
||||
const result = await stressfreiEmailApi.enableMailbox(email.id);
|
||||
if (result.success) {
|
||||
setMailboxEnabled(true);
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId.toString()] });
|
||||
queryClient.invalidateQueries({ queryKey: ['mailbox-accounts', customerId] });
|
||||
} else {
|
||||
setProvisionError(result.error || 'Mailbox-Aktivierung fehlgeschlagen');
|
||||
}
|
||||
} catch (error) {
|
||||
setProvisionError(error instanceof Error ? error.message : 'Fehler bei der Mailbox-Aktivierung');
|
||||
} finally {
|
||||
setIsEnablingMailbox(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Mailbox-Status mit Provider synchronisieren
|
||||
const syncMailboxStatusFromProvider = async () => {
|
||||
if (!email) return;
|
||||
try {
|
||||
const result = await stressfreiEmailApi.syncMailboxStatus(email.id);
|
||||
if (result.success && result.data) {
|
||||
setMailboxEnabled(result.data.hasMailbox);
|
||||
if (result.data.wasUpdated) {
|
||||
// DB wurde aktualisiert, Query invalidieren
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId.toString()] });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Synchronisieren des Mailbox-Status:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Mailbox-Zugangsdaten laden
|
||||
const loadCredentials = async () => {
|
||||
if (!email) return;
|
||||
setIsLoadingCredentials(true);
|
||||
try {
|
||||
const result = await stressfreiEmailApi.getMailboxCredentials(email.id);
|
||||
if (result.success && result.data) {
|
||||
setCredentials(result.data);
|
||||
setShowCredentials(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Zugangsdaten:', error);
|
||||
} finally {
|
||||
setIsLoadingCredentials(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Passwort zurücksetzen
|
||||
const handleResetPassword = async () => {
|
||||
if (!email) return;
|
||||
if (!confirm('Neues Passwort generieren? Das alte Passwort wird ungültig.')) return;
|
||||
|
||||
setIsResettingPassword(true);
|
||||
try {
|
||||
const result = await stressfreiEmailApi.resetPassword(email.id);
|
||||
if (result.success && result.data) {
|
||||
// Credentials mit neuem Passwort aktualisieren
|
||||
if (credentials) {
|
||||
setCredentials({ ...credentials, password: result.data.password });
|
||||
}
|
||||
alert('Passwort wurde erfolgreich zurückgesetzt.');
|
||||
} else {
|
||||
alert(result.error || 'Fehler beim Zurücksetzen des Passworts');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Zurücksetzen des Passworts:', error);
|
||||
alert(error instanceof Error ? error.message : 'Fehler beim Zurücksetzen des Passworts');
|
||||
} finally {
|
||||
setIsResettingPassword(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Reset form when modal opens or email changes
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
@@ -2853,40 +2953,45 @@ function StressfreiEmailModal({
|
||||
setLocalPart(emailLocalPart);
|
||||
setNotes(email.notes || '');
|
||||
setProviderStatus('idle');
|
||||
setMailboxEnabled(email.hasMailbox || false);
|
||||
// Status beim Provider prüfen wenn Provider vorhanden
|
||||
if (hasProvider) {
|
||||
checkProviderStatus(emailLocalPart);
|
||||
// Mailbox-Status synchronisieren
|
||||
syncMailboxStatusFromProvider();
|
||||
}
|
||||
} else {
|
||||
setLocalPart('');
|
||||
setNotes('');
|
||||
setProvisionAtProvider(false);
|
||||
setCreateMailbox(false);
|
||||
setProviderStatus('idle');
|
||||
setMailboxEnabled(false);
|
||||
}
|
||||
setProvisionError(null);
|
||||
// Zugangsdaten zurücksetzen
|
||||
setShowCredentials(false);
|
||||
setCredentials(null);
|
||||
}
|
||||
}, [isOpen, email, hasProvider]);
|
||||
|
||||
const createMutation = useMutation({
|
||||
mutationFn: async (data: { email: string; notes?: string; provision?: boolean }) => {
|
||||
// Wenn Provisionierung aktiviert, erst beim Provider anlegen
|
||||
if (data.provision && customerEmail) {
|
||||
const provisionResult = await emailProviderApi.provisionEmail(localPart, customerEmail);
|
||||
if (!provisionResult.data?.success) {
|
||||
throw new Error(provisionResult.data?.error || provisionResult.data?.message || 'Provisionierung fehlgeschlagen');
|
||||
}
|
||||
}
|
||||
// Dann in der Datenbank anlegen
|
||||
mutationFn: async (data: { email: string; notes?: string; provision?: boolean; createMailbox?: boolean }) => {
|
||||
// Verwendet die neue API-Funktion, die Provisioning und Mailbox-Erstellung unterstützt
|
||||
return stressfreiEmailApi.create(customerId, {
|
||||
email: data.email,
|
||||
notes: data.notes,
|
||||
provisionAtProvider: data.provision,
|
||||
createMailbox: data.createMailbox,
|
||||
});
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId.toString()] });
|
||||
queryClient.invalidateQueries({ queryKey: ['mailbox-accounts', customerId] });
|
||||
setLocalPart('');
|
||||
setNotes('');
|
||||
setProvisionAtProvider(false);
|
||||
setCreateMailbox(false);
|
||||
onClose();
|
||||
},
|
||||
onError: (error) => {
|
||||
@@ -2918,6 +3023,7 @@ function StressfreiEmailModal({
|
||||
email: fullEmail,
|
||||
notes: notes || undefined,
|
||||
provision: provisionAtProvider,
|
||||
createMailbox: provisionAtProvider && createMailbox,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -2966,24 +3072,48 @@ function StressfreiEmailModal({
|
||||
{hasProvider && customerEmail && (
|
||||
<div className="bg-blue-50 p-3 rounded-lg">
|
||||
{!isEditing ? (
|
||||
// Erstellen-Modus: Checkbox
|
||||
<label className="flex items-start gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={provisionAtProvider}
|
||||
onChange={(e) => setProvisionAtProvider(e.target.checked)}
|
||||
className="mt-1 rounded border-gray-300"
|
||||
/>
|
||||
<div>
|
||||
<span className="text-sm font-medium text-gray-700">
|
||||
Beim E-Mail-Provider anlegen
|
||||
</span>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
Die E-Mail-Weiterleitung wird automatisch auf dem konfigurierten Server erstellt.
|
||||
Weiterleitungsziel: {customerEmail}
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
// Erstellen-Modus: Checkboxen
|
||||
<div className="space-y-3">
|
||||
<label className="flex items-start gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={provisionAtProvider}
|
||||
onChange={(e) => {
|
||||
setProvisionAtProvider(e.target.checked);
|
||||
if (!e.target.checked) setCreateMailbox(false);
|
||||
}}
|
||||
className="mt-1 rounded border-gray-300"
|
||||
/>
|
||||
<div>
|
||||
<span className="text-sm font-medium text-gray-700">
|
||||
Beim E-Mail-Provider anlegen
|
||||
</span>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
Die E-Mail-Weiterleitung wird automatisch auf dem konfigurierten Server erstellt.
|
||||
Weiterleitungsziel: {customerEmail}
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
{provisionAtProvider && (
|
||||
<label className="flex items-start gap-2 cursor-pointer ml-6">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={createMailbox}
|
||||
onChange={(e) => setCreateMailbox(e.target.checked)}
|
||||
className="mt-1 rounded border-gray-300"
|
||||
/>
|
||||
<div>
|
||||
<span className="text-sm font-medium text-gray-700">
|
||||
Echte Mailbox erstellen (IMAP/SMTP-Zugang)
|
||||
</span>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
Ermöglicht E-Mails direkt im CRM zu empfangen und zu versenden.
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
// Bearbeiten-Modus: Status anzeigen
|
||||
<div className="space-y-2">
|
||||
@@ -3039,6 +3169,115 @@ function StressfreiEmailModal({
|
||||
Erneut prüfen
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{/* Mailbox-Status anzeigen wenn Provider vorhanden */}
|
||||
{providerStatus === 'exists' && (
|
||||
<div className="pt-3 mt-3 border-t border-blue-100">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium text-gray-700">
|
||||
Mailbox (IMAP/SMTP)
|
||||
</span>
|
||||
{mailboxEnabled ? (
|
||||
<span className="text-xs text-green-600 flex items-center gap-1">
|
||||
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Mailbox aktiv
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-xs text-orange-600">Keine Mailbox</span>
|
||||
)}
|
||||
</div>
|
||||
{!mailboxEnabled && (
|
||||
<div className="mt-2">
|
||||
<p className="text-xs text-gray-500 mb-2">
|
||||
Aktiviere eine echte Mailbox um E-Mails direkt im CRM zu empfangen und zu versenden.
|
||||
</p>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
onClick={handleEnableMailbox}
|
||||
disabled={isEnablingMailbox}
|
||||
>
|
||||
{isEnablingMailbox ? 'Wird aktiviert...' : 'Mailbox aktivieren'}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{/* Zugangsdaten anzeigen wenn Mailbox aktiv */}
|
||||
{mailboxEnabled && (
|
||||
<div className="mt-3">
|
||||
{!showCredentials ? (
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
onClick={loadCredentials}
|
||||
disabled={isLoadingCredentials}
|
||||
>
|
||||
{isLoadingCredentials ? (
|
||||
'Laden...'
|
||||
) : (
|
||||
<>
|
||||
<Eye className="w-4 h-4 mr-1" />
|
||||
Zugangsdaten anzeigen
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
) : credentials && (
|
||||
<div className="bg-white border border-gray-200 rounded-lg p-3 space-y-2">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<span className="text-xs font-medium text-gray-500 uppercase">Zugangsdaten</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowCredentials(false)}
|
||||
className="text-gray-400 hover:text-gray-600"
|
||||
>
|
||||
<EyeOff className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2 text-xs">
|
||||
<div>
|
||||
<span className="text-gray-500">E-Mail:</span>
|
||||
<p className="font-mono text-gray-900 break-all">{credentials.email}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-gray-500">Passwort:</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="font-mono text-gray-900 break-all select-all flex-1">{credentials.password}</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleResetPassword}
|
||||
disabled={isResettingPassword}
|
||||
className="text-blue-600 hover:text-blue-800 text-xs whitespace-nowrap disabled:opacity-50"
|
||||
title="Neues Kennwort generieren"
|
||||
>
|
||||
{isResettingPassword ? 'Generiere...' : 'Neu generieren'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{credentials.imap && (
|
||||
<div className="pt-2 border-t border-gray-100">
|
||||
<span className="text-xs font-medium text-gray-500">IMAP (Empfang)</span>
|
||||
<p className="font-mono text-xs text-gray-900">
|
||||
{credentials.imap.server}:{credentials.imap.port} ({credentials.imap.encryption})
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{credentials.smtp && (
|
||||
<div className="pt-2 border-t border-gray-100">
|
||||
<span className="text-xs font-medium text-gray-500">SMTP (Versand)</span>
|
||||
<p className="font-mono text-xs text-gray-900">
|
||||
{credentials.smtp.server}:{credentials.smtp.port} ({credentials.smtp.encryption})
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user