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:
@@ -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"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user