Files
Atlas/components/BookingDetail.tsx.bak3
2025-11-12 20:21:32 +01:00

377 lines
15 KiB
Plaintext

'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { FiCalendar, FiMapPin, FiUser, FiMail, FiPhone, FiEdit, FiSave, FiX, FiCamera, FiTruck, FiFileText } from 'react-icons/fi';
import Link from 'next/link';
import { formatDate, formatDateTime } from '@/lib/date-utils';
import ContractSection from './ContractSection';
interface BookingDetailProps {
booking: any;
emails: any[];
user: any;
}
export default function BookingDetail({ booking, emails, user }: BookingDetailProps) {
const router = useRouter();
const [editing, setEditing] = useState(false);
const [formData, setFormData] = useState(booking);
const [saving, setSaving] = useState(false);
const handleSave = async () => {
setSaving(true);
try {
const res = await fetch(`/api/bookings/${booking.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData),
});
if (res.ok) {
alert('Gespeichert!');
setEditing(false);
window.location.reload();
} else {
alert('Fehler beim Speichern');
}
} catch (error) {
alert('Fehler beim Speichern');
} finally {
setSaving(false);
}
};
const handleStatusChange = async (newStatus: string) => {
if (!confirm(`Status zu "${newStatus}" ändern?`)) return;
try {
const res = await fetch(`/api/bookings/${booking.id}/status`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status: newStatus }),
});
if (res.ok) {
alert('Status geändert!');
window.location.reload();
} else {
alert('Fehler beim Ändern');
}
} catch (error) {
alert('Fehler beim Ändern');
}
};
const getStatusColor = (status: string) => {
switch (status) {
case 'RESERVED': return 'bg-yellow-500/20 text-yellow-400 border-yellow-500/50';
case 'CONFIRMED': return 'bg-green-500/20 text-green-400 border-green-500/50';
case 'COMPLETED': return 'bg-blue-500/20 text-blue-400 border-blue-500/50';
case 'CANCELLED': return 'bg-red-500/20 text-red-400 border-red-500/50';
default: return 'bg-gray-500/20 text-gray-400 border-gray-500/50';
}
};
const getStatusLabel = (status: string) => {
const labels: any = {
RESERVED: 'Reserviert',
CONFIRMED: 'Bestätigt',
COMPLETED: 'Abgeschlossen',
CANCELLED: 'Storniert',
};
return labels[status] || status;
};
return (
<div className="max-w-6xl mx-auto">
<div className="flex items-center justify-between mb-8">
<div>
<Link href="/dashboard/bookings" className="text-sm text-gray-400 hover:text-gray-300 mb-2 inline-block transition-colors">
← Zurück zu Buchungen
</Link>
<h2 className="text-3xl font-bold text-white">Buchung {booking.bookingNumber}</h2>
</div>
</div>
<div className="flex gap-3">
{!editing ? (
<button
onClick={() => setEditing(true)}
className="flex items-center gap-2 px-4 py-2 bg-gradient-to-r from-gray-600 to-gray-700 text-white rounded-lg hover:from-gray-700 hover:to-gray-800 shadow-lg transition-all"
>
<FiEdit /> Bearbeiten
</button>
) : (
<>
<button
onClick={() => {
setEditing(false);
setFormData(booking);
}}
className="flex items-center gap-2 px-4 py-2 bg-gray-700 text-gray-300 rounded-lg hover:bg-gray-600 transition-colors"
>
<FiX /> Abbrechen
</button>
<button
onClick={handleSave}
disabled={saving}
className="flex items-center gap-2 px-4 py-2 bg-gradient-to-r from-red-600 to-pink-600 text-white rounded-lg hover:from-red-700 hover:to-pink-700 disabled:opacity-50 shadow-lg transition-all"
>
<FiSave /> {saving ? 'Speichern...' : 'Speichern'}
</button>
</>
)}
</div>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-2 space-y-6">
<div className="bg-gradient-to-br from-gray-800 to-gray-900 border border-gray-700 rounded-xl shadow-sm p-6">
<div className="flex items-center justify-between mb-6">
<h3 className="text-xl font-bold text-white">Status</h3>
<div className={`px-4 py-2 border-2 rounded-lg font-semibold ${getStatusColor(booking.status)}`}>
{getStatusLabel(booking.status)}
</div>
</div>
<div className="flex gap-2">
{booking.status === 'RESERVED' && (
<button
onClick={() => handleStatusChange('CONFIRMED')}
className="flex-1 px-4 py-2 bg-gradient-to-r from-green-600 to-emerald-600 text-white rounded-lg hover:from-green-700 hover:to-emerald-700 shadow-lg transition-all"
>
→ Bestätigen
</button>
)}
{booking.status === 'CONFIRMED' && (
<button
onClick={() => handleStatusChange('COMPLETED')}
className="flex-1 px-4 py-2 bg-gradient-to-r from-blue-600 to-cyan-600 text-white rounded-lg hover:from-blue-700 hover:to-cyan-700 shadow-lg transition-all"
>
→ Abschließen
</button>
)}
{['RESERVED', 'CONFIRMED'].includes(booking.status) && (
<button
onClick={() => handleStatusChange('CANCELLED')}
className="px-4 py-2 bg-gradient-to-r from-red-600 to-pink-600 text-white rounded-lg hover:from-red-700 hover:to-pink-700 shadow-lg transition-all"
>
Stornieren
</button>
)}
</div>
</div>
<div className="bg-gradient-to-br from-gray-800 to-gray-900 border border-gray-700 rounded-xl shadow-sm p-6">
<h3 className="text-xl font-bold text-white mb-4 flex items-center gap-2">
<FiUser /> Kundendaten
</h3>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-1">Name</label>
{editing ? (
<input
type="text"
value={formData.customerName}
onChange={(e) => setFormData({ ...formData, customerName: e.target.value })}
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 text-white rounded-lg focus:ring-2 focus:ring-red-500"
/>
) : (
<p className="text-white">{booking.customerName}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-1">E-Mail</label>
{editing ? (
<input
type="email"
value={formData.customerEmail}
onChange={(e) => setFormData({ ...formData, customerEmail: e.target.value })}
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 text-white rounded-lg focus:ring-2 focus:ring-red-500"
/>
) : (
<p className="text-white">{booking.customerEmail}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-1">Telefon</label>
{editing ? (
<input
type="tel"
value={formData.customerPhone}
onChange={(e) => setFormData({ ...formData, customerPhone: e.target.value })}
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 text-white rounded-lg focus:ring-2 focus:ring-red-500"
/>
) : (
<p className="text-white">{booking.customerPhone}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-1">Rechnungsart</label>
<p className="text-white">{booking.invoiceType === 'BUSINESS' ? 'Geschäftlich' : 'Privat'}</p>
</div>
{booking.companyName && (
<div className="col-span-2">
<label className="block text-sm font-medium text-gray-300 mb-1">Firma</label>
<p className="text-white">{booking.companyName}</p>
</div>
)}
</div>
</div>
<div className="bg-gradient-to-br from-gray-800 to-gray-900 border border-gray-700 rounded-xl shadow-sm p-6">
<h3 className="text-xl font-bold text-white mb-4 flex items-center gap-2">
<FiCalendar /> Event-Details
</h3>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-1">Datum</label>
<p className="text-white">{formatDate(booking.eventDate)}</p>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-1">Standort</label>
<p className="text-white">{booking.location.name}</p>
</div>
<div className="col-span-2">
<label className="block text-sm font-medium text-gray-300 mb-1">Event-Adresse</label>
<p className="text-white">{booking.eventAddress}</p>
<p className="text-gray-400 text-sm">{booking.eventZip} {booking.eventCity}</p>
</div>
{booking.eventLocation && (
<div className="col-span-2">
<label className="block text-sm font-medium text-gray-300 mb-1">Location-Name</label>
<p className="text-white">{booking.eventLocation}</p>
</div>
)}
<div>
<label className="block text-sm font-medium text-gray-300 mb-1">Aufbau ab</label>
<p className="text-white">{formatDateTime(booking.setupTimeStart)}</p>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-1">Aufbau spätestens</label>
<p className="text-white">{formatDateTime(booking.setupTimeLatest)}</p>
</div>
</div>
</div>
{booking.notes && (
<div className="bg-gradient-to-br from-gray-800 to-gray-900 border border-gray-700 rounded-xl shadow-sm p-6">
<h3 className="text-xl font-bold text-white mb-4 flex items-center gap-2">
<FiFileText /> Notizen
</h3>
<p className="text-gray-300 whitespace-pre-wrap">{booking.notes}</p>
</div>
)}
{emails.length > 0 && (
<div className="bg-gradient-to-br from-gray-800 to-gray-900 border border-gray-700 rounded-xl shadow-sm p-6">
<h3 className="text-xl font-bold text-white mb-4 flex items-center gap-2">
<FiMail /> E-Mail-Verlauf
</h3>
<div className="space-y-4">
{emails.map((email) => (
<div key={email.id} className="border border-gray-600 bg-gray-700/50 rounded-lg p-4">
<div className="flex items-center justify-between mb-2">
<span className="font-semibold text-white">{email.subject}</span>
<span className="text-sm text-gray-400">
{formatDate(email.receivedAt)}
</span>
</div>
<p className="text-sm text-gray-400 mb-2">Von: {email.from}</p>
{email.parsed && (
<span className="inline-block px-2 py-1 bg-green-500/20 text-green-400 border border-green-500/50 text-xs rounded">
Automatisch verarbeitet
</span>
)}
</div>
))}
</div>
</div>
)}
</div>
</div>
<div className="space-y-6">
<div className="bg-gradient-to-br from-gray-800 to-gray-900 border border-gray-700 rounded-xl shadow-sm p-6">
<h3 className="text-xl font-bold text-white mb-4 flex items-center gap-2">
<FiCamera /> Fotobox
</h3>
{booking.photobox ? (
<div>
<p className="text-gray-400 text-sm mb-2">Modell</p>
<p className="text-white font-semibold mb-4">{booking.photobox.model}</p>
<p className="text-gray-400 text-sm mb-2">Seriennummer</p>
<p className="text-white">{booking.photobox.serialNumber}</p>
</div>
) : (
<p className="text-gray-400">Noch nicht zugewiesen</p>
)}
</div>
<ContractSection booking={booking} onRefresh={() => router.refresh()} />
<div className="bg-gradient-to-br from-gray-800 to-gray-900 border border-gray-700 rounded-xl shadow-sm p-6">
<h3 className="text-xl font-bold text-white mb-4">Preis</h3>
<div className="text-center">
<p className="text-4xl font-bold text-white">
{booking.calculatedPrice || 0}€
</p>
{booking.distance && (
<p className="text-sm text-gray-400 mt-2">
+ {booking.distance} km Anfahrt
</p>
)}
</div>
</div>
<div className="bg-gradient-to-br from-gray-800 to-gray-900 border border-gray-700 rounded-xl shadow-sm p-6">
<h3 className="text-xl font-bold text-white mb-4">Vertrag</h3>
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-gray-300">Unterschrieben</span>
<span className={`font-semibold ${booking.contractSigned ? 'text-green-400' : 'text-red-400'}`}>
{booking.contractSigned ? 'Ja' : 'Nein'}
</span>
</div>
{booking.contractSignedAt && (
<p className="text-sm text-gray-400">
am {formatDate(booking.contractSignedAt)}
</p>
)}
</div>
</div>
{booking.tours && booking.tours.length > 0 && (
<div className="bg-gradient-to-br from-gray-800 to-gray-900 border border-gray-700 rounded-xl shadow-sm p-6">
<h3 className="text-xl font-bold text-white mb-4 flex items-center gap-2">
<FiTruck /> Tour
</h3>
{booking.tours.map((tour: any) => (
<div key={tour.id}>
{tour.driver ? (
<p className="text-white">Fahrer: {tour.driver.name}</p>
) : (
<p className="text-gray-400">Noch kein Fahrer zugewiesen</p>
)}
</div>
))}
</div>
)}
</div>
</div>
</div>
);
}