Reach the track colour from the mobile sheet

MPlaceSheet had no relation to route_geometry at all, so the section is
new. It saves through the single-field patch the sheet already uses for
the image, which keeps it clear of the edit form and its prefill.

The browse list gets the same colour stroke as the desktop sidebar. It
matters more here: the mobile map is full-bleed with no sidebar next to
it, so without the stroke a coloured line has nothing to tie it to.
This commit is contained in:
Maurice
2026-07-27 17:36:45 +02:00
committed by Maurice
parent 3c575a06f4
commit 502f604ca2
2 changed files with 60 additions and 1 deletions
@@ -8,6 +8,7 @@ import { useTripStore } from '../../../../store/tripStore'
import { useAddonStore } from '../../../../store/addonStore'
import PlaceAvatar from '../../../../components/shared/PlaceAvatar'
import { getCategoryIcon } from '../../../../components/shared/categoryIcons'
import { resolveTrackColor } from '../../../../components/Map/trackColors'
import MConfirmSheet from '../../settings/MConfirmSheet'
import type { MPlacesBrowserProps } from '../MTripShell'
import type { Place } from '../../../../types'
@@ -290,6 +291,15 @@ export default function MPlacesBrowser({ planner, shell }: MPlacesBrowserProps)
<PlaceAvatar place={place} category={cat} size={40} />
<span className="min-w-0 flex-1">
<span className="flex items-center gap-[6px]">
{/* Stroke in the track's colour — the mobile map is full-bleed
with no sidebar, so this is the only thing tying a line to
a row (#776). */}
{place.route_geometry && (
<span
className="h-[3px] w-[14px] flex-none rounded-full"
style={{ background: resolveTrackColor(place) }}
/>
)}
<CatIcon size={12} strokeWidth={2.2} className="flex-none" style={{ color: cat?.color || 'var(--m-muted)' }} />
<span className="truncate text-[0.8125rem] font-semibold text-m-ink">{place.name}</span>
</span>
@@ -1,7 +1,7 @@
import { useMemo, useRef, useState } from 'react'
import {
Bookmark, Camera, ExternalLink, Loader2, Map as MapIcon, Navigation, Paperclip,
Pencil, Phone, Plus, Trash2, Upload, X,
Pencil, Phone, Plus, Route, Trash2, Upload, X,
} from 'lucide-react'
import MSheet from '../../../components/MSheet'
import type { MTripSheetsProps } from '../MTripShell'
@@ -14,6 +14,8 @@ import { useSaveToCollectionStore } from '../../../../store/saveToCollectionStor
import { collectionTargetFromPlace } from '../lib/collectionTarget'
import { getCategoryIcon } from '../../../../components/shared/categoryIcons'
import PlaceRating from '../../../../components/shared/StarRating'
import TrackColorPicker from '../../../../components/shared/TrackColorPicker'
import { resolveTrackColor, inheritedTrackColor } from '../../../../components/Map/trackColors'
import { avatarSrc } from '../../../../utils/avatarSrc'
import { openFile } from '../../../../utils/fileDownload'
import { getGoogleMapsUrlForPlace } from '../../../../components/Planner/placeGoogleMaps'
@@ -42,6 +44,7 @@ export default function MPlaceSheet({ planner, shell }: MTripSheetsProps) {
const [participantPickerOpen, setParticipantPickerOpen] = useState(false)
const [uploading, setUploading] = useState(false)
const [imgBusy, setImgBusy] = useState(false)
const [colorPickerOpen, setColorPickerOpen] = useState(false)
const fileInputRef = useRef<HTMLInputElement | null>(null)
const imageInputRef = useRef<HTMLInputElement | null>(null)
@@ -132,6 +135,15 @@ export default function MPlaceSheet({ planner, shell }: MTripSheetsProps) {
}
}
const handleTrackColor = async (color: string | null) => {
if (!place) return
try {
await planner.tripActions.updatePlace(planner.tripId, place.id, { route_color: color })
} catch (err: unknown) {
planner.toast.error(translateApiError(t, err, 'common.unknownError'))
}
}
const handleImageRemove = async () => {
if (!place) return
setImgBusy(true)
@@ -290,6 +302,43 @@ export default function MPlaceSheet({ planner, shell }: MTripSheetsProps) {
</>
)}
{/* ── Track colour (#776) ── */}
{place.route_geometry && (
<>
<Eyebrow className="mb-[6px] mt-3">{t('inspector.trackColor')}</Eyebrow>
<div className={`rounded-[14px] px-3 py-[10px] ${INNER_CLS}`}>
<button
type="button"
onClick={() => setColorPickerOpen(v => !v)}
aria-expanded={colorPickerOpen}
className="flex w-full items-center justify-between gap-3"
>
{/* The eyebrow above already names the section — this row says
which colour is in effect, not the same word again. */}
<span className="flex min-w-0 items-center gap-2">
<Route size={14} className="shrink-0 text-m-muted" />
<span className="truncate font-geist text-[0.75rem] font-medium">
{place.route_color ?? t('inspector.trackColorAuto')}
</span>
</span>
<span
className="h-6 w-6 shrink-0 rounded-full ring-1 ring-inset ring-black/10 dark:ring-white/15"
style={{ background: resolveTrackColor(place) }}
/>
</button>
{colorPickerOpen && (
<div className="mt-[10px] border-t border-[color:var(--m-faint)] pt-[10px]">
<TrackColorPicker
value={place.route_color ?? null}
inheritedColor={inheritedTrackColor(place)}
onChange={handleTrackColor}
/>
</div>
)}
</div>
</>
)}
{/* ── Day assignments ── */}
<Eyebrow className="mb-[6px] mt-3">{t('mobileTrip.assignedDays')}</Eyebrow>
<div className="flex flex-wrap items-center gap-[6px]">