Show how long trains have been stopped#139
Conversation
Track and display how long trains have been stopped. Add getTrainStopKey helper and stoppedTrainsSince state in Map to record a timestamp when a train's velocity reaches zero, update it on fetch, and pass the data down to TrainsList, TrainMarker and SelectedTrainPopup. TrainText now accepts stoppedSince, computes a live-updating stopped duration (formatStoppedDuration) and shows it when the train is stationary. Minor prop and typing updates applied to components.
📝 WalkthroughWalkthroughThis PR adds stopped-train duration tracking. Map.tsx computes per-train stop timestamps keyed by train id or TrainNoLocal, storing them in state. This value is propagated through TrainsList and TrainMarker to SelectedTrainPopup and TrainText, which formats and displays elapsed stopped duration with a live-updating timer. ChangesStopped train duration feature
Estimated code review effort: 2 (Simple) | ~15 minutes Sequence Diagram(s)sequenceDiagram
participant Map
participant TrainsList
participant TrainMarker
participant SelectedTrainPopup
participant TrainText
Map->>Map: getTrains fetch, compute stoppedTrainsSince
Map->>TrainsList: trains, stoppedTrainsSince
TrainsList->>TrainMarker: stoppedSince (per train)
TrainMarker->>TrainText: stoppedSince
Map->>SelectedTrainPopup: stoppedSince (selected train)
SelectedTrainPopup->>TrainText: stoppedSince
TrainText->>TrainText: compute stoppedSeconds, render formatted duration
Related PRs: None identified. Suggested labels: enhancement Suggested reviewers: None identified. 🐰 A train stands still, the clock ticks on, 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/map/components/Map.tsx`:
- Around line 46-47: The train stop key logic is duplicated in Map.tsx and
TrainsList.tsx, which can cause the stoppedTrainsSince lookup to drift if one
copy changes. Extract the shared getTrainStopKey helper into a common utility
module and import it from both Map and TrainsList so the key used to build and
read the stoppedTrainsSince map stays consistent.
- Around line 83-109: The stopped-train tracking in getTrains currently rebuilds
stoppedTrainsSince only from the latest trainsData, so a train can lose its
original stop timestamp if it is briefly missing or its key changes. Update the
stopped-train merge logic inside setStoppedTrainsSince to preserve the existing
timestamp for any train that was already stopped and is still considered
stopped, and only remove/reset entries when the train is seen moving again. Use
getTrainStopKey, trainsData, and previousStoppedTrainsSince to keep the identity
stable across polls.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 8ae57de6-d3c0-4e7e-9269-38c8010e956f
📒 Files selected for processing (5)
packages/map/components/Map.tsxpackages/map/components/Markers/TrainMarker.tsxpackages/map/components/SelectedTrainPopup.tsxpackages/map/components/TrainText.tsxpackages/map/components/TrainsList.tsx
| const getTrainStopKey = (train: Train) => train.id ?? train.TrainNoLocal; | ||
|
|
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Duplicate getTrainStopKey implementation across files.
The exact same helper is redefined independently in packages/map/components/TrainsList.tsx (const getTrainStopKey = (train: Train) => train.id ?? train.TrainNoLocal;). Since TrainsList looks up entries in the stoppedTrainsSince map produced here using its own copy of this key function, any future edit to one copy without the other will silently desynchronize the lookup key and break the stopped-duration feature.
Extract this into a shared module (e.g. packages/map/utils/trains.ts) and import it in both files.
♻️ Proposed fix
+// packages/map/utils/trains.ts
+import type { Train } from "`@simrail/types`";
+
+export const getTrainStopKey = (train: Train) => train.id ?? train.TrainNoLocal;-const getTrainStopKey = (train: Train) => train.id ?? train.TrainNoLocal;
+import { getTrainStopKey } from "../utils/trains";🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/map/components/Map.tsx` around lines 46 - 47, The train stop key
logic is duplicated in Map.tsx and TrainsList.tsx, which can cause the
stoppedTrainsSince lookup to drift if one copy changes. Extract the shared
getTrainStopKey helper into a common utility module and import it from both Map
and TrainsList so the key used to build and read the stoppedTrainsSince map
stays consistent.
| const [stoppedTrainsSince, setStoppedTrainsSince] = useState< | ||
| Record<string, number> | ||
| >({}); | ||
|
|
||
| const getTrains = useCallback(() => { | ||
| fetch(`https://panel.simrail.eu:8084/trains-open?serverCode=${serverId}`) | ||
| .then((res) => res.json()) | ||
| .then((fetchedTrains) => { | ||
| setTrains(fetchedTrains.data); | ||
| const trainsData: Train[] = fetchedTrains.data; | ||
|
|
||
| setStoppedTrainsSince((previousStoppedTrainsSince) => { | ||
| const nextStoppedTrainsSince: Record<string, number> = {}; | ||
|
|
||
| for (const train of trainsData) { | ||
| const trainKey = getTrainStopKey(train); | ||
| const isStopped = Math.round(train.TrainData.Velocity) === 0; | ||
|
|
||
| if (isStopped) { | ||
| nextStoppedTrainsSince[trainKey] = | ||
| previousStoppedTrainsSince[trainKey] ?? Date.now(); | ||
| } | ||
| } | ||
|
|
||
| return nextStoppedTrainsSince; | ||
| }); | ||
|
|
||
| setTrains(trainsData); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Map.tsx slice =="
sed -n '1,220p' packages/map/components/Map.tsx
echo
echo "== Search getTrainStopKey =="
rg -n "getTrainStopKey|stoppedTrainsSince|setStoppedTrainsSince|TrainNoLocal|TrainData\.Velocity" packages/map -SRepository: simrail/map-v2
Length of output: 8148
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Train type definition =="
rg -n "export type Train|type Train|interface Train" -S .
echo
echo "== stoppedSince consumers =="
sed -n '1,140p' packages/map/components/TrainsList.tsx
echo
sed -n '1,220p' packages/map/components/SelectedTrainPopup.tsxRepository: simrail/map-v2
Length of output: 2626
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Search for Train id field usage in repo =="
rg -n "\.id\b|TrainNoLocal|ControlledBySteamID|TrainData" packages -S
echo
echo "== Look for local type re-exports or docs around `@simrail/types` =="
rg -n "`@simrail/types`|Train =" packages -SRepository: simrail/map-v2
Length of output: 6209
Preserve stopped timestamps across a missed poll — nextStoppedTrainsSince rebuilds from only trainsData, so a still-stopped train loses its elapsed time if one response omits it or if its key switches between id and TrainNoLocal. Keep the previous timestamp until the train is seen moving.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/map/components/Map.tsx` around lines 83 - 109, The stopped-train
tracking in getTrains currently rebuilds stoppedTrainsSince only from the latest
trainsData, so a train can lose its original stop timestamp if it is briefly
missing or its key changes. Update the stopped-train merge logic inside
setStoppedTrainsSince to preserve the existing timestamp for any train that was
already stopped and is still considered stopped, and only remove/reset entries
when the train is seen moving again. Use getTrainStopKey, trainsData, and
previousStoppedTrainsSince to keep the identity stable across polls.
Track and display how long trains have been stopped. Add getTrainStopKey helper and stoppedTrainsSince state in Map to record a timestamp when a train's velocity reaches zero, update it on fetch, and pass the data down to TrainsList, TrainMarker and SelectedTrainPopup. TrainText now accepts stoppedSince, computes a live-updating stopped duration (formatStoppedDuration) and shows it when the train is stationary. Minor prop and typing updates applied to components.
Summary by CodeRabbit