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
+46 -4
View File
@@ -1,14 +1,56 @@
import { Outlet } from 'react-router-dom';
import { Outlet, Link } from 'react-router-dom';
import { useQuery } from '@tanstack/react-query';
import { useAuth } from '../../context/AuthContext';
import { gdprApi } from '../../services/api';
import Sidebar from './Sidebar';
import ScrollToTopButton from '../ScrollToTopButton';
import { AlertTriangle, ArrowRight } from 'lucide-react';
function ConsentBanner() {
const { user, isCustomerPortal } = useAuth();
const { data } = useQuery({
queryKey: ['my-consent-status'],
queryFn: () => gdprApi.getMyConsentStatus(),
enabled: isCustomerPortal && !!user?.customerId,
staleTime: 30_000,
});
if (!isCustomerPortal || !data?.data) return null;
if (data.data.hasConsent) return null;
return (
<div className="bg-amber-50 border-b border-amber-200 px-6 py-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<AlertTriangle className="w-5 h-5 text-amber-600 flex-shrink-0" />
<p className="text-sm text-amber-800">
Bitte stimmen Sie unserer Datenschutzerklärung zu, damit wir Sie beraten können.
</p>
</div>
<Link
to="/privacy"
className="flex items-center gap-1 text-sm font-medium text-amber-700 hover:text-amber-900 whitespace-nowrap"
>
Jetzt zustimmen
<ArrowRight className="w-4 h-4" />
</Link>
</div>
</div>
);
}
export default function Layout() {
return (
<div className="flex min-h-screen">
<Sidebar />
<main className="flex-1 p-8 overflow-auto">
<Outlet />
</main>
<div className="flex-1 flex flex-col overflow-auto">
<ConsentBanner />
<main className="flex-1 p-8">
<Outlet />
</main>
</div>
<ScrollToTopButton />
</div>
);
+21 -1
View File
@@ -1,5 +1,7 @@
import { NavLink } from 'react-router-dom';
import { useQuery } from '@tanstack/react-query';
import { useAuth } from '../../context/AuthContext';
import { gdprApi } from '../../services/api';
import {
LayoutDashboard,
Users,
@@ -11,17 +13,35 @@ import {
ClipboardList,
MessageSquare,
AlertCircle,
Shield,
FileCheck,
UserCircle,
Gauge,
} from 'lucide-react';
export default function Sidebar() {
const { user, logout, hasPermission, isCustomer, developerMode } = useAuth();
const { user, logout, hasPermission, isCustomer, isCustomerPortal, developerMode } = useAuth();
// Prüfe ob Vollmachten vorhanden sind (nur für Portal-Kunden)
const { data: authData } = useQuery({
queryKey: ['my-authorizations'],
queryFn: () => gdprApi.getMyAuthorizations(),
enabled: isCustomerPortal,
staleTime: 60_000,
});
const hasAuthorizations = (authData?.data?.length ?? 0) > 0;
const navItems = [
{ to: '/', icon: LayoutDashboard, label: 'Dashboard', show: true, end: true },
{ to: '/my-profile', icon: UserCircle, label: 'Meine Daten', show: isCustomerPortal },
{ to: '/customers', icon: Users, label: 'Kunden', show: hasPermission('customers:read') && !isCustomer },
{ to: '/contracts', icon: FileText, label: 'Verträge', show: hasPermission('contracts:read'), end: true },
{ to: '/contracts/cockpit', icon: AlertCircle, label: 'Vertrags-Cockpit', show: hasPermission('contracts:read') && !isCustomer },
{ to: '/tasks', icon: isCustomer ? MessageSquare : ClipboardList, label: isCustomer ? 'Support-Anfragen' : 'Aufgaben', show: hasPermission('contracts:read') },
{ to: '/my-meters', icon: Gauge, label: 'Zählerstände', show: isCustomerPortal },
{ to: '/privacy', icon: Shield, label: 'Datenschutz', show: isCustomerPortal },
{ to: '/authorizations', icon: FileCheck, label: 'Vollmachten', show: isCustomerPortal && hasAuthorizations },
];
const developerItems = [
+18 -4
View File
@@ -1,4 +1,4 @@
import { ReactNode, useState } from 'react';
import { ReactNode, useState, useEffect } from 'react';
interface Tab {
id: string;
@@ -9,10 +9,24 @@ interface Tab {
interface TabsProps {
tabs: Tab[];
defaultTab?: string;
activeTab?: string;
onTabChange?: (tabId: string) => void;
}
export default function Tabs({ tabs, defaultTab }: TabsProps) {
const [activeTab, setActiveTab] = useState(defaultTab || tabs[0]?.id);
export default function Tabs({ tabs, defaultTab, activeTab: controlledTab, onTabChange }: TabsProps) {
const [internalTab, setInternalTab] = useState(defaultTab || tabs[0]?.id);
const activeTab = controlledTab ?? internalTab;
useEffect(() => {
if (controlledTab !== undefined) {
setInternalTab(controlledTab);
}
}, [controlledTab]);
const handleTabChange = (tabId: string) => {
setInternalTab(tabId);
onTabChange?.(tabId);
};
return (
<div>
@@ -21,7 +35,7 @@ export default function Tabs({ tabs, defaultTab }: TabsProps) {
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
onClick={() => handleTabChange(tab.id)}
className={`py-4 px-1 border-b-2 font-medium text-sm whitespace-nowrap ${
activeTab === tab.id
? 'border-blue-500 text-blue-600'