diff --git a/client/src/components/Planner/TransportModal.tsx b/client/src/components/Planner/TransportModal.tsx index 83ca6a58..259bf29e 100644 --- a/client/src/components/Planner/TransportModal.tsx +++ b/client/src/components/Planner/TransportModal.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react' +import { useState, useEffect, useMemo } from 'react' import { Plane, Train, Car, Ship } from 'lucide-react' import Modal from '../shared/Modal' import CustomSelect from '../shared/CustomSelect' @@ -7,6 +7,8 @@ import AirportSelect, { type Airport } from './AirportSelect' import LocationSelect, { type LocationPoint } from './LocationSelect' import { useTranslation } from '../../i18n' import { useToast } from '../shared/Toast' +import { useTripStore } from '../../store/tripStore' +import { useAddonStore } from '../../store/addonStore' import { formatDate } from '../../utils/formatters' import type { Day, Reservation, ReservationEndpoint } from '../../types' @@ -75,6 +77,8 @@ const defaultForm = { arrival_time: '', confirmation_number: '', notes: '', + price: '', + budget_category: '', meta_airline: '', meta_flight_number: '', meta_train_number: '', @@ -94,6 +98,13 @@ interface TransportModalProps { export function TransportModal({ isOpen, onClose, onSave, reservation, days, selectedDayId }: TransportModalProps) { const { t, locale } = useTranslation() const toast = useToast() + const isBudgetEnabled = useAddonStore(s => s.isEnabled('budget')) + const budgetItems = useTripStore(s => s.budgetItems) + const budgetCategories = useMemo(() => { + const cats = new Set() + budgetItems.forEach(i => { if (i.category) cats.add(i.category) }) + return Array.from(cats).sort() + }, [budgetItems]) const [form, setForm] = useState({ ...defaultForm }) const [isSaving, setIsSaving] = useState(false) const [fromPick, setFromPick] = useState({}) @@ -126,6 +137,8 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel meta_train_number: meta.train_number || '', meta_platform: meta.platform || '', meta_seat: meta.seat || '', + price: meta.price || '', + budget_category: (meta.budget_category && budgetItems.some(i => i.category === meta.budget_category)) ? meta.budget_category : '', }) if (type === 'flight') { setFromPick({ airport: airportFromEndpoint(from) || undefined }) @@ -139,7 +152,7 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel setFromPick({}) setToPick({}) } - }, [isOpen, reservation, selectedDayId]) + }, [isOpen, reservation, selectedDayId, budgetItems]) const set = (field: string, value: any) => setForm(prev => ({ ...prev, [field]: value })) @@ -173,6 +186,10 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel if (form.meta_platform) metadata.platform = form.meta_platform if (form.meta_seat) metadata.seat = form.meta_seat } + if (isBudgetEnabled) { + if (form.price) metadata.price = form.price + if (form.budget_category) metadata.budget_category = form.budget_category + } const startDate = startDay?.date ?? null const endDate = (endDay ?? startDay)?.date ?? null @@ -200,6 +217,11 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel endpoints, needs_review: false, } + if (isBudgetEnabled) { + (payload as any).create_budget_entry = form.price && parseFloat(form.price) > 0 + ? { total_price: parseFloat(form.price), category: form.budget_category || t(`reservations.type.${form.type}`) || 'Other' } + : { total_price: 0 } + } await onSave(payload) } catch (err: unknown) { toast.error(err instanceof Error ? err.message : t('common.unknownError')) @@ -422,6 +444,40 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel style={{ ...inputStyle, resize: 'none', lineHeight: 1.5 }} /> + {/* Price + Budget Category */} + {isBudgetEnabled && ( + <> +
+
+ + { const v = e.target.value; if (v === '' || /^\d*[.,]?\d{0,2}$/.test(v)) set('price', v.replace(',', '.')) }} + onPaste={e => { e.preventDefault(); let txt = e.clipboardData.getData('text').trim().replace(/[^\d.,-]/g, ''); const lc = txt.lastIndexOf(','), ld = txt.lastIndexOf('.'), dp = Math.max(lc, ld); if (dp > -1) { txt = txt.substring(0, dp).replace(/[.,]/g, '') + '.' + txt.substring(dp + 1) } else { txt = txt.replace(/[.,]/g, '') } set('price', txt) }} + placeholder="0.00" + style={inputStyle} /> +
+
+ + set('budget_category', v)} + options={[ + { value: '', label: t('reservations.budgetCategoryAuto') }, + ...budgetCategories.map(c => ({ value: c, label: c })), + ]} + placeholder={t('reservations.budgetCategoryAuto')} + size="sm" + /> +
+
+ {form.price && parseFloat(form.price) > 0 && ( +
+ {t('reservations.budgetHint')} +
+ )} + + )} + )