From 9f98917ea2e0df0fe427d19b8dc7da495edbc17f Mon Sep 17 00:00:00 2001 From: Phipsiart <98510944+Phipsiart@users.noreply.github.com> Date: Sat, 19 Oct 2024 21:52:05 +0200 Subject: [PATCH] big big update --- app/api/annoucement/route.js | 56 +++++++++++++++ app/arrivals/[slug]/page.js | 22 ++++++ app/{page.tsx => page.js} | 0 app/search-station/page.js | 14 ++++ components/Header.tsx | 91 ++++++++++++++++++++++--- components/core/TrainCard.js | 102 ++++++++++++++++++++++++++++ lib/GetStationByIBNR.js | 14 ++++ lib/GetTrainDepartures.js | 92 +++++++++++++++++++++++++ lib/filter/ReplaceNames.js | 3 + package.json | 1 + public/transportation-types/BRB.svg | 19 ++++++ tsconfig.json | 3 +- yarn.lock | 35 +++++----- 13 files changed, 421 insertions(+), 31 deletions(-) create mode 100644 app/api/annoucement/route.js create mode 100644 app/arrivals/[slug]/page.js rename app/{page.tsx => page.js} (100%) create mode 100644 app/search-station/page.js create mode 100644 components/core/TrainCard.js create mode 100644 lib/GetStationByIBNR.js create mode 100644 lib/GetTrainDepartures.js create mode 100644 public/transportation-types/BRB.svg diff --git a/app/api/annoucement/route.js b/app/api/annoucement/route.js new file mode 100644 index 0000000..b39bd44 --- /dev/null +++ b/app/api/annoucement/route.js @@ -0,0 +1,56 @@ +// app/api/announcement/route.js +import { createReadStream } from 'fs'; +import fetch from 'node-fetch'; + +export async function GET(req) { + const { searchParams } = new URL(req.url); + const IBNR = searchParams.get('IBNR'); + const language = searchParams.get('language'); + + // Validate input + if (!IBNR || !language) { + return new Response(JSON.stringify({ error: 'Missing IBNR or language' }), { + status: 400, + headers: { 'Content-Type': 'application/json' }, + }); + } + + const audioUrl = `https://${process.env.AUDIO_URL}/${language}/${IBNR}.wav`; + + // Fetch the audio file + const response = await fetch(audioUrl); + + // Check if the audio file exists + if (!response.ok) { + return new Response(JSON.stringify({ error: 'Audio file not found' }), { + status: 404, + headers: { 'Content-Type': 'application/json' }, + }); + } + + // Set headers for the audio response + const headers = new Headers({ + 'Content-Type': 'audio/wav', + 'Content-Disposition': 'inline; filename="audio.wav"', + }); + + // Create a ReadableStream to handle audio streaming with silence + const { readable, writable } = new TransformStream(); + + const writer = writable.getWriter(); + + // Write silence before the audio stream starts + writer.write(new Uint8Array(new Array(44100 * 2 * 0.3).fill(0))); // 0.3 seconds of silence for 16-bit audio (44100 Hz) + + // Pipe the response body to the writer + response.body.pipeTo(writer); + + // Close the writer when the audio stream ends + response.body.on('end', () => { + writer.close(); + }); + + return new Response(readable, { + headers, + }); +} diff --git a/app/arrivals/[slug]/page.js b/app/arrivals/[slug]/page.js new file mode 100644 index 0000000..3b8e723 --- /dev/null +++ b/app/arrivals/[slug]/page.js @@ -0,0 +1,22 @@ +import Header from "../../../components/Header"; +import TrainCard from "../../../components/core/TrainCard" +import GetTrainDepartures from "../../../lib/GetTrainDepartures"; +import GetStationByIBNR from "../../../lib/GetStationByIBNR"; +import RefreshData from "../../../components/core/RefreshData" +export default async function DeparturePage({ params, searchParams }) { + const IBNR = decodeURIComponent(params.slug); // ID aus der URL extrahieren + const showType = searchParams.show || 'departures'; // Standardmäßig auf 'arrivals' setzen + + // Abrufen der Daten + const data = await GetTrainDepartures(IBNR, showType); + const getstation = await GetStationByIBNR(IBNR) + return ( + <> + +
+
+ +
+ + ); +} diff --git a/app/page.tsx b/app/page.js similarity index 100% rename from app/page.tsx rename to app/page.js diff --git a/app/search-station/page.js b/app/search-station/page.js new file mode 100644 index 0000000..2d09ae2 --- /dev/null +++ b/app/search-station/page.js @@ -0,0 +1,14 @@ +import AutoCompleteSearch from "@/components/core/AutoCompleteSearch"; +import Header from "../../components/Header"; +export default function SearchStation(){ + return( + <> +
+
+
+ +
+
+ + ) +} \ No newline at end of file diff --git a/components/Header.tsx b/components/Header.tsx index 56b21f3..f1b46f9 100644 --- a/components/Header.tsx +++ b/components/Header.tsx @@ -1,13 +1,82 @@ +"use client"; + import Link from 'next/link'; -export default function Header() { - return ( - <> -
- - Innenanzeiger - -
-
- - ); +import { usePathname, useSearchParams } from 'next/navigation'; +import { useState, useEffect, Suspense } from 'react'; +import { useRouter } from 'next/navigation'; + +interface HeaderProps { + activeheadline?: string; + disablelinks?: boolean; + showArrivalDepartureSwitch?: boolean; } + +const Header: React.FC = ({ activeheadline, disablelinks, showArrivalDepartureSwitch }) => { + const pathname = usePathname(); // Aktuellen Pfad abrufen + const router = useRouter(); + const searchParams = useSearchParams(); // Suchparameter abrufen + const currentIBNR = pathname.split('/')[2]; // IBNR aus dem Pfad extrahieren + const [showArrivals, setShowArrivals] = useState(searchParams.get('show') === 'arrivals'); // Zustand für Ankünfte + + // Effect, um den Zustand bei Änderung der Suchparameter zu aktualisieren + useEffect(() => { + setShowArrivals(searchParams.get('show') === 'arrivals'); + }, [searchParams]); + + const handleSwitch = () => { + const newShow = showArrivals ? 'departures' : 'arrivals'; + router.push(`/arrivals/${currentIBNR}?show=${newShow}`); + }; + + return ( + <> +
+ + + {activeheadline || 'Innenanzeiger'} + + + {disablelinks ? null : ( + + Departures + + )} + {showArrivalDepartureSwitch ? ( +
+
+
+ +
+
+
+ + Arrivals + +
+
+ + Departures + +
+
+
+
+ ) : null} +
+
+ + ); +}; + +const HeaderWrapper: React.FC = (props) => ( + Loading...}> +
+ +); + +export default HeaderWrapper; diff --git a/components/core/TrainCard.js b/components/core/TrainCard.js new file mode 100644 index 0000000..f803d86 --- /dev/null +++ b/components/core/TrainCard.js @@ -0,0 +1,102 @@ +import Image from "next/image"; + +export default function TrainCard({ data }) { + return ( + <> + {data.map((train, index) => { + // Aktuelle Zeit im Vergleich zur geplanten Abfahrtszeit + const now = new Date(); + + // Angenommene `plannedWhen` im Format HH:mm, z.B. "14:30" + const [hours, minutes] = train.plannedWhen.split(":").map(Number); + const plannedDeparture = new Date(now.getFullYear(), now.getMonth(), now.getDate(), hours, minutes); + + // Wenn der Zug bereits abgefahren ist, nicht anzeigen + if (plannedDeparture < now) { + return null; // Züge, die bereits abgefahren sind, werden nicht gerendert + } + + const minutesToDeparture = plannedDeparture && !isNaN(plannedDeparture) + ? Math.max(0, Math.ceil((plannedDeparture - now) / 60000)) // Zeit in Minuten bis zur Abfahrt + : NaN; // Falls plannedDeparture ungültig ist + + // Dynamische Klassen für den Rahmen + const animatedClass = minutesToDeparture === 0 ? "animate-pulse border-4 border-green-500" : "border-transparent"; + + return ( +
+
+ {/* S-Bahn */} + {train.issbahnmuc ? ( + <> + {`Image +
+
+

{train.destination}

+
+
+ + ) : null} + + {/* U-Bahn */} + {train.isubahnmuc ? ( + <> + {`Image +
+
+

{train.destination}

+
+
+ + ) : null} + + {/* Alle anderen Transportmittel */} + {!train.issbahnmuc && !train.isubahnmuc ? ( + <> + {`Image +
+
+

{train.line.replace('Bus', '').replace('STR', '')}

+

{train.destination}

+
+
+ + ) : null} + + {/* Departure Time & Platform */} +
+
+ {train.plannedWhen} {/* Exact Time (HH:mm) */} +
+
+ {isNaN(minutesToDeparture) ? "N/A" : `in ${minutesToDeparture} min`} {/* Relative Time */} +
+
+
+ {train.platform} +
+
+
+ ); + })} + + ); +} diff --git a/lib/GetStationByIBNR.js b/lib/GetStationByIBNR.js new file mode 100644 index 0000000..01771d8 --- /dev/null +++ b/lib/GetStationByIBNR.js @@ -0,0 +1,14 @@ +import { readStations } from 'db-stations'; + +export default async function getStationByIBNR(ibnr) { + for await (const station of readStations()) { + // Check if the station's ID matches the provided IBNR + if (station.id === ibnr) { + // Return the station name if a match is found + return station.name; + } + } + + // If no station is found with the provided IBNR, return null or a message + return null; // or return 'Station not found'; +} diff --git a/lib/GetTrainDepartures.js b/lib/GetTrainDepartures.js new file mode 100644 index 0000000..78df28b --- /dev/null +++ b/lib/GetTrainDepartures.js @@ -0,0 +1,92 @@ +import ReplaceNames from "./filter/ReplaceNames"; +import FormatDate from "./filter/FormatDate"; + +export default async function GetTrainData(IBNR, type) { + const APIINSTANCE = process.env.API_INSTANCE; + + // Sicherstellen, dass die API-Umgebungsvariable gesetzt ist + if (!APIINSTANCE) { + throw new Error("API_INSTANCE environment variable is not set."); + } + + // Typ festlegen (arrivals oder departures) + const validTypes = ['arrivals', 'departures']; + if (!validTypes.includes(type)) { + throw new Error(`Invalid type: ${type}. Expected one of ${validTypes.join(', ')}`); + } + + // URL basierend auf dem Typ + const url = `https://${APIINSTANCE}/stops/${IBNR}/${type}?results=40&duration=10000`; + + try { + const response = await fetch(url); + + // Überprüfung auf gültige Antwort + if (!response.ok) { + throw new Error(`Error: ${response.status} - ${response.statusText}`); + } + + const apiresult = await response.json(); + + // Debug-Ausgabe, um das Ergebnis zu überprüfen + console.log("API result:", apiresult); + + // Sicherstellen, dass die richtigen Daten vorhanden sind + if (!apiresult || !apiresult[type]) { + throw new Error(`${type} data is missing or undefined.`); + } + + console.log(`${type} data found:`, apiresult[type]); + + // Daten nur verarbeiten, wenn Daten vorhanden sind + if (apiresult[type].length === 0) { + return { error: `No ${type} found.` }; + } + + // Daten verarbeiten + const data = apiresult[type].map((connection) => { + const direction = ReplaceNames(connection.direction); // Richtung ggf. ersetzen + const plannedWhen = FormatDate(connection.plannedWhen); // Datum formatieren + const delay = connection.delay ? connection.delay / 60 : 0; // Verspätung in Minuten umrechnen + const platform = connection.platform || ""; // Plattform oder "N/A" + const line = connection.line ? ReplaceNames(connection.line.name.replace(/ /g,'')) : "Unknown Line"; // Linienname oder unbekannt + const product = connection.line ? connection.line.productName : ''; + let issbahnmuc; + let isubahnmuc; + isubahnmuc = false; + issbahnmuc = false; + if (connection?.line?.operator?.id == "db-regio-ag-s-bahn-munchen"){ + issbahnmuc = true + } + if (connection?.line?.adminCode == "swm001"){ + isubahnmuc = true + } + // Zielname oder Ursprungsort je nach Typ + const destination = type === 'arrivals' + ? (connection.provenance ? ReplaceNames(connection.provenance) : "Unknown Origin") // Ursprungsort + : (connection.destination ? ReplaceNames(connection.destination.name) : "Unknown Destination"); // Zielname + + return { + direction, + plannedWhen, + delay, + platform, + line, + destination, + issbahnmuc, + isubahnmuc, + product, + }; + }); + + // Debug-Ausgabe der verarbeiteten Daten + console.log(`Processed ${type} data:`, data); + + // Verarbeitete Daten zurückgeben + return data; + + } catch (error) { + console.error(`Failed to fetch train ${type}:`, error); + return { error: error.message }; + } +} diff --git a/lib/filter/ReplaceNames.js b/lib/filter/ReplaceNames.js index 08cca43..ddeb6a8 100644 --- a/lib/filter/ReplaceNames.js +++ b/lib/filter/ReplaceNames.js @@ -13,6 +13,7 @@ export default function ReplaceNames(name) { //replace Station Names name = name.replace('Aßling(Oberbay)', 'Aßling'); name = name.replace('Wasserburg(Inn) Bf', 'Wasserburg'); + name = name.replace('Wasserburg(Inn)Bf', 'Wasserburg') name = name.replace('Ebersberg(Oberbay)', 'Ebersberg'); name = name.replace('Gmund(Tegernsee)', 'Gmund'); name = name.replace('München Leuchtenbergring', 'Leuchtenbergring'); @@ -26,6 +27,8 @@ export default function ReplaceNames(name) { name = name.replace('München Hirschgarten', 'Hirschgarten'); name = name.replace('München Harras', 'M. Harras'); name = name.replace('München Siemenswerke', 'M. Siemenswerke'); + name = name.replace('Bahnhof, Grafing b. München', 'Bahnhof, Grafing Stadt') + name = name.replace('Forschungszentrum, Garching b. München','Garching Forschungszentrum') //replace Train prefixes name = name.replace('BRB', ' '); name = name.replace('WFB', ' '); diff --git a/package.json b/package.json index 176b842..388a6bf 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "clsx": "^2.1.0", "date-fns": "^3.6.0", "db-stations": "^5.0.2", + "fluent-ffmpeg": "^2.1.3", "fuse.js": "^7.0.0", "init": "^0.1.2", "lodash.template": "^4.5.0", diff --git a/public/transportation-types/BRB.svg b/public/transportation-types/BRB.svg new file mode 100644 index 0000000..0e2c12b --- /dev/null +++ b/public/transportation-types/BRB.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 9963be3..7358a36 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -35,8 +35,9 @@ "404.js", "app/dashboard/screen/page.js", "app/api/search/route.js", + "app/search-station/page.js", "components/core/AutoCompleteSearch.js", "components/core/AutoCompleteSearch.js" - ], +, "components/Header.tsx", "app/page.js" ], "exclude": ["node_modules"] } diff --git a/yarn.lock b/yarn.lock index a66d156..89f3af0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1527,6 +1527,11 @@ ast-types@^0.16.1: dependencies: tslib "^2.0.1" +async@^0.2.9: + version "0.2.10" + resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1" + integrity sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ== + autoprefixer@^10.4.19: version "10.4.19" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.19.tgz#ad25a856e82ee9d7898c59583c1afeb3fa65f89f" @@ -2505,6 +2510,14 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== +fluent-ffmpeg@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/fluent-ffmpeg/-/fluent-ffmpeg-2.1.3.tgz#d6846be257777844249a4adeb320f25326d239f3" + integrity sha512-Be3narBNt2s6bsaqP6Jzq91heDgOEaDCJAXcE3qcma/EJBSy5FB4cvO31XBInuAuKBx8Kptf8dkhjK0IOru39Q== + dependencies: + async "^0.2.9" + which "^1.1.1" + for-each@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" @@ -4552,16 +4565,7 @@ streamsearch@^1.1.0: resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -4648,14 +4652,7 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -5086,7 +5083,7 @@ which-typed-array@^1.1.13, which-typed-array@^1.1.14, which-typed-array@^1.1.15, gopd "^1.0.1" has-tostringtag "^1.0.2" -which@^1.3.1: +which@^1.1.1, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==