Files
TREK/client/src/components/Planner/useBookingExpenseIntent.ts
T
Maurice 1e674f040a fix(planner): a create expense click that saves nothing no longer opens the editor on the next save
In the desktop booking and transport dialogs, clicking "Create expense"
(or editing a linked expense) before the booking had a title saved
nothing, which is right, but the wish was kept. The next ordinary save
then opened the expense editor out of nowhere. The same happened after a
save that threw.

useBookingExpenseIntent stored the wish in a ref and only cleared it when
the form read it, which the desktop forms do after a successful save. The
hook now clears the wish once the save its click started has settled,
unless a newer click replaced it in the meantime. The phone sheets read
it at the start of the save and are unaffected.

Red tests:
- FE-PLANNER-RESMODAL-107 in ReservationModal.test.tsx: "expected
  "vi.fn()" to not be called at all, but actually been called 1 times".
- FE-PLANNER-BOOKINGEXPENSE-005 in useBookingExpenseIntent.test.ts:
  "expected { create: true } to be null".
- FE-PLANNER-BOOKINGEXPENSE-006: "expected { editItem: { id: 9, ... } }
  to be null".

No existing assertions changed.
2026-10-09 17:43:24 +02:00

57 lines
2.0 KiB
TypeScript

import { useRef } from 'react';
import type { BudgetItem } from '../../types';
import type { BookingExpenseIntent } from './bookingFormModel';
export interface BookingExpenseRemoval {
/** Deletes the expense linked to the booking. */
remove: (item: BudgetItem) => Promise<unknown>;
/** Called when the delete fails. */
onError: () => void;
}
/**
* The expense the user asked for from a booking form, held in a ref (not state) so
* the save started by the same click reads it. Behind the desktop booking and
* transport dialogs and the phone's booking and transport sheets: `create` (and on
* the desktop `edit`) note the wish and save; the form reads it with `take` at the
* point it always did and opens the editor for the saved booking.
*/
export function useBookingExpenseIntent(submit: () => Promise<unknown>, removal?: BookingExpenseRemoval) {
const intentRef = useRef<BookingExpenseIntent | null>(null);
// The wish belongs to the save its click starts. A save that returns early
// (no title) or throws never reads it, and it must not open the editor on
// the next, unrelated save. A newer click's wish is left alone.
const saveWith = (intent: BookingExpenseIntent) => {
intentRef.current = intent;
void submit().finally(() => {
if (intentRef.current === intent) intentRef.current = null;
});
};
return {
/** Forgets a wish left from an earlier opening. */
reset: () => {
intentRef.current = null;
},
/** Reads the wish and clears it. */
take: (): BookingExpenseIntent | null => {
const intent = intentRef.current;
intentRef.current = null;
return intent;
},
create: () => saveWith({ create: true }),
/** Saves the booking first, then opens the linked expense. */
edit: (item: BudgetItem) => saveWith({ editItem: item }),
remove: async (item: BudgetItem) => {
if (!removal) return;
try {
await removal.remove(item);
} catch {
removal.onError();
}
},
};
}