Files
Atlas/app/dashboard/kalender/page.tsx
2025-11-12 20:21:32 +01:00

411 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useState, useEffect, useCallback } from 'react';
import { Calendar, dateFnsLocalizer, View } from 'react-big-calendar';
import { format, parse, startOfWeek, getDay, addMonths, subMonths } from 'date-fns';
import { de } from 'date-fns/locale';
import 'react-big-calendar/lib/css/react-big-calendar.css';
import DashboardSidebar from '@/components/DashboardSidebar';
import { useSession } from 'next-auth/react';
import { FiChevronLeft, FiChevronRight, FiCalendar } from 'react-icons/fi';
const locales = {
de: de,
};
const localizer = dateFnsLocalizer({
format,
parse,
startOfWeek: () => startOfWeek(new Date(), { locale: de }),
getDay,
locales,
});
interface CalendarEvent {
id: string;
title: string;
start: Date;
end: Date;
resource: {
bookingId: string;
status: string;
customerName: string;
customerEmail: string;
locationName: string;
photoboxName: string;
tourId?: string;
eventType: string;
};
}
export default function KalenderPage() {
const { data: session } = useSession();
const [events, setEvents] = useState<CalendarEvent[]>([]);
const [loading, setLoading] = useState(true);
const [selectedEvent, setSelectedEvent] = useState<CalendarEvent | null>(null);
const [view, setView] = useState<View>('month');
const [date, setDate] = useState(new Date());
const fetchEvents = useCallback(async (start?: Date, end?: Date) => {
setLoading(true);
try {
const params = new URLSearchParams();
if (start) params.append('start', start.toISOString());
if (end) params.append('end', end.toISOString());
const response = await fetch(`/api/calendar?${params.toString()}`);
const data = await response.json();
const parsedEvents = data.events.map((event: any) => ({
...event,
start: new Date(event.start),
end: new Date(event.end),
}));
setEvents(parsedEvents);
} catch (error) {
console.error('Error fetching calendar events:', error);
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
fetchEvents();
}, [fetchEvents]);
const handleNavigate = (newDate: Date) => {
setDate(newDate);
};
const handleViewChange = (newView: View) => {
setView(newView);
};
const eventStyleGetter = (event: CalendarEvent) => {
let backgroundColor = '#6b7280';
switch (event.resource.status) {
case 'PENDING':
backgroundColor = '#f59e0b';
break;
case 'RESERVED':
backgroundColor = '#3b82f6';
break;
case 'CONFIRMED':
backgroundColor = '#10b981';
break;
case 'TOUR_CREATED':
backgroundColor = '#8b5cf6';
break;
case 'COMPLETED':
backgroundColor = '#6b7280';
break;
case 'CANCELLED':
backgroundColor = '#ef4444';
break;
}
return {
style: {
backgroundColor,
borderRadius: '4px',
opacity: 0.9,
color: 'white',
border: '0px',
display: 'block',
},
};
};
const CustomToolbar = ({ label, onNavigate }: any) => {
return (
<div className="flex items-center justify-between mb-6 bg-gray-800 p-4 rounded-lg">
<div className="flex items-center gap-2">
<FiCalendar className="text-2xl text-red-400" />
<h2 className="text-2xl font-bold text-white">{label}</h2>
</div>
<div className="flex items-center gap-2">
<button
onClick={() => onNavigate('PREV')}
className="p-2 bg-gray-700 hover:bg-gray-600 text-white rounded-lg transition-colors"
>
<FiChevronLeft />
</button>
<button
onClick={() => onNavigate('TODAY')}
className="px-4 py-2 bg-red-600 hover:bg-red-500 text-white rounded-lg font-medium transition-colors"
>
Heute
</button>
<button
onClick={() => onNavigate('NEXT')}
className="p-2 bg-gray-700 hover:bg-gray-600 text-white rounded-lg transition-colors"
>
<FiChevronRight />
</button>
</div>
<div className="flex gap-2">
<button
onClick={() => setView('month')}
className={`px-4 py-2 rounded-lg font-medium transition-colors ${
view === 'month'
? 'bg-red-600 text-white'
: 'bg-gray-700 text-gray-300 hover:bg-gray-600'
}`}
>
Monat
</button>
<button
onClick={() => setView('week')}
className={`px-4 py-2 rounded-lg font-medium transition-colors ${
view === 'week'
? 'bg-red-600 text-white'
: 'bg-gray-700 text-gray-300 hover:bg-gray-600'
}`}
>
Woche
</button>
<button
onClick={() => setView('day')}
className={`px-4 py-2 rounded-lg font-medium transition-colors ${
view === 'day'
? 'bg-red-600 text-white'
: 'bg-gray-700 text-gray-300 hover:bg-gray-600'
}`}
>
Tag
</button>
</div>
</div>
);
};
return (
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-gray-800 to-gray-900">
<div className="flex">
<DashboardSidebar user={session?.user} />
<main className="flex-1 p-8">
<div className="bg-gray-800/50 rounded-lg p-6 shadow-lg">
{loading ? (
<div className="flex items-center justify-center h-96">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-red-500"></div>
</div>
) : (
<>
<div style={{ height: '700px' }} className="calendar-container">
<Calendar
localizer={localizer}
events={events}
startAccessor="start"
endAccessor="end"
style={{ height: '100%' }}
onSelectEvent={(event) => setSelectedEvent(event)}
eventPropGetter={eventStyleGetter}
view={view}
onView={handleViewChange}
date={date}
onNavigate={handleNavigate}
components={{
toolbar: CustomToolbar,
}}
messages={{
next: 'Weiter',
previous: 'Zurück',
today: 'Heute',
month: 'Monat',
week: 'Woche',
day: 'Tag',
agenda: 'Agenda',
date: 'Datum',
time: 'Zeit',
event: 'Event',
noEventsInRange: 'Keine Events in diesem Zeitraum',
showMore: (total) => `+ ${total} mehr`,
}}
/>
</div>
<div className="mt-6 flex gap-4 flex-wrap">
<div className="flex items-center gap-2">
<div className="w-4 h-4 rounded bg-amber-500"></div>
<span className="text-sm text-gray-300">Pending</span>
</div>
<div className="flex items-center gap-2">
<div className="w-4 h-4 rounded bg-blue-500"></div>
<span className="text-sm text-gray-300">Reserviert</span>
</div>
<div className="flex items-center gap-2">
<div className="w-4 h-4 rounded bg-green-500"></div>
<span className="text-sm text-gray-300">Bestätigt</span>
</div>
<div className="flex items-center gap-2">
<div className="w-4 h-4 rounded bg-purple-500"></div>
<span className="text-sm text-gray-300">Tour erstellt</span>
</div>
<div className="flex items-center gap-2">
<div className="w-4 h-4 rounded bg-gray-500"></div>
<span className="text-sm text-gray-300">Abgeschlossen</span>
</div>
<div className="flex items-center gap-2">
<div className="w-4 h-4 rounded bg-red-500"></div>
<span className="text-sm text-gray-300">Storniert</span>
</div>
</div>
</>
)}
</div>
{selectedEvent && (
<div
className="fixed inset-0 bg-black/50 flex items-center justify-center z-50"
onClick={() => setSelectedEvent(null)}
>
<div
className="bg-gray-800 rounded-lg p-6 max-w-lg w-full mx-4 shadow-2xl"
onClick={(e) => e.stopPropagation()}
>
<div className="flex items-center justify-between mb-4">
<h3 className="text-xl font-bold text-white">Buchungsdetails</h3>
<button
onClick={() => setSelectedEvent(null)}
className="text-gray-400 hover:text-white text-2xl"
>
×
</button>
</div>
<div className="space-y-3 text-gray-300">
<div>
<span className="font-semibold text-white">Kunde:</span>{' '}
{selectedEvent.resource.customerName}
</div>
<div>
<span className="font-semibold text-white">E-Mail:</span>{' '}
{selectedEvent.resource.customerEmail}
</div>
<div>
<span className="font-semibold text-white">Standort:</span>{' '}
{selectedEvent.resource.locationName}
</div>
<div>
<span className="font-semibold text-white">Fotobox:</span>{' '}
{selectedEvent.resource.photoboxName}
</div>
<div>
<span className="font-semibold text-white">Event-Typ:</span>{' '}
{selectedEvent.resource.eventType}
</div>
<div>
<span className="font-semibold text-white">Status:</span>{' '}
<span
className={`px-2 py-1 rounded text-sm ${
selectedEvent.resource.status === 'PENDING'
? 'bg-amber-500/20 text-amber-300'
: selectedEvent.resource.status === 'RESERVED'
? 'bg-blue-500/20 text-blue-300'
: selectedEvent.resource.status === 'CONFIRMED'
? 'bg-green-500/20 text-green-300'
: selectedEvent.resource.status === 'TOUR_CREATED'
? 'bg-purple-500/20 text-purple-300'
: selectedEvent.resource.status === 'COMPLETED'
? 'bg-gray-500/20 text-gray-300'
: 'bg-red-500/20 text-red-300'
}`}
>
{selectedEvent.resource.status}
</span>
</div>
<div>
<span className="font-semibold text-white">Datum:</span>{' '}
{format(selectedEvent.start, 'PPP', { locale: de })}
</div>
<div>
<span className="font-semibold text-white">Zeit:</span>{' '}
{format(selectedEvent.start, 'HH:mm', { locale: de })} -{' '}
{format(selectedEvent.end, 'HH:mm', { locale: de })}
</div>
</div>
<div className="mt-6 flex gap-3">
<a
href={`/dashboard/bookings?id=${selectedEvent.id}`}
className="flex-1 px-4 py-2 bg-red-600 hover:bg-red-500 text-white rounded-lg text-center font-medium transition-colors"
>
Buchung öffnen
</a>
{selectedEvent.resource.tourId && (
<a
href={`/dashboard/tours?id=${selectedEvent.resource.tourId}`}
className="flex-1 px-4 py-2 bg-purple-600 hover:bg-purple-500 text-white rounded-lg text-center font-medium transition-colors"
>
Tour anzeigen
</a>
)}
</div>
</div>
</div>
)}
</main>
</div>
<style jsx global>{`
.calendar-container .rbc-calendar {
background: transparent;
color: white;
}
.rbc-header {
padding: 12px 4px;
font-weight: 600;
color: white;
background: rgba(31, 41, 55, 0.5);
border-color: rgba(75, 85, 99, 0.5) !important;
}
.rbc-month-view,
.rbc-time-view {
background: rgba(31, 41, 55, 0.3);
border-color: rgba(75, 85, 99, 0.5) !important;
}
.rbc-day-bg,
.rbc-time-slot {
border-color: rgba(75, 85, 99, 0.5) !important;
}
.rbc-today {
background-color: rgba(239, 68, 68, 0.1) !important;
}
.rbc-off-range-bg {
background: rgba(17, 24, 39, 0.5) !important;
}
.rbc-date-cell {
padding: 6px;
color: #d1d5db;
}
.rbc-now .rbc-button-link {
color: #ef4444;
font-weight: 700;
}
.rbc-event {
padding: 2px 5px;
font-size: 0.875rem;
cursor: pointer;
}
.rbc-event:hover {
opacity: 1 !important;
}
.rbc-time-content {
border-color: rgba(75, 85, 99, 0.5) !important;
}
.rbc-time-header-content {
border-color: rgba(75, 85, 99, 0.5) !important;
}
.rbc-timeslot-group {
border-color: rgba(75, 85, 99, 0.5) !important;
}
.rbc-current-time-indicator {
background-color: #ef4444;
}
`}</style>
</div>
);
}