Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add location for measurements #377

Merged
merged 38 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
2910ef0
upgrade drizzle and drizzle-kit
mpfeil Oct 9, 2024
91165c8
add location to measurements for mobile devices
mpfeil Oct 9, 2024
43f79d8
drop last migration
mpfeil Oct 11, 2024
7d1b7ab
add locations for mobile devices
mpfeil Oct 15, 2024
79ec1c3
workaround to handle postgis geometry in query
mpfeil Oct 15, 2024
99a2bad
Merge branch 'dev' into feat/measurement-geometry
mpfeil Oct 15, 2024
55b0b05
remove migrations
freds-dev Oct 29, 2024
e8f86e1
Merge branch 'dev' into feat/measurement-geometry
freds-dev Oct 29, 2024
8917fc7
add locations
freds-dev Oct 29, 2024
761fce9
disable esLint for now
freds-dev Oct 30, 2024
3b3ec0d
Merge branch 'dev' into feat/measurement-geometry
freds-dev Nov 8, 2024
a74539d
get rid of `button can not be a descendant of button` error
freds-dev Nov 18, 2024
e84c332
make tags optional
freds-dev Nov 18, 2024
3e7ad8d
mobile box overview
freds-dev Nov 20, 2024
88c7b1a
add dynamic popup to mobile trajectories
freds-dev Nov 20, 2024
91ed5c2
enhance mobile boxes
freds-dev Nov 20, 2024
a3d4b92
Merge branch 'dev' into feat/measurement-geometry
freds-dev Nov 20, 2024
d8dd0b5
add build target
freds-dev Nov 20, 2024
d9bdd03
fix `top level await` error
freds-dev Nov 21, 2024
63661e5
add trips switch
freds-dev Nov 21, 2024
20b1d99
remove colors when trips are deactivated
freds-dev Nov 21, 2024
dee7bb6
allow to select two sensors on mobile device
freds-dev Nov 21, 2024
07d1f52
enable switching overlaying mobile sensor
freds-dev Nov 22, 2024
d7db39e
add sensor unit to dependencies in MobileBoxLayer effect
freds-dev Nov 22, 2024
731a5b4
Merge branch 'dev' into feat/measurement-geometry
freds-dev Nov 22, 2024
d0dbba7
refactor(routes): update route configuration and dependencies
freds-dev Nov 22, 2024
59d9004
refactor(donut-chart-cluster): simplify destructuring and formatting …
freds-dev Nov 22, 2024
9e85703
refactor(device): comment out time column in getDevice function
freds-dev Nov 22, 2024
86905f3
refactor(graph): rename LineWithZoom to GraphWithZoom and update zoom…
freds-dev Nov 25, 2024
588208a
refactor(mobile): update MobileBoxView structure and clean up MobileO…
freds-dev Nov 25, 2024
6daa439
refactor(device): add time column to extras in getDevice function
freds-dev Nov 25, 2024
74058b8
refactor(package): remove chartjs-scale-timestack dependency from pac…
freds-dev Nov 25, 2024
0e8ea93
update package-lock.json
freds-dev Nov 26, 2024
ae7ddaa
Merge branch 'dev' into feat/measurement-geometry
freds-dev Nov 26, 2024
a6454c5
refactor(color): update color palette and introduce dynamic color cal…
freds-dev Nov 26, 2024
78b396c
refactor(device-detail): improve image handling and update log entry …
freds-dev Nov 26, 2024
8b1e018
Merge branch 'dev' into feat/measurement-geometry
mpfeil Nov 26, 2024
86d64de
delete relation to location on device deletion
mpfeil Nov 28, 2024
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
5 changes: 2 additions & 3 deletions app/components/aggregation-filter.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Filter } from "lucide-react";
import { Button } from "./ui/button";
import { Separator } from "./ui/separator";
import { Badge } from "./ui/badge";
import { useSearchParams, useSubmit } from "@remix-run/react";
Expand Down Expand Up @@ -61,7 +60,7 @@ export function AggregationFilter() {
"flex h-10 w-full items-center justify-between bg-transparent px-3 py-2 text-sm placeholder:text-slate-500 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-800 dark:placeholder:text-slate-400"
}
>
<Button variant="outline" size="sm" className="h-8">
<div className="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 dark:ring-offset-slate-950 dark:focus-visible:ring-slate-300 h-8 border border-slate-200 bg-white hover:bg-slate-100 hover:text-slate-900 dark:border-slate-800 dark:bg-slate-950 dark:hover:bg-slate-800 dark:hover:text-slate-50 ">
<Filter className="mr-2 h-4 w-4" />
Aggregation
<>
Expand All @@ -70,7 +69,7 @@ export function AggregationFilter() {
{selectedAggregation?.label}
</Badge>
</>
</Button>
</div>
</SelectPrimitive.Trigger>
<SelectContent>
{aggregations.map((aggregation) => (
Expand Down
31 changes: 14 additions & 17 deletions app/components/device-detail/device-detail-box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
CalendarPlus,
Hash,
LandPlot,
Image as ImageIcon,
} from "lucide-react";
import { Fragment, useEffect, useRef, useState } from "react";
import type { DraggableData } from "react-draggable";
Expand All @@ -53,7 +54,7 @@ import { getArchiveLink } from "~/utils/device";
import { useBetween } from "use-between";
import { Alert, AlertDescription, AlertTitle } from "../ui/alert";
import { isTablet, isBrowser } from "react-device-detect";
import type { Device, Sensor, SensorWithMeasurement } from "~/schema";
import type { SensorWithMeasurement } from "~/schema";
import { format, formatDistanceToNow } from "date-fns";
import {
Card,
Expand Down Expand Up @@ -85,11 +86,6 @@ export interface MeasurementProps {
max_value: string;
}

export interface DeviceAndSelectedSensors {
device: Device;
selectedSensors: Sensor[];
}

const useCompareMode = () => {
const [compareMode, setCompareMode] = useState(false);
return { compareMode, setCompareMode };
Expand Down Expand Up @@ -204,11 +200,6 @@ export default function DeviceDetailBox() {
return () => clearInterval(interval);
}, [refreshOn, refreshSecond]);

const getDeviceImage = (imageUri: string) =>
imageUri !== null
? `https://opensensemap.org/userimages/${imageUri}`
: "https://images.placeholders.dev/?width=400&height=350&text=No%20image&bgColor=%234fae48&textColor=%23727373";

return (
<>
{open && (
Expand Down Expand Up @@ -331,11 +322,17 @@ export default function DeviceDetailBox() {
<div className="no-scrollbar relative flex-1 overflow-y-scroll">
<div className="space-y-4 sm:space-y-0 sm:flex sm:space-x-4">
<div className="md:w-1/2">
<img
className="w-full object-cover rounded-lg"
alt="device_image"
src={getDeviceImage(data.device.image)}
></img>
{data.device.image ? (
<img
className="w-full object-cover rounded-lg"
alt="device_image"
src={data.device.image}
></img>
) : (
<div className="w-full object-cover rounded-lg text-muted-foreground">
<ImageIcon strokeWidth={1} className="w-full h-full" />
</div>
)}
</div>
<div className="sm:w-1/2 space-y-2">
<InfoItem
Expand All @@ -362,7 +359,7 @@ export default function DeviceDetailBox() {
/>
</div>
</div>
{data.device.tags.length > 0 && (
{data.device.tags?.length > 0 && (
<div className="pt-4">
<div className="space-y-2">
<div className="text-sm font-medium text-muted-foreground">
Expand Down
4 changes: 2 additions & 2 deletions app/components/device-detail/entry-logs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ export default function EntryLogs({
<p className="font-bold pb-4">Logs</p>
<div className="flex items-center">
<div className="flex items-start space-x-4 w-full">
<div className="shrink-0 w-10 h-10 rounded-full bg-primary flex items-center justify-center">
<Activity className="text-primary-foreground w-5 h-5" />
<div className="shrink-0 w-10 h-10 rounded-full border-4 border-muted-foreground text-muted-foreground flex items-center justify-center">
<Activity className="w-5 h-5" />
</div>
<div className="flex-grow">
<p className="text-sm font-medium mb-2">{entryLogs[0].content}</p>
Expand Down
121 changes: 84 additions & 37 deletions app/components/device-detail/graph.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import {
useNavigate,
useNavigation,
useSearchParams,
} from "@remix-run/react";
import { useNavigate, useNavigation, useSearchParams } from "@remix-run/react";
import {
Chart as ChartJS,
LineElement,
Expand All @@ -18,7 +14,7 @@ import "chartjs-adapter-date-fns";
import { Line } from "react-chartjs-2";
import type { ChartOptions } from "chart.js";
// import { de, enGB } from "date-fns/locale";
import { useMemo, useRef, useState, useEffect } from "react";
import { useMemo, useRef, useState, useEffect, useContext } from "react";
import { Download, RefreshCcw, X } from "lucide-react";
import type { DraggableData } from "react-draggable";
import Draggable from "react-draggable";
Expand All @@ -42,6 +38,7 @@ import {
TooltipProvider,
TooltipTrigger,
} from "../ui/tooltip";
import { HoveredPointContext } from "../map/layers/mobile/mobile-box-layer";

ChartJS.register(
LineElement,
Expand All @@ -55,7 +52,7 @@ ChartJS.register(
);

// ClientOnly component to handle the plugin that needs window
const LineWithZoom = (props: any) => {
const GraphWithZoom = (props: any) => {
useMemo(() => {
// Dynamically import the zoom plugin
import("chartjs-plugin-zoom").then(({ default: zoomPlugin }) => {
Expand Down Expand Up @@ -85,11 +82,15 @@ export default function Graph({
startDate,
endDate,
}: GraphProps) {
const { setHoveredPoint } = useContext(HoveredPointContext);
const navigation = useNavigation();
const navigate = useNavigate();
const [offsetPositionX, setOffsetPositionX] = useState(0);
const [offsetPositionY, setOffsetPositionY] = useState(0);
const [isZoomed, setIsZoomed] = useState(false); // State to track zoom
const [currentZoom, setCurrentZoom] = useState<{
xMin: number;
xMax: number;
} | null>(null); // To track zoom
const [searchParams, setSearchParams] = useSearchParams();
const [colorPickerState, setColorPickerState] = useState({
open: false,
Expand All @@ -99,7 +100,24 @@ export default function Graph({
const isAggregated = aggregation !== "raw";

const nodeRef = useRef(null);
const chartRef = useRef<ChartJS<"line">>(null); // Define chartRef here
const chartRef = useRef<ChartJS<"line">>(null);

useEffect(() => {
if (chartRef.current) {
const canvas = chartRef.current.canvas;

const handleMouseLeave = () => {
setHoveredPoint(null); // Clear the hovered point when the mouse leaves the chart area
};

canvas.addEventListener("mouseleave", handleMouseLeave);

// Cleanup
return () => {
canvas.removeEventListener("mouseleave", handleMouseLeave);
};
}
}, [chartRef, setHoveredPoint]);

// get theme from tailwind
const [theme] = useTheme();
Expand Down Expand Up @@ -127,6 +145,7 @@ export default function Graph({
data: sensor.data.map((measurement) => ({
x: measurement.time,
y: measurement.value,
locationId: measurement.locationId,
})),
pointRadius: 0,
borderColor: sensor.color,
Expand All @@ -143,6 +162,7 @@ export default function Graph({
data: sensor.data.map((measurement) => ({
x: measurement.time,
y: measurement.min_value,
locationId: null,
})),
borderColor: sensor.color + "33",
backgroundColor: sensor.color + "33",
Expand All @@ -155,6 +175,7 @@ export default function Graph({
data: sensor.data.map((measurement) => ({
x: measurement.time,
y: measurement.max_value,
locationId: null,
})),
borderColor: sensor.color + "33",
backgroundColor: sensor.color + "33",
Expand Down Expand Up @@ -194,6 +215,7 @@ export default function Graph({
data: sensor.data.map((measurement) => ({
x: measurement.time,
y: measurement.value,
locationId: measurement.locationId,
})),
pointRadius: 0,
borderColor: sensor.color,
Expand All @@ -210,6 +232,7 @@ export default function Graph({
data: sensor.data.map((measurement) => ({
x: measurement.time,
y: measurement.min_value,
locationId: null,
})),
borderColor: sensor.color + "33",
backgroundColor: sensor.color + "33",
Expand All @@ -222,6 +245,7 @@ export default function Graph({
data: sensor.data.map((measurement) => ({
x: measurement.time,
y: measurement.max_value,
locationId: null,
})),
borderColor: sensor.color + "33",
backgroundColor: sensor.color + "33",
Expand Down Expand Up @@ -275,6 +299,8 @@ export default function Graph({
// locale: data.locale === "de" ? de : enGB,
// },
// },
min: currentZoom?.xMin,
max: currentZoom?.xMax,
ticks: {
major: {
enabled: true,
Expand Down Expand Up @@ -324,21 +350,38 @@ export default function Graph({
},
},
plugins: {
zoom: {
pan: {
enabled: true,
mode: "xy",
onPan: () => setIsZoomed(true), // Mark zoom as active
tooltip: {
enabled: true,
mode: "index",
intersect: false,
callbacks: {
label: (context: any) => {
const dataIndex = context.dataIndex;
const datasetIndex = context.datasetIndex;
const point = lineData.datasets[datasetIndex].data[dataIndex];
const locationId = point.locationId;
setHoveredPoint(locationId);
return `${context.dataset.label}: ${context.raw.y}`;
},
},
},
zoom: {
zoom: {
wheel: {
enabled: true,
},
pinch: {
drag: {
enabled: true,
},
mode: "xy",
onZoom: () => setIsZoomed(true), // Mark zoom as active
mode: "x",
onZoom: ({ chart }) => {
const xScale = chart.scales["x"];
const xMin = xScale.min;
const xMax = xScale.max;

// Track the zoom level
setCurrentZoom({ xMin, xMax });
},
},
},
legend: {
Expand Down Expand Up @@ -376,11 +419,13 @@ export default function Graph({
}, [
startDate,
endDate,
// data.locale,
sensors,
currentZoom?.xMin,
currentZoom?.xMax,
theme,
colorPickerState.open,
sensors,
lineData.datasets,
setHoveredPoint,
colorPickerState.open,
]);

function handleColorChange(newColor: string) {
Expand Down Expand Up @@ -463,7 +508,7 @@ export default function Graph({
function handleResetZoomClick() {
if (chartRef.current) {
chartRef.current.resetZoom(); // Use the resetZoom function from the zoom plugin
setIsZoomed(false); // Reset zoom state
setCurrentZoom(null); // Reset the zoom state
}
}

Expand Down Expand Up @@ -500,21 +545,23 @@ export default function Graph({
<AggregationFilter />
</div>
<div className="flex items-center justify-end gap-4">
{isZoomed && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<RefreshCcw
onClick={handleResetZoomClick}
className="cursor-pointer"
/>
</TooltipTrigger>
<TooltipContent>
<p>Reset zoom</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
{currentZoom !== null &&
currentZoom.xMax !== 0 &&
currentZoom.xMin !== 0 && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<RefreshCcw
onClick={handleResetZoomClick}
className="cursor-pointer"
/>
</TooltipTrigger>
<TooltipContent>
<p>Reset zoom</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
<DropdownMenu>
<DropdownMenuTrigger>
<Download />
Expand Down Expand Up @@ -552,7 +599,7 @@ export default function Graph({
) : (
<ClientOnly fallback={<Spinner />}>
{() => (
<LineWithZoom
<GraphWithZoom
lineData={lineData}
options={options}
chartRef={chartRef} // Pass chartRef as a prop
Expand Down
Loading
Loading