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 ? (
+ <>
+
+
+ >
+ ) : null}
+
+ {/* U-Bahn */}
+ {train.isubahnmuc ? (
+ <>
+
+
+ >
+ ) : null}
+
+ {/* Alle anderen Transportmittel */}
+ {!train.issbahnmuc && !train.isubahnmuc ? (
+ <>
+
+
+
+
{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==