Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
d57a298
Move react-intl to peer dependencies
alec-georgoff Jun 18, 2026
ed2a006
fix: Add classnames to timetable table components
alec-georgoff Jun 18, 2026
b3eccbb
Update snapshots
alec-georgoff Jun 23, 2026
ecf8ad7
fix: avoid name with commas being parsed as coordinate
miles-grant-ibigroup Jun 24, 2026
4fe586e
fix: notify user when geocoder fails
miles-grant-ibigroup Jun 24, 2026
e602f52
feat: Add closed stops functionality
alec-georgoff Jun 24, 2026
73e8942
Update snapshots
alec-georgoff Jun 24, 2026
b19835c
Merge pull request #958 from ibi-group/fix-bad-location-parsing
miles-grant-ibigroup Jun 25, 2026
21a24e6
chore(release): 16.0.5 [skip ci]
semantic-release-bot Jun 25, 2026
a41d925
Merge pull request #957 from ibi-group/timetable-package-tweaks
alec-georgoff Jun 26, 2026
e997c60
chore(release): 1.0.1 [skip ci]
semantic-release-bot Jun 26, 2026
4f6739b
Merge remote-tracking branch 'upstream/master' into timetable-add-clo…
alec-georgoff Jun 26, 2026
f8854b5
Merge pull request #959 from ibi-group/timetable-add-closed-stops
alec-georgoff Jun 26, 2026
0abd414
chore(release): 1.1.0 [skip ci]
semantic-release-bot Jun 26, 2026
e4b4df9
Merge pull request #941 from ibi-group/strict-mode-tooling
daniel-heppner-ibigroup Jul 1, 2026
ee63ce8
chore: upgrade TS target to es2020, fix dependencies, add missing typ…
danielhep Apr 27, 2026
8d8888c
add more missing deps to sub packages
danielhep Apr 28, 2026
5ae2266
add missing playwright dep
danielhep Apr 28, 2026
8bb7672
add wait-on dep
danielhep Apr 28, 2026
f626c74
feat(transitive-overlay): enable strict mode
danielhep Apr 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/core-utils/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opentripplanner/core-utils",
"version": "16.0.4",
"version": "16.0.5",
"description": "Core functionality that is shared among numerous UI components",
"engines": {
"node": ">=13"
Expand Down
4 changes: 4 additions & 0 deletions packages/core-utils/src/__tests__/__snapshots__/query.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,10 @@ exports[`query parseLocationString should return location with coordinates as na

exports[`query parseLocationString should return null for null input 1`] = `null`;

exports[`query parseLocationString should return null with name as name with no coords (but with commas) for input 1`] = `null`;

exports[`query parseLocationString should return null with name as name with no coords for input 1`] = `null`;

exports[`query planParamsToQuery should parse a depart at query 1`] = `
{
"bannedRoutes": "897ABC",
Expand Down
7 changes: 7 additions & 0 deletions packages/core-utils/src/__tests__/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,13 @@ describe("query", () => {
parseLocationString("33.983929829,-87.3892387982")
).toMatchSnapshot();
});

it("should return null with name as name with no coords for input", () => {
expect(parseLocationString("1 cool street")).toMatchSnapshot();
});
it("should return null with name as name with no coords (but with commas) for input", () => {
expect(parseLocationString("148, se street")).toMatchSnapshot();
});
});

describe("planParamsToQuery", () => {
Expand Down
4 changes: 2 additions & 2 deletions packages/core-utils/src/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ async function getFirstGeocodeResult(
if (firstResult) {
return geocoder.getLocationFromGeocodedFeature(firstResult);
}
return null;
return { name: `(Geocoder failure) ${text}` };
});
}

Expand All @@ -265,7 +265,7 @@ export function parseLocationString(value) {
? stringToCoords(parts[1])
: stringToCoords(parts[0]);
const name = parts[1] ? parts[0] : coordsToString(coordinates);
return coordinates.length === 2
return coordinates.length === 2 && coordinates.every(c => !!c)
? {
name: name || null,
lat: coordinates[0] || null,
Expand Down
4 changes: 2 additions & 2 deletions packages/timetable/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opentripplanner/timetable",
"version": "1.0.0",
"version": "1.1.0",
"description": "A component to display a timetable for a given route pattern",
"main": "lib/index.js",
"module": "esm/index.js",
Expand All @@ -11,13 +11,13 @@
"private": false,
"peerDependencies": {
"react": "catalog:",
"react-intl": "catalog:",
"styled-components": "catalog:"
},
"scripts": {
"tsc": "tsc"
},
"dependencies": {
"react-intl": "catalog:",
"toposort": "^2.0.2"
},
"devDependencies": {
Expand Down
39 changes: 27 additions & 12 deletions packages/timetable/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,20 @@ const TBody = styled.tbody`
white-space: nowrap;
`;

const TH = styled.th`
const TH = styled.th<{ closed?: boolean }>`
min-width: ${COLUMN_WIDTH};
padding: 1rem;
text-decoration: ${props => (props.closed ? "line-through" : "")};
`;

const TR = styled.tr`
text-align: center;
vertical-align: middle;
`;

const TD = styled.td`
const TD = styled.td<{ closed?: boolean }>`
padding: 1rem;
text-decoration: ${props => (props.closed ? "line-through" : "")};
`;

interface PatternStop {
Expand Down Expand Up @@ -176,6 +178,8 @@ interface TimeTableProps {
route: Route;
/** Whether to show only timepoint stops in the timetable */
timepointsOnly: boolean;
/** A set of gtfsId values for stops that are closed and should be shown with strikethrough */
closedStops?: Set<string>;
/** If the topological sort of the stop IDs fails for any reason, a `false` value here
* will cause the timetable to use a fallback "naive" stop sorting
*/
Expand All @@ -194,6 +198,7 @@ interface TimeTableProps {

const TimeTable = (props: TimeTableProps): JSX.Element => {
const {
closedStops,
directionId,
errorOnStopSorting,
includeDwellStops,
Expand Down Expand Up @@ -314,27 +319,35 @@ const TimeTable = (props: TimeTableProps): JSX.Element => {
}, [allTrips, comparator]);

return (
<Table tabIndex={0}>
<thead>
<Table className="timetable-table" tabIndex={0}>
<thead className="timetable-thead">
<tr>
{(showBlockId ? [{ id: "blockIdHeader", name: "Block ID" }] : [])
.concat(filteredPatternStops)
.map((s, index) => {
return (
<TH key={index} scope="col">
<TH
className="timetable-th"
key={index}
scope="col"
closed={closedStops && closedStops.has(s.id)}
>
{s.name}
</TH>
);
})}
</tr>
</thead>
<TBody>
<TBody className="timetable-tbody">
{timetableTrips.map((t, index) => {
const rowValues: string[] = showBlockId ? [t.blockId] : [];
const rowValues: { closed: boolean; value: string }[] = showBlockId
? [{ closed: false, value: t.blockId }]
: [];
filteredPatternStops.forEach(patternStop => {
const stopDetail = t.stops.get(patternStop.id);
rowValues.push(
stopDetail
rowValues.push({
closed: closedStops?.has(patternStop.id) || false,
value: stopDetail
? intl
? intl.formatTime(stopDetail.time)
: stopDetail.time.toLocaleTimeString("en-us", {
Expand All @@ -344,13 +357,15 @@ const TimeTable = (props: TimeTableProps): JSX.Element => {
minute: "2-digit"
})
: "-"
);
});
});

return (
<TR key={index}>
<TR className="timetable-tr" key={index}>
{rowValues.map((r, rowIndex) => (
<TD key={rowIndex}>{r}</TD>
<TD className="timetable-td" key={rowIndex} closed={r.closed}>
{r.value}
</TD>
))}
</TR>
);
Expand Down
23 changes: 22 additions & 1 deletion packages/timetable/src/stories/Timetable.story.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,40 @@
// eslint-disable-next-line prettier/prettier
import type { Meta, StoryObj } from "@storybook/react-vite"
import React from "react";

import TimeTable from ".."

import twinCitiesRouteMock from "../../__mocks__/route-mock.json"

const meta = {
component: TimeTable,
title: "Timetable"
title: "Timetable",
argTypes: {
closedStops: {
options: ["Grand St NE & 29th Ave NE (Direction: 1)", "46th St & I-35W (Direction: 0)"],
control: { type: "check" },
mapping: {
"Grand St NE & 29th Ave NE (Direction: 1)": "2:14634",
"46th St & I-35W (Direction: 0)": "2:53545"
}
},
directionId: {
control: { type: "radio" },
options: [0, 1]
}
}
} satisfies Meta<typeof TimeTable>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
// eslint-disable-next-line react/display-name
render: (args) => {
const { closedStops } = args;
// eslint-disable-next-line react/jsx-props-no-spreading
return <TimeTable {...args} closedStops={new Set(closedStops)} />
},
args: {
directionId: 0,
route: twinCitiesRouteMock.data.route,
Expand Down
Loading
Loading