fix(tours): restrict permanent deletion to tours workspace

This commit is contained in:
elmocito
2026-10-06 12:52:25 +02:00
parent 04c7491e2a
commit 8739b4c74d
17 changed files with 456 additions and 39 deletions
@@ -1490,6 +1490,55 @@ describe('DayPlanSidebar', () => {
expect(onDeletePlace).toHaveBeenCalledWith(42)
})
it('hides permanent Tour Delete from the TRIP-PLAN day card while retaining edit, route, view, and remove-from-day', async () => {
const user = userEvent.setup()
useAddonStore.setState({ addons: [{ id: 'tours', name: 'Tours', type: 'trip', icon: '', enabled: true }] })
const place = buildPlace({ id: 42, name: 'Ridge walk' })
const day = buildDay({ id: 10, date: '2025-06-01', title: 'Day 1' })
const assignment = buildAssignment({ id: 99, day_id: 10, order_index: 0, place })
const onPlaceClick = vi.fn()
const onRemoveAssignment = vi.fn()
const onDeletePlace = vi.fn()
render(
<DayPlanSidebar
{...makeDefaultProps({
days: [day],
places: [place],
tourPlaceIds: new Set([place.id]),
assignments: { '10': [assignment] },
onPlaceClick,
onRemoveAssignment,
onDeletePlace,
})}
/>
)
await user.click(screen.getByText('Ridge walk'))
expect(onPlaceClick).toHaveBeenCalled()
fireEvent.contextMenu(screen.getByText('Ridge walk'))
expect(screen.getByText(/Edit/i)).toBeInTheDocument()
expect(screen.getByText(/Leave out of route/i)).toBeInTheDocument()
expect(screen.queryByText(/Delete/i)).not.toBeInTheDocument()
await user.click(screen.getByText(/Remove from day/i))
expect(onRemoveAssignment).toHaveBeenCalledWith(10, 99)
expect(onDeletePlace).not.toHaveBeenCalled()
})
it('TRIP-PLAN preserves legacy route-backed Place Delete when Tours is disabled', async () => {
const user = userEvent.setup()
useAddonStore.setState({ addons: [{ id: 'tours', name: 'Tours', type: 'trip', icon: '', enabled: false }] })
const place = buildPlace({ id: 43, name: 'Old trail', route_geometry: '[[48,11],[48.01,11.02]]', tour_place_id: 43 })
const day = buildDay({ id: 10, date: '2025-06-01', title: 'Day 1' })
const assignment = buildAssignment({ id: 100, day_id: 10, order_index: 0, place })
const onDeletePlace = vi.fn()
render(<DayPlanSidebar {...makeDefaultProps({ days: [day], places: [place], tourPlaceIds: new Set([place.id]), assignments: { '10': [assignment] }, onDeletePlace })} />)
fireEvent.contextMenu(screen.getByText('Old trail'))
await user.click(screen.getByText(/Delete/i))
expect(onDeletePlace).toHaveBeenCalledWith(place.id)
})
// ── Note card editing (#2249) ─────────────────────────
it('FE-PLANNER-DAYPLAN-064: clicking a note row opens its edit modal', async () => {
@@ -78,6 +78,7 @@ interface DayPlanSidebarProps {
trip: Trip
days: Day[]
places: Place[]
tourPlaceIds?: ReadonlySet<number>
categories: Category[]
assignments: AssignmentsMap
selectedDayId: number | null
@@ -2190,7 +2191,14 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar(props: DayPlanSidebarP
safeHttpUrl(place.website) && { label: t('inspector.website'), icon: ExternalLink, onClick: () => window.open(safeHttpUrl(place.website)!, '_blank', 'noopener,noreferrer') },
collectionsEnabled && { label: t('inspector.saveToCollection'), icon: Bookmark, onClick: () => useSaveToCollectionStore.getState().open(placeToSaveTarget(place)) },
{ divider: true },
canEditPlaces && onDeletePlace && { label: t('common.delete'), icon: Trash2, danger: true, onClick: () => onDeletePlace(place.id) },
canEditPlaces &&
onDeletePlace &&
!(toursEnabled && (props.tourPlaceIds?.has(place.id) || place.tour_place_id === place.id)) && {
label: t('common.delete'),
icon: Trash2,
danger: true,
onClick: () => onDeletePlace(place.id),
},
]
return (