feat(journey): optionally place an entry where its first photo was taken (#1003)

This commit is contained in:
Maurice
2026-10-01 14:05:47 +02:00
parent f875c85474
commit 81d7022b96
38 changed files with 322 additions and 16 deletions
@@ -215,6 +215,14 @@ describe('JourneySettingsDialog', () => {
await waitFor(() => expect(updateJourney).toHaveBeenCalledWith(3, { status_override: null }))
})
it('FE-JRN-SETTINGS-1003: the photo-location switch saves on the spot', async () => {
const user = userEvent.setup()
const { onRefresh } = mountDialog()
await user.click(screen.getByRole('button', { name: 'Place entries from their photos' }))
await waitFor(() => expect(updateJourney).toHaveBeenCalledWith(3, { photo_location: true }))
expect(onRefresh).toHaveBeenCalled()
})
it('FE-JRN-SETTINGS-011: restores an archived journey', async () => {
const user = userEvent.setup()
mountDialog(buildJourney({ status: 'archived' }))
@@ -145,6 +145,20 @@ export function JourneySettingsDialog({ journey, onClose, onSaved, onOpenInvite,
}
}
// Entries placed from their photos (#1003), saved on the spot like the tracks switch.
const [savingPhotoLocation, setSavingPhotoLocation] = useState(false)
const handlePhotoLocationToggle = async () => {
setSavingPhotoLocation(true)
try {
await updateJourney(journey.id, { photo_location: !journey.photo_location })
onRefresh()
} catch {
toast.error(t('journey.settings.saveFailed'))
} finally {
setSavingPhotoLocation(false)
}
}
// The state shown for the journey, set by hand (#762). Saved on the spot like
// the switches; 'auto' stores null and hands it back to the trip dates.
const [savingStatus, setSavingStatus] = useState(false)
@@ -301,6 +315,23 @@ export function JourneySettingsDialog({ journey, onClose, onSaved, onOpenInvite,
</div>
</DialogSection>
{/* Places from photos (#1003): the GPS a picture carries puts its entry on the map */}
<DialogSection label={t('journey.settings.photosSection')}>
<div className={ROWS}>
<div className={`${ROW}${savingPhotoLocation ? ' opacity-60' : ''}`}>
<span className="min-w-0 flex-1">
<span className="block text-content" style={fs(13, 'body')}>{t('journey.settings.photoLocation')}</span>
<span className="block text-content-faint" style={fs(11.5)}>{t('journey.settings.photoLocationHint')}</span>
</span>
<ToggleSwitch
on={!!journey.photo_location}
onToggle={() => { if (!savingPhotoLocation) handlePhotoLocationToggle() }}
label={t('journey.settings.photoLocation')}
/>
</div>
</div>
</DialogSection>
{/* The three fields a journey may put away (discussion #2299) */}
<DialogSection label={t('journey.settings.entryFields')}>
<p className="m-0 mb-2.5 leading-normal text-content-faint" style={fs(11.5)}>{t('journey.settings.entryFieldsHint')}</p>