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 09e87c951b
commit c3edb8ad2e
1491 changed files with 265550 additions and 1292 deletions
@@ -1,7 +1,7 @@
import { useState, useMemo, useEffect, useRef } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { Link, useSearchParams } from 'react-router-dom';
import { contractApi } from '../../services/api';
import { contractApi, meterApi } from '../../services/api';
import Card from '../../components/ui/Card';
import Badge from '../../components/ui/Badge';
import Select from '../../components/ui/Select';
@@ -28,6 +28,12 @@ import {
BellOff,
RotateCcw,
Receipt,
ShieldAlert,
ShieldX,
CreditCard,
Gauge,
ExternalLink,
CheckCircle2,
} from 'lucide-react';
import type { CockpitContract, CockpitUrgencyLevel, ContractType } from '../../types';
@@ -84,6 +90,11 @@ const issueTypeIcons: Record<string, typeof Calendar> = {
draft_status: FileText,
review_due: RotateCcw,
missing_invoice: Receipt,
missing_identity_document: CreditCard,
identity_document_expired: CreditCard,
identity_document_expiring: CreditCard,
missing_consents: ShieldAlert,
consent_withdrawn: ShieldX,
};
const categoryLabels: Record<string, string> = {
@@ -95,9 +106,10 @@ const categoryLabels: Record<string, string> = {
pendingContracts: 'Wartende Verträge',
missingInvoices: 'Fehlende Rechnungen',
reviewDue: 'Erneute Prüfung fällig',
missingConsents: 'Fehlende Einwilligungen',
};
type FilterType = 'all' | 'critical' | 'warning' | 'ok' | 'deadlines' | 'credentials' | 'data' | 'tasks' | 'review' | 'invoices';
type FilterType = 'all' | 'critical' | 'warning' | 'ok' | 'deadlines' | 'credentials' | 'data' | 'tasks' | 'review' | 'invoices' | 'consents';
export default function ContractCockpit() {
const [searchParams, setSearchParams] = useSearchParams();
@@ -212,6 +224,10 @@ export default function ContractCockpit() {
return contracts.filter(c =>
c.issues.some(i => i.type.includes('invoice'))
);
case 'consents':
return contracts.filter(c =>
c.issues.some(i => ['missing_consents', 'consent_withdrawn'].includes(i.type))
);
default:
return contracts;
}
@@ -510,6 +526,129 @@ export default function ContractCockpit() {
</div>
</Card>
{/* Ausweis-Warnungen (vertragsunabhängig) */}
{cockpitData.data.documentAlerts && cockpitData.data.documentAlerts.length > 0 && (
<Card className="mb-6">
<div className="flex items-center gap-2 mb-3">
<CreditCard className="w-5 h-5 text-amber-500" />
<h3 className="font-medium">Ablaufende Ausweise</h3>
<Badge variant="warning">{cockpitData.data.documentAlerts.length}</Badge>
</div>
<div className="space-y-2">
{cockpitData.data.documentAlerts.map((alert) => (
<div
key={alert.id}
className={`flex items-center justify-between p-3 rounded-lg border ${
alert.urgency === 'critical' ? 'bg-red-50 border-red-200' : 'bg-amber-50 border-amber-200'
}`}
>
<div className="flex items-center gap-3">
<CreditCard className={`w-4 h-4 ${alert.urgency === 'critical' ? 'text-red-500' : 'text-amber-500'}`} />
<div>
<Link
to={`/customers/${alert.customer.id}?tab=documents`}
className="text-sm font-medium hover:underline"
>
{alert.customer.name}
</Link>
<span className="text-xs text-gray-500 ml-2">({alert.customer.customerNumber})</span>
<p className="text-xs text-gray-600">
{alert.type === 'ID_CARD' ? 'Personalausweis' :
alert.type === 'PASSPORT' ? 'Reisepass' :
alert.type === 'DRIVERS_LICENSE' ? 'Führerschein' : 'Ausweis'}{' '}
{alert.documentNumber}
</p>
</div>
</div>
<div className="text-right">
{alert.daysUntilExpiry < 0 ? (
<Badge variant="danger">Seit {Math.abs(alert.daysUntilExpiry)} Tagen abgelaufen</Badge>
) : (
<Badge variant={alert.daysUntilExpiry <= 30 ? 'danger' : 'warning'}>
Noch {alert.daysUntilExpiry} Tage
</Badge>
)}
<p className="text-xs text-gray-500 mt-1">
{new Date(alert.expiryDate).toLocaleDateString('de-DE')}
</p>
</div>
</div>
))}
</div>
</Card>
)}
{/* Gemeldete Zählerstände */}
{cockpitData.data.reportedReadings && cockpitData.data.reportedReadings.length > 0 && (
<Card className="mb-6">
<div className="flex items-center gap-2 mb-3">
<Gauge className="w-5 h-5 text-blue-500" />
<h3 className="font-medium">Gemeldete Zählerstände</h3>
<Badge variant="warning">{cockpitData.data.reportedReadings.length}</Badge>
</div>
<p className="text-sm text-gray-500 mb-3">
Von Kunden gemeldete Zählerstände bitte an den jeweiligen Anbieter übertragen.
</p>
<div className="space-y-2">
{cockpitData.data.reportedReadings.map((reading) => (
<div
key={reading.id}
className="flex items-center justify-between p-3 rounded-lg border bg-blue-50 border-blue-200"
>
<div className="flex items-center gap-3">
{reading.meter.type === 'ELECTRICITY' ? (
<Zap className="w-4 h-4 text-yellow-500" />
) : (
<Flame className="w-4 h-4 text-blue-500" />
)}
<div>
<div className="flex items-center gap-2">
<Link
to={`/customers/${reading.customer.id}?tab=meters`}
className="text-sm font-medium hover:underline"
>
{reading.customer.name}
</Link>
<span className="text-xs text-gray-500">({reading.customer.customerNumber})</span>
</div>
<p className="text-xs text-gray-600">
Zähler {reading.meter.meterNumber} <strong>{reading.value} {reading.unit}</strong> am{' '}
{new Date(reading.readingDate).toLocaleDateString('de-DE')}
{reading.notes && ` ${reading.notes}`}
</p>
</div>
</div>
<div className="flex items-center gap-2">
{reading.providerPortal && (
<a
href={reading.providerPortal.portalUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 text-xs px-2 py-1 bg-white border rounded hover:bg-gray-50"
title={reading.providerPortal.portalUsername ? `Login: ${reading.providerPortal.portalUsername}` : undefined}
>
<ExternalLink className="w-3 h-3" />
{reading.providerPortal.providerName}
</a>
)}
<Button
variant="secondary"
size="sm"
onClick={async () => {
await meterApi.markTransferred(reading.meter.id, reading.id);
queryClient.invalidateQueries({ queryKey: ['contract-cockpit'] });
}}
>
<CheckCircle2 className="w-3 h-3 mr-1" />
Übertragen
</Button>
</div>
</div>
))}
</div>
</Card>
)}
{/* Filter */}
<Card className="mb-6">
<div className="flex items-center gap-4">
@@ -528,6 +667,7 @@ export default function ContractCockpit() {
{ value: 'tasks', label: `Aufgaben/Status (${summary.byCategory.openTasks + summary.byCategory.pendingContracts})` },
{ value: 'review', label: `Erneute Prüfung (${summary.byCategory.reviewDue || 0})` },
{ value: 'invoices', label: `Fehlende Rechnungen (${summary.byCategory.missingInvoices || 0})` },
{ value: 'consents', label: `Einwilligungen (${summary.byCategory.missingConsents || 0})` },
]}
className="w-64"
/>