mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-28 01:31:47 +00:00
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.
This commit is contained in:
@@ -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 (
|
||||
<div>
|
||||
<label className={labelCls}>{t('reservations.linkedExpense')}</label>
|
||||
<div className="bg-surface-secondary border border-edge" style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 12px', borderRadius: 10 }}>
|
||||
<span style={{ width: 26, height: 26, borderRadius: 7, display: 'grid', placeItems: 'center', background: meta.color + '22', color: meta.color, flexShrink: 0 }}><Icon size={14} /></span>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div className="text-content" style={{ fontSize: 14, fontWeight: 600 }}>{t(meta.labelKey)}</div>
|
||||
<div className="text-content-faint" style={{ fontSize: 12 }}>{t('reservations.createExpenseHint')}</div>
|
||||
</div>
|
||||
<span className="text-content" style={{ fontSize: 14, fontWeight: 700, flexShrink: 0 }}>{formatMoney(pendingExpense.total_price, pendingExpense.currency || base, locale)}</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (linked) {
|
||||
const meta = catMeta(linked.category)
|
||||
const Icon = meta.Icon
|
||||
|
||||
@@ -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<string, unknown>) : 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 && (
|
||||
<BookingCostsSection
|
||||
reservationId={reservation?.id ?? null}
|
||||
pendingExpense={pendingExpense}
|
||||
onCreate={handleCreateExpense}
|
||||
onEdit={handleEditExpense}
|
||||
onRemove={handleRemoveExpense}
|
||||
|
||||
@@ -380,6 +380,13 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel
|
||||
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<string, unknown>) : 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<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0]
|
||||
if (!file) return
|
||||
@@ -740,6 +747,7 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel
|
||||
{isBudgetEnabled && (
|
||||
<BookingCostsSection
|
||||
reservationId={reservation?.id ?? null}
|
||||
pendingExpense={pendingExpense}
|
||||
onCreate={handleCreateExpense}
|
||||
onEdit={handleEditExpense}
|
||||
onRemove={handleRemoveExpense}
|
||||
|
||||
Reference in New Issue
Block a user