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