@@ -74,8 +81,14 @@ export default function DynamicList({ server_vehicles, track_id, logged_in, trac
{v.heading ? coordinateFormatter.format(v.heading) : "unbekannt"}
- {{}.toString()}
-
+
+
+ {v.trackerIds.map(trackerId => (
+
+ ))}
+
+
+
Link
diff --git a/Website/src/app/components/tracker.tsx b/Website/src/app/components/tracker.tsx
index 8d35c5b5..937e8d28 100644
--- a/Website/src/app/components/tracker.tsx
+++ b/Website/src/app/components/tracker.tsx
@@ -1,11 +1,29 @@
-import {getFetcher, TrackerIdRoute} from "@/utils/fetcher";
+import { getFetcher, TrackerIdRoute } from "@/utils/fetcher";
import useSWR from "swr";
+import { batteryLevelFormatter } from "@/utils/helpers";
-export default function TrackerCharge({trackerId}: {trackerId: string}) {
+export default function TrackerCharge({ trackerId }: { trackerId: string }) {
+ const safeTrackerId = encodeURIComponent(trackerId);
+ const { data: tracker_data } = useSWR(`/webapi/tracker/read/${safeTrackerId}`, getFetcher);
- const safeTrackerId = encodeURIComponent(trackerId);
- const {data: tracker_data} = useSWR(`/webapi/tracker/read/${safeTrackerId}`, getFetcher)
-
- return ({tracker_data && <>{tracker_data.id}: {tracker_data.data ?? "unbekannt"} %>}
)
-
-}
\ No newline at end of file
+ return (
+ <>
+ {tracker_data && (
+
+
+
{tracker_data.id}
+
+ {tracker_data.id}
+
+
+
+ {tracker_data.battery == undefined ? "?" : batteryLevelFormatter.format(tracker_data.battery)}
+
+
+ )}
+ >
+ );
+}
diff --git a/Website/src/app/list/page.tsx b/Website/src/app/list/page.tsx
index fb2d82c7..b0512c73 100644
--- a/Website/src/app/list/page.tsx
+++ b/Website/src/app/list/page.tsx
@@ -25,9 +25,8 @@ export default async function Home() {
return [undefined, [], [], []];
});
- console.log("server vehicles", server_vehicles);
return (
-
+
(f: () => T): T | undefined {
try {
return f();
} catch (e) {
- return
+ return;
}
}
export function apiError(statusCode: number): NextResponse {
- const statusText = http.STATUS_CODES[statusCode]
+ const statusText = http.STATUS_CODES[statusCode];
return new NextResponse(statusText + "\r\n", {
status: statusCode,
@@ -60,5 +60,5 @@ export function getUsername(token: string): string {
if (!isTokenPayload(payload)) {
throw new TypeError("Not a valid jwt auth token");
}
- return payload.username
+ return payload.username;
}
From 616a5fa28c38f18bcbe47c82c88e8a17c590d85e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20M=C3=A4ckelmann?=
<6890706+n1kPLV@users.noreply.github.com>
Date: Mon, 4 Sep 2023 22:53:25 +0200
Subject: [PATCH 4/6] Small dark mode improvements
---
Website/src/app/components/tracker.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Website/src/app/components/tracker.tsx b/Website/src/app/components/tracker.tsx
index 937e8d28..5f343e10 100644
--- a/Website/src/app/components/tracker.tsx
+++ b/Website/src/app/components/tracker.tsx
@@ -14,7 +14,7 @@ export default function TrackerCharge({ trackerId }: { trackerId: string }) {
{tracker_data.id}
{tracker_data.id}
From 890b40783dddad5e1c0479c5f20557b73efa0273 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20M=C3=A4ckelmann?=
<6890706+n1kPLV@users.noreply.github.com>
Date: Wed, 6 Sep 2023 17:15:04 +0200
Subject: [PATCH 5/6] Also provide vehicle speed on request
---
Server/src/models/api.ts | 3 ++-
Server/src/routes/track.route.ts | 4 +++-
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/Server/src/models/api.ts b/Server/src/models/api.ts
index 0c63a109..c4ea3ba0 100644
--- a/Server/src/models/api.ts
+++ b/Server/src/models/api.ts
@@ -102,6 +102,7 @@ export type Vehicle = UpdateVehicle & {
pos?: Position // undefined if position is unknown.
percentagePosition?: number // A position mapped onto percentage 0-100) e.g. 0% Malente; 100% Lütjenburg
heading?: number // between 0 and 360
+ speed?: number // in km/h
}
/**
@@ -129,7 +130,7 @@ export type VehicleType = UpdateVehicleType & {
export type Tracker = {
id: string
vehicleId: number | null
- battery?: number
+ battery?: number // ideally between 0 (empty) and 1 (full). But probably some arbitrary value...
data?: unknown
}
diff --git a/Server/src/routes/track.route.ts b/Server/src/routes/track.route.ts
index d670083e..c638b0f2 100644
--- a/Server/src/routes/track.route.ts
+++ b/Server/src/routes/track.route.ts
@@ -228,6 +228,7 @@ export class TrackRoute {
// This might not make much sense.
const percentagePosition = (await VehicleService.getVehicleTrackDistancePercentage(vehicle)) ?? undefined
const heading = await VehicleService.getVehicleHeading(vehicle)
+ const speed = await VehicleService.getVehicleSpeed(vehicle)
return {
id: vehicle.uid,
track: vehicle.trackId,
@@ -236,7 +237,8 @@ export class TrackRoute {
trackerIds: (await database.trackers.getByVehicleId(vehicle.uid)).map(y => y.uid),
pos,
percentagePosition,
- heading
+ heading,
+ speed
}
})
)
From adeb32bee31c674f7a9c03886ec62f011a190f81 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20M=C3=A4ckelmann?=
<6890706+n1kPLV@users.noreply.github.com>
Date: Wed, 6 Sep 2023 17:18:55 +0200
Subject: [PATCH 6/6] Add display of vehicle speed
---
Website/src/app/components/dynlist.tsx | 29 +++++++++++++----
Website/src/app/components/login_wrap.tsx | 21 ++++++-------
Website/src/app/components/map.tsx | 18 +++++++++--
Website/src/app/list/page.tsx | 38 ++++++++---------------
Website/src/app/map/page.tsx | 3 +-
Website/src/utils/api.ts | 1 +
Website/src/utils/helpers.ts | 9 +++++-
7 files changed, 71 insertions(+), 48 deletions(-)
diff --git a/Website/src/app/components/dynlist.tsx b/Website/src/app/components/dynlist.tsx
index 14713296..f1fd38fc 100644
--- a/Website/src/app/components/dynlist.tsx
+++ b/Website/src/app/components/dynlist.tsx
@@ -1,11 +1,12 @@
"use client";
-import { IMapRefreshConfig, RevalidateError } from "@/utils/types";
-import { Vehicle } from "@/utils/api";
+import { RevalidateError } from "@/utils/types";
+import { FullTrack, Vehicle } from "@/utils/api";
import useSWR from "swr";
import { coordinateFormatter } from "@/utils/helpers";
import Link from "next/link";
import TrackerCharge from "@/app/components/tracker";
+import { Dispatch, FunctionComponent } from "react";
const fetcher = async ([url, track_id]: [url: string, track_id: number]) => {
const res = await fetch(`${url}/${track_id}`, { method: "get" });
@@ -17,14 +18,32 @@ const fetcher = async ([url, track_id]: [url: string, track_id: number]) => {
return res_2;
};
+function FocusVehicleLink(props: { v: Vehicle }) {
+ return Link;
+}
+
/**
* A dynamic list of vehicles with their current position.
* @param server_vehicles A pre-fetched list of vehicles to be used until this data is fetched on the client side.
* @param track_id The id of the currently selected track. # TODO: remove redundant variable -> already in track_data
* @param logged_in A boolean indicating if the user is logged in.
* @param track_data The information about the currently selected track.
+ * @param FocusVehicle Component to focus the specific vehicle.
*/
-export default function DynamicList({ server_vehicles, track_id, logged_in, track_data }: IMapRefreshConfig) {
+export default function DynamicList({
+ server_vehicles,
+ track_id,
+ logged_in,
+ track_data,
+ FocusVehicle = FocusVehicleLink
+}: {
+ server_vehicles: Vehicle[];
+ track_id: number;
+ logged_in: boolean;
+ track_data?: FullTrack;
+ setLogin: Dispatch;
+ FocusVehicle?: FunctionComponent<{ v: Vehicle }>;
+}) {
const { data, error, isLoading } = useSWR(
logged_in && track_id ? ["/webapi/vehicles/list", track_id] : null,
fetcher,
@@ -89,9 +108,7 @@ export default function DynamicList({ server_vehicles, track_id, logged_in, trac
-
- Link
-
+
))}
diff --git a/Website/src/app/components/login_wrap.tsx b/Website/src/app/components/login_wrap.tsx
index 0ee1fa64..318ef70e 100644
--- a/Website/src/app/components/login_wrap.tsx
+++ b/Website/src/app/components/login_wrap.tsx
@@ -1,6 +1,5 @@
"use client";
-import { IMapRefreshConfig } from "@/utils/types";
-import { useState } from "react";
+import { Dispatch, FunctionComponent, useState } from "react";
import { LoginDialog } from "@/app/components/login";
import { SelectionDialog } from "@/app/components/track_selection";
@@ -9,19 +8,19 @@ import { SelectionDialog } from "@/app/components/track_selection";
* @param logged_in initial login state
* @param track_selected track selection state
* @param map_conf parameters for the construction of the child
- * @param child Function contructing the wrapped React Component.
+ * @param Child The wrapped React Component.
*/
-const LoginWrapper = ({
+function LoginWrapper({
logged_in,
track_selected,
- map_conf,
- child
+ childConf,
+ child: Child
}: {
logged_in: boolean;
track_selected: boolean;
- map_conf: IMapRefreshConfig;
- child: (conf: IMapRefreshConfig) => JSX.Element;
-}) => {
+ childConf: T;
+ child: FunctionComponent }>;
+}) {
const [loginState, setLogin] = useState(logged_in);
// console.log('track selected', track_selected, map_conf.track_id)
@@ -39,9 +38,9 @@ const LoginWrapper = ({
)
)}
- {child({ ...map_conf, logged_in: loginState, setLogin: setLogin })}
+
>
);
-};
+}
export default LoginWrapper;
diff --git a/Website/src/app/components/map.tsx b/Website/src/app/components/map.tsx
index 198e258d..09f892eb 100644
--- a/Website/src/app/components/map.tsx
+++ b/Website/src/app/components/map.tsx
@@ -4,7 +4,7 @@ import "leaflet-rotatedmarker";
import "leaflet/dist/leaflet.css";
import { useEffect, useMemo, useRef, useState } from "react";
import { IMapConfig } from "@/utils/types";
-import { coordinateFormatter } from "@/utils/helpers";
+import { coordinateFormatter, speedFormatter } from "@/utils/helpers";
import assert from "assert";
import { createPortal } from "react-dom";
import RotatingVehicleIcon from "@/utils/rotatingIcon";
@@ -169,7 +169,7 @@ function Map({
// We can then use a React portal to place content in there.
const popupElement = document.createElement("div");
popupElement.className = "w-96 flex p-1.5 flex-row flex-wrap";
- m.bindPopup(popupElement, {className: 'w-auto', maxWidth: undefined});
+ m.bindPopup(popupElement, { className: "w-auto", maxWidth: undefined });
setPopupContainer(popupElement);
// unset the focussed element on popup closing.
m.on("popupclose", () => {
@@ -235,7 +235,13 @@ function Map({
Vehicle "{vehicleInFocus?.name}"
Tracker-Ladezustand:
- {vehicleInFocus ? vehicleInFocus.trackerIds.map(trackerId => ) : "unbekannt"}
+
+ {vehicleInFocus
+ ? vehicleInFocus.trackerIds.map(trackerId => (
+
+ ))
+ : "unbekannt"}
+
Position:
{vehicleInFocus?.pos ? (
@@ -247,6 +253,12 @@ function Map({
"unbekannt"
)}
+ Geschwindigkeit:
+
+ {vehicleInFocus?.speed != undefined && vehicleInFocus.speed !== -1
+ ? speedFormatter.format(vehicleInFocus.speed)
+ : "unbekannt"}
+
>
) : (
diff --git a/Website/src/app/list/page.tsx b/Website/src/app/list/page.tsx
index b0512c73..f17b5b38 100644
--- a/Website/src/app/list/page.tsx
+++ b/Website/src/app/list/page.tsx
@@ -1,45 +1,33 @@
import { cookies } from "next/headers";
-import { getTrackData, getAllVehiclesOnTrack, getAllPOIsOnTrack, getAllPOITypes } from "@/utils/data";
+import { getAllVehiclesOnTrack, getTrackData } from "@/utils/data";
import LoginWrapper from "@/app/components/login_wrap";
-import { FullTrack, PointOfInterest, POIType, Vehicle } from "@/utils/api";
+import { FullTrack, Vehicle } from "@/utils/api";
import DynamicList from "@/app/components/dynlist";
export default async function Home() {
const token = cookies().get("token")?.value;
const track_id = parseInt(cookies().get("track_id")?.value ?? "", 10);
const track_selected = !isNaN(track_id);
- const [track_data, server_vehicles, points_of_interest, poi_types]: [
- FullTrack | undefined,
- Vehicle[],
- PointOfInterest[],
- POIType[]
- ] = !(token && track_selected)
- ? [undefined, [] as Vehicle[], [] as PointOfInterest[], [] as POIType[]]
- : await Promise.all([
- getTrackData(token, track_id),
- getAllVehiclesOnTrack(token, track_id),
- getAllPOIsOnTrack(token, track_id),
- getAllPOITypes(token)
- ]).catch(e => {
+ const [track_data, server_vehicles]: [FullTrack | undefined, Vehicle[]] = !(token && track_selected)
+ ? [undefined, [] as Vehicle[]]
+ : await Promise.all([getTrackData(token, track_id), getAllVehiclesOnTrack(token, track_id)]).catch(e => {
console.error("Error fetching Map Data from the Backend:", e);
- return [undefined, [], [], []];
+ return [undefined, []];
});
+ const listConf = {
+ server_vehicles,
+ track_data,
+ track_id
+ };
+
return (
diff --git a/Website/src/app/map/page.tsx b/Website/src/app/map/page.tsx
index 18ae3022..baf877f6 100644
--- a/Website/src/app/map/page.tsx
+++ b/Website/src/app/map/page.tsx
@@ -8,7 +8,6 @@ import { FullTrack, PointOfInterest, POIType, Vehicle } from "@/utils/api";
import { nanToUndefined } from "@/utils/helpers";
export default async function MapPage({ searchParams }: { searchParams: { focus?: string; success?: string } }) {
-
// get the login token and the ID of the selected track
const token = cookies().get("token")?.value;
const track_id = parseInt(cookies().get("track_id")?.value ?? "", 10);
@@ -49,7 +48,7 @@ export default async function MapPage({ searchParams }: { searchParams: { focus?
Promise = time =>
export const batteryLevelFormatter = new Intl.NumberFormat("de-DE", {
notation: "standard",
- style: "unit",
+ style: "percent",
unit: "percent",
maximumFractionDigits: 1
});
@@ -20,6 +20,13 @@ export const coordinateFormatter = new Intl.NumberFormat("de-DE", {
maximumFractionDigits: 4
});
+export const speedFormatter = new Intl.NumberFormat("de-DE", {
+ notation: "standard",
+ style: "unit",
+ unit: "kilometer-per-hour",
+ maximumFractionDigits: 1
+});
+
export function nanToUndefined(x: number): number | undefined {
if (Number.isNaN(x)) return;
return x;