diff --git a/client/src/components/Planner/DayPlanSidebar.jsx b/client/src/components/Planner/DayPlanSidebar.jsx index ee43083..d60eaeb 100644 --- a/client/src/components/Planner/DayPlanSidebar.jsx +++ b/client/src/components/Planner/DayPlanSidebar.jsx @@ -205,16 +205,17 @@ export default function DayPlanSidebar({ catch (err) { toast.error(err.message) } } - const handleMergedDrop = async (dayId, fromType, fromId, toType, toId) => { + const handleMergedDrop = async (dayId, fromType, fromId, toType, toId, insertAfter = false) => { const m = getMergedItems(dayId) const fromIdx = m.findIndex(i => i.type === fromType && i.data.id === fromId) const toIdx = m.findIndex(i => i.type === toType && i.data.id === toId) if (fromIdx === -1 || toIdx === -1 || fromIdx === toIdx) return - // Neue Reihenfolge erstellen — VOR dem Ziel einfügen (Standardkonvention) + // Neue Reihenfolge erstellen — VOR dem Ziel einfügen (Standard), oder NACH dem Ziel wenn insertAfter const newOrder = [...m] const [moved] = newOrder.splice(fromIdx, 1) - const adjustedTo = fromIdx < toIdx ? toIdx - 1 : toIdx + let adjustedTo = fromIdx < toIdx ? toIdx - 1 : toIdx + if (insertAfter) adjustedTo += 1 newOrder.splice(adjustedTo, 0, moved) // Orte: neuer order_index über onReorder @@ -522,9 +523,9 @@ export default function DayPlanSidebar({ if (m.length === 0) return const lastItem = m[m.length - 1] if (assignmentId && String(lastItem?.data?.id) !== assignmentId) - handleMergedDrop(day.id, 'place', Number(assignmentId), lastItem.type, lastItem.data.id) + handleMergedDrop(day.id, 'place', Number(assignmentId), lastItem.type, lastItem.data.id, true) else if (noteId && String(lastItem?.data?.id) !== noteId) - handleMergedDrop(day.id, 'note', Number(noteId), lastItem.type, lastItem.data.id) + handleMergedDrop(day.id, 'note', Number(noteId), lastItem.type, lastItem.data.id, true) }} > {merged.length === 0 && !dayNoteUi ? ( @@ -749,10 +750,41 @@ export default function DayPlanSidebar({ ) }) )} - {/* Drop-Indikator am Listenende */} - {!!draggingId && dropTargetKey === `end-${day.id}` && ( -
- )} + {/* Drop-Zone am Listenende — immer vorhanden als Drop-Target */} +
{ e.preventDefault(); e.stopPropagation(); setDropTargetKey(`end-${day.id}`) }} + onDragLeave={() => { if (dropTargetKey === `end-${day.id}`) setDropTargetKey(null) }} + onDrop={e => { + e.preventDefault(); e.stopPropagation() + const { placeId, assignmentId, noteId, fromDayId } = getDragData(e) + // Neuer Ort von der Orte-Liste + if (placeId) { + onAssignToDay?.(parseInt(placeId), day.id) + setDropTargetKey(null); window.__dragData = null; return + } + if (!assignmentId && !noteId) { dragDataRef.current = null; window.__dragData = null; return } + if (assignmentId && fromDayId !== day.id) { + tripStore.moveAssignment(tripId, Number(assignmentId), fromDayId, day.id).catch(err => toast.error(err.message)) + setDraggingId(null); setDropTargetKey(null); dragDataRef.current = null; return + } + if (noteId && fromDayId !== day.id) { + tripStore.moveDayNote(tripId, fromDayId, day.id, Number(noteId)).catch(err => toast.error(err.message)) + setDraggingId(null); setDropTargetKey(null); dragDataRef.current = null; return + } + const m = getMergedItems(day.id) + if (m.length === 0) return + const lastItem = m[m.length - 1] + if (assignmentId && String(lastItem?.data?.id) !== assignmentId) + handleMergedDrop(day.id, 'place', Number(assignmentId), lastItem.type, lastItem.data.id, true) + else if (noteId && String(lastItem?.data?.id) !== noteId) + handleMergedDrop(day.id, 'note', Number(noteId), lastItem.type, lastItem.data.id, true) + }} + > + {dropTargetKey === `end-${day.id}` && ( +
+ )} +
{/* Routen-Werkzeuge (ausgewählter Tag, 2+ Orte) */} {isSelected && getDayAssignments(day.id).length >= 2 && ( diff --git a/client/src/i18n/translations/de.js b/client/src/i18n/translations/de.js index 45242c8..154bc82 100644 --- a/client/src/i18n/translations/de.js +++ b/client/src/i18n/translations/de.js @@ -414,6 +414,9 @@ const de = { 'dayplan.optimize': 'Optimieren', 'dayplan.optimized': 'Route optimiert', 'dayplan.routeError': 'Fehler bei der Routenberechnung', + 'dayplan.toast.needTwoPlaces': 'Mindestens zwei Orte für Routenoptimierung nötig', + 'dayplan.toast.routeOptimized': 'Route optimiert', + 'dayplan.toast.noGeoPlaces': 'Keine Orte mit Koordinaten für Routenberechnung gefunden', 'dayplan.confirmed': 'Bestätigt', 'dayplan.pendingRes': 'Ausstehend', 'dayplan.pdf': 'PDF', diff --git a/client/src/i18n/translations/en.js b/client/src/i18n/translations/en.js index 9caf7c5..aa24d49 100644 --- a/client/src/i18n/translations/en.js +++ b/client/src/i18n/translations/en.js @@ -414,6 +414,9 @@ const en = { 'dayplan.optimize': 'Optimize', 'dayplan.optimized': 'Route optimized', 'dayplan.routeError': 'Failed to calculate route', + 'dayplan.toast.needTwoPlaces': 'At least two places needed for route optimization', + 'dayplan.toast.routeOptimized': 'Route optimized', + 'dayplan.toast.noGeoPlaces': 'No places with coordinates found for route calculation', 'dayplan.confirmed': 'Confirmed', 'dayplan.pendingRes': 'Pending', 'dayplan.pdf': 'PDF',