diff --git a/client/src/components/Planner/ReservationModal.test.tsx b/client/src/components/Planner/ReservationModal.test.tsx
index beedadaee..98e2348d6 100644
--- a/client/src/components/Planner/ReservationModal.test.tsx
+++ b/client/src/components/Planner/ReservationModal.test.tsx
@@ -1,4 +1,4 @@
-// FE-PLANNER-RESMODAL-001 to FE-PLANNER-RESMODAL-109
+// FE-PLANNER-RESMODAL-001 to FE-PLANNER-RESMODAL-110
import { render, screen, waitFor, fireEvent, within, act } from '../../../tests/helpers/render';
import userEvent from '@testing-library/user-event';
import { http, HttpResponse } from 'msw';
@@ -1829,4 +1829,13 @@ describe('ReservationModal', () => {
expect(onSave.mock.calls[0][0].confirmation_number).toBe('PNR-NEW');
});
});
+
+ it('FE-PLANNER-RESMODAL-110: a refresh of the trip while the dialog is open keeps what the user typed (#3069)', async () => {
+ const { rerender } = render();
+ const title = screen.getByPlaceholderText(/e\.g\. Lufthansa/i) as HTMLInputElement;
+ await userEvent.type(title, 'Le Jules Verne');
+ // A socket reconnect re-reads the trip: days, places and stays arrive as new arrays.
+ rerender();
+ expect(title).toHaveValue('Le Jules Verne');
+ });
});
diff --git a/client/src/components/Planner/ReservationModal.tsx b/client/src/components/Planner/ReservationModal.tsx
index 92c2a6301..f70448c96 100644
--- a/client/src/components/Planner/ReservationModal.tsx
+++ b/client/src/components/Planner/ReservationModal.tsx
@@ -132,8 +132,12 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p
setForm({ ...EMPTY_FORM, assignment_id: defaultAssignmentId ?? '' })
setPendingFiles([])
}
+ // Keyed only on what opens a different form. Days, places and stays are read
+ // when the form is seeded; a refresh of the trip while the dialog is open (a
+ // socket reconnect re-reads it) hands in new arrays of the same data and must
+ // not reseed it and wipe what the user is typing (#3069).
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [reservation, prefill, isOpen, selectedDayId, defaultAssignmentId, days, places, accommodations])
+ }, [reservation, prefill, isOpen, selectedDayId, defaultAssignmentId])
const handleSubmit = async (e?: { preventDefault?: () => void }) => {
e?.preventDefault?.()
diff --git a/client/src/components/Planner/TransportModal.test.tsx b/client/src/components/Planner/TransportModal.test.tsx
index c6ca7d0c6..ea2e70f0b 100644
--- a/client/src/components/Planner/TransportModal.test.tsx
+++ b/client/src/components/Planner/TransportModal.test.tsx
@@ -1,4 +1,4 @@
-// FE-PLANNER-TRANSMODAL-001 to FE-PLANNER-TRANSMODAL-083
+// FE-PLANNER-TRANSMODAL-001 to FE-PLANNER-TRANSMODAL-085
import { render, screen, waitFor, fireEvent, within, act } from '../../../tests/helpers/render';
import userEvent from '@testing-library/user-event';
import { http, HttpResponse } from 'msw';
@@ -18,6 +18,7 @@ import {
buildAssignment,
buildReservation,
buildTripFile,
+ buildBudgetItem,
} from '../../../tests/helpers/factories';
import { TransportModal } from './TransportModal';
import type { Day, Reservation } from '../../types';
@@ -1725,4 +1726,22 @@ describe('TransportModal', () => {
const legs = onSave.mock.calls[0][0].metadata.legs as { confirmation_number?: string }[];
expect(legs.map(l => l.confirmation_number ?? null)).toEqual([null, 'SEG-TWO']);
});
+
+ it('FE-PLANNER-TRANSMODAL-084: a store refresh while the dialog is open keeps what the user typed (#3069)', async () => {
+ render();
+ const title = screen.getByPlaceholderText(/e\.g\. Lufthansa/i) as HTMLInputElement;
+ await userEvent.type(title, 'LH 400');
+ // A socket reconnect re-reads the trip: the costs arrive as a new array.
+ act(() => { useTripStore.setState({ budgetItems: [buildBudgetItem()] }); });
+ expect(title).toHaveValue('LH 400');
+ });
+
+ it('FE-PLANNER-TRANSMODAL-085: opening a different transport still fills the form with it', () => {
+ const first = buildReservation({ title: 'First flight', type: 'flight' });
+ const second = buildReservation({ title: 'Second flight', type: 'flight' });
+ const { rerender } = render();
+ expect(screen.getByDisplayValue('First flight')).toBeInTheDocument();
+ rerender();
+ expect(screen.getByDisplayValue('Second flight')).toBeInTheDocument();
+ });
});
diff --git a/client/src/components/Planner/TransportModal.tsx b/client/src/components/Planner/TransportModal.tsx
index ccd0290a2..858eaa82b 100644
--- a/client/src/components/Planner/TransportModal.tsx
+++ b/client/src/components/Planner/TransportModal.tsx
@@ -135,7 +135,10 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel
meta_seat: meta.seat || '',
}),
})
- }, [isOpen, reservation, prefill, selectedDayId, budgetItems])
+ // Keyed only on what opens a different form. A store refresh while the dialog
+ // is open (a socket reconnect re-reads the trip, a fellow traveller's cost) must
+ // not reseed it and wipe what the user is typing (#3069).
+ }, [isOpen, reservation, prefill, selectedDayId])
const handleSubmit = async (e?: React.FormEvent) => {
e?.preventDefault()