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.*