Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
246 changes: 135 additions & 111 deletions app/components/bottom-bar/bottom-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,19 @@ import {
MinusCircleIcon,
ChevronDoubleUpIcon,
} from "@heroicons/react/24/solid";
import { Form, Link, useSearchParams, useSubmit } from "@remix-run/react";
import {
Form,
Link,
useNavigation,
useSearchParams,
useSubmit,
} from "@remix-run/react";
import Graph from "./graph";
import * as ToastPrimitive from "@radix-ui/react-toast";
import { clsx } from "clsx";
import type { Prisma, Sensor } from "@prisma/client";
import type { DeviceWithSensors } from "types";
import Spinner from "../spinner";

export interface LastMeasurementProps {
createdAt: Date;
Expand All @@ -22,6 +29,7 @@ export interface DeviceAndSelectedSensors {
}

export default function BottomBar(data: DeviceAndSelectedSensors) {
const navigation = useNavigation();
// state variables
const [isOpen, setIsOpen] = useState<Boolean>(true);
const [searchParams] = useSearchParams();
Expand All @@ -34,19 +42,19 @@ export default function BottomBar(data: DeviceAndSelectedSensors) {
const sensorIds = searchParams.getAll("sensor");

// helper function to filter an array of sensors by their ids
function filterSensorsById(
idArray: string[],
objectArray: Sensor[]
): Sensor[] {
const filteredArray: Sensor[] = [];
// function filterSensorsById(
// idArray: string[],
// objectArray: Sensor[]
// ): Sensor[] {
// const filteredArray: Sensor[] = [];

for (const obj of objectArray) {
if (idArray.includes(obj.id)) {
filteredArray.push(obj);
}
}
return filteredArray;
}
// for (const obj of objectArray) {
// if (idArray.includes(obj.id)) {
// filteredArray.push(obj);
// }
// }
// return filteredArray;
// }

// helper function to format a date as a string
const formattedDate = (date: Date) => {
Expand Down Expand Up @@ -92,114 +100,130 @@ export default function BottomBar(data: DeviceAndSelectedSensors) {
</Link>
</div>
</div>
<Form
method="get"
onChange={(e) => {
submit(e.currentTarget);
}}
>
<div className="-ms-overflow-style:none scrollable box-border w-full overflow-x-auto overflow-y-hidden">
<div className="flex justify-center whitespace-nowrap text-center">
{data.device.sensors.map((sensor: Sensor) => {
// dont really know why this is necessary - some kind of TypeScript/i18n bug?
const lastMeasurement =
sensor.lastMeasurement as Prisma.JsonObject;
const value = lastMeasurement.value as string;
return (
<div
key={sensor.id}
onClick={() => {
if (
!sensorIds.includes(sensor.id) &&
searchParams.getAll("sensor").length >= 2
) {
if (toastOpen) {
setToastOpen(false);
setTimeout(() => {
setToastOpen(true);
}, 300);
} else {
setToastOpen(true);
}
}
}}
>
<label htmlFor={sensor.id}>
<input
className="peer hidden"
disabled={
<div className="relative">
{navigation.state === "loading" && (
<div className="absolute inset-0 flex items-center justify-center bg-gray-100 bg-opacity-50">
<Spinner />
</div>
)}
<Form
method="get"
onChange={(e) => {
submit(e.currentTarget);
}}
className={
navigation.state === "loading" ? "pointer-events-none" : ""
}
>
<div
className={
"-ms-overflow-style:none scrollable box-border w-full overflow-x-auto overflow-y-hidden"
}
>
<div className="flex justify-center whitespace-nowrap text-center">
{data.device.sensors.map((sensor: Sensor) => {
// dont really know why this is necessary - some kind of TypeScript/i18n bug?
const lastMeasurement =
sensor.lastMeasurement as Prisma.JsonObject;
const value = lastMeasurement.value as string;
return (
<div
key={sensor.id}
onClick={() => {
if (
!sensorIds.includes(sensor.id) &&
searchParams.getAll("sensor").length >= 2
? true
: false
} // check if there are already two selected and this one is not one of them
type="checkbox"
name="sensor"
id={sensor.id}
value={sensor.id}
defaultChecked={sensorIds.includes(sensor.id)}
/>
<div
className={
"whitespace-nowrap" +
(sensorIds.includes(sensor.id)
? " text-green-100"
: "")
) {
if (toastOpen) {
setToastOpen(false);
setTimeout(() => {
setToastOpen(true);
}, 300);
} else {
setToastOpen(true);
}
}
>
<div className="w-220 min-w-150 relative flex flex-grow-0 flex-col items-center justify-center border-l border-r border-gray-300 py-2 px-6 hover:cursor-pointer hover:bg-gray-100">
<div>
<div className="flex h-8 items-center justify-center">
<div className="text-4xl">
{sensor.lastMeasurement ? (
<>
<b>{value.split(".")[0]}</b>
<span className="pl-[0.1rem] text-[0.35rem]">
<span>
{value.split(".")[1] || "00"}
}}
>
<label htmlFor={sensor.id}>
<input
className="peer hidden"
disabled={
!sensorIds.includes(sensor.id) &&
searchParams.getAll("sensor").length >= 2
? true
: false
} // check if there are already two selected and this one is not one of them
type="checkbox"
name="sensor"
id={sensor.id}
value={sensor.id}
defaultChecked={sensorIds.includes(sensor.id)}
/>
<div
className={
"whitespace-nowrap" +
(sensorIds.includes(sensor.id)
? " text-green-100"
: "")
}
>
<div
className={
"w-220 min-w-150 relative flex flex-grow-0 flex-col items-center justify-center border-l border-r border-gray-300 py-2 px-6 hover:cursor-pointer hover:bg-gray-100"
}
>
<div>
<div className="flex h-8 items-center justify-center">
<div className="text-4xl">
{sensor.lastMeasurement ? (
<>
<b>{value.split(".")[0]}</b>
<span className="pl-[0.1rem] text-[0.35rem]">
<span>
{value.split(".")[1] || "00"}
</span>
</span>
</span>
</>
) : (
<b className="text-xs opacity-80">n/a</b>
</>
) : (
<b className="text-xs opacity-80">n/a</b>
)}
</div>
{sensor.unit && sensor.lastMeasurement && (
<div className="relative left-[-10px] self-start text-sm font-bold">
{sensor.unit}
</div>
)}
</div>
{sensor.unit && sensor.lastMeasurement && (
<div className="relative left-[-10px] self-start text-sm font-bold">
{sensor.unit}
</div>
)}
<div className="text-center">
{sensor.title}
</div>
</div>
<div className="text-center">{sensor.title}</div>
{sensorIds.length >= 2 &&
!sensorIds.includes(sensor.id) ? null : (
<div className="absolute bottom-[5px] right-[5px] flex items-center justify-center p-1 text-2xl">
{sensorIds.includes(sensor.id) ? (
<div className="h-3 w-3 cursor-pointer rounded leading-3">
-
</div>
) : (
<div className="h-3 w-3 cursor-pointer rounded leading-3">
+
</div>
)}
</div>
)}
</div>
{sensorIds.length >= 2 &&
!sensorIds.includes(sensor.id) ? null : (
<div className="absolute bottom-[5px] right-[5px] flex items-center justify-center p-1 text-2xl hover:bg-green-300">
{sensorIds.includes(sensor.id) ? (
<div className="h-3 w-3 cursor-pointer rounded leading-3">
-
</div>
) : (
<div className="h-3 w-3 cursor-pointer rounded leading-3">
+
</div>
)}
</div>
)}
</div>
</div>
</label>
</div>
);
})}
</label>
</div>
);
})}
</div>
</div>
</div>
</Form>
{sensorIds.length > 0 ? (
<Graph
sensors={filterSensorsById(sensorIds, data.device.sensors)}
/>
) : null}
</Form>
{sensorIds.length > 0 ? <Graph /> : null}
</div>
</div>
<div
onClick={() => {
Expand Down
7 changes: 2 additions & 5 deletions app/components/bottom-bar/graph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ ChartJS.register(
//Legend
);

export default function Graph(data: any) {
export default function Graph() {
// access env variable on client side
const loaderData = useLoaderData<typeof loader>();

Expand Down Expand Up @@ -68,7 +68,7 @@ export default function Graph(data: any) {
],
};

const options: ChartOptions = {
const options: ChartOptions<"line"> = {
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to resolve TS error on <Line/> component

maintainAspectRatio: false,
interaction: {
mode: "index",
Expand Down Expand Up @@ -118,7 +118,6 @@ export default function Graph(data: any) {
type: "linear",
display: "auto",
position: "right",

// grid line settings
grid: {
drawOnChartArea: false, // only want the grid lines for one axis to show up
Expand All @@ -131,8 +130,6 @@ export default function Graph(data: any) {
<div className="text-gray-100 shadow-inner">
{loaderData.selectedSensors.length > 0 ? (
<div className="flex h-full w-full justify-center px-10">
{/*
// @ts-ignore */}
<Line data={lineData} options={options}></Line>
</div>
) : null}
Expand Down
44 changes: 44 additions & 0 deletions app/components/spinner/index.tsx
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • loading spinner out of new osem logo
  • tried to animate one circle clockwise and the other ne counter-clockwise - did not work yet.

Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
export default function Spinner() {
return (
<>
<div className="z-50 h-8 w-8">
<svg
width="100%"
height="100%"
viewBox="0 0 372 372"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
xmlSpace="preserve"
// xmlns:serif="http://www.serif.com/"
style={{
fillRule: "evenodd",
clipRule: "evenodd",
strokeLinejoin: "round",
strokeMiterlimit: 2,
}}
className="animate-spin"
>
<g transform="matrix(1,0,0,1,-69.074,-42.791)">
<path
d="M363.259,278.374C360.059,278.374 357.455,280.978 357.455,284.174C357.455,287.374 360.059,289.978 363.259,289.978C366.459,289.978 369.063,287.374 369.063,284.174C369.063,280.978 366.459,278.374 363.259,278.374M363.259,314.67C346.447,314.67 332.763,300.99 332.763,284.174C332.763,267.357 346.447,253.682 363.259,253.682C380.076,253.682 393.751,267.357 393.751,284.174C393.751,300.99 380.076,314.67 363.259,314.67"
style={{ fill: "rgb(7,120,188)", fillRule: "nonzero" }}
/>
<path
d="M101.732,139.974C97.34,139.974 93.769,143.545 93.769,147.937C93.769,152.324 97.34,155.895 101.732,155.895C106.119,155.895 109.69,152.324 109.69,147.937C109.69,143.545 106.119,139.974 101.732,139.974M101.732,180.587C83.728,180.587 69.082,165.937 69.082,147.937C69.082,129.933 83.728,115.287 101.732,115.287C119.736,115.287 134.378,129.933 134.378,147.937C134.378,165.937 119.736,180.587 101.732,180.587"
style={{ fill: "rgb(78,175,71)", fillRule: "nonzero" }}
/>
<path
d="M255.016,362.647C177.828,362.647 115.649,297.143 121.157,218.805C125.807,152.701 179.349,99.168 245.453,94.559C323.753,89.105 389.216,151.276 389.216,228.455C389.216,234.513 388.795,240.613 387.97,246.588C387.382,251.043 386.549,255.526 385.495,259.859L385.386,260.359C384.766,263.018 383.995,265.797 383.016,268.847C382.753,269.693 382.457,270.593 382.136,271.53L358.791,263.497C359.032,262.793 359.253,262.113 359.474,261.409C360.049,259.605 360.57,257.859 361.011,256.101L361.011,256.063L361.378,254.551C362.282,250.905 362.999,247.113 363.503,243.284C364.182,238.355 364.524,233.388 364.524,228.455C364.524,163.938 308.474,112.272 242.524,119.613C192.291,125.205 151.845,165.643 146.216,215.872C138.816,281.859 190.486,337.959 255.016,337.959C279.391,337.959 302.724,329.955 321.786,315.234C326.736,311.413 333.82,312.122 338.111,316.672C343.186,322.047 342.366,330.572 336.499,335.072C313.195,352.943 284.736,362.647 255.016,362.647"
style={{ fill: "rgb(7,120,188)", fillRule: "nonzero" }}
/>
<path
d="M268.991,413.575C160.207,421.741 69.074,335.541 69.074,228.454C69.074,213.895 70.766,199.441 74.099,185.491C75.045,181.408 76.224,177.1 77.586,172.783C78.411,170.05 79.391,167.191 80.449,164.333L81.441,161.754L104.453,170.704L103.532,173.104C102.711,175.312 101.899,177.679 101.178,180.066C99.961,183.92 98.953,187.595 98.132,191.141C95.228,203.308 93.761,215.829 93.761,228.454C93.761,321.016 172.303,395.591 266.249,389.016C345.541,383.47 409.77,319.233 415.303,239.941C421.857,146.008 347.291,67.479 254.732,67.479C213.22,67.479 173.27,83.629 143.428,112.141C138.711,116.645 131.37,116.841 126.536,112.462C121.282,107.708 121.007,99.433 126.12,94.529C160.566,61.504 206.741,42.791 254.732,42.791C361.82,42.791 448.024,133.929 439.857,242.712C433.045,333.479 359.761,406.762 268.991,413.575"
style={{ fill: "rgb(78,175,71)", fillRule: "nonzero" }}
/>
</g>
</svg>
</div>
</>
);
}