diff --git a/src/App.scss b/src/App.scss index ce316f97..82f12262 100644 --- a/src/App.scss +++ b/src/App.scss @@ -6,7 +6,7 @@ flex-direction: row; font-family: Heebo; - .header { + > .header { height: 100vh; background-color: white; padding: 30px; diff --git a/src/api/siriService.ts b/src/api/siriService.ts index db768f33..764b6c30 100644 --- a/src/api/siriService.ts +++ b/src/api/siriService.ts @@ -6,6 +6,7 @@ import { geoLocationBoundary, nearestLocation } from 'src/api/geoService' import { Coordinates } from 'src/model/location' import { log } from 'src/log' import { BusRoute } from 'src/model/busRoute' +import { SiriRideWithRelatedPydanticModel } from 'open-bus-stride-client/openapi/models/SiriRideWithRelatedPydanticModel' const SIRI_API = new SiriApi(API_CONFIG) const LOCATION_DELTA_METERS = 500 @@ -26,6 +27,21 @@ async function getRidesAsync(route: BusRoute, stop: BusStop, timestamp: moment.M }) } +export async function getSiriRideWithRelated( + siriRouteId: string, + vehicleRefs: string, + siriRouteLineRefs: string, +) { + const gtfs_route_promise: SiriRideWithRelatedPydanticModel[] = await SIRI_API.siriRidesListGet({ + limit: 1, + siriRouteIds: siriRouteId.toString(), + siriRouteLineRefs, + vehicleRefs, + }) + + return gtfs_route_promise[0] +} + export async function getSiriStopHitTimesAsync(route: BusRoute, stop: BusStop, timestamp: Moment) { log('looking for rides arriving at stop around time', { route, diff --git a/src/pages/RealtimeMapPage.tsx b/src/pages/RealtimeMapPage.tsx index 2084d9ce..cc498354 100644 --- a/src/pages/RealtimeMapPage.tsx +++ b/src/pages/RealtimeMapPage.tsx @@ -18,7 +18,8 @@ import { Label } from './components/Label' import { getColorByHashString } from './dashboard/OperatorHbarChart/utils' import createClusterCustomIcon from './components/utils/customCluster/customCluster' import { TimeSelector } from './components/TimeSelector' -import { busIcon } from './components/utils/BusIcon' +import { busIcon, busIconPath } from './components/utils/BusIcon' +import { BusToolTip } from 'src/pages/components/MapLayers/BusToolTip' export interface Point { loc: [number, number] @@ -152,7 +153,7 @@ export default function RealtimeMapPage() { {/* Buttons */} {/*TODO (another PR another issue) - 3) use text `TEXTS`. + 3) use text `TEXTS`. */} {/* loaded info */} @@ -205,19 +206,19 @@ export function Markers({ positions }: { positions: Point[] }) { return ( <> - {positions.map((pos) => ( - agency.operator_ref === pos.operator)?.agency_name, - })} - key={pos.point?.id}> - -
{JSON.stringify(pos, null, 2)}
-
-
- ))} + {positions.map((pos) => { + const icon = busIcon({ + operator_id: pos.operator?.toString() || 'default', + name: agencyList.find((agency) => agency.operator_ref === pos.operator)?.agency_name, + }) + return ( + + + + + + ) + })}
) diff --git a/src/pages/SingleLineMapPage.tsx b/src/pages/SingleLineMapPage.tsx index 39322910..0aa416ba 100644 --- a/src/pages/SingleLineMapPage.tsx +++ b/src/pages/SingleLineMapPage.tsx @@ -12,6 +12,7 @@ import { TEXTS } from 'src/resources/texts' import { SearchContext } from '../model/pageState' import { NotFound } from './components/NotFound' import { Point } from './RealtimeMapPage' + import Grid from '@mui/material/Unstable_Grid2' // Grid version 2 import './Map.scss' import getAgencyList, { Agency } from 'src/api/agencyList' @@ -21,7 +22,8 @@ import { DateSelector } from './components/DateSelector' import { CircularProgress } from '@mui/material' import { FilterPositionsByStartTimeSelector } from './components/FilterPositionsByStartTimeSelector' import { PageContainer } from './components/PageContainer' -import { busIcon } from './components/utils/BusIcon' +import { busIcon, busIconPath } from './components/utils/BusIcon' +import { BusToolTip } from 'src/pages/components/MapLayers/BusToolTip' interface Path { locations: VehicleLocation[] @@ -165,20 +167,19 @@ const SingleLineMapPage = () => { url="https://tile-a.openstreetmap.fr/hot/{z}/{x}/{y}.png" /> - {filteredPositions.map((pos, i) => ( - agency.operator_ref === pos.operator) - ?.agency_name, - })} - key={i}> - -
{JSON.stringify(pos, null, 2)}
-
-
- ))} + {filteredPositions.map((pos, i) => { + const icon = busIcon({ + operator_id: pos.operator?.toString() || 'default', + name: agencyList.find((agency) => agency.operator_ref === pos.operator)?.agency_name, + }) + return ( + + + + + + ) + })} {paths.map((path) => ( agency.operator_ref === pos.operator)?.agency_name, })} key={i}> - +
{JSON.stringify(pos, null, 2)}
diff --git a/src/pages/components/MapLayers/BusToolTip.scss b/src/pages/components/MapLayers/BusToolTip.scss new file mode 100644 index 00000000..95f65583 --- /dev/null +++ b/src/pages/components/MapLayers/BusToolTip.scss @@ -0,0 +1,33 @@ +.leaflet-popup-content:has(.bus-tooltip) { + width: 350px; + direction: rtl; + text-align: right; + + .bus-tooltip { + > ul { + list-style: none; + padding: 0; + margin: 0; + + > li { + font-weight: bold; + + > span { + font-weight: normal; + } + } + } + + > .header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; + + img { + width: 50px; + height: 50px; + } + } + } +} \ No newline at end of file diff --git a/src/pages/components/MapLayers/BusToolTip.tsx b/src/pages/components/MapLayers/BusToolTip.tsx new file mode 100644 index 00000000..1d106064 --- /dev/null +++ b/src/pages/components/MapLayers/BusToolTip.tsx @@ -0,0 +1,96 @@ +import React, { useEffect, useState } from 'react' +import { Point } from 'src/pages/RealtimeMapPage' +import { Button } from '@mui/material' +import moment from 'moment-timezone' +import './BusToolTip.scss' + +import { getSiriRideWithRelated } from 'src/api/siriService' +import { SiriRideWithRelatedPydanticModel } from 'open-bus-stride-client/openapi/models/SiriRideWithRelatedPydanticModel' +import { TEXTS } from 'src/resources/texts' +import { Spin } from 'antd' +import cn from 'classnames' + +export function BusToolTip({ position, icon }: { position: Point; icon: string }) { + const [siriRide, setSiriRide] = useState() + const [isLoading, setIsLoading] = useState(false) + const [showJson, setShowJson] = useState(false) + + useEffect(() => { + setIsLoading(true) + getSiriRideWithRelated( + position.point!.siri_route__id.toString(), + position.point!.siri_ride__vehicle_ref.toString(), + position.point!.siri_route__line_ref.toString(), + ) + .then((siriRideRes: SiriRideWithRelatedPydanticModel) => setSiriRide(siriRideRes)) + .finally(() => setIsLoading(false)) + }, [position]) + return ( +
+ {isLoading ? ( + <> + {TEXTS.loading_routes} + + + ) : ( + <> +
+

+ {TEXTS.line} :{siriRide && siriRide!.gtfsRouteRouteShortName} +

+ bus icon +
+
    +
  • + {TEXTS.from} : + {siriRide && siriRide!.gtfsRouteRouteLongName?.split('<->')[0]} +
  • +
  • + {TEXTS.destination} : + {siriRide && siriRide!.gtfsRouteRouteLongName?.split('<->')[1]} +
  • +
  • + {TEXTS.velocity} :{`${position.point?.velocity} ${TEXTS.kmh}`} +
  • + +
  • + {TEXTS.sample_time} : + + {moment(position.point!.recorded_at_time as string, moment.ISO_8601) + .tz('Israel') + .format('DD/MM/yyyy בשעה HH:mm')} + +
  • +
  • + {TEXTS.vehicle_ref} :{position.point?.siri_ride__vehicle_ref} +
  • +
+ {/*maybe option to add info like this in extend card for now I put this condition */} + {window.screen.height > 1100 && ( + <> +

+ {TEXTS.drive_direction} : + + ( {position.point?.bearing} {TEXTS.bearing}) + +

+

+ {TEXTS.coords} :{position.loc.join(' , ')} +

+ + )} + + )} + + {showJson && ( +
+          {JSON.stringify(position, null, 2)}
+          
+ {siriRide && JSON.stringify(siriRide, null, 2)} +
+ )} +
+ ) +} diff --git a/src/pages/components/header/menu/menu.scss b/src/pages/components/header/menu/menu.scss index c8695404..858f465c 100644 --- a/src/pages/components/header/menu/menu.scss +++ b/src/pages/components/header/menu/menu.scss @@ -33,7 +33,7 @@ @media screen and (max-width: $mobile-max-width) { - .main .header { + .main > .header { max-width: 0; padding: 0; transition: all 0.2s ease-in-out; @@ -56,7 +56,7 @@ } } - .main .header.open{ + .main > .header.open{ display: block; position: absolute; z-index: 500; diff --git a/src/pages/components/utils/BusIcon.tsx b/src/pages/components/utils/BusIcon.tsx index 8c843c59..559a57e6 100644 --- a/src/pages/components/utils/BusIcon.tsx +++ b/src/pages/components/utils/BusIcon.tsx @@ -1,7 +1,9 @@ import { DivIcon } from 'leaflet' +export const busIconPath = (operator_id: string) => `/bus-logos/${operator_id}.svg` + export const busIcon = ({ operator_id, name }: { operator_id: string; name?: string }) => { - const path = `/bus-logos/${operator_id}.svg` + const path = busIconPath(operator_id) return new DivIcon({ className: 'my-div-icon', html: ` diff --git a/src/resources/texts.tsx b/src/resources/texts.tsx index f58fb2c5..16bd7b6d 100644 --- a/src/resources/texts.tsx +++ b/src/resources/texts.tsx @@ -90,6 +90,18 @@ export const TEXTS = { missing_rides: 'מהנסיעות חסרות', order_by_hour: 'לפי שעה', order_by_severity: 'לפי חומרה', + line: 'קו', + from: 'מוצא', + destination: 'יעד', + sample_time: 'זמן דגימה', + velocity: 'מהירות', + drive_direction: 'כיוון נסיעה:', + coords: 'נ.צ:', + vehicle_ref: 'לוחית רישוי', + hide_document: 'הסתר מידע לחנונים', + show_document: 'הצג מידע לחנונים', + bearing: 'מעלות', + kmh: 'קמ״ש', } export const formatted = (text: string, value: string) => text.replace(PLACEHOLDER, value)