Files
opencrm/frontend/src/components/BirthdayModal.tsx
T
duffyduck 2a3928d0e7 Anrede-Verhältnis Du/Sie pro Kunde + Geburtstagsgruß respektiert Anrede
Schema:
- Customer.useInformalAddress: Boolean (Default: false = Sie)
- Auch bei Firmenkunden verfügbar (Chef kann man auch duzen)

Frontend:
- Neues Pflichtfeld "Anrede per" (Du/Sie) im Kunden-Formular
- Anzeige als Badge in CustomerDetail-Stammdaten

Geburtstagsgruß im Portal:
- Bei Du: "Herzlichen Glückwunsch, Max! Alles Gute zu deinem 42. Geburtstag!"
- Bei Sie: "Herzlichen Glückwunsch, Herr Müller! Alles Gute zu Ihrem 42. Geburtstag!"
- Konsistent auch bei nachträglichen Glückwünschen (hattest/hatten, bist/sind etc.)
- Backend liefert firstName, lastName, salutation und useInformalAddress

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 12:27:23 +02:00

95 lines
3.7 KiB
TypeScript

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useAuth } from '../context/AuthContext';
import { birthdayApi } from '../services/api';
import { Cake, X } from 'lucide-react';
import Button from './ui/Button';
export default function BirthdayModal() {
const { isCustomerPortal } = useAuth();
const queryClient = useQueryClient();
const { data } = useQuery({
queryKey: ['my-birthday'],
queryFn: () => birthdayApi.getMyBirthday(),
enabled: isCustomerPortal,
staleTime: 60 * 60_000, // 1h (reicht völlig)
});
const ackMutation = useMutation({
mutationFn: () => birthdayApi.acknowledgeMyBirthday(),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['my-birthday'] });
},
});
const info = data?.data;
if (!info?.show) return null;
const handleClose = () => ackMutation.mutate();
// Anrede abhängig vom Du/Sie-Verhältnis zusammenstellen
const greetingName = info.useInformalAddress
? info.firstName
: [info.salutation, info.lastName].filter(Boolean).join(' ') || info.firstName;
const greetingPronoun = info.useInformalAddress ? 'dir' : 'Ihnen';
const greetingPossessive = info.useInformalAddress ? 'deinem' : 'Ihrem';
const greetingYour = info.useInformalAddress ? 'Dein' : 'Ihr';
const hadBirthday = info.useInformalAddress ? 'hattest' : 'hatten';
const becameOld = info.useInformalAddress ? 'bist' : 'sind';
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 px-4">
<div className="bg-white rounded-2xl shadow-2xl max-w-md w-full overflow-hidden">
{/* Konfetti-Header */}
<div className="bg-gradient-to-br from-pink-500 via-purple-500 to-indigo-500 p-6 text-center relative">
<button
onClick={handleClose}
className="absolute top-3 right-3 text-white/80 hover:text-white transition-colors"
aria-label="Schließen"
>
<X className="w-5 h-5" />
</button>
<div className="inline-flex items-center justify-center w-20 h-20 rounded-full bg-white/20 backdrop-blur mb-3">
<Cake className="w-10 h-10 text-white" />
</div>
<div className="text-5xl mb-1">🎉🎂🎈</div>
</div>
{/* Nachricht */}
<div className="p-6 text-center">
{info.isToday ? (
<>
<h2 className="text-2xl font-bold text-gray-900 mb-2">
Herzlichen Glückwunsch, {greetingName}!
</h2>
<p className="text-gray-600 mb-1">
Alles Gute zu {greetingPossessive} {info.age}. Geburtstag!
</p>
<p className="text-gray-500 text-sm mb-6">
Wir wünschen {greetingPronoun} einen wunderschönen Tag und alles Gute für {greetingYour.toLowerCase()} neues Lebensjahr. 🌟
</p>
</>
) : (
<>
<h2 className="text-2xl font-bold text-gray-900 mb-2">
Nachträglich alles Gute, {greetingName}!
</h2>
<p className="text-gray-600 mb-1">
{greetingYour === 'Ihr' ? 'Sie' : 'Du'} {hadBirthday} vor {info.daysAgo} Tag{info.daysAgo === 1 ? '' : 'en'} Geburtstag
{info.age > 0 && ` und ${becameOld} ${info.age} Jahre alt geworden`}.
</p>
<p className="text-gray-500 text-sm mb-6">
Wir wünschen {greetingPronoun} alles Gute nachträglich und eine tolle Zeit im neuen Lebensjahr. 🌟
</p>
</>
)}
<Button onClick={handleClose} disabled={ackMutation.isPending} className="w-full">
Vielen Dank!
</Button>
</div>
</div>
</div>
);
}