• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hasadna / open-bus-map-search / 18713425753

22 Oct 2025 10:35AM UTC coverage: 76.346% (-5.4%) from 81.772%
18713425753

Pull #1300

github

web-flow
Merge 77e18de63 into a1cf845cb
Pull Request #1300: feat: enhance compliant form

665 of 923 branches covered (72.05%)

Branch coverage included in aggregate %.

29 of 85 new or added lines in 4 files covered. (34.12%)

5 existing lines in 1 file now uncovered.

1136 of 1436 relevant lines covered (79.11%)

37712.54 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.33
/src/pages/components/map-related/MapLayers/BusToolTip.tsx
1
import { GtfsRoutePydanticModel } from '@hasadna/open-bus-api-client'
2
import { Button, CircularProgress } from '@mui/material'
98✔
3
import { Skeleton } from 'antd'
4
import cn from 'classnames'
5
import { ReactNode, useEffect, useState } from 'react'
6
import { useTranslation } from 'react-i18next'
7
import { Link } from 'react-router'
8
import { getRoutesByLineRef } from 'src/api/gtfsService'
9
import dayjs from 'src/dayjs'
10
import { routeStartEnd, vehicleIDFormat } from 'src/pages/components/utils/rotueUtils'
11
import type { Point } from 'src/pages/timeBasedMap'
12
import CustomTreeView from '../../CustomTreeView'
13
import ComplaintModal from './ComplaintModal'
14
import './BusToolTip.scss'
15

16
export type BusToolTipProps = { position: Point; icon: string; children?: ReactNode }
17

18
export function BusToolTip({ position, icon, children }: BusToolTipProps) {
19
  const [route, setRoute] = useState<GtfsRoutePydanticModel>()
22✔
20
  const [isLoading, setIsLoading] = useState(false)
22✔
21
  const [showJson, setShowJson] = useState(false)
22✔
22
  const { t, i18n } = useTranslation()
22✔
23
  const [modalOpen, setModalOpen] = useState(false)
22✔
24

25
  useEffect(() => {
22✔
26
    if (!position.point?.id) return
4!
27
    setIsLoading(true)
4✔
28
    getRoutesByLineRef(
4✔
29
      position.point?.siri_route__operator_ref.toString(),
30
      position.point?.siri_route__line_ref.toString(),
31
      new Date(position.point?.siri_ride__scheduled_start_time),
32
    )
33
      .then((routes) => {
34
        setRoute(routes[0])
4✔
35
        setIsLoading(false)
4✔
36
      })
37
      .catch((error) => {
38
        console.error('Error fetching routes:', error)
×
39
        setIsLoading(false)
×
40
      })
41
  }, [position.point?.id])
42

43
  function getDirectionFromAngle(angle: number): string {
44
    // Normalize the angle to the range 0-360
45
    angle = ((angle % 360) + 360) % 360
14✔
46
    // Define the cardinal directions in clockwise order
47
    const directions: string[] = [
14✔
48
      t('directions.North', { defaultValue: 'North' }),
49
      t('directions.Northeast', { defaultValue: 'Northeast' }),
50
      t('directions.East', { defaultValue: 'East' }),
51
      t('directions.Southeast', { defaultValue: 'Southeast' }),
52
      t('directions.South', { defaultValue: 'South' }),
53
      t('directions.Southwest', { defaultValue: 'Southwest' }),
54
      t('directions.West', { defaultValue: 'West' }),
55
      t('directions.Northwest', { defaultValue: 'Northwest' }),
56
    ]
57
    // Divide the angle into 8 equal sections (45 degrees each)
58
    const index: number = Math.round(angle / 45) % 8
14✔
59

60
    return directions[index]
14✔
61
  }
62

63
  const [from, destination] = routeStartEnd(route?.routeLongName)
22✔
64

65
  return (
22✔
66
    <div className={cn('bus-tooltip', { hebrew: i18n.language === 'he' })}>
67
      {isLoading || !route ? (
62✔
68
        <div>
69
          <h1 className="loading title">
70
            <span>{t('loading_routes')}</span>
71
            <CircularProgress />
72
          </h1>
73
          <Skeleton title={false} paragraph={{ rows: 7 }} />
74
        </div>
75
      ) : (
76
        <>
77
          <header className="header">
78
            <h1 className="title">
79
              {`${t('line')}: `}
80
              <span>
81
                <Link to={`/profile/${route.id}`}>{route?.routeShortName || 'NaN'}</Link>
14!
82
              </span>
83
            </h1>
84
            <Link to={`/operator?operatorId=${position.point?.siri_route__operator_ref}`}>
85
              <img src={icon} alt="bus icon" className="bus-icon" />
86
            </Link>
87
          </header>
88
          <div className="content">
89
            <ul>
90
              <li>
91
                {`${t('lineProfile.agencyName')}: `}
92

93
                <span>
94
                  <Link to={`/operator?operatorId=${position.point?.siri_route__operator_ref}`}>
95
                    {route.agencyName}
96
                  </Link>
97
                </span>
98
              </li>
99
              <li>
100
                {`${t('from')}: `}
101
                <span>{from}</span>
102
              </li>
103
              <li>
104
                {`${t('destination')}: `}
105
                <span>{destination}</span>
106
              </li>
107
              <li>
108
                {`${t('velocity')}: `}
109
                <span>{`${position.point?.velocity} ${t('kmh')}`}</span>
110
              </li>
111
              <li>
112
                {`${t('sample_time')}: `}
113
                <span>
114
                  {dayjs(position.point!.recorded_at_time)
115
                    .tz('Israel')
116
                    .format(`l [${t('at_time')}] LT`)}
117
                </span>
118
              </li>
119
              <li>
120
                {`${t('vehicle_ref')}: `}
121
                <span>{vehicleIDFormat(position.point?.siri_ride__vehicle_ref)}</span>
122
              </li>
123
              <li>
124
                {`${t('drive_direction')}: `}
125
                <span>
126
                  {/* ({position.point?.bearing} {t('bearing')}) */}
127
                  {position.point?.bearing} {t('bearing')} (
128
                  {position.point?.bearing !== undefined
14!
129
                    ? getDirectionFromAngle(position.point.bearing)
130
                    : t('unknown', { defaultValue: 'unknown' })}
131
                  )
132
                </span>
133
              </li>
134
              <li>
135
                {`${t('coords')}: `}
136
                <span>{position.loc.join(' ,')}</span>
137
              </li>
138
            </ul>
139
            <Button
140
              variant="contained"
141
              color="success"
NEW
142
              onClick={() => setModalOpen((prev) => !prev)}
×
143
              style={{ borderRadius: '50px' }}>
144
              {t('open_complaint')}
145
            </Button>
146
            <br />
147
            <Button
148
              href="https://www.gov.il/BlobFolder/generalpage/gtfs_general_transit_feed_specifications/he/GTFS_Developer_Information_2024.11.21b.pdf"
149
              target="_blank"
150
              rel="noopener noreferrer"
151
              sx={{ marginTop: '2px' }}>
152
              {t('homepage.manual')}
153
            </Button>
154
            <br />
155
            <Button sx={{ margin: '2px 0' }} onClick={() => setShowJson((showJson) => !showJson)}>
8✔
156
              {showJson ? t('hide_document') : t('show_document')}
14✔
157
            </Button>
158
            {showJson && (
18✔
159
              <div dir={i18n.language === 'en' ? 'rtl' : 'ltr'}>
4!
160
                <CustomTreeView<Point>
161
                  id={`${position.point?.id}`}
162
                  data={position}
163
                  name={t('line')}
164
                />
165
                {route?.id && (
8✔
166
                  <CustomTreeView<GtfsRoutePydanticModel>
167
                    id={route?.id.toString()}
168
                    data={route}
169
                    name={t('plannedRoute')}
170
                  />
171
                )}
172
              </div>
173
            )}
174

175
            <ComplaintModal modalOpen={modalOpen} setModalOpen={setModalOpen} position={position} />
176
          </div>
177
          {children}
178
        </>
179
      )}
180
    </div>
181
  )
182
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc