280 lines
10 KiB
TypeScript
280 lines
10 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import { useRouter } from 'next/navigation';
|
|
import { formatDate } from '@/lib/date-utils';
|
|
import DashboardSidebar from '@/components/DashboardSidebar';
|
|
import { useSession } from 'next-auth/react';
|
|
|
|
export default function TourDetailPage({ params }: { params: { id: string } }) {
|
|
const router = useRouter();
|
|
const { data: session } = useSession();
|
|
const [tour, setTour] = useState<any>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [optimizing, setOptimizing] = useState(false);
|
|
|
|
useEffect(() => {
|
|
fetchTour();
|
|
}, [params.id]);
|
|
|
|
const fetchTour = async () => {
|
|
try {
|
|
const res = await fetch(`/api/tours/${params.id}`);
|
|
const data = await res.json();
|
|
setTour(data.tour);
|
|
} catch (error) {
|
|
console.error('Fetch error:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const updateStatus = async (newStatus: string) => {
|
|
try {
|
|
const res = await fetch(`/api/tours/${params.id}`, {
|
|
method: 'PATCH',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ status: newStatus }),
|
|
});
|
|
|
|
if (res.ok) {
|
|
fetchTour();
|
|
} else {
|
|
alert('Fehler beim Aktualisieren');
|
|
}
|
|
} catch (error) {
|
|
console.error('Update error:', error);
|
|
alert('Fehler beim Aktualisieren');
|
|
}
|
|
};
|
|
|
|
const handleDelete = async () => {
|
|
if (!confirm('Möchten Sie diese Tour wirklich löschen?')) return;
|
|
|
|
try {
|
|
const res = await fetch(`/api/tours/${params.id}`, {
|
|
method: 'DELETE',
|
|
});
|
|
|
|
if (res.ok) {
|
|
router.push('/dashboard/tours');
|
|
} else {
|
|
alert('Fehler beim Löschen');
|
|
}
|
|
} catch (error) {
|
|
console.error('Delete error:', error);
|
|
alert('Fehler beim Löschen');
|
|
}
|
|
};
|
|
|
|
const optimizeRoute = async () => {
|
|
if (!tour || tour.bookings.length === 0) {
|
|
alert('Keine Buchungen zum Optimieren vorhanden');
|
|
return;
|
|
}
|
|
|
|
setOptimizing(true);
|
|
try {
|
|
const res = await fetch(`/api/tours/${params.id}/optimize-route`, {
|
|
method: 'POST',
|
|
});
|
|
|
|
const data = await res.json();
|
|
|
|
if (res.ok) {
|
|
alert(`Route optimiert!\nGesamtstrecke: ${data.route.totalDistance.toFixed(1)} km\nGeschätzte Dauer: ${data.route.totalDuration} Min`);
|
|
fetchTour();
|
|
} else {
|
|
alert(data.error || 'Fehler bei der Routenoptimierung');
|
|
}
|
|
} catch (error) {
|
|
console.error('Optimization error:', error);
|
|
alert('Fehler bei der Routenoptimierung');
|
|
} finally {
|
|
setOptimizing(false);
|
|
}
|
|
};
|
|
|
|
if (loading) {
|
|
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} />
|
|
<div className="flex-1 p-8">
|
|
<p className="text-gray-300">Lädt...</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!tour) {
|
|
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} />
|
|
<div className="flex-1 p-8">
|
|
<p className="text-gray-300">Tour nicht gefunden</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const getStatusBadge = (status: string) => {
|
|
const styles: Record<string, string> = {
|
|
PLANNED: 'bg-blue-500/20 text-blue-400 border-blue-500/50',
|
|
IN_PROGRESS: 'bg-yellow-500/20 text-yellow-400 border-yellow-500/50',
|
|
COMPLETED: 'bg-green-500/20 text-green-400 border-green-500/50',
|
|
CANCELLED: 'bg-red-500/20 text-red-400 border-red-500/50',
|
|
};
|
|
return styles[status] || 'bg-gray-500/20 text-gray-400 border-gray-500/50';
|
|
};
|
|
|
|
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="max-w-6xl mx-auto">
|
|
<button
|
|
onClick={() => router.back()}
|
|
className="mb-6 text-blue-400 hover:text-blue-300 font-medium"
|
|
>
|
|
← Zurück
|
|
</button>
|
|
|
|
<div className="bg-gradient-to-br from-gray-800 to-gray-900 rounded-xl shadow-2xl p-8 mb-6 border border-gray-700">
|
|
<div className="flex justify-between items-start mb-6">
|
|
<div>
|
|
<h1 className="text-3xl font-bold text-white">{tour.tourNumber}</h1>
|
|
<p className="text-gray-400 mt-1">{formatDate(tour.tourDate)}</p>
|
|
</div>
|
|
<div className="flex gap-3 items-center">
|
|
<span className={`px-4 py-2 text-sm font-semibold rounded-full border ${getStatusBadge(tour.status)}`}>
|
|
{tour.status}
|
|
</span>
|
|
<button
|
|
onClick={handleDelete}
|
|
className="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 font-semibold"
|
|
>
|
|
Löschen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-6 mb-6">
|
|
<div>
|
|
<p className="text-sm text-gray-400">Fahrer</p>
|
|
<p className="text-lg font-semibold text-white">
|
|
{tour.driver ? tour.driver.name : 'Nicht zugewiesen'}
|
|
</p>
|
|
{tour.driver?.phoneNumber && (
|
|
<p className="text-sm text-gray-400">{tour.driver.phoneNumber}</p>
|
|
)}
|
|
</div>
|
|
<div>
|
|
<p className="text-sm text-gray-400">Buchungen</p>
|
|
<p className="text-lg font-semibold text-white">{tour.bookings.length}</p>
|
|
</div>
|
|
{tour.totalDistance && (
|
|
<div>
|
|
<p className="text-sm text-gray-400">Gesamtstrecke</p>
|
|
<p className="text-lg font-semibold text-white">{tour.totalDistance} km</p>
|
|
</div>
|
|
)}
|
|
{tour.estimatedDuration && (
|
|
<div>
|
|
<p className="text-sm text-gray-400">Geschätzte Dauer</p>
|
|
<p className="text-lg font-semibold text-white">~{tour.estimatedDuration} Min</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex gap-3">
|
|
{tour.bookings.length > 0 && (
|
|
<button
|
|
onClick={optimizeRoute}
|
|
disabled={optimizing}
|
|
className="px-6 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 font-semibold disabled:opacity-50 disabled:cursor-not-allowed"
|
|
>
|
|
{optimizing ? 'Optimiere Route...' : '🗺️ Route optimieren'}
|
|
</button>
|
|
)}
|
|
{tour.status === 'PLANNED' && (
|
|
<button
|
|
onClick={() => updateStatus('IN_PROGRESS')}
|
|
className="px-6 py-2 bg-yellow-600 text-white rounded-lg hover:bg-yellow-700 font-semibold"
|
|
>
|
|
Tour starten
|
|
</button>
|
|
)}
|
|
{tour.status === 'IN_PROGRESS' && (
|
|
<button
|
|
onClick={() => updateStatus('COMPLETED')}
|
|
className="px-6 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 font-semibold"
|
|
>
|
|
Tour abschließen
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-gradient-to-br from-gray-800 to-gray-900 rounded-xl shadow-2xl p-8 border border-gray-700">
|
|
<h2 className="text-2xl font-bold mb-6 text-white">
|
|
Buchungen ({tour.bookings.length})
|
|
</h2>
|
|
{tour.bookings.length > 0 ? (
|
|
<div className="space-y-4">
|
|
{tour.bookings.map((booking: any, index: number) => (
|
|
<div
|
|
key={booking.id}
|
|
className="p-5 bg-gray-700/50 border border-gray-600 rounded-lg hover:bg-gray-700 transition-colors"
|
|
>
|
|
<div className="flex justify-between items-start mb-3">
|
|
<div>
|
|
<div className="flex items-center gap-3 mb-2">
|
|
<span className="text-2xl font-bold text-blue-400">#{index + 1}</span>
|
|
<div>
|
|
<p className="font-bold text-white">{booking.bookingNumber}</p>
|
|
<p className="text-sm text-gray-400">{booking.customerName}</p>
|
|
</div>
|
|
</div>
|
|
<p className="text-white font-medium mt-2">{booking.eventLocation || 'Event'}</p>
|
|
<p className="text-gray-400 text-sm">{booking.eventAddress}, {booking.eventCity}</p>
|
|
</div>
|
|
<div className="text-right">
|
|
<p className="font-medium text-white">{formatDate(booking.eventDate)}</p>
|
|
<p className="text-sm text-gray-400">
|
|
Aufbau: {formatDate(booking.setupTimeStart)}
|
|
</p>
|
|
<button
|
|
onClick={() => router.push(`/dashboard/bookings/${booking.id}`)}
|
|
className="mt-2 text-blue-400 hover:text-blue-300 text-sm font-medium"
|
|
>
|
|
Details →
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{booking.photobox && (
|
|
<div className="mt-3 pt-3 border-t border-gray-600">
|
|
<p className="text-sm text-gray-400">
|
|
<span className="font-medium text-gray-300">Fotobox:</span>{' '}
|
|
{booking.photobox.model} (SN: {booking.photobox.serialNumber})
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<p className="text-gray-400">Keine Buchungen zugeordnet</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|