diff --git a/src/App.tsx b/src/App.tsx index 51d9746..cfe54d4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,12 +1,14 @@ import "@/App.css"; -import Timetable from "@/components/Timetable/Timetable"; import Flatastic from "@/components/Flatastic/Flatastic"; +import HomeAssistant from "@/components/HomeAssistant/HomeAssistant"; +import Timetable from "@/components/Timetable/Timetable"; function App() { return ( <> + ); } diff --git a/src/api/homeAssistant.ts b/src/api/homeAssistant.ts new file mode 100644 index 0000000..ded16d7 --- /dev/null +++ b/src/api/homeAssistant.ts @@ -0,0 +1,30 @@ +const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkMjQ1OTg0YjliYzE0OTNjYTdmZDJmNTA3ODgzN2U1YSIsImlhdCI6MTc1MzQwMjAzNiwiZXhwIjoyMDY4NzYyMDM2fQ.fnLSFKPdk8lkAEB-4ekdGUJ1PSCBxcAyasQF1PyrD3k"; + +async function fetchTentHumidity() { + const url = "/api/states/sensor.third_reality_inc_3rths0224z_luftfeuchtigkeit_2"; + const response = await fetch(url, { + method: "GET", + headers: { + "Authorization": `Bearer ${token}`, + } + }); + const data = await response.json(); + + return data.state; +} + +async function fetchTentTemperature() { + const url = "/api/states/sensor.third_reality_inc_3rths0224z_temperatur_2"; + const response = await fetch(url, { + method: "GET", + headers: { + "Authorization": `Bearer ${token}`, + } + }); + + const data = await response.json(); + + return data.state; +} + +export { fetchTentHumidity, fetchTentTemperature }; \ No newline at end of file diff --git a/src/api/kvv.ts b/src/api/kvv.ts index 6e1f901..f67cbc3 100644 --- a/src/api/kvv.ts +++ b/src/api/kvv.ts @@ -1,5 +1,5 @@ async function fetchKvvDepartures(stopId: number) { - const API_URL = `https://projekte.kvv-efa.de/sl3-alone/XSLT_DM_REQUEST?outputFormat=JSON&coordOutputFormat=WGS84[dd.ddddd]&depType=stopEvents&locationServerActive=1&mode=direct&name_dm=${stopId}&type_dm=stop&useOnlyStops=1&useRealtime=1&limit=6&line=kvv:22301:E:H:s25&line=kvv:21012:E:H:s25&line=kvv:21012:E:R:s25&line=kvv:22305:E:H:s25`; + const API_URL = `https://projekte.kvv-efa.de/sl3-alone/XSLT_DM_REQUEST?outputFormat=JSON&coordOutputFormat=WGS84%5Bdd.ddddd%5D&command=&itdLPxx_timeFormat=12&language=de&itdLPxx_useJs=1&std3_suggestMacro=std3_suggest&std3_commonMacro=dm&itdLPxx_contractor=&std3_contractorMacro=&includeCompleteStopSeq=1&useAllStops=1&name_dm=${stopId}&type_dm=any&itdDateTimeDepArr=dep&includedMeans=checkbox&itdLPxx_ptActive=on&useRealtime=1&std3_inclMOT_0Macro=true&std3_inclMOT_1Macro=true&std3_inclMOT_4Macro=true&std3_inclMOT_5Macro=true&imparedOptionsActive=1&nameInfo_dm=7000044&itdLPxx_multiStepDm=2&deleteAssignedStops=1&mode=direct&line=kvv%253A22301%253AE%253AH%253As25&line=kvv%253A22301%253AE%253AR%253As25&line=kvv%253A21012%253AE%253AH%253As25&line=kvv%253A21012%253AE%253AR%253As25&line=kvv%253A22305%253AE%253AH%253As25&line=kvv%253A22305%253AE%253AR%253As25&line=kvv%253A22308%253AE%253AR%253As25&line=kvv%253A22311%253AE%253AH%253As25&line=kvv%253A22351%253AE%253AH%253As25&line=kvv%253A22351%253AE%253AR%253As25&itdLPxx_snippet=1&itdLPxx_template=dmresults&limit=10`; const data = await fetch(API_URL, { method: "GET", diff --git a/src/components/DepartureList/DepartureList.tsx b/src/components/DepartureList/DepartureList.tsx new file mode 100644 index 0000000..c643460 --- /dev/null +++ b/src/components/DepartureList/DepartureList.tsx @@ -0,0 +1,66 @@ +import TimetableRow from "@/components/TimetableRow/TimetableRow"; +import type { DepartureType } from "@/types/departureType"; + +import style from "./style.module.css"; + +function parseTimetableData(data: DepartureType[]) { + const left: DepartureType[] = []; + const right: DepartureType[] = []; + + data.forEach((departure) => { + if ( + departure.servingLine?.liErgRiProj?.direction === "R" && + right.length < 5 + ) { + right.push(departure); + } else if (left.length < 5) { + left.push(departure); + } + + // Limit to 5 departures per side + if (left.length >= 5 && right.length >= 5) { + return; + } + }); + + return { + left, + right, + }; +} + +function departureTables(departures: DepartureType[], name: string) { + return ( +
+

{name} Departures

+ + + {departures.map((departure, index) => ( + // biome-ignore lint/suspicious/noArrayIndexKey: there is no id + + ))} + +
+
+ ); +} + +export default function DepartureList(props: { + departures: DepartureType[]; + name: string; +}) { + const { departures, name } = props; + + if (!departures || departures.length === 0) { + return
No departures available
; + } + + const { left, right } = parseTimetableData(departures); + + return ( +
+ {departureTables(left, name)} + {departureTables(right, name)} +
+ ); +} diff --git a/src/components/DepartureList/style.module.css b/src/components/DepartureList/style.module.css new file mode 100644 index 0000000..7ce8db2 --- /dev/null +++ b/src/components/DepartureList/style.module.css @@ -0,0 +1,5 @@ +.container { + display: flex; + flex-direction: row; + align-items: center; +} diff --git a/src/components/HomeAssistant/HomeAssistant.tsx b/src/components/HomeAssistant/HomeAssistant.tsx new file mode 100644 index 0000000..4d4ac81 --- /dev/null +++ b/src/components/HomeAssistant/HomeAssistant.tsx @@ -0,0 +1,24 @@ +import { useEffect } from "react"; +import { useHomeAssistantStore } from "@/store/homeAssistant"; + +export default function Timetable() { + const fetchHomeAssistantData = useHomeAssistantStore((state) => state.fetch); + const tentTemperature = useHomeAssistantStore((state) => state.tentTemperature); + const tentHumidity = useHomeAssistantStore((state) => state.tentHumidity); + + useEffect(() => { + fetchHomeAssistantData(); + const interval = setInterval(() => { + fetchHomeAssistantData(); + }, 60000); + return () => clearInterval(interval); + }, [fetchHomeAssistantData]); + + return ( +
+

Tent

+

Temperature: {tentTemperature}°C

+

Humidity: {tentHumidity}%

+
+ ); +} diff --git a/src/components/Timetable/Timetable.tsx b/src/components/Timetable/Timetable.tsx index 19c289b..c4d2d11 100644 --- a/src/components/Timetable/Timetable.tsx +++ b/src/components/Timetable/Timetable.tsx @@ -1,18 +1,6 @@ import { useEffect } from "react"; - -import TimetableRow from "@/components/TimetableRow/TimetableRow"; +import DepartureList from "@/components/DepartureList/DepartureList"; import { useKVVStore } from "@/store/kvv"; -import type { DepartureType } from "@/types/departureType"; - -function parseTimetableData(data: DepartureType[]) { - const result = data.map((item) => { - return { - ...item, - }; - }); - - return result; -} export default function Timetable() { const fetchTimetable = useKVVStore((state) => state.fetch); @@ -27,30 +15,11 @@ export default function Timetable() { return () => clearInterval(interval); }, [fetchTimetable]); - const hStreetData = parseTimetableData(hStreet.departureList || []); - const pStreetData = parseTimetableData(pStreet.departureList || []); - return (

Timetable

-

H-Street Departures

- - - {hStreetData.map((departure, index) => ( - // biome-ignore lint/suspicious/noArrayIndexKey: there is no id - - ))} - -
-

P-Street Departures

- - - {pStreetData.map((departure, index) => ( - // biome-ignore lint/suspicious/noArrayIndexKey: there is no id - - ))} - -
+ +
); } diff --git a/src/components/TimetableRow/TimetableRow.tsx b/src/components/TimetableRow/TimetableRow.tsx index ac5efa7..7c7bb15 100644 --- a/src/components/TimetableRow/TimetableRow.tsx +++ b/src/components/TimetableRow/TimetableRow.tsx @@ -14,7 +14,6 @@ export default function TimetableRow({ return ( {dateTimeString} - {departure.servingLine.name} {departure.servingLine.number} ({departure.servingLine.direction}) diff --git a/src/store/homeAssistant.ts b/src/store/homeAssistant.ts new file mode 100644 index 0000000..7ebbf73 --- /dev/null +++ b/src/store/homeAssistant.ts @@ -0,0 +1,28 @@ +import { create } from "zustand"; +import { devtools } from "zustand/middleware"; +import { fetchTentHumidity, fetchTentTemperature } from "@/api/homeAssistant"; + +const useHomeAssistantStore = create( + devtools( + (set) => ({ + tentTemperature: 0, + tentHumidity: 0, + fetch: async () => { + const [temperature, humidity] = await Promise.all([ + fetchTentTemperature(), + fetchTentHumidity(), + ]); + + set({ + tentTemperature: temperature, + tentHumidity: humidity, + }); + }, + }), + { + name: "home-assistant-store", + }, + ), +); + +export { useHomeAssistantStore }; diff --git a/src/types/departureType.ts b/src/types/departureType.ts index 1b29ea9..19687dd 100644 --- a/src/types/departureType.ts +++ b/src/types/departureType.ts @@ -10,6 +10,9 @@ export type DepartureType = { number: string; name: string; direction: string; + liErgRiProj: { + direction: string; + }; }; stopID: number; }; diff --git a/src/types/dwdType.ts b/src/types/dwdType.ts index 31d1231..57b4da4 100644 --- a/src/types/dwdType.ts +++ b/src/types/dwdType.ts @@ -1,13 +1,13 @@ export interface Forecast2 { - stationId: string; - start: number; - timeStep: number; - temperature: number[]; - precipitationTotal: number[]; - sunshine: number[]; - dewPoint2m: number[]; - surfacePressure: number[]; - humidity: number[]; - isDay: boolean[]; - temperatureStd: number[]; + stationId: string; + start: number; + timeStep: number; + temperature: number[]; + precipitationTotal: number[]; + sunshine: number[]; + dewPoint2m: number[]; + surfacePressure: number[]; + humidity: number[]; + isDay: boolean[]; + temperatureStd: number[]; } diff --git a/vite.config.ts b/vite.config.ts index 3d421f9..08dbeec 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,6 +1,6 @@ -import { defineConfig } from "vite"; -import react from "@vitejs/plugin-react-swc"; import path from "node:path"; +import react from "@vitejs/plugin-react-swc"; +import { defineConfig } from "vite"; // https://vite.dev/config/ export default defineConfig({ @@ -16,4 +16,14 @@ export default defineConfig({ "@slices": path.resolve(__dirname, "src/store/slices"), }, }, + server: { + proxy: { + "/api": { + target: "https://home.rivercry.com", + changeOrigin: true, + secure: false, + rewrite: (path) => path.replace(/^\/api/, "/api"), + }, + }, + }, });