gdpr audit implemented, email log, vollmachten, pdf delete cancel data privacy and vollmachten, removed message no id card in engergy car, and other contracts that are not telecom contracts, added insert counter for engery

This commit is contained in:
2026-03-21 11:59:53 +01:00
parent 89cf92eaf5
commit f2876f877e
1491 changed files with 265550 additions and 1292 deletions
+187
View File
@@ -0,0 +1,187 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useAuth } from '../../context/AuthContext';
import { gdprApi } from '../../services/api';
import type { ConsentType, ConsentStatus, CustomerConsent } from '../../types';
import {
Shield,
ShieldCheck,
ShieldX,
ShieldAlert,
FileDown,
CheckCircle2,
} from 'lucide-react';
import Card from '../../components/ui/Card';
const CONSENT_TYPE_LABELS: Record<ConsentType, { label: string; description: string }> = {
DATA_PROCESSING: {
label: 'Datenverarbeitung',
description: 'Grundlegende Verarbeitung personenbezogener Daten zur Vertragserfüllung',
},
MARKETING_EMAIL: {
label: 'E-Mail-Marketing',
description: 'Zusendung von Werbung und Angeboten per E-Mail',
},
MARKETING_PHONE: {
label: 'Telefonmarketing',
description: 'Kontaktaufnahme zu Werbezwecken per Telefon',
},
DATA_SHARING_PARTNER: {
label: 'Datenweitergabe an Partner',
description: 'Weitergabe Ihrer Daten an ausgewählte Partnerunternehmen',
},
};
export default function PortalPrivacy() {
const { user } = useAuth();
const queryClient = useQueryClient();
const { data, isLoading } = useQuery({
queryKey: ['my-privacy'],
queryFn: () => gdprApi.getMyPrivacy(),
});
const updateMutation = useMutation({
mutationFn: ({ consentType, status }: { consentType: ConsentType; status: ConsentStatus }) =>
gdprApi.updateConsent(user!.customerId!, consentType, { status, source: 'portal' }),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['my-privacy'] });
queryClient.invalidateQueries({ queryKey: ['my-consent-status'] });
},
});
const handleToggle = (consent: CustomerConsent) => {
const newStatus: ConsentStatus = consent.status === 'GRANTED' ? 'WITHDRAWN' : 'GRANTED';
updateMutation.mutate({ consentType: consent.consentType, status: newStatus });
};
const getStatusIcon = (status: ConsentStatus) => {
switch (status) {
case 'GRANTED':
return <ShieldCheck className="w-5 h-5 text-green-500" />;
case 'WITHDRAWN':
return <ShieldX className="w-5 h-5 text-red-500" />;
case 'PENDING':
return <ShieldAlert className="w-5 h-5 text-yellow-500" />;
default:
return <Shield className="w-5 h-5 text-gray-400" />;
}
};
const getStatusLabel = (status: ConsentStatus) => {
switch (status) {
case 'GRANTED':
return <span className="text-xs px-2 py-0.5 rounded-full bg-green-100 text-green-700">Erteilt</span>;
case 'WITHDRAWN':
return <span className="text-xs px-2 py-0.5 rounded-full bg-red-100 text-red-700">Widerrufen</span>;
case 'PENDING':
return <span className="text-xs px-2 py-0.5 rounded-full bg-yellow-100 text-yellow-700">Ausstehend</span>;
default:
return null;
}
};
if (isLoading) {
return <div className="text-center py-8 text-gray-500">Laden...</div>;
}
const consents = data?.data?.consents || [];
const privacyPolicyHtml = data?.data?.privacyPolicyHtml || '';
const allGranted = consents.every((c) => c.status === 'GRANTED');
const token = localStorage.getItem('token');
return (
<div>
<div className="flex items-center gap-3 mb-6">
<Shield className="w-6 h-6 text-blue-600" />
<h1 className="text-2xl font-bold">Datenschutz</h1>
</div>
{/* Erfolgs-Banner wenn alle erteilt */}
{allGranted && (
<div className="bg-green-50 border border-green-200 rounded-lg p-4 mb-6 flex items-center gap-3">
<CheckCircle2 className="w-5 h-5 text-green-600 flex-shrink-0" />
<p className="text-sm text-green-700">
Sie haben allen Einwilligungen zugestimmt. Vielen Dank!
</p>
</div>
)}
{/* Einwilligungen */}
<Card title="Ihre Einwilligungen" className="mb-6">
<p className="text-sm text-gray-500 mb-4">
Hier können Sie Ihre Datenschutz-Einwilligungen verwalten. Alle Einwilligungen sind erforderlich, damit wir Sie beraten können.
</p>
<div className="space-y-3">
{consents.map((consent) => {
const typeInfo = CONSENT_TYPE_LABELS[consent.consentType] || { label: consent.consentType, description: '' };
return (
<div key={consent.consentType} className="border rounded-lg p-4">
<div className="flex items-start justify-between">
<div className="flex items-start gap-3">
{getStatusIcon(consent.status)}
<div>
<div className="flex items-center gap-2">
<h4 className="font-medium">{typeInfo.label}</h4>
{getStatusLabel(consent.status)}
</div>
<p className="text-sm text-gray-500 mt-0.5">{typeInfo.description}</p>
{consent.grantedAt && consent.status === 'GRANTED' && (
<p className="text-xs text-gray-400 mt-1">
Erteilt am {new Date(consent.grantedAt).toLocaleDateString('de-DE', {
day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit',
})}
</p>
)}
</div>
</div>
<button
onClick={() => handleToggle(consent)}
disabled={updateMutation.isPending}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
consent.status === 'GRANTED'
? 'bg-green-500'
: 'bg-gray-300'
}`}
>
<span
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
consent.status === 'GRANTED' ? 'translate-x-6' : 'translate-x-1'
}`}
/>
</button>
</div>
</div>
);
})}
</div>
{updateMutation.isError && (
<div className="mt-4 p-3 bg-red-50 border border-red-200 rounded-lg text-sm text-red-700">
Fehler beim Speichern. Bitte versuchen Sie es erneut.
</div>
)}
</Card>
{/* Datenschutzerklärung */}
<Card title="Datenschutzerklärung" className="mb-6">
<div className="flex justify-end mb-4">
<a
href={`${gdprApi.getMyPrivacyPdfUrl}?token=${token}`}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 text-sm text-blue-600 hover:text-blue-800"
>
<FileDown className="w-4 h-4" />
Als PDF herunterladen
</a>
</div>
<div
className="prose prose-sm max-w-none"
dangerouslySetInnerHTML={{ __html: privacyPolicyHtml }}
/>
</Card>
</div>
);
}