diff --git a/src/app/api/dashboard/route.ts b/src/app/api/dashboard/route.ts index 668df35..7376786 100644 --- a/src/app/api/dashboard/route.ts +++ b/src/app/api/dashboard/route.ts @@ -36,7 +36,7 @@ export async function GET(request: Request) { transformedData.forEach((status) => { status.journey.line.appearance = getAppearanceForLine( - identifyLineByMagic(status.journey.hafasTripId, status.journey.line) + status.journey.line, ); }); diff --git a/src/app/api/stations/[station]/route.ts b/src/app/api/stations/[station]/route.ts index d35828b..0357dc4 100644 --- a/src/app/api/stations/[station]/route.ts +++ b/src/app/api/stations/[station]/route.ts @@ -2,7 +2,7 @@ import { identifyLineByMagic } from '@/helpers/identifyLineByMagic'; import { createLineAppearanceDataset } from '@/helpers/lineAppearance'; import { authOptions } from '@/pages/api/auth/[...nextauth]'; import { TraewellingSdk } from '@/traewelling-sdk'; -import { transformHAFASTrip } from '@/traewelling-sdk/transformers'; +import { transformTrwlDeparture } from '@/traewelling-sdk/transformers'; import { TransportType } from '@/traewelling-sdk/types'; import { AboardStation, AboardTrip } from '@/types/aboard'; import createErrorResponse from '@/utils/api/createErrorResponse'; @@ -84,12 +84,12 @@ export async function GET( } as AboardStation, times: data.meta?.times, }, - trips: data.trips.map(transformHAFASTrip), + trips: data.trips.map(transformTrwlDeparture), }; transformedData.trips.forEach((trip) => { trip.line.appearance = getAppearanceForLine( - identifyLineByMagic(trip.hafasId, trip.line) + trip.line, ); }); diff --git a/src/app/api/trips/route.ts b/src/app/api/trips/route.ts index 8038318..cd6b212 100644 --- a/src/app/api/trips/route.ts +++ b/src/app/api/trips/route.ts @@ -7,9 +7,9 @@ import { getServerSession } from 'next-auth'; export async function GET(request: Request) { try { - const { hafasTripId, lineName, start } = getSafeURLParams({ + const { hafasTripId, lineName } = getSafeURLParams({ url: request.url, - requiredParams: ['hafasTripId', 'lineName', 'start'], + requiredParams: ['hafasTripId', 'lineName'], }); const session = await getServerSession(authOptions); @@ -23,7 +23,6 @@ export async function GET(request: Request) { const data = await TraewellingSdk.trains.trip({ hafasTripId, lineName, - start, }); return createResponse({ diff --git a/src/app/status/[id]/page.tsx b/src/app/status/[id]/page.tsx index dab22f5..761ca79 100644 --- a/src/app/status/[id]/page.tsx +++ b/src/app/status/[id]/page.tsx @@ -31,13 +31,11 @@ async function getStatusData(id: string) { async function getTripData( hafasTripId: string, lineName: string, - start: string ) { try { const trip = await TraewellingSdk.trains.trip({ hafasTripId, lineName, - start, }); if (!('id' in trip)) { @@ -70,7 +68,6 @@ export default async function Page({ params }: StatusPageProps) { const tripData = await getTripData( status.journey.hafasTripId, status.journey.line.name, - status.journey.origin.station.trwlId!.toString() ); if ('trwlId' in tripData) { @@ -80,20 +77,20 @@ export default async function Page({ params }: StatusPageProps) { const arrivingAt = new Date( status.journey.destination.arrival.planned! - ).toISOString(); + ).getTime(); const departuringAt = new Date( status.journey.origin.departure.planned! - ).toISOString(); + ).getTime(); const destinationIndex = tripData.stopovers?.findLastIndex( ({ arrival, station }) => - new Date(arrival.planned!).toISOString() === arrivingAt && + new Date(arrival.planned!).getTime() === arrivingAt && station.trwlId === status.journey.destination.station.trwlId ); const originIndex = tripData.stopovers?.findIndex( ({ departure, station }) => - new Date(departure.planned!).toISOString() === departuringAt && + new Date(departure.planned!).getTime() === departuringAt && station.trwlId === status.journey.origin.station.trwlId ); diff --git a/src/app/traewelling/trips/route.ts b/src/app/traewelling/trips/route.ts index 8038318..cd6b212 100644 --- a/src/app/traewelling/trips/route.ts +++ b/src/app/traewelling/trips/route.ts @@ -7,9 +7,9 @@ import { getServerSession } from 'next-auth'; export async function GET(request: Request) { try { - const { hafasTripId, lineName, start } = getSafeURLParams({ + const { hafasTripId, lineName } = getSafeURLParams({ url: request.url, - requiredParams: ['hafasTripId', 'lineName', 'start'], + requiredParams: ['hafasTripId', 'lineName'], }); const session = await getServerSession(authOptions); @@ -23,7 +23,6 @@ export async function GET(request: Request) { const data = await TraewellingSdk.trains.trip({ hafasTripId, lineName, - start, }); return createResponse({ diff --git a/src/components/CheckIn/CheckIn.tsx b/src/components/CheckIn/CheckIn.tsx index be816e2..f45d598 100644 --- a/src/components/CheckIn/CheckIn.tsx +++ b/src/components/CheckIn/CheckIn.tsx @@ -3,7 +3,7 @@ import { useCurrentStatus } from '@/hooks/useCurrentStatus/useCurrentStatus'; import useUmami from '@/hooks/useUmami/useUmami'; import { CheckinInput } from '@/traewelling-sdk/functions/trains'; -import { Station, Stop } from '@/traewelling-sdk/types'; +import { Station, Stopover } from '@/traewelling-sdk/types'; import { AboardTrip } from '@/types/aboard'; import { Session } from 'next-auth'; import { useSession } from 'next-auth/react'; @@ -49,12 +49,12 @@ const post = async (status: CheckinInput, session?: Session | null) => { const CheckIn = () => { const { data: session } = useSession(); const [departureTime, setDepartureTime] = useState(); - const [destination, setDestination] = useState(); + const [destination, setDestination] = useState(); const [error, setError] = useState(); const [isOpen, setIsOpen] = useState(false); const [message, setMessage] = useState(''); const [origin, setOrigin] = - useState>(); + useState(); const [query, setQuery] = useState(''); const [step, setStep] = useState('origin'); const [travelType, setTravelType] = useState(0); @@ -73,10 +73,9 @@ const CheckIn = () => { body: message, business: travelType, departure: trip?.departure?.planned!, - destination: destination!.evaIdentifier, - ibnr: true, + destination: destination!.id, lineName: trip!.line.name, - start: trip?.departureStation?.ibnr!, + start: trip?.departureStation?.trwlId ?? -1, tripId: trip!.hafasId!, visibility: visibility, }, diff --git a/src/components/CheckIn/DestinationStep/DestinationStep.tsx b/src/components/CheckIn/DestinationStep/DestinationStep.tsx index a39168b..c4833be 100644 --- a/src/components/CheckIn/DestinationStep/DestinationStep.tsx +++ b/src/components/CheckIn/DestinationStep/DestinationStep.tsx @@ -25,7 +25,7 @@ const DestinationStep = () => { trip?.hafasId ?? '', trip?.line.name ?? '', trip?.departure?.planned ?? '', - trip?.departureStation?.evaId?.toString() ?? '' + trip?.departureStation?.trwlId ?? -1, ); const schedule = parseSchedule({ @@ -101,11 +101,11 @@ const DestinationStep = () => { {stops.map((stop, index) => (
  • setDestination(stop)} diff --git a/src/components/CheckIn/FinalStep/FinalStep.tsx b/src/components/CheckIn/FinalStep/FinalStep.tsx index fb265b1..e8c8274 100644 --- a/src/components/CheckIn/FinalStep/FinalStep.tsx +++ b/src/components/CheckIn/FinalStep/FinalStep.tsx @@ -17,6 +17,7 @@ import { MdOutlineGroups, MdOutlineLockOpen, MdOutlineLockPerson, + MdShield, MdSwapCalls, MdWorkOutline, } from 'react-icons/md'; @@ -61,6 +62,10 @@ const VISIBILITIES = [ icon: , name: 'Nur angemeldete Personen', }, + { + icon: , + name: 'Nur vertrauten Personen', + }, ]; const FinalStep = () => { @@ -80,7 +85,7 @@ const FinalStep = () => { } = useContext(CheckInContext); const arrivalSchedule = parseSchedule({ - actual: destination?.arrival, + actual: destination?.arrivalReal, planned: destination?.arrivalPlanned!, }); diff --git a/src/components/CheckIn/OriginStep/OriginStep.tsx b/src/components/CheckIn/OriginStep/OriginStep.tsx index 70ac5c1..80d828e 100644 --- a/src/components/CheckIn/OriginStep/OriginStep.tsx +++ b/src/components/CheckIn/OriginStep/OriginStep.tsx @@ -32,12 +32,12 @@ const OriginStep = () => { {stations && stations.length > 0 && (
      {stations.map((station) => ( -
    • +
    • setOrigin(station)} query={query} - rilIdentifier={station.rilIdentifier} + rilIdentifier={station.identifiers.filter(id => id.type === 'de_db_ril100')[0]?.identifier} />
    • ))} @@ -71,11 +71,11 @@ const OriginStep = () => { {recentStations && recentStations.length > 0 && (
        {recentStations.map((station) => ( -
      • +
      • setOrigin(station)} - rilIdentifier={station.rilIdentifier} + rilIdentifier={station.identifiers.filter(id => id.type === 'de_db_ril100')[0]?.identifier} />
      • ))} diff --git a/src/components/CheckIn/Search/Search.tsx b/src/components/CheckIn/Search/Search.tsx index 447ca9f..7a90d8a 100644 --- a/src/components/CheckIn/Search/Search.tsx +++ b/src/components/CheckIn/Search/Search.tsx @@ -60,12 +60,7 @@ const Search = () => { simpleEvent('nearby_clicked'); const station = (await response.json()) as NearbyResponse; - setOrigin({ - id: station.id, - ibnr: station.ibnr, - name: station.name, - rilIdentifier: station.rilIdentifier, - }); + setOrigin(station); }); }; diff --git a/src/components/CheckIn/TripStep/TripStep.tsx b/src/components/CheckIn/TripStep/TripStep.tsx index 6605b60..27aa2db 100644 --- a/src/components/CheckIn/TripStep/TripStep.tsx +++ b/src/components/CheckIn/TripStep/TripStep.tsx @@ -68,7 +68,7 @@ const TripStep = () => { } }, [departures]); - const servesMethod = (method: AboardMethod) => servedMethods.includes(method); + const servesMethod = (method: AboardMethod) => servedMethods.length === 0 || servedMethods.includes(method); const handleOnEarlierClick = () => { if (isLoading) { @@ -110,7 +110,7 @@ const TripStep = () => { {origin?.name} - {servedMethods.length > 1 && ( + {servedMethods && servedMethods.length > 1 && (
        {(servesMethod('national') || servesMethod('national-express')) && ( void; currentStatus: AboardStatus | null | undefined; departureTime: string | undefined; - destination: Stop | undefined; + destination: Stopover | undefined; error: string | undefined; // TODO: Temporary solution goBack: () => void; isOpen: boolean; message: string; - origin: Pick | undefined; + origin: Station | undefined; query: string; setDepartureTime: (value: string | undefined) => void; - setDestination: (value: Stop | undefined) => void; + setDestination: (value: Stopover | undefined) => void; setIsOpen: (value: boolean) => void; setMessage: (value: string) => void; - setOrigin: ( - value: Pick | undefined - ) => void; + setOrigin: (value: Station | undefined) => void; setQuery: (value: string) => void; setTravelType: (value: number) => void; setTrip: (value: AboardTrip | undefined) => void; diff --git a/src/components/Profile/Statuses/Statuses.tsx b/src/components/Profile/Statuses/Statuses.tsx index 36a3060..1c23cd9 100644 --- a/src/components/Profile/Statuses/Statuses.tsx +++ b/src/components/Profile/Statuses/Statuses.tsx @@ -10,11 +10,11 @@ const Statuses = ({ username }: { username: string }) => { return (
        {statuses?.map( - ({ id, username, profilePicture, body, likes, liked, train }) => { + ({ id, user, body, likes, liked, checkin }) => { return (
        -
        {username}
        -
        {train.lineName}
        +
        {user.username}
        +
        {checkin.lineName}
        {body}
        {likes}
        {liked && '❤️'}
        diff --git a/src/components/TripSelector/TripSelector.tsx b/src/components/TripSelector/TripSelector.tsx index eafcd2d..7f9b702 100644 --- a/src/components/TripSelector/TripSelector.tsx +++ b/src/components/TripSelector/TripSelector.tsx @@ -52,8 +52,8 @@ const Trip = ({ onClick, requestedStationName, trip }: TripProps) => { const isTimeDocked = isCancelled || departsFromDeviatingStation; - const fr = trip.hafasId?.match(/#FR#(\d+)#/)?.[1]; - const to = trip.hafasId?.match(/#TO#(\d+)#/)?.[1]; + // const fr = trip.hafasId?.match(/#FR#(\d+)#/)?.[1]; + // const to = trip.hafasId?.match(/#TO#(\d+)#/)?.[1]; return ( @@ -129,9 +129,9 @@ const Trip = ({ onClick, requestedStationName, trip }: TripProps) => { )}
        -

        + {/*

        {fr}, {to} -

        +

        */} ); }; diff --git a/src/helpers/getLineTheme/consts.ts b/src/helpers/getLineTheme/consts.ts index 498d4c4..b9208ef 100644 --- a/src/helpers/getLineTheme/consts.ts +++ b/src/helpers/getLineTheme/consts.ts @@ -89,4 +89,16 @@ export const PRODUCT_THEMES: Record> = { contrast: '#FFFFFF', contrastRGB: '255, 255, 255', }, + freightTrain: { + accent: '#415A77', + accentRGB: '65, 90, 119', + contrast: '#FFFFFF', + contrastRGB: '255, 255, 255', + }, + plane: { // TODO: Create proper color scheme + accent: '#A3007C', + accentRGB: '163, 0, 124', + contrast: '#FFFFFF', + contrastRGB: '255, 255, 255', + }, }; diff --git a/src/helpers/getStopsAfter.ts b/src/helpers/getStopsAfter.ts index f8d4798..eb83b09 100644 --- a/src/helpers/getStopsAfter.ts +++ b/src/helpers/getStopsAfter.ts @@ -1,16 +1,16 @@ -import { Stop } from '@/traewelling-sdk/types'; +import { Stopover } from '@/traewelling-sdk/types'; export const getStopsAfter = ( plannedDeparture: string, - stationId: string, - stops: Stop[] + stationId: number, + stops: Stopover[] ) => { const after = new Date(plannedDeparture).toISOString(); const startingAt = stops.findIndex( ({ departurePlanned, id }) => after === new Date(departurePlanned!).toISOString() && - stationId === id.toString() + stationId === id ); return stops.slice(typeof startingAt === 'undefined' ? 0 : startingAt + 1); diff --git a/src/helpers/lineAppearance/consts.ts b/src/helpers/lineAppearance/consts.ts index 6422adc..50ffcbc 100644 --- a/src/helpers/lineAppearance/consts.ts +++ b/src/helpers/lineAppearance/consts.ts @@ -74,6 +74,34 @@ export const FALLBACK_METHOD_APPEARANCES: Record< contrastColor: '#FFFFFF', shape: 'pill', }, + aerial: { // TODO: Create proper color scheme + accentColor: '#AF8000', + background: '#AF8000', + color: '#FFFFFF', + contrastColor: '#FFFFFF', + shape: 'smooth-rectangle', + }, + airplane: { // TODO: Create proper color scheme + accentColor: '#AF8000', + background: '#AF8000', + color: '#FFFFFF', + contrastColor: '#FFFFFF', + shape: 'smooth-rectangle', + }, + funicular: { // TODO: Create proper color scheme + accentColor: '#AF8000', + background: '#AF8000', + color: '#FFFFFF', + contrastColor: '#FFFFFF', + shape: 'smooth-rectangle', + }, + _: { // TODO: Create proper color scheme + accentColor: '#AF8000', + background: '#AF8000', + color: '#FFFFFF', + contrastColor: '#FFFFFF', + shape: 'smooth-rectangle', + }, }; export const LINE_APPEARANCE_OVERRIDES: [ diff --git a/src/helpers/lineAppearance/index.ts b/src/helpers/lineAppearance/index.ts index 02a1447..b8b73e7 100644 --- a/src/helpers/lineAppearance/index.ts +++ b/src/helpers/lineAppearance/index.ts @@ -10,61 +10,78 @@ import { import { fetchTrwlLineColorDefinitions } from './fetcher'; export const createLineAppearanceDataset = async () => { - const trwlLineColorDefinitions = await fetchTrwlLineColorDefinitions(); - const dataset = trwlLineColorDefinitions.map((definition) => { - const overrides = LINE_APPEARANCE_OVERRIDES.filter(([pattern]) => - pattern.test(definition.hafasLineId) - ).map(([, override]) => override); + // const trwlLineColorDefinitions = await fetchTrwlLineColorDefinitions(); + // const dataset = trwlLineColorDefinitions.map((definition) => { + // const overrides = LINE_APPEARANCE_OVERRIDES.filter(([pattern]) => + // pattern.test(definition.hafasLineId) + // ).map(([, override]) => override); - const accentColor = determineAccentColor(definition); - const [accentR, accentG, accentB] = colorConvert.hex.rgb(accentColor); + // const accentColor = determineAccentColor(definition); + // const [accentR, accentG, accentB] = colorConvert.hex.rgb(accentColor); - const appearance: Partial = Object.assign( - { - accentColor, - background: definition.backgroundColor, - border: definition.borderColor, - color: definition.textColor, - contrastColor: getContrastColor(accentR, accentG, accentB), - lineName: definition.lineName, - shape: transformTrwlLineShape(definition.shape), - } satisfies Partial, - ...overrides - ); + // const appearance: Partial = Object.assign( + // { + // accentColor, + // background: definition.backgroundColor, + // border: definition.borderColor, + // color: definition.textColor, + // contrastColor: getContrastColor(accentR, accentG, accentB), + // lineName: definition.lineName, + // shape: transformTrwlLineShape(definition.shape), + // } satisfies Partial, + // ...overrides + // ); - return { - appearance, - lineId: definition.hafasLineId, - operatorId: definition.hafasOperatorCode, - }; - }); + // return { + // appearance, + // lineId: definition.hafasLineId, + // operatorId: definition.hafasOperatorCode, + // }; + // }); return { getAppearanceForLine: (line: AboardLine): AboardLineAppearance => { - const fromDataset = dataset.find( - ({ lineId, operatorId }) => - lineId === line.id && - (operatorId === '' || operatorId === line.operator?.id) - ); + // const fromDataset = dataset.find( + // ({ lineId, operatorId }) => + // lineId === line.id && + // (operatorId === '' || operatorId === line.operator?.id) + // ); - if (fromDataset) { - return Object.assign(line.appearance, fromDataset.appearance); - } + // if (fromDataset) { + // return Object.assign(line.appearance, fromDataset.appearance); + // } + + // const overrides = LINE_APPEARANCE_OVERRIDES.filter(([pattern]) => + // pattern.test(line.id) + // ).map(([, override]) => override); + + const fb = FALLBACK_METHOD_APPEARANCES[line.method]; - const overrides = LINE_APPEARANCE_OVERRIDES.filter(([pattern]) => - pattern.test(line.id) - ).map(([, override]) => override); + line.appearance = { + lineName: line.appearance.lineName, + accentColor: line.appearance.accentColor ?? line.appearance.background ?? fb.accentColor, + background: line.appearance.background ?? fb.background, + border: fb.border, + color: line.appearance.color ?? fb.color, + contrastColor: line.appearance.color ?? fb.contrastColor, + shape: line.appearance.shape ?? fb.shape, + productName: '', + }; + + if (!line.appearance.accentColor) { + line.appearance.accentColor = determineAccentColor({ + backgroundColor: line.appearance.background!, + borderColor: line.appearance.border!, + textColor: line.appearance.color!, + }); + } - return Object.assign( - line.appearance, - FALLBACK_METHOD_APPEARANCES[line.method], - ...overrides - ); + return line.appearance; }, }; }; -const determineAccentColor = (definition: TrwlLineColorDefinition) => { +const determineAccentColor = (definition: Pick) => { // Preparations for background gradients // const backgroundColors = // definition.backgroundColor.toLowerCase().match(/(#[a-f\d]{3,6})/gi) ?? []; diff --git a/src/hooks/useStationSearch/useStationSearch.ts b/src/hooks/useStationSearch/useStationSearch.ts index dfc1b60..72acc03 100644 --- a/src/hooks/useStationSearch/useStationSearch.ts +++ b/src/hooks/useStationSearch/useStationSearch.ts @@ -20,9 +20,10 @@ const fetcher = async (query: string): Promise => { }; const getMatchDetails = ( - { name, rilIdentifier }: Pick, + { name, identifiers }: Pick, queryPattern: RegExp ) => { + const rilIdentifier = identifiers.filter(id => id.type === 'de_db_ril100')[0]?.identifier; const extendedName = `${name}${!rilIdentifier ? '' : ` (${rilIdentifier})`}`; const matchedLength = Array.from(extendedName.matchAll(queryPattern)) @@ -48,10 +49,13 @@ export const useStationSearch = (query: string) => { const [valueA, nameA] = getMatchDetails(a, queryPattern); const [valueB, nameB] = getMatchDetails(b, queryPattern); + const aRilIdentifier = a.identifiers.filter(id => id.type === 'de_db_ril100')[0]?.identifier; + const bRilIdentifier = b.identifiers.filter(id => id.type === 'de_db_ril100')[0]?.identifier; + // Always prioritize RIL identifiers - if (a.rilIdentifier === query) { + if (aRilIdentifier === query) { return -1; - } else if (b.rilIdentifier === query) { + } else if (bRilIdentifier === query) { return 1; } diff --git a/src/hooks/useStops/useStops.ts b/src/hooks/useStops/useStops.ts index b2795ea..a8f3d6d 100644 --- a/src/hooks/useStops/useStops.ts +++ b/src/hooks/useStops/useStops.ts @@ -5,16 +5,15 @@ import useSWR from 'swr'; const fetcher = async ( hafasTripId: string, lineName: string, - start: string ): Promise => { - if (!hafasTripId || !lineName || !start.trim()) { + if (!hafasTripId || !lineName) { return; } const response = await fetch( `/traewelling/trips?hafasTripId=${encodeURIComponent( hafasTripId - )}&lineName=${lineName}&start=${start.replace('/', '%20')}` + )}&lineName=${lineName}` ); if (!response.ok) { @@ -28,14 +27,14 @@ export const useStops = ( hafasTripId: string, lineName: string, plannedDeparture: string, - start: string + stationId: number, ) => { const { data, isLoading } = useSWR( - ['/traewelling/trips', hafasTripId, lineName, start], - ([_, hafasTripId, lineName, start]) => fetcher(hafasTripId, lineName, start) + ['/traewelling/trip', hafasTripId, lineName], + ([_, hafasTripId, lineName]) => fetcher(hafasTripId, lineName) ); - const stops = data && getStopsAfter(plannedDeparture, start, data.stopovers); + const stops = data && getStopsAfter(plannedDeparture, stationId, data.stopovers); return { isLoading, diff --git a/src/traewelling-sdk/functions/station.ts b/src/traewelling-sdk/functions/station.ts index 097a54f..1671db5 100644 --- a/src/traewelling-sdk/functions/station.ts +++ b/src/traewelling-sdk/functions/station.ts @@ -1,7 +1,6 @@ import { authOptions } from '@/pages/api/auth/[...nextauth]'; import { getServerSession } from 'next-auth'; -import { HAFASTrip } from '../hafasTypes'; -import { TransportType } from '../types'; +import { Departure, TransportType } from '../types'; type DeparturesInput = { id: number; @@ -25,7 +24,7 @@ export type DeparturesResponse = { prev: string; }; } | null; - trips: HAFASTrip[]; + trips: Departure[]; }; export const departures = async (input: DeparturesInput) => { diff --git a/src/traewelling-sdk/functions/trains.ts b/src/traewelling-sdk/functions/trains.ts index 0ce9831..1cb2277 100644 --- a/src/traewelling-sdk/functions/trains.ts +++ b/src/traewelling-sdk/functions/trains.ts @@ -6,10 +6,7 @@ type AutocompleteInput = { query: string; }; -export type AutocompleteResponse = Pick< - Station, - 'id' | 'ibnr' | 'name' | 'rilIdentifier' ->[]; +export type AutocompleteResponse = Station[]; export const autocomplete = async (input: AutocompleteInput) => { const session = await getServerSession(authOptions); @@ -144,7 +141,6 @@ export const nearby = async (input: NearbyInput) => { type TripInput = { hafasTripId: string; lineName: string; - start: string; }; export type TripResponse = Trip; @@ -158,11 +154,10 @@ export const trip = async (input: TripInput) => { }; } - const url = new URL('https://traewelling.de/api/v1/trains/trip/'); + const url = new URL('https://traewelling.de/api/v1/trains/trip'); url.searchParams.append('hafasTripId', input.hafasTripId); url.searchParams.append('lineName', input.lineName); - url.searchParams.append('start', input.start); const res = await fetch(url, { headers: { diff --git a/src/traewelling-sdk/hafasTypes.ts b/src/traewelling-sdk/hafasTypes.ts index 74f135e..ccc7e8f 100644 --- a/src/traewelling-sdk/hafasTypes.ts +++ b/src/traewelling-sdk/hafasTypes.ts @@ -35,7 +35,9 @@ export type HAFASProductType = | 'suburban' | 'subway' | 'taxi' - | 'tram'; + | 'tram' + | 'plane' + | 'freightTrain'; export type HAFASStation = { id: string; diff --git a/src/traewelling-sdk/motisTypes.ts b/src/traewelling-sdk/motisTypes.ts new file mode 100644 index 0000000..1b896da --- /dev/null +++ b/src/traewelling-sdk/motisTypes.ts @@ -0,0 +1,30 @@ +export type MotisMode = + 'WALK' | + 'BIKE' | + 'RENTAL' | + 'CAR' | + 'CAR_PARKING' | + 'CAR_DROPOFF' | + 'ODM' | + 'RIDE_SHARING' | + 'FLEX' | + 'TRANSIT' | + 'TRAM' | + 'SUBWAY' | + 'FERRY' | + 'AIRPLANE' | + 'SUBURBAN' | + 'BUS' | + 'COACH' | + 'RAIL' | + 'HIGHSPEED_RAIL' | + 'LONG_DISTANCE' | + 'NIGHT_RAIL' | + 'REGIONAL_FAST_RAIL' | + 'REGIONAL_RAIL' | + 'CABLE_CAR' | + 'FUNICULAR' | + 'AERIAL_LIFT' | + 'OTHER' | + 'AERAL_LIFT' | + 'METRO'; diff --git a/src/traewelling-sdk/transformers.ts b/src/traewelling-sdk/transformers.ts index 0dd955b..f1f46c3 100644 --- a/src/traewelling-sdk/transformers.ts +++ b/src/traewelling-sdk/transformers.ts @@ -10,30 +10,41 @@ import { AboardTrip, AboardVisibility, } from '@/types/aboard'; -import { - HAFASLine, - HAFASProductType, - HAFASStation, - HAFASStop, - HAFASTrip, -} from './hafasTypes'; -import { Station, Status, Stop, Trip } from './types'; +import { Departure, Station, Status, Stopover, Trip } from './types'; +import { MotisMode } from './motisTypes'; -const HAFAS_PRODUCT_MAPPER: Record = { - bus: 'bus', - ferry: 'ferry', - national: 'national', - nationalExpress: 'national-express', - regional: 'regional', - regionalExp: 'regional-express', - suburban: 'suburban', - subway: 'subway', - taxi: 'taxi', - tram: 'tram', +const MOTIS_PRODUCT_MAPPER: Record = { + BUS: 'bus', + COACH: 'bus', + FERRY: 'ferry', + REGIONAL_RAIL: 'national', + HIGHSPEED_RAIL: 'national-express', + RAIL: 'regional', + REGIONAL_FAST_RAIL: 'regional-express', + SUBURBAN: 'suburban', + METRO: 'suburban', + SUBWAY: 'subway', + CAR: 'taxi', + TRAM: 'tram', + AERAL_LIFT: 'aerial', + AERIAL_LIFT: 'aerial', + AIRPLANE: 'airplane', + BIKE: '_', + CABLE_CAR: 'aerial', + CAR_DROPOFF: '_', + CAR_PARKING: '_', + FLEX: 'taxi', + FUNICULAR: 'funicular', + LONG_DISTANCE: '_', + NIGHT_RAIL: 'national', + ODM: '_', + OTHER: '_', + RENTAL: '_', + RIDE_SHARING: '_', + TRANSIT: '_', + WALK: '_', }; -const HIDDEN_PRODUCT_NAMES = ['Bus', 'Fäh', 'STB', 'STR']; - const TRWL_BUSINESS_MAPPER: AboardTravelReason[] = [ 'private', 'business', @@ -68,112 +79,93 @@ export const transformAboardVisibility = ( return TRWL_VISIBILITY_MAPPER.indexOf(visibility); }; -export const transformHAFASLine = (line: HAFASLine): AboardLine => { - return { - appearance: { - lineName: line.name - .replaceAll( - new RegExp(`^(${HIDDEN_PRODUCT_NAMES.join('|')})(.)`, 'gi'), - '$2' - ) - .trim(), - productName: line.productName, - }, - id: line.id, - method: transformHAFASProductType(line.product), - name: line.name, - operator: !line.operator - ? undefined - : { - id: line.operator.id, - name: line.operator.name, - }, - }; -}; +// export const transformHAFASLine = (line: HAFASLine): AboardLine => { +// return { +// appearance: { +// lineName: line.name +// .replaceAll( +// new RegExp(`^(${HIDDEN_PRODUCT_NAMES.join('|')})(.)`, 'gi'), +// '$2' +// ) +// .trim(), +// productName: line.productName, +// }, +// id: line.id, +// method: transformHAFASProductType(line.product), +// name: line.name, +// operator: !line.operator +// ? undefined +// : { +// id: line.operator.id, +// name: line.operator.name, +// }, +// }; +// }; -export const transformHAFASProductType = ( - productType: HAFASProductType +export const transformMotisMode = ( + mode: MotisMode ): AboardMethod => { - return HAFAS_PRODUCT_MAPPER[productType]; + return MOTIS_PRODUCT_MAPPER[mode]; }; -export const transformHAFASStation = (station: HAFASStation): AboardStation => { - return { - evaId: +station.id, - ibnr: undefined, - latitude: station.location.latitude, - longitude: station.location.longitude, - name: station.name, - rilId: undefined, - servesMethod: Object.entries(station.products).reduce( - (transformed, [product, value]) => ({ - ...transformed, - [transformHAFASProductType(product as HAFASProductType)]: value, - }), - {} - ) as AboardStation['servesMethod'], - trwlId: undefined, - }; -}; - -export const transformHAFASStop = (stop: HAFASStop): AboardStation => { - return { - evaId: undefined, - ibnr: +stop.id, - latitude: stop.location.latitude, - longitude: stop.location.longitude, - name: stop.name, - rilId: undefined, - servesMethod: Object.entries(stop.products).reduce( - (transformed, [product, value]) => ({ - ...transformed, - [transformHAFASProductType(product as HAFASProductType)]: value, - }), - {} - ) as AboardStation['servesMethod'], - trwlId: undefined, - }; -}; +// export const transformHAFASStop = (stop: HAFASStop): AboardStation => { +// return { +// evaId: undefined, +// ibnr: +stop.id, +// latitude: stop.location.latitude, +// longitude: stop.location.longitude, +// name: stop.name, +// rilId: undefined, +// servesMethod: Object.entries(stop.products).reduce( +// (transformed, [product, value]) => ({ +// ...transformed, +// [transformHAFASProductType(product as HAFASProductType)]: value, +// }), +// {} +// ) as AboardStation['servesMethod'], +// trwlId: undefined, +// }; +// }; -export const transformHAFASTrip = (trip: HAFASTrip): AboardTrip => { - return { - departure: { - actual: trip.when ?? undefined, - planned: trip.plannedWhen ?? undefined, - }, - departureStation: { - evaId: trip.station.id, - ibnr: trip.station.ibnr, - latitude: +trip.station.latitude, - longitude: +trip.station.longitude, - name: trip.station.name, - rilId: trip.station.rilIdentifier ?? undefined, - servesMethod: Object.entries(trip.stop.products).reduce( - (transformed, [product, value]) => ({ - ...transformed, - [transformHAFASProductType(product as HAFASProductType)]: value, - }), - {} - ) as AboardStation['servesMethod'], - trwlId: undefined, - }, - designation: trip.direction, - destination: transformHAFASStop(trip.destination), - hafasId: trip.tripId, - line: transformHAFASLine(trip.line), - origin: !trip.origin ? undefined : transformHAFASStop(trip.origin), - platform: { - actual: trip.platform ?? undefined, - planned: trip.plannedPlatform ?? undefined, - }, - runningNumber: - !trip.line.fahrtNr || trip.line.fahrtNr === '0' - ? undefined - : trip.line.fahrtNr, - stopovers: undefined, - trwlId: undefined, - }; -}; +// export const transformHAFASTrip = (trip: HAFASTrip): AboardTrip => { +// return { +// departure: { +// actual: trip.when ?? undefined, +// planned: trip.plannedWhen ?? undefined, +// }, +// departureStation: { +// evaId: trip.station.id, +// ibnr: trip.station.ibnr, +// latitude: +trip.station.latitude, +// longitude: +trip.station.longitude, +// name: trip.station.name, +// rilId: trip.station.rilIdentifier ?? undefined, +// servesMethod: Object.entries(trip.stop.products).reduce( +// (transformed, [product, value]) => ({ +// ...transformed, +// [transformHAFASProductType(product as HAFASProductType)]: value, +// }), +// {} +// ) as AboardStation['servesMethod'], +// trwlId: undefined, +// }, +// designation: trip.direction, +// destination: transformHAFASStop(trip.destination), +// hafasId: trip.tripId, +// line: transformHAFASLine(trip.line), +// origin: !trip.origin ? undefined : transformHAFASStop(trip.origin), +// platform: { +// actual: trip.platform ?? undefined, +// planned: trip.plannedPlatform ?? undefined, +// }, +// runningNumber: +// !trip.line.fahrtNr || trip.line.fahrtNr === '0' +// ? undefined +// : trip.line.fahrtNr, +// stopovers: undefined, +// trwlId: undefined, +// }; +// }; export const transformTrwlLineShape = ( shape: string @@ -184,77 +176,101 @@ export const transformTrwlLineShape = ( export const transformTrwlStation = (station: Station): AboardStation => { return { evaId: undefined, - ibnr: station.ibnr, + ibnr: +station.identifiers?.filter(id => id.type === 'de_db_ibnr')[0]?.identifier, latitude: station.latitude, longitude: station.longitude, name: station.name, - rilId: station.rilIdentifier ?? undefined, + rilId: station.identifiers?.filter(id => id.type === 'de_db_ril100')[0]?.identifier, servesMethod: undefined, trwlId: station.id, }; }; +export const transformTrwlStopover = (stopover: Stopover): AboardStopover => { + return { + station: { + trwlId: stopover.id, + name: stopover.name, + }, + arrival: { + planned: stopover.arrivalPlanned, + actual: stopover.arrivalReal ?? undefined, + }, + departure: { + planned: stopover.departurePlanned, + actual: stopover.departureReal ?? undefined, + }, + platform: { + planned: stopover.departurePlatformPlanned ?? stopover.arrivalPlatformPlanned ?? undefined, + actual: stopover.departurePlatformReal ?? stopover.arrivalPlatformReal ?? undefined, + }, + status: stopover.cancelled ? 'cancelled' : 'scheduled', + }; +}; + +export const transformTrwlColor = (color: string | null): (string | undefined) => { + if (!color) return; + return `#${color}`; +}; + export const transformTrwlStatus = (status: Status): AboardStatus => { return { createdAt: status.createdAt, event: status.event, - id: status.id, + id: status.id.toString(), isLikeable: status.isLikable, journey: { - destination: transformTrwlStop(status.train.destination), - distance: status.train.distance, - duration: status.train.duration, - hafasTripId: status.train.hafasId, + destination: transformTrwlStop(status.checkin.destination), + distance: status.checkin.distance, + duration: status.checkin.duration, + hafasTripId: status.checkin.hafasId, line: { appearance: { - lineName: status.train.lineName - .replaceAll( - new RegExp(`^(${HIDDEN_PRODUCT_NAMES.join('|')})(.)`, 'gi'), - '$2' - ) + lineName: status.checkin.lineName .trim(), + color: transformTrwlColor(status.checkin.routeTextColor), + background: transformTrwlColor(status.checkin.routeColor), productName: '', }, - id: status.train.number, - method: transformHAFASProductType(status.train.category), - name: status.train.lineName, + id: status.checkin.number, + method: transformMotisMode(status.checkin.mode), + name: status.checkin.lineName, operator: { - id: status.train.operator?.identifier ?? '', - name: status.train.operator?.name ?? '', + id: status.checkin.operator?.identifier ?? '', + name: status.checkin.operator?.name ?? '', }, }, - manualArrival: status.train.manualArrival, - manualDeparture: status.train.manualDeparture, - origin: transformTrwlStop(status.train.origin), - pointsAwarded: status.train.points, - trwlTripId: status.train.trip, - runningNumber: status.train.journeyNumber?.toString() ?? '', + manualArrival: status.checkin.manualArrival ?? status.checkin.destination.arrivalReal ?? status.checkin.destination.arrivalPlanned, + manualDeparture: status.checkin.manualDeparture ?? status.checkin.origin.departureReal ?? status.checkin.origin.departurePlanned, + origin: transformTrwlStop(status.checkin.origin), + pointsAwarded: status.checkin.points, + trwlTripId: status.checkin.trip, + runningNumber: status.checkin.journeyNumber?.toString() ?? '', }, likeCount: status.likes, likedByMe: status.liked, message: status.body, - preventIndex: status.preventIndex, + preventIndex: status.user.preventIndex, travelReason: TRWL_BUSINESS_MAPPER[status.business], - userAvatarUrl: status.profilePicture, - userId: status.user, - username: status.username, + userAvatarUrl: status.user.profilePicture, + userId: status.user.id, + username: status.user.username, visibility: TRWL_VISIBILITY_MAPPER[status.visibility], }; }; -export const transformTrwlStop = (stop: Stop): AboardStopover => { +export const transformTrwlStop = (stop: Stopover): AboardStopover => { return { arrival: { - actual: stop.arrival ?? stop.arrivalReal ?? undefined, + actual: stop.arrivalReal ?? undefined, planned: stop.arrivalPlanned ?? undefined, }, departure: { - actual: stop.departure ?? stop.departureReal ?? undefined, + actual: stop.departureReal ?? undefined, planned: stop.departurePlanned ?? undefined, }, platform: { actual: - stop.platform ?? stop.arrivalPlatformReal ?? stop.departurePlatformReal ?? undefined, @@ -264,12 +280,12 @@ export const transformTrwlStop = (stop: Stop): AboardStopover => { undefined, }, station: { - evaId: stop.evaIdentifier, + evaId: undefined, ibnr: undefined, latitude: undefined, longitude: undefined, name: stop.name, - rilId: stop.rilIdentifier ?? undefined, + rilId: undefined, trwlId: stop.id, }, status: stop.cancelled ? 'cancelled' : 'scheduled', @@ -278,28 +294,61 @@ export const transformTrwlStop = (stop: Stop): AboardStopover => { export const transformTrwlTrip = (trip: Trip): AboardTrip => { return { - departure: undefined, - departureStation: undefined, - designation: trip.stopovers.at(-1)?.name ?? '', + designation: trip.destination.name ?? '', destination: transformTrwlStation(trip.destination), - hafasId: undefined, line: { appearance: { lineName: trip.lineName, productName: '', }, id: trip.number, - method: transformHAFASProductType(trip.category), + method: transformMotisMode(trip.mode), name: trip.lineName, operator: { id: '', name: '', }, }, + stopovers: trip.stopovers.map(transformTrwlStopover), origin: transformTrwlStation(trip.origin), platform: undefined, runningNumber: trip.journeyNumber?.toString(), - stopovers: trip.stopovers.map(transformTrwlStop), trwlId: trip.id, + hafasId: trip.id.toString(), + }; +}; + +export const transformTrwlDeparture = (departure: Departure): AboardTrip => { + return { + designation: departure.direction, + destination: { + name: departure.direction, + }, + line: { + appearance: { + lineName: departure.line.name, + color: transformTrwlColor(departure.line.textColor), + background: transformTrwlColor(departure.line.color), + productName: '', + }, + id: departure.line.id, + method: transformMotisMode(departure.line.mode), + name: departure.line.name, + operator: { + id: '', + name: '', + }, + }, + platform: { + actual: departure.platform ?? undefined, + planned: departure.plannedPlatform ?? undefined, + }, + departure: { + planned: departure.plannedWhen, + actual: departure.when ?? undefined, + }, + departureStation: transformTrwlStation(departure.station), + runningNumber: departure.line.fahrtNr, + hafasId: departure.tripId, }; }; diff --git a/src/traewelling-sdk/types.ts b/src/traewelling-sdk/types.ts index 41ab512..8dd8ce0 100644 --- a/src/traewelling-sdk/types.ts +++ b/src/traewelling-sdk/types.ts @@ -1,4 +1,5 @@ import { HAFASProductType } from './hafasTypes'; +import { MotisMode } from './motisTypes'; export type TrwlLineColorDefinition = { backgroundColor: string; @@ -12,12 +13,27 @@ export type TrwlLineColorDefinition = { }; export type Station = { - ibnr: number; id: number; latitude: number; longitude: number; name: string; - rilIdentifier: string | null; + areas: Area[]; + identifiers: StationIdentifier[], + time_offset: number | null; + created_at: string | null; +}; + +export type Area = { + name: string; + default: boolean; + adminLevel: number; +}; + +export type StationIdentifier = { + type: string; + identifier: string; + name: string | null; + origin: string | null; }; export type Status = { @@ -25,57 +41,74 @@ export type Status = { business: number; createdAt: string; event: any; // TODO: Add type - id: string; + id: number; isLikable: boolean; liked: boolean; likes: number; - preventIndex: boolean; - profilePicture: string; - train: Train; - user: number; - username: string; + checkin: Transport; + user: LightUser; + createdBy: LightUser | null; + tags: StatusTag[]; + moderation_notes: string | null; + lock_visibility: boolean; + hide_body: boolean; visibility: number; }; -export type Stop = { - arrival: string | null; - arrivalPlanned: string | null; - arrivalPlatformPlanned: string | null; - arrivalPlatformReal: string | null; - arrivalReal: string | null; - cancelled: boolean; - departure: string | null; - departurePlanned: string | null; - departurePlatformPlanned: string | null; - departurePlatformReal: string | null; - departureReal: string | null; - evaIdentifier: number; - id: number; - isArrivalDelayed: boolean; - isDepartureDelayed: boolean; - name: string; - platform: string | null; - rilIdentifier: string | null; +export type StatusTag = { + key: string; + value: string; + visibility: number; }; -export type Train = { - category: HAFASProductType; - destination: Stop; - distance: number; - duration: number; - hafasId: string; - journeyNumber?: number; - lineName: string; - manualArrival: string; - manualDeparture: string; - number: string; // LINE ID - operator: { - identifier: string; - name: string; - } | null; - origin: Stop; - points: number; - trip: number; +export type Transport = { + trip: number; + hafasId: string; + category: HAFASProductType; + mode: MotisMode; + number: string; + lineName: string; + routeColor: string | null; + routeTextColor: string | null; + journeyNumber: number; + manualJourneyNumber: string | null; + distance: number; // in meters + points: number; + duration: number; // in minutes + manualDeparture: string | null; + manualArrival: string | null; + origin: Stopover; + destination: Stopover; + operator: Operator | null; + dataSource: DataSource | null; +}; + +export type Stopover = { + id: number; + name: string; + arrivalPlanned: string; + arrivalReal: string | null; + arrivalPlatformPlanned: string | null; + arrivalPlatformReal: string | null; + departurePlanned: string; + departureReal: string | null; + departurePlatformPlanned: string | null; + departurePlatformReal: string | null; + platform: string | null; + isArrivalDelayed: boolean; + isDepartureDelayed: boolean; + cancelled: boolean; +}; + +export type Operator = { + id: number; + identifier: string; + name: string; +}; + +export type DataSource = { + id: string; + attribution: string; }; export type TransportType = @@ -88,34 +121,26 @@ export type TransportType = | 'taxi' | 'tram'; -export type Trip = { - category: HAFASProductType; - destination: Station; - id: number; - journeyNumber: number | null; // Zugnummer - lineName: string; - number: string; // HAFAS line id - origin: Station; - stopovers: Stop[]; +export type LightUser = { + id: number; + uuid: string; + displayName: string; + username: string; + profilePicture: string; + mastodon: any; + preventIndex: boolean; }; -export type User = { - displayName: string; +export type User = LightUser & { home: Station | null; - id: number; language: string | null; - mastodonUrl: string | null; points: number; - preventIndex: boolean; privacyHideDays: number | null; privateProfile: boolean; - profilePicture: string; role: number; // TODO: Type trainDistance: number; trainDuration: number; trainSpeed: number; - twitterUrl: string | null; - username: string; }; export type PublicUser = { @@ -136,3 +161,48 @@ export type PublicUser = { userInvisibleToMe: boolean; username: string; }; + +export type Departure = { + tripId: string; + stop: { + type: 'stop'; + id: number; + name: string; + location: { + type: 'location'; + id: string | null; + latitude: number; + longitude: number; + } + }; + when: string | null; + plannedWhen: string; + platform: string | null; + plannedPlatform: string | null; + direction: string; + line: { + type: 'line'; + id: string; + fahrtNr: string; + name: string; + color: string | null; + textColor: string | null; + mode: MotisMode; + product: HAFASProductType; + }; + cancelled: boolean; + station: Station; +}; + +export type Trip = { + id: number; + category: HAFASProductType, + mode: MotisMode, + number: string; + lineName: string; + journeyNumber: number; + origin: Station; + destination: Station; + stopovers: Stopover[]; + dataSource: DataSource | null; +}; diff --git a/src/types/aboard.ts b/src/types/aboard.ts index 37d2b64..426cc41 100644 --- a/src/types/aboard.ts +++ b/src/types/aboard.ts @@ -27,16 +27,20 @@ export type AboardLineAppearance = { }; export type AboardMethod = - | 'bus' - | 'ferry' - | 'national-express' - | 'national' - | 'regional-express' - | 'regional' - | 'suburban' - | 'subway' - | 'taxi' - | 'tram'; + | 'bus' + | 'ferry' + | 'national-express' + | 'national' + | 'regional-express' + | 'regional' + | 'suburban' + | 'subway' + | 'taxi' + | 'tram' + | 'aerial' + | 'airplane' + | 'funicular' + | '_'; export type AboardOperator = { id: string; diff --git a/tsconfig.json b/tsconfig.json index 7682565..749d712 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,7 +22,8 @@ { "name": "next" } - ] + ], + "types": ["node"] }, "include": [ "next-env.d.ts",