From 5176dc29d61a63da57fe2cb2134fffb91dc1b649 Mon Sep 17 00:00:00 2001 From: Maurice Date: Fri, 13 Nov 2026 09:37:06 +0100 Subject: [PATCH] feat(places): say when a place has an address but no coordinates (#2837) A place saved with a typed address and no search pick, link or map point gets no coordinates, so it never shows on the map, and nothing said why. The place form warns under the address while that is so, and the inspector, the phone's place sheet and both place lists mark such a place. No geocoding on save; the form stays unchanged for a service stop, which explains a missing position itself. --- .../Planner/PlaceFormLocation.test.tsx | 39 ++++++++++ .../components/Planner/PlaceFormLocation.tsx | 78 +++++++++++++++++++ .../src/components/Planner/PlaceFormModal.tsx | 49 +----------- .../Planner/PlaceInspector.test.tsx | 13 ++++ .../components/Planner/PlaceInspectorHead.tsx | 17 +++- .../components/Planner/PlacesSidebar.test.tsx | 15 +++- .../components/Planner/PlacesSidebarRow.tsx | 12 ++- .../Planner/placeCoordinates.test.ts | 25 ++++++ .../components/Planner/placeCoordinates.ts | 22 ++++++ .../screens/trip/places/MPlacesBrowser.tsx | 13 +++- .../screens/trip/sheets/MPlaceEditSheet.tsx | 10 ++- .../screens/trip/sheets/MPlaceSheet.tsx | 10 ++- .../unit/mobile/trip/MPlaceEditSheet.test.tsx | 14 +++- .../unit/mobile/trip/MPlaceSheet.test.tsx | 12 ++- .../unit/mobile/trip/MPlacesBrowser.test.tsx | 10 ++- shared/src/i18n/ar/places.ts | 4 + shared/src/i18n/az/places.ts | 4 + shared/src/i18n/br/places.ts | 4 + shared/src/i18n/ca/places.ts | 4 + shared/src/i18n/cs/places.ts | 4 + shared/src/i18n/de/places.ts | 5 ++ shared/src/i18n/en/places.ts | 4 + shared/src/i18n/es/places.ts | 4 + shared/src/i18n/et/places.ts | 4 + shared/src/i18n/fr/places.ts | 4 + shared/src/i18n/gr/places.ts | 5 ++ shared/src/i18n/he/places.ts | 4 + shared/src/i18n/hu/places.ts | 5 ++ shared/src/i18n/id/places.ts | 4 + shared/src/i18n/it/places.ts | 4 + shared/src/i18n/ja/places.ts | 4 + shared/src/i18n/ko/places.ts | 4 + shared/src/i18n/nl/places.ts | 4 + shared/src/i18n/pl/places.ts | 4 + shared/src/i18n/ru/places.ts | 4 + shared/src/i18n/sk/places.ts | 4 + shared/src/i18n/sv/places.ts | 4 + shared/src/i18n/th/places.ts | 4 + shared/src/i18n/tr/places.ts | 4 + shared/src/i18n/uk/places.ts | 4 + shared/src/i18n/vi/places.ts | 4 + shared/src/i18n/zh-TW/places.ts | 4 + shared/src/i18n/zh/places.ts | 4 + wiki/Places-and-Search.md | 6 +- 44 files changed, 401 insertions(+), 59 deletions(-) create mode 100644 client/src/components/Planner/PlaceFormLocation.test.tsx create mode 100644 client/src/components/Planner/PlaceFormLocation.tsx create mode 100644 client/src/components/Planner/placeCoordinates.test.ts create mode 100644 client/src/components/Planner/placeCoordinates.ts diff --git a/client/src/components/Planner/PlaceFormLocation.test.tsx b/client/src/components/Planner/PlaceFormLocation.test.tsx new file mode 100644 index 000000000..7b9693d0b --- /dev/null +++ b/client/src/components/Planner/PlaceFormLocation.test.tsx @@ -0,0 +1,39 @@ +import { fireEvent, render, screen } from '../../../tests/helpers/render'; +import { PlaceFormLocation } from './PlaceFormLocation'; + +import { describe, expect, it, vi } from 'vitest'; + +const HINT = + 'This address has no coordinates yet, so the place will not show on the map. Pick it from the search above or enter latitude and longitude.'; + +function renderLocation(form: { address: string; lat: string; lng: string }, warnWithoutCoordinates = true) { + const onChange = vi.fn(); + render( + + ); + return onChange; +} + +describe('PlaceFormLocation', () => { + it('FE-PLANNER-PFLOC-001: an address typed without coordinates says the place will not be on the map (#2837)', () => { + renderLocation({ address: 'Rue de Rivoli 1, Paris', lat: '', lng: '' }); + expect(screen.getByText(HINT)).toBeInTheDocument(); + }); + + it('FE-PLANNER-PFLOC-002: no word with coordinates, without an address, or where the form explains it itself', () => { + renderLocation({ address: 'Rue de Rivoli 1, Paris', lat: '48.86', lng: '2.35' }); + renderLocation({ address: '', lat: '', lng: '' }); + renderLocation({ address: 'Rue de Rivoli 1, Paris', lat: '', lng: '' }, false); + expect(screen.queryByText(HINT)).toBeNull(); + }); + + it('FE-PLANNER-PFLOC-003: the fields write through, and a pasted pair fills both coordinates', () => { + const onChange = renderLocation({ address: '', lat: '', lng: '' }); + fireEvent.change(screen.getByLabelText('Address'), { target: { value: 'Rue de Rivoli 1' } }); + expect(onChange).toHaveBeenCalledWith('address', 'Rue de Rivoli 1'); + + fireEvent.paste(screen.getByLabelText('Latitude'), { clipboardData: { getData: () => '48.8566, 2.3522' } }); + expect(onChange).toHaveBeenCalledWith('lat', '48.8566'); + expect(onChange).toHaveBeenCalledWith('lng', '2.3522'); + }); +}); diff --git a/client/src/components/Planner/PlaceFormLocation.tsx b/client/src/components/Planner/PlaceFormLocation.tsx new file mode 100644 index 000000000..b5ce59ae5 --- /dev/null +++ b/client/src/components/Planner/PlaceFormLocation.tsx @@ -0,0 +1,78 @@ +import { MapPinOff } from 'lucide-react'; + +import { useTranslation } from '../../i18n'; +import { EditorField, GRID_2, INPUT, PANEL } from '../shared/dialogParts'; +import { NumericInput } from '../shared/NumericInput'; +import { addressWithoutCoordinates } from './placeCoordinates'; +import { parseCoordinatePair } from './PlaceFormModal.helpers'; + +type LocationField = 'address' | 'lat' | 'lng'; + +interface PlaceFormLocationProps { + /** The form's id prefix, so each label points at its own field. */ + fieldId: string; + form: Record; + onChange: (field: LocationField, value: string) => void; + /** Off where the form explains a missing position in its own words (a service stop). */ + warnWithoutCoordinates: boolean; +} + +/** + * Where the place is: what a pick, a pasted link or the map filled in, or typed by hand. + * An address typed without coordinates says under the field that the place will not be + * on the map, and how to put it there (#2837). + */ +export function PlaceFormLocation({ fieldId, form, onChange, warnWithoutCoordinates }: PlaceFormLocationProps) { + const { t } = useTranslation(); + const hint = warnWithoutCoordinates && addressWithoutCoordinates(form) && ( + + + ); + return ( +
+ + onChange('address', e.target.value)} + placeholder={t('places.formAddressPlaceholder')} + className={INPUT} + /> + +
+ + onChange('lat', v)} + onPaste={(e) => { + // "48.85, 2.35" pasted into the latitude fills both halves. + const pair = parseCoordinatePair(e.clipboardData.getData('text')); + if (pair) { + e.preventDefault(); + onChange('lat', pair[0]); + onChange('lng', pair[1]); + } + }} + placeholder={t('places.formLat')} + className={INPUT} + /> + + + onChange('lng', v)} + placeholder={t('places.formLng')} + className={INPUT} + /> + +
+
+ ); +} diff --git a/client/src/components/Planner/PlaceFormModal.tsx b/client/src/components/Planner/PlaceFormModal.tsx index 9e14521b0..3e19e5fac 100644 --- a/client/src/components/Planner/PlaceFormModal.tsx +++ b/client/src/components/Planner/PlaceFormModal.tsx @@ -15,10 +15,11 @@ import { Tooltip } from '../shared/Tooltip' import { Search, RotateCcw, LocateFixed, PencilLine } from 'lucide-react' import { useTranslation } from '../../i18n' import { PlaceContactFields } from './PlaceContactFields' +import { PlaceFormLocation } from './PlaceFormLocation' import { weekFromPeriods } from './placeHours' import type { PlaceOpeningHours } from '@trek/shared' import { - DEFAULT_FORM, endsBeforeStart, findDuplicatePlace, formPin, mergeResult, parseCoordinatePair, placeEditForm, placeFormPayload, + DEFAULT_FORM, endsBeforeStart, findDuplicatePlace, formPin, mergeResult, placeEditForm, placeFormPayload, prefillForm, savedCategoryId, type PlaceFormData, type PlacePrefill, } from './PlaceFormModal.helpers' import { useNewCategory, usePlaceForm } from './usePlaceForm' @@ -30,7 +31,6 @@ import { useLocationBias } from '../../hooks/useLocationBias' import { BookingCostsSection } from './BookingCostsSection' import type { BookingExpenseRequest } from './BookingCostsSection.types' import type { Place, Category, Assignment, BudgetItem } from '../../types' -import { NumericInput } from '../shared/NumericInput' import { formatDistance } from '../../utils/units' import ServiceStopSection from '../Roadtrip/ServiceStopSection' import { DEFAULT_SERVICE_KIND, serviceStopChoice, type ServiceStopMode } from '../Roadtrip/manualStop' @@ -770,50 +770,7 @@ export default function PlaceFormModal(props: PlaceFormModalProps) { )} - {/* Where it is: what a pick, a pasted link or the map filled in, or typed by hand. */} -
- - handleChange('address', e.target.value)} - placeholder={t('places.formAddressPlaceholder')} - className={INPUT} - /> - -
- - handleChange('lat', v)} - onPaste={e => { - // "48.85, 2.35" pasted into the latitude fills both halves. - const pair = parseCoordinatePair(e.clipboardData.getData('text')) - if (pair) { - e.preventDefault() - handleChange('lat', pair[0]) - handleChange('lng', pair[1]) - } - }} - placeholder={t('places.formLat')} - className={INPUT} - /> - - - handleChange('lng', v)} - placeholder={t('places.formLng')} - className={INPUT} - /> - -
-
+ {/* For a stop on a drive: the kind of stop, how long, and where it belongs. The category it replaces is gone from the head band too: refuelling is diff --git a/client/src/components/Planner/PlaceInspector.test.tsx b/client/src/components/Planner/PlaceInspector.test.tsx index 3d842c475..31252640b 100644 --- a/client/src/components/Planner/PlaceInspector.test.tsx +++ b/client/src/components/Planner/PlaceInspector.test.tsx @@ -1871,3 +1871,16 @@ describe('PlaceInspector folded to its head (#3039)', () => { expect(screen.queryByRole('button', { name: 'Collapse' })).toBeNull(); }); }); + +// ── An address without coordinates (#2837) ───────────────────────────────────── + +describe('PlaceInspector without coordinates (#2837)', () => { + it('FE-PLANNER-INSPECTOR-114: a typed address without coordinates says the place is not on the map', () => { + const typed = buildPlace({ id: 3, name: 'Corner Cafe', address: 'Rue de Rivoli 1, Paris', lat: null, lng: null }); + const { rerender } = render(); + expect(screen.getByText('No coordinates, so not on the map')).toBeInTheDocument(); + + rerender(); + expect(screen.queryByText('No coordinates, so not on the map')).toBeNull(); + }); +}); diff --git a/client/src/components/Planner/PlaceInspectorHead.tsx b/client/src/components/Planner/PlaceInspectorHead.tsx index 30016ae16..d17b65753 100644 --- a/client/src/components/Planner/PlaceInspectorHead.tsx +++ b/client/src/components/Planner/PlaceInspectorHead.tsx @@ -1,4 +1,4 @@ -import { Banknote, ChevronDown, ChevronUp, Mail, MapPin, Phone, Star, X } from 'lucide-react'; +import { Banknote, ChevronDown, ChevronUp, Mail, MapPin, MapPinOff, Phone, Star, X } from 'lucide-react'; import type { KeyboardEvent, ReactNode, RefObject } from 'react'; import { useTranslation } from '../../i18n'; @@ -9,6 +9,7 @@ import { NEUTRAL_TINT, fs } from '../shared/DialogShell'; import PlaceAvatar from '../shared/PlaceAvatar'; import { Tooltip } from '../shared/Tooltip'; import PlacePhotoGallery from './PlacePhotoGallery'; +import { addressWithoutCoordinates } from './placeCoordinates'; import { SoftPill, TimePill, tintOf } from './planParts'; /** The round buttons at the end of the band. */ @@ -81,6 +82,8 @@ export function InspectorHead({ const { t, locale, language } = useTranslation(); const ring = openNow === true ? 'bg-success' : openNow === false ? 'bg-danger' : 'bg-surface-card shadow-sm'; const hasCoords = !!(place.lat && place.lng); + // A typed address that never got coordinates: the place has no marker, and says why (#2837). + const offMap = addressWithoutCoordinates(place); const hasPills = openNow !== null || !!category || @@ -90,7 +93,8 @@ export function InspectorHead({ place.source === 'dawarich' || !!phone || !!email || - hasCoords; + hasCoords || + offMap; return (
+ {offMap && ( + + + }> + {t('places.noCoordinates')} + + + + )} {openNow !== null && ( { expect(screen.queryByRole('button', { name: 'No Category' })).not.toBeInTheDocument(); }); }); + +describe('Places without coordinates', () => { + it('FE-PLANNER-SIDEBAR-068: a typed address without coordinates is marked as not on the map (#2837)', () => { + const typed = buildPlace({ name: 'Typed Cafe', address: 'Rue de Rivoli 1, Paris', lat: null, lng: null }); + const picked = buildPlace({ name: 'Picked Cafe', address: 'Rue de Rivoli 2, Paris' }); + const note = buildPlace({ name: 'Loose Idea', address: null, lat: null, lng: null }); + render(); + + const marks = screen.getAllByRole('img', { name: 'No coordinates, so not on the map' }); + expect(marks).toHaveLength(1); + expect(marks[0].closest('[data-place-id]')).toHaveAttribute('data-place-id', String(typed.id)); + }); +}); diff --git a/client/src/components/Planner/PlacesSidebarRow.tsx b/client/src/components/Planner/PlacesSidebarRow.tsx index 5ca261fa0..695b4c4dc 100644 --- a/client/src/components/Planner/PlacesSidebarRow.tsx +++ b/client/src/components/Planner/PlacesSidebarRow.tsx @@ -1,7 +1,7 @@ import React, { useId } from 'react' import Markdown from 'react-markdown' import remarkGfm from 'remark-gfm' -import { Plus, Check, Star } from 'lucide-react' +import { Plus, Check, Star, MapPinOff } from 'lucide-react' import PlaceAvatar from '../shared/PlaceAvatar' import { Tooltip } from '../shared/Tooltip' import { fs } from '../shared/DialogShell' @@ -10,6 +10,7 @@ import { resolveTrackColor } from '../Map/trackColors' import { MoreButton, SoftPill, type MenuEntry } from './planParts' import type { Place, Category } from '../../types' import { startPlaceDrag } from '../Map/markerDrag' +import { addressWithoutCoordinates } from './placeCoordinates' interface MemoPlaceRowProps { place: Place @@ -115,6 +116,15 @@ export const MemoPlaceRow = React.memo(function MemoPlaceRow({ {place.name} + {/* A typed address that never got coordinates: nothing stands for this place on the + map, and the row says so (#2837). */} + {addressWithoutCoordinates(place) && ( + + + + + + )} {/* Average member rating (#1435). */} {hasRating && ( }> diff --git a/client/src/components/Planner/placeCoordinates.test.ts b/client/src/components/Planner/placeCoordinates.test.ts new file mode 100644 index 000000000..5c73197e5 --- /dev/null +++ b/client/src/components/Planner/placeCoordinates.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from 'vitest'; + +import { addressWithoutCoordinates } from './placeCoordinates'; + +describe('addressWithoutCoordinates', () => { + it('FE-PLANNER-COORDS-001: an address without coordinates is flagged, from a saved place or the form (#2837)', () => { + expect(addressWithoutCoordinates({ address: 'Rue de Rivoli 1, Paris', lat: null, lng: null })).toBe(true); + expect(addressWithoutCoordinates({ address: 'Rue de Rivoli 1, Paris', lat: '', lng: '' })).toBe(true); + // One half is not a position either. + expect(addressWithoutCoordinates({ address: 'Rue de Rivoli 1, Paris', lat: 48.86, lng: null })).toBe(true); + expect(addressWithoutCoordinates({ address: 'Rue de Rivoli 1, Paris', lat: '48.86', lng: ' ' })).toBe(true); + }); + + it('FE-PLANNER-COORDS-002: a place with coordinates, zero included, is not flagged', () => { + expect(addressWithoutCoordinates({ address: 'Rue de Rivoli 1, Paris', lat: 48.86, lng: 2.35 })).toBe(false); + expect(addressWithoutCoordinates({ address: 'Rue de Rivoli 1, Paris', lat: '48.86', lng: '2.35' })).toBe(false); + expect(addressWithoutCoordinates({ address: 'Null Island', lat: 0, lng: 0 })).toBe(false); + }); + + it('FE-PLANNER-COORDS-003: a place without an address is a note on purpose and is not flagged', () => { + expect(addressWithoutCoordinates({ address: null, lat: null, lng: null })).toBe(false); + expect(addressWithoutCoordinates({ address: ' ', lat: null, lng: null })).toBe(false); + expect(addressWithoutCoordinates({})).toBe(false); + }); +}); diff --git a/client/src/components/Planner/placeCoordinates.ts b/client/src/components/Planner/placeCoordinates.ts new file mode 100644 index 000000000..ac847f075 --- /dev/null +++ b/client/src/components/Planner/placeCoordinates.ts @@ -0,0 +1,22 @@ +type Coordinate = number | string | null | undefined; + +/** A number the map can use. Zero is one; an empty form field or a blank string is not. */ +const isCoordinate = (value: Coordinate): boolean => + value != null && String(value).trim() !== '' && Number.isFinite(Number(value)); + +/** + * Whether a place has an address but nothing to put it on the map with (#2837). + * + * The address field is plain text: a place saved with a typed address, and no pick from + * the search, no pasted link and no point on the map, has no coordinates, so it gets no + * marker, no route and no navigation, and nothing used to say why. The form, the place's + * detail and the place lists say so. A place with neither is a note on purpose and is + * left alone. Reads a saved place (numbers) and the form (strings) alike. + */ +export function addressWithoutCoordinates(place: { + address?: string | null; + lat?: Coordinate; + lng?: Coordinate; +}): boolean { + return !!place.address?.trim() && !(isCoordinate(place.lat) && isCoordinate(place.lng)); +} diff --git a/client/src/mobile/screens/trip/places/MPlacesBrowser.tsx b/client/src/mobile/screens/trip/places/MPlacesBrowser.tsx index db45385a4..333192148 100644 --- a/client/src/mobile/screens/trip/places/MPlacesBrowser.tsx +++ b/client/src/mobile/screens/trip/places/MPlacesBrowser.tsx @@ -1,6 +1,6 @@ import { ReactNode, useEffect, useMemo, useState } from 'react' import { - Bookmark, CheckCheck, CheckCircle2, Download, ListChecks, Loader2, Plus, + Bookmark, CheckCheck, CheckCircle2, Download, ListChecks, Loader2, MapPinOff, Plus, SlidersHorizontal, Tag, Trash2, X, } from 'lucide-react' import MDancingTrek from '../../../components/MDancingTrek' @@ -26,6 +26,7 @@ import { filterPool, firstPlannedDayNumbers, plannedPlaceIds } from './placesBro import { MCategoryFilterList, MRatingFloorChips, SquareCheck } from './MPlacesFilterControls' import { countActivePlacesFilters } from '../../../../utils/placesFilter' import { usePlacesPool } from '../../../../components/Planner/usePlacesPool' +import { addressWithoutCoordinates } from '../../../../components/Planner/placeCoordinates' /** * Fullscreen places pool (mode === 'browse'): All/Unplanned/Tracks filter @@ -340,6 +341,16 @@ export default function MPlacesBrowser({ planner, shell }: MPlacesBrowserProps) )} {place.name} + {/* A typed address without coordinates: this place has no marker (#2837). */} + {addressWithoutCoordinates(place) && ( + + )} {sub && ( {sub} diff --git a/client/src/mobile/screens/trip/sheets/MPlaceEditSheet.tsx b/client/src/mobile/screens/trip/sheets/MPlaceEditSheet.tsx index f1977b094..e90101987 100644 --- a/client/src/mobile/screens/trip/sheets/MPlaceEditSheet.tsx +++ b/client/src/mobile/screens/trip/sheets/MPlaceEditSheet.tsx @@ -1,5 +1,5 @@ import { useEffect, useMemo, useRef, useState, type ClipboardEvent } from 'react' -import { MapPin } from 'lucide-react' +import { MapPin, MapPinOff } from 'lucide-react' import MSheet from '../../../components/MSheet' import { DEFAULT_FORM, @@ -12,6 +12,7 @@ import { type PlaceFormData, } from '../../../../components/Planner/PlaceFormModal.helpers' import { usePlaceForm } from '../../../../components/Planner/usePlaceForm' +import { addressWithoutCoordinates } from '../../../../components/Planner/placeCoordinates' import { Eyebrow, FIELD_AREA_CLS, FIELD_CLS, FormSheetBody, FormSheetFooter, FormSheetHeader } from './PlSheetChrome' import { useAddonStore } from '../../../../store/addonStore' import type { BookingExpenseRequest } from '../../../../components/Planner/BookingCostsSection.types' @@ -331,6 +332,13 @@ export default function MPlaceEditSheet({ planner, onOpenExpense }: MPlaceEditSh className={`${FIELD_CLS} flex-1 text-[0.8125rem] [font-variant-numeric:tabular-nums]`} /> + {/* An address typed without coordinates puts nothing on the map (#2837). */} + {addressWithoutCoordinates(form) && ( +

+

+ )} {t('places.formCategory')} handleChange('category_id', id)} /> diff --git a/client/src/mobile/screens/trip/sheets/MPlaceSheet.tsx b/client/src/mobile/screens/trip/sheets/MPlaceSheet.tsx index 03b057fbe..f3c824b85 100644 --- a/client/src/mobile/screens/trip/sheets/MPlaceSheet.tsx +++ b/client/src/mobile/screens/trip/sheets/MPlaceSheet.tsx @@ -1,7 +1,7 @@ import { useMemo, useRef, useState } from 'react' import DawarichIcon from '../../../../components/shared/DawarichIcon' import { - Bookmark, ChevronRight, ExternalLink, Map as MapIcon, Navigation, Paperclip, + Bookmark, ChevronRight, ExternalLink, Map as MapIcon, MapPinOff, Navigation, Paperclip, Pencil, Phone, Plus, Route, RouteOff, Sparkles, Trash2, Upload, X, } from 'lucide-react' import MSheet from '../../../components/MSheet' @@ -24,6 +24,7 @@ import { safeHttpUrl } from '../../../../utils/safeUrl' import { openFile } from '../../../../utils/fileDownload' import { filesForPlace } from '../../../../utils/placeFiles' import { navigationTargetLabel, getNavigationTargets, openNavigationTarget } from '../../../../components/Planner/placeNavigation' +import { addressWithoutCoordinates } from '../../../../components/Planner/placeCoordinates' import { NavigationMenu } from '../../../../components/shared/NavigationMenu' import { getPlaceBookings } from '../../../../utils/dayMerge' import type { Assignment, Day, Reservation, TripMember } from '../../../../types' @@ -219,6 +220,13 @@ export default function MPlaceSheet({ planner, shell }: MTripSheetsProps) { {place.address && (
{place.address}
)} + {/* A typed address that never got coordinates: no marker, and the sheet says why (#2837). */} + {addressWithoutCoordinates(place) && ( +
+
+ )} {/* The category under the name and address, as wide as its name: squeezed under the photo it was cut to "Shoppi…". */} {category && ( diff --git a/client/tests/unit/mobile/trip/MPlaceEditSheet.test.tsx b/client/tests/unit/mobile/trip/MPlaceEditSheet.test.tsx index 3743d7241..b9aa4d6b3 100644 --- a/client/tests/unit/mobile/trip/MPlaceEditSheet.test.tsx +++ b/client/tests/unit/mobile/trip/MPlaceEditSheet.test.tsx @@ -11,7 +11,7 @@ import { server } from '../../../helpers/msw/server' import { resetAllStores, seedStore } from '../../../helpers/store' import { fireEvent, render, screen, waitFor } from '../../../helpers/render' -// FE-MOB-PLEDIT-001 to FE-MOB-PLEDIT-048, plus the 009b, 025b and 029b variants +// FE-MOB-PLEDIT-001 to FE-MOB-PLEDIT-049, plus the 009b, 025b and 029b variants // planner.t echoes the key, so every label/placeholder is asserted as its key. const CATEGORIES = [ @@ -718,4 +718,16 @@ describe('MPlaceEditSheet', () => { expect(planner.handleSavePlace).toHaveBeenCalledWith(expect.objectContaining({ name: 'Trailhead', osm_id: 'plugin:trail-finder:th-043' })) expect(planner.handleSavePlace).not.toHaveBeenCalledWith(expect.objectContaining({ google_place_id: expect.anything() })) }) + + it('FE-MOB-PLEDIT-049: a typed address without coordinates warns that the place will not be on the map (#2837)', () => { + setup() + expect(screen.queryByText('places.noCoordinatesFormHint')).not.toBeInTheDocument() + + fireEvent.change(screen.getByPlaceholderText('places.formAddressPlaceholder'), { target: { value: '1-36-3 Asakusa' } }) + expect(screen.getByText('places.noCoordinatesFormHint')).toBeInTheDocument() + + fireEvent.change(screen.getByPlaceholderText('places.formLat'), { target: { value: '35.71' } }) + fireEvent.change(screen.getByPlaceholderText('places.formLng'), { target: { value: '139.79' } }) + expect(screen.queryByText('places.noCoordinatesFormHint')).not.toBeInTheDocument() + }) }) diff --git a/client/tests/unit/mobile/trip/MPlaceSheet.test.tsx b/client/tests/unit/mobile/trip/MPlaceSheet.test.tsx index 1356fc265..9a58cbc98 100644 --- a/client/tests/unit/mobile/trip/MPlaceSheet.test.tsx +++ b/client/tests/unit/mobile/trip/MPlaceSheet.test.tsx @@ -13,7 +13,7 @@ import type { Assignment, Category, Day, Place, TripFile, TripMember } from '../ import { resetAllStores, seedStore } from '../../../helpers/store' import { act, fireEvent, render, screen, waitFor } from '../../../helpers/render' -// FE-MOB-PLSH-001 to FE-MOB-PLSH-032 +// FE-MOB-PLSH-001 to FE-MOB-PLSH-033 vi.mock('../../../../src/utils/fileDownload', () => ({ openFile: vi.fn() })) // The gallery says "no pictures" through the shared toast, the sheet's own writes through planner.toast. @@ -561,4 +561,14 @@ describe('MPlaceSheet', () => { expect(screen.queryByText('Someone elses ferry')).not.toBeInTheDocument() }) + + it('FE-MOB-PLSH-033: a typed address without coordinates says the place is not on the map (#2837)', () => { + const typed = { ...PLACE, lat: null, lng: null } as unknown as Place + const { unmount } = renderSheet(makePlanner({ selectedPlace: typed }) as unknown as TripPlanner) + expect(screen.getByText('No coordinates, so not on the map')).toBeInTheDocument() + unmount() + + renderSheet() + expect(screen.queryByText('No coordinates, so not on the map')).toBeNull() + }) }) diff --git a/client/tests/unit/mobile/trip/MPlacesBrowser.test.tsx b/client/tests/unit/mobile/trip/MPlacesBrowser.test.tsx index a2689331b..d48094e1a 100644 --- a/client/tests/unit/mobile/trip/MPlacesBrowser.test.tsx +++ b/client/tests/unit/mobile/trip/MPlacesBrowser.test.tsx @@ -11,7 +11,7 @@ import { resetAllStores, seedStore } from '../../../helpers/store' import type { MTripShellApi, TripPlanner } from '../../../../src/mobile/screens/trip/MTripShell' import type { AssignmentsMap, Category, Day, Place } from '../../../../src/types' -// FE-MOB-PBROW-001 to FE-MOB-PBROW-036 +// FE-MOB-PBROW-001 to FE-MOB-PBROW-037 const CATEGORIES = [ { id: 1, name: 'Sights', color: '#123456', icon: 'landmark' }, @@ -514,4 +514,12 @@ describe('MPlacesBrowser', () => { expect(screen.getByText('places.selectionCount:2')).toBeInTheDocument() expect(screen.getByRole('button', { name: 'common.selectAll' })).toBeInTheDocument() }) + + it('FE-MOB-PBROW-037: a typed address without coordinates is marked as not on the map (#2837)', () => { + const TYPED = place({ id: 5, name: 'Typed Cafe', address: 'Rue de Rivoli 1', lat: null, lng: null } as Partial) + renderBrowser(makePlanner({ places: [TYPED, LOUVRE], assignments: {} } as Partial)) + + expect(within(row('Typed Cafe')).getByRole('img', { name: 'places.noCoordinates' })).toBeInTheDocument() + expect(within(row('Louvre')).queryByRole('img', { name: 'places.noCoordinates' })).toBeNull() + }) }) diff --git a/shared/src/i18n/ar/places.ts b/shared/src/i18n/ar/places.ts index 3726b3709..a3c390dbc 100644 --- a/shared/src/i18n/ar/places.ts +++ b/shared/src/i18n/ar/places.ts @@ -123,6 +123,10 @@ const places: TranslationStrings = { 'places.formLng': 'خط الطول (مثال: 2.3522)', 'places.formLatLabel': 'خط العرض', 'places.formLngLabel': 'خط الطول', + 'places.noCoordinates': 'لا إحداثيات، لذا لا يظهر على الخريطة', + 'places.noCoordinatesHint': 'عدّل المكان واختره من البحث، أو أدخل خط العرض وخط الطول.', + 'places.noCoordinatesFormHint': + 'لا توجد إحداثيات لهذا العنوان بعد، لذا لن يظهر المكان على الخريطة. اختره من البحث أعلاه أو أدخل خط العرض وخط الطول.', 'places.formCategory': 'الفئة', 'places.noCategory': 'بلا فئة', 'places.newCategory': 'فئة جديدة', diff --git a/shared/src/i18n/az/places.ts b/shared/src/i18n/az/places.ts index 70dd86f36..754721736 100644 --- a/shared/src/i18n/az/places.ts +++ b/shared/src/i18n/az/places.ts @@ -101,6 +101,10 @@ const places: TranslationStrings = { 'places.formLng': 'Uzunluq (məs. 2.3522)', 'places.formLatLabel': 'Enlik', 'places.formLngLabel': 'Uzunluq', + 'places.noCoordinates': 'Koordinat yoxdur, xəritədə görünmür', + 'places.noCoordinatesHint': 'Məkanı redaktə et və axtarışdan seç, ya da enlik və uzunluğu daxil et.', + 'places.noCoordinatesFormHint': + 'Bu ünvanın hələ koordinatı yoxdur, ona görə məkan xəritədə görünməyəcək. Onu yuxarıdakı axtarışdan seç və ya enlik və uzunluğu daxil et.', 'places.formCategory': 'Kateqoriya', 'places.noCategory': 'Kateqoriya yoxdur', 'places.newCategory': 'Yeni kateqoriya', diff --git a/shared/src/i18n/br/places.ts b/shared/src/i18n/br/places.ts index 8007bdb98..6f2ab2c4a 100644 --- a/shared/src/i18n/br/places.ts +++ b/shared/src/i18n/br/places.ts @@ -92,6 +92,10 @@ const places: TranslationStrings = { 'places.formLatLabel': 'Latitude', // same-as-en 'places.formLngLabel': 'Longitude', // same-as-en 'places.formCategory': 'Categoria', + 'places.noCoordinates': 'Sem coordenadas, por isso fora do mapa', + 'places.noCoordinatesHint': 'Edite o lugar e escolha-o na busca, ou informe a latitude e a longitude.', + 'places.noCoordinatesFormHint': + 'Este endereço ainda não tem coordenadas, então o lugar não aparecerá no mapa. Escolha-o na busca acima ou informe a latitude e a longitude.', 'places.noCategory': 'Sem categoria', 'places.newCategory': 'Nova categoria', 'places.openInMaps': 'Abrir em mapas', diff --git a/shared/src/i18n/ca/places.ts b/shared/src/i18n/ca/places.ts index 2b25d35a6..b90a53c76 100644 --- a/shared/src/i18n/ca/places.ts +++ b/shared/src/i18n/ca/places.ts @@ -89,6 +89,10 @@ const places: TranslationStrings = { 'places.formLng': 'Longitud (p. ex. 2.3522)', 'places.formLatLabel': 'Latitud', 'places.formLngLabel': 'Longitud', + 'places.noCoordinates': 'Sense coordenades, per tant no surt al mapa', + 'places.noCoordinatesHint': "Edita el lloc i tria'l a la cerca, o introdueix la latitud i la longitud.", + 'places.noCoordinatesFormHint': + "Aquesta adreça encara no té coordenades, així que el lloc no sortirà al mapa. Tria'l a la cerca de dalt o introdueix la latitud i la longitud.", 'places.formCategory': 'Categoria', 'places.noCategory': 'Sense categoria', 'places.newCategory': 'Categoria nova', diff --git a/shared/src/i18n/cs/places.ts b/shared/src/i18n/cs/places.ts index 46fb45ac1..ef1b6d3aa 100644 --- a/shared/src/i18n/cs/places.ts +++ b/shared/src/i18n/cs/places.ts @@ -99,6 +99,10 @@ const places: TranslationStrings = { 'places.formLng': 'Zeměpisná délka', 'places.formLatLabel': 'Zeměpisná šířka', 'places.formLngLabel': 'Zeměpisná délka', + 'places.noCoordinates': 'Bez souřadnic, proto není na mapě', + 'places.noCoordinatesHint': 'Uprav místo a vyber ho ve vyhledávání, nebo zadej zeměpisnou šířku a délku.', + 'places.noCoordinatesFormHint': + 'Tato adresa zatím nemá souřadnice, takže se místo na mapě nezobrazí. Vyber ho ve vyhledávání výše, nebo zadej zeměpisnou šířku a délku.', 'places.formCategory': 'Kategorie', 'places.noCategory': 'Bez kategorie', 'places.newCategory': 'Nová kategorie', diff --git a/shared/src/i18n/de/places.ts b/shared/src/i18n/de/places.ts index 24e8237e5..f3276e79f 100644 --- a/shared/src/i18n/de/places.ts +++ b/shared/src/i18n/de/places.ts @@ -91,6 +91,11 @@ const places: TranslationStrings = { 'places.formLng': 'Längengrad (z.B. 2.3522)', 'places.formLatLabel': 'Breitengrad', 'places.formLngLabel': 'Längengrad', + 'places.noCoordinates': 'Keine Koordinaten, daher nicht auf der Karte', + 'places.noCoordinatesHint': + 'Bearbeite den Ort und wähle ihn in der Suche aus, oder trage Breiten- und Längengrad ein.', + 'places.noCoordinatesFormHint': + 'Diese Adresse hat noch keine Koordinaten, der Ort erscheint also nicht auf der Karte. Wähle ihn oben in der Suche aus oder trage Breiten- und Längengrad ein.', 'places.formCategory': 'Kategorie', 'places.noCategory': 'Keine Kategorie', 'places.newCategory': 'Neue Kategorie', diff --git a/shared/src/i18n/en/places.ts b/shared/src/i18n/en/places.ts index 712eeeeff..84a459e45 100644 --- a/shared/src/i18n/en/places.ts +++ b/shared/src/i18n/en/places.ts @@ -98,6 +98,10 @@ const places: TranslationStrings = { /** The small labels over the two coordinate fields of the desktop place dialog. */ 'places.formLatLabel': 'Latitude', 'places.formLngLabel': 'Longitude', + 'places.noCoordinates': 'No coordinates, so not on the map', + 'places.noCoordinatesHint': 'Edit the place and pick it from the search, or enter its latitude and longitude.', + 'places.noCoordinatesFormHint': + 'This address has no coordinates yet, so the place will not show on the map. Pick it from the search above or enter latitude and longitude.', 'places.formCategory': 'Category', 'places.noCategory': 'No Category', 'places.newCategory': 'New category', diff --git a/shared/src/i18n/es/places.ts b/shared/src/i18n/es/places.ts index 88eac75aa..c6f309d70 100644 --- a/shared/src/i18n/es/places.ts +++ b/shared/src/i18n/es/places.ts @@ -92,6 +92,10 @@ const places: TranslationStrings = { 'places.formLng': 'Longitud (p. ej. 2.3522)', 'places.formLatLabel': 'Latitud', 'places.formLngLabel': 'Longitud', + 'places.noCoordinates': 'Sin coordenadas, así que no aparece en el mapa', + 'places.noCoordinatesHint': 'Edita el lugar y elígelo en la búsqueda, o introduce la latitud y la longitud.', + 'places.noCoordinatesFormHint': + 'Esta dirección aún no tiene coordenadas, así que el lugar no aparecerá en el mapa. Elígelo en la búsqueda de arriba o introduce la latitud y la longitud.', 'places.formCategory': 'Categoría', 'places.noCategory': 'Sin categoría', 'places.newCategory': 'Nueva categoría', diff --git a/shared/src/i18n/et/places.ts b/shared/src/i18n/et/places.ts index 0faa47059..6fd831682 100644 --- a/shared/src/i18n/et/places.ts +++ b/shared/src/i18n/et/places.ts @@ -92,6 +92,10 @@ const places: TranslationStrings = { 'places.formLng': 'Pikkuskraad (nt 2.3522)', 'places.formLatLabel': 'Laiuskraad', 'places.formLngLabel': 'Pikkuskraad', + 'places.noCoordinates': 'Koordinaadid puuduvad, seega pole kaardil', + 'places.noCoordinatesHint': 'Muuda kohta ja vali see otsingust või sisesta laius- ja pikkuskraad.', + 'places.noCoordinatesFormHint': + 'Sellel aadressil pole veel koordinaate, seega koht kaardile ei ilmu. Vali see ülalt otsingust või sisesta laius- ja pikkuskraad.', 'places.formCategory': 'Kategooria', 'places.noCategory': 'Kategooriata', 'places.newCategory': 'Uus kategooria', diff --git a/shared/src/i18n/fr/places.ts b/shared/src/i18n/fr/places.ts index 9dccfa52f..4e744bd7b 100644 --- a/shared/src/i18n/fr/places.ts +++ b/shared/src/i18n/fr/places.ts @@ -93,6 +93,10 @@ const places: TranslationStrings = { 'places.formLatLabel': 'Latitude', // same-as-en 'places.formLngLabel': 'Longitude', // same-as-en 'places.formCategory': 'Catégorie', + 'places.noCoordinates': 'Pas de coordonnées, donc absent de la carte', + 'places.noCoordinatesHint': 'Modifie le lieu et choisis-le dans la recherche, ou saisis sa latitude et sa longitude.', + 'places.noCoordinatesFormHint': + 'Cette adresse n’a pas encore de coordonnées, le lieu n’apparaîtra donc pas sur la carte. Choisis-le dans la recherche ci-dessus ou saisis la latitude et la longitude.', 'places.noCategory': 'Sans catégorie', 'places.newCategory': 'Nouvelle catégorie', 'places.openInMaps': 'Ouvrir dans une app de cartes', diff --git a/shared/src/i18n/gr/places.ts b/shared/src/i18n/gr/places.ts index f811f3a52..664cc25ca 100644 --- a/shared/src/i18n/gr/places.ts +++ b/shared/src/i18n/gr/places.ts @@ -92,6 +92,11 @@ const places: TranslationStrings = { 'places.formLng': 'Γεωγραφικό Μήκος (π.χ. 2.3522)', 'places.formLatLabel': 'Γεωγραφικό Πλάτος', 'places.formLngLabel': 'Γεωγραφικό Μήκος', + 'places.noCoordinates': 'Χωρίς συντεταγμένες, άρα όχι στον χάρτη', + 'places.noCoordinatesHint': + 'Επεξεργάσου το μέρος και διάλεξέ το από την αναζήτηση ή γράψε γεωγραφικό πλάτος και μήκος.', + 'places.noCoordinatesFormHint': + 'Αυτή η διεύθυνση δεν έχει ακόμη συντεταγμένες, οπότε το μέρος δεν θα εμφανιστεί στον χάρτη. Διάλεξέ το από την αναζήτηση παραπάνω ή γράψε γεωγραφικό πλάτος και μήκος.', 'places.formCategory': 'Κατηγορία', 'places.noCategory': 'Χωρίς Κατηγορία', 'places.newCategory': 'Νέα κατηγορία', diff --git a/shared/src/i18n/he/places.ts b/shared/src/i18n/he/places.ts index 5cc10483a..99bb0b4d8 100644 --- a/shared/src/i18n/he/places.ts +++ b/shared/src/i18n/he/places.ts @@ -98,6 +98,10 @@ const places: TranslationStrings = { 'places.formLng': 'קו אורך (למשל 2.3522)', 'places.formLatLabel': 'קו רוחב', 'places.formLngLabel': 'קו אורך', + 'places.noCoordinates': 'אין קואורדינטות, ולכן לא על המפה', + 'places.noCoordinatesHint': 'ערכו את המקום ובחרו אותו מהחיפוש, או הזינו קו רוחב וקו אורך.', + 'places.noCoordinatesFormHint': + 'לכתובת הזו עדיין אין קואורדינטות, ולכן המקום לא יופיע על המפה. בחרו אותו מהחיפוש למעלה או הזינו קו רוחב וקו אורך.', 'places.formCategory': 'קטגוריה', 'places.noCategory': 'ללא קטגוריה', 'places.newCategory': 'קטגוריה חדשה', diff --git a/shared/src/i18n/hu/places.ts b/shared/src/i18n/hu/places.ts index 981e95b79..32792fe8b 100644 --- a/shared/src/i18n/hu/places.ts +++ b/shared/src/i18n/hu/places.ts @@ -91,6 +91,11 @@ const places: TranslationStrings = { 'places.formLng': 'Hosszúsági fok (pl. 2.3522)', 'places.formLatLabel': 'Szélességi fok', 'places.formLngLabel': 'Hosszúsági fok', + 'places.noCoordinates': 'Nincsenek koordináták, ezért nincs a térképen', + 'places.noCoordinatesHint': + 'Szerkeszd a helyet, és válaszd ki a keresésből, vagy add meg a szélességet és a hosszúságot.', + 'places.noCoordinatesFormHint': + 'Ehhez a címhez még nincsenek koordináták, így a hely nem jelenik meg a térképen. Válaszd ki a fenti keresésből, vagy add meg a szélességet és a hosszúságot.', 'places.formCategory': 'Kategória', 'places.noCategory': 'Nincs kategória', 'places.newCategory': 'Új kategória', diff --git a/shared/src/i18n/id/places.ts b/shared/src/i18n/id/places.ts index 1be846bc7..c3666c1e6 100644 --- a/shared/src/i18n/id/places.ts +++ b/shared/src/i18n/id/places.ts @@ -82,6 +82,10 @@ const places: TranslationStrings = { 'places.formLng': 'Bujur (mis. 2.3522)', 'places.formLatLabel': 'Lintang', 'places.formLngLabel': 'Bujur', + 'places.noCoordinates': 'Tanpa koordinat, jadi tidak ada di peta', + 'places.noCoordinatesHint': 'Edit tempat ini dan pilih dari pencarian, atau masukkan lintang dan bujurnya.', + 'places.noCoordinatesFormHint': + 'Alamat ini belum punya koordinat, jadi tempat ini tidak akan muncul di peta. Pilih dari pencarian di atas atau masukkan lintang dan bujur.', 'places.formCategory': 'Kategori', 'places.noCategory': 'Tanpa Kategori', 'places.newCategory': 'Kategori baru', diff --git a/shared/src/i18n/it/places.ts b/shared/src/i18n/it/places.ts index 37d6ebc84..16f701534 100644 --- a/shared/src/i18n/it/places.ts +++ b/shared/src/i18n/it/places.ts @@ -91,6 +91,10 @@ const places: TranslationStrings = { 'places.formLng': 'Longitudine (es. 2.3522)', 'places.formLatLabel': 'Latitudine', 'places.formLngLabel': 'Longitudine', + 'places.noCoordinates': 'Nessuna coordinata, quindi non è sulla mappa', + 'places.noCoordinatesHint': 'Modifica il luogo e sceglilo dalla ricerca, oppure inserisci latitudine e longitudine.', + 'places.noCoordinatesFormHint': + 'Questo indirizzo non ha ancora coordinate, quindi il luogo non comparirà sulla mappa. Sceglilo dalla ricerca qui sopra oppure inserisci latitudine e longitudine.', 'places.formCategory': 'Categoria', 'places.noCategory': 'Nessuna categoria', 'places.newCategory': 'Nuova categoria', diff --git a/shared/src/i18n/ja/places.ts b/shared/src/i18n/ja/places.ts index 7f01710aa..90c65d738 100644 --- a/shared/src/i18n/ja/places.ts +++ b/shared/src/i18n/ja/places.ts @@ -83,6 +83,10 @@ const places: TranslationStrings = { 'places.formLng': '経度(例:2.3522)', 'places.formLatLabel': '緯度', 'places.formLngLabel': '経度', + 'places.noCoordinates': '座標がないため地図に表示されません', + 'places.noCoordinatesHint': '場所を編集して検索から選ぶか、緯度と経度を入力してください。', + 'places.noCoordinatesFormHint': + 'この住所にはまだ座標がないため、地図に表示されません。上の検索から選ぶか、緯度と経度を入力してください。', 'places.formCategory': 'カテゴリ', 'places.noCategory': 'カテゴリなし', 'places.newCategory': '新しいカテゴリ', diff --git a/shared/src/i18n/ko/places.ts b/shared/src/i18n/ko/places.ts index 3b700873c..dedb75016 100644 --- a/shared/src/i18n/ko/places.ts +++ b/shared/src/i18n/ko/places.ts @@ -82,6 +82,10 @@ const places: TranslationStrings = { 'places.formLng': '경도 (예: 2.3522)', 'places.formLatLabel': '위도', 'places.formLngLabel': '경도', + 'places.noCoordinates': '좌표가 없어 지도에 표시되지 않음', + 'places.noCoordinatesHint': '장소를 편집해 검색에서 고르거나 위도와 경도를 입력하세요.', + 'places.noCoordinatesFormHint': + '이 주소에는 아직 좌표가 없어 지도에 표시되지 않습니다. 위 검색에서 고르거나 위도와 경도를 입력하세요.', 'places.formCategory': '카테고리', 'places.noCategory': '카테고리 없음', 'places.newCategory': '새 카테고리', diff --git a/shared/src/i18n/nl/places.ts b/shared/src/i18n/nl/places.ts index 9a5c97598..1c2f3bcd4 100644 --- a/shared/src/i18n/nl/places.ts +++ b/shared/src/i18n/nl/places.ts @@ -91,6 +91,10 @@ const places: TranslationStrings = { 'places.formLng': 'Lengtegraad (bijv. 2.3522)', 'places.formLatLabel': 'Breedtegraad', 'places.formLngLabel': 'Lengtegraad', + 'places.noCoordinates': 'Geen coördinaten, dus niet op de kaart', + 'places.noCoordinatesHint': 'Bewerk de plek en kies hem in de zoekfunctie, of vul de breedte- en lengtegraad in.', + 'places.noCoordinatesFormHint': + 'Dit adres heeft nog geen coördinaten, dus de plek verschijnt niet op de kaart. Kies hem in de zoekfunctie hierboven of vul de breedte- en lengtegraad in.', 'places.formCategory': 'Categorie', 'places.noCategory': 'Geen categorie', 'places.newCategory': 'Nieuwe categorie', diff --git a/shared/src/i18n/pl/places.ts b/shared/src/i18n/pl/places.ts index d49877b15..d3f37eb84 100644 --- a/shared/src/i18n/pl/places.ts +++ b/shared/src/i18n/pl/places.ts @@ -91,6 +91,10 @@ const places: TranslationStrings = { 'places.formLng': 'Długość (np. 2.3522)', 'places.formLatLabel': 'Szerokość', 'places.formLngLabel': 'Długość', + 'places.noCoordinates': 'Brak współrzędnych, więc nie ma na mapie', + 'places.noCoordinatesHint': 'Edytuj miejsce i wybierz je z wyszukiwania albo wpisz szerokość i długość geograficzną.', + 'places.noCoordinatesFormHint': + 'Ten adres nie ma jeszcze współrzędnych, więc miejsce nie pojawi się na mapie. Wybierz je z wyszukiwania powyżej albo wpisz szerokość i długość geograficzną.', 'places.formCategory': 'Kategoria', 'places.noCategory': 'Brak kategorii', 'places.newCategory': 'Nowa kategoria', diff --git a/shared/src/i18n/ru/places.ts b/shared/src/i18n/ru/places.ts index d7292ba67..b59cd57b4 100644 --- a/shared/src/i18n/ru/places.ts +++ b/shared/src/i18n/ru/places.ts @@ -107,6 +107,10 @@ const places: TranslationStrings = { 'places.formLng': 'Долгота (напр. 2.3522)', 'places.formLatLabel': 'Широта', 'places.formLngLabel': 'Долгота', + 'places.noCoordinates': 'Нет координат, поэтому не на карте', + 'places.noCoordinatesHint': 'Измени место и выбери его в поиске или введи широту и долготу.', + 'places.noCoordinatesFormHint': + 'У этого адреса пока нет координат, поэтому место не появится на карте. Выбери его в поиске выше или введи широту и долготу.', 'places.formCategory': 'Категория', 'places.noCategory': 'Без категории', 'places.newCategory': 'Новая категория', diff --git a/shared/src/i18n/sk/places.ts b/shared/src/i18n/sk/places.ts index a65b8668f..801b49f96 100644 --- a/shared/src/i18n/sk/places.ts +++ b/shared/src/i18n/sk/places.ts @@ -100,6 +100,10 @@ const places: TranslationStrings = { 'places.formLng': 'Zemepisná dĺžka (napr. 2.3522)', 'places.formLatLabel': 'Zemepisná šírka', 'places.formLngLabel': 'Zemepisná dĺžka', + 'places.noCoordinates': 'Bez súradníc, preto nie je na mape', + 'places.noCoordinatesHint': 'Uprav miesto a vyber ho vo vyhľadávaní, alebo zadaj zemepisnú šírku a dĺžku.', + 'places.noCoordinatesFormHint': + 'Táto adresa zatiaľ nemá súradnice, takže sa miesto na mape nezobrazí. Vyber ho vo vyhľadávaní vyššie, alebo zadaj zemepisnú šírku a dĺžku.', 'places.formCategory': 'Kategória', 'places.noCategory': 'Bez kategórie', 'places.newCategory': 'Nová kategória', diff --git a/shared/src/i18n/sv/places.ts b/shared/src/i18n/sv/places.ts index 887c1d59b..421c10ac5 100644 --- a/shared/src/i18n/sv/places.ts +++ b/shared/src/i18n/sv/places.ts @@ -91,6 +91,10 @@ const places: TranslationStrings = { 'places.formLng': 'Longitud (t.ex. 2.3522)', 'places.formLatLabel': 'Latitud', 'places.formLngLabel': 'Longitud', + 'places.noCoordinates': 'Inga koordinater, så inte på kartan', + 'places.noCoordinatesHint': 'Redigera platsen och välj den i sökningen, eller ange latitud och longitud.', + 'places.noCoordinatesFormHint': + 'Den här adressen har inga koordinater än, så platsen syns inte på kartan. Välj den i sökningen ovan eller ange latitud och longitud.', 'places.formCategory': 'Kategori', 'places.noCategory': 'Ingen kategori', 'places.newCategory': 'Ny kategori', diff --git a/shared/src/i18n/th/places.ts b/shared/src/i18n/th/places.ts index c40f10976..62798468c 100644 --- a/shared/src/i18n/th/places.ts +++ b/shared/src/i18n/th/places.ts @@ -77,6 +77,10 @@ const places: TranslationStrings = { 'places.formLng': 'ลองจิจูด (เช่น 2.3522)', 'places.formLatLabel': 'ละติจูด', 'places.formLngLabel': 'ลองจิจูด', + 'places.noCoordinates': 'ไม่มีพิกัด จึงไม่แสดงบนแผนที่', + 'places.noCoordinatesHint': 'แก้ไขสถานที่แล้วเลือกจากการค้นหา หรือใส่ละติจูดและลองจิจูด', + 'places.noCoordinatesFormHint': + 'ที่อยู่นี้ยังไม่มีพิกัด สถานที่จึงจะไม่แสดงบนแผนที่ เลือกจากการค้นหาด้านบน หรือใส่ละติจูดและลองจิจูด', 'places.formCategory': 'หมวดหมู่', 'places.noCategory': 'ไม่มีหมวดหมู่', 'places.newCategory': 'หมวดหมู่ใหม่', diff --git a/shared/src/i18n/tr/places.ts b/shared/src/i18n/tr/places.ts index 259c0b8ff..a6a04bd30 100644 --- a/shared/src/i18n/tr/places.ts +++ b/shared/src/i18n/tr/places.ts @@ -93,6 +93,10 @@ const places: TranslationStrings = { 'places.formLng': 'Boylam (ör. 2,3522)', 'places.formLatLabel': 'Enlem', 'places.formLngLabel': 'Boylam', + 'places.noCoordinates': 'Koordinat yok, bu yüzden haritada değil', + 'places.noCoordinatesHint': 'Yeri düzenle ve aramadan seç ya da enlem ve boylamını gir.', + 'places.noCoordinatesFormHint': + 'Bu adresin henüz koordinatı yok, bu yüzden yer haritada görünmeyecek. Yukarıdaki aramadan seç ya da enlem ve boylamı gir.', 'places.formCategory': 'Kategori', 'places.noCategory': 'Kategori Yok', 'places.newCategory': 'Yeni kategori', diff --git a/shared/src/i18n/uk/places.ts b/shared/src/i18n/uk/places.ts index 14c2bbecc..cb47f626c 100644 --- a/shared/src/i18n/uk/places.ts +++ b/shared/src/i18n/uk/places.ts @@ -107,6 +107,10 @@ const places: TranslationStrings = { 'places.formLng': 'Довгота (наприклад, 2.3522)', 'places.formLatLabel': 'Широта', 'places.formLngLabel': 'Довгота', + 'places.noCoordinates': 'Немає координат, тому не на мапі', + 'places.noCoordinatesHint': 'Відредагуй місце й вибери його в пошуку або введи широту й довготу.', + 'places.noCoordinatesFormHint': + 'Ця адреса ще не має координат, тож місце не з’явиться на мапі. Вибери його в пошуку вище або введи широту й довготу.', 'places.formCategory': 'Категорія', 'places.noCategory': 'Без категорії', 'places.newCategory': 'Нова категорія', diff --git a/shared/src/i18n/vi/places.ts b/shared/src/i18n/vi/places.ts index 5eda89c28..c21db914f 100644 --- a/shared/src/i18n/vi/places.ts +++ b/shared/src/i18n/vi/places.ts @@ -83,6 +83,10 @@ const places: TranslationStrings = { 'places.formLng': 'Kinh độ (ví dụ: 2,3522)', 'places.formLatLabel': 'Vĩ độ', 'places.formLngLabel': 'Kinh độ', + 'places.noCoordinates': 'Không có tọa độ nên không hiện trên bản đồ', + 'places.noCoordinatesHint': 'Hãy sửa địa điểm và chọn từ ô tìm kiếm, hoặc nhập vĩ độ và kinh độ.', + 'places.noCoordinatesFormHint': + 'Địa chỉ này chưa có tọa độ nên địa điểm sẽ không hiện trên bản đồ. Hãy chọn từ ô tìm kiếm phía trên hoặc nhập vĩ độ và kinh độ.', 'places.formCategory': 'Loại', 'places.noCategory': 'Không có danh mục', 'places.newCategory': 'Danh mục mới', diff --git a/shared/src/i18n/zh-TW/places.ts b/shared/src/i18n/zh-TW/places.ts index daeb787f7..099fd1544 100644 --- a/shared/src/i18n/zh-TW/places.ts +++ b/shared/src/i18n/zh-TW/places.ts @@ -82,6 +82,10 @@ const places: TranslationStrings = { 'places.formLng': '經度(如 2.3522)', 'places.formLatLabel': '緯度', 'places.formLngLabel': '經度', + 'places.noCoordinates': '沒有座標,因此不在地圖上', + 'places.noCoordinatesHint': '編輯此地點並從搜尋中選擇,或輸入緯度和經度。', + 'places.noCoordinatesFormHint': + '此地址還沒有座標,因此該地點不會顯示在地圖上。請從上方搜尋中選擇,或輸入緯度和經度。', 'places.formCategory': '分類', 'places.noCategory': '無分類', 'places.newCategory': '新增分類', diff --git a/shared/src/i18n/zh/places.ts b/shared/src/i18n/zh/places.ts index 8950ee73c..57c135ab3 100644 --- a/shared/src/i18n/zh/places.ts +++ b/shared/src/i18n/zh/places.ts @@ -82,6 +82,10 @@ const places: TranslationStrings = { 'places.formLng': '经度(如 2.3522)', 'places.formLatLabel': '纬度', 'places.formLngLabel': '经度', + 'places.noCoordinates': '没有坐标,因此不在地图上', + 'places.noCoordinatesHint': '编辑该地点并从搜索中选择,或输入纬度和经度。', + 'places.noCoordinatesFormHint': + '此地址还没有坐标,因此该地点不会显示在地图上。请从上方搜索中选择,或输入纬度和经度。', 'places.formCategory': '分类', 'places.noCategory': '无分类', 'places.newCategory': '新建分类', diff --git a/wiki/Places-and-Search.md b/wiki/Places-and-Search.md index ddc7f5df6..09b87f24c 100644 --- a/wiki/Places-and-Search.md +++ b/wiki/Places-and-Search.md @@ -21,7 +21,7 @@ The right column of the Plan tab lists every place of the trip. Its head band ha With a day open and **Planned** chosen, the list shows only that day's places, and a chip says **Showing the open day only**. Its **X** (*Show the whole trip*) closes the day. See [Trip Planner Overview](Trip-Planner-Overview#layout). -Each row shows the place's photo or category tile, a short stroke in the track's colour for an imported track, the category icon, the name, the average member rating, and a line with the description or the address. Click a row to open the [place inspector](#the-place-inspector), or drag it onto a day. While a day is open, a place that is not on it yet shows a **+** (*+ Day*) that puts it on that day. +Each row shows the place's photo or category tile, a short stroke in the track's colour for an imported track, the category icon, the name, the average member rating, and a line with the description or the address. A place with an address but no coordinates carries a crossed-out pin beside its name (*No coordinates, so not on the map*), on the phone too. Click a row to open the [place inspector](#the-place-inspector), or drag it onto a day. While a day is open, a place that is not on it yet shows a **+** (*+ Day*) that puts it on that day. The **…** at the end of a row (*More options*) offers what a right-click offers: **Edit**, **+ Day** while a day is open, **Open Website**, **Google Maps**, **Save to Collection** with the [Collections](Collections) addon, and **Delete**. @@ -71,7 +71,7 @@ Click **Select** (the tick next to the search field) to switch the list into sel |---|---| | Name | Required. Typed into the head band. | | Category | Pick one, **No Category**, or create a new one with the **+** (a new category starts with the colour `#6366f1` and the `MapPin` icon). | -| Address | Free text. | +| Address | Free text. Typing an address does not place the pin: without a pick from the search, a pasted link, coordinates or a point on the map the place has no coordinates and does not appear on the map. The form says so under the field while that is the case. | | Latitude / Longitude | Decimal degrees. See [Entering coordinates manually](#entering-coordinates-manually). | | Start / End | Only when you edit the place from a day: the time of this visit. | | Notes for this day | Only when you edit the place from a day: a note for this visit alone. It shows under the place in the day plan. | @@ -275,7 +275,7 @@ Click a place in the places column, in a day, or on the map, and its inspector o ![The place inspector over the map: the photo, name, address, category and coordinates in its head, then the rating, the notes, the stay booked there under Bookings and the files, with Add to Day, Navigation, Save to Collection, Delete and Edit at its foot](assets/PlaceInspector.png) -- **The head band** shows the photo (a click opens it in a gallery, see [The photo gallery](#the-photo-gallery)), with a green ring while the place is open and a red one while it is closed, the name (double-click it to rename the place), the address on one line with the full address in a tooltip, and pills: **Open** or **Closed**, the category, the time of the visit, the Google rating, the phone number to call, the e-mail address, and the coordinates. The chevron (**Collapse**) folds the inspector to a slim bar with the photo and the name, so the place stays selected and its marker highlighted while you pan the map; **Expand** unfolds it again, and the next place you pick opens in full. **X** closes the inspector and deselects the place. +- **The head band** shows the photo (a click opens it in a gallery, see [The photo gallery](#the-photo-gallery)), with a green ring while the place is open and a red one while it is closed, the name (double-click it to rename the place), the address on one line with the full address in a tooltip, and pills: **Open** or **Closed**, the category, the time of the visit, the Google rating, the phone number to call, the e-mail address, and the coordinates. A place with an address but no coordinates shows **No coordinates, so not on the map** instead, with a tooltip on how to fix it. The chevron (**Collapse**) folds the inspector to a slim bar with the photo and the name, so the place stays selected and its marker highlighted while you pan the map; **Expand** unfolds it again, and the next place you pick opens in full. **X** closes the inspector and deselects the place. - **Rating**: every member's vote, see [Rating a place](#rating-a-place). - **Description**, **Notes** and **Notes for this day**, rendered as Markdown. - **Bookings** pinned to this stop, as small cards with the status, date, time and booking code. For a hotel, the stays booked there are listed too (see [Accommodations](Accommodations)). Click a card (*Open booking*) to open the booking's detail. Next to them, **Participants** says who joins this stop.