From f3077ce4f00168f0583feb7cd48534c7d7fc3059 Mon Sep 17 00:00:00 2001 From: Maurice Date: Fri, 26 Jun 2026 16:27:06 +0200 Subject: [PATCH] fix(import): preview the parsed cost as linked in the review modal During the per-item import review the booking isn't saved yet, so the Costs section showed an empty 'Create expense' even though a linked cost will be created on save. Show the parsed price (amount + category) as the pending linked expense so the user can verify it up front. Reuses existing i18n keys. --- .../Planner/BookingCostsSection.tsx | 23 ++++++++++++++++++- .../components/Planner/ReservationModal.tsx | 8 +++++++ .../src/components/Planner/TransportModal.tsx | 8 +++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/client/src/components/Planner/BookingCostsSection.tsx b/client/src/components/Planner/BookingCostsSection.tsx index 1683b895..2938b178 100644 --- a/client/src/components/Planner/BookingCostsSection.tsx +++ b/client/src/components/Planner/BookingCostsSection.tsx @@ -12,8 +12,10 @@ import type { BudgetItem } from '../../types' * button (the modal saves the booking first, then opens the full Costs editor); * once linked it shows the expense with edit / remove actions. */ -export function BookingCostsSection({ reservationId, onCreate, onEdit, onRemove }: { +export function BookingCostsSection({ reservationId, pendingExpense, onCreate, onEdit, onRemove }: { reservationId: number | null + /** A cost parsed from an import that will be linked on save — previewed before the booking exists. */ + pendingExpense?: { total_price: number; currency?: string | null; category: string } | null onCreate: () => void onEdit: (item: BudgetItem) => void onRemove: (item: BudgetItem) => void @@ -27,6 +29,25 @@ export function BookingCostsSection({ reservationId, onCreate, onEdit, onRemove const labelCls = 'block text-[11px] font-semibold uppercase tracking-[0.08em] text-content-faint mb-[6px]' + // Import review (booking not saved yet): preview the parsed cost that will be linked on save. + if (!linked && pendingExpense && pendingExpense.total_price > 0) { + const meta = catMeta(pendingExpense.category) + const Icon = meta.Icon + return ( +
+ +
+ +
+
{t(meta.labelKey)}
+
{t('reservations.createExpenseHint')}
+
+ {formatMoney(pendingExpense.total_price, pendingExpense.currency || base, locale)} +
+
+ ) + } + if (linked) { const meta = catMeta(linked.category) const Icon = meta.Icon diff --git a/client/src/components/Planner/ReservationModal.tsx b/client/src/components/Planner/ReservationModal.tsx index 2518f2dd..c5bae7b1 100644 --- a/client/src/components/Planner/ReservationModal.tsx +++ b/client/src/components/Planner/ReservationModal.tsx @@ -299,6 +299,13 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p try { await deleteBudgetItem(Number(tripId), item.id) } catch { toast.error(t('common.unknownError')) } } + // On an import review (not yet saved), preview the parsed price as the cost that will be linked. + const prefillMeta = prefill?.metadata && typeof prefill.metadata === 'object' ? (prefill.metadata as Record) : null + const prefillPrice = Number(prefillMeta?.price) + const pendingExpense = !reservation && Number.isFinite(prefillPrice) && prefillPrice > 0 + ? { total_price: prefillPrice, currency: (prefillMeta?.priceCurrency as string | null) ?? null, category: typeToCostCategory(form.type) } + : null + const handleFileChange = async (e) => { const file = (e.target as HTMLInputElement).files?.[0] if (!file) return @@ -685,6 +692,7 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p {isBudgetEnabled && ( ) : null + const prefillPrice = Number(prefillMeta?.price) + const pendingExpense = !reservation && Number.isFinite(prefillPrice) && prefillPrice > 0 + ? { total_price: prefillPrice, currency: (prefillMeta?.priceCurrency as string | null) ?? null, category: typeToCostCategory(form.type) } + : null + const handleFileChange = async (e: React.ChangeEvent) => { const file = e.target.files?.[0] if (!file) return @@ -740,6 +747,7 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel {isBudgetEnabled && (