import React, { useState, useCallback } from 'react' import { Plus, Search, ChevronUp, ChevronDown, X, Map, ExternalLink, Navigation, RotateCcw, Clock, Euro, FileText, Package } from 'lucide-react' import { calculateRoute, generateGoogleMapsUrl, optimizeRoute } from '../Map/RouteCalculator' import PackingListPanel from '../Packing/PackingListPanel' import { ReservationModal } from './ReservationModal' import { PlaceDetailPanel } from './PlaceDetailPanel' import { useTripStore } from '../../store/tripStore' import { useToast } from '../shared/Toast' const TABS = [ { id: 'orte', label: 'Orte', icon: '📍' }, { id: 'tagesplan', label: 'Tagesplan', icon: '📅' }, { id: 'reservierungen', label: 'Reservierungen', icon: '🎫' }, { id: 'packliste', label: 'Packliste', icon: '🎒' }, ] const TRANSPORT_MODES = [ { value: 'driving', label: 'Auto', icon: '🚗' }, { value: 'walking', label: 'Fuß', icon: '🚶' }, { value: 'cycling', label: 'Rad', icon: '🚲' }, ] export function RightPanel({ trip, days, places, categories, tags, assignments, reservations, packingItems, selectedDay, selectedDayId, selectedPlaceId, onPlaceClick, onPlaceEdit, onPlaceDelete, onAssignToDay, onRemoveAssignment, onReorder, onAddPlace, onEditTrip, onRouteCalculated, tripId, }) { const [activeTab, setActiveTab] = useState('orte') const [search, setSearch] = useState('') const [categoryFilter, setCategoryFilter] = useState('') const [transportMode, setTransportMode] = useState('driving') const [isCalculatingRoute, setIsCalculatingRoute] = useState(false) const [showReservationModal, setShowReservationModal] = useState(false) const [editingReservation, setEditingReservation] = useState(null) const [routeInfo, setRouteInfo] = useState(null) const tripStore = useTripStore() const toast = useToast() // Filtered places for Orte tab const filteredPlaces = places.filter(p => { const matchesSearch = !search || p.name.toLowerCase().includes(search.toLowerCase()) || (p.address || '').toLowerCase().includes(search.toLowerCase()) const matchesCategory = !categoryFilter || String(p.category_id) === String(categoryFilter) return matchesSearch && matchesCategory }) // Ordered assignments for selected day const dayAssignments = selectedDayId ? (assignments[String(selectedDayId)] || []).slice().sort((a, b) => a.order_index - b.order_index) : [] const isAssignedToSelectedDay = (placeId) => selectedDayId && dayAssignments.some(a => a.place?.id === placeId) // Calculate schedule with times const getSchedule = () => { if (!dayAssignments.length) return [] let currentTime = null return dayAssignments.map((assignment, idx) => { const place = assignment.place const startTime = place?.place_time || (currentTime ? currentTime : null) const duration = place?.duration_minutes || 60 if (startTime) { const [h, m] = startTime.split(':').map(Number) const endMinutes = h * 60 + m + duration const endH = Math.floor(endMinutes / 60) % 24 const endM = endMinutes % 60 currentTime = `${String(endH).padStart(2, '0')}:${String(endM).padStart(2, '0')}` } return { assignment, startTime, endTime: currentTime } }) } const handleCalculateRoute = async () => { if (!selectedDayId) return const waypoints = dayAssignments .map(a => a.place) .filter(p => p?.lat && p?.lng) .map(p => ({ lat: p.lat, lng: p.lng })) if (waypoints.length < 2) { toast.error('Mindestens 2 Orte mit Koordinaten benötigt') return } setIsCalculatingRoute(true) try { const result = await calculateRoute(waypoints, transportMode) if (result) { setRouteInfo({ distance: result.distanceText, duration: result.durationText }) onRouteCalculated?.(result) toast.success('Route berechnet') } else { toast.error('Route konnte nicht berechnet werden') } } catch (err) { toast.error('Fehler bei der Routenberechnung') } finally { setIsCalculatingRoute(false) } } const handleOptimizeRoute = async () => { if (!selectedDayId || dayAssignments.length < 3) return const places = dayAssignments.map(a => a.place).filter(p => p?.lat && p?.lng) const optimized = optimizeRoute(places) const optimizedIds = optimized.map(p => { const a = dayAssignments.find(a => a.place?.id === p.id) return a?.id }).filter(Boolean) await onReorder(selectedDayId, optimizedIds) toast.success('Route optimiert') } const handleOpenGoogleMaps = () => { const places = dayAssignments.map(a => a.place).filter(p => p?.lat && p?.lng) const url = generateGoogleMapsUrl(places) if (url) window.open(url, '_blank') else toast.error('Keine Orte mit Koordinaten vorhanden') } const handleMoveUp = async (idx) => { if (idx === 0) return const ids = dayAssignments.map(a => a.id) ;[ids[idx - 1], ids[idx]] = [ids[idx], ids[idx - 1]] await onReorder(selectedDayId, ids) } const handleMoveDown = async (idx) => { if (idx === dayAssignments.length - 1) return const ids = dayAssignments.map(a => a.id) ;[ids[idx], ids[idx + 1]] = [ids[idx + 1], ids[idx]] await onReorder(selectedDayId, ids) } const handleAddReservation = () => { setEditingReservation(null) setShowReservationModal(true) } const handleSaveReservation = async (data) => { try { if (editingReservation) { await tripStore.updateReservation(tripId, editingReservation.id, data) toast.success('Reservierung aktualisiert') } else { await tripStore.addReservation(tripId, { ...data, day_id: selectedDayId || null }) toast.success('Reservierung hinzugefügt') } setShowReservationModal(false) } catch (err) { toast.error(err.message) } } const handleDeleteReservation = async (id) => { if (!confirm('Reservierung löschen?')) return try { await tripStore.deleteReservation(tripId, id) toast.success('Reservierung gelöscht') } catch (err) { toast.error(err.message) } } // Reservations for selected day (or all if no day selected) const filteredReservations = selectedDayId ? reservations.filter(r => String(r.day_id) === String(selectedDayId) || !r.day_id) : reservations const selectedPlace = selectedPlaceId ? places.find(p => p.id === selectedPlaceId) : null return (
{/* Tabs */}
{TABS.map(tab => ( ))}
{/* Tab Content */}
{/* ORTE TAB */} {activeTab === 'orte' && (
{/* Place detail (when selected) */} {selectedPlace && (
onPlaceClick(null)} onEdit={() => onPlaceEdit(selectedPlace)} onDelete={() => onPlaceDelete(selectedPlace.id)} onAssignToDay={onAssignToDay} onRemoveAssignment={onRemoveAssignment} />
)} {/* Search & filter */}
setSearch(e.target.value)} placeholder="Orte suchen..." className="w-full pl-8 pr-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-slate-900" /> {search && ( )}
{/* Places list */}
{filteredPlaces.length === 0 ? (
📍

Keine Orte gefunden

) : (
{filteredPlaces.map(place => { const category = categories.find(c => c.id === place.category_id) const isInDay = isAssignedToSelectedDay(place.id) const isSelected = place.id === selectedPlaceId return (
onPlaceClick(isSelected ? null : place.id)} className={`px-3 py-2.5 cursor-pointer transition-colors ${ isSelected ? 'bg-slate-50' : 'hover:bg-gray-50' }`} >
{/* Category color bar */}
{place.name}
{isInDay && ( )} {!isInDay && selectedDayId && ( )}
{category && ( {category.icon} {category.name} )} {place.address && (

{place.address}

)}
{place.place_time && ( 🕐 {place.place_time} )} {place.price > 0 && ( {place.price} {place.currency || trip?.currency} )}
) })}
)}
)} {/* TAGESPLAN TAB */} {activeTab === 'tagesplan' && (
{!selectedDayId ? (
📅

Wähle einen Tag aus der linken Liste um den Tagesplan zu sehen

) : ( <> {/* Day header */}

Tag {selectedDay?.day_number} {selectedDay?.date && ( {formatGermanDate(selectedDay.date)} )}

{dayAssignments.length} Ort{dayAssignments.length !== 1 ? 'e' : ''} {dayAssignments.length > 0 && ` · ${dayAssignments.reduce((s, a) => s + (a.place?.duration_minutes || 60), 0)} Min. gesamt`}

{/* Transport mode */}
{TRANSPORT_MODES.map(m => ( ))}
{/* Places list with order */}
{dayAssignments.length === 0 ? (
🗺️

Noch keine Orte für diesen Tag

) : (
{getSchedule().map(({ assignment, startTime, endTime }, idx) => { const place = assignment.place if (!place) return null const category = categories.find(c => c.id === place.category_id) return (
{/* Order number */}
{idx + 1}
{/* Place info */}
{place.name}
{startTime && ( 🕐 {startTime} )} {place.duration_minutes || 60} Min. {place.price > 0 && ( {place.price} {place.currency || trip?.currency} )}
{place.address && (

{place.address}

)} {assignment.notes && (

{assignment.notes}

)}
{/* Actions */}
) })}
)}
{/* Route buttons */} {dayAssignments.length >= 2 && (
{routeInfo && (
🛣️ {routeInfo.distance} · ⏱️ {routeInfo.duration}
)}
)} )}
)} {/* RESERVIERUNGEN TAB */} {activeTab === 'reservierungen' && (

Reservierungen {selectedDay && · Tag {selectedDay.day_number}}

{filteredReservations.length === 0 ? (
🎫

Keine Reservierungen

) : (
{filteredReservations.map(reservation => (
{reservation.title}
{reservation.reservation_time && (
{formatDateTime(reservation.reservation_time)}
)} {reservation.location && (
📍 {reservation.location}
)} {reservation.confirmation_number && (
# {reservation.confirmation_number}
)} {reservation.notes && (

{reservation.notes}

)}
))}
)}
)} {/* PACKLISTE TAB */} {activeTab === 'packliste' && ( )}
{/* Reservation Modal */} { setShowReservationModal(false); setEditingReservation(null) }} onSave={handleSaveReservation} reservation={editingReservation} days={days} places={places} selectedDayId={selectedDayId} />
) } function formatGermanDate(dateStr) { if (!dateStr) return '' const date = new Date(dateStr + 'T00:00:00') return date.toLocaleDateString('de-DE', { weekday: 'long', day: 'numeric', month: 'long' }) } function formatDateTime(dt) { if (!dt) return '' try { return new Date(dt).toLocaleString('de-DE', { dateStyle: 'medium', timeStyle: 'short' }) } catch { return dt } }