Skip to content

Commit

Permalink
Added smart-ish links to the header
Browse files Browse the repository at this point in the history
  • Loading branch information
n1kPLV committed Sep 19, 2023
1 parent 33c4d42 commit 128ee97
Show file tree
Hide file tree
Showing 12 changed files with 141 additions and 38 deletions.
14 changes: 9 additions & 5 deletions Website/src/app/components/dynlist.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import TrackerCharge from "@/app/components/tracker";
import { FunctionComponent } from "react";
import { getFetcher } from "@/utils/fetcher";
import { useRouter } from "next/navigation";
import { SelectTrackButton } from "@/app/components/selectTrackButton";

/**
* A component to focus a vehicle. A link to the map view with the respective search parameter
Expand Down Expand Up @@ -77,7 +78,7 @@ export function VehicleList({
</td>
)}
<td className={"px-2 text-center"}>
<div className={"max-w-[16rem] mx-auto"}>
<div className={"max-w-[16rem] w-fit mx-auto"}>
{v.trackerIds.map(trackerId => (
<TrackerCharge key={trackerId} trackerId={trackerId} />
))}
Expand Down Expand Up @@ -127,7 +128,7 @@ export default function DynamicList({
const sorted_vehicles = vehicles?.sort((a, b) => a.id - b.id);

// obtain the NextJS router
const router = useRouter()
const router = useRouter();

if (logged_in && error) {
if (error instanceof UnauthorizedError || (error instanceof RevalidateError && error.statusCode === 401)) {
Expand All @@ -139,9 +140,12 @@ export default function DynamicList({

return (
<>
<h2>
Fahrzeuge der Strecke {track_data?.start} - {track_data?.end}
</h2>
<div className={"flex flex-wrap justify-between items-center gap-2 mb-2"}>
<h2 className={"text-xl text-left"}>
Fahrzeuge der Strecke {track_data?.start} - {track_data?.end}
</h2>
<SelectTrackButton />
</div>
<VehicleList sorted_vehicles={sorted_vehicles} FocusVehicle={FocusVehicle} />
</>
);
Expand Down
5 changes: 5 additions & 0 deletions Website/src/app/components/dynmap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import useSWR from "swr";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { getFetcher } from "@/utils/fetcher";
import { SelectTrackButton } from "@/app/components/selectTrackButton";

// This complicated thing with `dynamic` is necessary to disable server side rendering
// for the actual map, which does not work with leaflet.
Expand Down Expand Up @@ -76,6 +77,10 @@ export default function DynamicMap({
setFocus
}}
/>
{/* This will stack over the map, if all map layers have a z-index < 1100 (which should be the default) */}
<div className={"absolute left-5 bottom-5 z-1100"}>
<SelectTrackButton />
</div>
</div>
);
}
2 changes: 1 addition & 1 deletion Website/src/app/components/dynmap_with_list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export default function DynamicMapList({
}}
/>
</div>
<div className={"basis-30 flex flex-col gap-2 mr-2"}>
<div className={"basis-32 flex grow-0 flex-col gap-2 mr-2"}>
<div className={"grow overflow-y-auto basis-0"}>
<VehicleList
sorted_vehicles={sorted_vehicles}
Expand Down
16 changes: 7 additions & 9 deletions Website/src/app/components/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,13 @@ import React from "react";
* children of this element.
* @constructor
*/
export function FormWrapper({children}: { children: React.ReactNode }) {
return (
<main className="mx-auto max-w-2xl w-full grow">
<div className={'bg-white dark:bg-slate-800 dark:text-white p-4 rounded'}>
{children}
</div>
</main>
)
export function FormWrapper({ children }: { children: React.ReactNode }) {
return (
<main className="mx-auto mt-2 max-w-2xl w-full grow">
<div className={"bg-white dark:bg-slate-800 dark:text-white p-4 rounded"}>{children}</div>
</main>
);
}

// TODO: create a component for a form in a dialog to replace/refactor
// the LoginDialog and SelectionDialog components.
// the LoginDialog and SelectionDialog components.
17 changes: 11 additions & 6 deletions Website/src/app/components/layout/header.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Link from "next/link";
import { CurrentUser } from "@/app/components/layout/currentUser";
import SmartNavigation from "@/app/components/layout/smart_navigation";

/**
* The header for the web page
Expand All @@ -8,18 +9,22 @@ export default function Header() {
return (
<header
className={
"flex flex-row w-full justify-items-center justify-around p-2 flex-wrap bg-white dark:bg-slate-800"
"flex flex-row w-full items-center justify-around p-2 flex-wrap bg-white dark:bg-slate-900 gap-2"
}>
<div className={"mr-auto"}>
<Link href={"/"}>RailTrail Verwaltung</Link>
</div>
<CurrentUser className={"ml-auto sm:order-last"} />
<CurrentUser className={"ml-auto md:order-last"} />
{/* Force a line break for small devices */}
<div className={"w-full sm:hidden"} />
<div className={"w-full md:hidden"} />

<div>Foo</div>
<div>Bar</div>
<div>Baz</div>
<SmartNavigation href={"/map"}>Karte</SmartNavigation>
<SmartNavigation href={"/list"}>Liste</SmartNavigation>
<SmartNavigation href={"/mapList"} className={"px-2 border-2 border-transparent hidden sm:block"}>
Karte + Liste
</SmartNavigation>

<SmartNavigation href={"/management"}>Datenverwaltung</SmartNavigation>
</header>
);
}
39 changes: 39 additions & 0 deletions Website/src/app/components/layout/smart_navigation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"use client";

import Link, { LinkProps } from "next/link";
import { usePathname } from "next/navigation";
import { PropsWithChildren } from "react";

/**
* A navigation link that has a different style when the user is on the page it links to, or on a sub-page
* @param href The URL to navigate to
* @param className CSS classes for the enclosing div
* @param activeClassName Additional CSS classes for the enclosing div, when active
* @param linkClassName The CSS classes for the anchor tag
* @param children The contents in the anchor tag
* @param props Other options applicable to <Link>
* @constructor
*/
export default function SmartNavigation({
href,
className = "px-2 border-2 border-transparent",
linkClassName,
children,
...props
}: PropsWithChildren<LinkProps & { href: string; className?: string; linkClassName?: string }>) {
// get the path of the currently open page
const currentPath = usePathname();

// and determine if we are currently on that path
const active = (currentPath === href || currentPath?.startsWith(href + "/")) ?? false;

const activeClassName = "bg-neutral-500/20 !border-gray-500 rounded";

return (
<div className={className + (active ? " " + activeClassName : "")}>
<Link href={href} className={linkClassName} {...props}>
{children}
</Link>
</div>
);
}
14 changes: 14 additions & 0 deletions Website/src/app/components/selectTrackButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Link from "next/link";

/**
* A link that somewhat resembles a button to select a different track.
*/
export function SelectTrackButton() {
return (
<Link
href={"/select_track"}
className={"bg-gray-200 dark:bg-slate-600 border-2 border-gray-500 rounded px-2 py-1 no-a-style"}>
Andere Strecke wählen
</Link>
);
}
32 changes: 24 additions & 8 deletions Website/src/app/components/track_selection.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
"use client";

import { Dispatch, FormEventHandler, PropsWithChildren, useEffect, useRef, useState } from "react";

import Footer from "@/app/components/layout/footer";
import useSWR from "swr";
import { setCookie } from "cookies-next";
import { getCookie, setCookie } from "cookies-next";
import { inter } from "@/utils/common";
import { getFetcher } from "@/utils/fetcher";
import { useRouter } from "next/navigation";
Expand All @@ -24,6 +22,7 @@ export default function Selection({
const { data, error, isLoading } = useSWR("/webapi/tracks/list", getFetcher<"/webapi/tracks/list">);
// get the next page router
const router = useRouter();
const selectedTrack = getCookie("track_id")?.toString();

const selectTrack: FormEventHandler = e => {
e.preventDefault();
Expand All @@ -41,7 +40,7 @@ export default function Selection({
};

return (
<form onSubmit={selectTrack} className="grid grid-cols-2 gap-y-1 my-1.5 items-center h-24">
<form onSubmit={selectTrack} className="grid grid-cols-[1fr, 7fr] gap-y-1 my-1.5 items-center h-24">
{isLoading ? (
<div className={"flex col-span-2 justify-center items-center gap-5"}>
<Spinner className={"h-10 w-auto"} />
Expand All @@ -52,14 +51,18 @@ export default function Selection({
) : completed ? (
<div className={"flex col-span-2 justify-center items-center gap-5"}>
<Spinner className={"h-10 w-auto"} />
<div>Wird gepeichert...</div>
<div>Wird gespeichert...</div>
</div>
) : (
<>
<label className={""} htmlFor="track">
Strecke:{" "}
</label>
<select id={"track"} name={"track"} className="dark:bg-slate-700 rounded">
<select
defaultValue={selectedTrack}
id={"track"}
name={"track"}
className="dark:bg-slate-700 rounded">
{data?.map(({ id, start, end }) => (
<option
value={id}
Expand All @@ -80,10 +83,12 @@ export default function Selection({

/**
* The track selection form wrapped in a dialog, for easy display in a modal way.
* @param children HTML elements to display over the login form in the dialog, for example for explanations.
* @param children HTML elements to display over the login form in the dialog, for example for explanations.
* @param modal Whether this is shown as part of a modal route.
*/
export function SelectionDialog({ children }: PropsWithChildren) {
export function SelectionDialog({ children, modal = false }: PropsWithChildren<{ modal?: boolean }>) {
const dialogRef = useRef(null as HTMLDialogElement | null);
const router = useRouter();

// get a "completed" state
const [completed, setCompleted] = useState(false);
Expand All @@ -94,10 +99,21 @@ export function SelectionDialog({ children }: PropsWithChildren) {
}
}, []);

// if this is a modal, we need to move back to the previous page using the router
useEffect(() => {
if (completed && modal) {
router.back();
}
}, [completed, modal, router]);

return (
<dialog
ref={dialogRef}
onCancel={event => {
if (modal) {
// if this is a modal, we need to move back to the previous page using the router
router.back();
}
event.preventDefault();
}}
className="drop-shadow-xl shadow-black bg-white p-4 rounded max-w-2xl w-full dark:bg-slate-800 dark:text-white backdrop:bg-gray-200/30 backdrop:backdrop-blur">
Expand Down
8 changes: 4 additions & 4 deletions Website/src/app/components/tracker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@ export default function TrackerCharge({ trackerId }: { trackerId: string }) {
return (
<>
{tracker_data && (
<div className={"w-full flex flex-nowrap my-1 gap-1"}>
<div className={"group relative sm:grow shrink min-w-0 basis-32 text-left"}>
<div className={"truncate w-32 sm:w-full max-w-full min-w-0"}>{tracker_data.id}</div>
<div className={"flex flex-nowrap my-1 gap-1 min-w-0 w-32 sm:w-44 md:w-52 xl:w-64"}>
<div className={"group relative grow-0 md:grow shrink min-w-0 basis-30 lg:basis-32 text-left"}>
<div className={"truncate basis-32 sm:w-full max-w-full min-w-0"}>{tracker_data.id}</div>
<div
className={
"opacity-0 group-hover:opacity-100 z-10 transition-opacity pointer-events-none absolute dark:bg-gray-900 dark:text-white bg-gray-100 rounded py-2 px-3 top-8 -left-3 w-max"
}>
{tracker_data.id}
</div>
</div>
<div className={"basis-10 text-right shrink-0"}>
<div className={"basis-14 text-right shrink-0"}>
{tracker_data.battery == undefined ? "?" : batteryLevelFormatter.format(tracker_data.battery)}
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion Website/src/app/list/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default async function Home() {
};

return (
<main className="mx-auto w-full max-w-4xl grow">
<main className="mx-auto w-full max-w-4xl grow mt-2">
<div className={"bg-white dark:bg-slate-800 dark:text-white p-4 rounded"}>
<LoginWrapper
logged_in={token !== undefined}
Expand Down
20 changes: 19 additions & 1 deletion Website/src/app/management/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,29 @@
import { FormWrapper } from "@/app/components/form";
import React from "react";
import SmartNavigation from "@/app/components/layout/smart_navigation";

/**
* The
* @param children
* @constructor
*/
export default function Layout({ children }: { children: React.ReactNode }) {
return <FormWrapper>{children}</FormWrapper>;
return (
<>
<header
className={
"flex flex-row w-full justify-items-center justify-around p-2 flex-wrap bg-gray-100 dark:bg-slate-800 gap-2"
}>
<SmartNavigation href={"/management/add_track"}>Strecke hinzufügen</SmartNavigation>
<SmartNavigation href={"/management/vehicles"}>Fahrzeuge</SmartNavigation>
<SmartNavigation href={"/management/trackers"}>Tracker</SmartNavigation>
<SmartNavigation href={"/management/poi"}>Interessenspunkte</SmartNavigation>
<SmartNavigation href={"/management/vehicleTypes"}>Fahrzeugarten</SmartNavigation>
<SmartNavigation href={"/management/poiTypes"}>Interessenspunktarten</SmartNavigation>
<SmartNavigation href={"/management/users"}>andere Nutzer</SmartNavigation>
<SmartNavigation href={"/management/myself"}>eigener Nutzer</SmartNavigation>
</header>
<FormWrapper>{children}</FormWrapper>
</>
);
}
10 changes: 7 additions & 3 deletions Website/src/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,22 @@ import { decodeJwt, JWTPayload } from "jose";
import { NextResponse } from "next/server";
import { isTokenPayload } from "@/utils/api";

export const batteryLevelFormatter = new Intl.NumberFormat("de-DE", {
export const batteryLevelFormatter = {
format: (voltage: number) => `${voltage.toFixed(2)}\xA0V`
};

/* new Intl.NumberFormat("de-DE", {
notation: "standard",
style: "percent",
unit: "percent",
maximumFractionDigits: 1
});
}); */

export const coordinateFormatter = new Intl.NumberFormat("de-DE", {
notation: "standard",
style: "unit",
unit: "degree",
maximumFractionDigits: 4
maximumFractionDigits: 3
});

export const speedFormatter = new Intl.NumberFormat("de-DE", {
Expand Down

0 comments on commit 128ee97

Please sign in to comment.