fix: mobile place editing and detail view (#269)
- PlacesSidebar mobile: tap opens action sheet with view details, edit, assign to day, and delete options - PlaceInspector renders as fullscreen portal overlay on mobile - DayPlanSidebar mobile: tapping a place closes overlay and opens inspector - Inspector closes when edit or delete is triggered on mobile - i18n: added places.viewDetails for all 12 languages
This commit is contained in:
@@ -2,7 +2,7 @@ import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { useState, useRef, useMemo, useCallback } from 'react'
|
||||
import DOM from 'react-dom'
|
||||
import { Search, Plus, X, CalendarDays, Pencil, Trash2, ExternalLink, Navigation, Upload, ChevronDown, Check, MapPin } from 'lucide-react'
|
||||
import { Search, Plus, X, CalendarDays, Pencil, Trash2, ExternalLink, Navigation, Upload, ChevronDown, Check, MapPin, Eye } from 'lucide-react'
|
||||
import PlaceAvatar from '../shared/PlaceAvatar'
|
||||
import { getCategoryIcon } from '../shared/categoryIcons'
|
||||
import { useTranslation } from '../../i18n'
|
||||
@@ -92,6 +92,7 @@ const PlacesSidebar = React.memo(function PlacesSidebar({
|
||||
}
|
||||
const [dayPickerPlace, setDayPickerPlace] = useState(null)
|
||||
const [catDropOpen, setCatDropOpen] = useState(false)
|
||||
const [mobileShowDays, setMobileShowDays] = useState(false)
|
||||
|
||||
// Alle geplanten Ort-IDs abrufen (einem Tag zugewiesen)
|
||||
const plannedIds = useMemo(() => new Set(
|
||||
@@ -286,7 +287,7 @@ const PlacesSidebar = React.memo(function PlacesSidebar({
|
||||
window.__dragData = { placeId: String(place.id) }
|
||||
}}
|
||||
onClick={() => {
|
||||
if (isMobile && days?.length > 0) {
|
||||
if (isMobile) {
|
||||
setDayPickerPlace(place)
|
||||
} else {
|
||||
onPlaceClick(isSelected ? null : place.id)
|
||||
@@ -353,49 +354,75 @@ const PlacesSidebar = React.memo(function PlacesSidebar({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{dayPickerPlace && days?.length > 0 && ReactDOM.createPortal(
|
||||
{dayPickerPlace && ReactDOM.createPortal(
|
||||
<div
|
||||
onClick={() => setDayPickerPlace(null)}
|
||||
onClick={() => { setDayPickerPlace(null); setMobileShowDays(false) }}
|
||||
style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.4)', zIndex: 99999, display: 'flex', alignItems: 'flex-end', justifyContent: 'center' }}
|
||||
>
|
||||
<div
|
||||
onClick={e => e.stopPropagation()}
|
||||
style={{ background: 'var(--bg-card)', borderRadius: '20px 20px 0 0', width: '100%', maxWidth: 500, maxHeight: '60vh', display: 'flex', flexDirection: 'column', overflow: 'hidden', paddingBottom: 'env(safe-area-inset-bottom)' }}
|
||||
style={{ background: 'var(--bg-card)', borderRadius: '20px 20px 0 0', width: '100%', maxWidth: 500, maxHeight: '70vh', display: 'flex', flexDirection: 'column', overflow: 'hidden', paddingBottom: 'env(safe-area-inset-bottom)' }}
|
||||
>
|
||||
<div style={{ padding: '16px 20px 12px', borderBottom: '1px solid var(--border-secondary)' }}>
|
||||
<div style={{ fontSize: 15, fontWeight: 700, color: 'var(--text-primary)' }}>{dayPickerPlace.name}</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--text-faint)', marginTop: 2 }}>{t('places.assignToDay')}</div>
|
||||
{dayPickerPlace.address && <div style={{ fontSize: 12, color: 'var(--text-faint)', marginTop: 2 }}>{dayPickerPlace.address}</div>}
|
||||
</div>
|
||||
<div style={{ flex: 1, overflowY: 'auto', padding: '8px 12px 16px' }}>
|
||||
{days.map((day, i) => {
|
||||
return (
|
||||
<div style={{ overflowY: 'auto', padding: '8px 12px' }}>
|
||||
{/* View details */}
|
||||
<button
|
||||
onClick={() => { onPlaceClick(dayPickerPlace.id); setDayPickerPlace(null); setMobileShowDays(false) }}
|
||||
style={{ display: 'flex', alignItems: 'center', gap: 12, width: '100%', padding: '12px 14px', borderRadius: 12, border: 'none', cursor: 'pointer', background: 'transparent', fontFamily: 'inherit', textAlign: 'left', fontSize: 14, color: 'var(--text-primary)' }}
|
||||
>
|
||||
<Eye size={18} color="var(--text-muted)" /> {t('places.viewDetails')}
|
||||
</button>
|
||||
{/* Edit */}
|
||||
{canEditPlaces && (
|
||||
<button
|
||||
onClick={() => { onEditPlace(dayPickerPlace); setDayPickerPlace(null); setMobileShowDays(false) }}
|
||||
style={{ display: 'flex', alignItems: 'center', gap: 12, width: '100%', padding: '12px 14px', borderRadius: 12, border: 'none', cursor: 'pointer', background: 'transparent', fontFamily: 'inherit', textAlign: 'left', fontSize: 14, color: 'var(--text-primary)' }}
|
||||
>
|
||||
<Pencil size={18} color="var(--text-muted)" /> {t('common.edit')}
|
||||
</button>
|
||||
)}
|
||||
{/* Assign to day */}
|
||||
{days?.length > 0 && (
|
||||
<>
|
||||
<button
|
||||
key={day.id}
|
||||
onClick={() => { onAssignToDay(dayPickerPlace.id, day.id); setDayPickerPlace(null) }}
|
||||
style={{
|
||||
display: 'flex', alignItems: 'center', gap: 10, width: '100%',
|
||||
padding: '12px 14px', borderRadius: 12, border: 'none', cursor: 'pointer',
|
||||
background: 'transparent', fontFamily: 'inherit', textAlign: 'left',
|
||||
transition: 'background 0.1s',
|
||||
}}
|
||||
onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-hover)'}
|
||||
onMouseLeave={e => e.currentTarget.style.background = 'transparent'}
|
||||
onClick={() => setMobileShowDays(v => !v)}
|
||||
style={{ display: 'flex', alignItems: 'center', gap: 12, width: '100%', padding: '12px 14px', borderRadius: 12, border: 'none', cursor: 'pointer', background: 'transparent', fontFamily: 'inherit', textAlign: 'left', fontSize: 14, color: 'var(--text-primary)' }}
|
||||
>
|
||||
<div style={{
|
||||
width: 32, height: 32, borderRadius: '50%', background: 'var(--bg-tertiary)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
fontSize: 13, fontWeight: 700, color: 'var(--text-primary)', flexShrink: 0,
|
||||
}}>{i + 1}</div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{ fontSize: 13, fontWeight: 600, color: 'var(--text-primary)' }}>
|
||||
{day.title || `${t('dayplan.dayN', { n: i + 1 })}`}
|
||||
</div>
|
||||
{day.date && <div style={{ fontSize: 11, color: 'var(--text-faint)' }}>{new Date(day.date + 'T00:00:00').toLocaleDateString()}</div>}
|
||||
</div>
|
||||
{(assignments[String(day.id)] || []).some(a => a.place?.id === dayPickerPlace.id) && <span style={{ fontSize: 11, color: 'var(--text-faint)' }}>✓</span>}
|
||||
<CalendarDays size={18} color="var(--text-muted)" /> {t('places.assignToDay')}
|
||||
<ChevronDown size={14} style={{ marginLeft: 'auto', color: 'var(--text-faint)', transform: mobileShowDays ? 'rotate(180deg)' : 'none', transition: 'transform 0.15s' }} />
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
{mobileShowDays && (
|
||||
<div style={{ paddingLeft: 20 }}>
|
||||
{days.map((day, i) => (
|
||||
<button
|
||||
key={day.id}
|
||||
onClick={() => { onAssignToDay(dayPickerPlace.id, day.id); setDayPickerPlace(null); setMobileShowDays(false) }}
|
||||
style={{ display: 'flex', alignItems: 'center', gap: 10, width: '100%', padding: '10px 14px', borderRadius: 10, border: 'none', cursor: 'pointer', background: 'transparent', fontFamily: 'inherit', textAlign: 'left' }}
|
||||
>
|
||||
<div style={{ width: 28, height: 28, borderRadius: '50%', background: 'var(--bg-tertiary)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 12, fontWeight: 700, color: 'var(--text-primary)', flexShrink: 0 }}>{i + 1}</div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{ fontSize: 13, fontWeight: 500, color: 'var(--text-primary)' }}>{day.title || t('dayplan.dayN', { n: i + 1 })}</div>
|
||||
{day.date && <div style={{ fontSize: 11, color: 'var(--text-faint)' }}>{new Date(day.date + 'T00:00:00').toLocaleDateString()}</div>}
|
||||
</div>
|
||||
{(assignments[String(day.id)] || []).some(a => a.place?.id === dayPickerPlace.id) && <Check size={14} color="var(--text-faint)" />}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{/* Delete */}
|
||||
{canEditPlaces && (
|
||||
<button
|
||||
onClick={() => { onDeletePlace(dayPickerPlace.id); setDayPickerPlace(null); setMobileShowDays(false) }}
|
||||
style={{ display: 'flex', alignItems: 'center', gap: 12, width: '100%', padding: '12px 14px', borderRadius: 12, border: 'none', cursor: 'pointer', background: 'transparent', fontFamily: 'inherit', textAlign: 'left', fontSize: 14, color: '#ef4444' }}
|
||||
>
|
||||
<Trash2 size={18} /> {t('common.delete')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
|
||||
@@ -791,6 +791,7 @@ const ar: Record<string, string | { name: string; category: string }[]> = {
|
||||
'places.googleListHint': 'الصق رابط قائمة Google Maps المشتركة لاستيراد جميع الأماكن.',
|
||||
'places.googleListImported': 'تم استيراد {count} أماكن من "{list}"',
|
||||
'places.googleListError': 'فشل استيراد قائمة Google Maps',
|
||||
'places.viewDetails': 'عرض التفاصيل',
|
||||
'places.urlResolved': 'تم استيراد المكان من الرابط',
|
||||
'places.assignToDay': 'إلى أي يوم تريد الإضافة؟',
|
||||
'places.all': 'الكل',
|
||||
|
||||
@@ -771,6 +771,7 @@ const br: Record<string, string | { name: string; category: string }[]> = {
|
||||
'places.googleListHint': 'Cole um link compartilhado de uma lista do Google Maps para importar todos os lugares.',
|
||||
'places.googleListImported': '{count} lugares importados de "{list}"',
|
||||
'places.googleListError': 'Falha ao importar lista do Google Maps',
|
||||
'places.viewDetails': 'Ver detalhes',
|
||||
'places.urlResolved': 'Lugar importado da URL',
|
||||
'places.assignToDay': 'Adicionar a qual dia?',
|
||||
'places.all': 'Todos',
|
||||
|
||||
@@ -792,6 +792,7 @@ const cs: Record<string, string | { name: string; category: string }[]> = {
|
||||
'places.googleListHint': 'Vložte sdílený odkaz na seznam Google Maps pro import všech míst.',
|
||||
'places.googleListImported': '{count} míst importováno ze seznamu "{list}"',
|
||||
'places.googleListError': 'Import seznamu Google Maps se nezdařil',
|
||||
'places.viewDetails': 'Zobrazit detaily',
|
||||
'places.assignToDay': 'Přidat do kterého dne?',
|
||||
'places.all': 'Vše',
|
||||
'places.unplanned': 'Nezařazené',
|
||||
|
||||
@@ -790,6 +790,7 @@ const de: Record<string, string | { name: string; category: string }[]> = {
|
||||
'places.googleListHint': 'Geteilten Google Maps Listen-Link einfügen, um alle Orte zu importieren.',
|
||||
'places.googleListImported': '{count} Orte aus "{list}" importiert',
|
||||
'places.googleListError': 'Google Maps Liste konnte nicht importiert werden',
|
||||
'places.viewDetails': 'Details anzeigen',
|
||||
'places.assignToDay': 'Zu welchem Tag hinzufügen?',
|
||||
'places.all': 'Alle',
|
||||
'places.unplanned': 'Ungeplant',
|
||||
|
||||
@@ -786,6 +786,7 @@ const en: Record<string, string | { name: string; category: string }[]> = {
|
||||
'places.googleListHint': 'Paste a shared Google Maps list link to import all places.',
|
||||
'places.googleListImported': '{count} places imported from "{list}"',
|
||||
'places.googleListError': 'Failed to import Google Maps list',
|
||||
'places.viewDetails': 'View Details',
|
||||
'places.assignToDay': 'Add to which day?',
|
||||
'places.all': 'All',
|
||||
'places.unplanned': 'Unplanned',
|
||||
|
||||
@@ -765,6 +765,7 @@ const es: Record<string, string> = {
|
||||
'places.googleListHint': 'Pega un enlace compartido de una lista de Google Maps para importar todos los lugares.',
|
||||
'places.googleListImported': '{count} lugares importados de "{list}"',
|
||||
'places.googleListError': 'Error al importar la lista de Google Maps',
|
||||
'places.viewDetails': 'Ver detalles',
|
||||
'places.urlResolved': 'Lugar importado desde URL',
|
||||
'places.assignToDay': '¿A qué día añadirlo?',
|
||||
'places.all': 'Todo',
|
||||
|
||||
@@ -788,6 +788,7 @@ const fr: Record<string, string> = {
|
||||
'places.googleListHint': 'Collez un lien de liste Google Maps partagée pour importer tous les lieux.',
|
||||
'places.googleListImported': '{count} lieux importés depuis "{list}"',
|
||||
'places.googleListError': 'Impossible d\'importer la liste Google Maps',
|
||||
'places.viewDetails': 'Voir les détails',
|
||||
'places.urlResolved': 'Lieu importé depuis l\'URL',
|
||||
'places.assignToDay': 'Ajouter à quel jour ?',
|
||||
'places.all': 'Tous',
|
||||
|
||||
@@ -788,6 +788,7 @@ const hu: Record<string, string | { name: string; category: string }[]> = {
|
||||
'places.googleListHint': 'Illessz be egy megosztott Google Maps lista linket az osszes hely importalasahoz.',
|
||||
'places.googleListImported': '{count} hely importalva a(z) "{list}" listabol',
|
||||
'places.googleListError': 'Google Maps lista importalasa sikertelen',
|
||||
'places.viewDetails': 'Részletek megtekintése',
|
||||
'places.assignToDay': 'Melyik naphoz adod?',
|
||||
'places.all': 'Összes',
|
||||
'places.unplanned': 'Nem tervezett',
|
||||
|
||||
@@ -788,6 +788,7 @@ const it: Record<string, string | { name: string; category: string }[]> = {
|
||||
'places.googleListHint': 'Incolla un link condiviso di una lista Google Maps per importare tutti i luoghi.',
|
||||
'places.googleListImported': '{count} luoghi importati da "{list}"',
|
||||
'places.googleListError': 'Importazione lista Google Maps non riuscita',
|
||||
'places.viewDetails': 'Visualizza dettagli',
|
||||
'places.assignToDay': 'A quale giorno aggiungere?',
|
||||
'places.all': 'Tutti',
|
||||
'places.unplanned': 'Non pianificati',
|
||||
|
||||
@@ -788,6 +788,7 @@ const nl: Record<string, string> = {
|
||||
'places.googleListHint': 'Plak een gedeelde Google Maps lijstlink om alle plaatsen te importeren.',
|
||||
'places.googleListImported': '{count} plaatsen geimporteerd uit "{list}"',
|
||||
'places.googleListError': 'Google Maps lijst importeren mislukt',
|
||||
'places.viewDetails': 'Details bekijken',
|
||||
'places.urlResolved': 'Plaats geïmporteerd van URL',
|
||||
'places.assignToDay': 'Aan welke dag toevoegen?',
|
||||
'places.all': 'Alle',
|
||||
|
||||
@@ -788,6 +788,7 @@ const ru: Record<string, string> = {
|
||||
'places.googleListHint': 'Вставьте ссылку на общий список Google Maps для импорта всех мест.',
|
||||
'places.googleListImported': '{count} мест импортировано из "{list}"',
|
||||
'places.googleListError': 'Не удалось импортировать список Google Maps',
|
||||
'places.viewDetails': 'Подробности',
|
||||
'places.urlResolved': 'Место импортировано из URL',
|
||||
'places.assignToDay': 'Добавить в какой день?',
|
||||
'places.all': 'Все',
|
||||
|
||||
@@ -788,6 +788,7 @@ const zh: Record<string, string> = {
|
||||
'places.googleListHint': '粘贴共享的 Google Maps 列表链接以导入所有地点。',
|
||||
'places.googleListImported': '已从"{list}"导入 {count} 个地点',
|
||||
'places.googleListError': 'Google Maps 列表导入失败',
|
||||
'places.viewDetails': '查看详情',
|
||||
'places.urlResolved': '已从 URL 导入地点',
|
||||
'places.assignToDay': '添加到哪一天?',
|
||||
'places.all': '全部',
|
||||
|
||||
@@ -562,7 +562,7 @@ export default function TripPlannerPage(): React.ReactElement | null {
|
||||
)
|
||||
})()}
|
||||
|
||||
{selectedPlace && (
|
||||
{selectedPlace && !isMobile && (
|
||||
<PlaceInspector
|
||||
place={selectedPlace}
|
||||
categories={categories}
|
||||
@@ -573,7 +573,6 @@ export default function TripPlannerPage(): React.ReactElement | null {
|
||||
reservations={reservations}
|
||||
onClose={() => setSelectedPlaceId(null)}
|
||||
onEdit={() => {
|
||||
// When editing from assignment context, use assignment-level times
|
||||
if (selectedAssignmentId) {
|
||||
const assignmentObj = Object.values(assignments).flat().find(a => a.id === selectedAssignmentId)
|
||||
const placeWithAssignmentTimes = assignmentObj?.place ? { ...selectedPlace, place_time: assignmentObj.place.place_time, end_time: assignmentObj.place.end_time } : selectedPlace
|
||||
@@ -609,6 +608,58 @@ export default function TripPlannerPage(): React.ReactElement | null {
|
||||
/>
|
||||
)}
|
||||
|
||||
{selectedPlace && isMobile && ReactDOM.createPortal(
|
||||
<div style={{ position: 'fixed', inset: 0, zIndex: 9999, display: 'flex', alignItems: 'flex-end', justifyContent: 'center', background: 'rgba(0,0,0,0.3)' }} onClick={() => setSelectedPlaceId(null)}>
|
||||
<div style={{ width: '100%', maxHeight: '85vh' }} onClick={e => e.stopPropagation()}>
|
||||
<PlaceInspector
|
||||
place={selectedPlace}
|
||||
categories={categories}
|
||||
days={days}
|
||||
selectedDayId={selectedDayId}
|
||||
selectedAssignmentId={selectedAssignmentId}
|
||||
assignments={assignments}
|
||||
reservations={reservations}
|
||||
onClose={() => setSelectedPlaceId(null)}
|
||||
onEdit={() => {
|
||||
if (selectedAssignmentId) {
|
||||
const assignmentObj = Object.values(assignments).flat().find(a => a.id === selectedAssignmentId)
|
||||
const placeWithAssignmentTimes = assignmentObj?.place ? { ...selectedPlace, place_time: assignmentObj.place.place_time, end_time: assignmentObj.place.end_time } : selectedPlace
|
||||
setEditingPlace(placeWithAssignmentTimes)
|
||||
} else {
|
||||
setEditingPlace(selectedPlace)
|
||||
}
|
||||
setEditingAssignmentId(selectedAssignmentId || null)
|
||||
setShowPlaceForm(true)
|
||||
setSelectedPlaceId(null)
|
||||
}}
|
||||
onDelete={() => { handleDeletePlace(selectedPlace.id); setSelectedPlaceId(null) }}
|
||||
onAssignToDay={handleAssignToDay}
|
||||
onRemoveAssignment={handleRemoveAssignment}
|
||||
files={files}
|
||||
onFileUpload={canUploadFiles ? (fd) => tripStore.addFile(tripId, fd) : undefined}
|
||||
tripMembers={tripMembers}
|
||||
onSetParticipants={async (assignmentId, dayId, userIds) => {
|
||||
try {
|
||||
const data = await assignmentsApi.setParticipants(tripId, assignmentId, userIds)
|
||||
useTripStore.setState(state => ({
|
||||
assignments: {
|
||||
...state.assignments,
|
||||
[String(dayId)]: (state.assignments[String(dayId)] || []).map(a =>
|
||||
a.id === assignmentId ? { ...a, participants: data.participants } : a
|
||||
),
|
||||
}
|
||||
}))
|
||||
} catch {}
|
||||
}}
|
||||
onUpdatePlace={async (placeId, data) => { try { await tripStore.updatePlace(tripId, placeId, data) } catch (err: unknown) { toast.error(err instanceof Error ? err.message : 'Unknown error') } }}
|
||||
leftWidth={0}
|
||||
rightWidth={0}
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
)}
|
||||
|
||||
{mobileSidebarOpen && ReactDOM.createPortal(
|
||||
<div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.3)', zIndex: 9999 }} onClick={() => setMobileSidebarOpen(null)}>
|
||||
<div style={{ position: 'absolute', top: 'var(--nav-h)', left: 0, right: 0, bottom: 0, background: 'var(--bg-card)', display: 'flex', flexDirection: 'column', overflow: 'hidden' }} onClick={e => e.stopPropagation()}>
|
||||
@@ -620,8 +671,8 @@ export default function TripPlannerPage(): React.ReactElement | null {
|
||||
</div>
|
||||
<div style={{ flex: 1, overflow: 'auto' }}>
|
||||
{mobileSidebarOpen === 'left'
|
||||
? <DayPlanSidebar tripId={tripId} trip={trip} days={days} places={places} categories={categories} assignments={assignments} selectedDayId={selectedDayId} selectedPlaceId={selectedPlaceId} selectedAssignmentId={selectedAssignmentId} onSelectDay={(id) => { handleSelectDay(id); setMobileSidebarOpen(null) }} onPlaceClick={handlePlaceClick} onReorder={handleReorder} onUpdateDayTitle={handleUpdateDayTitle} onAssignToDay={handleAssignToDay} onRouteCalculated={(r) => { if (r) { setRoute(r.coordinates); setRouteInfo({ distance: r.distanceText, duration: r.durationText }) } }} reservations={reservations} onAddReservation={(dayId) => { setEditingReservation(null); tripStore.setSelectedDay(dayId); setShowReservationModal(true); setMobileSidebarOpen(null) }} onDayDetail={(day) => { setShowDayDetail(day); setSelectedPlaceId(null); setSelectedAssignmentId(null); setMobileSidebarOpen(null) }} accommodations={tripAccommodations} onNavigateToFiles={() => { setMobileSidebarOpen(null); handleTabChange('dateien') }} />
|
||||
: <PlacesSidebar tripId={tripId} places={places} categories={categories} assignments={assignments} selectedDayId={selectedDayId} selectedPlaceId={selectedPlaceId} onPlaceClick={handlePlaceClick} onAddPlace={() => { setEditingPlace(null); setShowPlaceForm(true); setMobileSidebarOpen(null) }} onAssignToDay={handleAssignToDay} days={days} isMobile onCategoryFilterChange={setMapCategoryFilter} />
|
||||
? <DayPlanSidebar tripId={tripId} trip={trip} days={days} places={places} categories={categories} assignments={assignments} selectedDayId={selectedDayId} selectedPlaceId={selectedPlaceId} selectedAssignmentId={selectedAssignmentId} onSelectDay={(id) => { handleSelectDay(id); setMobileSidebarOpen(null) }} onPlaceClick={(placeId, assignmentId) => { handlePlaceClick(placeId, assignmentId); setMobileSidebarOpen(null) }} onReorder={handleReorder} onUpdateDayTitle={handleUpdateDayTitle} onAssignToDay={handleAssignToDay} onRouteCalculated={(r) => { if (r) { setRoute(r.coordinates); setRouteInfo({ distance: r.distanceText, duration: r.durationText }) } }} reservations={reservations} onAddReservation={(dayId) => { setEditingReservation(null); tripStore.setSelectedDay(dayId); setShowReservationModal(true); setMobileSidebarOpen(null) }} onDayDetail={(day) => { setShowDayDetail(day); setSelectedPlaceId(null); setSelectedAssignmentId(null); setMobileSidebarOpen(null) }} accommodations={tripAccommodations} onNavigateToFiles={() => { setMobileSidebarOpen(null); handleTabChange('dateien') }} />
|
||||
: <PlacesSidebar tripId={tripId} places={places} categories={categories} assignments={assignments} selectedDayId={selectedDayId} selectedPlaceId={selectedPlaceId} onPlaceClick={(placeId) => { handlePlaceClick(placeId); setMobileSidebarOpen(null) }} onAddPlace={() => { setEditingPlace(null); setShowPlaceForm(true); setMobileSidebarOpen(null) }} onAssignToDay={handleAssignToDay} onEditPlace={(place) => { setEditingPlace(place); setEditingAssignmentId(null); setShowPlaceForm(true); setMobileSidebarOpen(null) }} onDeletePlace={(placeId) => handleDeletePlace(placeId)} days={days} isMobile onCategoryFilterChange={setMapCategoryFilter} />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user