mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-21 06:11:45 +00:00
feat: undo button for trip planner
Implements a full undo history system for the Plan screen. New hook: usePlannerHistory (client/src/hooks/usePlannerHistory.ts) - Maintains a LIFO stack (up to 30 entries) of reversible actions - Exposes pushUndo(label, fn), undo(), canUndo, lastActionLabel Tracked actions: - Assign place to day (undo: remove the assignment) - Remove place from day (undo: re-assign at original position) - Reorder places within a day (undo: restore previous order) - Move place to a different day (undo: move back) - Optimize route (undo: restore original order) - Lock / unlock place (undo: toggle back) - Delete place (undo: recreate place + restore all day assignments) - Add place (undo: delete it) - Import from GPX (undo: delete all imported places) - Import from Google Maps list (undo: delete all imported places) UI: Undo button (Undo2 icon) in DayPlanSidebar header. PDF, ICS and Undo buttons all use custom instant hover tooltips instead of native title attributes. A toast notification confirms each undo action. Translations: undo.* keys added to all 12 language files.
This commit is contained in:
@@ -29,11 +29,12 @@ interface PlacesSidebarProps {
|
||||
days: Day[]
|
||||
isMobile: boolean
|
||||
onCategoryFilterChange?: (categoryId: string) => void
|
||||
pushUndo?: (label: string, undoFn: () => Promise<void> | void) => void
|
||||
}
|
||||
|
||||
const PlacesSidebar = React.memo(function PlacesSidebar({
|
||||
tripId, places, categories, assignments, selectedDayId, selectedPlaceId,
|
||||
onPlaceClick, onAddPlace, onAssignToDay, onEditPlace, onDeletePlace, days, isMobile, onCategoryFilterChange,
|
||||
onPlaceClick, onAddPlace, onAssignToDay, onEditPlace, onDeletePlace, days, isMobile, onCategoryFilterChange, pushUndo,
|
||||
}: PlacesSidebarProps) {
|
||||
const { t } = useTranslation()
|
||||
const toast = useToast()
|
||||
@@ -52,6 +53,15 @@ const PlacesSidebar = React.memo(function PlacesSidebar({
|
||||
const result = await placesApi.importGpx(tripId, file)
|
||||
await loadTrip(tripId)
|
||||
toast.success(t('places.gpxImported', { count: result.count }))
|
||||
if (result.places?.length > 0) {
|
||||
const importedIds: number[] = result.places.map((p: { id: number }) => p.id)
|
||||
pushUndo?.(t('undo.importGpx'), async () => {
|
||||
for (const id of importedIds) {
|
||||
try { await placesApi.delete(tripId, id) } catch {}
|
||||
}
|
||||
await loadTrip(tripId)
|
||||
})
|
||||
}
|
||||
} catch (err: any) {
|
||||
toast.error(err?.response?.data?.error || t('places.gpxError'))
|
||||
}
|
||||
@@ -70,6 +80,15 @@ const PlacesSidebar = React.memo(function PlacesSidebar({
|
||||
toast.success(t('places.googleListImported', { count: result.count, list: result.listName }))
|
||||
setGoogleListOpen(false)
|
||||
setGoogleListUrl('')
|
||||
if (result.places?.length > 0) {
|
||||
const importedIds: number[] = result.places.map((p: { id: number }) => p.id)
|
||||
pushUndo?.(t('undo.importGoogleList'), async () => {
|
||||
for (const id of importedIds) {
|
||||
try { await placesApi.delete(tripId, id) } catch {}
|
||||
}
|
||||
await loadTrip(tripId)
|
||||
})
|
||||
}
|
||||
} catch (err: any) {
|
||||
toast.error(err?.response?.data?.error || t('places.googleListError'))
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user