diff --git a/assets/src/components/v2/departures/departure_time.tsx b/assets/src/components/v2/departures/departure_time.tsx index 9266d8ca1..bfa8f79b8 100644 --- a/assets/src/components/v2/departures/departure_time.tsx +++ b/assets/src/components/v2/departures/departure_time.tsx @@ -24,7 +24,10 @@ const TimestampDepartureTime = ({ hour, minute }) => { type DepartureTime = | (TextDeparture & { type: "text" }) | (MinutesDeparture & { type: "minutes" }) - | (TimestampDeparture & { type: "timestamp" }); + | (TimestampDeparture & { type: "timestamp" }) + // Note: `overnight` is only produced in the DUP code path, and so is only + // supported in the DUP version of this component. + | { type: "overnight" }; interface TextDeparture { text: string; @@ -35,6 +38,10 @@ interface MinutesDeparture { interface TimestampDeparture { hour: number; minute: number; + // Note: `am_pm` fields are currently only supported by the DUP version of + // this component, but are always present in departures serialization. + am_pm: string; + show_am_pm: boolean; } const DepartureTime: ComponentType = ({ type, ...data }) => { diff --git a/assets/src/components/v2/departures/departure_times.tsx b/assets/src/components/v2/departures/departure_times.tsx index bd2d71cc5..c15ed58ca 100644 --- a/assets/src/components/v2/departures/departure_times.tsx +++ b/assets/src/components/v2/departures/departure_times.tsx @@ -6,6 +6,9 @@ import DepartureCrowding, { CrowdingLevel } from "./departure_crowding"; export type TimeWithCrowding = { id: string; time: DepartureTime; + // Note: `scheduled_time` is currently only supported by the DUP version of + // `DepartureTime`, but is always present in departures serialization. + scheduled_time?: DepartureTime; crowding: CrowdingLevel | null; }; diff --git a/assets/src/components/v2/dup/departures.tsx b/assets/src/components/v2/dup/departures.tsx index 18608205d..0f5a289af 100644 --- a/assets/src/components/v2/dup/departures.tsx +++ b/assets/src/components/v2/dup/departures.tsx @@ -1,32 +1,39 @@ -import React from "react"; +import React, { ComponentType } from "react"; +import { type Section as SectionBase } from "Components/v2/departures/section"; import NormalSection from "./departures/normal_section"; import NoticeSection from "Components/v2/departures/notice_section"; import HeadwaySection from "./departures/headway_section"; import NoDataSection from "./departures/no_data_section"; import OvernightSection from "./departures/overnight_section"; -const Departures = ({ sections }) => { +type Section = + | SectionBase + | (HeadwaySection & { type: "headway_section" }) + | (NoDataSection & { type: "no_data_section" }) + | (OvernightSection & { type: "overnight_section" }); + +interface Props { + sections: Section[]; +} + +const Departures: ComponentType = ({ sections }) => { return (
- {sections.map(({ type, ...data }, i) => { - switch (type) { + {sections.map((section, i) => { + switch (section.type) { case "normal_section": - return ; + return ; case "notice_section": - return ; + return ; case "headway_section": - return ( - - ); + return ; case "no_data_section": - return ; + return ; case "overnight_section": - return ; + return ; } - - throw new Error(`unimplemented section type: ${type}`); })}
diff --git a/assets/src/components/v2/dup/departures/departure_row.tsx b/assets/src/components/v2/dup/departures/departure_row.tsx index ca2114d0c..8529ffb24 100644 --- a/assets/src/components/v2/dup/departures/departure_row.tsx +++ b/assets/src/components/v2/dup/departures/departure_row.tsx @@ -1,10 +1,15 @@ -import React from "react"; +import React, { ComponentType } from "react"; +import type DepartureRowBase from "Components/v2/departures/departure_row"; import RoutePill from "Components/v2/departures/route_pill"; import Destination from "./destination"; import DepartureTimes from "./departure_times"; -const DepartureRow = ({ +interface Props extends DepartureRowBase { + currentPage: number; +} + +const DepartureRow: ComponentType = ({ headsign, route, times_with_crowding: timesWithCrowding, diff --git a/assets/src/components/v2/dup/departures/departure_time.tsx b/assets/src/components/v2/dup/departures/departure_time.tsx index 95541f263..d5414bb4b 100644 --- a/assets/src/components/v2/dup/departures/departure_time.tsx +++ b/assets/src/components/v2/dup/departures/departure_time.tsx @@ -1,6 +1,8 @@ -import React from "react"; +import React, { ComponentType } from "react"; import { classWithModifier, classWithModifiers, imagePath } from "Util/util"; +import type DepartureTimeBase from "Components/v2/departures/departure_time"; + const TextDepartureTime = ({ text }) => { return
{text}
; }; @@ -26,9 +28,19 @@ const TimestampDepartureTime = ({ hour, minute, am_pm, show_am_pm }) => { ); }; -const DepartureTime = ({ scheduled_time, time, currentPage }) => { +interface Props { + time: DepartureTimeBase; + scheduled_time?: DepartureTimeBase; + currentPage: number; +} + +const DepartureTime: ComponentType = ({ + scheduled_time, + time, + currentPage, +}) => { let predictedTime; - if (time.type === "icon") { + if (time.type === "overnight") { predictedTime = ( ); @@ -50,11 +62,11 @@ const DepartureTime = ({ scheduled_time, time, currentPage }) => { let scheduledTime; - if (time.type === "text") { + if (scheduled_time.type === "text") { scheduledTime = ; - } else if (time.type === "minutes") { + } else if (scheduled_time.type === "minutes") { scheduledTime = ; - } else if (time.type === "timestamp") { + } else if (scheduled_time.type === "timestamp") { scheduledTime = ; } if (currentPage === 0) { @@ -67,7 +79,7 @@ const DepartureTime = ({ scheduled_time, time, currentPage }) => { return (
diff --git a/assets/src/components/v2/dup/departures/departure_times.tsx b/assets/src/components/v2/dup/departures/departure_times.tsx index 7c3e2ad63..b02270688 100644 --- a/assets/src/components/v2/dup/departures/departure_times.tsx +++ b/assets/src/components/v2/dup/departures/departure_times.tsx @@ -1,8 +1,17 @@ -import React from "react"; +import React, { ComponentType } from "react"; +import { type TimeWithCrowding } from "Components/v2/departures/departure_times"; import DepartureTime from "./departure_time"; -const DepartureTimes = ({ timesWithCrowding, currentPage }) => { +interface Props { + timesWithCrowding: TimeWithCrowding[]; + currentPage: number; +} + +const DepartureTimes: ComponentType = ({ + timesWithCrowding, + currentPage, +}) => { return ( <> {timesWithCrowding.map(({ id, time, scheduled_time }) => ( diff --git a/assets/src/components/v2/dup/departures/destination.tsx b/assets/src/components/v2/dup/departures/destination.tsx index 595540855..f6f8e5edf 100644 --- a/assets/src/components/v2/dup/departures/destination.tsx +++ b/assets/src/components/v2/dup/departures/destination.tsx @@ -1,4 +1,6 @@ -import React, { useLayoutEffect, useRef, useState } from "react"; +import React, { ComponentType, useLayoutEffect, useRef, useState } from "react"; + +import type DestinationBase from "Components/v2/departures/destination"; const LINE_HEIGHT = 138; // px @@ -42,7 +44,11 @@ const RenderedDestination = ({ parts, index1, index2, currentPageIndex }) => { ); }; -const Destination = ({ headsign, currentPage }) => { +interface Props extends DestinationBase { + currentPage: number; +} + +const Destination: ComponentType = ({ headsign, currentPage }) => { const firstLineRef = useRef(null); const secondLineRef = useRef(null); diff --git a/assets/src/components/v2/dup/departures/headway_section.tsx b/assets/src/components/v2/dup/departures/headway_section.tsx index 85316cef5..141e161e8 100644 --- a/assets/src/components/v2/dup/departures/headway_section.tsx +++ b/assets/src/components/v2/dup/departures/headway_section.tsx @@ -1,8 +1,13 @@ -import React from "react"; -import FreeText from "Components/v2/free_text"; +import React, { ComponentType } from "react"; +import FreeText, { FreeTextType } from "Components/v2/free_text"; import { classWithModifier } from "Util/util"; -const HeadwaySection = ({ text, layout }) => { +interface HeadwaySection { + layout: string; + text: FreeTextType; +} + +const HeadwaySection: ComponentType = ({ text, layout }) => { return (
{ +interface NoDataSection { + text: FreeTextType; +} + +const NoDataSection: ComponentType = ({ text }) => { return (
diff --git a/assets/src/components/v2/dup/departures/normal_section.tsx b/assets/src/components/v2/dup/departures/normal_section.tsx index 3d80f8aed..6707022ef 100644 --- a/assets/src/components/v2/dup/departures/normal_section.tsx +++ b/assets/src/components/v2/dup/departures/normal_section.tsx @@ -1,10 +1,11 @@ -import React from "react"; +import React, { ComponentType } from "react"; +import { type NormalSection as Props } from "Components/v2/departures/normal_section"; import DepartureRow from "./departure_row"; import NoticeRow from "Components/v2/departures/notice_row"; import useCurrentPage from "Hooks/use_current_dup_page"; -const NormalSection = ({ rows }) => { +const NormalSection: ComponentType = ({ rows }) => { if (rows.length == 0) return null; const currentPage = useCurrentPage(); @@ -12,13 +13,12 @@ const NormalSection = ({ rows }) => { return (
{rows.map((row, index) => { - const { id, type, ...data } = row; - if (type === "departure_row") { - return ; - } else if (type === "notice_row") { - return ; + if (row.type === "departure_row") { + return ( + + ); } else { - throw new Error(`unimplemented row type: ${type}`); + return ; } })}
diff --git a/assets/src/components/v2/dup/departures/overnight_section.tsx b/assets/src/components/v2/dup/departures/overnight_section.tsx index 6fb70dfad..d97442760 100644 --- a/assets/src/components/v2/dup/departures/overnight_section.tsx +++ b/assets/src/components/v2/dup/departures/overnight_section.tsx @@ -1,8 +1,12 @@ -import React from "react"; +import React, { ComponentType } from "react"; -import FreeText from "Components/v2/free_text"; +import FreeText, { FreeTextType } from "Components/v2/free_text"; -const OvernightSection = ({ text }) => { +interface OvernightSection { + text: FreeTextType; +} + +const OvernightSection: ComponentType = ({ text }) => { return (
diff --git a/lib/screens/v2/candidate_generator/dup/departures.ex b/lib/screens/v2/candidate_generator/dup/departures.ex index a861bfb2c..9ad5cff8e 100644 --- a/lib/screens/v2/candidate_generator/dup/departures.ex +++ b/lib/screens/v2/candidate_generator/dup/departures.ex @@ -561,6 +561,7 @@ defmodule Screens.V2.CandidateGenerator.Dup.Departures do ) do if DateTime.compare(now, last_schedule_today.departure_time) == :gt or DateTime.compare(now, first_schedule_today.departure_time) == :lt do + # nil/nil acts as a flag for the serializer to produce an `overnight` departure time %Departure{ schedule: %{last_schedule_today | departure_time: nil, arrival_time: nil} } diff --git a/lib/screens/v2/widget_instance/departures.ex b/lib/screens/v2/widget_instance/departures.ex index 3a3a0a5c6..c9f268501 100644 --- a/lib/screens/v2/widget_instance/departures.ex +++ b/lib/screens/v2/widget_instance/departures.ex @@ -413,7 +413,7 @@ defmodule Screens.V2.WidgetInstance.Departures do _screen, _now ), - do: %{time: %{type: :icon, icon: :overnight}} + do: %{time: %{type: :overnight}} defp serialize_time(departure, _screen, now) do stop_id = Departure.stop_id(departure) diff --git a/test/screens/v2/widget_instance/departures_test.exs b/test/screens/v2/widget_instance/departures_test.exs index 6f3ef01bf..e23b726b6 100644 --- a/test/screens/v2/widget_instance/departures_test.exs +++ b/test/screens/v2/widget_instance/departures_test.exs @@ -132,13 +132,7 @@ defmodule Screens.V2.WidgetInstance.DeparturesTest do id: "1B2M2Y8AsgTpgAmY7PhCfg==", inline_alerts: [], route: %{color: :orange, text: "OL", type: :text}, - times_with_crowding: [ - %{ - crowding: nil, - id: nil, - time: %{icon: :overnight, type: :icon} - } - ], + times_with_crowding: [%{id: nil, crowding: nil, time: %{type: :overnight}}], type: :departure_row } ],