diff --git a/client/src/components/Layout/BottomNav.test.tsx b/client/src/components/Layout/BottomNav.test.tsx
index 0647ec6f..fadcbed2 100644
--- a/client/src/components/Layout/BottomNav.test.tsx
+++ b/client/src/components/Layout/BottomNav.test.tsx
@@ -1,4 +1,4 @@
-// FE-COMP-BOTTOMNAV-001 to FE-COMP-BOTTOMNAV-006
+// FE-COMP-BOTTOMNAV-001 to FE-COMP-BOTTOMNAV-010
vi.mock('../../api/websocket', () => ({
connect: vi.fn(),
@@ -30,6 +30,7 @@ const currentUser = buildUser({ id: 1, username: 'testuser', email: 'test@exampl
beforeEach(() => {
resetAllStores();
mockNavigate.mockClear();
+ sessionStorage.clear();
seedStore(useAuthStore, { user: currentUser, isAuthenticated: true });
});
@@ -79,4 +80,37 @@ describe('BottomNav', () => {
render();
expect(screen.queryByText('Foo Addon')).not.toBeInTheDocument();
});
+
+ // Context-aware "+" inside a trip — #1349
+ it('FE-COMP-BOTTOMNAV-007: in a trip, the "+" adds a place by default (plan tab)', async () => {
+ const user = userEvent.setup();
+ sessionStorage.setItem('trip-tab-42', 'plan');
+ render(, { initialEntries: ['/trips/42'] });
+ await user.click(screen.getByRole('button', { name: 'Add Place/Activity' }));
+ expect(mockNavigate).toHaveBeenCalledWith('/trips/42?create=place');
+ });
+
+ it('FE-COMP-BOTTOMNAV-008: Bookings tab → "+" creates a reservation', async () => {
+ const user = userEvent.setup();
+ sessionStorage.setItem('trip-tab-42', 'buchungen');
+ render(, { initialEntries: ['/trips/42'] });
+ await user.click(screen.getByRole('button', { name: 'Manual Booking' }));
+ expect(mockNavigate).toHaveBeenCalledWith('/trips/42?create=reservation');
+ });
+
+ it('FE-COMP-BOTTOMNAV-009: Transports tab → "+" creates a transport', async () => {
+ const user = userEvent.setup();
+ sessionStorage.setItem('trip-tab-42', 'transports');
+ render(, { initialEntries: ['/trips/42'] });
+ await user.click(screen.getByRole('button', { name: 'Manual Transport' }));
+ expect(mockNavigate).toHaveBeenCalledWith('/trips/42?create=transport');
+ });
+
+ it('FE-COMP-BOTTOMNAV-010: Costs tab → "+" creates an expense', async () => {
+ const user = userEvent.setup();
+ sessionStorage.setItem('trip-tab-42', 'finanzplan');
+ render(, { initialEntries: ['/trips/42'] });
+ await user.click(screen.getByRole('button', { name: 'Add expense' }));
+ expect(mockNavigate).toHaveBeenCalledWith('/trips/42?create=expense');
+ });
});
diff --git a/client/src/components/Layout/BottomNav.tsx b/client/src/components/Layout/BottomNav.tsx
index 04b74c6a..5eb75cfc 100644
--- a/client/src/components/Layout/BottomNav.tsx
+++ b/client/src/components/Layout/BottomNav.tsx
@@ -25,12 +25,15 @@ function useCreateAction(): { label: string; run: () => void } {
const onJourneyList = useMatch('/journey')
if (inTrip) {
- // On the Costs tab the "+" adds an expense; otherwise it adds a place.
- const tripTab = typeof sessionStorage !== 'undefined' ? sessionStorage.getItem(`trip-tab-${inTrip.params.id}`) : null
- if (tripTab === 'finanzplan') {
- return { label: t('costs.addExpense'), run: () => navigate(`/trips/${inTrip.params.id}?create=expense`) }
- }
- return { label: t('places.addPlace'), run: () => navigate(`/trips/${inTrip.params.id}?create=place`) }
+ // The "+" is context-aware per active tab: Bookings → reservation,
+ // Transports → transport, Costs → expense. Tabs without a create modal
+ // (lists / files / collab) fall through to adding a place. #1349
+ const id = inTrip.params.id
+ const tripTab = typeof sessionStorage !== 'undefined' ? sessionStorage.getItem(`trip-tab-${id}`) : null
+ if (tripTab === 'finanzplan') return { label: t('costs.addExpense'), run: () => navigate(`/trips/${id}?create=expense`) }
+ if (tripTab === 'buchungen') return { label: t('reservations.addManual'), run: () => navigate(`/trips/${id}?create=reservation`) }
+ if (tripTab === 'transports') return { label: t('transport.addManual'), run: () => navigate(`/trips/${id}?create=transport`) }
+ return { label: t('places.addPlace'), run: () => navigate(`/trips/${id}?create=place`) }
}
if (inJourney) {
return { label: t('journey.detail.addEntry'), run: () => navigate(`/journey/${inJourney.params.id}?create=entry`) }
diff --git a/client/src/pages/tripPlanner/useTripPlanner.ts b/client/src/pages/tripPlanner/useTripPlanner.ts
index 3017628e..7a354909 100644
--- a/client/src/pages/tripPlanner/useTripPlanner.ts
+++ b/client/src/pages/tripPlanner/useTripPlanner.ts
@@ -161,6 +161,20 @@ export function useTripPlanner() {
const [showTransportModal, setShowTransportModal] = useState(false)
const [editingTransport, setEditingTransport] = useState(null)
const [transportModalDayId, setTransportModalDayId] = useState(null)
+
+ // The bottom-nav "+" is context-aware per tab: on the Bookings / Transports tabs
+ // it opens the booking / transport modal via ?create=reservation|transport
+ // (place is handled above, expense in CostsPanel). #1349
+ useEffect(() => {
+ const intent = searchParams.get('create')
+ if (intent === 'reservation') {
+ setEditingReservation(null); setBookingForAssignmentId(null); setShowReservationModal(true)
+ setSearchParams(p => { p.delete('create'); return p }, { replace: true })
+ } else if (intent === 'transport') {
+ setEditingTransport(null); setTransportModalDayId(null); setShowTransportModal(true)
+ setSearchParams(p => { p.delete('create'); return p }, { replace: true })
+ }
+ }, [searchParams])
// Review-before-save import: each parsed item pre-fills the normal edit modal so
// the user checks/fixes it, then saves. A ref drives the queue (no stale closures).
const [reservationPrefill, setReservationPrefill] = useState(null)