Dashboard: anstehende Geburtstage anzeigen (wie im Cockpit)

Die Geburtstags-Sektion gab es bisher nur im Vertrags-Cockpit.
Jetzt zusätzlich auf dem Dashboard für Mitarbeiter/Admins – gleiche
birthdayApi.getUpcoming(7, 30)-Query und identische Karten-
Darstellung (Heute/vergangen/kommend, Link zur Kundenakte). Kein
Backend-Change.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 11:33:58 +02:00
co-authored by Claude Opus 4.7
parent e775b0e439
commit bc5d639703
2 changed files with 74 additions and 1 deletions
+7
View File
@@ -97,6 +97,13 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
## ✅ Erledigt
- [x] **🎂 Anstehende Geburtstage auch im Dashboard**
- Die Geburtstags-Sektion gab es bisher nur im Vertrags-Cockpit.
Jetzt 1:1 auch auf dem Dashboard (nur Mitarbeiter/Admin,
`enabled: !isCustomer`), gleiche `birthdayApi.getUpcoming(7, 30)`-
Query + identische Karten-Darstellung (Heute/vergangen/kommend,
Link auf die Kundenakte). Kein Backend-Change nötig.
- [x] **🔴 Pentest R121 Audit-Verify: Fehlalarm „manipuliert" bei leerem resourceId**
- `POST /api/audit-logs/verify` meldete ~73% der Einträge als
manipuliert. Kein echtes Tampering, sondern ein Bug in
+67 -1
View File
@@ -2,11 +2,14 @@ import { useState, useMemo } from 'react';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { Link, useNavigate } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';
import { customerApi, contractApi, contractTaskApi, appSettingsApi } from '../services/api';
import { customerApi, contractApi, contractTaskApi, appSettingsApi, birthdayApi } from '../services/api';
import { buildContractLabelParts } from '../utils/contractLabel';
import { pushHistory } from '../utils/navigation';
import { formatDate } from '../utils/dateFormat';
import Card from '../components/ui/Card';
import Button from '../components/ui/Button';
import Input from '../components/ui/Input';
import Badge from '../components/ui/Badge';
import Modal from '../components/ui/Modal';
import {
Users,
@@ -20,6 +23,7 @@ import {
Plus,
Clock,
XCircle,
Cake,
} from 'lucide-react';
import type { Contract } from '../types';
@@ -79,6 +83,14 @@ export default function Dashboard() {
staleTime: 0,
});
// Anstehende Geburtstage (kommende 30 Tage + vergangene 7) wie im Cockpit,
// nur für Mitarbeiter/Admins.
const { data: birthdaysData } = useQuery({
queryKey: ['upcoming-birthdays'],
queryFn: () => birthdayApi.getUpcoming(7, 30),
enabled: !isCustomer,
});
// Für Kundenportal: Verträge nach eigene/fremd gruppieren
const { ownContracts, representedContracts } = useMemo(() => {
if (!isCustomerPortal || !contractsData?.data) {
@@ -378,6 +390,60 @@ export default function Dashboard() {
</div>
)}
{/* Anstehende Geburtstage (wie im Cockpit) */}
{birthdaysData?.data && birthdaysData.data.length > 0 && (
<Card className="mb-6">
<div className="flex items-center gap-2 mb-3">
<Cake className="w-5 h-5 text-pink-500" />
<h2 className="text-lg font-semibold">Geburtstage</h2>
<Badge variant="default">{birthdaysData.data.length}</Badge>
</div>
<p className="text-sm text-gray-500 mb-3">
Kunden mit Geburtstag in den nächsten 30 Tagen oder den letzten 7 Tagen.
</p>
<div className="space-y-2">
{birthdaysData.data.map((b) => (
<div
key={b.customerId}
className={`flex items-center justify-between p-3 rounded-lg border ${
b.isToday
? 'bg-pink-50 border-pink-300'
: b.isPast
? 'bg-amber-50 border-amber-200'
: 'bg-gray-50 border-gray-200'
}`}
>
<div className="flex items-center gap-3">
<Cake className={`w-4 h-4 ${b.isToday ? 'text-pink-500' : b.isPast ? 'text-amber-500' : 'text-gray-400'}`} />
<div>
<Link
to={`/customers/${b.customerId}`}
state={pushHistory('/')}
className="text-sm font-medium hover:underline"
>
{b.name}
</Link>
<span className="text-xs text-gray-500 ml-2">({b.customerNumber})</span>
<p className="text-xs text-gray-600">
{formatDate(b.birthDate)} wird {b.age} Jahre
</p>
</div>
</div>
<div className="text-right">
{b.isToday ? (
<Badge variant="warning">🎉 Heute!</Badge>
) : b.isPast ? (
<Badge variant="default">Vor {Math.abs(b.daysUntil)} Tag{Math.abs(b.daysUntil) === 1 ? '' : 'en'}</Badge>
) : (
<Badge variant="default">In {b.daysUntil} Tag{b.daysUntil === 1 ? '' : 'en'}</Badge>
)}
</div>
</div>
))}
</div>
</Card>
)}
{/* Aufgaben Stats für Mitarbeiter */}
<div className="mb-6">
<div className="flex items-center gap-2 mb-3">