feat: CalDAV-Integration für Admin-Kalender
- Neue CalDAV-Route mit PROPFIND und GET-Endpoints - ICS-Format-Generator für Buchungsdaten - Token-basierte Authentifizierung für CalDAV-Zugriff - Admin-Interface mit CalDAV-Link-Generator - Schritt-für-Schritt-Anleitung für Kalender-Apps - 24h-Token-Ablaufzeit für Sicherheit - Unterstützung für Outlook, Google Calendar, Apple Calendar, Thunderbird Fixes: Admin kann jetzt Terminkalender in externen Apps abonnieren
This commit is contained in:
@@ -9,6 +9,10 @@ export function AdminCalendar() {
|
||||
const [sendDeleteEmail, setSendDeleteEmail] = useState(false);
|
||||
const [deleteActionType, setDeleteActionType] = useState<'delete' | 'cancel'>('delete');
|
||||
|
||||
// CalDAV state
|
||||
const [caldavData, setCaldavData] = useState<any>(null);
|
||||
const [showCaldavInstructions, setShowCaldavInstructions] = useState(false);
|
||||
|
||||
// Manual booking modal state
|
||||
const [showCreateModal, setShowCreateModal] = useState(false);
|
||||
const [createFormData, setCreateFormData] = useState({
|
||||
@@ -77,6 +81,11 @@ export function AdminCalendar() {
|
||||
queryClient.bookings.proposeReschedule.mutationOptions()
|
||||
);
|
||||
|
||||
// CalDAV token generation mutation
|
||||
const { mutate: generateCalDAVToken, isPending: isGeneratingToken } = useMutation(
|
||||
queryClient.bookings.generateCalDAVToken.mutationOptions()
|
||||
);
|
||||
|
||||
const getTreatmentName = (treatmentId: string) => {
|
||||
return treatments?.find(t => t.id === treatmentId)?.name || "Unbekannte Behandlung";
|
||||
};
|
||||
@@ -275,6 +284,31 @@ export function AdminCalendar() {
|
||||
});
|
||||
};
|
||||
|
||||
const handleGenerateCalDAVToken = () => {
|
||||
const sessionId = localStorage.getItem('sessionId');
|
||||
if (!sessionId) return;
|
||||
|
||||
generateCalDAVToken({
|
||||
sessionId
|
||||
}, {
|
||||
onSuccess: (data) => {
|
||||
setCaldavData(data);
|
||||
setShowCaldavInstructions(true);
|
||||
},
|
||||
onError: (error: any) => {
|
||||
console.error('CalDAV Token Generation Error:', error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const copyToClipboard = (text: string) => {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
// Optional: Show success message
|
||||
}).catch(err => {
|
||||
console.error('Failed to copy text: ', err);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-6">Kalender - Bevorstehende Buchungen</h2>
|
||||
@@ -307,6 +341,62 @@ export function AdminCalendar() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* CalDAV Integration */}
|
||||
<div className="bg-white rounded-lg shadow p-6 mb-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900">Kalender-Abonnement</h3>
|
||||
<p className="text-sm text-gray-600">Abonniere deinen Terminkalender in deiner Kalender-App</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleGenerateCalDAVToken}
|
||||
disabled={isGeneratingToken}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors text-sm font-medium"
|
||||
>
|
||||
{isGeneratingToken ? 'Generiere...' : 'CalDAV-Link erstellen'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{caldavData && (
|
||||
<div className="border-t pt-4">
|
||||
<div className="bg-gray-50 rounded-lg p-4 mb-4">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label className="text-sm font-medium text-gray-700">CalDAV-URL:</label>
|
||||
<button
|
||||
onClick={() => copyToClipboard(caldavData.caldavUrl)}
|
||||
className="text-blue-600 hover:text-blue-800 text-sm"
|
||||
>
|
||||
Kopieren
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
value={caldavData.caldavUrl}
|
||||
readOnly
|
||||
className="w-full p-2 bg-white border border-gray-300 rounded text-sm font-mono"
|
||||
/>
|
||||
<div className="text-xs text-gray-500 mt-2">
|
||||
Gültig bis: {new Date(caldavData.expiresAt).toLocaleString('de-DE')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-sm text-gray-600">
|
||||
<p className="mb-2">
|
||||
<strong>So abonnierst du den Kalender:</strong>
|
||||
</p>
|
||||
<ul className="list-disc list-inside space-y-1 text-sm">
|
||||
{caldavData.instructions.steps.map((step: string, index: number) => (
|
||||
<li key={index}>{step}</li>
|
||||
))}
|
||||
</ul>
|
||||
<p className="mt-3 text-amber-700 bg-amber-50 p-2 rounded">
|
||||
<strong>Hinweis:</strong> {caldavData.instructions.note}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Calendar */}
|
||||
<div className="bg-white rounded-lg shadow-lg overflow-hidden">
|
||||
{/* Calendar Header */}
|
||||
|
Reference in New Issue
Block a user