Compare commits
5 Commits
a67213e669
...
2e63609129
| Author | SHA1 | Date | |
|---|---|---|---|
|
2e63609129
|
|||
|
6e9c5291ba
|
|||
|
6c3ef85cb8
|
|||
|
88b8f7cdee
|
|||
|
2a37483d88
|
@@ -1,9 +1,9 @@
|
|||||||
import { useQuery } from "@tanstack/react-query"
|
import { useQuery } from "@tanstack/react-query"
|
||||||
import Chart from "chart.js/auto"
|
import Chart from "chart.js/auto"
|
||||||
import { Fragment, useEffect, useId, useLayoutEffect, useRef, useState } from "react"
|
import { useEffect, useLayoutEffect, useRef, useState } from "react"
|
||||||
import { beszelSystemsQuery } from "./beszel"
|
import { beszelSystemsQuery } from "./beszel"
|
||||||
import cn from "./components/lib/cn"
|
import cn from "./components/lib/cn"
|
||||||
import { StatusSeverity, formatLineName, getLineColor, getStatusBorderColor, tflDisruptionsQuery } from "./tfl"
|
import { StatusSeverity, TubeLine, formatLineName, tflDisruptionsQuery } from "./tfl"
|
||||||
import {
|
import {
|
||||||
DEFAULT_LATITUDE,
|
DEFAULT_LATITUDE,
|
||||||
DEFAULT_LONGITUDE,
|
DEFAULT_LONGITUDE,
|
||||||
@@ -15,31 +15,27 @@ import {
|
|||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<div className="h-screen bg-black gap-4 text-neutral-200 grid grid-cols-4 grid-rows-5 p-4">
|
<div className="h-screen bg-neutral-300 dark:bg-neutral-800 p-2">
|
||||||
<DateTimeTile />
|
<div className="w-full h-full grid grid-cols-4 grid-rows-5 gap-2 bg-neutral-300 text-neutral-700 dark:bg-neutral-800 dark:text-neutral-300">
|
||||||
<WeatherTile />
|
<DateTimeTile />
|
||||||
<TFLTile />
|
<WeatherTile />
|
||||||
<SystemTile systemName="helian" displayName="Helian" />
|
<SystemTile className="row-start-2 row-span-1" systemName="helian" displayName="Helian" />
|
||||||
<SystemTile systemName="akira" displayName="Akira" />
|
<SystemTile className="row-start-2 row-span-1" systemName="akira" displayName="Akira" />
|
||||||
|
<TFLTile className="row-start-1 row-span-1" />
|
||||||
|
<Tile className="row-start-3 col-span-2 row-span-3" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Tile({
|
function Tile({ children, className }: { children?: React.ReactNode; className?: string }) {
|
||||||
decorations = true,
|
|
||||||
children,
|
|
||||||
className,
|
|
||||||
}: { decorations?: boolean; children: React.ReactNode; className?: string }) {
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("relative bg-neutral-900 flex flex-col justify-end items-start", className)}>
|
<div
|
||||||
{decorations && (
|
className={cn(
|
||||||
<>
|
"relative rounded-xl bg-neutral-200 dark:bg-neutral-900 flex flex-col justify-end items-start",
|
||||||
<div className="absolute top-0 left-0 w-4 h-[1px] bg-neutral-200" />
|
className,
|
||||||
<div className="absolute top-0 left-0 w-[1px] h-4 bg-neutral-200" />
|
|
||||||
<div className="absolute bottom-0 right-0 w-4 h-[1px] bg-neutral-200" />
|
|
||||||
<div className="absolute bottom-0 right-0 w-[1px] h-4 bg-neutral-200" />
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -69,8 +65,8 @@ function DateTimeTile() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Tile className="col-start-1 row-start-1 col-span-2 row-span-3 p-6">
|
<Tile className="col-start-1 row-start-1 col-span-2 row-span-3 p-6">
|
||||||
<p className="text-4xl mb-2 font-extralight">{formattedDate}</p>
|
<p className="text-4xl mb-2 font-mono uppercase tracking-tigher">{formattedDate}</p>
|
||||||
<p className="text-8xl font-bold">{formattedTime}</p>
|
<p className="text-8xl font-extralight tracking-tight">{formattedTime}</p>
|
||||||
</Tile>
|
</Tile>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -164,18 +160,18 @@ function WeatherTile() {
|
|||||||
<div
|
<div
|
||||||
// biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
|
// biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
|
||||||
key={index}
|
key={index}
|
||||||
className={cn("w-10 bg-teal-400 h-[2px]")}
|
className={cn("w-10 bg-teal-500 dark:bg-teal-400 h-[2px]")}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"absolute flex flex-row items-center space-x-1 top-0 right-0 bg-teal-400 text-neutral-900 px-2 py-1 text-2xl font-bold rounded-r-sm translate-x-[calc(100%-1px)]",
|
"absolute flex flex-row items-center space-x-1 top-0 right-0 bg-teal-500 dark:bg-teal-400 text-neutral-200 dark:text-neutral-900 px-2 py-1 text-4xl font-bold rounded-r translate-x-[calc(100%-1px)]",
|
||||||
percentage < 0.3
|
percentage < 0.3
|
||||||
? "-translate-y-[calc(100%-2px)] rounded-tl-sm"
|
? "-translate-y-[calc(100%-2px)] rounded-tl"
|
||||||
: "rounded-bl-sm",
|
: "rounded-bl",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<p className="leading-none translate-y-px">{temperature}°</p>
|
<p className="leading-none translate-y-px">{temperature}°</p>
|
||||||
<WeatherIcon className="size-6" strokeWidth={3} />
|
<WeatherIcon className="size-8" strokeWidth={3} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -187,14 +183,14 @@ function WeatherTile() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
"w-4",
|
"w-4",
|
||||||
index >= highlightIndexStart
|
index >= highlightIndexStart
|
||||||
? "bg-teal-400 w-8 h-[2px]"
|
? "bg-teal-500 dark:bg-teal-400 w-8 h-[2px]"
|
||||||
: "bg-neutral-400 w-4 h-[1px]",
|
: "bg-neutral-400 w-4 h-[1px]",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col justify-start h-full space-y-2 flex-[3]">
|
<div className="flex flex-col justify-start h-full space-y-2 flex-[2]">
|
||||||
<p
|
<p
|
||||||
className={cn("text-3xl leading-none tracking-tight font-light", {
|
className={cn("text-3xl leading-none tracking-tight font-light", {
|
||||||
"text-red-400": errorWeatherDescription,
|
"text-red-400": errorWeatherDescription,
|
||||||
@@ -209,7 +205,16 @@ function WeatherTile() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function TFLTile() {
|
function TFLTile({ className }: { className?: string }) {
|
||||||
|
const linesIDontCareAbout = [
|
||||||
|
TubeLine.WaterlooCity,
|
||||||
|
TubeLine.Windrush,
|
||||||
|
TubeLine.Lioness,
|
||||||
|
TubeLine.Lioness,
|
||||||
|
TubeLine.Tram,
|
||||||
|
TubeLine.Mildmay,
|
||||||
|
]
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: tflData,
|
data: tflData,
|
||||||
isLoading: isLoadingTFL,
|
isLoading: isLoadingTFL,
|
||||||
@@ -219,8 +224,10 @@ function TFLTile() {
|
|||||||
select: (data) => {
|
select: (data) => {
|
||||||
data.disruptions.sort((a, b) => {
|
data.disruptions.sort((a, b) => {
|
||||||
if (a.lineName.match(/northern/i)) return -1
|
if (a.lineName.match(/northern/i)) return -1
|
||||||
|
if (b.lineName.match(/northern/i)) return 1
|
||||||
return a.statusSeverity - b.statusSeverity
|
return a.statusSeverity - b.statusSeverity
|
||||||
})
|
})
|
||||||
|
data.disruptions = data.disruptions.filter((disruption) => !linesIDontCareAbout.includes(disruption.lineId))
|
||||||
return data
|
return data
|
||||||
},
|
},
|
||||||
refetchInterval: 5 * 60 * 1000, // 5 minutes
|
refetchInterval: 5 * 60 * 1000, // 5 minutes
|
||||||
@@ -229,7 +236,9 @@ function TFLTile() {
|
|||||||
|
|
||||||
if (isLoadingTFL) {
|
if (isLoadingTFL) {
|
||||||
return (
|
return (
|
||||||
<Tile className="col-start-3 h-full row-start-1 col-span-2 row-span-1 flex flex-row justify-start items-center p-8">
|
<Tile
|
||||||
|
className={cn("h-full col-span-2 row-span-1 flex flex-row justify-start items-center p-8", className)}
|
||||||
|
>
|
||||||
<p className="text-2xl font-light animate-pulse">Loading tube status</p>
|
<p className="text-2xl font-light animate-pulse">Loading tube status</p>
|
||||||
</Tile>
|
</Tile>
|
||||||
)
|
)
|
||||||
@@ -237,7 +246,9 @@ function TFLTile() {
|
|||||||
|
|
||||||
if (errorTFL) {
|
if (errorTFL) {
|
||||||
return (
|
return (
|
||||||
<Tile className="col-start-3 h-full row-start-1 col-span-2 row-span-1 flex flex-row justify-start items-center p-8">
|
<Tile
|
||||||
|
className={cn("h-full col-span-2 row-span-1 flex flex-row justify-start items-center p-8", className)}
|
||||||
|
>
|
||||||
<p className="text-2xl font-light text-red-400">Error loading from TfL</p>
|
<p className="text-2xl font-light text-red-400">Error loading from TfL</p>
|
||||||
<p className="text-neutral-400">{errorTFL?.message}</p>
|
<p className="text-neutral-400">{errorTFL?.message}</p>
|
||||||
</Tile>
|
</Tile>
|
||||||
@@ -246,7 +257,9 @@ function TFLTile() {
|
|||||||
|
|
||||||
if (!tflData) {
|
if (!tflData) {
|
||||||
return (
|
return (
|
||||||
<Tile className="col-start-3 h-full row-start-1 col-span-2 row-span-1 flex flex-row justify-start items-center p-8">
|
<Tile
|
||||||
|
className={cn("h-full col-span-2 row-span-1 flex flex-row justify-start items-center p-8", className)}
|
||||||
|
>
|
||||||
<p className="text-2xl font-light">No TfL data available</p>
|
<p className="text-2xl font-light">No TfL data available</p>
|
||||||
</Tile>
|
</Tile>
|
||||||
)
|
)
|
||||||
@@ -254,36 +267,132 @@ function TFLTile() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Tile
|
<Tile
|
||||||
decorations={false}
|
className={cn(
|
||||||
className="gap-x-1 col-start-3 h-full row-start-1 col-span-2 row-span-1 grid grid-cols-[min-content_1fr] auto-rows-min overflow-y-auto"
|
"gap-x-1 pt-1 h-full col-span-2 row-span-1 grid grid-cols-[min-content_1fr] auto-rows-min overflow-y-auto",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{tflData.goodService.includes("Northern") && (
|
{tflData.goodService.includes("Northern") && (
|
||||||
<TFLDistruptionItem lineId="northern" reason="Good service" severity={StatusSeverity.GoodService} />
|
<TFLDistruptionItem
|
||||||
|
lineId={TubeLine.Northern}
|
||||||
|
reason="Good service"
|
||||||
|
severity={StatusSeverity.GoodService}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{tflData.disruptions.map((disruption) => (
|
{tflData.disruptions.map((disruption) => (
|
||||||
<Fragment key={disruption.lineId}>
|
<TFLDistruptionItem
|
||||||
<TFLDistruptionItem
|
key={disruption.lineId}
|
||||||
lineId={disruption.lineId}
|
lineId={disruption.lineId}
|
||||||
reason={disruption.reason ?? "Unknown reason"}
|
reason={disruption.reason ?? "Unknown reason"}
|
||||||
severity={disruption.statusSeverity}
|
severity={disruption.statusSeverity}
|
||||||
/>
|
/>
|
||||||
<hr className="col-span-2 border-neutral-700" />
|
|
||||||
</Fragment>
|
|
||||||
))}
|
))}
|
||||||
</Tile>
|
</Tile>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function TFLDistruptionItem({ lineId, reason, severity }: { lineId: string; reason: string; severity: number }) {
|
function TFLDistruptionItem({ lineId, reason, severity }: { lineId: TubeLine; reason: string; severity: number }) {
|
||||||
const lineName = formatLineName(lineId)
|
const lineName = formatLineName(lineId)
|
||||||
|
|
||||||
|
let lineStyleClass: string
|
||||||
|
switch (lineId) {
|
||||||
|
case "bakerloo":
|
||||||
|
lineStyleClass = "bg-amber-700"
|
||||||
|
break
|
||||||
|
case "central":
|
||||||
|
lineStyleClass = "bg-red-600"
|
||||||
|
break
|
||||||
|
case "circle":
|
||||||
|
lineStyleClass = "bg-yellow-400 text-neutral-900"
|
||||||
|
break
|
||||||
|
case "district":
|
||||||
|
lineStyleClass = "bg-green-600"
|
||||||
|
break
|
||||||
|
case "hammersmith-city":
|
||||||
|
lineStyleClass = "bg-pink-400"
|
||||||
|
break
|
||||||
|
case "jubilee":
|
||||||
|
lineStyleClass = "bg-slate-500"
|
||||||
|
break
|
||||||
|
case "metropolitan":
|
||||||
|
lineStyleClass = "bg-purple-800"
|
||||||
|
break
|
||||||
|
case "northern":
|
||||||
|
lineStyleClass = "bg-black"
|
||||||
|
break
|
||||||
|
case "piccadilly":
|
||||||
|
lineStyleClass = "bg-blue-900"
|
||||||
|
break
|
||||||
|
case "victoria":
|
||||||
|
lineStyleClass = "bg-sky-500"
|
||||||
|
break
|
||||||
|
case "waterloo-city":
|
||||||
|
lineStyleClass = "bg-teal-500"
|
||||||
|
break
|
||||||
|
case "london-overground":
|
||||||
|
lineStyleClass = "bg-orange-500"
|
||||||
|
break
|
||||||
|
case "dlr":
|
||||||
|
lineStyleClass = "bg-teal-600"
|
||||||
|
break
|
||||||
|
case "elizabeth":
|
||||||
|
lineStyleClass = "bg-purple-600"
|
||||||
|
break
|
||||||
|
case "tram":
|
||||||
|
lineStyleClass = "bg-green-500"
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
lineStyleClass = "bg-gray-500"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
let statusBorderClass: string
|
||||||
|
switch (severity) {
|
||||||
|
case StatusSeverity.GoodService:
|
||||||
|
statusBorderClass = "border-green-500"
|
||||||
|
break
|
||||||
|
case StatusSeverity.MinorDelays:
|
||||||
|
statusBorderClass = "border-yellow-500"
|
||||||
|
break
|
||||||
|
case StatusSeverity.Suspended:
|
||||||
|
statusBorderClass = "border-red-600"
|
||||||
|
break
|
||||||
|
case StatusSeverity.PartSuspended:
|
||||||
|
statusBorderClass = "border-red-500"
|
||||||
|
break
|
||||||
|
case StatusSeverity.PlannedClosure:
|
||||||
|
statusBorderClass = "border-orange-600"
|
||||||
|
break
|
||||||
|
case StatusSeverity.PartClosure:
|
||||||
|
statusBorderClass = "border-yellow-500"
|
||||||
|
break
|
||||||
|
case StatusSeverity.SevereDelays:
|
||||||
|
statusBorderClass = "border-red-500"
|
||||||
|
break
|
||||||
|
case StatusSeverity.ReducedService:
|
||||||
|
statusBorderClass = "border-orange-500"
|
||||||
|
break
|
||||||
|
case StatusSeverity.BusService:
|
||||||
|
statusBorderClass = "border-blue-500"
|
||||||
|
break
|
||||||
|
case StatusSeverity.Information:
|
||||||
|
statusBorderClass = "border-blue-400"
|
||||||
|
break
|
||||||
|
case StatusSeverity.ServiceClosed:
|
||||||
|
statusBorderClass = "border-red-700"
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
statusBorderClass = "border-gray-400"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="h-full flex items-center justify-center px-2 py-0.5">
|
<div className="h-full flex items-center justify-center px-2 py-0.5">
|
||||||
<p
|
<p
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-xl uppercase font-bold bg-blue-500 w-full text-center px-1 rounded-sm",
|
"text-neutral-200 text-xl uppercase font-bold w-full text-center px-1 rounded-lg",
|
||||||
getLineColor(lineId),
|
lineStyleClass,
|
||||||
getStatusBorderColor(severity),
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{lineName}
|
{lineName}
|
||||||
@@ -291,8 +400,8 @@ function TFLDistruptionItem({ lineId, reason, severity }: { lineId: string; reas
|
|||||||
</div>
|
</div>
|
||||||
<p
|
<p
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-xl text-wrap text-neutral-300 leading-tight self-center pr-2 py-1 font-light border-r-4",
|
"text-xl text-wrap leading-tight self-center pr-2 py-1.5 font-light border-r-4",
|
||||||
getStatusBorderColor(severity),
|
statusBorderClass,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{reason}
|
{reason}
|
||||||
@@ -378,17 +487,14 @@ function SystemTile({
|
|||||||
|
|
||||||
if (!beszelSystemsData) {
|
if (!beszelSystemsData) {
|
||||||
return (
|
return (
|
||||||
<Tile className={cn("h-full row-start-2 flex flex-row justify-start items-center p-8", className)}>
|
<Tile className={cn("h-full flex flex-row justify-start items-center p-8", className)}>
|
||||||
<p className="text-2xl font-light">No system status available</p>
|
<p className="text-2xl font-light">No system status available</p>
|
||||||
</Tile>
|
</Tile>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tile
|
<Tile className={cn("h-full flex flex-col justify-start items-start", className)}>
|
||||||
decorations={false}
|
|
||||||
className={cn("h-full row-start-2 flex flex-col justify-start items-start", className)}
|
|
||||||
>
|
|
||||||
<div className="grid grid-cols-6 px-4 pt-3 w-full">
|
<div className="grid grid-cols-6 px-4 pt-3 w-full">
|
||||||
<div className="col-span-3 flex flex-row items-center space-x-2">
|
<div className="col-span-3 flex flex-row items-center space-x-2">
|
||||||
<p className="text-2xl">{displayName}</p>
|
<p className="text-2xl">{displayName}</p>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const API_BASE_URL = import.meta.env.VITE_API_URL || "http://localhost:8000"
|
|||||||
|
|
||||||
// Disruption Summary
|
// Disruption Summary
|
||||||
export interface DisruptionSummary {
|
export interface DisruptionSummary {
|
||||||
lineId: string
|
lineId: TubeLine
|
||||||
lineName: string
|
lineName: string
|
||||||
mode: string
|
mode: string
|
||||||
status: string
|
status: string
|
||||||
@@ -53,6 +53,27 @@ export enum StatusSeverity {
|
|||||||
ServiceClosed = 20,
|
ServiceClosed = 20,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum TubeLine {
|
||||||
|
Bakerloo = "bakerloo",
|
||||||
|
Central = "central",
|
||||||
|
Circle = "circle",
|
||||||
|
District = "district",
|
||||||
|
HammersmithCity = "hammersmith-city",
|
||||||
|
Jubilee = "jubilee",
|
||||||
|
Metropolitan = "metropolitan",
|
||||||
|
Northern = "northern",
|
||||||
|
Piccadilly = "piccadilly",
|
||||||
|
Victoria = "victoria",
|
||||||
|
WaterlooCity = "waterloo-city",
|
||||||
|
LondonOverground = "london-overground",
|
||||||
|
DLR = "dlr",
|
||||||
|
Elizabeth = "elizabeth",
|
||||||
|
Tram = "tram",
|
||||||
|
Lioness = "lioness",
|
||||||
|
Windrush = "windrush",
|
||||||
|
Mildmay = "mildmay",
|
||||||
|
}
|
||||||
|
|
||||||
// Helper function to get severity color
|
// Helper function to get severity color
|
||||||
export function getSeverityColor(severity: number): string {
|
export function getSeverityColor(severity: number): string {
|
||||||
if (severity >= 10) return "green" // Good Service
|
if (severity >= 10) return "green" // Good Service
|
||||||
@@ -90,89 +111,27 @@ export function getSeverityLabel(severity: number): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to format line name for display
|
// Helper function to format line name for display
|
||||||
export function formatLineName(lineId: string): string {
|
export function formatLineName(line: TubeLine): string {
|
||||||
const lineNames: Record<string, string> = {
|
const lineNames: Record<TubeLine, string> = {
|
||||||
bakerloo: "Bakerloo",
|
[TubeLine.Bakerloo]: "Bakerloo",
|
||||||
central: "Central",
|
[TubeLine.Central]: "Central",
|
||||||
circle: "Circle",
|
[TubeLine.Circle]: "Circle",
|
||||||
district: "District",
|
[TubeLine.District]: "District",
|
||||||
"hammersmith-city": "H&C",
|
[TubeLine.HammersmithCity]: "H&C",
|
||||||
jubilee: "Jubilee",
|
[TubeLine.Jubilee]: "Jubilee",
|
||||||
metropolitan: "Metropolitan",
|
[TubeLine.Metropolitan]: "Met",
|
||||||
northern: "Northern",
|
[TubeLine.Northern]: "Northern",
|
||||||
piccadilly: "Piccadilly",
|
[TubeLine.Piccadilly]: "Piccadilly",
|
||||||
victoria: "Victoria",
|
[TubeLine.Victoria]: "Victoria",
|
||||||
"waterloo-city": "W&C",
|
[TubeLine.WaterlooCity]: "W&C",
|
||||||
"london-overground": "London Overground",
|
[TubeLine.LondonOverground]: "London Overground",
|
||||||
dlr: "DLR",
|
[TubeLine.DLR]: "DLR",
|
||||||
"elizabeth-line": "Elizabeth Line",
|
[TubeLine.Elizabeth]: "Lizzie",
|
||||||
tram: "Tram",
|
[TubeLine.Tram]: "Tram",
|
||||||
}
|
}
|
||||||
return lineNames[lineId] || lineId
|
return lineNames[line] || line
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map of tube lines to their official TfL colors (as Tailwind classes)
|
|
||||||
export function getLineColor(lineId: string): string {
|
|
||||||
const lineColors: Record<string, string> = {
|
|
||||||
bakerloo: "bg-amber-700",
|
|
||||||
central: "bg-red-600",
|
|
||||||
circle: "bg-yellow-400",
|
|
||||||
district: "bg-green-600",
|
|
||||||
"hammersmith-city": "bg-pink-400",
|
|
||||||
jubilee: "bg-slate-500",
|
|
||||||
metropolitan: "bg-purple-800",
|
|
||||||
northern: "bg-black",
|
|
||||||
piccadilly: "bg-blue-900",
|
|
||||||
victoria: "bg-sky-500",
|
|
||||||
"waterloo-city": "bg-teal-500",
|
|
||||||
"london-overground": "bg-orange-500",
|
|
||||||
dlr: "bg-teal-600",
|
|
||||||
"elizabeth-line": "bg-purple-600",
|
|
||||||
tram: "bg-green-500",
|
|
||||||
}
|
|
||||||
return lineColors[lineId] || "bg-gray-500"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Map of status severity to border colors
|
|
||||||
export function getStatusBorderColor(severity: number): string {
|
|
||||||
const borderColors: Record<number, string> = {
|
|
||||||
[StatusSeverity.SpecialService]: "border-gray-500",
|
|
||||||
[StatusSeverity.Closed]: "border-red-700",
|
|
||||||
[StatusSeverity.Suspended]: "border-red-600",
|
|
||||||
[StatusSeverity.PartSuspended]: "border-red-500",
|
|
||||||
[StatusSeverity.PlannedClosure]: "border-orange-600",
|
|
||||||
[StatusSeverity.PartClosure]: "border-yellow-500",
|
|
||||||
[StatusSeverity.SevereDelays]: "border-red-500",
|
|
||||||
[StatusSeverity.ReducedService]: "border-orange-500",
|
|
||||||
[StatusSeverity.BusService]: "border-blue-500",
|
|
||||||
[StatusSeverity.MinorDelays]: "border-yellow-500",
|
|
||||||
[StatusSeverity.GoodService]: "border-green-500",
|
|
||||||
[StatusSeverity.PartClosed]: "border-orange-600",
|
|
||||||
[StatusSeverity.ExitOnly]: "border-gray-600",
|
|
||||||
[StatusSeverity.NoStepFreeAccess]: "border-gray-500",
|
|
||||||
[StatusSeverity.ChangeOfFrequency]: "border-blue-400",
|
|
||||||
[StatusSeverity.Diverted]: "border-purple-500",
|
|
||||||
[StatusSeverity.NotRunning]: "border-red-600",
|
|
||||||
[StatusSeverity.IssuesReported]: "border-yellow-400",
|
|
||||||
[StatusSeverity.NoIssues]: "border-green-500",
|
|
||||||
[StatusSeverity.Information]: "border-blue-400",
|
|
||||||
[StatusSeverity.ServiceClosed]: "border-red-700",
|
|
||||||
}
|
|
||||||
return borderColors[severity] || "border-gray-400"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to check if there are any disruptions
|
|
||||||
export function hasDisruptions(data: DisruptionsResponse): boolean {
|
|
||||||
return data.disruptedLines > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to get critical disruptions (severe or worse)
|
|
||||||
export function getCriticalDisruptions(data: DisruptionsResponse): DisruptionSummary[] {
|
|
||||||
return data.disruptions.filter((d) => d.statusSeverity <= 6)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TanStack Query Options
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query options for fetching current TfL disruptions
|
* Query options for fetching current TfL disruptions
|
||||||
* Returns disruptions across Tube, Overground, DLR, Elizabeth Line, and Tram
|
* Returns disruptions across Tube, Overground, DLR, Elizabeth Line, and Tram
|
||||||
|
|||||||
Reference in New Issue
Block a user