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
|
## ✅ 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**
|
- [x] **🔴 Pentest R121 – Audit-Verify: Fehlalarm „manipuliert" bei leerem resourceId**
|
||||||
- `POST /api/audit-logs/verify` meldete ~73% der Einträge als
|
- `POST /api/audit-logs/verify` meldete ~73% der Einträge als
|
||||||
manipuliert. Kein echtes Tampering, sondern ein Bug in
|
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 { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
import { useAuth } from '../context/AuthContext';
|
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 { buildContractLabelParts } from '../utils/contractLabel';
|
||||||
|
import { pushHistory } from '../utils/navigation';
|
||||||
|
import { formatDate } from '../utils/dateFormat';
|
||||||
import Card from '../components/ui/Card';
|
import Card from '../components/ui/Card';
|
||||||
import Button from '../components/ui/Button';
|
import Button from '../components/ui/Button';
|
||||||
import Input from '../components/ui/Input';
|
import Input from '../components/ui/Input';
|
||||||
|
import Badge from '../components/ui/Badge';
|
||||||
import Modal from '../components/ui/Modal';
|
import Modal from '../components/ui/Modal';
|
||||||
import {
|
import {
|
||||||
Users,
|
Users,
|
||||||
@@ -20,6 +23,7 @@ import {
|
|||||||
Plus,
|
Plus,
|
||||||
Clock,
|
Clock,
|
||||||
XCircle,
|
XCircle,
|
||||||
|
Cake,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import type { Contract } from '../types';
|
import type { Contract } from '../types';
|
||||||
|
|
||||||
@@ -79,6 +83,14 @@ export default function Dashboard() {
|
|||||||
staleTime: 0,
|
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
|
// Für Kundenportal: Verträge nach eigene/fremd gruppieren
|
||||||
const { ownContracts, representedContracts } = useMemo(() => {
|
const { ownContracts, representedContracts } = useMemo(() => {
|
||||||
if (!isCustomerPortal || !contractsData?.data) {
|
if (!isCustomerPortal || !contractsData?.data) {
|
||||||
@@ -378,6 +390,60 @@ export default function Dashboard() {
|
|||||||
</div>
|
</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 */}
|
{/* Aufgaben Stats für Mitarbeiter */}
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<div className="flex items-center gap-2 mb-3">
|
<div className="flex items-center gap-2 mb-3">
|
||||||
|
|||||||
Reference in New Issue
Block a user