From 8538763e3eef4d6bf9996452b092c9dc1724bece Mon Sep 17 00:00:00 2001 From: Heikki Vuorinen Date: Wed, 15 Jul 2026 09:59:40 +0300 Subject: [PATCH 01/21] update schema --- build/schema.graphql | 164 +++++++++++++++--- .../package.json | 2 +- .../schema/schema.graphql | 164 +++++++++++++++--- 3 files changed, 283 insertions(+), 47 deletions(-) diff --git a/build/schema.graphql b/build/schema.graphql index 88e306cf56..5ddb72b236 100644 --- a/build/schema.graphql +++ b/build/schema.graphql @@ -346,6 +346,33 @@ type CallSchedule { time: CallScheduledTime } +"Cancellation statistics grouped by entities." +type CanceledTripsSummary { + "How many canceled trips there are for the routes which have any cancellations." + routes: [CanceledTripsSummaryRoute!]! +} + +"Contains pattern and the information for how many canceled trips there are for the pattern." +type CanceledTripsSummaryPattern { + "How many canceled trips there are for the pattern." + cancellationCount: Int! + "The pattern for which the cancellation statistics are provided for." + pattern: Pattern! +} + +""" +Contains route, how many canceled trips there are for the route and cancellation statistics grouped +by patterns. +""" +type CanceledTripsSummaryRoute { + "How many canceled trips there are for the route." + cancellationCount: Int! + "How many canceled trips there are for the patterns which have any cancellations." + patterns: [CanceledTripsSummaryPattern!]! + "The route for which the cancellation statistics are provided for." + route: Route! +} + "Car park represents a location where cars can be parked." type CarPark implements Node & PlaceInterface { "ID of the car park" @@ -1071,7 +1098,24 @@ type Pattern implements Node { """ types: [PatternAlertType] ): [Alert] - "ID of the pattern" + """ + Get canceled trips for this pattern. Planned cancellations are not currently supported, so + only real-time canceled trips are returned. + + Avoid using this when fetching all patterns at once. + """ + canceledTrips( + """ + Only return canceled trips whose service date falls within one of these ranges. + An empty list or a list containing `null` is forbidden. If this argument is not provided or its + value is `null`, no filtering is applied. + """ + serviceDateRanges: [LocalDateRangeInput!] + ): [TripOnServiceDate!]! + """ + ID of the pattern. This is not always a stable ID that originates from the transit data. This can + be generated by the system and it's not guaranteed to stay stable over time. + """ code: String! """ Direction of the pattern. Possible values: 0, 1 or -1. @@ -1100,7 +1144,11 @@ type Pattern implements Node { same, even if their ids have changed. """ semanticHash: String - "List of stops served by this pattern" + """ + List of stops in order in which they are visited by this pattern. + + If a stop is visited more than once, it will appear several times. + """ stops: [Stop!] "Trips which run on this pattern" trips: [Trip!] @@ -1346,8 +1394,14 @@ type QueryType { """ Can be used to filter the results. When paging, the filter should not be changed between pages. - An empty list of filters or no value means that all trips should be included. Only one filter - is allowed for now. Once new filtering criteria is introduced, this limitation will be removed. + An empty list of filters or no value means that all trips should be included. Multiple filters + are allowed. + + Filter order does not matter. Each filter entry is evaluated independently and the final result + is the union (OR) of all matching filter entries. + + Inside one filter entry, selectors in `include` are combined with OR, selectors in `exclude` + are combined with OR, and the final match is `include AND NOT(exclude)`. """ filters: [CanceledTripsFilterInput!], """ @@ -1362,6 +1416,18 @@ type QueryType { """ last: Int ): TripOnServiceDateConnection @timingData + """ + Summary of canceled trips. Can be used as a preliminary query before using other + queries to fetch the details of the canceled trips. + """ + canceledTripsSummary( + """ + Can be used to filter the results. + + An empty list of filters or no value means that all cancellations should be included. + """ + filters: [CanceledTripsSummaryFilterInput!] + ): CanceledTripsSummary @timingData "Get canceled TripTimes." cancelledTripTimes( "Feed feedIds (e.g. [\"HSL\"])." @@ -1489,6 +1555,16 @@ type QueryType { pattern(id: String!): Pattern @timingData "Get all patterns" patterns: [Pattern] @timingData + """ + Get the patterns for the given pattern codes (the value of a pattern's `code` field). + + **Note**: Pattern codes can change quite often, for example whenever the underlying transit + data is updated. Therefore they should not be persisted for a long time. + """ + patternsByIds( + "Return the patterns with these codes, i.e. values of the field `code`." + ids: [String!]! + ): [Pattern] @timingData "Plans an itinerary from point A to point B based on the given arguments" plan( "Invariant: `boardSlack + alightSlack <= transferSlack`. Default value: 0" @@ -2303,6 +2379,22 @@ type Stop implements Node & PlaceInterface { """ types: [StopAlertType] ): [Alert] + """ + Canceled stop calls (arrivals/departures) at this stop. A call is included if either the trip it + belongs to has been canceled, or the visit at this stop has been canceled (skipped). + + **Note**: A service date is a technical term useful for transit planning purposes and might not + correspond to how a passenger thinks of a calendar date. For example, a night bus running on + Sunday morning at 1am to 3am, might have the previous Saturday's service date. + """ + canceledCalls( + """ + Only include canceled calls whose trip's service date is within any of these date ranges. + If omitted (or `null`), no service date filter is applied and canceled calls across the entire + transit service period are returned. Defining a range is recommended. + """ + serviceDateRanges: [LocalDateRangeInput!] + ): [StopCallOnTripOnServiceDate!]! "The cluster which this stop is part of" cluster: Cluster @deprecated(reason : "Not implemented") "Stop code which is visible at the stop" @@ -2468,6 +2560,14 @@ type StopCall { stopLocation: CallStopLocation! } +"A stop call together with the trip on service date it belongs to." +type StopCallOnTripOnServiceDate { + "A stop call which belongs to the trip on a service date." + stopCall: StopCall! + "The trip on service date that the stop call belongs to." + tripOnServiceDate: TripOnServiceDate! +} + type StopGeometries { "Representation of the stop geometries as GeoJSON (https://geojson.org/)" geoJson: GeoJson @@ -3285,13 +3385,7 @@ one can use the triangle factors. enum CyclingOptimizationType { "Emphasize flatness over safety or duration of the route. This option was previously called `FLAT`." FLAT_STREETS - """ - Completely ignore the elevation differences and prefer the streets, that are evaluated - to be the safest, even more than with the `SAFE_STREETS` option. - Safety can also include other concerns such as convenience and general cyclist preferences - by taking into account road surface etc. This option was previously called `GREENWAYS`. - """ - SAFEST_STREETS + SAFEST_STREETS @deprecated(reason : "Use SAFE_STREETS instead") """ Emphasize cycling safety over flatness or duration of the route. Safety can also include other concerns such as convenience and general cyclist preferences by taking into account @@ -3959,15 +4053,7 @@ one can use the triangle factors. enum ScooterOptimizationType { "Emphasize flatness over safety or duration of the route. This option was previously called `FLAT`." FLAT_STREETS - """ - Completely ignore the elevation differences and prefer the streets, that are evaluated - to be safest for scooters, even more than with the `SAFE_STREETS` option. - Safety can also include other concerns such as convenience and general preferences by taking - into account road surface etc. Note, currently the same criteria is used both for cycling and - scooter travel to determine how safe streets are for cycling or scooter. - This option was previously called `GREENWAYS`. - """ - SAFEST_STREETS + SAFEST_STREETS @deprecated(reason : "Use SAFE_STREETS instead") """ Emphasize scooter safety over flatness or duration of the route. Safety can also include other concerns such as convenience and general preferences by taking into account road surface etc. @@ -4303,10 +4389,10 @@ input BoardPreferencesInput { } """ -A collection of selectors for including or excluding trips from canceled trips search. Currently, -you can only use either include or exclude filter (as filtering is only supported with transit modes). +A collection of selectors for including or excluding trips from canceled trips search. Excludes +override includes. """ -input CanceledTripsFilterInput @oneOf { +input CanceledTripsFilterInput { """ A list of selectors for excluding canceled trips. @@ -4328,6 +4414,38 @@ means everything is allowed to be returned and empty lists are not allowed. input CanceledTripsFilterSelectInput { "Canceled trips from these transit modes should be included/excluded." modes: [TransitMode!] + """ + Canceled trips are included/excluded based on if their service date (but not necessarily their running date) is within any of these time + ranges. + """ + serviceDateRanges: [LocalDateRangeInput!] +} + +"Used to limit which canceled trips are included for the summary." +input CanceledTripsSummaryFilterInput { + """ + A list of selectors for excluding canceled trips. + + An empty list or a list containing `null` is forbidden. + """ + exclude: [CanceledTripsSummaryFilterSelectInput!] + """ + A list of selectors for including canceled trips. + + An empty list or a list containing `null` is forbidden. + """ + include: [CanceledTripsSummaryFilterSelectInput!] +} + +"Filter options for including or excluding canceled trips from the canceled trips summary." +input CanceledTripsSummaryFilterSelectInput { + "Canceled trips from these transit modes should be included/excluded." + modes: [TransitMode!] + """ + Canceled trips are included/excluded based on if their service date is within any of these time + ranges. + """ + serviceDateRanges: [LocalDateRangeInput!] } "Preferences for car parking facilities used during the routing." diff --git a/digitransit-search-util/packages/digitransit-search-util-query-utils/package.json b/digitransit-search-util/packages/digitransit-search-util-query-utils/package.json index e7f43b75f2..fe01d12361 100644 --- a/digitransit-search-util/packages/digitransit-search-util-query-utils/package.json +++ b/digitransit-search-util/packages/digitransit-search-util-query-utils/package.json @@ -1,6 +1,6 @@ { "name": "@digitransit-search-util/digitransit-search-util-query-utils", - "version": "4.5.10", + "version": "4.5.11", "description": "digitransit-search-util query-utils module", "main": "lib/index.js", "publishConfig": { diff --git a/digitransit-search-util/packages/digitransit-search-util-query-utils/schema/schema.graphql b/digitransit-search-util/packages/digitransit-search-util-query-utils/schema/schema.graphql index 88e306cf56..5ddb72b236 100644 --- a/digitransit-search-util/packages/digitransit-search-util-query-utils/schema/schema.graphql +++ b/digitransit-search-util/packages/digitransit-search-util-query-utils/schema/schema.graphql @@ -346,6 +346,33 @@ type CallSchedule { time: CallScheduledTime } +"Cancellation statistics grouped by entities." +type CanceledTripsSummary { + "How many canceled trips there are for the routes which have any cancellations." + routes: [CanceledTripsSummaryRoute!]! +} + +"Contains pattern and the information for how many canceled trips there are for the pattern." +type CanceledTripsSummaryPattern { + "How many canceled trips there are for the pattern." + cancellationCount: Int! + "The pattern for which the cancellation statistics are provided for." + pattern: Pattern! +} + +""" +Contains route, how many canceled trips there are for the route and cancellation statistics grouped +by patterns. +""" +type CanceledTripsSummaryRoute { + "How many canceled trips there are for the route." + cancellationCount: Int! + "How many canceled trips there are for the patterns which have any cancellations." + patterns: [CanceledTripsSummaryPattern!]! + "The route for which the cancellation statistics are provided for." + route: Route! +} + "Car park represents a location where cars can be parked." type CarPark implements Node & PlaceInterface { "ID of the car park" @@ -1071,7 +1098,24 @@ type Pattern implements Node { """ types: [PatternAlertType] ): [Alert] - "ID of the pattern" + """ + Get canceled trips for this pattern. Planned cancellations are not currently supported, so + only real-time canceled trips are returned. + + Avoid using this when fetching all patterns at once. + """ + canceledTrips( + """ + Only return canceled trips whose service date falls within one of these ranges. + An empty list or a list containing `null` is forbidden. If this argument is not provided or its + value is `null`, no filtering is applied. + """ + serviceDateRanges: [LocalDateRangeInput!] + ): [TripOnServiceDate!]! + """ + ID of the pattern. This is not always a stable ID that originates from the transit data. This can + be generated by the system and it's not guaranteed to stay stable over time. + """ code: String! """ Direction of the pattern. Possible values: 0, 1 or -1. @@ -1100,7 +1144,11 @@ type Pattern implements Node { same, even if their ids have changed. """ semanticHash: String - "List of stops served by this pattern" + """ + List of stops in order in which they are visited by this pattern. + + If a stop is visited more than once, it will appear several times. + """ stops: [Stop!] "Trips which run on this pattern" trips: [Trip!] @@ -1346,8 +1394,14 @@ type QueryType { """ Can be used to filter the results. When paging, the filter should not be changed between pages. - An empty list of filters or no value means that all trips should be included. Only one filter - is allowed for now. Once new filtering criteria is introduced, this limitation will be removed. + An empty list of filters or no value means that all trips should be included. Multiple filters + are allowed. + + Filter order does not matter. Each filter entry is evaluated independently and the final result + is the union (OR) of all matching filter entries. + + Inside one filter entry, selectors in `include` are combined with OR, selectors in `exclude` + are combined with OR, and the final match is `include AND NOT(exclude)`. """ filters: [CanceledTripsFilterInput!], """ @@ -1362,6 +1416,18 @@ type QueryType { """ last: Int ): TripOnServiceDateConnection @timingData + """ + Summary of canceled trips. Can be used as a preliminary query before using other + queries to fetch the details of the canceled trips. + """ + canceledTripsSummary( + """ + Can be used to filter the results. + + An empty list of filters or no value means that all cancellations should be included. + """ + filters: [CanceledTripsSummaryFilterInput!] + ): CanceledTripsSummary @timingData "Get canceled TripTimes." cancelledTripTimes( "Feed feedIds (e.g. [\"HSL\"])." @@ -1489,6 +1555,16 @@ type QueryType { pattern(id: String!): Pattern @timingData "Get all patterns" patterns: [Pattern] @timingData + """ + Get the patterns for the given pattern codes (the value of a pattern's `code` field). + + **Note**: Pattern codes can change quite often, for example whenever the underlying transit + data is updated. Therefore they should not be persisted for a long time. + """ + patternsByIds( + "Return the patterns with these codes, i.e. values of the field `code`." + ids: [String!]! + ): [Pattern] @timingData "Plans an itinerary from point A to point B based on the given arguments" plan( "Invariant: `boardSlack + alightSlack <= transferSlack`. Default value: 0" @@ -2303,6 +2379,22 @@ type Stop implements Node & PlaceInterface { """ types: [StopAlertType] ): [Alert] + """ + Canceled stop calls (arrivals/departures) at this stop. A call is included if either the trip it + belongs to has been canceled, or the visit at this stop has been canceled (skipped). + + **Note**: A service date is a technical term useful for transit planning purposes and might not + correspond to how a passenger thinks of a calendar date. For example, a night bus running on + Sunday morning at 1am to 3am, might have the previous Saturday's service date. + """ + canceledCalls( + """ + Only include canceled calls whose trip's service date is within any of these date ranges. + If omitted (or `null`), no service date filter is applied and canceled calls across the entire + transit service period are returned. Defining a range is recommended. + """ + serviceDateRanges: [LocalDateRangeInput!] + ): [StopCallOnTripOnServiceDate!]! "The cluster which this stop is part of" cluster: Cluster @deprecated(reason : "Not implemented") "Stop code which is visible at the stop" @@ -2468,6 +2560,14 @@ type StopCall { stopLocation: CallStopLocation! } +"A stop call together with the trip on service date it belongs to." +type StopCallOnTripOnServiceDate { + "A stop call which belongs to the trip on a service date." + stopCall: StopCall! + "The trip on service date that the stop call belongs to." + tripOnServiceDate: TripOnServiceDate! +} + type StopGeometries { "Representation of the stop geometries as GeoJSON (https://geojson.org/)" geoJson: GeoJson @@ -3285,13 +3385,7 @@ one can use the triangle factors. enum CyclingOptimizationType { "Emphasize flatness over safety or duration of the route. This option was previously called `FLAT`." FLAT_STREETS - """ - Completely ignore the elevation differences and prefer the streets, that are evaluated - to be the safest, even more than with the `SAFE_STREETS` option. - Safety can also include other concerns such as convenience and general cyclist preferences - by taking into account road surface etc. This option was previously called `GREENWAYS`. - """ - SAFEST_STREETS + SAFEST_STREETS @deprecated(reason : "Use SAFE_STREETS instead") """ Emphasize cycling safety over flatness or duration of the route. Safety can also include other concerns such as convenience and general cyclist preferences by taking into account @@ -3959,15 +4053,7 @@ one can use the triangle factors. enum ScooterOptimizationType { "Emphasize flatness over safety or duration of the route. This option was previously called `FLAT`." FLAT_STREETS - """ - Completely ignore the elevation differences and prefer the streets, that are evaluated - to be safest for scooters, even more than with the `SAFE_STREETS` option. - Safety can also include other concerns such as convenience and general preferences by taking - into account road surface etc. Note, currently the same criteria is used both for cycling and - scooter travel to determine how safe streets are for cycling or scooter. - This option was previously called `GREENWAYS`. - """ - SAFEST_STREETS + SAFEST_STREETS @deprecated(reason : "Use SAFE_STREETS instead") """ Emphasize scooter safety over flatness or duration of the route. Safety can also include other concerns such as convenience and general preferences by taking into account road surface etc. @@ -4303,10 +4389,10 @@ input BoardPreferencesInput { } """ -A collection of selectors for including or excluding trips from canceled trips search. Currently, -you can only use either include or exclude filter (as filtering is only supported with transit modes). +A collection of selectors for including or excluding trips from canceled trips search. Excludes +override includes. """ -input CanceledTripsFilterInput @oneOf { +input CanceledTripsFilterInput { """ A list of selectors for excluding canceled trips. @@ -4328,6 +4414,38 @@ means everything is allowed to be returned and empty lists are not allowed. input CanceledTripsFilterSelectInput { "Canceled trips from these transit modes should be included/excluded." modes: [TransitMode!] + """ + Canceled trips are included/excluded based on if their service date (but not necessarily their running date) is within any of these time + ranges. + """ + serviceDateRanges: [LocalDateRangeInput!] +} + +"Used to limit which canceled trips are included for the summary." +input CanceledTripsSummaryFilterInput { + """ + A list of selectors for excluding canceled trips. + + An empty list or a list containing `null` is forbidden. + """ + exclude: [CanceledTripsSummaryFilterSelectInput!] + """ + A list of selectors for including canceled trips. + + An empty list or a list containing `null` is forbidden. + """ + include: [CanceledTripsSummaryFilterSelectInput!] +} + +"Filter options for including or excluding canceled trips from the canceled trips summary." +input CanceledTripsSummaryFilterSelectInput { + "Canceled trips from these transit modes should be included/excluded." + modes: [TransitMode!] + """ + Canceled trips are included/excluded based on if their service date is within any of these time + ranges. + """ + serviceDateRanges: [LocalDateRangeInput!] } "Preferences for car parking facilities used during the routing." From 83452b768b21df72280019f78c6f343ae24fbea6 Mon Sep 17 00:00:00 2001 From: Heikki Vuorinen Date: Wed, 15 Jul 2026 16:01:58 +0300 Subject: [PATCH 02/21] refactor cancellations to use new canceledTripsSummary api --- app/component/trafficnow/CanceledTripCard.js | 97 ++++------- app/component/trafficnow/CanceledTrips.js | 155 +++++++----------- .../trafficnow/CanceledTripsContainer.js | 13 +- .../trafficnow/CanceledTripsModal.js | 88 +++++----- app/component/trafficnow/Disruptions.js | 55 +++---- .../components/CancellationContainer.js | 53 +++--- .../components/PatternWithCancellations.js | 46 +++--- .../queries/CanceledTripNodeFieldsFragment.js | 39 ----- .../queries/CanceledTripsForModeQuery.js | 29 +++- .../queries/CanceledTripsOverviewFragment.js | 17 -- .../queries/CanceledTripsOverviewQuery.js | 123 +++++++++++--- .../CanceledTripsPaginationFragment.js | 24 --- .../queries/CanceledTripsPatternFragment.js | 29 ++++ app/translations/en.js | 2 +- app/translations/fi.js | 2 +- app/translations/sv.js | 2 +- 16 files changed, 376 insertions(+), 398 deletions(-) delete mode 100644 app/component/trafficnow/queries/CanceledTripNodeFieldsFragment.js delete mode 100644 app/component/trafficnow/queries/CanceledTripsOverviewFragment.js delete mode 100644 app/component/trafficnow/queries/CanceledTripsPaginationFragment.js create mode 100644 app/component/trafficnow/queries/CanceledTripsPatternFragment.js diff --git a/app/component/trafficnow/CanceledTripCard.js b/app/component/trafficnow/CanceledTripCard.js index b3a139736a..bebb261a75 100644 --- a/app/component/trafficnow/CanceledTripCard.js +++ b/app/component/trafficnow/CanceledTripCard.js @@ -1,58 +1,27 @@ import React from 'react'; import { useRouter } from 'found'; -import { DateTime } from 'luxon'; import PropTypes from 'prop-types'; import { useConfigContext } from '../../configurations/ConfigContext'; -import { PREFIX_TIMETABLE, TRAFFICNOW, routePagePath } from '../../util/path'; +import { TRAFFICNOW, routePagePath } from '../../util/path'; import Card from '../Card'; import Icon from '../Icon'; -import CanceledDepartures from './components/CanceledDepartures'; +// import CanceledDepartures from './components/CanceledDepartures'; import DisruptionStatus from './components/DisruptionStatus'; import RouteBadgeGroup from './components/RouteBadgeGroup'; import DisruptionBadge from './DisruptionBadge'; +import { patternShape, routeShape } from '../../util/shapes'; -const CanceledTripCard = ({ mode, totalCount, trips, isMobile = false }) => { +const MAX_BADGES = 5; + +const CanceledTripCard = ({ mode, routes, isMobile = false }) => { const { router } = useRouter(); const { colors } = useConfigContext(); - const handleRouteBadgeClick = url => e => { e.preventDefault(); e.stopPropagation(); router.push(url); }; - /* eslint-disable no-param-reassign */ - const groupedTrips = trips.reduce((container, { start, trip }) => { - if (!trip?.route?.gtfsId || !start?.schedule?.time?.departure) { - return container; - } - - const shortName = trip?.route?.shortName || 'unknown'; - if (container[shortName]) { - container[shortName].trips.push({ - ...trip, - departureTime: DateTime.fromISO( - start?.schedule.time.departure, - ).toFormat('HH:mm'), - }); - } else { - container[shortName] = { - routeGtfsId: trip.route.gtfsId, - trips: [ - { - ...trip, - departureTime: DateTime.fromISO( - start?.schedule.time.departure, - ).toFormat('HH:mm'), - }, - ], - }; - } - return container; - }, {}); - - const isSingleRoute = Object.keys(groupedTrips).length === 1; - return ( { ({ - id: shortName, - name: shortName, - url: routePagePath(routeGtfsId, PREFIX_TIMETABLE), - gtfsId: routeGtfsId, - trips: groupedRouteTrips, - }), - )} - renderRouteSuffix={({ trips: groupedRouteTrips }) => - isSingleRoute ? ( - ({ - tripId, - departureTime, - }), - )} - /> - ) : null - } + routes={routes.slice(0, MAX_BADGES).map(({ route }) => ({ + name: route.shortName, + gtfsId: route.gtfsId, + id: route.id, + url: routePagePath(route.gtfsId), + }))} + // TODO: show canceled departures on single route card + // renderRouteSuffix={() => + // true ? ( + // ({ + // tripId: gtfsId, + // departureTime: '11:23', + // }))} + // /> + // ) : null + // } renderSuffix={ - totalCount > trips.length ? ( + routes.length > MAX_BADGES ? ( @@ -124,9 +89,19 @@ const CanceledTripCard = ({ mode, totalCount, trips, isMobile = false }) => { CanceledTripCard.propTypes = { mode: PropTypes.string.isRequired, - totalCount: PropTypes.number.isRequired, - trips: PropTypes.arrayOf(PropTypes.shape({})).isRequired, isMobile: PropTypes.bool, + routes: PropTypes.arrayOf( + PropTypes.shape({ + cancellationCount: PropTypes.number.isRequired, + route: routeShape.isRequired, + patterns: PropTypes.arrayOf( + PropTypes.shape({ + pattern: patternShape.isRequired, + cancellationCount: PropTypes.number.isRequired, + }).isRequired, + ).isRequired, + }), + ).isRequired, }; export default CanceledTripCard; diff --git a/app/component/trafficnow/CanceledTrips.js b/app/component/trafficnow/CanceledTrips.js index 1642e12777..f767787b66 100644 --- a/app/component/trafficnow/CanceledTrips.js +++ b/app/component/trafficnow/CanceledTrips.js @@ -1,11 +1,9 @@ -import React, { useMemo, useState } from 'react'; +import React, { useState } from 'react'; import Button from '@hsl-fi/button'; import cx from 'classnames'; import Link from 'found/Link'; -import { DateTime } from 'luxon'; import PropTypes from 'prop-types'; import { FormattedMessage, useIntl } from 'react-intl'; -import { usePaginationFragment } from 'react-relay/hooks'; import { useConfigContext } from '../../configurations/ConfigContext'; import Card from '../Card'; import Icon from '../Icon'; @@ -14,70 +12,18 @@ import CancellationContainer from './components/CancellationContainer'; import ResultsProgressBar from './components/ResultsProgressBar'; import DisruptionBadge from './DisruptionBadge'; import DisruptionStatus from './components/DisruptionStatus'; -import CanceledTripsPaginationFragment from './queries/CanceledTripsPaginationFragment'; +import { patternShape, routeShape } from '../../util/shapes'; -const CANCELED_TRIPS_QUERY_AMOUNT = 20; +const DEFAULT_ROUTES_SHOWN_AMOUNT = 8; -const CanceledTrips = ({ query, isMobile = false, ...props }) => { +const CanceledTrips = ({ canceledRoutes = [], mode, isMobile = false }) => { const { colors } = useConfigContext(); const intl = useIntl(); const [detailsKey, setDetailsKey] = useState(null); - - const { - data: { canceledTrips }, - loadNext, - isLoadingNext, - hasNext, - } = usePaginationFragment(CanceledTripsPaginationFragment, query); - - const mode = props.mode.toLowerCase(); - - const allEdges = canceledTrips?.edges ?? []; - - const trips = useMemo( - () => - /* eslint-disable no-param-reassign */ - allEdges.reduce((routeGroups, { node }) => { - if ( - !node?.trip?.route?.gtfsId || - !node?.start?.schedule?.time?.departure - ) { - return routeGroups; - } - - const { start, end, trip } = node; - const routeShortName = trip?.route?.shortName; - const patternCode = trip?.pattern?.code; - - if (!routeGroups[routeShortName]) { - routeGroups[routeShortName] = { - routeGtfsId: trip.route.gtfsId, - patterns: {}, - }; - } - - if (routeGroups[routeShortName].patterns[patternCode]) { - routeGroups[routeShortName].patterns[ - patternCode - ].canceledDepartures.push( - DateTime.fromISO(start?.schedule.time.departure).toFormat('HH:mm'), - ); - } else { - routeGroups[routeShortName].patterns[patternCode] = { - start, - end, - trip, - canceledDepartures: [ - DateTime.fromISO(start?.schedule.time.departure).toFormat( - 'HH:mm', - ), - ], - }; - } - - return routeGroups; - }, {}), - [allEdges], + const [showAmount, setShowAmount] = useState( + DEFAULT_ROUTES_SHOWN_AMOUNT > canceledRoutes.length + ? canceledRoutes.length + : DEFAULT_ROUTES_SHOWN_AMOUNT, ); const content = ( @@ -87,40 +33,29 @@ const CanceledTrips = ({ query, isMobile = false, ...props }) => {
- {Object.entries(trips).map( - ([routeShortName, { routeGtfsId, patterns }], i, arr) => - isMobile ? ( - - - - ) : ( + {canceledRoutes.slice(0, showAmount).map((routeSummary, i) => + isMobile ? ( + - ), + + ) : ( + + ), )}