From 1bad0531b50bc0c9ea48149875e6c5766096dfa7 Mon Sep 17 00:00:00 2001 From: elpatron Date: Thu, 11 Jun 2026 14:37:19 +0200 Subject: [PATCH] feat: Abfrageort bei Gezeiten speichern und anzeigen Ort oder GPS-Koordinaten werden im Entry-Payload persistiert und im Tiden-Accordion sowie im Live-Journal-Modal als lesbare Zeile angezeigt. Co-authored-by: Cursor --- client/src/App.css | 8 +++ client/src/components/LiveLogView.tsx | 38 ++++-------- client/src/components/LogEntryEditor.tsx | 50 +++++++--------- client/src/i18n/locales/da.json | 3 + client/src/i18n/locales/de.json | 3 + client/src/i18n/locales/en.json | 3 + client/src/i18n/locales/es.json | 3 + client/src/i18n/locales/fr.json | 3 + client/src/i18n/locales/nb.json | 3 + client/src/i18n/locales/sv.json | 3 + client/src/utils/logEntryPayload.test.ts | 20 +++++++ client/src/utils/logEntryPayload.ts | 33 +++++++++- client/src/utils/tideLocation.test.ts | 33 +++++++++- client/src/utils/tideLocation.ts | 76 +++++++++++++++++++++++- 14 files changed, 218 insertions(+), 61 deletions(-) diff --git a/client/src/App.css b/client/src/App.css index dd60701..d98ec2b 100644 --- a/client/src/App.css +++ b/client/src/App.css @@ -4628,6 +4628,14 @@ html.theme-cupertino .events-scroll-container { line-height: 1.45; } +.tides-panel__location { + margin: 0; + font-size: 13.5px; + font-weight: 500; + color: var(--app-text); + line-height: 1.45; +} + .tides-panel__fields { margin: 0; } diff --git a/client/src/components/LiveLogView.tsx b/client/src/components/LiveLogView.tsx index cc280b3..d5a8beb 100644 --- a/client/src/components/LiveLogView.tsx +++ b/client/src/components/LiveLogView.tsx @@ -59,7 +59,11 @@ const formatSpeedKn = (speedKn: number) => import { parseOwmCurrentWeather } from '../utils/openWeatherMap.js' import { fetchOpenWeatherCurrent, WeatherApiError } from '../services/weather.js' import { fetchTidesByPlace, fetchTidesNearby, TidesApiError } from '../services/tides.js' -import { resolveTideFetchLocation } from '../utils/tideLocation.js' +import { + buildTideLocationMeta, + formatTideLocationLabel, + resolveTideFetchLocation +} from '../utils/tideLocation.js' import { parseTideTurtleForDate } from '../utils/tideTurtle.js' import { geolocationErrorI18nKey, @@ -211,10 +215,7 @@ export default function LiveLogView({ const [tidePreview, setTidePreview] = useState<{ highWater: string lowWater: string - placeName?: string - distanceKm?: number - source: 'gps' | 'departure' - departureQuery?: string + location: ReturnType } | null>(null) const [isOnline, setIsOnline] = useState(navigator.onLine) const [commentText, setCommentText] = useState('') @@ -851,10 +852,7 @@ export default function LiveLogView({ setTidePreview({ highWater: parsed.highWater, lowWater: parsed.lowWater, - placeName: parsed.placeName, - distanceKm: parsed.distanceKm, - source: location.source, - departureQuery: location.mode === 'by-place' ? location.query : undefined + location: buildTideLocationMeta(location, data) }) setModal('tides') } catch (err) { @@ -886,7 +884,8 @@ export default function LiveLogView({ void runQuickAction(async () => { await patchEntryTides(logbookId, entryId, { highWater: preview.highWater, - lowWater: preview.lowWater + lowWater: preview.lowWater, + ...preview.location }) setTidePreview(null) setModal('none') @@ -1585,24 +1584,9 @@ export default function LiveLogView({

{t('logs.tide_disclaimer')}

- {tidePreview.source === 'departure' && tidePreview.departureQuery ? ( + {formatTideLocationLabel(tidePreview.location, t) ? (

- {t('logs.tide_fetched_from_departure', { - place: tidePreview.placeName || tidePreview.departureQuery - })} -

- ) : tidePreview.source === 'gps' ? ( -

- {t('logs.tide_fetched_at_position')} -

- ) : tidePreview.placeName ? ( -

- {tidePreview.distanceKm != null - ? t('logs.tide_fetched_from', { - place: tidePreview.placeName, - distance: formatAppDecimal(tidePreview.distanceKm, { maximumFractionDigits: 1 }) ?? String(tidePreview.distanceKm) - }) - : tidePreview.placeName} + {formatTideLocationLabel(tidePreview.location, t)}

) : null}
diff --git a/client/src/components/LogEntryEditor.tsx b/client/src/components/LogEntryEditor.tsx index d2fe6f8..15a0d94 100644 --- a/client/src/components/LogEntryEditor.tsx +++ b/client/src/components/LogEntryEditor.tsx @@ -44,7 +44,13 @@ import { getLogbookAccess } from '../services/logbookAccess.js' import { PlausibleEvents, trackPlausibleEvent } from '../services/analytics.js' import { fetchOpenWeatherCurrent, WeatherApiError } from '../services/weather.js' import { fetchTidesByPlace, fetchTidesNearby, TidesApiError } from '../services/tides.js' -import { resolveTideFetchLocation } from '../utils/tideLocation.js' +import { + buildTideLocationMeta, + formatTideLocationLabel, + pickTideLocationMeta, + resolveTideFetchLocation, + type TideLocationMeta +} from '../utils/tideLocation.js' import { parseTideTurtleForDate } from '../utils/tideTurtle.js' import { buildTravelDayContext, @@ -306,8 +312,8 @@ export default function LogEntryEditor({ const [tidesCollapsed, setTidesCollapsed] = useState(true) const [tideHighWater, setTideHighWater] = useState('') const [tideLowWater, setTideLowWater] = useState('') + const [tideLocation, setTideLocation] = useState({}) const [tidesLoading, setTidesLoading] = useState(false) - const [tideFetchHint, setTideFetchHint] = useState('') const [tanksCollapsed, setTanksCollapsed] = useState(true) const [columnSelectorOpen, setColumnSelectorOpen] = useState(false) @@ -440,7 +446,7 @@ export default function LogEntryEditor({ consumption: parseAppDecimalOrZero(fuelConsumption) }, greywater: { level: parseAppDecimalOrZero(greywaterLevel) }, - tides: { highWater: tideHighWater, lowWater: tideLowWater }, + tides: { highWater: tideHighWater, lowWater: tideLowWater, ...tideLocation }, trackDistanceNm: parseOptionalFormDecimal(trackDistanceNm), trackSpeedMaxKn: parseOptionalFormDecimal(trackSpeedMaxKn), trackSpeedAvgKn: parseOptionalFormDecimal(trackSpeedAvgKn), @@ -453,7 +459,7 @@ export default function LogEntryEditor({ fwMorning, fwRefilled, fwEvening, fwConsumption, fuelMorning, fuelRefilled, fuelEvening, fuelConsumption, greywaterLevel, - tideHighWater, tideLowWater, + tideHighWater, tideLowWater, tideLocation, trackDistanceNm, trackSpeedMaxKn, trackSpeedAvgKn, motorHours, events, entryCrew @@ -504,6 +510,11 @@ export default function LogEntryEditor({ [fuelMorning, fuelRefilled, tankCapacities.fuelCapacityL] ) + const tideLocationLabel = useMemo( + () => formatTideLocationLabel(tideLocation, t), + [tideLocation, t] + ) + const currentFingerprint = useMemo(() => { const payload = buildPayloadForSigning() return JSON.stringify({ @@ -936,7 +947,7 @@ export default function LogEntryEditor({ const preloadedTides = readLogEntryTides(preloadedEntry as Record) setTideHighWater(preloadedTides.highWater) setTideLowWater(preloadedTides.lowWater) - setTideFetchHint('') + setTideLocation(pickTideLocationMeta(preloadedTides)) setSignSkipper(normalizeSignature(preloadedEntry.signSkipper) || '') setSignCrew(normalizeSignature(preloadedEntry.signCrew) || '') @@ -982,7 +993,7 @@ export default function LogEntryEditor({ const loadedTides = readLogEntryTides(decrypted as Record) setTideHighWater(loadedTides.highWater) setTideLowWater(loadedTides.lowWater) - setTideFetchHint('') + setTideLocation(pickTideLocationMeta(loadedTides)) setSignSkipper(normalizeSignature(decrypted.signSkipper) || '') setSignCrew(normalizeSignature(decrypted.signCrew) || '') @@ -1300,7 +1311,6 @@ export default function LogEntryEditor({ } setTidesLoading(true) - setTideFetchHint('') try { const loaded = await loadEntry(logbookId, entryId) const eventsForLocation = loaded @@ -1339,25 +1349,7 @@ export default function LogEntryEditor({ if (parsed.highWater) setTideHighWater(parsed.highWater) if (parsed.lowWater) setTideLowWater(parsed.lowWater) - - if (location.source === 'departure') { - setTideFetchHint( - t('logs.tide_fetched_from_departure', { - place: parsed.placeName || location.query - }) - ) - } else if (location.source === 'gps') { - setTideFetchHint(t('logs.tide_fetched_at_position')) - } else if (parsed.placeName) { - setTideFetchHint( - parsed.distanceKm != null - ? t('logs.tide_fetched_from', { - place: parsed.placeName, - distance: formatAppDecimal(parsed.distanceKm, { maximumFractionDigits: 1 }) ?? String(parsed.distanceKm) - }) - : parsed.placeName - ) - } + setTideLocation(buildTideLocationMeta(location, data)) } catch (err) { if (err instanceof TidesApiError) { if (err.code === 'OFFLINE') { @@ -2250,9 +2242,9 @@ export default function LogEntryEditor({

{t('logs.tide_disclaimer')}

- {tideFetchHint ? ( -

- {tideFetchHint} + {tideLocationLabel ? ( +

+ {tideLocationLabel}

) : null} diff --git a/client/src/i18n/locales/da.json b/client/src/i18n/locales/da.json index fa34b46..b3a3e85 100644 --- a/client/src/i18n/locales/da.json +++ b/client/src/i18n/locales/da.json @@ -202,6 +202,9 @@ "tide_no_data": "Ingen tidevandsdata for dette sted.", "tide_place_not_found": "“{{place}}” kunne ikke findes — angiv en kystby eller havn.", "tide_fetched_at_position": "Modelprognose ved aktuel position (Open-Meteo Marine).", + "tide_data_for_position": "Forespørgsel for position {{lat}}, {{lng}}", + "tide_data_for_place": "Forespørgsel for {{place}}", + "tide_data_for_place_and_position": "Forespørgsel for {{place}} ({{lat}}, {{lng}})", "tide_fetched_from": "Data fra {{place}} (ca. {{distance}} km væk)", "tide_fetched_from_departure": "Tidevand baseret på afgang “{{place}}” (ingen aktuel GPS-position).", "tide_applied_success": "Tidevand overført: højvande {{highWater}}, lavvande {{lowWater}}. Synligt i rejsedagseditoren under “Tidevand”.", diff --git a/client/src/i18n/locales/de.json b/client/src/i18n/locales/de.json index 2117590..8a0466a 100644 --- a/client/src/i18n/locales/de.json +++ b/client/src/i18n/locales/de.json @@ -202,6 +202,9 @@ "tide_no_data": "Für diesen Ort liegen keine Gezeitendaten vor.", "tide_place_not_found": "„{{place}}“ konnte nicht geortet werden — bitte einen Küstenort oder Hafen angeben.", "tide_fetched_at_position": "Modellprognose am aktuellen Standort (Open-Meteo Marine).", + "tide_data_for_position": "Abfrage für Position {{lat}}, {{lng}}", + "tide_data_for_place": "Abfrage für {{place}}", + "tide_data_for_place_and_position": "Abfrage für {{place}} ({{lat}}, {{lng}})", "tide_fetched_from": "Daten von {{place}} (ca. {{distance}} km entfernt)", "tide_fetched_from_departure": "Gezeiten basierend auf Abfahrtsort „{{place}}“ (keine aktuelle GPS-Position).", "tide_applied_success": "Gezeiten übernommen: Hochwasser {{highWater}}, Niedrigwasser {{lowWater}}. Im Reisetag-Editor unter „Tiden“ sichtbar.", diff --git a/client/src/i18n/locales/en.json b/client/src/i18n/locales/en.json index ffd487e..ca17aa0 100644 --- a/client/src/i18n/locales/en.json +++ b/client/src/i18n/locales/en.json @@ -202,6 +202,9 @@ "tide_no_data": "No tide data available for this location.", "tide_place_not_found": "“{{place}}” could not be geocoded — please use a coastal place or harbour name.", "tide_fetched_at_position": "Model forecast at current position (Open-Meteo Marine).", + "tide_data_for_position": "Query for position {{lat}}, {{lng}}", + "tide_data_for_place": "Query for {{place}}", + "tide_data_for_place_and_position": "Query for {{place}} ({{lat}}, {{lng}})", "tide_fetched_from": "Data from {{place}} (about {{distance}} km away)", "tide_fetched_from_departure": "Tides based on departure “{{place}}” (no current GPS position).", "tide_applied_success": "Tides applied: high water {{highWater}}, low water {{lowWater}}. Visible in the travel day editor under “Tides”.", diff --git a/client/src/i18n/locales/es.json b/client/src/i18n/locales/es.json index 0dff7f7..9e5f5b1 100644 --- a/client/src/i18n/locales/es.json +++ b/client/src/i18n/locales/es.json @@ -202,6 +202,9 @@ "tide_no_data": "No hay datos de marea para este lugar.", "tide_place_not_found": "«{{place}}» no se encontró — indica un lugar costero o puerto.", "tide_fetched_at_position": "Pronóstico modelo en la posición actual (Open-Meteo Marine).", + "tide_data_for_position": "Consulta para la posición {{lat}}, {{lng}}", + "tide_data_for_place": "Consulta para {{place}}", + "tide_data_for_place_and_position": "Consulta para {{place}} ({{lat}}, {{lng}})", "tide_fetched_from": "Datos de {{place}} (aprox. {{distance}} km)", "tide_fetched_from_departure": "Mareas según salida «{{place}}» (sin posición GPS actual).", "tide_applied_success": "Mareas guardadas: pleamar {{highWater}}, bajamar {{lowWater}}. Visible en el editor del día de viaje, sección «Mareas».", diff --git a/client/src/i18n/locales/fr.json b/client/src/i18n/locales/fr.json index 3108758..309809f 100644 --- a/client/src/i18n/locales/fr.json +++ b/client/src/i18n/locales/fr.json @@ -202,6 +202,9 @@ "tide_no_data": "Aucune donnée de marée pour cet endroit.", "tide_place_not_found": "« {{place}} » introuvable — indiquez un lieu côtier ou un port.", "tide_fetched_at_position": "Prévision modèle à la position actuelle (Open-Meteo Marine).", + "tide_data_for_position": "Requête pour la position {{lat}}, {{lng}}", + "tide_data_for_place": "Requête pour {{place}}", + "tide_data_for_place_and_position": "Requête pour {{place}} ({{lat}}, {{lng}})", "tide_fetched_from": "Données de {{place}} (env. {{distance}} km)", "tide_fetched_from_departure": "Marées basées sur le départ « {{place}} » (pas de position GPS actuelle).", "tide_applied_success": "Marées enregistrées : pleine mer {{highWater}}, basse mer {{lowWater}}. Visible dans l’éditeur du jour de voyage, section « Marées ».", diff --git a/client/src/i18n/locales/nb.json b/client/src/i18n/locales/nb.json index 766341c..00b3a43 100644 --- a/client/src/i18n/locales/nb.json +++ b/client/src/i18n/locales/nb.json @@ -202,6 +202,9 @@ "tide_no_data": "Ingen tidevannsdata for dette stedet.", "tide_place_not_found": "«{{place}}» ble ikke funnet — oppgi en kyststad eller havn.", "tide_fetched_at_position": "Modellprognose ved gjeldende posisjon (Open-Meteo Marine).", + "tide_data_for_position": "Forespørsel for posisjon {{lat}}, {{lng}}", + "tide_data_for_place": "Forespørsel for {{place}}", + "tide_data_for_place_and_position": "Forespørsel for {{place}} ({{lat}}, {{lng}})", "tide_fetched_from": "Data fra {{place}} (ca. {{distance}} km unna)", "tide_fetched_from_departure": "Tidevann basert på avreise «{{place}}» (ingen aktuell GPS-posisjon).", "tide_applied_success": "Tidevann lagret: høyvann {{highWater}}, lavvann {{lowWater}}. Synlig i reisedagseditoren under «Tidevann».", diff --git a/client/src/i18n/locales/sv.json b/client/src/i18n/locales/sv.json index c586d66..49aec33 100644 --- a/client/src/i18n/locales/sv.json +++ b/client/src/i18n/locales/sv.json @@ -202,6 +202,9 @@ "tide_no_data": "Inga tidvattendata för denna plats.", "tide_place_not_found": "“{{place}}” kunde inte hittas — ange en kustort eller hamn.", "tide_fetched_at_position": "Modellprognos vid aktuell position (Open-Meteo Marine).", + "tide_data_for_position": "Förfrågan för position {{lat}}, {{lng}}", + "tide_data_for_place": "Förfrågan för {{place}}", + "tide_data_for_place_and_position": "Förfrågan för {{place}} ({{lat}}, {{lng}})", "tide_fetched_from": "Data från {{place}} (ca {{distance}} km bort)", "tide_fetched_from_departure": "Tidvatten baserat på avgång “{{place}}” (ingen aktuell GPS-position).", "tide_applied_success": "Tidvatten tillämpat: högvatten {{highWater}}, lågvatten {{lowWater}}. Syns i resedagseditorn under “Tidvatten”.", diff --git a/client/src/utils/logEntryPayload.test.ts b/client/src/utils/logEntryPayload.test.ts index 26a3073..cca7eba 100644 --- a/client/src/utils/logEntryPayload.test.ts +++ b/client/src/utils/logEntryPayload.test.ts @@ -91,4 +91,24 @@ describe('buildLogEntryPayload tides', () => { }) expect(payload.tides).toEqual({ highWater: '18:34', lowWater: '12:05' }) }) + + it('persists tide location metadata', () => { + const payload = buildLogEntryPayload({ + ...base, + tides: { + highWater: '06:00', + lowWater: '00:04', + locationSource: 'gps', + lat: '53.624526', + lng: '7.155263' + } + }) + expect(payload.tides).toEqual({ + highWater: '06:00', + lowWater: '00:04', + locationSource: 'gps', + lat: '53.624526', + lng: '7.155263' + }) + }) }) diff --git a/client/src/utils/logEntryPayload.ts b/client/src/utils/logEntryPayload.ts index a9146f4..85b71f5 100644 --- a/client/src/utils/logEntryPayload.ts +++ b/client/src/utils/logEntryPayload.ts @@ -150,9 +150,15 @@ export function sortLogEventsByTime(events: T[]): T[] return [...events].sort((a, b) => (a.time || '').localeCompare(b.time || '')) } +export type TideLocationSource = 'gps' | 'departure' | 'geocoded' + export interface LogEntryTides { highWater: string lowWater: string + locationSource?: TideLocationSource + placeName?: string + lat?: string + lng?: string } export interface LogEntryPayloadInput { @@ -172,13 +178,28 @@ export interface LogEntryPayloadInput { entryCrew?: EntryCrewFields } +function readTideLocationSource(value: unknown): TideLocationSource | undefined { + const source = String(value ?? '').trim() + if (source === 'gps' || source === 'departure' || source === 'geocoded') return source + return undefined +} + export function readLogEntryTides(data: Record): LogEntryTides { const tides = data.tides as Record | undefined const highRaw = String(tides?.highWater ?? '').trim() const lowRaw = String(tides?.lowWater ?? '').trim() + const placeName = String(tides?.placeName ?? '').trim() + const lat = String(tides?.lat ?? '').trim() + const lng = String(tides?.lng ?? '').trim() + const locationSource = readTideLocationSource(tides?.locationSource) + return { highWater: parseTimeToHHMM(highRaw) ?? '', - lowWater: parseTimeToHHMM(lowRaw) ?? '' + lowWater: parseTimeToHHMM(lowRaw) ?? '', + ...(locationSource ? { locationSource } : {}), + ...(placeName ? { placeName } : {}), + ...(lat ? { lat } : {}), + ...(lng ? { lng } : {}) } } @@ -211,7 +232,15 @@ export function buildLogEntryPayload(input: LogEntryPayloadInput): Record = { highWater, lowWater } + if (input.tides.locationSource) tides.locationSource = input.tides.locationSource + const placeName = input.tides.placeName?.trim() + if (placeName) tides.placeName = placeName + const lat = input.tides.lat?.trim() + if (lat) tides.lat = lat + const lng = input.tides.lng?.trim() + if (lng) tides.lng = lng + payload.tides = tides } } diff --git a/client/src/utils/tideLocation.test.ts b/client/src/utils/tideLocation.test.ts index 28e743a..f63a443 100644 --- a/client/src/utils/tideLocation.test.ts +++ b/client/src/utils/tideLocation.test.ts @@ -1,6 +1,10 @@ import { describe, expect, it } from 'vitest' import { LIVE_EVENT_CODES } from './liveEventCodes.js' -import { resolveTideFetchLocation } from './tideLocation.js' +import { + buildTideLocationMeta, + formatTideLocationLabel, + resolveTideFetchLocation +} from './tideLocation.js' const entryDate = '2026-06-11' const nowMs = new Date('2026-06-11T12:00:00').getTime() @@ -108,6 +112,33 @@ describe('resolveTideFetchLocation', () => { expect(result).toEqual({ error: 'stale' }) }) + it('builds GPS location metadata from nearby fetch', () => { + const meta = buildTideLocationMeta( + { mode: 'nearby', lat: '53.624526', lng: '7.155263', source: 'gps' }, + { location: { name: 'Norddeich', lat: 53.62, lon: 7.15, source: 'coordinates' } } + ) + expect(meta).toEqual({ + locationSource: 'gps', + lat: '53.624526', + lng: '7.155263', + placeName: 'Norddeich' + }) + }) + + it('formats coordinate and place labels', () => { + const t = (key: string, options?: Record) => + `${key}:${JSON.stringify(options ?? {})}` + expect( + formatTideLocationLabel( + { locationSource: 'gps', lat: '53.62', lng: '7.15', placeName: 'Norddeich' }, + t + ) + ).toContain('tide_data_for_place_and_position') + expect( + formatTideLocationLabel({ locationSource: 'gps', lat: '53.62', lng: '7.15' }, t) + ).toContain('tide_data_for_position') + }) + it('returns missing without position or departure', () => { const result = resolveTideFetchLocation({ events: [], diff --git a/client/src/utils/tideLocation.ts b/client/src/utils/tideLocation.ts index 2839635..072b7dc 100644 --- a/client/src/utils/tideLocation.ts +++ b/client/src/utils/tideLocation.ts @@ -3,9 +3,11 @@ import { getLatestLoggedPosition, LIVE_LOG_TIDE_POSITION_MAX_AGE_MS } from './liveEventCodes.js' -import type { LogEventPayload } from './logEntryPayload.js' +import type { LogEntryTides, LogEventPayload, TideLocationSource } from './logEntryPayload.js' -export type TideLocationSource = 'gps' | 'departure' +export type { TideLocationSource } + +export type TideLocationMeta = Pick export type TideFetchLocation = | { mode: 'nearby'; lat: string; lng: string; source: 'gps' } @@ -45,3 +47,73 @@ export function resolveTideFetchLocation(options: { return { error: 'missing' } } + +function asRecord(value: unknown): Record | null { + return value && typeof value === 'object' && !Array.isArray(value) + ? (value as Record) + : null +} + +export function buildTideLocationMeta( + fetchLocation: TideFetchLocation, + apiData: Record +): TideLocationMeta { + const apiLocation = asRecord(apiData.location) + + if (fetchLocation.mode === 'nearby') { + return { + locationSource: 'gps', + lat: fetchLocation.lat, + lng: fetchLocation.lng, + placeName: apiLocation?.name ? String(apiLocation.name) : undefined + } + } + + const placeName = apiLocation?.name ? String(apiLocation.name) : fetchLocation.query + const lat = apiLocation?.lat != null && apiLocation.lat !== '' ? String(apiLocation.lat) : undefined + const lng = apiLocation?.lon != null && apiLocation.lon !== '' ? String(apiLocation.lon) : undefined + + return { + locationSource: apiLocation?.source === 'geocoded' ? 'geocoded' : 'departure', + placeName, + lat, + lng + } +} + +type TideLocationLabelT = ( + key: string, + options?: Record +) => string + +export function formatTideLocationLabel( + tides: TideLocationMeta, + t: TideLocationLabelT +): string { + const placeName = tides.placeName?.trim() + const lat = tides.lat?.trim() + const lng = tides.lng?.trim() + + if (placeName && lat && lng) { + return t('logs.tide_data_for_place_and_position', { place: placeName, lat, lng }) + } + if (lat && lng) { + return t('logs.tide_data_for_position', { lat, lng }) + } + if (placeName) { + if (tides.locationSource === 'departure') { + return t('logs.tide_fetched_from_departure', { place: placeName }) + } + return t('logs.tide_data_for_place', { place: placeName }) + } + return '' +} + +export function pickTideLocationMeta(tides: LogEntryTides): TideLocationMeta { + return { + locationSource: tides.locationSource, + placeName: tides.placeName, + lat: tides.lat, + lng: tides.lng + } +}