From 64b86c2403e0f2e1cabe80fbda1b0017c4d09403 Mon Sep 17 00:00:00 2001 From: Maurice Date: Fri, 13 Nov 2026 09:24:06 +0100 Subject: [PATCH] feat(planner): edit the trip from its settings in the planner (#2811) The trip form was mounted in the planner but nothing opened it, so title, dates, cover and currency could only be changed from My Trips. Trip settings now lists Edit Trip at the foot of its areas for anyone with trip_edit; it swaps the dialog for the trip form. The phone already has Edit Trip in the More sheet. --- .../Trips/TripMembersModal.test.tsx | 24 +++++++++- .../src/components/Trips/TripMembersModal.tsx | 2 + .../src/components/Trips/TripShareDialog.tsx | 47 ++++++++++++++----- client/src/pages/TripPlannerPage.tsx | 4 +- .../pages/TripPlannerPage.wiring.fixture.ts | 1 + .../src/pages/TripPlannerPage.wiring.test.tsx | 10 +++- .../tripPlanner/usePlannerDialogs.test.ts | 12 ++++- .../pages/tripPlanner/usePlannerDialogs.ts | 8 +++- .../src/pages/tripPlanner/useTripPlanner.ts | 4 +- client/tests/helpers/mobileTrip.ts | 1 + shared/src/i18n/ar/trip.ts | 1 + shared/src/i18n/az/trip.ts | 1 + shared/src/i18n/br/trip.ts | 1 + shared/src/i18n/ca/trip.ts | 1 + shared/src/i18n/cs/trip.ts | 1 + shared/src/i18n/de/trip.ts | 1 + shared/src/i18n/en/trip.ts | 1 + shared/src/i18n/es/trip.ts | 1 + shared/src/i18n/et/trip.ts | 1 + shared/src/i18n/fr/trip.ts | 1 + shared/src/i18n/gr/trip.ts | 1 + shared/src/i18n/he/trip.ts | 1 + shared/src/i18n/hu/trip.ts | 1 + shared/src/i18n/id/trip.ts | 1 + shared/src/i18n/it/trip.ts | 1 + shared/src/i18n/ja/trip.ts | 1 + shared/src/i18n/ko/trip.ts | 1 + shared/src/i18n/nl/trip.ts | 1 + shared/src/i18n/pl/trip.ts | 1 + shared/src/i18n/ru/trip.ts | 1 + shared/src/i18n/sk/trip.ts | 1 + shared/src/i18n/sv/trip.ts | 1 + shared/src/i18n/th/trip.ts | 1 + shared/src/i18n/tr/trip.ts | 1 + shared/src/i18n/uk/trip.ts | 1 + shared/src/i18n/vi/trip.ts | 1 + shared/src/i18n/zh-TW/trip.ts | 1 + shared/src/i18n/zh/trip.ts | 1 + wiki/Currencies.md | 2 +- wiki/Trip-Settings.md | 2 + 40 files changed, 123 insertions(+), 22 deletions(-) diff --git a/client/src/components/Trips/TripMembersModal.test.tsx b/client/src/components/Trips/TripMembersModal.test.tsx index 6abe9e1f8..157d7b640 100644 --- a/client/src/components/Trips/TripMembersModal.test.tsx +++ b/client/src/components/Trips/TripMembersModal.test.tsx @@ -1,6 +1,6 @@ -// FE-COMP-MEMBERS-001 to FE-COMP-MEMBERS-061 +// FE-COMP-MEMBERS-001 to FE-COMP-MEMBERS-063 import type { Mock } from 'vitest'; -import { act, render, screen, fireEvent, waitFor } from '../../../tests/helpers/render'; +import { act, render, screen, fireEvent, waitFor, within } from '../../../tests/helpers/render'; import userEvent from '@testing-library/user-event'; import { delay, http, HttpResponse } from 'msw'; import { server } from '../../../tests/helpers/msw/server'; @@ -1107,4 +1107,24 @@ describe('TripMembersModal', () => { expect(writeText).toHaveBeenCalledWith(expect.stringContaining('/join/inv9')); view.unmount(); }); + + it('FE-COMP-MEMBERS-062: Edit Trip in the side column hands over to the trip form (#2811)', async () => { + const user = userEvent.setup(); + const onEditTrip = vi.fn(); + render(); + + // The column stands for it even where People is the only area. + const nav = await screen.findByRole('navigation', { name: 'Trip settings' }); + await user.click(within(nav).getByRole('button', { name: 'Edit Trip' })); + + expect(onEditTrip).toHaveBeenCalledTimes(1); + expect(within(nav).getByText('Title, dates, cover, description and currency.')).toBeInTheDocument(); + }); + + it('FE-COMP-MEMBERS-063: without the right to edit the trip, the dialog offers no way to its form', async () => { + render(); + + expect(await screen.findByText('owner')).toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'Edit Trip' })).toBeNull(); + }); }); diff --git a/client/src/components/Trips/TripMembersModal.tsx b/client/src/components/Trips/TripMembersModal.tsx index 8ff93705b..d9d8a228a 100644 --- a/client/src/components/Trips/TripMembersModal.tsx +++ b/client/src/components/Trips/TripMembersModal.tsx @@ -8,6 +8,8 @@ interface TripMembersModalProps { /** Called after the roster changes (guest/member added, renamed or removed) so the * planner can refresh its members for Costs participants, Collab, etc. */ onMembersChanged?: () => void + /** Opens the trip's form from the dialog, for someone who may edit the trip (#2811). */ + onEditTrip?: () => void } /** Share: the trip's members, guests and links. The phone has its own sheet (MTripSettingsSheet) on the same hooks. */ diff --git a/client/src/components/Trips/TripShareDialog.tsx b/client/src/components/Trips/TripShareDialog.tsx index e08913fba..fcf168fbc 100644 --- a/client/src/components/Trips/TripShareDialog.tsx +++ b/client/src/components/Trips/TripShareDialog.tsx @@ -18,6 +18,8 @@ interface TripShareDialogProps { tripId: number tripTitle: string onMembersChanged?: () => void + /** Hands over to the trip's own form; left out for someone who may not edit the trip (#2811). */ + onEditTrip?: () => void } /** A question the dialog asks before it acts, answered in a ConfirmDialog over it. */ @@ -35,6 +37,8 @@ const BADGE = 'inline-flex flex-none items-center gap-1 rounded-full px-2 py-[2p /** A fixed height on a wide screen, so the dialog does not jump when another area is picked. */ const BODY = 'flex min-h-0 flex-1 max-md:flex-col md:h-[min(640px,calc(100dvh-240px))]' const NAV = 'flex flex-none gap-1 border-edge-faint bg-surface-secondary p-3 md:w-[236px] md:flex-col md:border-e max-md:overflow-x-auto max-md:border-b' +const NAV_ENTRY = 'flex flex-none items-center gap-2.5 rounded-[12px] px-2.5 py-2 text-start transition-colors' +const NAV_ENTRY_OFF = 'text-content-muted hover:bg-surface-hover hover:text-content' const DANGER_BUTTON = 'inline-flex items-center gap-1.5 rounded-[10px] bg-surface-card px-3.5 py-2 font-medium text-danger shadow-sm ring-1 ring-edge-faint hover:bg-danger-soft disabled:cursor-default disabled:opacity-50' /** @@ -54,7 +58,7 @@ const AREAS: { id: Area; icon: (on: boolean) => ReactNode; labelKey: string; hin { id: 'journal', icon: () => , labelKey: 'trip.journal.title', hintKey: 'trip.journal.hint' }, ] -function OpenDialog({ onClose, tripId, tripTitle, onMembersChanged }: TripShareDialogProps) { +function OpenDialog({ onClose, tripId, tripTitle, onMembersChanged, onEditTrip }: TripShareDialogProps) { const { t } = useTranslation() const labelId = useId() const m = useTripMembers({ isOpen: true, tripId, onClose, onMembersChanged }) @@ -65,13 +69,15 @@ function OpenDialog({ onClose, tripId, tripTitle, onMembersChanged }: TripShareD const current = areas.find(a => a.id === area) ?? areas[0] const panelOf = (id: Area) => areas.find(a => a.id === id) const persons = `${m.realMembers.length} ${t('members.persons', { count: m.realMembers.length })}` + // The way to the trip's form needs the side nav even where People is the only area. + const withNav = areas.length > 1 || !!onEditTrip return ( <> 1 ? 'wide' : 'detail'} + width={withNav ? 'wide' : 'detail'} blocked={!!ask} bodyClassName={BODY} header={( @@ -91,10 +97,10 @@ function OpenDialog({ onClose, tripId, tripTitle, onMembersChanged }: TripShareD /> )} > - {areas.length > 1 && } + {withNav && }
{/* Every area stays mounted, so its links and lists are loaded by the time it is picked. */} - 1}> + {m.canManageMembers && } {(m.isCurrentOwner || m.guests.length > 0) && } @@ -124,8 +130,11 @@ function OpenDialog({ onClose, tripId, tripTitle, onMembersChanged }: TripShareD ) } -/** The areas down the side on a wide screen, a row of pills above the content on a narrow one. */ -function AreaNav({ areas, current, onPick }: { areas: typeof AREAS; current: Area; onPick: (area: Area) => void }) { +/** + * The areas down the side on a wide screen, a row of pills above the content on a narrow one. + * Edit trip sits at the foot of the column: it opens the trip's form rather than an area of its own. + */ +function AreaNav({ areas, current, onPick, onEditTrip }: { areas: typeof AREAS; current: Area; onPick: (area: Area) => void; onEditTrip?: () => void }) { const { t } = useTranslation() return ( ) } +/** An entry's tile, its name and, on a wide screen, the line under the name. */ +function NavEntryText({ icon, on, label, hint }: { icon: ReactNode; on: boolean; label: string; hint: string }) { + return ( + <> + {icon} + + {label} + {hint} + + + ) +} + /** One area: its name and what it is for, then its parts. Hidden rather than unmounted while another one shows. */ function AreaPanel({ area, shown, titled, children }: { area: (typeof AREAS)[number] | undefined; shown: boolean; titled: boolean; children: ReactNode }) { const { t } = useTranslation() diff --git a/client/src/pages/TripPlannerPage.tsx b/client/src/pages/TripPlannerPage.tsx index 8b3c6ca05..d6053c365 100644 --- a/client/src/pages/TripPlannerPage.tsx +++ b/client/src/pages/TripPlannerPage.tsx @@ -92,7 +92,7 @@ function TripPlannerPageDesktop(): React.ReactElement | null { highlightedAlternative, setHighlightedAlternative, moveRoadtripStopToDay, dropPoiOnRoute, - showTripForm, setShowTripForm, showMembersModal, setShowMembersModal, + showTripForm, setShowTripForm, showMembersModal, setShowMembersModal, editTripFromSettings, showReservationModal, setShowReservationModal, editingReservation, setEditingReservation, showBookingImport, setShowBookingImport, bookingImportKind, setBookingImportKind, bookingImportAvailable, airTrailAvailable, showAirTrailImport, setShowAirTrailImport, @@ -830,7 +830,7 @@ function TripPlannerPageDesktop(): React.ReactElement | null { trip={trip} onCoverUpdate={applyTripCoverUpdate} /> - setShowMembersModal(false)} tripId={tripId} tripTitle={trip?.title} onMembersChanged={refreshMembers} /> + setShowMembersModal(false)} tripId={tripId} tripTitle={trip?.title} onMembersChanged={refreshMembers} onEditTrip={can('trip_edit', trip) ? editTripFromSettings : undefined} /> { if (importReviewActive) { advanceImportReview() } else { setShowReservationModal(false); setEditingReservation(null); setBookingForAssignmentId(null) } }} onSave={async (data, steps) => { const r = await handleSaveReservation(data, steps); if (importReviewActive && r) advanceImportReview(); return r }} reservation={editingReservation} prefill={reservationPrefill} days={days} places={places} assignments={assignments} selectedDayId={selectedDayId} files={files} onFileUpload={canUploadFiles ? (fd) => tripActions.addFile(tripId, fd) : undefined} onFileDelete={(id) => tripActions.deleteFile(tripId, id)} accommodations={tripAccommodations} defaultAssignmentId={bookingForAssignmentId} onOpenExpense={openBookingExpense} tripMembers={tripMembers} /> {showTransportModal && ( diff --git a/client/src/pages/TripPlannerPage.wiring.fixture.ts b/client/src/pages/TripPlannerPage.wiring.fixture.ts index ca43e059b..8b77f8622 100644 --- a/client/src/pages/TripPlannerPage.wiring.fixture.ts +++ b/client/src/pages/TripPlannerPage.wiring.fixture.ts @@ -206,6 +206,7 @@ function dialogState(): HookState { setShowTripForm: vi.fn(), showMembersModal: false, setShowMembersModal: vi.fn(), + editTripFromSettings: vi.fn(), showReservationModal: false, setShowReservationModal: vi.fn(), editingReservation: null, diff --git a/client/src/pages/TripPlannerPage.wiring.test.tsx b/client/src/pages/TripPlannerPage.wiring.test.tsx index a07238bca..f588ea373 100644 --- a/client/src/pages/TripPlannerPage.wiring.test.tsx +++ b/client/src/pages/TripPlannerPage.wiring.test.tsx @@ -1,4 +1,4 @@ -// FE-PAGE-TPW-001 to FE-PAGE-TPW-074 +// FE-PAGE-TPW-001 to FE-PAGE-TPW-075 // // The planner page is a wiring container: everything stateful lives in // useTripPlanner (covered in src/pages/tripPlanner/useTripPlanner.test.tsx). @@ -1552,6 +1552,14 @@ describe('TripPlannerPage — modals', () => { expect(hookState.setShowMembersModal).toHaveBeenCalledWith(false) }) + it('FE-PAGE-TPW-075: the members modal leads to the trip form only for someone who may edit the trip (#2811)', () => { + renderPage() + expect(props('membersModal').onEditTrip).toBe(hookState.editTripFromSettings) + + renderPage({ can: vi.fn((action: string) => action !== 'trip_edit') }) + expect(props('membersModal').onEditTrip).toBeUndefined() + }) + it('FE-PAGE-TPW-050: closing the booking modal outside a review resets the editor', async () => { renderPage() diff --git a/client/src/pages/tripPlanner/usePlannerDialogs.test.ts b/client/src/pages/tripPlanner/usePlannerDialogs.test.ts index 696702641..bcb69e8e4 100644 --- a/client/src/pages/tripPlanner/usePlannerDialogs.test.ts +++ b/client/src/pages/tripPlanner/usePlannerDialogs.test.ts @@ -1,4 +1,4 @@ -// FE-TP-DIALOGS-001 to FE-TP-DIALOGS-012 +// FE-TP-DIALOGS-001 to FE-TP-DIALOGS-013 // // The planner's dialogs and the URL intents that open them, driven straight through // usePlannerDialogs. The search params come in as options, so each test owns the URL @@ -248,4 +248,14 @@ describe('usePlannerDialogs', () => { act(() => { result.current.changeTransitRoute(buildReservation({ id: 15, type: 'transit' })) }) expect(result.current.transitPrefill).toEqual({ from: null, to: null }) }) + + it('FE-TP-DIALOGS-013: editing the trip from its settings swaps the settings for the trip form (#2811)', () => { + const { result } = renderDialogs() + act(() => { result.current.setShowMembersModal(true) }) + + act(() => { result.current.editTripFromSettings() }) + + expect(result.current.showMembersModal).toBe(false) + expect(result.current.showTripForm).toBe(true) + }) }) diff --git a/client/src/pages/tripPlanner/usePlannerDialogs.ts b/client/src/pages/tripPlanner/usePlannerDialogs.ts index bdb2840a1..44ac069ff 100644 --- a/client/src/pages/tripPlanner/usePlannerDialogs.ts +++ b/client/src/pages/tripPlanner/usePlannerDialogs.ts @@ -104,6 +104,12 @@ export function usePlannerDialogs(options: PlannerDialogsOptions) { }, [searchParams]) const [showTripForm, setShowTripForm] = useState(false) const [showMembersModal, setShowMembersModal] = useState(false) + // The trip settings hand over to the trip's form rather than stack a second dialog + // on their own: the planner's way to the title, dates, cover and currency (#2811). + const editTripFromSettings = useCallback(() => { + setShowMembersModal(false) + setShowTripForm(true) + }, []) const [showReservationModal, setShowReservationModal] = useState(false) const [editingReservation, setEditingReservation] = useState(null) const [showBookingImport, setShowBookingImport] = useState(false) @@ -167,7 +173,7 @@ export function usePlannerDialogs(options: PlannerDialogsOptions) { editingAssignmentId, setEditingAssignmentId, placeFormDayId, setPlaceFormDayId, placeFormPosition, setPlaceFormPosition, serviceStopForm, setServiceStopForm, serviceStopKind, setServiceStopKind, stopDraft, setStopDraft, stayRelease, setStayRelease, reservationModalDayId, setReservationModalDayId, - showTripForm, setShowTripForm, showMembersModal, setShowMembersModal, + showTripForm, setShowTripForm, showMembersModal, setShowMembersModal, editTripFromSettings, showReservationModal, setShowReservationModal, editingReservation, setEditingReservation, showBookingImport, setShowBookingImport, bookingImportKind, setBookingImportKind, bookingImportAvailable, setBookingImportAvailable, airTrailAvailable, showAirTrailImport, setShowAirTrailImport, diff --git a/client/src/pages/tripPlanner/useTripPlanner.ts b/client/src/pages/tripPlanner/useTripPlanner.ts index 7221e0fdf..6d46d59cd 100644 --- a/client/src/pages/tripPlanner/useTripPlanner.ts +++ b/client/src/pages/tripPlanner/useTripPlanner.ts @@ -360,7 +360,7 @@ export function useTripPlanner() { // The dialog state that only passes through to the shells. const { showPlaceForm, prefillCoords, reservationModalDayId, setReservationModalDayId, - showTripForm, setShowTripForm, showMembersModal, setShowMembersModal, showReservationModal, + showTripForm, setShowTripForm, showMembersModal, setShowMembersModal, editTripFromSettings, showReservationModal, showBookingImport, setShowBookingImport, bookingImportKind, setBookingImportKind, bookingImportAvailable, airTrailAvailable, showAirTrailImport, setShowAirTrailImport, bookingForAssignmentId, setBookingForAssignmentId, showTransportModal, transportModalDayId, transportModalAutomated, setTransportModalAutomated, @@ -749,7 +749,7 @@ export function useTripPlanner() { highlightedAlternative, setHighlightedAlternative, moveRoadtripStopToDay, dropPoiOnRoute, - showTripForm, setShowTripForm, showMembersModal, setShowMembersModal, + showTripForm, setShowTripForm, showMembersModal, setShowMembersModal, editTripFromSettings, showReservationModal, setShowReservationModal, editingReservation, setEditingReservation, showBookingImport, setShowBookingImport, bookingImportKind, setBookingImportKind, bookingImportAvailable, airTrailAvailable, showAirTrailImport, setShowAirTrailImport, diff --git a/client/tests/helpers/mobileTrip.ts b/client/tests/helpers/mobileTrip.ts index 37eb71438..7e988ee53 100644 --- a/client/tests/helpers/mobileTrip.ts +++ b/client/tests/helpers/mobileTrip.ts @@ -134,6 +134,7 @@ export function buildPlanner(overrides: Partial = {}): TripPlanner setShowTripForm: vi.fn(), showMembersModal: false, setShowMembersModal: vi.fn(), + editTripFromSettings: vi.fn(), showReservationModal: false, setShowReservationModal: vi.fn(), editingReservation: null, diff --git a/shared/src/i18n/ar/trip.ts b/shared/src/i18n/ar/trip.ts index 1dc714aca..30451a1d6 100644 --- a/shared/src/i18n/ar/trip.ts +++ b/shared/src/i18n/ar/trip.ts @@ -127,6 +127,7 @@ const trip: TranslationStrings = { 'trip.settings.peopleHint': 'من المشاركون في هذه الرحلة، والضيوف بلا حساب.', 'trip.settings.sharing': 'المشاركة', 'trip.settings.sharingHint': 'رابط للقراءة فقط لأي شخص، ورابط دعوة للانضمام.', + 'trip.settings.editTripHint': 'العنوان والتواريخ والغلاف والوصف والعملة.', 'trip.tabs.journal': 'اليوميات', 'trip.journal.title': 'اليوميات', 'trip.journal.hint': 'اربط إحدى يومياتك بهذه الرحلة. من تمنحه الوصول يفتحها هنا مباشرة في تبويب اليوميات، بشكل حي.', diff --git a/shared/src/i18n/az/trip.ts b/shared/src/i18n/az/trip.ts index c40dae85b..100dfe5dd 100644 --- a/shared/src/i18n/az/trip.ts +++ b/shared/src/i18n/az/trip.ts @@ -118,6 +118,7 @@ const trip: TranslationStrings = { 'trip.settings.peopleHint': 'Bu səyahətdə kimlərin olduğu və hesabı olmayan qonaqlar.', 'trip.settings.sharing': 'Paylaşma', 'trip.settings.sharingHint': 'Hər kəs üçün yalnız oxunan keçid və qoşulmaq üçün dəvət keçidi.', + 'trip.settings.editTripHint': 'Ad, tarixlər, üz qabığı, təsvir və valyuta.', 'trip.tabs.journal': 'Gündəlik', 'trip.journal.title': 'Gündəlik', 'trip.journal.hint': diff --git a/shared/src/i18n/br/trip.ts b/shared/src/i18n/br/trip.ts index c7e4cc9e0..acc23bdda 100644 --- a/shared/src/i18n/br/trip.ts +++ b/shared/src/i18n/br/trip.ts @@ -107,6 +107,7 @@ const trip: TranslationStrings = { 'trip.settings.peopleHint': 'Quem está nesta viagem e os convidados sem conta.', 'trip.settings.sharing': 'Compartilhar', 'trip.settings.sharingHint': 'Um link somente leitura para qualquer pessoa e um link de convite para entrar.', + 'trip.settings.editTripHint': 'Título, datas, capa, descrição e moeda.', 'trip.tabs.journal': 'Diário', 'trip.journal.title': 'Diário', 'trip.journal.hint': diff --git a/shared/src/i18n/ca/trip.ts b/shared/src/i18n/ca/trip.ts index 70e9e79d2..22bb1e635 100644 --- a/shared/src/i18n/ca/trip.ts +++ b/shared/src/i18n/ca/trip.ts @@ -107,6 +107,7 @@ const trip: TranslationStrings = { 'trip.settings.peopleHint': 'Qui és en aquest viatge i els convidats sense compte.', 'trip.settings.sharing': 'Compartir', 'trip.settings.sharingHint': "Un enllaç de només lectura per a tothom i un enllaç d'invitació per unir-se.", + 'trip.settings.editTripHint': 'Títol, dates, portada, descripció i moneda.', 'trip.tabs.journal': 'Diari', 'trip.journal.title': 'Diari', 'trip.journal.hint': diff --git a/shared/src/i18n/cs/trip.ts b/shared/src/i18n/cs/trip.ts index fd4c4e71c..10bb1b531 100644 --- a/shared/src/i18n/cs/trip.ts +++ b/shared/src/i18n/cs/trip.ts @@ -112,6 +112,7 @@ const trip: TranslationStrings = { 'trip.settings.peopleHint': 'Kdo je na této cestě a hosté bez účtu.', 'trip.settings.sharing': 'Sdílení', 'trip.settings.sharingHint': 'Odkaz jen pro čtení pro kohokoli a pozvánka k připojení.', + 'trip.settings.editTripHint': 'Název, termíny, obálka, popis a měna.', 'trip.tabs.journal': 'Deník', 'trip.journal.title': 'Deník', 'trip.journal.hint': diff --git a/shared/src/i18n/de/trip.ts b/shared/src/i18n/de/trip.ts index 011372084..b74b4c576 100644 --- a/shared/src/i18n/de/trip.ts +++ b/shared/src/i18n/de/trip.ts @@ -108,6 +108,7 @@ const trip: TranslationStrings = { 'trip.settings.peopleHint': 'Wer auf dieser Reise ist, und Gäste ohne Konto.', 'trip.settings.sharing': 'Teilen', 'trip.settings.sharingHint': 'Ein Nur-Lesen-Link für alle und ein Einladungslink zum Beitreten.', + 'trip.settings.editTripHint': 'Titel, Daten, Titelbild, Beschreibung und Währung.', 'trip.tabs.journal': 'Journal', // same-as-en 'trip.journal.title': 'Journal', // same-as-en 'trip.journal.hint': diff --git a/shared/src/i18n/en/trip.ts b/shared/src/i18n/en/trip.ts index a3c662c4c..ab8de2096 100644 --- a/shared/src/i18n/en/trip.ts +++ b/shared/src/i18n/en/trip.ts @@ -107,6 +107,7 @@ const trip: TranslationStrings = { 'trip.settings.peopleHint': 'Who is on this trip, and the guests without an account.', 'trip.settings.sharing': 'Sharing', 'trip.settings.sharingHint': 'A read-only link for anyone, and an invite link to join.', + 'trip.settings.editTripHint': 'Title, dates, cover, description and currency.', 'trip.tabs.journal': 'Journal', 'trip.journal.title': 'Journal', 'trip.journal.hint': diff --git a/shared/src/i18n/es/trip.ts b/shared/src/i18n/es/trip.ts index 181c63eb2..1c9d26888 100644 --- a/shared/src/i18n/es/trip.ts +++ b/shared/src/i18n/es/trip.ts @@ -107,6 +107,7 @@ const trip: TranslationStrings = { 'trip.settings.peopleHint': 'Quién está en este viaje y los invitados sin cuenta.', 'trip.settings.sharing': 'Compartir', 'trip.settings.sharingHint': 'Un enlace de solo lectura para cualquiera y un enlace de invitación para unirse.', + 'trip.settings.editTripHint': 'Título, fechas, portada, descripción y moneda.', 'trip.tabs.journal': 'Diario', 'trip.journal.title': 'Diario', 'trip.journal.hint': diff --git a/shared/src/i18n/et/trip.ts b/shared/src/i18n/et/trip.ts index 33b738349..648c9d374 100644 --- a/shared/src/i18n/et/trip.ts +++ b/shared/src/i18n/et/trip.ts @@ -107,6 +107,7 @@ const trip: TranslationStrings = { 'trip.settings.peopleHint': 'Kes on sellel reisil, ja kontota külalised.', 'trip.settings.sharing': 'Jagamine', 'trip.settings.sharingHint': 'Kirjutuskaitstud link kõigile ja kutselink liitumiseks.', + 'trip.settings.editTripHint': 'Pealkiri, kuupäevad, kaanepilt, kirjeldus ja valuuta.', 'trip.tabs.journal': 'Päevik', 'trip.journal.title': 'Päevik', 'trip.journal.hint': diff --git a/shared/src/i18n/fr/trip.ts b/shared/src/i18n/fr/trip.ts index 350b1f243..b6e9619bb 100644 --- a/shared/src/i18n/fr/trip.ts +++ b/shared/src/i18n/fr/trip.ts @@ -108,6 +108,7 @@ const trip: TranslationStrings = { 'trip.settings.peopleHint': 'Qui participe à ce voyage, et les invités sans compte.', 'trip.settings.sharing': 'Partage', 'trip.settings.sharingHint': "Un lien en lecture seule pour tous, et un lien d'invitation pour rejoindre.", + 'trip.settings.editTripHint': 'Titre, dates, couverture, description et devise.', 'trip.tabs.journal': 'Journal', // same-as-en 'trip.journal.title': 'Journal', // same-as-en 'trip.journal.hint': diff --git a/shared/src/i18n/gr/trip.ts b/shared/src/i18n/gr/trip.ts index 913d96b6f..26abe9e74 100644 --- a/shared/src/i18n/gr/trip.ts +++ b/shared/src/i18n/gr/trip.ts @@ -110,6 +110,7 @@ const trip: TranslationStrings = { 'trip.settings.sharing': 'Κοινοποίηση', 'trip.settings.sharingHint': 'Ένας σύνδεσμος μόνο για ανάγνωση για όλους και ένας σύνδεσμος πρόσκλησης για συμμετοχή.', + 'trip.settings.editTripHint': 'Τίτλος, ημερομηνίες, εξώφυλλο, περιγραφή και νόμισμα.', 'trip.tabs.journal': 'Ημερολόγιο', 'trip.journal.title': 'Ημερολόγιο', 'trip.journal.hint': diff --git a/shared/src/i18n/he/trip.ts b/shared/src/i18n/he/trip.ts index 28584d1e0..55a3cf01e 100644 --- a/shared/src/i18n/he/trip.ts +++ b/shared/src/i18n/he/trip.ts @@ -111,6 +111,7 @@ const trip: TranslationStrings = { 'trip.settings.peopleHint': 'מי בטיול הזה, והאורחים שאין להם חשבון.', 'trip.settings.sharing': 'שיתוף', 'trip.settings.sharingHint': 'קישור לקריאה בלבד לכל אחד, וקישור הזמנה להצטרפות.', + 'trip.settings.editTripHint': 'כותרת, תאריכים, תמונת שער, תיאור ומטבע.', 'trip.tabs.journal': 'יומן', 'trip.journal.title': 'יומן', 'trip.journal.hint': diff --git a/shared/src/i18n/hu/trip.ts b/shared/src/i18n/hu/trip.ts index a115cd3fd..69e4ed1a9 100644 --- a/shared/src/i18n/hu/trip.ts +++ b/shared/src/i18n/hu/trip.ts @@ -110,6 +110,7 @@ const trip: TranslationStrings = { 'trip.settings.peopleHint': 'Kik vannak ezen az utazáson, és a fiók nélküli vendégek.', 'trip.settings.sharing': 'Megosztás', 'trip.settings.sharingHint': 'Csak olvasható link bárkinek, és meghívó link a csatlakozáshoz.', + 'trip.settings.editTripHint': 'Cím, dátumok, borítókép, leírás és pénznem.', 'trip.tabs.journal': 'Napló', 'trip.journal.title': 'Napló', 'trip.journal.hint': diff --git a/shared/src/i18n/id/trip.ts b/shared/src/i18n/id/trip.ts index ec5899c5a..852913b4a 100644 --- a/shared/src/i18n/id/trip.ts +++ b/shared/src/i18n/id/trip.ts @@ -103,6 +103,7 @@ const trip: TranslationStrings = { 'trip.settings.peopleHint': 'Siapa saja dalam perjalanan ini, dan tamu tanpa akun.', 'trip.settings.sharing': 'Berbagi', 'trip.settings.sharingHint': 'Tautan hanya-baca untuk siapa saja, dan tautan undangan untuk bergabung.', + 'trip.settings.editTripHint': 'Judul, tanggal, sampul, deskripsi, dan mata uang.', 'trip.tabs.journal': 'Jurnal', 'trip.journal.title': 'Jurnal', 'trip.journal.hint': diff --git a/shared/src/i18n/it/trip.ts b/shared/src/i18n/it/trip.ts index 0ce4b885a..d1d3afa59 100644 --- a/shared/src/i18n/it/trip.ts +++ b/shared/src/i18n/it/trip.ts @@ -107,6 +107,7 @@ const trip: TranslationStrings = { 'trip.settings.peopleHint': 'Chi partecipa a questo viaggio e gli ospiti senza account.', 'trip.settings.sharing': 'Condivisione', 'trip.settings.sharingHint': 'Un link di sola lettura per tutti e un link di invito per unirsi.', + 'trip.settings.editTripHint': 'Titolo, date, copertina, descrizione e valuta.', 'trip.tabs.journal': 'Diario', 'trip.journal.title': 'Diario', 'trip.journal.hint': diff --git a/shared/src/i18n/ja/trip.ts b/shared/src/i18n/ja/trip.ts index 07c606e3a..18e163f0d 100644 --- a/shared/src/i18n/ja/trip.ts +++ b/shared/src/i18n/ja/trip.ts @@ -100,6 +100,7 @@ const trip: TranslationStrings = { 'trip.settings.peopleHint': 'この旅行の参加者と、アカウントのないゲスト。', 'trip.settings.sharing': '共有', 'trip.settings.sharingHint': '誰でも見られる閲覧専用リンクと、参加用の招待リンク。', + 'trip.settings.editTripHint': 'タイトル、日程、カバー画像、説明、通貨。', 'trip.tabs.journal': 'ジャーナル', 'trip.journal.title': 'ジャーナル', 'trip.journal.hint': diff --git a/shared/src/i18n/ko/trip.ts b/shared/src/i18n/ko/trip.ts index b683eeaf1..380202250 100644 --- a/shared/src/i18n/ko/trip.ts +++ b/shared/src/i18n/ko/trip.ts @@ -100,6 +100,7 @@ const trip: TranslationStrings = { 'trip.settings.peopleHint': '이 여행의 참여자와 계정 없는 게스트.', 'trip.settings.sharing': '공유', 'trip.settings.sharingHint': '누구나 볼 수 있는 읽기 전용 링크와 참여용 초대 링크.', + 'trip.settings.editTripHint': '제목, 날짜, 커버 이미지, 설명, 통화.', 'trip.tabs.journal': '저널', 'trip.journal.title': '저널', 'trip.journal.hint': diff --git a/shared/src/i18n/nl/trip.ts b/shared/src/i18n/nl/trip.ts index 8558736ab..eaa91e493 100644 --- a/shared/src/i18n/nl/trip.ts +++ b/shared/src/i18n/nl/trip.ts @@ -107,6 +107,7 @@ const trip: TranslationStrings = { 'trip.settings.peopleHint': 'Wie er op deze reis zijn, en gasten zonder account.', 'trip.settings.sharing': 'Delen', 'trip.settings.sharingHint': 'Een alleen-lezen-link voor iedereen en een uitnodigingslink om mee te doen.', + 'trip.settings.editTripHint': 'Titel, data, omslagfoto, beschrijving en valuta.', 'trip.tabs.journal': 'Dagboek', 'trip.journal.title': 'Dagboek', 'trip.journal.hint': diff --git a/shared/src/i18n/pl/trip.ts b/shared/src/i18n/pl/trip.ts index 6ef4f9d5d..2c1cd377b 100644 --- a/shared/src/i18n/pl/trip.ts +++ b/shared/src/i18n/pl/trip.ts @@ -117,6 +117,7 @@ const trip: TranslationStrings = { 'trip.settings.peopleHint': 'Kto bierze udział w podróży oraz goście bez konta.', 'trip.settings.sharing': 'Udostępnianie', 'trip.settings.sharingHint': 'Link tylko do odczytu dla każdego i link z zaproszeniem do dołączenia.', + 'trip.settings.editTripHint': 'Tytuł, daty, okładka, opis i waluta.', 'trip.tabs.journal': 'Dziennik', 'trip.journal.title': 'Dziennik', 'trip.journal.hint': diff --git a/shared/src/i18n/ru/trip.ts b/shared/src/i18n/ru/trip.ts index e0a35a058..bff015fb7 100644 --- a/shared/src/i18n/ru/trip.ts +++ b/shared/src/i18n/ru/trip.ts @@ -117,6 +117,7 @@ const trip: TranslationStrings = { 'trip.settings.peopleHint': 'Кто участвует в поездке, и гости без аккаунта.', 'trip.settings.sharing': 'Доступ по ссылке', 'trip.settings.sharingHint': 'Ссылка только для чтения для всех и ссылка-приглашение для участия.', + 'trip.settings.editTripHint': 'Название, даты, обложка, описание и валюта.', 'trip.tabs.journal': 'Дневник', 'trip.journal.title': 'Дневник', 'trip.journal.hint': diff --git a/shared/src/i18n/sk/trip.ts b/shared/src/i18n/sk/trip.ts index 17a94214a..0ee33ea24 100644 --- a/shared/src/i18n/sk/trip.ts +++ b/shared/src/i18n/sk/trip.ts @@ -112,6 +112,7 @@ const trip: TranslationStrings = { 'trip.settings.peopleHint': 'Kto je na tejto ceste a hostia bez účtu.', 'trip.settings.sharing': 'Zdieľanie', 'trip.settings.sharingHint': 'Odkaz len na čítanie pre kohokoľvek a pozvánka na pripojenie.', + 'trip.settings.editTripHint': 'Názov, termíny, obálka, popis a mena.', 'trip.tabs.journal': 'Denník', 'trip.journal.title': 'Denník', 'trip.journal.hint': diff --git a/shared/src/i18n/sv/trip.ts b/shared/src/i18n/sv/trip.ts index 300a80518..4b23cbf47 100644 --- a/shared/src/i18n/sv/trip.ts +++ b/shared/src/i18n/sv/trip.ts @@ -107,6 +107,7 @@ const trip: TranslationStrings = { 'trip.settings.peopleHint': 'Vilka som är med på resan, och gäster utan konto.', 'trip.settings.sharing': 'Dela', 'trip.settings.sharingHint': 'En skrivskyddad länk för alla och en inbjudningslänk för att gå med.', + 'trip.settings.editTripHint': 'Titel, datum, omslagsbild, beskrivning och valuta.', 'trip.tabs.journal': 'Dagbok', 'trip.journal.title': 'Dagbok', 'trip.journal.hint': diff --git a/shared/src/i18n/th/trip.ts b/shared/src/i18n/th/trip.ts index 35cbfce3c..c649e3fb5 100644 --- a/shared/src/i18n/th/trip.ts +++ b/shared/src/i18n/th/trip.ts @@ -102,6 +102,7 @@ const trip: TranslationStrings = { 'trip.settings.peopleHint': 'ใครอยู่ในทริปนี้ และแขกที่ไม่มีบัญชี', 'trip.settings.sharing': 'การแชร์', 'trip.settings.sharingHint': 'ลิงก์อ่านอย่างเดียวสำหรับทุกคน และลิงก์เชิญเพื่อเข้าร่วม', + 'trip.settings.editTripHint': 'ชื่อ วันที่ ภาพปก คำอธิบาย และสกุลเงิน', 'trip.tabs.journal': 'บันทึก', 'trip.journal.title': 'บันทึก', 'trip.journal.hint': 'เชื่อมบันทึกของคุณเล่มหนึ่งเข้ากับทริปนี้ ผู้ที่คุณให้สิทธิ์จะเปิดได้ที่แท็บบันทึกตรงนี้ แบบสด', diff --git a/shared/src/i18n/tr/trip.ts b/shared/src/i18n/tr/trip.ts index a3926fe34..300bbfc0f 100644 --- a/shared/src/i18n/tr/trip.ts +++ b/shared/src/i18n/tr/trip.ts @@ -108,6 +108,7 @@ const trip: TranslationStrings = { 'trip.settings.peopleHint': 'Bu seyahatte kimlerin olduğu ve hesabı olmayan misafirler.', 'trip.settings.sharing': 'Paylaşım', 'trip.settings.sharingHint': 'Herkes için salt okunur bir bağlantı ve katılmak için davet bağlantısı.', + 'trip.settings.editTripHint': 'Başlık, tarihler, kapak, açıklama ve para birimi.', 'trip.tabs.journal': 'Günlük', 'trip.journal.title': 'Günlük', 'trip.journal.hint': diff --git a/shared/src/i18n/uk/trip.ts b/shared/src/i18n/uk/trip.ts index aee121da2..f7e97e0a6 100644 --- a/shared/src/i18n/uk/trip.ts +++ b/shared/src/i18n/uk/trip.ts @@ -117,6 +117,7 @@ const trip: TranslationStrings = { 'trip.settings.peopleHint': 'Хто бере участь у подорожі, і гості без облікового запису.', 'trip.settings.sharing': 'Поширення', 'trip.settings.sharingHint': 'Посилання лише для читання для всіх і посилання-запрошення, щоб приєднатися.', + 'trip.settings.editTripHint': 'Назва, дати, обкладинка, опис і валюта.', 'trip.tabs.journal': 'Щоденник', 'trip.journal.title': 'Щоденник', 'trip.journal.hint': diff --git a/shared/src/i18n/vi/trip.ts b/shared/src/i18n/vi/trip.ts index 2200ed8c7..906b4d925 100644 --- a/shared/src/i18n/vi/trip.ts +++ b/shared/src/i18n/vi/trip.ts @@ -102,6 +102,7 @@ const trip: TranslationStrings = { 'trip.settings.peopleHint': 'Những ai trong chuyến đi này, và khách không có tài khoản.', 'trip.settings.sharing': 'Chia sẻ', 'trip.settings.sharingHint': 'Liên kết chỉ đọc cho mọi người và liên kết mời để tham gia.', + 'trip.settings.editTripHint': 'Tiêu đề, ngày, ảnh bìa, mô tả và tiền tệ.', 'trip.tabs.journal': 'Nhật ký', 'trip.journal.title': 'Nhật ký', 'trip.journal.hint': diff --git a/shared/src/i18n/zh-TW/trip.ts b/shared/src/i18n/zh-TW/trip.ts index 6307b9e23..a6734513a 100644 --- a/shared/src/i18n/zh-TW/trip.ts +++ b/shared/src/i18n/zh-TW/trip.ts @@ -99,6 +99,7 @@ const trip: TranslationStrings = { 'trip.settings.peopleHint': '參與此旅行的人,以及沒有帳戶的訪客。', 'trip.settings.sharing': '分享', 'trip.settings.sharingHint': '任何人可用的唯讀連結,以及用於加入的邀請連結。', + 'trip.settings.editTripHint': '標題、日期、封面、描述和貨幣。', 'trip.tabs.journal': '日誌', 'trip.journal.title': '日誌', 'trip.journal.hint': '將你的一本日誌連結到此旅行。獲得存取權限的人可在此處的日誌分頁中即時開啟它。', diff --git a/shared/src/i18n/zh/trip.ts b/shared/src/i18n/zh/trip.ts index 7d2251356..048dcf28f 100644 --- a/shared/src/i18n/zh/trip.ts +++ b/shared/src/i18n/zh/trip.ts @@ -99,6 +99,7 @@ const trip: TranslationStrings = { 'trip.settings.peopleHint': '参与此旅行的人,以及没有账户的访客。', 'trip.settings.sharing': '分享', 'trip.settings.sharingHint': '任何人可用的只读链接,以及用于加入的邀请链接。', + 'trip.settings.editTripHint': '标题、日期、封面、描述和货币。', 'trip.tabs.journal': '日志', 'trip.journal.title': '日志', 'trip.journal.hint': '将你的一本日志关联到此旅行。获得访问权限的人可在此处的日志标签中实时打开它。', diff --git a/wiki/Currencies.md b/wiki/Currencies.md index b44ad38b8..672e415e5 100644 --- a/wiki/Currencies.md +++ b/wiki/Currencies.md @@ -12,7 +12,7 @@ The short version: **the trip currency is the accounting base, the expense curre ## Trip currency -Every trip has exactly one currency. It is set when you create the trip and can be changed later in the same dialog: on **My Trips**, click the pencil on the trip card (**Edit Trip**). This needs the `trip_edit` permission. The **Trip settings** pill beside the trip name in the planner does not hold the currency; it only holds People, Sharing and Journal. The new-trip dialog pre-fills it with **your display currency**, falling back to **EUR** when you have left that on *Trip currency*. A trip created through MCP or by a plugin without a currency gets the same value. This is the one moment the display currency touches stored data: it seeds the field, and from then on the trip's currency is its own. +Every trip has exactly one currency. It is set when you create the trip and can be changed later in the same dialog: on **My Trips**, click the pencil on the trip card (**Edit Trip**), or in the planner open **Trip settings** and pick **Edit Trip** at the foot of its list. This needs the `trip_edit` permission. The new-trip dialog pre-fills it with **your display currency**, falling back to **EUR** when you have left that on *Trip currency*. A trip created through MCP or by a plugin without a currency gets the same value. This is the one moment the display currency touches stored data: it seeds the field, and from then on the trip's currency is its own. This is the trip's **accounting base**. It is not a cosmetic label: diff --git a/wiki/Trip-Settings.md b/wiki/Trip-Settings.md index 754c1a672..7c3a3b324 100644 --- a/wiki/Trip-Settings.md +++ b/wiki/Trip-Settings.md @@ -9,6 +9,8 @@ On a wide screen the areas are listed down the left side, each with a line saying what it is for; on a narrow one they are a row of pills above the content. The head of the dialog shows how many people and guests the trip has. +If you may edit the trip (`trip_edit`), **Edit Trip** sits at the foot of that list (at the end of the pills on a narrow screen). It closes Trip settings and opens the trip's own dialog, the same one as the pencil on the trip card in **My Trips**, so the title, description, dates, currency and cover can be changed without leaving the planner. + ## People *Who is on this trip, and the guests without an account.*