@@ -43,9 +43,40 @@ type DeployedWaypoint = {
4343 loading ?: boolean ;
4444} ;
4545
46- const NO_TRACK_SPECIFIED_ID = '__no_track_specified__' ;
4746const NO_TRACK_SPECIFIED_SYMBOL = '[ ]' ;
4847
48+ // Extracts the first integer (including negative) from a string, or null if none is found
49+ function extractFirstNumber ( s : string ) : number | null {
50+ const match = s . match ( / - ? \d + / ) ;
51+ return match ? parseInt ( match [ 0 ] , 10 ) : null ;
52+ }
53+
54+ //Sorts tracks for display in the track occupancy diagram.
55+ function sortTracks ( infraTracks : Track [ ] , virtualTracks : Track [ ] ) : Track [ ] {
56+ const ncTrack = virtualTracks . find ( ( t ) => t . id === NO_TRACK_SPECIFIED_SYMBOL ) ;
57+ const otherVirtual = virtualTracks . filter ( ( t ) => t . id !== NO_TRACK_SPECIFIED_SYMBOL ) ;
58+
59+ // First pass: alphabetical sort as the stable base (tracks without a name sort last)
60+ otherVirtual . sort ( ( a , b ) => {
61+ if ( ! a . name && ! b . name ) return 0 ;
62+ if ( ! a . name ) return 1 ;
63+ if ( ! b . name ) return - 1 ;
64+ return a . name . localeCompare ( b . name ) ;
65+ } ) ;
66+
67+ const withNumbers : Track [ ] = [ ] ;
68+ const withoutNumbers : Track [ ] = [ ] ;
69+ for ( const track of otherVirtual ) {
70+ if ( track . name && extractFirstNumber ( track . name ) !== null ) withNumbers . push ( track ) ;
71+ else withoutNumbers . push ( track ) ;
72+ }
73+
74+ // Numeric sort (Array.sort is stable, so alphabetical order is preserved for equal values)
75+ withNumbers . sort ( ( a , b ) => extractFirstNumber ( a . name ! ) ! - extractFirstNumber ( b . name ! ) ! ) ;
76+
77+ return [ ...infraTracks , ...withNumbers , ...withoutNumbers , ...( ncTrack ? [ ncTrack ] : [ ] ) ] ;
78+ }
79+
4980type StationLabel = { type ?: 'label' ; label : string } | { type : 'requestedPoint' } ;
5081function extractStationLabel (
5182 stationLabel : StationLabel | undefined ,
@@ -210,7 +241,7 @@ const useTrackOccupancy = ({
210241 const { local_track_name : localTrackName , trains } = trackItem ;
211242 let trackId : string ;
212243 if ( ! localTrackName ) {
213- trackId = NO_TRACK_SPECIFIED_ID ;
244+ trackId = NO_TRACK_SPECIFIED_SYMBOL ;
214245 } else {
215246 const mappedTrackId = waypointId
216247 ? localTrackNameToTrackIdRef . current . get ( waypointId ) ?. get ( localTrackName )
@@ -329,7 +360,7 @@ const useTrackOccupancy = ({
329360 }
330361 const virtualTracks : Track [ ] = [ ...virtualTrackIds ] . map ( ( id ) => ( {
331362 id,
332- name : id === NO_TRACK_SPECIFIED_ID ? NO_TRACK_SPECIFIED_SYMBOL : id ,
363+ name : id === NO_TRACK_SPECIFIED_SYMBOL ? NO_TRACK_SPECIFIED_SYMBOL : id ,
333364 } ) ) ;
334365
335366 res . push ( {
@@ -346,7 +377,7 @@ const useTrackOccupancy = ({
346377 } ;
347378 } ) ,
348379 loading : opState . zones . type === 'loading' ,
349- tracks : [ ... infraTracks , ... virtualTracks ] ,
380+ tracks : sortTracks ( infraTracks , virtualTracks ) ,
350381 } ) ;
351382 }
352383 } ) ;
0 commit comments