Skip to content

Commit

Permalink
Add pagination and itinerary debug
Browse files Browse the repository at this point in the history
  • Loading branch information
testower committed Nov 20, 2023
1 parent c80b685 commit 07079d9
Show file tree
Hide file tree
Showing 13 changed files with 215 additions and 21 deletions.
15 changes: 15 additions & 0 deletions client-next/src/components/ItineraryList/ItineraryDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { TripPattern } from '../../gql/graphql.ts';
import { ItineraryLegDetails } from './ItineraryLegDetails.tsx';

export function ItineraryDetails({ tripPattern }: { tripPattern: TripPattern }) {
return (
<div>
{tripPattern.systemNotices.length > 0 && (
<p>System tags: {tripPattern.systemNotices.map((systemNotice) => systemNotice.tag).join(', ')}</p>
)}
{tripPattern.legs.map((leg, i) => (
<ItineraryLegDetails key={leg.id ? leg.id : `noid_${i}`} leg={leg} isLast={i === tripPattern.legs.length - 1} />
))}
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { TripPattern } from '../../gql/graphql.ts';
import { TIME_BOX_WIDTH, useHeaderContentStyleCalculations } from './useHeaderContentStyleCalculations.ts';
import { ItineraryHeaderLegContent } from './ItineraryHeaderLegContent.tsx';
import { useMemo } from 'react';
import { formatTime } from '../../util/formatTime.ts';

export function ItineraryHeaderContent({
tripPattern,
Expand All @@ -24,20 +25,12 @@ export function ItineraryHeaderContent({
);

const formattedStartTime = useMemo(
() =>
new Date(tripPattern.expectedStartTime).toLocaleTimeString('en-US', {
timeStyle: 'short',
hourCycle: 'h24',
}),
() => formatTime(tripPattern.expectedStartTime, 'short'),
[tripPattern.expectedStartTime],
);

const formattedEndTime = useMemo(
() =>
new Date(tripPattern.expectedEndTime).toLocaleTimeString('en-US', {
timeStyle: 'short',
hourCycle: 'h24',
}),
() => formatTime(tripPattern.expectedEndTime, 'short'),
[tripPattern.expectedEndTime],
);

Expand Down
23 changes: 23 additions & 0 deletions client-next/src/components/ItineraryList/ItineraryLegDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Leg, Mode } from '../../gql/graphql.ts';
import { LegTime } from './LegTime.tsx';
import { formatDistance } from '../../util/formatDistance.ts';
import { formatDuration } from '../../util/formatDuration.ts';

export function ItineraryLegDetails({ leg, isLast }: { leg: Leg; isLast: boolean }) {
return (
<div style={{ border: '1px dotted grey' }}>
<LegTime aimedTime={leg.aimedStartTime} expectedTime={leg.expectedStartTime} />-{' '}
<LegTime aimedTime={leg.aimedEndTime} expectedTime={leg.expectedEndTime} /> <b>{leg.mode}</b>{' '}
{leg.line && (
<>
<u>
{leg.line.publicCode} {leg.toEstimatedCall?.destinationDisplay?.frontText}
</u>
, {leg.authority?.name}
</>
)}{' '}
{formatDistance(leg.distance)}, {formatDuration(leg.duration)}
{leg.mode !== Mode.Foot && <u>from {leg.fromPlace.name}</u>} {!isLast && <u>to {leg.toPlace.name}</u>}
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,41 @@ import { Accordion } from 'react-bootstrap';
import { useContainerWidth } from './useContainerWidth.ts';
import { ItineraryHeaderContent } from './ItineraryHeaderContent.tsx';
import { useEarliestAndLatestTimes } from './useEarliestAndLatestTimes.ts';
import { ItineraryDetails } from './ItineraryDetails.tsx';
import { ItineraryPaginationControl } from './ItineraryPaginationControl.tsx';

export function ItineraryListContainer({
tripQueryResult,
selectedTripPatternIndex,
setSelectedTripPatternIndex,
pageResults,
}: {
tripQueryResult: QueryType | null;
selectedTripPatternIndex: number;
setSelectedTripPatternIndex: (selectedTripPatterIndex: number) => void;
pageResults: (cursor: string) => void;
}) {
const [earliestStartTime, latestEndTime] = useEarliestAndLatestTimes(tripQueryResult);
const { containerRef, containerWidth } = useContainerWidth();

return (
<section className="itinerary-list-container" ref={containerRef}>
<ItineraryPaginationControl
onPagination={pageResults}
previousPageCursor={tripQueryResult?.trip.previousPageCursor}
nextPageCursor={tripQueryResult?.trip.nextPageCursor}
/>
<Accordion
activeKey={`${selectedTripPatternIndex}`}
onSelect={(eventKey) => setSelectedTripPatternIndex(parseInt(eventKey as string))}
>
{tripQueryResult &&
tripQueryResult.trip.tripPatterns.map((tripPattern, itineraryIndex) => (
<Accordion.Item eventKey={`${itineraryIndex}`} key={`${itineraryIndex}`}>
<Accordion.Item
eventKey={`${itineraryIndex}`}
key={`${itineraryIndex}`}
bsPrefix={tripPattern.systemNotices.length === 0 ? '' : 'accordion-item-filtered'}
>
<Accordion.Header>
<ItineraryHeaderContent
containerWidth={containerWidth}
Expand All @@ -34,7 +47,9 @@ export function ItineraryListContainer({
latestEndTime={latestEndTime}
/>
</Accordion.Header>
<Accordion.Body>Itinerary details</Accordion.Body>
<Accordion.Body>
<ItineraryDetails tripPattern={tripPattern} />
</Accordion.Body>
</Accordion.Item>
))}
</Accordion>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Button } from 'react-bootstrap';

export function ItineraryPaginationControl({
previousPageCursor,
nextPageCursor,
onPagination,
}: {
previousPageCursor?: string | null;
nextPageCursor?: string | null;
onPagination: (cursor: string) => void;
}) {
return (
<div style={{ display: 'flex', justifyContent: 'space-evenly', margin: '1rem 0 ' }}>
<Button
disabled={!previousPageCursor}
onClick={() => {
previousPageCursor && onPagination(previousPageCursor);
}}
>
Previous page
</Button>{' '}
<Button
disabled={!nextPageCursor}
onClick={() => {
nextPageCursor && onPagination(nextPageCursor);
}}
>
Next page
</Button>
</div>
);
}
12 changes: 12 additions & 0 deletions client-next/src/components/ItineraryList/LegTime.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { formatTime } from '../../util/formatTime.ts';

export function LegTime({ aimedTime, expectedTime }: { aimedTime: string; expectedTime: string }) {
return aimedTime !== expectedTime ? (
<>
<span style={{ color: 'red' }}>{formatTime(expectedTime)}</span>
<span style={{ textDecoration: 'line-through' }}>{formatTime(aimedTime)}</span>
</>
) : (
<span>{formatTime(expectedTime)}</span>
);
}
2 changes: 1 addition & 1 deletion client-next/src/components/SearchBar/SearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export function SearchBar({
setTripQueryVariables={setTripQueryVariables}
/>
<div className="search-bar-route-button-wrapper">
<Button variant="primary" onClick={onRoute}>
<Button variant="primary" onClick={() => onRoute()}>
Route
</Button>
</div>
Expand Down
48 changes: 40 additions & 8 deletions client-next/src/hooks/useTripQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const query = graphql(`
$searchWindow: Int
$modes: Modes
$itineraryFiltersDebug: ItineraryFilterDebugProfile
$pageCursor: String
) {
trip(
from: $from
Expand All @@ -30,7 +31,10 @@ const query = graphql(`
searchWindow: $searchWindow
modes: $modes
itineraryFilters: { debug: $itineraryFiltersDebug }
pageCursor: $pageCursor
) {
previousPageCursor
nextPageCursor
tripPatterns {
aimedStartTime
aimedEndTime
Expand All @@ -45,28 +49,56 @@ const query = graphql(`
aimedEndTime
expectedEndTime
expectedStartTime
distance
duration
fromPlace {
name
}
toPlace {
name
}
toEstimatedCall {
destinationDisplay {
frontText
}
}
line {
publicCode
name
}
authority {
name
}
pointsOnLink {
points
}
}
systemNotices {
tag
}
}
}
}
`);

type TripQueryHook = (variables?: TripQueryVariables) => [QueryType | null, () => Promise<void>];
type TripQueryHook = (variables?: TripQueryVariables) => [QueryType | null, (pageCursor?: string) => Promise<void>];

export const useTripQuery: TripQueryHook = (variables) => {
const [data, setData] = useState<QueryType | null>(null);
const callback = useCallback(async () => {
if (variables) {
setData((await request(endpoint, query, variables)) as QueryType);
} else {
console.warn("Can't search without variables");
}
}, [setData, variables]);
const callback = useCallback(
async (pageCursor?: string) => {
console.log({ pageCursor });
if (variables) {
if (pageCursor) {
setData((await request(endpoint, query, { ...variables, pageCursor })) as QueryType);
} else {
setData((await request(endpoint, query, variables)) as QueryType);
}
} else {
console.warn("Can't search without variables");
}
},
[setData, variables],
);
return [data, callback];
};
1 change: 1 addition & 0 deletions client-next/src/screens/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export function App() {
tripQueryResult={tripQueryResult}
selectedTripPatternIndex={selectedTripPatternIndex}
setSelectedTripPatternIndex={setSelectedTripPatternIndex}
pageResults={callback}
/>
<MapView
tripQueryResult={tripQueryResult}
Expand Down
8 changes: 8 additions & 0 deletions client-next/src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@

.itinerary-header-wrapper {
position: relative;
background: #0a53be;
}

.accordion-item-filtered {
margin-bottom: 0;
background-color: lightpink;
--bs-accordion-btn-bg: lightpink;
--bs-accordion-active-bg: pink;
}

.itinerary-header-itinerary-number {
Expand Down
18 changes: 18 additions & 0 deletions client-next/src/util/formatDistance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Format distance
*
* Adapted from src/client/js/otp/util/Geo.js#distanceStringMetric
*/
export function formatDistance(meters: number) {
let kilometers = meters / 1000;

Check failure on line 7 in client-next/src/util/formatDistance.ts

View workflow job for this annotation

GitHub Actions / build-linux

'kilometers' is never reassigned. Use 'const' instead
if (kilometers > 100) {
//100 km => 999999999 km
return `${kilometers.toFixed(0)} km`;
} else if (kilometers > 1) {
//1.1 km => 99.9 km
return `${kilometers.toFixed(1)} km`;
} else {
//1m => 999m
return `${meters.toFixed(0)} m`;
}
}
32 changes: 32 additions & 0 deletions client-next/src/util/formatDuration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Format duration in seconds
*
* Adapted from src/client/js/otp/util/Time.js#secsToHrMinSec
*/
export function formatDuration(seconds: number) {
const hrs = Math.floor(seconds / 3600);
const mins = Math.floor(seconds / 60) % 60;
const secs = seconds % 60;

let formatted = '';

if (hrs === 1) {
formatted = `${formatted}${hrs} hr `;
} else if (hrs > 1) {
formatted = `${formatted}${hrs} hrs `;
}

if (mins === 1) {
formatted = `${formatted}${mins} min `;
} else if (mins > 1) {
formatted = `${formatted}${mins} mins `;
}

if (secs === 1) {
formatted = `${formatted}${secs} sec `;
} else if (secs > 1) {
formatted = `${formatted}${secs} secs `;
}

return formatted;
}
13 changes: 13 additions & 0 deletions client-next/src/util/formatTime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Format departure and arrival times from scalar dateTime strings
*
* If style argument is provided formatted with ('medium') or without ('short') seconds,
* otherwise seconds are shown if not 0.
*/
export function formatTime(dateTime: string, style?: 'short' | 'medium') {
const parsed = new Date(dateTime);
return parsed.toLocaleTimeString('en-US', {
timeStyle: style ? style : parsed.getSeconds() === 0 ? 'short' : 'medium',
hourCycle: 'h24',
});
}

0 comments on commit 07079d9

Please sign in to comment.