diff --git a/src/App.tsx b/src/App.tsx index 652da90..a99ccef 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -36,10 +36,10 @@ function App() { -

News

-

Weather

+

News

+
) diff --git a/src/components/Indicators.tsx b/src/components/Indicators.tsx new file mode 100644 index 0000000..30d6a0c --- /dev/null +++ b/src/components/Indicators.tsx @@ -0,0 +1,93 @@ + +import type {State} from "../state"; +import {filterTrainHour} from "../containers"; +import {mean} from "lodash"; + +import styled from "styled-components"; +import { Clock, AlertTriangle, XCircle } from "lucide-react"; +import {accentColor} from "../styles"; + +export const IndicatorStyled = styled.div` + display: flex; + justify-content: space-around; + align-items: center; + background: #0b0c10; + color: #fff; + padding: 1.5rem 2rem; + border-radius: 16px; + box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35); + font-family: 'Inter', sans-serif; +`; + +const StatBlock = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + flex: 1; + + & svg { + margin-bottom: 0.3em; + color: ${accentColor}; + } + + .value { + font-weight: 600; + line-height: 1.1; + } + + .label { + font-size: 1em; + opacity: 0.7; + margin-top: 0.3em; + } +`; + +const formatTime = (seconds?: number) => { + if (seconds == null || isNaN(seconds)) return "–"; + const mins = Math.floor(seconds / 60); + const secs = Math.round(seconds % 60); + return `${mins}m ${secs}s`; +}; + +const formatPercent = (num?: number) => { + if (num == null || isNaN(num)) return "–"; + return `${(num * 100).toFixed(1)}%`; +}; + +export const Indicator = ({ state }: { state: State }) => { + const inOneHour = state.departures?.filter(filterTrainHour(60)); + const inThreeHour = state.departures?.filter(filterTrainHour(-60 * 3)); + + const averageDelayGeneral = mean(inOneHour?.map(d => parseInt(d.delay))); + const averageDelayedOnly = mean( + inOneHour?.filter(d => parseInt(d.delay) !== 0)?.map(d => parseInt(d.delay)) + ); + + const cancelled = + (inThreeHour?.filter(d => d?.canceled === '1')?.length || 0) / + (inThreeHour?.length || 1); + + return ( + + + +
{formatTime(averageDelayGeneral)}
+
Avg Delay (all)
+
+ + + +
{formatTime(averageDelayedOnly)}
+
Avg Delay (delayed only)
+
+ + + +
{formatPercent(cancelled)}
+
Cancelled
+
+
+ ); +}; + diff --git a/src/components/NewsArticle.tsx b/src/components/NewsArticle.tsx index 1cbca0a..0ce3fbd 100644 --- a/src/components/NewsArticle.tsx +++ b/src/components/NewsArticle.tsx @@ -108,6 +108,7 @@ export const NewsArticle = ({ article }: NewsArticleProps) => { {article.description} +