+
{/* from / to */}
@@ -568,6 +569,5 @@ export default function TransitSearchModal({ isOpen, onClose, day, days, places,
)}
-
)
}
diff --git a/client/src/components/Planner/TransportModal.test.tsx b/client/src/components/Planner/TransportModal.test.tsx
index 6abe36301..c258d75d0 100644
--- a/client/src/components/Planner/TransportModal.test.tsx
+++ b/client/src/components/Planner/TransportModal.test.tsx
@@ -366,4 +366,28 @@ describe('TransportModal', () => {
expect(payload.metadata?.transit).toBeUndefined();
expect(payload.endpoints.map((e: { role: string }) => e.role)).toEqual(['from', 'to']);
});
+
+ // ── Manual / Automated creation switch (#1065) ─────────────────────────────
+
+ it('FE-PLANNER-TRANSMODAL-022: creating shows the Manual/Automated switch; Automated opens the transit search', async () => {
+ render(
);
+ expect(screen.getByRole('button', { name: 'Manual transport' })).toBeInTheDocument();
+ await userEvent.click(screen.getByRole('button', { name: 'Automated transport' }));
+ // No day selected in defaultProps (days: []) — the pick-day hint shows.
+ expect(screen.getByText(/Pick a day/)).toBeInTheDocument();
+ // The manual form is gone in automated mode.
+ expect(screen.queryByPlaceholderText(/e\.g\. Lufthansa/i)).not.toBeInTheDocument();
+ });
+
+ it('FE-PLANNER-TRANSMODAL-023: initialAutomated opens straight in the transit search with the day preset', () => {
+ const days = [{ id: 10, trip_id: 1, day_number: 1, date: '2025-06-01', title: 'Day 1' }] as any;
+ render(
);
+ expect(screen.getAllByPlaceholderText('Search stop or station…')).toHaveLength(2);
+ });
+
+ it('FE-PLANNER-TRANSMODAL-024: editing shows no Manual/Automated switch', () => {
+ const res = buildReservation({ title: 'My Train', type: 'train' });
+ render(
);
+ expect(screen.queryByRole('button', { name: 'Automated transport' })).not.toBeInTheDocument();
+ });
});
diff --git a/client/src/components/Planner/TransportModal.tsx b/client/src/components/Planner/TransportModal.tsx
index 79e07733b..6738d85ce 100644
--- a/client/src/components/Planner/TransportModal.tsx
+++ b/client/src/components/Planner/TransportModal.tsx
@@ -13,11 +13,12 @@ import { useAddonStore } from '../../store/addonStore'
import { formatDate, splitReservationDateTime, resolveDayId } from '../../utils/formatters'
import { openFile } from '../../utils/fileDownload'
import apiClient from '../../api/client'
-import type { Day, Reservation, ReservationEndpoint, TripFile, BudgetItem } from '../../types'
+import type { Day, Place, Accommodation, Reservation, ReservationEndpoint, TripFile, BudgetItem } from '../../types'
import { parseReservationMetadata, orderedEndpoints } from '../../utils/flightLegs'
import { BookingCostsSection } from './BookingCostsSection'
import type { BookingExpenseRequest } from './BookingCostsSection.types'
import type { BookingReviewDraft } from './parsedItemToDraft'
+import TransitSearchPanel, { type PickedPlace } from './TransitSearchPanel'
import { typeToCostCategory } from '@trek/shared'
const TRANSPORT_TYPES = ['flight', 'train', 'bus', 'car', 'taxi', 'bicycle', 'cruise', 'ferry', 'transit', 'transport_other'] as const
@@ -96,7 +97,6 @@ const TYPE_OPTIONS = [
{ value: 'bicycle', labelKey: 'reservations.type.bicycle', Icon: Bike },
{ value: 'cruise', labelKey: 'reservations.type.cruise', Icon: Ship },
{ value: 'ferry', labelKey: 'reservations.type.ferry', Icon: Sailboat },
- { value: 'transit', labelKey: 'reservations.type.transit', Icon: TramFront },
{ value: 'transport_other', labelKey: 'reservations.type.transport_other', Icon: Route },
]
@@ -131,9 +131,16 @@ interface TransportModalProps {
// Pre-fill a brand-new transport booking from a parsed import item (review-
// before-save); like `reservation` for the form but stays in create mode.
prefill?: BookingReviewDraft | null
+ /** Data for the Automated (public transit) mode's quick picks. */
+ places?: Place[]
+ accommodations?: Accommodation[]
+ /** Open directly in the Automated public-transit mode (day-header tram button, "change route"). */
+ initialAutomated?: boolean
+ /** Pre-seed the transit search — used by "change route" on an existing journey. */
+ transitPrefill?: { from?: PickedPlace | null; to?: PickedPlace | null } | null
}
-export function TransportModal({ isOpen, onClose, onSave, reservation, days, selectedDayId, files = [], onFileUpload, onFileDelete, onOpenExpense, prefill = null }: TransportModalProps) {
+export function TransportModal({ isOpen, onClose, onSave, reservation, days, selectedDayId, files = [], onFileUpload, onFileDelete, onOpenExpense, prefill = null, places = [], accommodations = [], initialAutomated = false, transitPrefill = null }: TransportModalProps) {
const { t, locale } = useTranslation()
const toast = useToast()
const isBudgetEnabled = useAddonStore(s => s.isEnabled('budget'))
@@ -145,6 +152,8 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel
// the post-save handler knows to open the Costs editor for the saved booking.
const expenseIntentRef = useRef<{ editItem?: BudgetItem; create?: boolean } | null>(null)
const [form, setForm] = useState({ ...defaultForm })
+ // Manual vs Automated (public transit search) creation mode (#1065).
+ const [automated, setAutomated] = useState(false)
const [isSaving, setIsSaving] = useState(false)
const [fromPick, setFromPick] = useState
({})
const [toPick, setToPick] = useState({})
@@ -162,6 +171,7 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel
// Either way the init reads the same fields — `reservation` still decides
// edit-vs-create at submit time.
const src = (reservation ?? prefill) as Reservation | null
+ if (src) setAutomated(initialAutomated)
// On a review-import, seed the booking's Files with the parsed source document.
setPendingFiles(!reservation && prefill?._sourceFiles ? prefill._sourceFiles : [])
if (src) {
@@ -233,6 +243,7 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel
}
} else {
setForm({ ...defaultForm, start_day_id: selectedDayId ?? '', end_day_id: selectedDayId ?? '' })
+ setAutomated(initialAutomated)
setFromPick({})
setToPick({})
setWaypoints([emptyWaypoint(selectedDayId ?? ''), emptyWaypoint(selectedDayId ?? '')])
@@ -465,19 +476,62 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel
+ {!automated && (
+ )}
}
>
+ {/* Manual vs Automated creation switch (#1065) — creating only; editing a
+ journey re-enters via "change route" with the switch hidden. */}
+ {!reservation && (
+
+ {([['manual', t('transport.modeManual')], ['automated', t('transport.modeAutomated')]] as const).map(([m, label]) => {
+ const active = (m === 'automated') === automated
+ return (
+
+ )
+ })}
+
+ )}
+
+ {automated ? (
+ /* ── Automated: public transit search (#1065) ── */
+