- {sensor.lastMeasurement?.value ?? '–'}
+ {sensor.lastMeasurement?.value ??
+ '–'}
{sensor.unit}
@@ -588,7 +589,8 @@ export default function DeviceDetailBox() {
- {sensor.lastMeasurement?.value ?? '–'}
+ {sensor.lastMeasurement?.value ??
+ '–'}
{sensor.unit}
diff --git a/app/components/device-detail/entry-logs.tsx b/app/components/device-detail/entry-logs.tsx
index 5eabf15f8..098923d73 100644
--- a/app/components/device-detail/entry-logs.tsx
+++ b/app/components/device-detail/entry-logs.tsx
@@ -1,165 +1,167 @@
-import { useMediaQuery } from "@mantine/hooks";
-import { Activity, Clock, ExternalLink } from "lucide-react";
-import { useState } from "react";
-import { Button } from "../ui/button";
+import { useMediaQuery } from '@mantine/hooks'
+import { Activity, Clock, ExternalLink } from 'lucide-react'
+import { useState } from 'react'
+import { Button } from '../ui/button'
import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "../ui/dialog";
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from '../ui/dialog'
import {
- Drawer,
- DrawerClose,
- DrawerContent,
- DrawerDescription,
- DrawerFooter,
- DrawerHeader,
- DrawerTitle,
- DrawerTrigger,
-} from "../ui/drawer";
+ Drawer,
+ DrawerClose,
+ DrawerContent,
+ DrawerDescription,
+ DrawerFooter,
+ DrawerHeader,
+ DrawerTitle,
+ DrawerTrigger,
+} from '../ui/drawer'
import {
- Tooltip,
- TooltipContent,
- TooltipProvider,
- TooltipTrigger,
-} from "../ui/tooltip";
-import { Card } from "@/components/ui/card";
-import { ScrollArea } from "@/components/ui/scroll-area";
-import { type LogEntry } from "~/schema/log-entry";
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from '../ui/tooltip'
+import { Card } from '@/components/ui/card'
+import { ScrollArea } from '@/components/ui/scroll-area'
+import { type LogEntry } from '~/schema/log-entry'
export default function EntryLogs({
- entryLogs = [],
+ entryLogs = [],
}: {
- entryLogs: LogEntry[];
+ entryLogs: LogEntry[]
}) {
- const [open, setOpen] = useState(false);
- const isDesktop = useMediaQuery("(min-width: 768px)");
+ const [open, setOpen] = useState(false)
+ const isDesktop = useMediaQuery('(min-width: 768px)')
- if (isDesktop) {
- return (
-
-
Logs
-
-
-
-
-
{entryLogs[entryLogs.length -1].content}
-
-
- {new Date(entryLogs[0].createdAt).toLocaleString()}
-
-
-
-
-
-
-
-
-
-
- {" "}
-
-
-
- Show all logs.
-
-
-
-
-
-
-
- Device Logs
-
- If this is your device, you can make changes in your device
- settings.
-
-
-
-
-
-
-
-
- );
- }
+ if (isDesktop) {
+ return (
+
+
Logs
+
+
+
+
+
+ {entryLogs[entryLogs.length - 1].content}
+
+
+
+ {new Date(entryLogs[0].createdAt).toLocaleString()}
+
+
+
+
+
+
+
+
+
+
+ {' '}
+
+
+
+ Show all logs.
+
+
+
+
+
+
+
+ Device Logs
+
+ If this is your device, you can make changes in your device
+ settings.
+
+
+
+
+
+
+
+
+ )
+ }
- return (
-
-
Logs
-
-
-
-
-
{entryLogs[0].content}
-
-
- {new Date(entryLogs[0].createdAt).toLocaleString()}
-
-
-
-
-
-
-
-
-
-
-
-
- Device Logs
-
- If this is your device, you can make changes in your device
- settings.
-
-
-
-
-
- Close
-
-
-
-
-
-
- );
+ return (
+
+
Logs
+
+
+
+
+
{entryLogs[0].content}
+
+
+ {new Date(entryLogs[0].createdAt).toLocaleString()}
+
+
+
+
+
+
+
+
+
+
+
+
+ Device Logs
+
+ If this is your device, you can make changes in your device
+ settings.
+
+
+
+
+
+ Close
+
+
+
+
+
+
+ )
}
function LogList({ entryLogs = [] }: { entryLogs: LogEntry[] }) {
- return (
-
-
- {entryLogs.map((log, index) => (
-
-
-
-
- {log.content}
-
-
- {new Date(log.createdAt).toLocaleString()}
-
-
-
- {index < entryLogs.length - 1 && (
-
- )}
-
- ))}
-
-
- );
+ return (
+
+
+ {entryLogs.map((log, index) => (
+
+
+
+
+ {log.content}
+
+
+ {new Date(log.createdAt).toLocaleString()}
+
+
+
+ {index < entryLogs.length - 1 && (
+
+ )}
+
+ ))}
+
+
+ )
}
diff --git a/app/components/device-detail/graph.tsx b/app/components/device-detail/graph.tsx
index 594d983c4..114675ba7 100644
--- a/app/components/device-detail/graph.tsx
+++ b/app/components/device-detail/graph.tsx
@@ -13,7 +13,14 @@ import {
import 'chartjs-adapter-date-fns'
// import { de, enGB } from "date-fns/locale";
import { Download, RefreshCcw, X } from 'lucide-react'
-import { useMemo, useRef, useState, useEffect, useContext,type RefObject } from 'react'
+import {
+ useMemo,
+ useRef,
+ useState,
+ useEffect,
+ useContext,
+ type RefObject,
+} from 'react'
import { Scatter } from 'react-chartjs-2'
import { isBrowser, isTablet } from 'react-device-detect'
import Draggable, { type DraggableData } from 'react-draggable'
diff --git a/app/components/device-detail/profile-box-selection.tsx b/app/components/device-detail/profile-box-selection.tsx
index 10aadbc36..80c3597ec 100644
--- a/app/components/device-detail/profile-box-selection.tsx
+++ b/app/components/device-detail/profile-box-selection.tsx
@@ -1,70 +1,70 @@
// import { useState } from "react";
import {
- Card,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
-} from "../ui/card";
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from '../ui/card'
import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "../ui/select";
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '../ui/select'
const dummyBoxes = [
- {
- name: "Box at IFGI",
- id: "1",
- image: "/sensebox_outdoor.jpg",
- },
- {
- name: "senseBox at Aasee",
- id: "2",
- image: "https://picsum.photos/200/300",
- },
- {
- name: "Box at Schlossgarten",
- id: "3",
- image: "https://picsum.photos/200/300",
- },
-];
+ {
+ name: 'Box at IFGI',
+ id: '1',
+ image: '/sensebox_outdoor.jpg',
+ },
+ {
+ name: 'senseBox at Aasee',
+ id: '2',
+ image: 'https://picsum.photos/200/300',
+ },
+ {
+ name: 'Box at Schlossgarten',
+ id: '3',
+ image: 'https://picsum.photos/200/300',
+ },
+]
export default function ProfileBoxSelection() {
- // const [selectedBox, setSelectedBox] = useState(dummyBoxes[0]);
- return (
-
- {/* this is all jsut dummy data - the real data will be fetched from the API as soon as the route is implemented */}
-
-
- {dummyBoxes[0].name}
- Last activity: 13min ago
-
-
-
-
-
-
-
-
-
-
-
-
- Light
- Dark
- System
-
-
-
-
-
- );
+ // const [selectedBox, setSelectedBox] = useState(dummyBoxes[0]);
+ return (
+
+ {/* this is all jsut dummy data - the real data will be fetched from the API as soon as the route is implemented */}
+
+
+ {dummyBoxes[0].name}
+ Last activity: 13min ago
+
+
+
+
+
+
+
+
+
+
+
+
+ Light
+ Dark
+ System
+
+
+
+
+
+ )
}
diff --git a/app/components/device-detail/share-link.tsx b/app/components/device-detail/share-link.tsx
index 42066ac58..bd4d5077a 100644
--- a/app/components/device-detail/share-link.tsx
+++ b/app/components/device-detail/share-link.tsx
@@ -1,99 +1,99 @@
-import { Copy, Link } from "lucide-react";
-import { Button } from "../ui/button";
-import { Input } from "../ui/input";
-import { useToast } from "@/components/ui/use-toast";
+import { Copy, Link } from 'lucide-react'
+import { Button } from '../ui/button'
+import { Input } from '../ui/input'
+import { useToast } from '@/components/ui/use-toast'
export default function ShareLink() {
- const { toast } = useToast();
+ const { toast } = useToast()
- return (
-
-
- {/* */}
-
- {/* */}
-
- {/* */}
-
- {/* */}
-
- {/* */}
-
-
- {/* */}
-
-
-
- {
- void navigator.clipboard.writeText(window.location.href);
- toast({
- title: "Copied to clipboard",
- description: "Go ahead and share your link! 🎉",
- });
- }}
- className="inline-flex h-9 transform items-center justify-center rounded-md bg-primary px-2 py-2 text-sm font-medium text-primary-foreground shadow transition-transform active:scale-75"
- >
-
- Copy
-
-
-
- );
+ return (
+
+
+ {/* */}
+
+ {/* */}
+
+ {/* */}
+
+ {/* */}
+
+ {/* */}
+
+
+ {/* */}
+
+
+
+ {
+ void navigator.clipboard.writeText(window.location.href)
+ toast({
+ title: 'Copied to clipboard',
+ description: 'Go ahead and share your link! 🎉',
+ })
+ }}
+ className="inline-flex h-9 transform items-center justify-center rounded-md bg-primary px-2 py-2 text-sm font-medium text-primary-foreground shadow transition-transform active:scale-75"
+ >
+
+ Copy
+
+
+
+ )
}
diff --git a/app/components/device/new/advanced-info.tsx b/app/components/device/new/advanced-info.tsx
index ba7019d24..240c8f200 100644
--- a/app/components/device/new/advanced-info.tsx
+++ b/app/components/device/new/advanced-info.tsx
@@ -1,246 +1,246 @@
-import { useFormContext } from "react-hook-form";
+import { useFormContext } from 'react-hook-form'
import {
- Card,
- CardContent,
- CardDescription,
- CardHeader,
- CardTitle,
-} from "~/components/ui/card";
-import { Input } from "~/components/ui/input";
-import { Label } from "~/components/ui/label";
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from '~/components/ui/card'
+import { Input } from '~/components/ui/input'
+import { Label } from '~/components/ui/label'
import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "~/components/ui/select";
-import { Switch } from "~/components/ui/switch";
-import { Textarea } from "~/components/ui/textarea";
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '~/components/ui/select'
+import { Switch } from '~/components/ui/switch'
+import { Textarea } from '~/components/ui/textarea'
export function AdvancedStep() {
- const { register, setValue, watch, resetField } = useFormContext();
-
- // Watch field states
- const isMqttEnabled = watch("mqttEnabled") || false;
- const isTtnEnabled = watch("ttnEnabled") || false;
-
- // Clear corresponding fields when disabling
- const handleMqttToggle = (checked: boolean) => {
- setValue("mqttEnabled", checked);
- if (!checked) {
- resetField("url");
- resetField("topic");
- resetField("messageFormat");
- resetField("decodeOptions");
- resetField("connectionOptions");
- }
- };
-
- const handleTtnToggle = (checked: boolean) => {
- setValue("ttnEnabled", checked);
- if (!checked) {
- resetField("dev_id");
- resetField("app_id");
- resetField("profile");
- resetField("decodeOptions");
- resetField("port");
- }
- };
-
- const handleInputChange = (
- event: React.ChangeEvent
,
- ) => {
- const { name, value } = event.target;
- setValue(name, value);
- };
-
- const handleSelectChange = (field: string, value: string) => {
- setValue(field, value);
- };
-
- return (
- <>
- {/* MQTT Configuration */}
-
-
- MQTT Configuration
-
- Configure your MQTT settings for data streaming
-
-
-
-
-
- Enable MQTT
-
-
-
-
- {isMqttEnabled && (
-
-
- MQTT URL
-
-
-
-
- MQTT Topic
-
-
-
-
- Message Format
-
- handleSelectChange("messageFormat", value)
- }
- defaultValue={watch("messageFormat")}
- >
-
-
-
-
- JSON
- CSV
-
-
-
-
-
- Decode Options
-
-
-
-
-
- Connection Options
-
-
-
-
- )}
-
-
-
- {/* TTN Configuration */}
-
-
- TTN Configuration
-
- Configure your TTN (The Things Network) settings
-
-
-
-
-
- Enable TTN
-
-
-
-
- {isTtnEnabled && (
-
-
- Device ID
-
-
-
-
- Application ID
-
-
-
-
- Profile
-
- handleSelectChange("profile", value)
- }
- defaultValue={watch("profile")}
- >
-
-
-
-
-
- Lora Serialization
-
- Sensebox/Home
- JSON
- Debug
- Cayenne LPP
-
-
-
-
-
- Decode Options
-
-
-
-
- Port
-
-
-
- )}
-
-
- >
- );
+ const { register, setValue, watch, resetField } = useFormContext()
+
+ // Watch field states
+ const isMqttEnabled = watch('mqttEnabled') || false
+ const isTtnEnabled = watch('ttnEnabled') || false
+
+ // Clear corresponding fields when disabling
+ const handleMqttToggle = (checked: boolean) => {
+ setValue('mqttEnabled', checked)
+ if (!checked) {
+ resetField('url')
+ resetField('topic')
+ resetField('messageFormat')
+ resetField('decodeOptions')
+ resetField('connectionOptions')
+ }
+ }
+
+ const handleTtnToggle = (checked: boolean) => {
+ setValue('ttnEnabled', checked)
+ if (!checked) {
+ resetField('dev_id')
+ resetField('app_id')
+ resetField('profile')
+ resetField('decodeOptions')
+ resetField('port')
+ }
+ }
+
+ const handleInputChange = (
+ event: React.ChangeEvent,
+ ) => {
+ const { name, value } = event.target
+ setValue(name, value)
+ }
+
+ const handleSelectChange = (field: string, value: string) => {
+ setValue(field, value)
+ }
+
+ return (
+ <>
+ {/* MQTT Configuration */}
+
+
+ MQTT Configuration
+
+ Configure your MQTT settings for data streaming
+
+
+
+
+
+ Enable MQTT
+
+
+
+
+ {isMqttEnabled && (
+
+
+ MQTT URL
+
+
+
+
+ MQTT Topic
+
+
+
+
+ Message Format
+
+ handleSelectChange('messageFormat', value)
+ }
+ defaultValue={watch('messageFormat')}
+ >
+
+
+
+
+ JSON
+ CSV
+
+
+
+
+
+ Decode Options
+
+
+
+
+
+ Connection Options
+
+
+
+
+ )}
+
+
+
+ {/* TTN Configuration */}
+
+
+ TTN Configuration
+
+ Configure your TTN (The Things Network) settings
+
+
+
+
+
+ Enable TTN
+
+
+
+
+ {isTtnEnabled && (
+
+
+ Device ID
+
+
+
+
+ Application ID
+
+
+
+
+ Profile
+
+ handleSelectChange('profile', value)
+ }
+ defaultValue={watch('profile')}
+ >
+
+
+
+
+
+ Lora Serialization
+
+ Sensebox/Home
+ JSON
+ Debug
+ Cayenne LPP
+
+
+
+
+
+ Decode Options
+
+
+
+
+ Port
+
+
+
+ )}
+
+
+ >
+ )
}
diff --git a/app/components/device/new/custom-device-config.tsx b/app/components/device/new/custom-device-config.tsx
index 458969018..a7e898c5d 100644
--- a/app/components/device/new/custom-device-config.tsx
+++ b/app/components/device/new/custom-device-config.tsx
@@ -1,116 +1,116 @@
-import { X } from "lucide-react";
-import { useState, useEffect } from "react";
-import { useFormContext } from "react-hook-form";
-import { type Sensor } from "./sensors-info";
-import { Button } from "@/components/ui/button";
-import { Card, CardContent } from "@/components/ui/card";
-import { Input } from "@/components/ui/input";
-import { Label } from "@/components/ui/label";
-import { Separator } from "~/components/ui/separator";
+import { X } from 'lucide-react'
+import { useState, useEffect } from 'react'
+import { useFormContext } from 'react-hook-form'
+import { type Sensor } from './sensors-info'
+import { Button } from '@/components/ui/button'
+import { Card, CardContent } from '@/components/ui/card'
+import { Input } from '@/components/ui/input'
+import { Label } from '@/components/ui/label'
+import { Separator } from '~/components/ui/separator'
export function CustomDeviceConfig() {
- const { setValue, watch } = useFormContext();
+ const { setValue, watch } = useFormContext()
- // Initialize state from form context
- const [sensors, setSensors] = useState(
- () => watch("selectedSensors") || [],
- );
- const [newSensor, setNewSensor] = useState({
- title: "",
- unit: "",
- sensorType: "",
- });
+ // Initialize state from form context
+ const [sensors, setSensors] = useState(
+ () => watch('selectedSensors') || [],
+ )
+ const [newSensor, setNewSensor] = useState({
+ title: '',
+ unit: '',
+ sensorType: '',
+ })
- // Sync state with form context on mount
- useEffect(() => {
- const savedSensors = watch("selectedSensors") || [];
- if (savedSensors.length > 0) {
- setSensors(savedSensors);
- }
- }, [watch]);
+ // Sync state with form context on mount
+ useEffect(() => {
+ const savedSensors = watch('selectedSensors') || []
+ if (savedSensors.length > 0) {
+ setSensors(savedSensors)
+ }
+ }, [watch])
- const updateNewSensor = (field: keyof Sensor, value: string) => {
- setNewSensor((prev) => ({ ...prev, [field]: value }));
- };
+ const updateNewSensor = (field: keyof Sensor, value: string) => {
+ setNewSensor((prev) => ({ ...prev, [field]: value }))
+ }
- const addSensor = () => {
- if (newSensor.title && newSensor.unit && newSensor.sensorType) {
- const updatedSensors = [...sensors, newSensor];
- setSensors(updatedSensors);
- setValue("selectedSensors", updatedSensors); // Sync with form
- setNewSensor({ title: "", unit: "", sensorType: "" });
- }
- };
+ const addSensor = () => {
+ if (newSensor.title && newSensor.unit && newSensor.sensorType) {
+ const updatedSensors = [...sensors, newSensor]
+ setSensors(updatedSensors)
+ setValue('selectedSensors', updatedSensors) // Sync with form
+ setNewSensor({ title: '', unit: '', sensorType: '' })
+ }
+ }
- const removeSensor = (index: number) => {
- const updatedSensors = sensors.filter((_, i) => i !== index);
- setSensors(updatedSensors);
- setValue("selectedSensors", updatedSensors); // Sync with form
- };
+ const removeSensor = (index: number) => {
+ const updatedSensors = sensors.filter((_, i) => i !== index)
+ setSensors(updatedSensors)
+ setValue('selectedSensors', updatedSensors) // Sync with form
+ }
- return (
-
-
+ return (
+
+
- {sensors.length > 0 &&
}
- {sensors.map((sensor, index) => (
-
-
-
- {sensor.title} ({sensor.unit}
- ) - {sensor.sensorType}
-
- {
- e.preventDefault();
- removeSensor(index);
- }}
- >
-
-
-
-
- ))}
-
- );
+ {sensors.length > 0 &&
}
+ {sensors.map((sensor, index) => (
+
+
+
+ {sensor.title} ({sensor.unit}
+ ) - {sensor.sensorType}
+
+ {
+ e.preventDefault()
+ removeSensor(index)
+ }}
+ >
+
+
+
+
+ ))}
+
+ )
}
diff --git a/app/components/device/new/device-info.tsx b/app/components/device/new/device-info.tsx
index ddd94b284..9a217d9cd 100644
--- a/app/components/device/new/device-info.tsx
+++ b/app/components/device/new/device-info.tsx
@@ -1,188 +1,188 @@
-import { X } from "lucide-react";
-import { useEffect, useState } from "react";
-import { useFormContext } from "react-hook-form";
-import { Button } from "~/components/ui/button";
-import { Card, CardContent } from "~/components/ui/card";
-import { Label } from "~/components/ui/label";
-import { RadioGroup, RadioGroupItem } from "~/components/ui/radio-group";
-import { Separator } from "~/components/ui/separator";
-import { cn } from "~/lib/utils";
+import { X } from 'lucide-react'
+import { useEffect, useState } from 'react'
+import { useFormContext } from 'react-hook-form'
+import { Button } from '~/components/ui/button'
+import { Card, CardContent } from '~/components/ui/card'
+import { Label } from '~/components/ui/label'
+import { RadioGroup, RadioGroupItem } from '~/components/ui/radio-group'
+import { Separator } from '~/components/ui/separator'
+import { cn } from '~/lib/utils'
const devices = [
- {
- name: "senseBox:Home",
- image:
- "https://sensebox.kaufen/api//public/uploads/thumbs/thumb--1525013086964-mcu_one_top.png",
- },
- {
- name: "senseBox:Edu",
- image:
- "https://sensebox.kaufen/api//public/uploads/thumbs/thumb--1524084284270-mcu%20top.png",
- },
- {
- name: "luftdaten.info",
- image:
- "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLXdpbmQiPjxwYXRoIGQ9Ik0xMi44IDE5LjZBMiAyIDAgMSAwIDE0IDE2SDIiLz48cGF0aCBkPSJNMTcuNSA4YTIuNSAyLjUgMCAxIDEgMiA0SDIiLz48cGF0aCBkPSJNOS44IDQuNEEyIDIgMCAxIDEgMTEgOEgyIi8+PC9zdmc+",
- },
- {
- name: "custom",
- image:
- "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLXdyZW5jaCI+PHBhdGggZD0iTTE0LjcgNi4zYTEgMSAwIDAgMCAwIDEuNGwxLjYgMS42YTEgMSAwIDAgMCAxLjQgMGwzLjc3LTMuNzdhNiA2IDAgMCAxLTcuOTQgNy45NGwtNi45MSA2LjkxYTIuMTIgMi4xMiAwIDAgMS0zLTNsNi45MS02LjkxYTYgNiAwIDAgMSA3Ljk0LTcuOTRsLTMuNzYgMy43NnoiLz48L3N2Zz4=",
- },
-];
+ {
+ name: 'senseBox:Home',
+ image:
+ 'https://sensebox.kaufen/api//public/uploads/thumbs/thumb--1525013086964-mcu_one_top.png',
+ },
+ {
+ name: 'senseBox:Edu',
+ image:
+ 'https://sensebox.kaufen/api//public/uploads/thumbs/thumb--1524084284270-mcu%20top.png',
+ },
+ {
+ name: 'luftdaten.info',
+ image:
+ 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLXdpbmQiPjxwYXRoIGQ9Ik0xMi44IDE5LjZBMiAyIDAgMSAwIDE0IDE2SDIiLz48cGF0aCBkPSJNMTcuNSA4YTIuNSAyLjUgMCAxIDEgMiA0SDIiLz48cGF0aCBkPSJNOS44IDQuNEEyIDIgMCAxIDEgMTEgOEgyIi8+PC9zdmc+',
+ },
+ {
+ name: 'custom',
+ image:
+ 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLXdyZW5jaCI+PHBhdGggZD0iTTE0LjcgNi4zYTEgMSAwIDAgMCAwIDEuNGwxLjYgMS42YTEgMSAwIDAgMCAxLjQgMGwzLjc3LTMuNzdhNiA2IDAgMCAxLTcuOTQgNy45NGwtNi45MSA2LjkxYTIuMTIgMi4xMiAwIDAgMS0zLTNsNi45MS02LjkxYTYgNiAwIDAgMSA3Ljk0LTcuOTRsLTMuNzYgMy43NnoiLz48L3N2Zz4=',
+ },
+]
-const connectionTypes = ["Wifi", "Lora", "Ethernet"];
+const connectionTypes = ['Wifi', 'Lora', 'Ethernet']
export function DeviceSelectionStep() {
- const { setValue, watch } = useFormContext();
+ const { setValue, watch } = useFormContext()
- // Watch the existing values from the form state
- const model = watch("model");
+ // Watch the existing values from the form state
+ const model = watch('model')
- // Initialize component state with form values
- const [selectedDevice, setSelectedDevice] = useState(model || null);
- const [selectedConnectionType, setSelectedConnectionType] = useState("");
+ // Initialize component state with form values
+ const [selectedDevice, setSelectedDevice] = useState(model || null)
+ const [selectedConnectionType, setSelectedConnectionType] = useState('')
- useEffect(() => {
- if (
- model === "homeV2Wifi" ||
- model === "homeV2Ethernet" ||
- model === "homeV2Lora"
- ) {
- setSelectedDevice("senseBox:Home");
- const connectionMap: Record = {
- homeV2Wifi: "Wifi",
- homeV2Ethernet: "Ethernet",
- homeV2Lora: "Lora",
- };
- setSelectedConnectionType(connectionMap[model] || "");
- } else {
- setSelectedDevice(model || "");
- setSelectedConnectionType("");
- }
- }, [model]);
+ useEffect(() => {
+ if (
+ model === 'homeV2Wifi' ||
+ model === 'homeV2Ethernet' ||
+ model === 'homeV2Lora'
+ ) {
+ setSelectedDevice('senseBox:Home')
+ const connectionMap: Record = {
+ homeV2Wifi: 'Wifi',
+ homeV2Ethernet: 'Ethernet',
+ homeV2Lora: 'Lora',
+ }
+ setSelectedConnectionType(connectionMap[model] || '')
+ } else {
+ setSelectedDevice(model || '')
+ setSelectedConnectionType('')
+ }
+ }, [model])
- const handleDeviceChange = (value: string) => {
- setValue("selectedSensors", null); // Reset the selected sensors
- if (selectedDevice === value) {
- // Deselect the currently selected device
- setSelectedDevice(null);
- setValue("model", ""); // Reset the model
- } else {
- // Select a new device
- setSelectedDevice(value);
+ const handleDeviceChange = (value: string) => {
+ setValue('selectedSensors', null) // Reset the selected sensors
+ if (selectedDevice === value) {
+ // Deselect the currently selected device
+ setSelectedDevice(null)
+ setValue('model', '') // Reset the model
+ } else {
+ // Select a new device
+ setSelectedDevice(value)
- // Set the model for the selected device
- if (value === "senseBox:Home") {
- setValue("model", "homeV2Wifi"); // Default to a valid connection type for Home
- } else {
- setValue("model", value); // Set model to the selected device name
- }
- }
- };
+ // Set the model for the selected device
+ if (value === 'senseBox:Home') {
+ setValue('model', 'homeV2Wifi') // Default to a valid connection type for Home
+ } else {
+ setValue('model', value) // Set model to the selected device name
+ }
+ }
+ }
- const handleConnectionTypeChange = (type: string) => {
- // Dynamically set the model based on the connection type
- const modelMap: Record = {
- Wifi: "homeV2Wifi",
- Ethernet: "homeV2Ethernet",
- Lora: "homeV2Lora",
- };
- setValue("model", modelMap[type]);
- setSelectedConnectionType(type); // Update local state
- };
+ const handleConnectionTypeChange = (type: string) => {
+ // Dynamically set the model based on the connection type
+ const modelMap: Record = {
+ Wifi: 'homeV2Wifi',
+ Ethernet: 'homeV2Ethernet',
+ Lora: 'homeV2Lora',
+ }
+ setValue('model', modelMap[type])
+ setSelectedConnectionType(type) // Update local state
+ }
- const handleClose = () => {
- setSelectedDevice(null);
- setValue("model", null);
- };
+ const handleClose = () => {
+ setSelectedDevice(null)
+ setValue('model', null)
+ }
- return (
-
-
- {devices.map((device) => {
- if (
- selectedDevice === "senseBox:Home" &&
- device.name !== selectedDevice
- )
- return null;
+ return (
+
+
+ {devices.map((device) => {
+ if (
+ selectedDevice === 'senseBox:Home' &&
+ device.name !== selectedDevice
+ )
+ return null
- return (
-
{
- if (selectedDevice === "senseBox:Home") {
- return;
- }
- handleDeviceChange(device.name);
- }}
- >
-
- {selectedDevice === "senseBox:Home" && (
- {
- e.stopPropagation();
- handleClose();
- }}
- >
-
-
- )}
-
- {device.name}
- {device.name === "senseBox:Home" &&
- selectedDevice === "senseBox:Home" && (
- <>
-
-
-
- Connection Type:
-
-
- handleConnectionTypeChange(value)
- }
- className="flex flex-col space-y-1"
- >
- {connectionTypes.map((type) => (
-
-
-
- {type}
-
-
- ))}
-
-
- >
- )}
-
-
- );
- })}
-
-
- );
+ return (
+
{
+ if (selectedDevice === 'senseBox:Home') {
+ return
+ }
+ handleDeviceChange(device.name)
+ }}
+ >
+
+ {selectedDevice === 'senseBox:Home' && (
+ {
+ e.stopPropagation()
+ handleClose()
+ }}
+ >
+
+
+ )}
+
+ {device.name}
+ {device.name === 'senseBox:Home' &&
+ selectedDevice === 'senseBox:Home' && (
+ <>
+
+
+
+ Connection Type:
+
+
+ handleConnectionTypeChange(value)
+ }
+ className="flex flex-col space-y-1"
+ >
+ {connectionTypes.map((type) => (
+
+
+
+ {type}
+
+
+ ))}
+
+
+ >
+ )}
+
+
+ )
+ })}
+
+
+ )
}
diff --git a/app/components/device/new/sensors-info.tsx b/app/components/device/new/sensors-info.tsx
index 115b7c61e..1c6b77ed0 100644
--- a/app/components/device/new/sensors-info.tsx
+++ b/app/components/device/new/sensors-info.tsx
@@ -1,217 +1,216 @@
-import { useState, useEffect } from "react";
-import { useFormContext } from "react-hook-form";
-import { z } from "zod";
-import { CustomDeviceConfig } from "./custom-device-config";
-import { Card, CardContent } from "~/components/ui/card";
-import { cn } from "~/lib/utils";
-import { getSensorsForModel } from "~/utils/model-definitions";
+import { useState, useEffect } from 'react'
+import { useFormContext } from 'react-hook-form'
+import { z } from 'zod'
+import { CustomDeviceConfig } from './custom-device-config'
+import { Card, CardContent } from '~/components/ui/card'
+import { cn } from '~/lib/utils'
+import { getSensorsForModel } from '~/utils/model-definitions'
export const sensorSchema = z.object({
- title: z.string(),
- unit: z.string(),
- sensorType: z.string(),
- icon: z.string().optional(),
- image: z.string().optional(),
-});
+ title: z.string(),
+ unit: z.string(),
+ sensorType: z.string(),
+ icon: z.string().optional(),
+ image: z.string().optional(),
+})
-export type Sensor = z.infer;
+export type Sensor = z.infer
type SensorGroup = {
- sensorType: string;
- sensors: Sensor[];
- image?: string;
-};
+ sensorType: string
+ sensors: Sensor[]
+ image?: string
+}
export function SensorSelectionStep() {
- const { watch, setValue } = useFormContext();
- const selectedDevice = watch("model");
- const [selectedDeviceModel, setSelectedDeviceModel] = useState(
- null,
- );
- const [sensors, setSensors] = useState([]);
- const [selectedSensors, setSelectedSensors] = useState([]);
-
- useEffect(() => {
- if (selectedDevice) {
- const deviceModel = selectedDevice.startsWith("homeV2")
- ? "senseBoxHomeV2"
- : selectedDevice;
- setSelectedDeviceModel(deviceModel);
-
- if (deviceModel !== "custom") {
- const fetchedSensors = getSensorsForModel(deviceModel);
- setSensors(fetchedSensors);
- } else {
- setSensors([]);
- }
-
- }
- }, [selectedDevice]);
-
- useEffect(() => {
- const savedSelectedSensors = watch("selectedSensors") || [];
- setSelectedSensors(savedSelectedSensors);
- }, [watch]);
-
- const groupSensorsByType = (sensors: Sensor[]): SensorGroup[] => {
- const grouped = sensors.reduce(
- (acc, sensor) => {
- if (!acc[sensor.sensorType]) {
- acc[sensor.sensorType] = [];
- }
- acc[sensor.sensorType].push(sensor);
- return acc;
- },
- {} as Record,
- );
-
- return Object.entries(grouped).map(([sensorType, sensors]) => ({
- sensorType,
- sensors,
- image: sensors.find((sensor) => sensor.image)?.image,
- }));
- };
-
- const sensorGroups = groupSensorsByType(sensors);
-
- const handleGroupToggle = (group: SensorGroup) => {
- const isGroupSelected = group.sensors.every((sensor) =>
- selectedSensors.some(
- (s) => s.title === sensor.title && s.sensorType === sensor.sensorType,
- ),
- );
-
- const updatedSensors = isGroupSelected
- ? selectedSensors.filter(
- (s) =>
- !group.sensors.some(
- (sensor) =>
- s.title === sensor.title && s.sensorType === sensor.sensorType,
- ),
- )
- : [
- ...selectedSensors,
- ...group.sensors.filter(
- (sensor) =>
- !selectedSensors.some(
- (s) =>
- s.title === sensor.title &&
- s.sensorType === sensor.sensorType,
- ),
- ),
- ];
-
- setSelectedSensors(updatedSensors);
- setValue("selectedSensors", updatedSensors);
- };
-
- const handleSensorToggle = (sensor: Sensor) => {
- const isAlreadySelected = selectedSensors.some(
- (s) => s.title === sensor.title && s.sensorType === sensor.sensorType,
- );
-
- const updatedSensors = isAlreadySelected
- ? selectedSensors.filter(
- (s) =>
- !(s.title === sensor.title && s.sensorType === sensor.sensorType),
- )
- : [...selectedSensors, sensor];
-
- setSelectedSensors(updatedSensors);
- setValue("selectedSensors", updatedSensors);
- };
-
- if (!selectedDevice) {
- return Please select a device first.
;
- }
-
- if (selectedDevice === "custom") {
- return ;
- }
-
- return (
-
-
-
- {sensorGroups.map((group) => {
- const isGroupSelected = group.sensors.every((sensor) =>
- selectedSensors.some(
- (s) =>
- s.title === sensor.title &&
- s.sensorType === sensor.sensorType,
- ),
- );
-
- return (
-
handleGroupToggle(group)
- : undefined
- }
- >
-
-
- {group.sensorType}
-
-
-
- {group.sensors.map((sensor) => {
- const isSelected = selectedSensors.some(
- (s) =>
- s.title === sensor.title &&
- s.sensorType === sensor.sensorType,
- );
-
- return (
- {
- e.stopPropagation();
- handleSensorToggle(sensor);
- }
- : undefined
- }
- >
- {sensor.title} ({sensor.unit})
-
- );
- })}
-
-
- {group.image && (
-
- )}
-
-
-
- );
- })}
-
-
-
- );
+ const { watch, setValue } = useFormContext()
+ const selectedDevice = watch('model')
+ const [selectedDeviceModel, setSelectedDeviceModel] = useState(
+ null,
+ )
+ const [sensors, setSensors] = useState([])
+ const [selectedSensors, setSelectedSensors] = useState([])
+
+ useEffect(() => {
+ if (selectedDevice) {
+ const deviceModel = selectedDevice.startsWith('homeV2')
+ ? 'senseBoxHomeV2'
+ : selectedDevice
+ setSelectedDeviceModel(deviceModel)
+
+ if (deviceModel !== 'custom') {
+ const fetchedSensors = getSensorsForModel(deviceModel)
+ setSensors(fetchedSensors)
+ } else {
+ setSensors([])
+ }
+ }
+ }, [selectedDevice])
+
+ useEffect(() => {
+ const savedSelectedSensors = watch('selectedSensors') || []
+ setSelectedSensors(savedSelectedSensors)
+ }, [watch])
+
+ const groupSensorsByType = (sensors: Sensor[]): SensorGroup[] => {
+ const grouped = sensors.reduce(
+ (acc, sensor) => {
+ if (!acc[sensor.sensorType]) {
+ acc[sensor.sensorType] = []
+ }
+ acc[sensor.sensorType].push(sensor)
+ return acc
+ },
+ {} as Record,
+ )
+
+ return Object.entries(grouped).map(([sensorType, sensors]) => ({
+ sensorType,
+ sensors,
+ image: sensors.find((sensor) => sensor.image)?.image,
+ }))
+ }
+
+ const sensorGroups = groupSensorsByType(sensors)
+
+ const handleGroupToggle = (group: SensorGroup) => {
+ const isGroupSelected = group.sensors.every((sensor) =>
+ selectedSensors.some(
+ (s) => s.title === sensor.title && s.sensorType === sensor.sensorType,
+ ),
+ )
+
+ const updatedSensors = isGroupSelected
+ ? selectedSensors.filter(
+ (s) =>
+ !group.sensors.some(
+ (sensor) =>
+ s.title === sensor.title && s.sensorType === sensor.sensorType,
+ ),
+ )
+ : [
+ ...selectedSensors,
+ ...group.sensors.filter(
+ (sensor) =>
+ !selectedSensors.some(
+ (s) =>
+ s.title === sensor.title &&
+ s.sensorType === sensor.sensorType,
+ ),
+ ),
+ ]
+
+ setSelectedSensors(updatedSensors)
+ setValue('selectedSensors', updatedSensors)
+ }
+
+ const handleSensorToggle = (sensor: Sensor) => {
+ const isAlreadySelected = selectedSensors.some(
+ (s) => s.title === sensor.title && s.sensorType === sensor.sensorType,
+ )
+
+ const updatedSensors = isAlreadySelected
+ ? selectedSensors.filter(
+ (s) =>
+ !(s.title === sensor.title && s.sensorType === sensor.sensorType),
+ )
+ : [...selectedSensors, sensor]
+
+ setSelectedSensors(updatedSensors)
+ setValue('selectedSensors', updatedSensors)
+ }
+
+ if (!selectedDevice) {
+ return Please select a device first.
+ }
+
+ if (selectedDevice === 'custom') {
+ return
+ }
+
+ return (
+
+
+
+ {sensorGroups.map((group) => {
+ const isGroupSelected = group.sensors.every((sensor) =>
+ selectedSensors.some(
+ (s) =>
+ s.title === sensor.title &&
+ s.sensorType === sensor.sensorType,
+ ),
+ )
+
+ return (
+
handleGroupToggle(group)
+ : undefined
+ }
+ >
+
+
+ {group.sensorType}
+
+
+
+ {group.sensors.map((sensor) => {
+ const isSelected = selectedSensors.some(
+ (s) =>
+ s.title === sensor.title &&
+ s.sensorType === sensor.sensorType,
+ )
+
+ return (
+ {
+ e.stopPropagation()
+ handleSensorToggle(sensor)
+ }
+ : undefined
+ }
+ >
+ {sensor.title} ({sensor.unit})
+
+ )
+ })}
+
+
+ {group.image && (
+
+ )}
+
+
+
+ )
+ })}
+
+
+
+ )
}
diff --git a/app/components/device/new/summary-info.tsx b/app/components/device/new/summary-info.tsx
index 6d7c49301..245c9c107 100644
--- a/app/components/device/new/summary-info.tsx
+++ b/app/components/device/new/summary-info.tsx
@@ -1,84 +1,87 @@
-import { MapPin, Tag, Smartphone, Cpu, Cog } from "lucide-react";
-import { useFormContext } from "react-hook-form";
-import { Badge } from "@/components/ui/badge";
-import { Card, CardContent } from "@/components/ui/card";
+import { MapPin, Tag, Smartphone, Cpu, Cog } from 'lucide-react'
+import { useFormContext } from 'react-hook-form'
+import { Badge } from '@/components/ui/badge'
+import { Card, CardContent } from '@/components/ui/card'
export function SummaryInfo() {
- const { getValues } = useFormContext();
- const formData = getValues();
+ const { getValues } = useFormContext()
+ const formData = getValues()
- const sections = [
- {
- title: "General Info",
- icon: ,
- data: [
- { label: "Name", value: formData.name },
- { label: "Exposure", value: formData.exposure },
- {
- label: "Tags",
- value:
- formData.tags?.map((tag: any) => tag.value).join(", ") || "None",
- },
- ],
- },
- {
- title: "Location",
- icon: ,
- data: [
- { label: "Latitude", value: parseFloat(formData.latitude).toFixed(4) },
- { label: "Longitude", value: parseFloat(formData.longitude).toFixed(4) },
- ],
- },
- {
- title: "Device",
- icon: ,
- data: [{ label: "Model", value: formData.model }],
- },
- {
- title: "Sensors",
- icon: ,
- data:
- formData.selectedSensors?.map((sensor: any) => ({
- value: sensor.sensorType,
- label: sensor.title,
- })) || [],
- },
- {
- title: "Advanced",
- icon: ,
- data: [
- { label: "MQTT Enabled", value: formData.mqttEnabled ? "Yes" : "No" },
- { label: "TTN Enabled", value: formData.ttnEnabled ? "Yes" : "No" },
- ],
- },
- ];
+ const sections = [
+ {
+ title: 'General Info',
+ icon: ,
+ data: [
+ { label: 'Name', value: formData.name },
+ { label: 'Exposure', value: formData.exposure },
+ {
+ label: 'Tags',
+ value:
+ formData.tags?.map((tag: any) => tag.value).join(', ') || 'None',
+ },
+ ],
+ },
+ {
+ title: 'Location',
+ icon: ,
+ data: [
+ { label: 'Latitude', value: parseFloat(formData.latitude).toFixed(4) },
+ {
+ label: 'Longitude',
+ value: parseFloat(formData.longitude).toFixed(4),
+ },
+ ],
+ },
+ {
+ title: 'Device',
+ icon: ,
+ data: [{ label: 'Model', value: formData.model }],
+ },
+ {
+ title: 'Sensors',
+ icon: ,
+ data:
+ formData.selectedSensors?.map((sensor: any) => ({
+ value: sensor.sensorType,
+ label: sensor.title,
+ })) || [],
+ },
+ {
+ title: 'Advanced',
+ icon: ,
+ data: [
+ { label: 'MQTT Enabled', value: formData.mqttEnabled ? 'Yes' : 'No' },
+ { label: 'TTN Enabled', value: formData.ttnEnabled ? 'Yes' : 'No' },
+ ],
+ },
+ ]
- return (
-
-
- {sections.map((section, index) => (
-
-
-
- {section.icon}
-
- {section.title}
-
-
-
- {section.data.map((item: any, idx: any) => (
-
- {item.label}:
-
- {item.value}
-
-
- ))}
-
-
-
- ))}
-
-
- );
+ return (
+
+
+ {sections.map((section, index) => (
+
+
+
+ {section.icon}
+
+ {section.title}
+
+
+
+ {section.data.map((item: any, idx: any) => (
+
+ {item.label}:
+
+ {item.value}
+
+
+ ))}
+
+
+
+ ))}
+
+
+ )
}
diff --git a/app/components/error-boundary.tsx b/app/components/error-boundary.tsx
index 7a5406116..81ac6eb30 100644
--- a/app/components/error-boundary.tsx
+++ b/app/components/error-boundary.tsx
@@ -1,40 +1,45 @@
-import React from "react";
-import { isRouteErrorResponse, useParams, useRouteError, type ErrorResponse } from "react-router";
-import { getErrorMessage } from "~/utils/misc";
+import React from 'react'
+import {
+ isRouteErrorResponse,
+ useParams,
+ useRouteError,
+ type ErrorResponse,
+} from 'react-router'
+import { getErrorMessage } from '~/utils/misc'
type StatusHandler = (info: {
- error: ErrorResponse;
- params: Record;
-}) => React.JSX.Element | null;
+ error: ErrorResponse
+ params: Record
+}) => React.JSX.Element | null
export function GeneralErrorBoundary({
- defaultStatusHandler = ({ error }) => (
-
- {error.status} {error.data}
-
- ),
- statusHandlers,
- unexpectedErrorHandler = (error) => {getErrorMessage(error)}
,
+ defaultStatusHandler = ({ error }) => (
+
+ {error.status} {error.data}
+
+ ),
+ statusHandlers,
+ unexpectedErrorHandler = (error) => {getErrorMessage(error)}
,
}: {
- defaultStatusHandler?: StatusHandler;
- statusHandlers?: Record;
- unexpectedErrorHandler?: (error: unknown) => React.JSX.Element | null;
+ defaultStatusHandler?: StatusHandler
+ statusHandlers?: Record
+ unexpectedErrorHandler?: (error: unknown) => React.JSX.Element | null
}) {
- const error = useRouteError();
- const params = useParams();
+ const error = useRouteError()
+ const params = useParams()
- if (typeof document !== "undefined") {
- console.error(error);
- }
+ if (typeof document !== 'undefined') {
+ console.error(error)
+ }
- return (
-
- {isRouteErrorResponse(error)
- ? (statusHandlers?.[error.status] ?? defaultStatusHandler)({
- error,
- params,
- })
- : unexpectedErrorHandler(error)}
-
- );
+ return (
+
+ {isRouteErrorResponse(error)
+ ? (statusHandlers?.[error.status] ?? defaultStatusHandler)({
+ error,
+ params,
+ })
+ : unexpectedErrorHandler(error)}
+
+ )
}
diff --git a/app/components/error-message.tsx b/app/components/error-message.tsx
index e8c1adbf0..266189246 100644
--- a/app/components/error-message.tsx
+++ b/app/components/error-message.tsx
@@ -1,29 +1,27 @@
-import { X } from "lucide-react";
-import { useNavigate } from "react-router";
-import { Alert, AlertDescription } from "./ui/alert";
+import { X } from 'lucide-react'
+import { useNavigate } from 'react-router'
+import { Alert, AlertDescription } from './ui/alert'
export default function ErrorMessage() {
- let navigate = useNavigate();
- const goBack = () => navigate(-1);
+ let navigate = useNavigate()
+ const goBack = () => navigate(-1)
- return (
-
-
- {
- void goBack();
- }}
- />
-
-
- Oh no, this shouldn't happen, but don't worry, our team is on the case!
-
-
-
- Add some info here.
-
-
-
- );
+ return (
+
+
+ {
+ void goBack()
+ }}
+ />
+
+
+ Oh no, this shouldn't happen, but don't worry, our team is on the case!
+
+
+ Add some info here.
+
+
+ )
}
diff --git a/app/components/header/download.tsx b/app/components/header/download.tsx
index 1f6a34188..f8d0c5aed 100644
--- a/app/components/header/download.tsx
+++ b/app/components/header/download.tsx
@@ -1,4 +1,4 @@
-import {type BBox } from 'geojson'
+import { type BBox } from 'geojson'
import { Download as DownloadIcon } from 'lucide-react'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
@@ -7,321 +7,421 @@ import { Form, useNavigation, useActionData } from 'react-router'
import { Button } from '../ui/button'
import { Checkbox } from '../ui/checkbox'
import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
} from '../ui/dialog'
import { Input } from '../ui/input'
import { Label } from '../ui/label'
import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
} from '../ui/select'
import { toast } from '../ui/use-toast'
// Custom Loading Animation Component
const PulsingDownloadAnimation = () => {
- const { t } = useTranslation('download')
- return (
-
-
- {/* Main download icon */}
-
-
- {/* Animated ripples */}
-
-
-
- {/* Small data points moving toward the download icon */}
-
-
-
-
-
{t('processingData')}
-
- );
-};
+ const { t } = useTranslation('download')
+ return (
+
+
+ {/* Main download icon */}
+
+
+ {/* Animated ripples */}
+
+
+
+ {/* Small data points moving toward the download icon */}
+
+
+
+
+
+ {t('processingData')}
+
+
+ )
+}
// Data Ready Animation
const DataReadyAnimation = () => {
- const { t } = useTranslation('download')
- return (
-
-
-
-
-
-
{t('readyToDownload')}
-
- );
-};
-
+ const { t } = useTranslation('download')
+ return (
+
+
+
+
+
+
{t('readyToDownload')}
+
+ )
+}
+
export default function Download(props: any) {
- const { t } = useTranslation('download')
- const actionData = useActionData()
- const navigation = useNavigation()
- const isLoading = navigation.state === "submitting"
- const devices = props.devices.features || []
- const { osem: mapRef } = useMap()
+ const { t } = useTranslation('download')
+ const actionData = useActionData()
+ const navigation = useNavigation()
+ const isLoading = navigation.state === 'submitting'
+ const devices = props.devices.features || []
+ const { osem: mapRef } = useMap()
- const [isDownloadReady, setIsDownloadReady] = useState(false)
- const [showReadyAnimation, setShowReadyAnimation] = useState(false)
- const [errorMessage, setErrorMessage] = useState(null)
-
- // Update download ready state when actionData changes
- useEffect(() => {
- if (actionData && actionData.error) {
- setErrorMessage(actionData.error)
- } else {
- setErrorMessage(null)
- // Only set download ready if there's no error
- if (actionData) {
- setIsDownloadReady(true)
- setShowReadyAnimation(true)
- }
- }
- }, [actionData])
+ const [isDownloadReady, setIsDownloadReady] = useState(false)
+ const [showReadyAnimation, setShowReadyAnimation] = useState(false)
+ const [errorMessage, setErrorMessage] = useState(null)
- // Reset download ready state when format changes
- const [format, setFormat] = useState('csv')
- const handleFormatChange = (value: string) => {
- setFormat(value)
- setShowReadyAnimation(false)
- setIsDownloadReady(false)
- setErrorMessage(null);
- }
+ // Update download ready state when actionData changes
+ useEffect(() => {
+ if (actionData && actionData.error) {
+ setErrorMessage(actionData.error)
+ } else {
+ setErrorMessage(null)
+ // Only set download ready if there's no error
+ if (actionData) {
+ setIsDownloadReady(true)
+ setShowReadyAnimation(true)
+ }
+ }
+ }, [actionData])
- const [downloadStarted, setDownloadStarted] = useState(false)
+ // Reset download ready state when format changes
+ const [format, setFormat] = useState('csv')
+ const handleFormatChange = (value: string) => {
+ setFormat(value)
+ setShowReadyAnimation(false)
+ setIsDownloadReady(false)
+ setErrorMessage(null)
+ }
-// Add this function to handle download start
-const handleDownloadStart = () => {
- const Delay = 3500;
- setDownloadStarted(true)
- setShowReadyAnimation(false)
- toast({
- description: t('toast'),
- duration: Delay,
- variant:"success"
- })
-
- // Reset the download started state after a delay
- setTimeout(() => {
- setDownloadStarted(false)
- setOpen(false)
- }, Delay)
-}
+ const [downloadStarted, setDownloadStarted] = useState(false)
+
+ // Add this function to handle download start
+ const handleDownloadStart = () => {
+ const Delay = 3500
+ setDownloadStarted(true)
+ setShowReadyAnimation(false)
+ toast({
+ description: t('toast'),
+ duration: Delay,
+ variant: 'success',
+ })
- // Filter devices inside the current bounds
- const bounds = mapRef?.getMap().getBounds().toArray().flat() as BBox ?? undefined;
- const devicesInBounds =
- bounds && bounds.length === 4
- ? devices.filter((device: any) => {
- // Ensure the device has coordinates
-
- if (!device.geometry || !device.geometry.coordinates) return false
+ // Reset the download started state after a delay
+ setTimeout(() => {
+ setDownloadStarted(false)
+ setOpen(false)
+ }, Delay)
+ }
- const [longitude, latitude] = device.geometry.coordinates
+ // Filter devices inside the current bounds
+ const bounds =
+ (mapRef?.getMap().getBounds().toArray().flat() as BBox) ?? undefined
+ const devicesInBounds =
+ bounds && bounds.length === 4
+ ? devices.filter((device: any) => {
+ // Ensure the device has coordinates
- // Check if bounds are defined properly
- const [minLon, minLat] = bounds.slice(0, 2) // [minLongitude, minLatitude]
- const [maxLon, maxLat] = bounds.slice(2, 4) // [maxLongitude, maxLatitude]
+ if (!device.geometry || !device.geometry.coordinates) return false
- return (
- longitude >= minLon &&
- longitude <= maxLon &&
- latitude >= minLat &&
- latitude <= maxLat
- )
- })
- : []
-
- let deviceIDs: Array = [];
- devicesInBounds.map((device: any) => {
- deviceIDs.push(device.properties.id);
- })
-
- const [aggregate, setAggregate] = useState('10m')
- const [fields, setFields] = useState({
- title: true,
- unit: true,
- value: true,
- timestamp: true,
- })
- const [open, setOpen] = useState(false)
- const handleFieldChange = (field: keyof typeof fields) => {
- setFields((prev) => ({ ...prev, [field]: !prev[field] }))
- setIsDownloadReady(false)
- setErrorMessage(null);
- setShowReadyAnimation(false);
- }
-
- return (
- {
- setOpen(!open);
- setIsDownloadReady(false);
- setErrorMessage(null);
- setShowReadyAnimation(false);}}>
- setOpen(true)}>
-
-
-
-
-
-
-
-
- {t('downloadOptions')}
-
- {t('downloadDescription')}
-
-
-
-
-
- )
-}
\ No newline at end of file
+ const [longitude, latitude] = device.geometry.coordinates
+
+ // Check if bounds are defined properly
+ const [minLon, minLat] = bounds.slice(0, 2) // [minLongitude, minLatitude]
+ const [maxLon, maxLat] = bounds.slice(2, 4) // [maxLongitude, maxLatitude]
+
+ return (
+ longitude >= minLon &&
+ longitude <= maxLon &&
+ latitude >= minLat &&
+ latitude <= maxLat
+ )
+ })
+ : []
+
+ let deviceIDs: Array = []
+ devicesInBounds.map((device: any) => {
+ deviceIDs.push(device.properties.id)
+ })
+
+ const [aggregate, setAggregate] = useState('10m')
+ const [fields, setFields] = useState({
+ title: true,
+ unit: true,
+ value: true,
+ timestamp: true,
+ })
+ const [open, setOpen] = useState(false)
+ const handleFieldChange = (field: keyof typeof fields) => {
+ setFields((prev) => ({ ...prev, [field]: !prev[field] }))
+ setIsDownloadReady(false)
+ setErrorMessage(null)
+ setShowReadyAnimation(false)
+ }
+
+ return (
+ {
+ setOpen(!open)
+ setIsDownloadReady(false)
+ setErrorMessage(null)
+ setShowReadyAnimation(false)
+ }}
+ >
+ setOpen(true)}
+ >
+
+
+
+
+
+
+
+
+ {t('downloadOptions')}
+ {t('downloadDescription')}
+
+
+
+
+ )
+}
diff --git a/app/components/header/home/index.tsx b/app/components/header/home/index.tsx
index d197fb694..80e597b2b 100644
--- a/app/components/header/home/index.tsx
+++ b/app/components/header/home/index.tsx
@@ -1,22 +1,22 @@
-import { Link } from "react-router";
+import { Link } from 'react-router'
export default function Home() {
- return (
-
-
-
-
-
-
-
-
-
- );
+ return (
+
+
+
+
+
+
+
+
+
+ )
}
diff --git a/app/components/header/index.tsx b/app/components/header/index.tsx
index 67b8fd6fd..d3ad676bc 100644
--- a/app/components/header/index.tsx
+++ b/app/components/header/index.tsx
@@ -1,30 +1,30 @@
-import LanguageSelector from "../landing/header/language-selector";
-import Download from "./download";
-import Home from "./home";
-import Menu from "./menu";
-import NavBar from "./nav-bar";
+import LanguageSelector from '../landing/header/language-selector'
+import Download from './download'
+import Home from './home'
+import Menu from './menu'
+import NavBar from './nav-bar'
// import { useLoaderData } from "@remix-run/react";
// import Notification from "./notification";
// import type { loader } from "~/routes/explore.$deviceId._index";
interface HeaderProps {
- devices: any;
+ devices: any
}
export default function Header(props: HeaderProps) {
- // const data = useLoaderData();
- return (
-
-
-
-
-
-
-
-
- {/* {data?.user?.email ?
: null} */}
-
-
-
- );
+ // const data = useLoaderData();
+ return (
+
+
+
+
+
+
+
+
+ {/* {data?.user?.email ?
: null} */}
+
+
+
+ )
}
diff --git a/app/components/header/menu/index.tsx b/app/components/header/menu/index.tsx
index 64389ff91..8c1fed44c 100644
--- a/app/components/header/menu/index.tsx
+++ b/app/components/header/menu/index.tsx
@@ -1,222 +1,228 @@
import {
- Globe,
- LogIn,
- LogOut,
- Puzzle,
- Menu as MenuIcon,
- FileLock2,
- Coins,
- User2,
- ExternalLink,
- Settings,
- Compass,
-} from "lucide-react";
-import { useState } from "react";
-import { useTranslation } from "react-i18next";
-import { Form, Link, useMatches, useNavigation, useSearchParams } from "react-router";
+ Globe,
+ LogIn,
+ LogOut,
+ Puzzle,
+ Menu as MenuIcon,
+ FileLock2,
+ Coins,
+ User2,
+ ExternalLink,
+ Settings,
+ Compass,
+} from 'lucide-react'
+import { useState } from 'react'
+import { useTranslation } from 'react-i18next'
import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuItem,
- DropdownMenuLabel,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu";
-import Spinner from "~/components/spinner";
-import { toast } from "~/components/ui/use-toast";
-import { useOptionalUser } from "~/utils";
+ Form,
+ Link,
+ useMatches,
+ useNavigation,
+ useSearchParams,
+} from 'react-router'
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu'
+import Spinner from '~/components/spinner'
+import { toast } from '~/components/ui/use-toast'
+import { useOptionalUser } from '~/utils'
export default function Menu() {
- const [searchParams] = useSearchParams();
- const redirectTo =
- searchParams.size > 0 ? "/explore?" + searchParams.toString() : "/explore";
- const [open, setOpen] = useState(false);
- const navigation = useNavigation();
- const isLoggingOut = Boolean(navigation.state === "submitting");
- const user = useOptionalUser();
- const matches = useMatches();
+ const [searchParams] = useSearchParams()
+ const redirectTo =
+ searchParams.size > 0 ? '/explore?' + searchParams.toString() : '/explore'
+ const [open, setOpen] = useState(false)
+ const navigation = useNavigation()
+ const isLoggingOut = Boolean(navigation.state === 'submitting')
+ const user = useOptionalUser()
+ const matches = useMatches()
- const { t } = useTranslation("menu");
+ const { t } = useTranslation('menu')
- return (
-
-
-
-
- {!user ? (
-
- ) : (
-
- )}
-
-
-
-
-
-
- {!user ? (
-
-
{t("title")}
-
- {t("subtitle")}
-
-
- ) : (
-
-
- {/* Max Mustermann */}
- {user?.name}
-
-
- {user?.email}
-
-
- )}
-
-
- {user && (
-
- {navigation.state === "loading" && (
-
-
-
- )}
- {!(matches[1].pathname === "/explore") && (
-
-
-
- {t("explore_label")}
-
-
- )}
- {!(matches[1].pathname === "/profile") && (
-
-
-
- {t("profile_label")}
-
-
- )}
+ return (
+
+
+
+
+ {!user ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+ {!user ? (
+
+
{t('title')}
+
+ {t('subtitle')}
+
+
+ ) : (
+
+
+ {/* Max Mustermann */}
+ {user?.name}
+
+
+ {user?.email}
+
+
+ )}
+
+
+ {user && (
+
+ {navigation.state === 'loading' && (
+
+
+
+ )}
+ {!(matches[1].pathname === '/explore') && (
+
+
+
+ {t('explore_label')}
+
+
+ )}
+ {!(matches[1].pathname === '/profile') && (
+
+
+
+ {t('profile_label')}
+
+
+ )}
- {!(matches[1].pathname === "/settings") && (
-
-
-
- {t("settings_label")}
-
-
-
- )}
-
- )}
-
-
-
-
- {t("tutorials_label")}
-
-
-
+ {!(matches[1].pathname === '/settings') && (
+
+
+
+ {t('settings_label')}
+
+
+
+ )}
+
+ )}
+
+
+
+
+ {t('tutorials_label')}
+
+
+
-
-
-
- {t("api_docs_label")}
-
-
-
-
-
-
-
-
-
- {t("data_protection_label")}
-
-
-
-
+
+
+
+ {t('api_docs_label')}
+
+
+
+
+
+
+
+
+
+ {t('data_protection_label')}
+
+
+
+
-
-
- e.preventDefault()}
- className="cursor-pointer"
- >
-
- {t("donate_label")}
-
-
-
-
+
+
+ e.preventDefault()}
+ className="cursor-pointer"
+ >
+
+ {t('donate_label')}
+
+
+
+
-
+
-
- {
- // Prevent dropdown from closing
- e.preventDefault();
- }}
- >
- {!user ? (
- setOpen(false)}
- className="cursor-pointer w-full"
- >
-
-
- {t("login_label")}
-
-
- ) : (
-
- )}
-
-
-
-
-
- );
+
+ {
+ // Prevent dropdown from closing
+ e.preventDefault()
+ }}
+ >
+ {!user ? (
+ setOpen(false)}
+ className="w-full cursor-pointer"
+ >
+
+
+ {t('login_label')}
+
+
+ ) : (
+
+ )}
+
+
+
+
+
+ )
}
diff --git a/app/components/header/menu/my-devices/index.tsx b/app/components/header/menu/my-devices/index.tsx
index ba8ec7e13..3978d38cd 100644
--- a/app/components/header/menu/my-devices/index.tsx
+++ b/app/components/header/menu/my-devices/index.tsx
@@ -1,32 +1,32 @@
import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogHeader,
- DialogTitle,
-} from "@/components/ui/dialog";
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+} from '@/components/ui/dialog'
interface MyDevicesDialogProps {
- isMyDevicesDialogOpen: boolean;
- setIsMyDevicesDialogOpen: (value: boolean) => void;
+ isMyDevicesDialogOpen: boolean
+ setIsMyDevicesDialogOpen: (value: boolean) => void
}
export default function MyDevicesDialog(props: MyDevicesDialogProps) {
- return (
-
-
-
-
- My Devices
-
- Here you can see all your devices.
-
-
-
-
-
- );
+ return (
+
+
+
+
+ My Devices
+
+ Here you can see all your devices.
+
+
+
+
+
+ )
}
diff --git a/app/components/header/menu/profile/index.tsx b/app/components/header/menu/profile/index.tsx
index 7ca83bbde..752a83cd4 100644
--- a/app/components/header/menu/profile/index.tsx
+++ b/app/components/header/menu/profile/index.tsx
@@ -1,32 +1,32 @@
import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogHeader,
- DialogTitle,
-} from "@/components/ui/dialog";
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+} from '@/components/ui/dialog'
interface ProfileDialogProps {
- isProfileDialogOpen: boolean;
- setIsProfileDialogOpen: (value: boolean) => void;
+ isProfileDialogOpen: boolean
+ setIsProfileDialogOpen: (value: boolean) => void
}
export default function ProfileDialog(props: ProfileDialogProps) {
- return (
-
-
-
-
- Profile
-
- Here you can edit your profile information.
-
-
-
-
-
- );
+ return (
+
+
+
+
+ Profile
+
+ Here you can edit your profile information.
+
+
+
+
+
+ )
}
diff --git a/app/components/header/menu/user-settings/index.tsx b/app/components/header/menu/user-settings/index.tsx
index bf34376ad..0ebdb3aa1 100644
--- a/app/components/header/menu/user-settings/index.tsx
+++ b/app/components/header/menu/user-settings/index.tsx
@@ -1,32 +1,32 @@
import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogHeader,
- DialogTitle,
-} from "@/components/ui/dialog";
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+} from '@/components/ui/dialog'
interface UserSettingsDialogProps {
- isSettingsDialogOpen: boolean;
- setIsSettingsDialogOpen: (value: boolean) => void;
+ isSettingsDialogOpen: boolean
+ setIsSettingsDialogOpen: (value: boolean) => void
}
export default function UserSettingsDialog(props: UserSettingsDialogProps) {
- return (
-
-
-
-
- Settings
-
- Here you can edit your user settings
-
-
-
-
-
- );
+ return (
+
+
+
+
+ Settings
+
+ Here you can edit your user settings
+
+
+
+
+
+ )
}
diff --git a/app/components/header/nav-bar/filter-options/filter-options.tsx b/app/components/header/nav-bar/filter-options/filter-options.tsx
index 96880cbf8..874850095 100644
--- a/app/components/header/nav-bar/filter-options/filter-options.tsx
+++ b/app/components/header/nav-bar/filter-options/filter-options.tsx
@@ -1,177 +1,171 @@
-import { X } from "lucide-react";
-import { useContext, useEffect, useState } from "react";
-import { useSearchParams, useNavigation } from "react-router";
-import { NavbarContext } from "..";
-import Spinner from "../../../spinner";
-import { Button } from "~/components/ui/button";
-import { Label } from "~/components/ui/label";
-import { ToggleGroup, ToggleGroupItem } from "~/components/ui/toggle-group";
-import { type DeviceExposureType, type DeviceStatusType } from "~/schema/enum";
+import { X } from 'lucide-react'
+import { useContext, useEffect, useState } from 'react'
+import { useSearchParams, useNavigation } from 'react-router'
+import { NavbarContext } from '..'
+import Spinner from '../../../spinner'
+import { Button } from '~/components/ui/button'
+import { Label } from '~/components/ui/label'
+import { ToggleGroup, ToggleGroupItem } from '~/components/ui/toggle-group'
+import { type DeviceExposureType, type DeviceStatusType } from '~/schema/enum'
export default function FilterOptions() {
- const { setOpen } = useContext(NavbarContext);
- const [searchParams, setSearchParams] = useSearchParams();
- const navigation = useNavigation();
+ const { setOpen } = useContext(NavbarContext)
+ const [searchParams, setSearchParams] = useSearchParams()
+ const navigation = useNavigation()
- const [exposureVal, setExposureVal] = useState(
- (searchParams.get("exposure") as DeviceExposureType) ?? "all",
- );
- const [statusVal, setStatusVal] = useState(
- (searchParams.get("status") as DeviceStatusType) ?? "all",
- );
+ const [exposureVal, setExposureVal] = useState(
+ (searchParams.get('exposure') as DeviceExposureType) ?? 'all',
+ )
+ const [statusVal, setStatusVal] = useState(
+ (searchParams.get('status') as DeviceStatusType) ?? 'all',
+ )
- const [tempExposureVal, setTempExposureVal] = useState(exposureVal);
- const [tempStatusVal, setTempStatusVal] = useState(statusVal);
- const [isChanged, setIsChanged] = useState(false);
+ const [tempExposureVal, setTempExposureVal] = useState(exposureVal)
+ const [tempStatusVal, setTempStatusVal] = useState(statusVal)
+ const [isChanged, setIsChanged] = useState(false)
- useEffect(() => {
- setExposureVal(
- (searchParams.get("exposure") as DeviceExposureType) ?? "all",
- );
- setStatusVal((searchParams.get("status") as DeviceStatusType) ?? "all");
- setTempExposureVal(
- (searchParams.get("exposure") as DeviceExposureType) ?? "all",
- );
- setTempStatusVal((searchParams.get("status") as DeviceStatusType) ?? "all");
- }, [searchParams]);
+ useEffect(() => {
+ setExposureVal(
+ (searchParams.get('exposure') as DeviceExposureType) ?? 'all',
+ )
+ setStatusVal((searchParams.get('status') as DeviceStatusType) ?? 'all')
+ setTempExposureVal(
+ (searchParams.get('exposure') as DeviceExposureType) ?? 'all',
+ )
+ setTempStatusVal((searchParams.get('status') as DeviceStatusType) ?? 'all')
+ }, [searchParams])
- useEffect(() => {
- setIsChanged(
- tempExposureVal !== exposureVal || tempStatusVal !== statusVal,
- );
- }, [tempExposureVal, tempStatusVal, exposureVal, statusVal]);
+ useEffect(() => {
+ setIsChanged(tempExposureVal !== exposureVal || tempStatusVal !== statusVal)
+ }, [tempExposureVal, tempStatusVal, exposureVal, statusVal])
- const handleApplyChanges = () => {
- setExposureVal(tempExposureVal);
- setStatusVal(tempStatusVal);
- searchParams.set("exposure", tempExposureVal);
- searchParams.set("status", tempStatusVal);
- setSearchParams(searchParams);
- setIsChanged(false);
- setOpen(false);
- };
+ const handleApplyChanges = () => {
+ setExposureVal(tempExposureVal)
+ setStatusVal(tempStatusVal)
+ searchParams.set('exposure', tempExposureVal)
+ searchParams.set('status', tempStatusVal)
+ setSearchParams(searchParams)
+ setIsChanged(false)
+ setOpen(false)
+ }
- const handleResetFilters = () => {
- setTempExposureVal("all");
- setTempStatusVal("all");
- searchParams.set("exposure", "all");
- searchParams.set("status", "all");
- setSearchParams(searchParams);
- setIsChanged(false);
- };
+ const handleResetFilters = () => {
+ setTempExposureVal('all')
+ setTempStatusVal('all')
+ searchParams.set('exposure', 'all')
+ searchParams.set('status', 'all')
+ setSearchParams(searchParams)
+ setIsChanged(false)
+ }
- return (
-
- {navigation.state === "loading" && (
-
-
-
- )}
-
-
- Exposure:
- {
- if (values.length === 0) {
- setTempExposureVal("all");
- } else if (
- values.includes("all") &&
- values.indexOf("all") === 0
- ) {
- const filteredValues = values.filter(
- (value) => value !== "all",
- );
- setTempExposureVal(
- filteredValues.join(",") as DeviceExposureType | "all",
- );
- } else if (values.includes("all")) {
- setTempExposureVal("all");
- } else {
- setTempExposureVal(
- values.join(",") as DeviceExposureType | "all",
- );
- }
- }}
- >
-
- all
-
-
- indoor
-
-
- outdoor
-
-
- mobile
-
-
-
-
- Status:
- {
- if (values.length === 0) {
- setTempStatusVal("all");
- } else if (
- values.includes("all") &&
- values.indexOf("all") === 0
- ) {
- const filteredValues = values.filter(
- (value) => value !== "all",
- );
- setTempStatusVal(
- filteredValues.join(",") as DeviceStatusType | "all",
- );
- } else if (values.includes("all")) {
- setTempStatusVal("all");
- } else {
- setTempStatusVal(values.join(",") as DeviceStatusType | "all");
- }
- }}
- >
-
- all
-
-
- active
-
-
- inactive
-
-
- old
-
-
-
-
-
-
-
- Reset
-
-
-
- Apply
-
-
-
- );
+ return (
+
+ {navigation.state === 'loading' && (
+
+
+
+ )}
+
+
+ Exposure:
+ {
+ if (values.length === 0) {
+ setTempExposureVal('all')
+ } else if (
+ values.includes('all') &&
+ values.indexOf('all') === 0
+ ) {
+ const filteredValues = values.filter((value) => value !== 'all')
+ setTempExposureVal(
+ filteredValues.join(',') as DeviceExposureType | 'all',
+ )
+ } else if (values.includes('all')) {
+ setTempExposureVal('all')
+ } else {
+ setTempExposureVal(
+ values.join(',') as DeviceExposureType | 'all',
+ )
+ }
+ }}
+ >
+
+ all
+
+
+ indoor
+
+
+ outdoor
+
+
+ mobile
+
+
+
+
+ Status:
+ {
+ if (values.length === 0) {
+ setTempStatusVal('all')
+ } else if (
+ values.includes('all') &&
+ values.indexOf('all') === 0
+ ) {
+ const filteredValues = values.filter((value) => value !== 'all')
+ setTempStatusVal(
+ filteredValues.join(',') as DeviceStatusType | 'all',
+ )
+ } else if (values.includes('all')) {
+ setTempStatusVal('all')
+ } else {
+ setTempStatusVal(values.join(',') as DeviceStatusType | 'all')
+ }
+ }}
+ >
+
+ all
+
+
+ active
+
+
+ inactive
+
+
+ old
+
+
+
+
+
+
+
+ Reset
+
+
+
+ Apply
+
+
+
+ )
}
diff --git a/app/components/header/nav-bar/filter-options/filter-tags.tsx b/app/components/header/nav-bar/filter-options/filter-tags.tsx
index 7ff70dd42..ca42bfb30 100644
--- a/app/components/header/nav-bar/filter-options/filter-tags.tsx
+++ b/app/components/header/nav-bar/filter-options/filter-tags.tsx
@@ -1,130 +1,128 @@
-import { Plus, X } from "lucide-react";
-import { useContext, useEffect, useState } from "react";
-import { useNavigation, useSearchParams } from "react-router";
-import { NavbarContext } from "..";
-import Spinner from "~/components/spinner";
-import { Badge } from "~/components/ui/badge";
-import { Button } from "~/components/ui/button";
-import { Input } from "~/components/ui/input";
-import { Label } from "~/components/ui/label";
+import { Plus, X } from 'lucide-react'
+import { useContext, useEffect, useState } from 'react'
+import { useNavigation, useSearchParams } from 'react-router'
+import { NavbarContext } from '..'
+import Spinner from '~/components/spinner'
+import { Badge } from '~/components/ui/badge'
+import { Button } from '~/components/ui/button'
+import { Input } from '~/components/ui/input'
+import { Label } from '~/components/ui/label'
export default function FilterTags() {
- const { setOpen } = useContext(NavbarContext);
- const [searchParams, setSearchParams] = useSearchParams();
- const navigation = useNavigation();
+ const { setOpen } = useContext(NavbarContext)
+ const [searchParams, setSearchParams] = useSearchParams()
+ const navigation = useNavigation()
- const [tags, setTags] = useState(
- searchParams.getAll("tags").flatMap((t) => t.split(",")),
- );
- const [newTag, setNewTag] = useState("");
- const [isChanged, setIsChanged] = useState(false);
+ const [tags, setTags] = useState(
+ searchParams.getAll('tags').flatMap((t) => t.split(',')),
+ )
+ const [newTag, setNewTag] = useState('')
+ const [isChanged, setIsChanged] = useState(false)
- useEffect(() => {
- setTags(searchParams.getAll("tags").flatMap((t) => t.split(",")));
- }, [searchParams]);
+ useEffect(() => {
+ setTags(searchParams.getAll('tags').flatMap((t) => t.split(',')))
+ }, [searchParams])
- useEffect(() => {
- const currentTags = searchParams
- .getAll("tags")
- .flatMap((t) => t.split(","));
- setIsChanged(JSON.stringify(tags) !== JSON.stringify(currentTags));
- }, [tags, searchParams]);
+ useEffect(() => {
+ const currentTags = searchParams.getAll('tags').flatMap((t) => t.split(','))
+ setIsChanged(JSON.stringify(tags) !== JSON.stringify(currentTags))
+ }, [tags, searchParams])
- const handleApplyChanges = () => {
- searchParams.delete("tags");
- if (tags.length > 0) {
- // Join all tags into a single string separated by commas
- searchParams.set("tags", tags.join(","));
- }
- setSearchParams(searchParams);
- setIsChanged(false);
- setOpen(false);
- };
+ const handleApplyChanges = () => {
+ searchParams.delete('tags')
+ if (tags.length > 0) {
+ // Join all tags into a single string separated by commas
+ searchParams.set('tags', tags.join(','))
+ }
+ setSearchParams(searchParams)
+ setIsChanged(false)
+ setOpen(false)
+ }
- const handleResetFilters = () => {
- setTags([]);
- searchParams.delete("tags");
- setSearchParams(searchParams);
- setIsChanged(false);
- };
+ const handleResetFilters = () => {
+ setTags([])
+ searchParams.delete('tags')
+ setSearchParams(searchParams)
+ setIsChanged(false)
+ }
- const handleAddTag = () => {
- if (newTag && !tags.includes(newTag)) {
- setTags([...tags, newTag]);
- setNewTag("");
- }
- };
+ const handleAddTag = () => {
+ if (newTag && !tags.includes(newTag)) {
+ setTags([...tags, newTag])
+ setNewTag('')
+ }
+ }
- const handleRemoveTag = (tagToRemove: string) => {
- setTags(tags.filter((tag) => tag !== tagToRemove));
- };
+ const handleRemoveTag = (tagToRemove: string) => {
+ setTags(tags.filter((tag) => tag !== tagToRemove))
+ }
- return (
-
- {navigation.state === "loading" && (
-
-
-
- )}
-
-
- Tags:
-
-
-
setNewTag(e.target.value)}
- onKeyPress={(e) => {
- if (e.key === "Enter") {
- e.preventDefault();
- handleAddTag();
- }
- }}
- />
-
-
-
-
-
- {tags.map((tag, index) => (
-
- {tag}
- handleRemoveTag(tag)}
- className="ml-2 text-xs"
- aria-label={`Remove ${tag} tag`}
- >
-
-
-
- ))}
-
-
-
-
-
- Reset
-
-
-
- Apply
-
-
-
- );
+ return (
+
+ {navigation.state === 'loading' && (
+
+
+
+ )}
+
+
+ Tags:
+
+
+
setNewTag(e.target.value)}
+ onKeyPress={(e) => {
+ if (e.key === 'Enter') {
+ e.preventDefault()
+ handleAddTag()
+ }
+ }}
+ />
+
+
+
+
+
+ {tags.map((tag, index) => (
+
+ {tag}
+ handleRemoveTag(tag)}
+ className="ml-2 text-xs"
+ aria-label={`Remove ${tag} tag`}
+ >
+
+
+
+ ))}
+
+
+
+
+
+ Reset
+
+
+
+ Apply
+
+
+
+ )
}
diff --git a/app/components/header/nav-bar/index.tsx b/app/components/header/nav-bar/index.tsx
index c75dd2063..ec9d94ffc 100644
--- a/app/components/header/nav-bar/index.tsx
+++ b/app/components/header/nav-bar/index.tsx
@@ -1,119 +1,119 @@
-import { useMediaQuery } from "@mantine/hooks";
-import { AnimatePresence, motion } from "framer-motion";
-import { SearchIcon, XIcon } from "lucide-react";
-import { useState, useEffect, useRef, createContext } from "react";
-import { useTranslation } from "react-i18next";
-import { useMap } from "react-map-gl";
-import NavbarHandler from "./nav-bar-handler";
-import FilterVisualization from "~/components/map/filter-visualization";
-import { type Device } from "~/schema";
+import { useMediaQuery } from '@mantine/hooks'
+import { AnimatePresence, motion } from 'framer-motion'
+import { SearchIcon, XIcon } from 'lucide-react'
+import { useState, useEffect, useRef, createContext } from 'react'
+import { useTranslation } from 'react-i18next'
+import { useMap } from 'react-map-gl'
+import NavbarHandler from './nav-bar-handler'
+import FilterVisualization from '~/components/map/filter-visualization'
+import { type Device } from '~/schema'
interface NavBarProps {
- devices: Device[];
+ devices: Device[]
}
export const NavbarContext = createContext({
- open: false,
- setOpen: (_open: boolean) => {},
-});
+ open: false,
+ setOpen: (_open: boolean) => {},
+})
export default function NavBar(props: NavBarProps) {
- const [open, setOpen] = useState(false);
- const inputRef = useRef(null);
- const [searchString, setSearchString] = useState("");
+ const [open, setOpen] = useState(false)
+ const inputRef = useRef(null)
+ const [searchString, setSearchString] = useState('')
- const { osem: mapRef } = useMap();
+ const { osem: mapRef } = useMap()
- const { t } = useTranslation("search");
+ const { t } = useTranslation('search')
- useEffect(() => {
- if (mapRef) {
- mapRef.on("click", () => setOpen(false));
- }
- }, [mapRef]);
+ useEffect(() => {
+ if (mapRef) {
+ mapRef.on('click', () => setOpen(false))
+ }
+ }, [mapRef])
- // register keyboard shortcuts
- useEffect(() => {
- const down = (e: KeyboardEvent) => {
- if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
- e.preventDefault();
- setOpen((prevState) => !prevState);
- }
- if (e.key === "Escape") {
- e.preventDefault();
- setOpen(false);
- }
- };
- document.addEventListener("keydown", down);
- return () => document.removeEventListener("keydown", down);
- }, []);
+ // register keyboard shortcuts
+ useEffect(() => {
+ const down = (e: KeyboardEvent) => {
+ if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
+ e.preventDefault()
+ setOpen((prevState) => !prevState)
+ }
+ if (e.key === 'Escape') {
+ e.preventDefault()
+ setOpen(false)
+ }
+ }
+ document.addEventListener('keydown', down)
+ return () => document.removeEventListener('keydown', down)
+ }, [])
- // focus input when opening
- useEffect(() => {
- if (open) {
- inputRef.current?.focus();
- } else {
- inputRef.current?.blur();
- setSearchString("");
- }
- }, [open]);
+ // focus input when opening
+ useEffect(() => {
+ if (open) {
+ inputRef.current?.focus()
+ } else {
+ inputRef.current?.blur()
+ setSearchString('')
+ }
+ }, [open])
- const isDesktop = useMediaQuery("(min-width: 768px)");
+ const isDesktop = useMediaQuery('(min-width: 768px)')
- return (
-
-
-
-
-
- setOpen(true)}
- onChange={(e) => setSearchString(e.target.value)}
- className="h-fit w-full flex-1 border-none focus:border-none bg-white focus:outline-none focus:ring-0 dark:bg-zinc-800 dark:text-zinc-200"
- value={searchString}
- />
- {!open && (
-
- ctrl + K
-
- )}
- {open && (
- {
- setSearchString("");
- setOpen(false);
- inputRef.current?.blur();
- }}
- className="h-6"
- />
- )}
-
-
-
- {open && (
-
-
-
- )}
-
-
-
- {!open && isDesktop && (
-
-
-
- )}
-
-
- );
+ return (
+
+
+
+
+
+ setOpen(true)}
+ onChange={(e) => setSearchString(e.target.value)}
+ className="h-fit w-full flex-1 border-none bg-white focus:border-none focus:outline-none focus:ring-0 dark:bg-zinc-800 dark:text-zinc-200"
+ value={searchString}
+ />
+ {!open && (
+
+ ctrl + K
+
+ )}
+ {open && (
+ {
+ setSearchString('')
+ setOpen(false)
+ inputRef.current?.blur()
+ }}
+ className="h-6"
+ />
+ )}
+
+
+
+ {open && (
+
+
+
+ )}
+
+
+
+ {!open && isDesktop && (
+
+
+
+ )}
+
+
+ )
}
diff --git a/app/components/header/nav-bar/nav-bar-handler.tsx b/app/components/header/nav-bar/nav-bar-handler.tsx
index 62d866121..553ad0277 100644
--- a/app/components/header/nav-bar/nav-bar-handler.tsx
+++ b/app/components/header/nav-bar/nav-bar-handler.tsx
@@ -1,81 +1,81 @@
-import { Clock4Icon, Filter, IceCream2Icon, Tag } from "lucide-react";
-import FilterOptions from "./filter-options/filter-options";
+import { Clock4Icon, Filter, IceCream2Icon, Tag } from 'lucide-react'
+import FilterOptions from './filter-options/filter-options'
// import { PhenomenonSelect } from "./phenomenon-select/phenomenon-select";
-import FilterTags from "./filter-options/filter-tags";
-import useKeyboardNav from "./use-keyboard-nav";
-import Search from "~/components/search";
-import { cn } from "~/lib/utils";
-import { type Device } from "~/schema";
+import FilterTags from './filter-options/filter-tags'
+import useKeyboardNav from './use-keyboard-nav'
+import Search from '~/components/search'
+import { cn } from '~/lib/utils'
+import { type Device } from '~/schema'
interface NavBarHandlerProps {
- devices: Device[];
- searchString: string;
+ devices: Device[]
+ searchString: string
}
function getSections() {
- return [
- {
- title: "Filter",
- icon: Filter,
- color: "bg-blue-100",
- component: ,
- },
- {
- title: "Tags",
- icon: Tag,
- color: "bg-light-green",
- component: ,
- },
- {
- title: "Date & Time",
- icon: Clock4Icon,
- color: "bg-gray-300",
- component: Coming soon...
,
- },
- {
- title: "Phänomen",
- icon: IceCream2Icon,
- color: "bg-slate-500",
- component: Coming soon...
// ,
- },
- ];
+ return [
+ {
+ title: 'Filter',
+ icon: Filter,
+ color: 'bg-blue-100',
+ component: ,
+ },
+ {
+ title: 'Tags',
+ icon: Tag,
+ color: 'bg-light-green',
+ component: ,
+ },
+ {
+ title: 'Date & Time',
+ icon: Clock4Icon,
+ color: 'bg-gray-300',
+ component: Coming soon...
,
+ },
+ {
+ title: 'Phänomen',
+ icon: IceCream2Icon,
+ color: 'bg-slate-500',
+ component: Coming soon...
, // ,
+ },
+ ]
}
export default function NavbarHandler({
- devices,
- searchString,
+ devices,
+ searchString,
}: NavBarHandlerProps) {
- const sections = getSections();
+ const sections = getSections()
- const { cursor, setCursor } = useKeyboardNav(0, 0, sections.length);
+ const { cursor, setCursor } = useKeyboardNav(0, 0, sections.length)
- if (searchString.length >= 2) {
- return ;
- }
+ if (searchString.length >= 2) {
+ return
+ }
- return (
-
-
- {sections.map((section, index) => (
-
{
- setCursor(index);
- }}
- >
-
- {section.title}
-
- ))}
-
-
{sections[cursor].component}
-
- );
+ return (
+
+
+ {sections.map((section, index) => (
+
{
+ setCursor(index)
+ }}
+ >
+
+ {section.title}
+
+ ))}
+
+
{sections[cursor].component}
+
+ )
}
diff --git a/app/components/header/nav-bar/phenomenon-select/phenomenon-select.tsx b/app/components/header/nav-bar/phenomenon-select/phenomenon-select.tsx
index de38bafe7..7a5e9ef2a 100644
--- a/app/components/header/nav-bar/phenomenon-select/phenomenon-select.tsx
+++ b/app/components/header/nav-bar/phenomenon-select/phenomenon-select.tsx
@@ -1,56 +1,59 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
-import { X } from "lucide-react";
+import { X } from 'lucide-react'
// import { useTranslation } from "react-i18next";
-import { useState, useEffect, type Key } from "react"; // Changed import
-import { useLoaderData, useNavigation, useSearchParams } from "react-router";
-import Spinner from "~/components/spinner";
-import { Button } from "~/components/ui/button";
-import { Checkbox } from "~/components/ui/checkbox";
-import { Label } from "~/components/ui/label";
-import { ScrollArea } from "~/components/ui/scroll-area";
-import { type loader } from "~/routes/explore";
-import { sensorWikiLabel, type SensorWikiLabel } from "~/utils/sensor-wiki-helper";
+import { useState, useEffect, type Key } from 'react' // Changed import
+import { useLoaderData, useNavigation, useSearchParams } from 'react-router'
+import Spinner from '~/components/spinner'
+import { Button } from '~/components/ui/button'
+import { Checkbox } from '~/components/ui/checkbox'
+import { Label } from '~/components/ui/label'
+import { ScrollArea } from '~/components/ui/scroll-area'
+import { type loader } from '~/routes/explore'
+import {
+ sensorWikiLabel,
+ type SensorWikiLabel,
+} from '~/utils/sensor-wiki-helper'
export function PhenomenonSelect() {
- const data = useLoaderData();
- // const { t } = useTranslation("navbar");
- const loaderData = useLoaderData();
- const navigation = useNavigation();
- const [searchParams, setSearchParams] = useSearchParams();
- const [selectedCheckboxes, setSelectedCheckboxes] = useState([]); // Added state for selected checkboxes
+ const data = useLoaderData()
+ // const { t } = useTranslation("navbar");
+ const loaderData = useLoaderData()
+ const navigation = useNavigation()
+ const [searchParams, setSearchParams] = useSearchParams()
+ const [selectedCheckboxes, setSelectedCheckboxes] = useState([]) // Added state for selected checkboxes
- useEffect(() => {
- // When URL parameters change, update selected checkboxes accordingly
- const phenomenonParam = searchParams.get("phenomenon") || "all";
- const phenomenonArray =
- phenomenonParam === "all" ? [] : phenomenonParam.split(",");
- setSelectedCheckboxes(phenomenonArray);
- }, [searchParams]);
+ useEffect(() => {
+ // When URL parameters change, update selected checkboxes accordingly
+ const phenomenonParam = searchParams.get('phenomenon') || 'all'
+ const phenomenonArray =
+ phenomenonParam === 'all' ? [] : phenomenonParam.split(',')
+ setSelectedCheckboxes(phenomenonArray)
+ }, [searchParams])
- const handleCheckboxChange = (slug: string) => {
- const updatedCheckboxes = selectedCheckboxes.includes(slug)
- ? selectedCheckboxes.filter((checkbox) => checkbox !== slug)
- : [...selectedCheckboxes, slug];
+ const handleCheckboxChange = (slug: string) => {
+ const updatedCheckboxes = selectedCheckboxes.includes(slug)
+ ? selectedCheckboxes.filter((checkbox) => checkbox !== slug)
+ : [...selectedCheckboxes, slug]
- setSelectedCheckboxes(updatedCheckboxes);
+ setSelectedCheckboxes(updatedCheckboxes)
- // Update URL search parameters
- if (updatedCheckboxes.length === 0) {
- searchParams.delete("phenomenon");
- } else {
- searchParams.set("phenomenon", updatedCheckboxes.join(","));
- }
- setSearchParams(searchParams);
- };
+ // Update URL search parameters
+ if (updatedCheckboxes.length === 0) {
+ searchParams.delete('phenomenon')
+ } else {
+ searchParams.set('phenomenon', updatedCheckboxes.join(','))
+ }
+ setSearchParams(searchParams)
+ }
- return (
-
- {navigation.state === "loading" && (
-
-
-
- )}
- {/*
+ return (
+
+ {navigation.state === 'loading' && (
+
+
+
+ )}
+ {/*
{loaderData.phenomena.map(
(
@@ -91,7 +94,7 @@ export function PhenomenonSelect() {
*/}
- Coming soon
-
- );
+ Coming soon
+
+ )
}
diff --git a/app/components/header/nav-bar/time-filter/index.tsx b/app/components/header/nav-bar/time-filter/index.tsx
index e7a2756fd..15a5918b0 100644
--- a/app/components/header/nav-bar/time-filter/index.tsx
+++ b/app/components/header/nav-bar/time-filter/index.tsx
@@ -1,426 +1,425 @@
-"use client";
+'use client'
-import { CalendarIcon } from "@heroicons/react/24/outline";
-import { format } from "date-fns";
-import { de, enGB } from "date-fns/locale";
-import { getUserLocale } from "get-user-locale";
-import { Clock, CalendarSearch, CalendarClock } from "lucide-react";
-import * as React from "react";
+import { CalendarIcon } from '@heroicons/react/24/outline'
+import { format } from 'date-fns'
+import { de, enGB } from 'date-fns/locale'
+import { getUserLocale } from 'get-user-locale'
+import { Clock, CalendarSearch, CalendarClock } from 'lucide-react'
+import * as React from 'react'
// import { useSearchParams, useSubmit } from "@remix-run/react";
-import { type DateRange } from "react-day-picker";
-
-import { useTranslation } from "react-i18next";
-import { Form } from "react-router";
-import { Button } from "@/components/ui/button";
-import { Calendar } from "@/components/ui/calendar";
-import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
-import { useToast } from "@/components/ui/use-toast";
-import { cn } from "@/lib/utils";
+import { type DateRange } from 'react-day-picker'
+import { useTranslation } from 'react-i18next'
+import { Form } from 'react-router'
+import { Button } from '@/components/ui/button'
+import { Calendar } from '@/components/ui/calendar'
+import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog'
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
+import { useToast } from '@/components/ui/use-toast'
+import { cn } from '@/lib/utils'
interface TimeFilterProps {
- className?: React.HTMLAttributes["className"];
+ className?: React.HTMLAttributes['className']
- dateRange: DateRange | undefined;
- setDateRange: (date: DateRange | undefined) => void;
+ dateRange: DateRange | undefined
+ setDateRange: (date: DateRange | undefined) => void
- singleDate: Date | undefined;
- setSingleDate: (date: Date | undefined) => void;
+ singleDate: Date | undefined
+ setSingleDate: (date: Date | undefined) => void
- isDialogOpen: boolean;
- setIsDialogOpen: (open: boolean) => void;
+ isDialogOpen: boolean
+ setIsDialogOpen: (open: boolean) => void
- setIsHovered: (hovered: boolean) => void;
+ setIsHovered: (hovered: boolean) => void
- timeState: string | undefined;
- setTimeState: (value: string) => void;
+ timeState: string | undefined
+ setTimeState: (value: string) => void
- onChange: (timerange: any) => void;
- value: any;
+ onChange: (timerange: any) => void
+ value: any
}
export function TimeFilter(props: TimeFilterProps) {
- // const submit = useSubmit();
- // const [searchParams] = useSearchParams();
- const { toast } = useToast();
+ // const submit = useSubmit();
+ // const [searchParams] = useSearchParams();
+ const { toast } = useToast()
- const { t } = useTranslation("navbar");
- const userLocaleString = getUserLocale();
- const userLocale = userLocaleString === "de" ? de : enGB;
+ const { t } = useTranslation('navbar')
+ const userLocaleString = getUserLocale()
+ const userLocale = userLocaleString === 'de' ? de : enGB
- const today = new Date();
+ const today = new Date()
- return (
-
-
-
- props.setIsDialogOpen(true)}
- >
-
- {props.timeState === "live" ? (
- Live
- ) : props.timeState === "pointintime" ? (
- props.singleDate ? (
- <>
- {format(
- props.singleDate,
- userLocaleString === "de" ? "dd/MM/yyyy" : "MM/dd/yyyy"
- )}
- >
- ) : (
- t("date_picker_label")
- )
- ) : props.timeState === "timeperiod" ? (
- props.dateRange?.from ? (
- props.dateRange.to ? (
- <>
- {format(
- props.dateRange.from,
- userLocaleString === "de" ? "dd/MM/yyyy" : "MM/dd/yyyy"
- )}{" "}
- -{" "}
- {format(
- props.dateRange.to,
- userLocaleString === "de" ? "dd/MM/yyyy" : "MM/dd/yyyy"
- )}
- >
- ) : (
- format(
- props.dateRange.from,
- userLocaleString === "de" ? "dd/MM/yyyy" : "MM/dd/yyyy"
- )
- )
- ) : (
- t("date_range_picker_label")
- )
- ) : null}
-
-
- props.setIsHovered(false)}
- >
-
-
-
-
- {t("live_label")}
-
-
-
- {t("pointintime_label")}
-
-
-
- {t("timeperiod_label")}
-
-
-
-
- {t("live_description")}
-
-
+
+
+
+
+
+ )
}
diff --git a/app/components/header/nav-bar/time-filter/time-filter.tsx b/app/components/header/nav-bar/time-filter/time-filter.tsx
index 00198d97b..d2b3fd7f6 100644
--- a/app/components/header/nav-bar/time-filter/time-filter.tsx
+++ b/app/components/header/nav-bar/time-filter/time-filter.tsx
@@ -1,363 +1,362 @@
-"use client";
+'use client'
-import { de, enGB } from "date-fns/locale";
-import { getUserLocale } from "get-user-locale";
-import { Clock, CalendarSearch, CalendarClock } from "lucide-react";
-import * as React from "react";
+import { de, enGB } from 'date-fns/locale'
+import { getUserLocale } from 'get-user-locale'
+import { Clock, CalendarSearch, CalendarClock } from 'lucide-react'
+import * as React from 'react'
// import { useSearchParams, useSubmit } from "@remix-run/react";
-import { type DateRange } from "react-day-picker";
-
-import { useTranslation } from "react-i18next";
-import { Form } from "react-router";
-import { Button } from "@/components/ui/button";
-import { Calendar } from "@/components/ui/calendar";
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
-import { useToast } from "@/components/ui/use-toast";
+import { type DateRange } from 'react-day-picker'
+import { useTranslation } from 'react-i18next'
+import { Form } from 'react-router'
+import { Button } from '@/components/ui/button'
+import { Calendar } from '@/components/ui/calendar'
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
+import { useToast } from '@/components/ui/use-toast'
interface TimeFilterProps {
- className?: React.HTMLAttributes["className"];
+ className?: React.HTMLAttributes['className']
- dateRange: DateRange | undefined;
- setDateRange: (date: DateRange | undefined) => void;
+ dateRange: DateRange | undefined
+ setDateRange: (date: DateRange | undefined) => void
- singleDate: Date | undefined;
- setSingleDate: (date: Date | undefined) => void;
+ singleDate: Date | undefined
+ setSingleDate: (date: Date | undefined) => void
- isDialogOpen: boolean;
- setIsDialogOpen: (open: boolean) => void;
+ isDialogOpen: boolean
+ setIsDialogOpen: (open: boolean) => void
- setIsHovered: (hovered: boolean) => void;
+ setIsHovered: (hovered: boolean) => void
- timeState: string;
- setTimeState: (value: string) => void;
+ timeState: string
+ setTimeState: (value: string) => void
- onChange: (timerange: any) => void;
- value: any;
+ onChange: (timerange: any) => void
+ value: any
}
export function TimeFilter(props: TimeFilterProps) {
- // const submit = useSubmit();
- // const [searchParams] = useSearchParams();
- const { toast } = useToast();
+ // const submit = useSubmit();
+ // const [searchParams] = useSearchParams();
+ const { toast } = useToast()
- const { t } = useTranslation("navbar");
- const userLocaleString = getUserLocale();
- const userLocale = userLocaleString === "de" ? de : enGB;
+ const { t } = useTranslation('navbar')
+ const userLocaleString = getUserLocale()
+ const userLocale = userLocaleString === 'de' ? de : enGB
- const today = new Date();
+ const today = new Date()
- return (
-
-
-
-
- {t("live_label")}
-
-
-
- {t("pointintime_label")}
-
-
-
- {t("timeperiod_label")}
-
-
-
-
- {t("live_description")}
-
-
-
{
- props.setTimeState("live");
- props.setIsDialogOpen(false);
- }}
- >
-
-
+ return (
+
+
+
+
+ {t('live_label')}
+
+
+
+ {t('pointintime_label')}
+
+
+
+ {t('timeperiod_label')}
+
+
+
+
+ {t('live_description')}
+
+
+
{
+ props.setTimeState('live')
+ props.setIsDialogOpen(false)
+ }}
+ >
+
+
-
- {t("button")}
-
-
-
-
-
-
- {t("pointintime_description")}
-
-
- {props.singleDate === undefined ? (
-
- {t("date_picker_label")}
-
- ) : (
-
-
- {props.singleDate?.getDate() < 10
- ? "0" + props.singleDate?.getDate()
- : props.singleDate?.getDate()}
-
-
-
- {new Intl.DateTimeFormat(
- userLocaleString === "de" ? "de" : "en-GB",
- { month: "long" }
- ).format(props.singleDate)}{" "}
- {props.singleDate?.getFullYear()}
-
-
- {new Intl.DateTimeFormat(
- userLocaleString === "de" ? "de" : "en-GB",
- { weekday: "long" }
- ).format(props.singleDate)}
-
-
-
- )}
-
-
- {
- props.setSingleDate(value);
- }}
- locale={userLocale}
- className="mx-auto"
- disabled={{ after: today }}
- toMonth={today}
- />
-
-
- {
- props.setSingleDate(new Date());
- }}
- >
- Today
-
- {
- props.setSingleDate(undefined);
- }}
- >
- Clear
-
-
-
-
{
- if (props.singleDate === undefined) {
- e.preventDefault();
- toast({
- description: "Please select a date",
- });
- } else {
- props.setTimeState("pointintime");
- props.setIsDialogOpen(false);
- }
- }}
- >
-
-
+
+ {t('button')}
+
+
+
+
+
+
+ {t('pointintime_description')}
+
+
+ {props.singleDate === undefined ? (
+
+ {t('date_picker_label')}
+
+ ) : (
+
+
+ {props.singleDate?.getDate() < 10
+ ? '0' + props.singleDate?.getDate()
+ : props.singleDate?.getDate()}
+
+
+
+ {new Intl.DateTimeFormat(
+ userLocaleString === 'de' ? 'de' : 'en-GB',
+ { month: 'long' },
+ ).format(props.singleDate)}{' '}
+ {props.singleDate?.getFullYear()}
+
+
+ {new Intl.DateTimeFormat(
+ userLocaleString === 'de' ? 'de' : 'en-GB',
+ { weekday: 'long' },
+ ).format(props.singleDate)}
+
+
+
+ )}
+
+
+ {
+ props.setSingleDate(value)
+ }}
+ locale={userLocale}
+ className="mx-auto"
+ disabled={{ after: today }}
+ toMonth={today}
+ />
+
+
+ {
+ props.setSingleDate(new Date())
+ }}
+ >
+ Today
+
+ {
+ props.setSingleDate(undefined)
+ }}
+ >
+ Clear
+
+
+
+
{
+ if (props.singleDate === undefined) {
+ e.preventDefault()
+ toast({
+ description: 'Please select a date',
+ })
+ } else {
+ props.setTimeState('pointintime')
+ props.setIsDialogOpen(false)
+ }
+ }}
+ >
+
+
-
-
+
+
-
- {t("button")}
-
-
-
-
-
-
- {t("timeperiod_description")}
-
-
- {props.dateRange === undefined ||
- props.dateRange.from === undefined ? (
-
- {t("date_range_picker_label")}
-
- ) : (
-
-
-
- {props.dateRange?.from?.getDate() < 10
- ? "0" + props.dateRange.from?.getDate()
- : props.dateRange.from?.getDate()}
-
-
-
- {new Intl.DateTimeFormat(
- userLocaleString === "de" ? "de" : "en-GB",
- { month: "long" }
- ).format(props.dateRange.from)}{" "}
- {props.dateRange.from?.getFullYear()}
-
-
- {new Intl.DateTimeFormat(
- userLocaleString === "de" ? "de" : "en-GB",
- { weekday: "long" }
- ).format(props.dateRange.from)}
-
-
-
+
+ {t('button')}
+
+
+
+
+
+
+ {t('timeperiod_description')}
+
+
+ {props.dateRange === undefined ||
+ props.dateRange.from === undefined ? (
+
+ {t('date_range_picker_label')}
+
+ ) : (
+
+
+
+ {props.dateRange?.from?.getDate() < 10
+ ? '0' + props.dateRange.from?.getDate()
+ : props.dateRange.from?.getDate()}
+
+
+
+ {new Intl.DateTimeFormat(
+ userLocaleString === 'de' ? 'de' : 'en-GB',
+ { month: 'long' },
+ ).format(props.dateRange.from)}{' '}
+ {props.dateRange.from?.getFullYear()}
+
+
+ {new Intl.DateTimeFormat(
+ userLocaleString === 'de' ? 'de' : 'en-GB',
+ { weekday: 'long' },
+ ).format(props.dateRange.from)}
+
+
+
-
+
- {props.dateRange.to !== undefined ? (
-
-
- {props.dateRange.to?.getDate() < 10
- ? "0" + props.dateRange.to?.getDate()
- : props.dateRange.to?.getDate()}
-
-
-
- {new Intl.DateTimeFormat(
- userLocaleString === "de" ? "de" : "en-GB",
- { month: "long" }
- ).format(props.dateRange.to)}{" "}
- {props.dateRange.to?.getFullYear()}
-
-
- {new Intl.DateTimeFormat(
- userLocaleString === "de" ? "de" : "en-GB",
- { weekday: "long" }
- ).format(props.dateRange.to)}
-
-
-
- ) : (
-
- )}
-
- )}
-
-
-
-
-
- {
- props.setDateRange({ from: new Date(), to: new Date() });
- }}
- >
- Today
-
- {
- props.setDateRange(undefined);
- }}
- >
- Clear
-
-
-
-
{
- if (
- props.dateRange?.from === undefined ||
- props.dateRange?.to === undefined
- ) {
- e.preventDefault();
- toast({
- description: "Please select a date range",
- });
- } else {
- props.setTimeState("timeperiod");
- props.setIsDialogOpen(false);
- }
- }}
- >
-
-
+ {props.dateRange.to !== undefined ? (
+
+
+ {props.dateRange.to?.getDate() < 10
+ ? '0' + props.dateRange.to?.getDate()
+ : props.dateRange.to?.getDate()}
+
+
+
+ {new Intl.DateTimeFormat(
+ userLocaleString === 'de' ? 'de' : 'en-GB',
+ { month: 'long' },
+ ).format(props.dateRange.to)}{' '}
+ {props.dateRange.to?.getFullYear()}
+
+
+ {new Intl.DateTimeFormat(
+ userLocaleString === 'de' ? 'de' : 'en-GB',
+ { weekday: 'long' },
+ ).format(props.dateRange.to)}
+
+
+
+ ) : (
+
+ )}
+
+ )}
+
+
+
+
+
+ {
+ props.setDateRange({ from: new Date(), to: new Date() })
+ }}
+ >
+ Today
+
+ {
+ props.setDateRange(undefined)
+ }}
+ >
+ Clear
+
+
+
+
{
+ if (
+ props.dateRange?.from === undefined ||
+ props.dateRange?.to === undefined
+ ) {
+ e.preventDefault()
+ toast({
+ description: 'Please select a date range',
+ })
+ } else {
+ props.setTimeState('timeperiod')
+ props.setIsDialogOpen(false)
+ }
+ }}
+ >
+
+
-
-
+
+
-
-
+
+
-
- {t("button")}
-
-
-
-
-
- );
+
+ {t('button')}
+
+
+
+
+
+ )
}
diff --git a/app/components/header/nav-bar/use-keyboard-nav.tsx b/app/components/header/nav-bar/use-keyboard-nav.tsx
index 34b14832f..9a12b7a84 100644
--- a/app/components/header/nav-bar/use-keyboard-nav.tsx
+++ b/app/components/header/nav-bar/use-keyboard-nav.tsx
@@ -1,63 +1,63 @@
-import { type Key, useState, useEffect } from "react";
+import { type Key, useState, useEffect } from 'react'
export default function useKeyboardNav(
- initCursorVal: number = 0,
- cursorMin: number = 0,
- cursorMax: number = 0
+ initCursorVal: number = 0,
+ cursorMin: number = 0,
+ cursorMax: number = 0,
) {
- const useKeyPress = function (targetKey: Key) {
- const [keyPressed, setKeyPressed] = useState(false);
-
- useEffect(() => {
- const downHandler = ({ key }: { key: string }) => {
- if (key === targetKey) {
- setKeyPressed(true);
- }
- };
-
- const upHandler = ({ key }: { key: string }) => {
- if (key === targetKey) {
- setKeyPressed(false);
- }
- };
-
- window.addEventListener("keydown", downHandler);
- window.addEventListener("keyup", upHandler);
-
- return () => {
- window.removeEventListener("keydown", downHandler);
- window.removeEventListener("keyup", upHandler);
- };
- }, [targetKey]);
-
- return keyPressed;
- };
-
- const downPress = useKeyPress("ArrowDown");
- const upPress = useKeyPress("ArrowUp");
- const enterPress = useKeyPress("Enter");
- const controlPress = useKeyPress("Control");
- const metaPress = useKeyPress("Meta");
- const [cursor, setCursor] = useState(initCursorVal);
-
- useEffect(() => {
- if (downPress && cursor < cursorMax - 1) {
- setCursor(cursor + 1);
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [downPress, cursorMax]);
- useEffect(() => {
- if (upPress && cursor > 0) {
- setCursor(cursor - 1);
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [upPress, cursorMin]);
-
- return {
- cursor,
- setCursor,
- enterPress,
- controlPress,
- metaPress,
- };
+ const useKeyPress = function (targetKey: Key) {
+ const [keyPressed, setKeyPressed] = useState(false)
+
+ useEffect(() => {
+ const downHandler = ({ key }: { key: string }) => {
+ if (key === targetKey) {
+ setKeyPressed(true)
+ }
+ }
+
+ const upHandler = ({ key }: { key: string }) => {
+ if (key === targetKey) {
+ setKeyPressed(false)
+ }
+ }
+
+ window.addEventListener('keydown', downHandler)
+ window.addEventListener('keyup', upHandler)
+
+ return () => {
+ window.removeEventListener('keydown', downHandler)
+ window.removeEventListener('keyup', upHandler)
+ }
+ }, [targetKey])
+
+ return keyPressed
+ }
+
+ const downPress = useKeyPress('ArrowDown')
+ const upPress = useKeyPress('ArrowUp')
+ const enterPress = useKeyPress('Enter')
+ const controlPress = useKeyPress('Control')
+ const metaPress = useKeyPress('Meta')
+ const [cursor, setCursor] = useState(initCursorVal)
+
+ useEffect(() => {
+ if (downPress && cursor < cursorMax - 1) {
+ setCursor(cursor + 1)
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [downPress, cursorMax])
+ useEffect(() => {
+ if (upPress && cursor > 0) {
+ setCursor(cursor - 1)
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [upPress, cursorMin])
+
+ return {
+ cursor,
+ setCursor,
+ enterPress,
+ controlPress,
+ metaPress,
+ }
}
diff --git a/app/components/header/notification/index.tsx b/app/components/header/notification/index.tsx
index c00ef1b8f..2ff3b2fe9 100644
--- a/app/components/header/notification/index.tsx
+++ b/app/components/header/notification/index.tsx
@@ -1,48 +1,49 @@
import {
- NovuProvider,
- PopoverNotificationCenter,
- NotificationBell, type IMessage
-} from "@novu/notification-center";
-import { useLoaderData } from "react-router";
-import { type loader } from "~/root";
+ NovuProvider,
+ PopoverNotificationCenter,
+ NotificationBell,
+ type IMessage,
+} from '@novu/notification-center'
+import { useLoaderData } from 'react-router'
+import { type loader } from '~/root'
function onNotificationClick(message: IMessage) {
- if (message?.cta?.data?.url) {
- //window.location.href = message.cta.data.url;
- window.open(message.cta.data.url, "_blank");
- }
+ if (message?.cta?.data?.url) {
+ //window.location.href = message.cta.data.url;
+ window.open(message.cta.data.url, '_blank')
+ }
}
export default function Notification() {
- const data = useLoaderData();
- // get theme from tailwind
- const [theme] = "light"; // useTheme();
- return (
-
-
- {
- //header content here
- return
;
- }}
- footer={() => {
- //footer content here
- return
;
- }}
- >
- {({ unseenCount }) => }
-
-
-
- );
+ const data = useLoaderData()
+ // get theme from tailwind
+ const [theme] = 'light' // useTheme();
+ return (
+
+
+ {
+ //header content here
+ return
+ }}
+ footer={() => {
+ //footer content here
+ return
+ }}
+ >
+ {({ unseenCount }) => }
+
+
+
+ )
}
diff --git a/app/components/label-button.tsx b/app/components/label-button.tsx
index 4a77d3ad0..b15be8f87 100644
--- a/app/components/label-button.tsx
+++ b/app/components/label-button.tsx
@@ -1,5 +1,5 @@
export function LabelButton({
- ...props
-}: Omit, "className">) {
- return ;
+ ...props
+}: Omit, 'className'>) {
+ return
}
diff --git a/app/components/landing/globe.client.tsx b/app/components/landing/globe.client.tsx
index caf898996..a25e318cc 100644
--- a/app/components/landing/globe.client.tsx
+++ b/app/components/landing/globe.client.tsx
@@ -1,55 +1,55 @@
-"use client";
+'use client'
-import { useEffect, useRef } from "react";
-import Globe from "react-globe.gl";
+import { useEffect, useRef } from 'react'
+import Globe from 'react-globe.gl'
interface Device {
- id: string;
- name: string;
- latitude: number;
- longitude: number;
+ id: string
+ name: string
+ latitude: number
+ longitude: number
}
interface GlobeComponentProps {
- latestDevices: Device[];
+ latestDevices: Device[]
}
export const GlobeComponent = ({ latestDevices }: GlobeComponentProps) => {
- const globeEl = useRef(null);
-
- const colorInterpolator = (t: number) =>
- `rgba(${50 + Math.floor(t * 50)}, ${100 + Math.floor(t * 100)}, 255, ${Math.sqrt(1 - t)})`;
-
- // Transform latestDevices into the format required by Globe's ringsData
- const sensorData = latestDevices.map((device) => ({
- lat: device.latitude,
- lng: device.longitude,
- maxR: 10,
- propagationSpeed: 5,
- repeatPeriod: 1000,
- }));
-
- useEffect(() => {
- if (globeEl.current) {
- globeEl.current.controls().autoRotate = true;
- globeEl.current.controls().autoRotateSpeed = 1.0;
- globeEl.current.controls().enableZoom = false;
- }
- }, []);
-
- return (
- colorInterpolator}
- ringMaxRadius="maxR"
- ringPropagationSpeed="propagationSpeed"
- ringRepeatPeriod="repeatPeriod"
- />
- );
-};
+ const globeEl = useRef(null)
+
+ const colorInterpolator = (t: number) =>
+ `rgba(${50 + Math.floor(t * 50)}, ${100 + Math.floor(t * 100)}, 255, ${Math.sqrt(1 - t)})`
+
+ // Transform latestDevices into the format required by Globe's ringsData
+ const sensorData = latestDevices.map((device) => ({
+ lat: device.latitude,
+ lng: device.longitude,
+ maxR: 10,
+ propagationSpeed: 5,
+ repeatPeriod: 1000,
+ }))
+
+ useEffect(() => {
+ if (globeEl.current) {
+ globeEl.current.controls().autoRotate = true
+ globeEl.current.controls().autoRotateSpeed = 1.0
+ globeEl.current.controls().enableZoom = false
+ }
+ }, [])
+
+ return (
+ colorInterpolator}
+ ringMaxRadius="maxR"
+ ringPropagationSpeed="propagationSpeed"
+ ringRepeatPeriod="repeatPeriod"
+ />
+ )
+}
diff --git a/app/components/landing/header/header.tsx b/app/components/landing/header/header.tsx
index e11e23efa..79efc9cdc 100644
--- a/app/components/landing/header/header.tsx
+++ b/app/components/landing/header/header.tsx
@@ -1,131 +1,131 @@
-import { useState } from "react";
-import { Link } from "react-router";
+import { useState } from 'react'
+import { Link } from 'react-router'
// import { ModeToggle } from "../../mode-toggle";
-import LanguageSelector from "./language-selector";
-import { useTranslation } from "react-i18next";
+import LanguageSelector from './language-selector'
+import { useTranslation } from 'react-i18next'
const links = [
- {
- name: "Explore",
- link: "/explore",
- },
- {
- name: "Features",
- link: "#features",
- },
- // {
- // name: "Tools",
- // link: "#tools",
- // },
- // {
- // name: "Use Cases",
- // link: "#useCases",
- // },
- // {
- // name: "Partners",
- // link: "#partners",
- // },
- {
- name: "Sponsor",
- link: "#pricing",
- },
-];
+ {
+ name: 'Explore',
+ link: '/explore',
+ },
+ {
+ name: 'Features',
+ link: '#features',
+ },
+ // {
+ // name: "Tools",
+ // link: "#tools",
+ // },
+ // {
+ // name: "Use Cases",
+ // link: "#useCases",
+ // },
+ // {
+ // name: "Partners",
+ // link: "#partners",
+ // },
+ {
+ name: 'Sponsor',
+ link: '#pricing',
+ },
+]
export default function Header() {
- const [openMenu, setOpenMenu] = useState(false);
+ const [openMenu, setOpenMenu] = useState(false)
- const { t } = useTranslation("header");
+ const { t } = useTranslation('header')
- return (
-
- );
+ return (
+
+ )
}
diff --git a/app/components/landing/sections/connect.tsx b/app/components/landing/sections/connect.tsx
index 80dabba97..50d5a8a44 100644
--- a/app/components/landing/sections/connect.tsx
+++ b/app/components/landing/sections/connect.tsx
@@ -1,70 +1,70 @@
-import { BookA, Wrench } from "lucide-react";
-import { useTranslation } from "react-i18next";
+import { BookA, Wrench } from 'lucide-react'
+import { useTranslation } from 'react-i18next'
export default function Connect() {
- const { t } = useTranslation('connect')
- return (
-
-
-
- {t("title")}
-
- {t("description")}
-
-
-
-
-
-
- );
+ const { t } = useTranslation('connect')
+ return (
+
+
+
+ {t('title')}
+
+ {t('description')}
+
+
+
+
+
+
+ )
}
diff --git a/app/components/landing/sections/features-card.tsx b/app/components/landing/sections/features-card.tsx
index 7b85ed279..289b42ce2 100644
--- a/app/components/landing/sections/features-card.tsx
+++ b/app/components/landing/sections/features-card.tsx
@@ -1,18 +1,18 @@
-import { type Feature } from "~/lib/directus";
+import { type Feature } from '~/lib/directus'
export default function FeatureCard(item: Feature) {
- return (
-
-
- {item.title}
-
-
{item.description}
-
-
-
-
- );
+ return (
+
+
+ {item.title}
+
+
{item.description}
+
+
+
+
+ )
}
diff --git a/app/components/landing/sections/features-carousel.tsx b/app/components/landing/sections/features-carousel.tsx
index a80adabda..fd1f633a0 100644
--- a/app/components/landing/sections/features-carousel.tsx
+++ b/app/components/landing/sections/features-carousel.tsx
@@ -1,188 +1,188 @@
-import { AnimatePresence, motion, type Variants } from "framer-motion";
-import { ArrowLeft, ArrowRight } from "lucide-react";
-import { useState, useEffect } from "react";
-import FeatureCard from "./features-card";
-import { type Feature } from "~/lib/directus";
+import { AnimatePresence, motion, type Variants } from 'framer-motion'
+import { ArrowLeft, ArrowRight } from 'lucide-react'
+import { useState, useEffect } from 'react'
+import FeatureCard from './features-card'
+import { type Feature } from '~/lib/directus'
type FeaturesProps = {
- data: Feature[];
-};
+ data: Feature[]
+}
const variants: Variants = {
- enter: ({ direction }) => {
- return { scale: 0.2, x: direction < 1 ? 50 : -50, opacity: 0 };
- },
- center: ({ position, direction }) => {
- return {
- scale: position() === "center" ? 1 : 0.7,
- x: 0,
- zIndex: getZIndex({ position, direction }),
- opacity: 1,
- };
- },
- exit: ({ direction }) => {
- return { scale: 0.2, x: direction < 1 ? -50 : 50, opacity: 0 };
- },
-};
+ enter: ({ direction }) => {
+ return { scale: 0.2, x: direction < 1 ? 50 : -50, opacity: 0 }
+ },
+ center: ({ position, direction }) => {
+ return {
+ scale: position() === 'center' ? 1 : 0.7,
+ x: 0,
+ zIndex: getZIndex({ position, direction }),
+ opacity: 1,
+ }
+ },
+ exit: ({ direction }) => {
+ return { scale: 0.2, x: direction < 1 ? -50 : 50, opacity: 0 }
+ },
+}
function getZIndex({
- position,
- direction,
+ position,
+ direction,
}: {
- position: () => string;
- direction: number;
+ position: () => string
+ direction: number
}): number {
- const indexes: { [key: string]: number } = {
- left: direction > 0 ? 2 : 1,
- center: 3,
- right: direction > 0 ? 1 : 2,
- };
- return indexes[position()];
+ const indexes: { [key: string]: number } = {
+ left: direction > 0 ? 2 : 1,
+ center: 3,
+ right: direction > 0 ? 1 : 2,
+ }
+ return indexes[position()]
}
export default function FeaturesCarousel({ data }: FeaturesProps) {
- const [[activeIndex, direction], setActiveIndex] = useState<[number, number]>(
- [0, 0],
- );
- const [isButtonDisabled, setIsButtonDisabled] = useState(false);
- const [isMobileScreen, setIsMobileScreen] = useState(false);
+ const [[activeIndex, direction], setActiveIndex] = useState<[number, number]>(
+ [0, 0],
+ )
+ const [isButtonDisabled, setIsButtonDisabled] = useState(false)
+ const [isMobileScreen, setIsMobileScreen] = useState(false)
- useEffect(() => {
- const handleResize = () => {
- setIsMobileScreen(window.innerWidth <= 768); // Adjust the breakpoint as needed
- };
+ useEffect(() => {
+ const handleResize = () => {
+ setIsMobileScreen(window.innerWidth <= 768) // Adjust the breakpoint as needed
+ }
- handleResize(); // Check on initial render
+ handleResize() // Check on initial render
- window.addEventListener("resize", handleResize);
- return () => {
- window.removeEventListener("resize", handleResize);
- };
- }, []);
+ window.addEventListener('resize', handleResize)
+ return () => {
+ window.removeEventListener('resize', handleResize)
+ }
+ }, [])
- const indexInArrayScope =
- ((activeIndex % data.length) + data.length) % data.length;
+ const indexInArrayScope =
+ ((activeIndex % data.length) + data.length) % data.length
- const visibleItems: Feature[] = isMobileScreen
- ? [data[indexInArrayScope]]
- : [...data, ...data].slice(indexInArrayScope, indexInArrayScope + 3);
+ const visibleItems: Feature[] = isMobileScreen
+ ? [data[indexInArrayScope]]
+ : [...data, ...data].slice(indexInArrayScope, indexInArrayScope + 3)
- const handleClick = (newDirection: number): void => {
- if (isButtonDisabled) return; // Prevent clicking if the button is disabled
+ const handleClick = (newDirection: number): void => {
+ if (isButtonDisabled) return // Prevent clicking if the button is disabled
- setActiveIndex((prevIndex) => [prevIndex[0] + newDirection, newDirection]);
- setIsButtonDisabled(true); // Disable the button
+ setActiveIndex((prevIndex) => [prevIndex[0] + newDirection, newDirection])
+ setIsButtonDisabled(true) // Disable the button
- setTimeout(() => {
- setIsButtonDisabled(false); // Enable the button after 1 second
- }, 1000);
- };
+ setTimeout(() => {
+ setIsButtonDisabled(false) // Enable the button after 1 second
+ }, 1000)
+ }
- return (
-
-
- handleClick(-1)}
- initial="hidden"
- whileInView="visible"
- viewport={{ once: true }}
- transition={{
- duration: 0.5,
- delay: 0.8,
- type: "spring",
- stiffness: 150,
- }}
- variants={{
- visible: { opacity: 1, x: 0 },
- hidden: { opacity: 0, x: -50 },
- }}
- >
-
-
-
-
-
-
-
- {visibleItems.map((item: Feature) => {
- return (
- {
- if (item === visibleItems[0]) {
- return "left";
- } else if (item === visibleItems[1]) {
- return "center";
- } else {
- return "right";
- }
- },
- }}
- variants={variants}
- initial="enter"
- animate="center"
- exit="exit"
- transition={{ duration: 1 }}
- >
-
-
- );
- })}
-
- handleClick(1)}
- initial="hidden"
- whileInView="visible"
- viewport={{ once: true }}
- transition={{
- duration: 0.3,
- delay: 0.8,
- type: "spring",
- stiffness: 150,
- }}
- variants={{
- visible: { opacity: 1, x: 0 },
- hidden: { opacity: 0, x: 50 },
- }}
- >
-
-
-
-
-
-
-
-
- );
+ return (
+
+
+ handleClick(-1)}
+ initial="hidden"
+ whileInView="visible"
+ viewport={{ once: true }}
+ transition={{
+ duration: 0.5,
+ delay: 0.8,
+ type: 'spring',
+ stiffness: 150,
+ }}
+ variants={{
+ visible: { opacity: 1, x: 0 },
+ hidden: { opacity: 0, x: -50 },
+ }}
+ >
+
+
+
+
+
+
+
+ {visibleItems.map((item: Feature) => {
+ return (
+ {
+ if (item === visibleItems[0]) {
+ return 'left'
+ } else if (item === visibleItems[1]) {
+ return 'center'
+ } else {
+ return 'right'
+ }
+ },
+ }}
+ variants={variants}
+ initial="enter"
+ animate="center"
+ exit="exit"
+ transition={{ duration: 1 }}
+ >
+
+
+ )
+ })}
+
+ handleClick(1)}
+ initial="hidden"
+ whileInView="visible"
+ viewport={{ once: true }}
+ transition={{
+ duration: 0.3,
+ delay: 0.8,
+ type: 'spring',
+ stiffness: 150,
+ }}
+ variants={{
+ visible: { opacity: 1, x: 0 },
+ hidden: { opacity: 0, x: 50 },
+ }}
+ >
+
+
+
+
+
+
+
+
+ )
}
diff --git a/app/components/landing/sections/features.tsx b/app/components/landing/sections/features.tsx
index 8d57e70fd..424d61e6b 100644
--- a/app/components/landing/sections/features.tsx
+++ b/app/components/landing/sections/features.tsx
@@ -1,76 +1,73 @@
import {
- Copyleft,
- Download,
- GitFork,
- Scale,
- Telescope,
- Terminal,
- Trash,
-} from "lucide-react";
-import { useTranslation } from "react-i18next";
+ Copyleft,
+ Download,
+ GitFork,
+ Scale,
+ Telescope,
+ Terminal,
+ Trash,
+} from 'lucide-react'
+import { useTranslation } from 'react-i18next'
export default function Features() {
- const { t } = useTranslation('features')
- return (
-
-
-
- {t("features")}
-
- {t("description")}
-
-
-
-
-
-
-
-
-
- {t("dataAggregation")}
-
-
-
-
-
- {t("noDataRetention")}
-
-
-
-
-
- {t("dataPublished")}
-
-
-
-
-
- {t("discoverDevices")}
-
-
-
-
-
- {t("compareDevices")}
-
-
-
-
-
- {t("downloadOptions")}
-
-
-
-
-
- {t("httpRestApi")}
-
-
-
-
-
- );
+ const { t } = useTranslation('features')
+ return (
+
+
+
+ {t('features')}
+
+ {t('description')}
+
+
+
+
+
+
+
+
+
+ {t('dataAggregation')}
+
+
+
+
+
+ {t('noDataRetention')}
+
+
+
+
+
+ {t('dataPublished')}
+
+
+
+
+
+ {t('discoverDevices')}
+
+
+
+
+
+ {t('compareDevices')}
+
+
+
+
+
+ {t('downloadOptions')}
+
+
+
+
+
+ {t('httpRestApi')}
+
+
+
+
+
+ )
}
diff --git a/app/components/landing/sections/integrations.tsx b/app/components/landing/sections/integrations.tsx
index 184f54259..f47ad975b 100644
--- a/app/components/landing/sections/integrations.tsx
+++ b/app/components/landing/sections/integrations.tsx
@@ -1,59 +1,56 @@
-import { ArrowUpDown, RadioTower, Unplug } from "lucide-react";
-import { useTranslation } from "react-i18next";
+import { ArrowUpDown, RadioTower, Unplug } from 'lucide-react'
+import { useTranslation } from 'react-i18next'
export default function Integrations() {
- const { t } = useTranslation('integrations')
- return (
-
-
-
- {t("title")}
-
- {t("description")}
-
-
-
-
-
-
- );
+ const { t } = useTranslation('integrations')
+ return (
+
+
+
+ {t('title')}
+
+ {t('description')}
+
+
+
+
+
+
+ )
}
diff --git a/app/components/landing/sections/partners.tsx b/app/components/landing/sections/partners.tsx
index 7dbb3e64a..55947465b 100644
--- a/app/components/landing/sections/partners.tsx
+++ b/app/components/landing/sections/partners.tsx
@@ -1,73 +1,73 @@
-import { motion } from "framer-motion";
-import { useTranslation } from "react-i18next";
-import { type Partner } from "~/lib/directus";
+import { motion } from 'framer-motion'
+import { useTranslation } from 'react-i18next'
+import { type Partner } from '~/lib/directus'
type PartnersProps = {
- data: Partner[];
-};
+ data: Partner[]
+}
export default function Partners({ data }: PartnersProps) {
- const { t } = useTranslation('partners')
- return (
-
-
-
-
- {data.map((partner, index) => {
- return (
-
-
-
- );
- })}
-
-
- {t("hosted")}
-
-
-
-
- );
+ const { t } = useTranslation('partners')
+ return (
+
+
+
+
+ {data.map((partner, index) => {
+ return (
+
+
+
+ )
+ })}
+
+
+ {t('hosted')}
+
+
+
+
+ )
}
diff --git a/app/components/landing/sections/pricing-plans.tsx b/app/components/landing/sections/pricing-plans.tsx
index 9f03b903b..c2ac721c0 100644
--- a/app/components/landing/sections/pricing-plans.tsx
+++ b/app/components/landing/sections/pricing-plans.tsx
@@ -1,44 +1,44 @@
-import { Gift, Star } from "lucide-react";
-import { useTranslation } from "react-i18next";
+import { Gift, Star } from 'lucide-react'
+import { useTranslation } from 'react-i18next'
export default function PricingPlans() {
-
- const { t } = useTranslation('pricing-plans')
- return (
-
-
-
-
{t("Pricing")}
-
- {(t("kidding"))}
- {(t("contribution"))}
-
-
-
-
-
- );
+ const { t } = useTranslation('pricing-plans')
+ return (
+
+
+
+
{t('Pricing')}
+
+ {t('kidding')}
+
+ {t('contribution')}
+
+
+
+
+
+ )
}
diff --git a/app/components/landing/sections/tools.tsx b/app/components/landing/sections/tools.tsx
index fc485ffb2..2a84ece09 100644
--- a/app/components/landing/sections/tools.tsx
+++ b/app/components/landing/sections/tools.tsx
@@ -1,92 +1,92 @@
-import { motion } from "framer-motion";
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
-import { useTranslation } from "react-i18next";
+import { motion } from 'framer-motion'
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
+import { useTranslation } from 'react-i18next'
export default function Tools() {
- const { t } = useTranslation('tools')
- const tools = [
- {
- name: "Tool 1",
- video: "/landing/stock_video.mp4",
- id: 1,
- },
- {
- name: "Tool 2",
- video: "/landing/stock_video.mp4",
- id: 2,
- },
- {
- name: "Tool 3",
- video: "/landing/stock_video.mp4",
- id: 3,
- },
- {
- name: "Tool 4",
- video: "/landing/stock_video.mp4",
- id: 4,
- },
- {
- name: "Tool 5",
- video: "/landing/stock_video.mp4",
- id: 5,
- },
- ];
- return (
-
- );
+ const { t } = useTranslation('tools')
+ const tools = [
+ {
+ name: 'Tool 1',
+ video: '/landing/stock_video.mp4',
+ id: 1,
+ },
+ {
+ name: 'Tool 2',
+ video: '/landing/stock_video.mp4',
+ id: 2,
+ },
+ {
+ name: 'Tool 3',
+ video: '/landing/stock_video.mp4',
+ id: 3,
+ },
+ {
+ name: 'Tool 4',
+ video: '/landing/stock_video.mp4',
+ id: 4,
+ },
+ {
+ name: 'Tool 5',
+ video: '/landing/stock_video.mp4',
+ id: 5,
+ },
+ ]
+ return (
+
+ )
}
diff --git a/app/components/landing/stats.tsx b/app/components/landing/stats.tsx
index 1d94e0110..fe79afecb 100644
--- a/app/components/landing/stats.tsx
+++ b/app/components/landing/stats.tsx
@@ -1,61 +1,61 @@
-import { motion } from "framer-motion";
-import { useTranslation } from "react-i18next";
-import AnimatedCounter from "../ui/animated-counter";
+import { motion } from 'framer-motion'
+import { useTranslation } from 'react-i18next'
+import AnimatedCounter from '../ui/animated-counter'
export default function Stats(stats: number[]) {
- const { t } = useTranslation("stats");
+ const { t } = useTranslation('stats')
- const osemStats = [
- {
- id: 1,
- name: "devices",
- value: stats[0] / 1000,
- unit: "k",
- },
- {
- id: 2,
- name: "measurements_total",
- value: stats[1] / 1000000,
- unit: "m",
- },
- {
- id: 3,
- name: "measurements_per_minute",
- value: stats[2] / 1000,
- unit: "k",
- },
- ];
+ const osemStats = [
+ {
+ id: 1,
+ name: 'devices',
+ value: stats[0] / 1000,
+ unit: 'k',
+ },
+ {
+ id: 2,
+ name: 'measurements_total',
+ value: stats[1] / 1000000,
+ unit: 'm',
+ },
+ {
+ id: 3,
+ name: 'measurements_per_minute',
+ value: stats[2] / 1000,
+ unit: 'k',
+ },
+ ]
- return (
-
-
- {osemStats.map((stat) => (
-
-
-
-
-
-
-
-
- {stat.unit}
-
-
-
{t(stat.name)}
-
-
-
- ))}
-
-
- );
+ return (
+
+
+ {osemStats.map((stat) => (
+
+
+
+
+
+
+
+
+ {stat.unit}
+
+
+
{t(stat.name)}
+
+
+
+ ))}
+
+
+ )
}
diff --git a/app/components/map/filter-visualization.tsx b/app/components/map/filter-visualization.tsx
index 66d292d57..b618662c3 100644
--- a/app/components/map/filter-visualization.tsx
+++ b/app/components/map/filter-visualization.tsx
@@ -1,117 +1,117 @@
-import { X } from "lucide-react";
-import { Fragment, useEffect } from "react";
-import { useLoaderData, useNavigate } from "react-router";
-import { type loader } from "~/routes/explore";
-import { DeviceExposureZodEnum, DeviceStatusZodEnum } from "~/schema/enum";
+import { X } from 'lucide-react'
+import { Fragment, useEffect } from 'react'
+import { useLoaderData, useNavigate } from 'react-router'
+import { type loader } from '~/routes/explore'
+import { DeviceExposureZodEnum, DeviceStatusZodEnum } from '~/schema/enum'
export default function FilterVisualization() {
- const data = useLoaderData();
- const navigate = useNavigate();
- const params = new URLSearchParams(data.filterParams);
+ const data = useLoaderData()
+ const navigate = useNavigate()
+ const params = new URLSearchParams(data.filterParams)
- // Validate filter values using the predefined enums
- const isValidFilter = (key: string, value: string) => {
- switch (key) {
- case "exposure":
- return DeviceExposureZodEnum.safeParse(value).success;
- case "status":
- return DeviceStatusZodEnum.safeParse(value).success;
- case "tags":
- return true; // No validation for tags
- default:
- return false; // Invalid key
- }
- };
+ // Validate filter values using the predefined enums
+ const isValidFilter = (key: string, value: string) => {
+ switch (key) {
+ case 'exposure':
+ return DeviceExposureZodEnum.safeParse(value).success
+ case 'status':
+ return DeviceStatusZodEnum.safeParse(value).success
+ case 'tags':
+ return true // No validation for tags
+ default:
+ return false // Invalid key
+ }
+ }
- // Update the search params to remove invalid filters
- const cleanSearchParams = () => {
- let modified = false;
- const newParams = new URLSearchParams(params);
+ // Update the search params to remove invalid filters
+ const cleanSearchParams = () => {
+ let modified = false
+ const newParams = new URLSearchParams(params)
- params.forEach((value, key) => {
- const values = value.split(","); // Handle comma-separated values
- const validValues = values.filter((v) => isValidFilter(key, v));
+ params.forEach((value, key) => {
+ const values = value.split(',') // Handle comma-separated values
+ const validValues = values.filter((v) => isValidFilter(key, v))
- if (validValues.length === 0) {
- // Remove entire parameter if no valid values
- newParams.delete(key);
- modified = true;
- } else if (validValues.length !== values.length) {
- // Update the parameter with only valid values
- newParams.set(key, validValues.join(","));
- modified = true;
- }
- });
+ if (validValues.length === 0) {
+ // Remove entire parameter if no valid values
+ newParams.delete(key)
+ modified = true
+ } else if (validValues.length !== values.length) {
+ // Update the parameter with only valid values
+ newParams.set(key, validValues.join(','))
+ modified = true
+ }
+ })
- if (modified) {
- // Update the URL without reloading the page
- void navigate(`?${newParams.toString()}`, { replace: true });
- }
- };
+ if (modified) {
+ // Update the URL without reloading the page
+ void navigate(`?${newParams.toString()}`, { replace: true })
+ }
+ }
- // Clean search params when the component mounts
- useEffect(() => {
- cleanSearchParams();
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
+ // Clean search params when the component mounts
+ useEffect(() => {
+ cleanSearchParams()
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [])
- // Group valid filters by key
- const groupedFilters: { [key: string]: string[] } = {};
+ // Group valid filters by key
+ const groupedFilters: { [key: string]: string[] } = {}
- params.forEach((value, key) => {
- const values = value.split(",").filter((v) => isValidFilter(key, v));
- if (values.length > 0) {
- groupedFilters[key] = values; // Group valid values under the same key
- }
- });
+ params.forEach((value, key) => {
+ const values = value.split(',').filter((v) => isValidFilter(key, v))
+ if (values.length > 0) {
+ groupedFilters[key] = values // Group valid values under the same key
+ }
+ })
- // Don't render anything if there are no active valid filters
- if (Object.keys(groupedFilters).length === 0) {
- return null;
- }
+ // Don't render anything if there are no active valid filters
+ if (Object.keys(groupedFilters).length === 0) {
+ return null
+ }
- const onRemoveFilter = (key: string) => {
- const newParams = new URLSearchParams(params);
+ const onRemoveFilter = (key: string) => {
+ const newParams = new URLSearchParams(params)
- // Set the key to "all" and remove all other values
- newParams.delete(key);
+ // Set the key to "all" and remove all other values
+ newParams.delete(key)
- // Update the URL without reloading the page
- void navigate(`?${newParams.toString()}`, { replace: true });
- };
+ // Update the URL without reloading the page
+ void navigate(`?${newParams.toString()}`, { replace: true })
+ }
- return (
-
- {Object.entries(groupedFilters).map(([key, values]) => (
-
-
- {key}
-
-
- {values.map((value, index) => (
-
- {index > 0 && / }
- {value}
-
- ))}
-
-
onRemoveFilter(key)}
- className="ml-2 text-blue-500 hover:text-blue-700 focus:outline-none"
- aria-label={`Remove ${key} filter`}
- >
-
-
-
- ))}
-
-
- {"Total Devices: " + data.filteredDevices.features.length}
-
-
-
- );
+ return (
+
+ {Object.entries(groupedFilters).map(([key, values]) => (
+
+
+ {key}
+
+
+ {values.map((value, index) => (
+
+ {index > 0 && / }
+ {value}
+
+ ))}
+
+
onRemoveFilter(key)}
+ className="ml-2 text-blue-500 hover:text-blue-700 focus:outline-none"
+ aria-label={`Remove ${key} filter`}
+ >
+
+
+
+ ))}
+
+
+ {'Total Devices: ' + data.filteredDevices.features.length}
+
+
+
+ )
}
diff --git a/app/components/map/geocoder-control.tsx b/app/components/map/geocoder-control.tsx
index b514412f7..396634f0a 100644
--- a/app/components/map/geocoder-control.tsx
+++ b/app/components/map/geocoder-control.tsx
@@ -1,114 +1,143 @@
-import MapboxGeocoder, {type GeocoderOptions} from '@mapbox/mapbox-gl-geocoder';
-import * as React from 'react';
-import {useState} from 'react';
-import {useControl, Marker, type MarkerProps, type ControlPosition} from 'react-map-gl';
+import MapboxGeocoder, {
+ type GeocoderOptions,
+} from '@mapbox/mapbox-gl-geocoder'
+import * as React from 'react'
+import { useState } from 'react'
+import {
+ useControl,
+ Marker,
+ type MarkerProps,
+ type ControlPosition,
+} from 'react-map-gl'
-type GeocoderControlProps = Omit & {
- mapboxAccessToken: string | undefined;
- marker?: boolean | Omit;
+type GeocoderControlProps = Omit<
+ GeocoderOptions,
+ 'accessToken' | 'mapboxgl' | 'marker'
+> & {
+ mapboxAccessToken: string | undefined
+ marker?: boolean | Omit
- position: ControlPosition;
+ position: ControlPosition
- onLoading: (e: object) => void;
- onResults: (e: object) => void;
- onResult: (e: object) => void;
- onError: (e: object) => void;
-};
+ onLoading: (e: object) => void
+ onResults: (e: object) => void
+ onResult: (e: object) => void
+ onError: (e: object) => void
+}
export default function GeocoderControl(props: GeocoderControlProps) {
- const [marker, setMarker] = useState(null);
+ const [marker, setMarker] = useState(null)
- const geocoder = useControl(
- () => {
- const ctrl = new MapboxGeocoder({
- ...props,
- marker: false,
- accessToken: props.mapboxAccessToken || '',
- });
- ctrl.on('loading', props.onLoading);
- ctrl.on('results', props.onResults);
- ctrl.on('result', evt => {
- props.onResult(evt);
+ const geocoder = useControl(
+ () => {
+ const ctrl = new MapboxGeocoder({
+ ...props,
+ marker: false,
+ accessToken: props.mapboxAccessToken || '',
+ })
+ ctrl.on('loading', props.onLoading)
+ ctrl.on('results', props.onResults)
+ ctrl.on('result', (evt) => {
+ props.onResult(evt)
- const {result} = evt;
- const location =
- result &&
- (result.center || (result.geometry?.type === 'Point' && result.geometry.coordinates));
- if (location && props.marker) {
- setMarker( );
- } else {
- setMarker(null);
- }
- });
- ctrl.on('error', props.onError);
- return ctrl;
- },
- {
- position: props.position
- }
- );
+ const { result } = evt
+ const location =
+ result &&
+ (result.center ||
+ (result.geometry?.type === 'Point' && result.geometry.coordinates))
+ if (location && props.marker) {
+ setMarker( )
+ } else {
+ setMarker(null)
+ }
+ })
+ ctrl.on('error', props.onError)
+ return ctrl
+ },
+ {
+ position: props.position,
+ },
+ )
- // @ts-ignore (TS2339) private member
- if (geocoder._map) {
- if (geocoder.getProximity() !== props.proximity && props.proximity !== undefined) {
- geocoder.setProximity(props.proximity);
- }
- if (geocoder.getRenderFunction() !== props.render && props.render !== undefined) {
- geocoder.setRenderFunction(props.render);
- }
- if (geocoder.getLanguage() !== props.language && props.language !== undefined) {
- geocoder.setLanguage(props.language);
- }
- if (geocoder.getZoom() !== props.zoom && props.zoom !== undefined) {
- geocoder.setZoom(props.zoom);
- }
- if (geocoder.getFlyTo() !== props.flyTo && props.flyTo !== undefined) {
- geocoder.setFlyTo(props.flyTo);
- }
- if (geocoder.getPlaceholder() !== props.placeholder && props.placeholder !== undefined) {
- geocoder.setPlaceholder(props.placeholder);
- }
- if (geocoder.getCountries() !== props.countries && props.countries !== undefined) {
- geocoder.setCountries(props.countries);
- }
- if (geocoder.getTypes() !== props.types && props.types !== undefined) {
- geocoder.setTypes(props.types);
- }
- if (geocoder.getMinLength() !== props.minLength && props.minLength !== undefined) {
- geocoder.setMinLength(props.minLength);
- }
- if (geocoder.getLimit() !== props.limit && props.limit !== undefined) {
- geocoder.setLimit(props.limit);
- }
- if (geocoder.getFilter() !== props.filter && props.filter !== undefined) {
- geocoder.setFilter(props.filter);
- }
- if (geocoder.getOrigin() !== props.origin && props.origin !== undefined) {
- geocoder.setOrigin(props.origin);
- }
- // Types missing from @types/mapbox__mapbox-gl-geocoder
- // if (geocoder.getAutocomplete() !== props.autocomplete && props.autocomplete !== undefined) {
- // geocoder.setAutocomplete(props.autocomplete);
- // }
- // if (geocoder.getFuzzyMatch() !== props.fuzzyMatch && props.fuzzyMatch !== undefined) {
- // geocoder.setFuzzyMatch(props.fuzzyMatch);
- // }
- // if (geocoder.getRouting() !== props.routing && props.routing !== undefined) {
- // geocoder.setRouting(props.routing);
- // }
- // if (geocoder.getWorldview() !== props.worldview && props.worldview !== undefined) {
- // geocoder.setWorldview(props.worldview);
- // }
- }
- return marker;
+ // @ts-ignore (TS2339) private member
+ if (geocoder._map) {
+ if (
+ geocoder.getProximity() !== props.proximity &&
+ props.proximity !== undefined
+ ) {
+ geocoder.setProximity(props.proximity)
+ }
+ if (
+ geocoder.getRenderFunction() !== props.render &&
+ props.render !== undefined
+ ) {
+ geocoder.setRenderFunction(props.render)
+ }
+ if (
+ geocoder.getLanguage() !== props.language &&
+ props.language !== undefined
+ ) {
+ geocoder.setLanguage(props.language)
+ }
+ if (geocoder.getZoom() !== props.zoom && props.zoom !== undefined) {
+ geocoder.setZoom(props.zoom)
+ }
+ if (geocoder.getFlyTo() !== props.flyTo && props.flyTo !== undefined) {
+ geocoder.setFlyTo(props.flyTo)
+ }
+ if (
+ geocoder.getPlaceholder() !== props.placeholder &&
+ props.placeholder !== undefined
+ ) {
+ geocoder.setPlaceholder(props.placeholder)
+ }
+ if (
+ geocoder.getCountries() !== props.countries &&
+ props.countries !== undefined
+ ) {
+ geocoder.setCountries(props.countries)
+ }
+ if (geocoder.getTypes() !== props.types && props.types !== undefined) {
+ geocoder.setTypes(props.types)
+ }
+ if (
+ geocoder.getMinLength() !== props.minLength &&
+ props.minLength !== undefined
+ ) {
+ geocoder.setMinLength(props.minLength)
+ }
+ if (geocoder.getLimit() !== props.limit && props.limit !== undefined) {
+ geocoder.setLimit(props.limit)
+ }
+ if (geocoder.getFilter() !== props.filter && props.filter !== undefined) {
+ geocoder.setFilter(props.filter)
+ }
+ if (geocoder.getOrigin() !== props.origin && props.origin !== undefined) {
+ geocoder.setOrigin(props.origin)
+ }
+ // Types missing from @types/mapbox__mapbox-gl-geocoder
+ // if (geocoder.getAutocomplete() !== props.autocomplete && props.autocomplete !== undefined) {
+ // geocoder.setAutocomplete(props.autocomplete);
+ // }
+ // if (geocoder.getFuzzyMatch() !== props.fuzzyMatch && props.fuzzyMatch !== undefined) {
+ // geocoder.setFuzzyMatch(props.fuzzyMatch);
+ // }
+ // if (geocoder.getRouting() !== props.routing && props.routing !== undefined) {
+ // geocoder.setRouting(props.routing);
+ // }
+ // if (geocoder.getWorldview() !== props.worldview && props.worldview !== undefined) {
+ // geocoder.setWorldview(props.worldview);
+ // }
+ }
+ return marker
}
-const noop = () => {};
+const noop = () => {}
GeocoderControl.defaultProps = {
- marker: true,
- onLoading: noop,
- onResults: noop,
- onResult: noop,
- onError: noop
-};
\ No newline at end of file
+ marker: true,
+ onLoading: noop,
+ onResults: noop,
+ onResult: noop,
+ onError: noop,
+}
diff --git a/app/components/map/index.ts b/app/components/map/index.ts
index fcf42b99f..0fef3aa03 100644
--- a/app/components/map/index.ts
+++ b/app/components/map/index.ts
@@ -1,3 +1,3 @@
-export { default } from "./map";
+export { default } from './map'
-export { default as Map } from "./map";
+export { default as Map } from './map'
diff --git a/app/components/map/layers/cluster/cluster-layer.tsx b/app/components/map/layers/cluster/cluster-layer.tsx
index d22883eac..d8f8bbd99 100644
--- a/app/components/map/layers/cluster/cluster-layer.tsx
+++ b/app/components/map/layers/cluster/cluster-layer.tsx
@@ -1,152 +1,152 @@
-import {
- type GeoJsonProperties,
- type BBox,
- type FeatureCollection,
- type Point,
-} from "geojson";
-import debounce from "lodash.debounce";
-import { useMemo, useCallback, useState, useEffect } from "react";
-import { Marker, useMap } from "react-map-gl";
-import { type PointFeature } from "supercluster";
-import useSupercluster from "use-supercluster";
-import BoxMarker from "./box-marker";
-import DonutChartCluster from "./donut-chart-cluster";
-import { type DeviceClusterProperties } from "~/routes/explore";
-import { type Device } from "~/schema";
-
-const DEBOUNCE_VALUE = 50;
+import {
+ type GeoJsonProperties,
+ type BBox,
+ type FeatureCollection,
+ type Point,
+} from 'geojson'
+import debounce from 'lodash.debounce'
+import { useMemo, useCallback, useState, useEffect } from 'react'
+import { Marker, useMap } from 'react-map-gl'
+import { type PointFeature } from 'supercluster'
+import useSupercluster from 'use-supercluster'
+import BoxMarker from './box-marker'
+import DonutChartCluster from './donut-chart-cluster'
+import { type DeviceClusterProperties } from '~/routes/explore'
+import { type Device } from '~/schema'
+
+const DEBOUNCE_VALUE = 50
// supercluster options
const options = {
- radius: 50,
- maxZoom: 14,
- map: (props: any) => ({ categories: { [props.status]: 1 } }),
- reduce: (accumulated: any, props: any) => {
- const categories: any = {};
- // clone the categories object from the accumulator
- for (const key in accumulated.categories) {
- categories[key] = accumulated.categories[key];
- }
- // add props' category data to the clone
- for (const key in props.categories) {
- if (key in accumulated.categories) {
- categories[key] = accumulated.categories[key] + props.categories[key];
- } else {
- categories[key] = props.categories[key];
- }
- }
- // assign the clone to the accumulator
- accumulated.categories = categories;
- },
-};
+ radius: 50,
+ maxZoom: 14,
+ map: (props: any) => ({ categories: { [props.status]: 1 } }),
+ reduce: (accumulated: any, props: any) => {
+ const categories: any = {}
+ // clone the categories object from the accumulator
+ for (const key in accumulated.categories) {
+ categories[key] = accumulated.categories[key]
+ }
+ // add props' category data to the clone
+ for (const key in props.categories) {
+ if (key in accumulated.categories) {
+ categories[key] = accumulated.categories[key] + props.categories[key]
+ } else {
+ categories[key] = props.categories[key]
+ }
+ }
+ // assign the clone to the accumulator
+ accumulated.categories = categories
+ },
+}
export default function ClusterLayer({
- devices,
+ devices,
}: {
- devices: FeatureCollection;
+ devices: FeatureCollection
}) {
- const { osem: mapRef } = useMap();
-
- // the viewport bounds and zoom level
- const [bounds, setBounds] = useState(
- mapRef?.getMap().getBounds().toArray().flat() as BBox
- );
- const [zoom, setZoom] = useState(mapRef?.getZoom() || 0);
-
- // get clusters
- const points: PointFeature[] = useMemo(() => {
- return devices.features.map((device) => ({
- type: "Feature",
- properties: {
- cluster: false,
- ...device.properties,
- },
- geometry: device.geometry,
- }));
- }, [devices.features]);
-
- // get bounds and zoom level from the map
- // debounce the change handler to prevent too many updates
- const debouncedChangeHandler = debounce(() => {
- if (!mapRef) return;
- setBounds(mapRef.getMap().getBounds().toArray().flat() as BBox);
- setZoom(mapRef.getZoom());
- }, DEBOUNCE_VALUE);
-
- // register the debounced change handler to map events
- useEffect(() => {
- if (!mapRef) return;
-
- mapRef?.getMap().on("load", debouncedChangeHandler);
- mapRef?.getMap().on("zoom", debouncedChangeHandler);
- mapRef?.getMap().on("move", debouncedChangeHandler);
- mapRef?.getMap().on("resize", debouncedChangeHandler);
- }, [debouncedChangeHandler, mapRef]);
-
- const { clusters, supercluster } = useSupercluster({
- points,
- bounds,
- zoom,
- options,
- });
-
- const clusterOnClick = useCallback(
- (cluster: DeviceClusterProperties) => {
- // supercluster from hook can be null or undefined
- if (!supercluster) return;
-
- const [longitude, latitude] = cluster.geometry.coordinates;
-
- const expansionZoom = Math.min(
- supercluster.getClusterExpansionZoom(cluster.id as number),
- 20
- );
-
- mapRef?.getMap().flyTo({
- center: [longitude, latitude],
- animate: true,
- speed: 1.6,
- zoom: expansionZoom,
- essential: true,
- });
- },
- [mapRef, supercluster]
- );
-
- const clusterMarker = useMemo(() => {
- return clusters.map((cluster) => {
- // every cluster point has coordinates
- const [longitude, latitude] = cluster.geometry.coordinates;
- // the point may be either a cluster or a crime point
- const { cluster: isCluster } = cluster.properties;
-
- // we have a cluster to render
- if (isCluster) {
- return (
-
-
-
- );
- }
-
- // we have a single device to render
- return (
-
- );
- });
- }, [clusterOnClick, clusters]);
-
- return <>{clusterMarker}>;
+ const { osem: mapRef } = useMap()
+
+ // the viewport bounds and zoom level
+ const [bounds, setBounds] = useState(
+ mapRef?.getMap().getBounds().toArray().flat() as BBox,
+ )
+ const [zoom, setZoom] = useState(mapRef?.getZoom() || 0)
+
+ // get clusters
+ const points: PointFeature[] = useMemo(() => {
+ return devices.features.map((device) => ({
+ type: 'Feature',
+ properties: {
+ cluster: false,
+ ...device.properties,
+ },
+ geometry: device.geometry,
+ }))
+ }, [devices.features])
+
+ // get bounds and zoom level from the map
+ // debounce the change handler to prevent too many updates
+ const debouncedChangeHandler = debounce(() => {
+ if (!mapRef) return
+ setBounds(mapRef.getMap().getBounds().toArray().flat() as BBox)
+ setZoom(mapRef.getZoom())
+ }, DEBOUNCE_VALUE)
+
+ // register the debounced change handler to map events
+ useEffect(() => {
+ if (!mapRef) return
+
+ mapRef?.getMap().on('load', debouncedChangeHandler)
+ mapRef?.getMap().on('zoom', debouncedChangeHandler)
+ mapRef?.getMap().on('move', debouncedChangeHandler)
+ mapRef?.getMap().on('resize', debouncedChangeHandler)
+ }, [debouncedChangeHandler, mapRef])
+
+ const { clusters, supercluster } = useSupercluster({
+ points,
+ bounds,
+ zoom,
+ options,
+ })
+
+ const clusterOnClick = useCallback(
+ (cluster: DeviceClusterProperties) => {
+ // supercluster from hook can be null or undefined
+ if (!supercluster) return
+
+ const [longitude, latitude] = cluster.geometry.coordinates
+
+ const expansionZoom = Math.min(
+ supercluster.getClusterExpansionZoom(cluster.id as number),
+ 20,
+ )
+
+ mapRef?.getMap().flyTo({
+ center: [longitude, latitude],
+ animate: true,
+ speed: 1.6,
+ zoom: expansionZoom,
+ essential: true,
+ })
+ },
+ [mapRef, supercluster],
+ )
+
+ const clusterMarker = useMemo(() => {
+ return clusters.map((cluster) => {
+ // every cluster point has coordinates
+ const [longitude, latitude] = cluster.geometry.coordinates
+ // the point may be either a cluster or a crime point
+ const { cluster: isCluster } = cluster.properties
+
+ // we have a cluster to render
+ if (isCluster) {
+ return (
+
+
+
+ )
+ }
+
+ // we have a single device to render
+ return (
+
+ )
+ })
+ }, [clusterOnClick, clusters])
+
+ return <>{clusterMarker}>
}
diff --git a/app/components/map/layers/cluster/donut-chart-cluster.tsx b/app/components/map/layers/cluster/donut-chart-cluster.tsx
index c00fc37c8..f20f38564 100644
--- a/app/components/map/layers/cluster/donut-chart-cluster.tsx
+++ b/app/components/map/layers/cluster/donut-chart-cluster.tsx
@@ -1,102 +1,102 @@
-import { type DeviceClusterProperties } from "~/routes/explore";
+import { type DeviceClusterProperties } from '~/routes/explore'
type DonutChartClusterType = {
- cluster: any;
- clusterOnClick: (cluster: DeviceClusterProperties) => void;
-};
+ cluster: any
+ clusterOnClick: (cluster: DeviceClusterProperties) => void
+}
// colors to use for the categories
const colors = [
- { color: "#4EAF47", opacity: 1 },
- { color: "#575757", opacity: 0.65 },
- { color: "#575757", opacity: 0.65 },
- { color: "#38AADD", opacity: 1 },
-];
+ { color: '#4EAF47', opacity: 1 },
+ { color: '#575757', opacity: 0.65 },
+ { color: '#575757', opacity: 0.65 },
+ { color: '#38AADD', opacity: 1 },
+]
export default function DonutChartCluster({
- cluster,
- clusterOnClick,
+ cluster,
+ clusterOnClick,
}: DonutChartClusterType) {
- const [theme] = "light"; //useTheme();
- const { categories, point_count: pointCount } = cluster.properties;
- const { active = 0, inactive = 0, old = 0 } = categories;
- const counts: number[] = [active, inactive, old];
- const offsets: number[] = [];
- let total = 0;
- for (const count of counts) {
- offsets.push(total);
- total += count;
- }
+ const [theme] = 'light' //useTheme();
+ const { categories, point_count: pointCount } = cluster.properties
+ const { active = 0, inactive = 0, old = 0 } = categories
+ const counts: number[] = [active, inactive, old]
+ const offsets: number[] = []
+ let total = 0
+ for (const count of counts) {
+ offsets.push(total)
+ total += count
+ }
- const fontSize =
- pointCount >= 1000
- ? 14
- : pointCount >= 100
- ? 12
- : pointCount >= 10
- ? 10
- : 10;
- const r =
- pointCount >= 1000
- ? 36
- : pointCount >= 100
- ? 20
- : pointCount >= 10
- ? 18
- : 18;
- const r0 = Math.round(r * 0.7);
- const w = r * 2;
+ const fontSize =
+ pointCount >= 1000
+ ? 14
+ : pointCount >= 100
+ ? 12
+ : pointCount >= 10
+ ? 10
+ : 10
+ const r =
+ pointCount >= 1000
+ ? 36
+ : pointCount >= 100
+ ? 20
+ : pointCount >= 10
+ ? 18
+ : 18
+ const r0 = Math.round(r * 0.7)
+ const w = r * 2
- return (
- clusterOnClick(cluster)}>
-
- {counts.map((count, i) => {
- const start = offsets[i] / total;
- let end = (offsets[i] + count) / total;
+ return (
+ clusterOnClick(cluster)}>
+
+ {counts.map((count, i) => {
+ const start = offsets[i] / total
+ let end = (offsets[i] + count) / total
- if (end - start === 1) end -= 0.00001;
- const a0 = 2 * Math.PI * (start - 0.25);
- const a1 = 2 * Math.PI * (end - 0.25);
- const x0 = Math.cos(a0),
- y0 = Math.sin(a0);
- const x1 = Math.cos(a1),
- y1 = Math.sin(a1);
- const largeArc = end - start > 0.5 ? 1 : 0;
+ if (end - start === 1) end -= 0.00001
+ const a0 = 2 * Math.PI * (start - 0.25)
+ const a1 = 2 * Math.PI * (end - 0.25)
+ const x0 = Math.cos(a0),
+ y0 = Math.sin(a0)
+ const x1 = Math.cos(a1),
+ y1 = Math.sin(a1)
+ const largeArc = end - start > 0.5 ? 1 : 0
- return (
-
- );
- })}
-
-
- {pointCount}
-
-
-
- );
+ return (
+
+ )
+ })}
+
+
+ {pointCount}
+
+
+
+ )
}
diff --git a/app/components/map/layers/mobile/color-palette.ts b/app/components/map/layers/mobile/color-palette.ts
index 4b757024d..7109a2370 100644
--- a/app/components/map/layers/mobile/color-palette.ts
+++ b/app/components/map/layers/mobile/color-palette.ts
@@ -1,17 +1,17 @@
-import chroma from "chroma-js";
+import chroma from 'chroma-js'
-export const LOW_COLOR = "#0740ba";
-export const HIGH_COLOR = "#97a6c7";
+export const LOW_COLOR = '#0740ba'
+export const HIGH_COLOR = '#97a6c7'
export const createPalette = (
- min: number,
- max: number,
- minColor = LOW_COLOR,
- maxColor = HIGH_COLOR,
-) => chroma.scale([minColor, maxColor]).domain([min, max]);
+ min: number,
+ max: number,
+ minColor = LOW_COLOR,
+ maxColor = HIGH_COLOR,
+) => chroma.scale([minColor, maxColor]).domain([min, max])
export function calculateColorRange(baseColor: string) {
- const lowColor = chroma(baseColor).darken(1.5).hex(); // Darken the base color for LOW
- const highColor = chroma(baseColor).brighten(1.5).hex(); // Brighten the base color for HIGH
- return { lowColor, highColor };
+ const lowColor = chroma(baseColor).darken(1.5).hex() // Darken the base color for LOW
+ const highColor = chroma(baseColor).brighten(1.5).hex() // Brighten the base color for HIGH
+ return { lowColor, highColor }
}
diff --git a/app/components/map/layers/mobile/mobile-box-layer.tsx b/app/components/map/layers/mobile/mobile-box-layer.tsx
index 03d1181bb..4cd0d4b6e 100644
--- a/app/components/map/layers/mobile/mobile-box-layer.tsx
+++ b/app/components/map/layers/mobile/mobile-box-layer.tsx
@@ -1,208 +1,208 @@
-import bbox from "@turf/bbox";
+import bbox from '@turf/bbox'
import {
- featureCollection,
- lineString,
- multiLineString,
- point,
-} from "@turf/helpers";
-import { type MultiLineString, type Point } from "geojson";
-import mapboxgl from "mapbox-gl";
-import { createContext, useContext, useEffect, useRef, useState } from "react";
-import { Layer, Source, useMap } from "react-map-gl";
-import { HIGH_COLOR, LOW_COLOR, createPalette } from "./color-palette";
-import { type Sensor } from "~/schema";
+ featureCollection,
+ lineString,
+ multiLineString,
+ point,
+} from '@turf/helpers'
+import { type MultiLineString, type Point } from 'geojson'
+import mapboxgl from 'mapbox-gl'
+import { createContext, useContext, useEffect, useRef, useState } from 'react'
+import { Layer, Source, useMap } from 'react-map-gl'
+import { HIGH_COLOR, LOW_COLOR, createPalette } from './color-palette'
+import { type Sensor } from '~/schema'
interface CustomGeoJsonProperties {
- locationId: number;
- value: number;
- createdAt: Date;
- color: string;
+ locationId: number
+ value: number
+ createdAt: Date
+ color: string
}
export const HoveredPointContext = createContext({
- hoveredPoint: null,
- setHoveredPoint: (_point: number | null) => {},
-});
+ hoveredPoint: null,
+ setHoveredPoint: (_point: number | null) => {},
+})
export default function MobileBoxLayer({
- sensor,
- minColor = LOW_COLOR,
- maxColor = HIGH_COLOR,
+ sensor,
+ minColor = LOW_COLOR,
+ maxColor = HIGH_COLOR,
}: {
- sensor: Sensor;
- minColor?:
- | mapboxgl.CirclePaint["circle-color"]
- | mapboxgl.LinePaint["line-color"];
- maxColor?:
- | mapboxgl.CirclePaint["circle-color"]
- | mapboxgl.LinePaint["line-color"];
+ sensor: Sensor
+ minColor?:
+ | mapboxgl.CirclePaint['circle-color']
+ | mapboxgl.LinePaint['line-color']
+ maxColor?:
+ | mapboxgl.CirclePaint['circle-color']
+ | mapboxgl.LinePaint['line-color']
}) {
- const [sourceData, setSourceData] = useState();
- const { hoveredPoint, setHoveredPoint } = useContext(HoveredPointContext);
- const popupRef = useRef(null);
-
- const { osem: mapRef } = useMap();
-
- useEffect(() => {
- const sensorData = sensor.data! as unknown as {
- value: String;
- location: { x: number; y: number; id: number };
- createdAt: Date;
- }[];
-
- // create color palette from min and max values
- const minValue = Math.min(...sensorData.map((d) => Number(d.value)));
- const maxValue = Math.max(...sensorData.map((d) => Number(d.value)));
- const palette = createPalette(
- minValue,
- maxValue,
- minColor as string,
- maxColor as string,
- );
-
- // generate points from the sensor data
- // apply color from palette
- const points = sensorData.map((measurement) => {
- const tempPoint = point(
- [measurement.location.x, measurement.location.y],
- {
- value: Number(measurement.value),
- createdAt: new Date(measurement.createdAt),
- color: palette(Number(measurement.value)).hex(),
- locationId: measurement.location.id,
- },
- );
- return tempPoint;
- });
-
- if (points.length === 0) return;
-
- // generate a line from the points
- const line = lineString(points.map((point) => point.geometry.coordinates));
- const lines = multiLineString([line.geometry.coordinates]);
-
- setSourceData(
- featureCollection([...points, lines]),
- );
- }, [maxColor, minColor, sensor.data]);
-
- useEffect(() => {
- if (!mapRef || !sourceData) return;
-
- const bounds = bbox(sourceData).slice(0, 4) as [
- number,
- number,
- number,
- number,
- ];
- mapRef.fitBounds(bounds, {
- padding: {
- top: 100,
- bottom: 400,
- left: 500,
- right: 100,
- },
- });
- }, [mapRef, sourceData]);
-
- useEffect(() => {
- if (!mapRef) return;
-
- const map = mapRef.getMap();
-
- map.on("mousemove", "box-layer-point", (e) => {
- if (!e.features || e.features.length === 0) return;
-
- const feature = e.features[0];
- const { locationId } = feature.properties as CustomGeoJsonProperties;
-
- setHoveredPoint(locationId); // Update hoveredPoint dynamically
- });
-
- map.on("mouseleave", "box-layer-point", () => {
- setHoveredPoint(null); // Clear hoveredPoint
- });
- }, [mapRef, setHoveredPoint]);
-
- useEffect(() => {
- if (!mapRef) return;
-
- const map = mapRef.getMap();
-
- // Cleanup previous popup
- if (popupRef.current) {
- popupRef.current.remove();
- popupRef.current = null;
- }
-
- if (hoveredPoint !== null) {
- const feature = sourceData?.features.find(
- (feat) => feat.properties?.locationId === hoveredPoint,
- );
-
- if (feature && feature.geometry.type === "Point") {
- const { coordinates } = feature.geometry;
- const { value } = feature.properties as CustomGeoJsonProperties;
-
- popupRef.current = new mapboxgl.Popup({
- closeButton: false,
- closeOnClick: false,
- className: "highlight-popup",
- })
- .setLngLat(coordinates as [number, number])
- .setHTML(
- `
+ const [sourceData, setSourceData] = useState
()
+ const { hoveredPoint, setHoveredPoint } = useContext(HoveredPointContext)
+ const popupRef = useRef(null)
+
+ const { osem: mapRef } = useMap()
+
+ useEffect(() => {
+ const sensorData = sensor.data! as unknown as {
+ value: String
+ location: { x: number; y: number; id: number }
+ createdAt: Date
+ }[]
+
+ // create color palette from min and max values
+ const minValue = Math.min(...sensorData.map((d) => Number(d.value)))
+ const maxValue = Math.max(...sensorData.map((d) => Number(d.value)))
+ const palette = createPalette(
+ minValue,
+ maxValue,
+ minColor as string,
+ maxColor as string,
+ )
+
+ // generate points from the sensor data
+ // apply color from palette
+ const points = sensorData.map((measurement) => {
+ const tempPoint = point(
+ [measurement.location.x, measurement.location.y],
+ {
+ value: Number(measurement.value),
+ createdAt: new Date(measurement.createdAt),
+ color: palette(Number(measurement.value)).hex(),
+ locationId: measurement.location.id,
+ },
+ )
+ return tempPoint
+ })
+
+ if (points.length === 0) return
+
+ // generate a line from the points
+ const line = lineString(points.map((point) => point.geometry.coordinates))
+ const lines = multiLineString([line.geometry.coordinates])
+
+ setSourceData(
+ featureCollection([...points, lines]),
+ )
+ }, [maxColor, minColor, sensor.data])
+
+ useEffect(() => {
+ if (!mapRef || !sourceData) return
+
+ const bounds = bbox(sourceData).slice(0, 4) as [
+ number,
+ number,
+ number,
+ number,
+ ]
+ mapRef.fitBounds(bounds, {
+ padding: {
+ top: 100,
+ bottom: 400,
+ left: 500,
+ right: 100,
+ },
+ })
+ }, [mapRef, sourceData])
+
+ useEffect(() => {
+ if (!mapRef) return
+
+ const map = mapRef.getMap()
+
+ map.on('mousemove', 'box-layer-point', (e) => {
+ if (!e.features || e.features.length === 0) return
+
+ const feature = e.features[0]
+ const { locationId } = feature.properties as CustomGeoJsonProperties
+
+ setHoveredPoint(locationId) // Update hoveredPoint dynamically
+ })
+
+ map.on('mouseleave', 'box-layer-point', () => {
+ setHoveredPoint(null) // Clear hoveredPoint
+ })
+ }, [mapRef, setHoveredPoint])
+
+ useEffect(() => {
+ if (!mapRef) return
+
+ const map = mapRef.getMap()
+
+ // Cleanup previous popup
+ if (popupRef.current) {
+ popupRef.current.remove()
+ popupRef.current = null
+ }
+
+ if (hoveredPoint !== null) {
+ const feature = sourceData?.features.find(
+ (feat) => feat.properties?.locationId === hoveredPoint,
+ )
+
+ if (feature && feature.geometry.type === 'Point') {
+ const { coordinates } = feature.geometry
+ const { value } = feature.properties as CustomGeoJsonProperties
+
+ popupRef.current = new mapboxgl.Popup({
+ closeButton: false,
+ closeOnClick: false,
+ className: 'highlight-popup',
+ })
+ .setLngLat(coordinates as [number, number])
+ .setHTML(
+ `
${sensor.title}
${value}${sensor.unit}
`,
- )
- .addTo(map);
- }
- } else if (popupRef.current) {
- (popupRef.current as mapboxgl.Popup).remove();
- popupRef.current = null;
- }
- }, [hoveredPoint, sourceData, mapRef, sensor.title, sensor.unit]);
-
- if (!sourceData) return null;
-
- return (
- <>
-
-
-
-
-
- >
- );
+ )
+ .addTo(map)
+ }
+ } else if (popupRef.current) {
+ ;(popupRef.current as mapboxgl.Popup).remove()
+ popupRef.current = null
+ }
+ }, [hoveredPoint, sourceData, mapRef, sensor.title, sensor.unit])
+
+ if (!sourceData) return null
+
+ return (
+ <>
+
+
+
+
+
+ >
+ )
}
diff --git a/app/components/map/layers/mobile/mobile-box-view.tsx b/app/components/map/layers/mobile/mobile-box-view.tsx
index 4bb8f217d..bce575415 100644
--- a/app/components/map/layers/mobile/mobile-box-view.tsx
+++ b/app/components/map/layers/mobile/mobile-box-view.tsx
@@ -1,154 +1,152 @@
-import { ArrowDownUp } from "lucide-react";
-import { useEffect, useRef, useState } from "react";
-import { calculateColorRange } from "./color-palette";
-import MobileBoxLayer from "./mobile-box-layer";
-import { Button } from "~/components/ui/button";
-import { type Sensor } from "~/schema";
+import { ArrowDownUp } from 'lucide-react'
+import { useEffect, useRef, useState } from 'react'
+import { calculateColorRange } from './color-palette'
+import MobileBoxLayer from './mobile-box-layer'
+import { Button } from '~/components/ui/button'
+import { type Sensor } from '~/schema'
interface SensorWithColor extends Sensor {
- color: string; // Add the color property
+ color: string // Add the color property
}
export default function MobileBoxView({
- sensors: initialSensors,
+ sensors: initialSensors,
}: {
- sensors: SensorWithColor[];
+ sensors: SensorWithColor[]
}) {
- console.log("🚀 ~ sensors:", initialSensors)
- const [sensors, setSensors] = useState(initialSensors);
- console.log("🚀 ~ sensors:", sensors)
+ console.log('🚀 ~ sensors:', initialSensors)
+ const [sensors, setSensors] = useState(initialSensors)
+ console.log('🚀 ~ sensors:', sensors)
- useEffect(() => {
- setSensors(initialSensors);
- }, [initialSensors]);
-
-
+ useEffect(() => {
+ setSensors(initialSensors)
+ }, [initialSensors])
- // Toggle the order of sensors
- const switchSensors = () => {
- setSensors((prevSensors) => [...prevSensors].reverse());
- };
+ // Toggle the order of sensors
+ const switchSensors = () => {
+ setSensors((prevSensors) => [...prevSensors].reverse())
+ }
- return (
-
- {sensors.map((sensor, index) => (
-
- {index === 1 && sensors.length === 2 && (
-
-
-
- )}
-
-
- ))}
-
- );
+ return (
+
+ {sensors.map((sensor, index) => (
+
+ {index === 1 && sensors.length === 2 && (
+
+
+
+ )}
+
+
+ ))}
+
+ )
}
function SensorView({
- sensor,
- index,
+ sensor,
+ index,
}: {
- sensor: SensorWithColor;
- index: number;
+ sensor: SensorWithColor
+ index: number
}) {
- const { lowColor, highColor } = calculateColorRange(sensor.color); // Calculate dynamically
+ const { lowColor, highColor } = calculateColorRange(sensor.color) // Calculate dynamically
- const [minColor, setMinColor] = useState(lowColor);
- const [maxColor, setMaxColor] = useState(highColor);
+ const [minColor, setMinColor] = useState(lowColor)
+ const [maxColor, setMaxColor] = useState(highColor)
- return (
- <>
- {
- setMinColor(min);
- setMaxColor(max);
- }}
- />
- {index < 1 && (
-
- )}
- >
- );
+ return (
+ <>
+ {
+ setMinColor(min)
+ setMaxColor(max)
+ }}
+ />
+ {index < 1 && (
+
+ )}
+ >
+ )
}
function Legend({
- sensor,
- onColorChange,
+ sensor,
+ onColorChange,
}: {
- sensor: SensorWithColor;
- onColorChange?: (min: string, max: string) => void;
+ sensor: SensorWithColor
+ onColorChange?: (min: string, max: string) => void
}) {
- const { lowColor, highColor } = calculateColorRange(sensor.color);
+ const { lowColor, highColor } = calculateColorRange(sensor.color)
- const minColorInputRef = useRef(null);
- const maxColorInputRef = useRef(null);
+ const minColorInputRef = useRef(null)
+ const maxColorInputRef = useRef(null)
- const [minColor, setMinColor] = useState(lowColor);
- const [maxColor, setMaxColor] = useState(highColor);
+ const [minColor, setMinColor] = useState(lowColor)
+ const [maxColor, setMaxColor] = useState(highColor)
- useEffect(() => {
- onColorChange && onColorChange(minColor, maxColor);
- }, [minColor, maxColor, onColorChange]);
+ useEffect(() => {
+ onColorChange && onColorChange(minColor, maxColor)
+ }, [minColor, maxColor, onColorChange])
- const sensorData = sensor.data! as unknown as {
- value: String;
- location: { x: number; y: number; id: number };
- createdAt: Date;
- }[];
+ const sensorData = sensor.data! as unknown as {
+ value: String
+ location: { x: number; y: number; id: number }
+ createdAt: Date
+ }[]
- const minValue = Math.min(...sensorData.map((d) => Number(d.value)));
- const maxValue = Math.max(...sensorData.map((d) => Number(d.value)));
+ const minValue = Math.min(...sensorData.map((d) => Number(d.value)))
+ const maxValue = Math.max(...sensorData.map((d) => Number(d.value)))
- return (
-
- );
+ return (
+
+ )
}
diff --git a/app/components/map/layers/mobile/mobile-overview-layer.tsx b/app/components/map/layers/mobile/mobile-overview-layer.tsx
index 97c5fd63f..6afb59a44 100644
--- a/app/components/map/layers/mobile/mobile-overview-layer.tsx
+++ b/app/components/map/layers/mobile/mobile-overview-layer.tsx
@@ -18,23 +18,33 @@ const CLUSTER_DISTANCE_METERS = 8 // Distance threshold for clustering
const MIN_CLUSTER_SIZE = 15 // Minimum points to form a cluster
// Function to calculate distance between two points in meters
-function calculateDistance(point1: LocationPoint, point2: LocationPoint): number {
+function calculateDistance(
+ point1: LocationPoint,
+ point2: LocationPoint,
+): number {
const R = 6371000 // Earth's radius in meters
const lat1Rad = (point1.geometry.y * Math.PI) / 180
const lat2Rad = (point2.geometry.y * Math.PI) / 180
const deltaLatRad = ((point2.geometry.y - point1.geometry.y) * Math.PI) / 180
const deltaLonRad = ((point2.geometry.x - point1.geometry.x) * Math.PI) / 180
- const a = Math.sin(deltaLatRad / 2) * Math.sin(deltaLatRad / 2) +
- Math.cos(lat1Rad) * Math.cos(lat2Rad) *
- Math.sin(deltaLonRad / 2) * Math.sin(deltaLonRad / 2)
+ const a =
+ Math.sin(deltaLatRad / 2) * Math.sin(deltaLatRad / 2) +
+ Math.cos(lat1Rad) *
+ Math.cos(lat2Rad) *
+ Math.sin(deltaLonRad / 2) *
+ Math.sin(deltaLonRad / 2)
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
return R * c
}
// Cluster points within a single trip
-function clusterTripPoints(points: LocationPoint[], distanceThreshold: number, minClusterSize: number) {
+function clusterTripPoints(
+ points: LocationPoint[],
+ distanceThreshold: number,
+ minClusterSize: number,
+) {
const clusters: LocationPoint[][] = []
const visited = new Set()
@@ -60,7 +70,7 @@ function clusterTripPoints(points: LocationPoint[], distanceThreshold: number, m
clusters.push(cluster)
} else {
// Add individual points that don't form clusters
- cluster.forEach(point => clusters.push([point]))
+ cluster.forEach((point) => clusters.push([point]))
}
}
@@ -69,12 +79,16 @@ function clusterTripPoints(points: LocationPoint[], distanceThreshold: number, m
// Calculate cluster center and metadata
function calculateClusterCenter(cluster: LocationPoint[], clusterId: string) {
- const centerX = cluster.reduce((sum, point) => sum + point.geometry.x, 0) / cluster.length
- const centerY = cluster.reduce((sum, point) => sum + point.geometry.y, 0) / cluster.length
-
+ const centerX =
+ cluster.reduce((sum, point) => sum + point.geometry.x, 0) / cluster.length
+ const centerY =
+ cluster.reduce((sum, point) => sum + point.geometry.y, 0) / cluster.length
+
// Sort by timestamp to get earliest and latest
- const sortedByTime = cluster.sort((a, b) => new Date(a.time).getTime() - new Date(b.time).getTime())
-
+ const sortedByTime = cluster.sort(
+ (a, b) => new Date(a.time).getTime() - new Date(b.time).getTime(),
+ )
+
return {
coordinates: [centerX, centerY],
pointCount: cluster.length,
@@ -82,7 +96,7 @@ function calculateClusterCenter(cluster: LocationPoint[], clusterId: string) {
endTime: sortedByTime[sortedByTime.length - 1].time,
isCluster: cluster.length > 1,
clusterId: clusterId,
- originalPoints: cluster // Keep reference to original points
+ originalPoints: cluster, // Keep reference to original points
}
}
@@ -115,20 +129,27 @@ export default function MobileOverviewLayer({
if (!trips || trips.length === 0) return []
return trips.map((trip, tripIndex) => {
- const clusters = clusterTripPoints(trip.points, CLUSTER_DISTANCE_METERS, MIN_CLUSTER_SIZE)
-
+ const clusters = clusterTripPoints(
+ trip.points,
+ CLUSTER_DISTANCE_METERS,
+ MIN_CLUSTER_SIZE,
+ )
+
return {
...trip,
- clusters: clusters.map((cluster, clusterIndex) =>
- calculateClusterCenter(cluster, `trip-${tripIndex}-cluster-${clusterIndex}`)
- )
+ clusters: clusters.map((cluster, clusterIndex) =>
+ calculateClusterCenter(
+ cluster,
+ `trip-${tripIndex}-cluster-${clusterIndex}`,
+ ),
+ ),
}
})
}, [trips])
const [sourceData, setSourceData] = useState | null>(null)
- const [expandedSourceData, setExpandedSourceData] = useState | null>(null)
-
+ const [expandedSourceData, setExpandedSourceData] =
+ useState | null>(null)
+
const { osem: mapRef } = useMap()
// Legend items state
@@ -172,7 +194,7 @@ export default function MobileOverviewLayer({
pointCount?: number
isCluster?: boolean
} | null>(null)
-
+
const [showOriginalColors, setShowOriginalColors] = useState(true)
useEffect(() => {
@@ -200,16 +222,16 @@ export default function MobileOverviewLayer({
const expandedPoints = clusteredTrips.flatMap((trip, tripIndex) =>
trip.clusters.flatMap((cluster) => {
if (!cluster.isCluster || !cluster.originalPoints) return []
-
+
return cluster.originalPoints.map((originalPoint) =>
point([originalPoint.geometry.x, originalPoint.geometry.y], {
color: colors[tripIndex],
tripNumber: tripIndex + 1,
timestamp: originalPoint.time,
clusterId: cluster.clusterId,
- })
+ }),
)
- })
+ }),
)
// Set legend items for the trips
@@ -253,7 +275,14 @@ export default function MobileOverviewLayer({
if (event.features && event.features.length > 0) {
const feature = event.features[0]
- const { tripNumber, startTime, endTime, pointCount, isCluster, clusterId } = feature.properties
+ const {
+ tripNumber,
+ startTime,
+ endTime,
+ pointCount,
+ isCluster,
+ clusterId,
+ } = feature.properties
setHighlightedTrip(tripNumber)
// Set hovered cluster if it's a cluster
@@ -264,13 +293,13 @@ export default function MobileOverviewLayer({
}
const [longitude, latitude] = feature.geometry.coordinates
- setPopupInfo({
- longitude,
- latitude,
- startTime,
- endTime,
+ setPopupInfo({
+ longitude,
+ latitude,
+ startTime,
+ endTime,
pointCount,
- isCluster
+ isCluster,
})
} else {
setHighlightedTrip(null)
@@ -329,39 +358,33 @@ export default function MobileOverviewLayer({
'interpolate',
['linear'],
['get', 'pointCount'],
- 1, 5, // Single point: radius 5
- 10, 8, // 10 points: radius 8
- 50, 12, // 50 points: radius 12
- 100, 16 // 100+ points: radius 16
+ 1,
+ 5, // Single point: radius 5
+ 10,
+ 8, // 10 points: radius 8
+ 50,
+ 12, // 50 points: radius 12
+ 100,
+ 16, // 100+ points: radius 16
],
- 3 // Non-cluster points: radius 3
+ 3, // Non-cluster points: radius 3
],
'circle-opacity': showOriginalColors
- ? [
- 'case',
- ['==', ['get', 'tripNumber'], highlightedTrip],
- 1,
- 0.5,
- ]
+ ? ['case', ['==', ['get', 'tripNumber'], highlightedTrip], 1, 0.5]
: 1,
'circle-stroke-width': [
'case',
['get', 'isCluster'],
2, // Clusters have stroke
- 0 // Individual points don't
+ 0, // Individual points don't
],
'circle-stroke-color': '#222',
'circle-stroke-opacity': showOriginalColors
- ? [
- 'case',
- ['==', ['get', 'tripNumber'], highlightedTrip],
- 1,
- 0.3,
- ]
+ ? ['case', ['==', ['get', 'tripNumber'], highlightedTrip], 1, 0.3]
: 1,
}}
/>
-
+
{/* Text layer for cluster point counts */}
@@ -390,7 +408,11 @@ export default function MobileOverviewLayer({
{/* Expanded cluster points - shown on hover */}
{hoveredCluster && expandedSourceData && (
-
+
)}
-
+
{highlightedTrip && popupInfo && (
- {popupInfo.isCluster && popupInfo.startTime !== popupInfo.endTime && (
-
-
- To
-
-
- {format(new Date(popupInfo.endTime), 'Pp')}
-
-
- )}
+ {popupInfo.isCluster &&
+ popupInfo.startTime !== popupInfo.endTime && (
+
+
+ To
+
+
+ {format(new Date(popupInfo.endTime), 'Pp')}
+
+
+ )}
)}
@@ -462,4 +485,3 @@ export default function MobileOverviewLayer({
>
)
}
-
diff --git a/app/components/map/legend.tsx b/app/components/map/legend.tsx
index c905f4af0..cf30e5eaf 100644
--- a/app/components/map/legend.tsx
+++ b/app/components/map/legend.tsx
@@ -67,7 +67,7 @@ export default function Legend({ title, values }: LegendProps) {
return (
(
top: 0,
left: 0,
}}
- touchZoomRotate={{around: 'center'}}
+ touchZoomRotate={{ around: 'center' }}
onLoad={handleMapLoad}
{...props}
>
diff --git a/app/components/mydevices/dt/data-table.tsx b/app/components/mydevices/dt/data-table.tsx
index fb1f8e39c..51711a664 100644
--- a/app/components/mydevices/dt/data-table.tsx
+++ b/app/components/mydevices/dt/data-table.tsx
@@ -47,32 +47,32 @@ export function DataTable
({
data,
}: DataTableProps) {
const [sorting, setSorting] = React.useState([
- { id: 'createdAt', desc: true }
+ { id: 'createdAt', desc: true },
])
const [columnFilters, setColumnFilters] = React.useState(
[],
)
- const table = useReactTable({
- data,
- columns,
- getCoreRowModel: getCoreRowModel(),
- getPaginationRowModel: getPaginationRowModel(),
- onSortingChange: setSorting,
- getSortedRowModel: getSortedRowModel(),
- onColumnFiltersChange: setColumnFilters,
- getFilteredRowModel: getFilteredRowModel(),
- state: {
- sorting,
- columnFilters,
- },
- initialState: {
- pagination: {
- pageSize: 5,
- },
- },
- });
- const tableColsWidth = [30, 30, 30, 40];
+ const table = useReactTable({
+ data,
+ columns,
+ getCoreRowModel: getCoreRowModel(),
+ getPaginationRowModel: getPaginationRowModel(),
+ onSortingChange: setSorting,
+ getSortedRowModel: getSortedRowModel(),
+ onColumnFiltersChange: setColumnFilters,
+ getFilteredRowModel: getFilteredRowModel(),
+ state: {
+ sorting,
+ columnFilters,
+ },
+ initialState: {
+ pagination: {
+ pageSize: 5,
+ },
+ },
+ })
+ const tableColsWidth = [30, 30, 30, 40]
const { t } = useTranslation('data-table')
@@ -133,7 +133,7 @@ export function DataTable({
{t('no_results')}
diff --git a/app/components/mydevices/edit-device/edit-device-sidebar-nav.tsx b/app/components/mydevices/edit-device/edit-device-sidebar-nav.tsx
index cf81b28d7..1a93dfdca 100644
--- a/app/components/mydevices/edit-device/edit-device-sidebar-nav.tsx
+++ b/app/components/mydevices/edit-device/edit-device-sidebar-nav.tsx
@@ -1,44 +1,44 @@
-import { useLocation, Link } from "react-router";
-import { buttonVariants } from "~/components/ui/button";
-import { cn } from "~/lib/utils";
+import { useLocation, Link } from 'react-router'
+import { buttonVariants } from '~/components/ui/button'
+import { cn } from '~/lib/utils'
interface SidebarNavProps extends React.HTMLAttributes {
- items: {
- href: string;
- title: string;
- icon: any;
- }[];
+ items: {
+ href: string
+ title: string
+ icon: any
+ }[]
}
export function EditDviceSidebarNav({
- className,
- items,
- ...props
+ className,
+ items,
+ ...props
}: SidebarNavProps) {
- const pathname = useLocation().pathname;
- return (
-
- {items.map((item) => (
-
- {item.title}
-
- ))}
-
- );
+ const pathname = useLocation().pathname
+ return (
+
+ {items.map((item) => (
+
+ {item.title}
+
+ ))}
+
+ )
}
diff --git a/app/components/search/index.tsx b/app/components/search/index.tsx
index d04577770..bbac18590 100644
--- a/app/components/search/index.tsx
+++ b/app/components/search/index.tsx
@@ -1,127 +1,127 @@
-import getUserLocale from "get-user-locale";
-import { useEffect, useState } from "react";
-import { useTranslation, Trans } from "react-i18next";
-import SearchList from "./search-list";
+import getUserLocale from 'get-user-locale'
+import { useEffect, useState } from 'react'
+import { useTranslation, Trans } from 'react-i18next'
+import SearchList from './search-list'
interface SearchProps {
- searchString: string;
- devices: any;
+ searchString: string
+ devices: any
}
export default function Search(props: SearchProps) {
- let { t } = useTranslation("search");
+ let { t } = useTranslation('search')
- const userLocaleString = getUserLocale()?.toString() || "en";
- const [searchResultsLocation, setSearchResultsLocation] = useState([]);
- const [searchResultsDevice, setSearchResultsDevice] = useState([]);
+ const userLocaleString = getUserLocale()?.toString() || 'en'
+ const [searchResultsLocation, setSearchResultsLocation] = useState([])
+ const [searchResultsDevice, setSearchResultsDevice] = useState([])
- /**
- * One of the functions that is called when the user types in the search bar. It returns the search results for locations, retrived from the mapbox geocode API.
- *
- * @param searchstring string to search for locations on mapbox geocode API
- */
- function getLocations(searchstring: string) {
- var url: URL = new URL(ENV.MAPBOX_GEOCODING_API + `${searchstring}.json`);
+ /**
+ * One of the functions that is called when the user types in the search bar. It returns the search results for locations, retrived from the mapbox geocode API.
+ *
+ * @param searchstring string to search for locations on mapbox geocode API
+ */
+ function getLocations(searchstring: string) {
+ var url: URL = new URL(ENV.MAPBOX_GEOCODING_API + `${searchstring}.json`)
- url.search = new URLSearchParams({
- access_token: `${ENV.MAPBOX_ACCESS_TOKEN}`,
- limit: "4",
- language: userLocaleString,
- }).toString();
+ url.search = new URLSearchParams({
+ access_token: `${ENV.MAPBOX_ACCESS_TOKEN}`,
+ limit: '4',
+ language: userLocaleString,
+ }).toString()
- var requestOptions: RequestInit = {
- method: "GET",
- redirect: "follow",
- };
+ var requestOptions: RequestInit = {
+ method: 'GET',
+ redirect: 'follow',
+ }
- fetch(url, requestOptions)
- .then((response) => response.json())
- .then((data) => {
- if (data.features.length === 0) {
- setSearchResultsLocation([]);
- } else {
- data.features.forEach((feature: any) => {
- feature.type = "location";
- });
- setSearchResultsLocation(data.features);
- }
- })
- .catch((error) => console.log("error", error));
- }
+ fetch(url, requestOptions)
+ .then((response) => response.json())
+ .then((data) => {
+ if (data.features.length === 0) {
+ setSearchResultsLocation([])
+ } else {
+ data.features.forEach((feature: any) => {
+ feature.type = 'location'
+ })
+ setSearchResultsLocation(data.features)
+ }
+ })
+ .catch((error) => console.log('error', error))
+ }
- /**
- * One of the functions that is called when the user types in the search bar. It returns the search results for devices, retrived from the device list. The device list is proviided by the database in the /explore route and passed down to the search component.
- *
- * @param searchString string to search for devices in the device list
- */
- function getDevices(searchString: string) {
- var results: any[] = [];
- var deviceResults = 0;
+ /**
+ * One of the functions that is called when the user types in the search bar. It returns the search results for devices, retrived from the device list. The device list is proviided by the database in the /explore route and passed down to the search component.
+ *
+ * @param searchString string to search for devices in the device list
+ */
+ function getDevices(searchString: string) {
+ var results: any[] = []
+ var deviceResults = 0
- for (let index = 0; index < props.devices.features.length; index++) {
- const device = props.devices.features[index];
- if (deviceResults === 4) {
- setSearchResultsDevice(results);
- return;
- }
- if (
- device.properties.name
- .toLowerCase()
- .includes(searchString.toLowerCase()) ||
- device.properties.id.toLowerCase().includes(searchString.toLowerCase())
- ) {
- var newStructured = {
- display_name: device.properties.name,
- deviceId: device.properties.id,
- lng: device.properties.longitude,
- lat: device.properties.latitude,
- type: "device",
- };
- results.push(newStructured);
- deviceResults++;
- setSearchResultsDevice(results);
- }
- if (deviceResults === 0) {
- setSearchResultsDevice([]);
- }
- }
- }
+ for (let index = 0; index < props.devices.features.length; index++) {
+ const device = props.devices.features[index]
+ if (deviceResults === 4) {
+ setSearchResultsDevice(results)
+ return
+ }
+ if (
+ device.properties.name
+ .toLowerCase()
+ .includes(searchString.toLowerCase()) ||
+ device.properties.id.toLowerCase().includes(searchString.toLowerCase())
+ ) {
+ var newStructured = {
+ display_name: device.properties.name,
+ deviceId: device.properties.id,
+ lng: device.properties.longitude,
+ lat: device.properties.latitude,
+ type: 'device',
+ }
+ results.push(newStructured)
+ deviceResults++
+ setSearchResultsDevice(results)
+ }
+ if (deviceResults === 0) {
+ setSearchResultsDevice([])
+ }
+ }
+ }
- /**
- * useEffect hook that is called when the search string changes. It calls the getLocations and getDevices functions to get the search results for locations and devices.
- */
- useEffect(() => {
- if (props.searchString.length >= 2) {
- getLocations(props.searchString);
- getDevices(props.searchString);
- }
- if (props.searchString.length < 2) {
- setSearchResultsLocation([]);
- setSearchResultsDevice([]);
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [props.searchString]);
+ /**
+ * useEffect hook that is called when the search string changes. It calls the getLocations and getDevices functions to get the search results for locations and devices.
+ */
+ useEffect(() => {
+ if (props.searchString.length >= 2) {
+ getLocations(props.searchString)
+ getDevices(props.searchString)
+ }
+ if (props.searchString.length < 2) {
+ setSearchResultsLocation([])
+ setSearchResultsDevice([])
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [props.searchString])
- if (searchResultsLocation.length > 0 || searchResultsDevice.length > 0)
- return (
-
- );
+ if (searchResultsLocation.length > 0 || searchResultsDevice.length > 0)
+ return (
+
+ )
- return null;
+ return null
}
diff --git a/app/components/search/search-list-item.tsx b/app/components/search/search-list-item.tsx
index ecfa1a8f6..764bbc611 100644
--- a/app/components/search/search-list-item.tsx
+++ b/app/components/search/search-list-item.tsx
@@ -1,56 +1,56 @@
-import { type VariantProps, cva } from "class-variance-authority";
-import { type HTMLProps } from "react";
+import { type VariantProps, cva } from 'class-variance-authority'
+import { type HTMLProps } from 'react'
export type HeroIcon = React.ComponentType<
- React.PropsWithoutRef> & {
- title?: string | undefined;
- titleId?: string | undefined;
- }
->;
+ React.PropsWithoutRef> & {
+ title?: string | undefined
+ titleId?: string | undefined
+ }
+>
interface SearchListItemProps
- extends VariantProps,
- HTMLProps {
- index: number;
- controlPress: boolean;
- icon: HeroIcon;
- name: string;
+ extends VariantProps,
+ HTMLProps {
+ index: number
+ controlPress: boolean
+ icon: HeroIcon
+ name: string
}
const searchListItemStyle = cva(
- "relative my-1 flex gap-2 h-8 px-2 items-center rounded-lg data-[active=true]:bg-light-green data-[active=true]:text-white",
- {
- variants: {
- active: {
- true: "bg-light-green text-white",
- },
- },
- }
-);
+ 'relative my-1 flex h-8 items-center gap-2 rounded-lg px-2 data-[active=true]:bg-light-green data-[active=true]:text-white',
+ {
+ variants: {
+ active: {
+ true: 'bg-light-green text-white',
+ },
+ },
+ },
+)
export default function SearchListItem({
- active,
- index,
- controlPress,
- icon,
- name,
- ...props
+ active,
+ index,
+ controlPress,
+ icon,
+ name,
+ ...props
}: SearchListItemProps) {
- const Icon = icon;
+ const Icon = icon
- return (
-
- {controlPress && (
-
- {index + 1}
-
- )}
-
-
-
-
- {name}
-
-
- );
+ return (
+
+ {controlPress && (
+
+ {index + 1}
+
+ )}
+
+
+
+
+ {name}
+
+
+ )
}
diff --git a/app/components/search/search-list.tsx b/app/components/search/search-list.tsx
index 48d4323a8..0496ee934 100644
--- a/app/components/search/search-list.tsx
+++ b/app/components/search/search-list.tsx
@@ -8,7 +8,6 @@ import useKeyboardNav from '../header/nav-bar/use-keyboard-nav'
import SearchListItem from './search-list-item'
import { goTo } from '~/lib/search-map-helper'
-
interface SearchListProps {
searchResultsLocation: any[]
searchResultsDevice: any[]
diff --git a/app/components/sensor-icon.tsx b/app/components/sensor-icon.tsx
index cf14ca5f8..46f5cf7a0 100644
--- a/app/components/sensor-icon.tsx
+++ b/app/components/sensor-icon.tsx
@@ -1,17 +1,17 @@
-import { Activity, ThermometerIcon, Volume1Icon } from "lucide-react";
+import { Activity, ThermometerIcon, Volume1Icon } from 'lucide-react'
interface SensorIconProps {
- title: string;
- className: string | undefined;
+ title: string
+ className: string | undefined
}
export default function SensorIcon(props: SensorIconProps) {
- switch (props.title.toLowerCase()) {
- case "temperatur":
- return ;
- case "lautstärke":
- return ;
- default:
- return ;
- }
+ switch (props.title.toLowerCase()) {
+ case 'temperatur':
+ return
+ case 'lautstärke':
+ return
+ default:
+ return
+ }
}
diff --git a/app/components/sidebar-settings-nav.tsx b/app/components/sidebar-settings-nav.tsx
index f66f8f620..261d14658 100644
--- a/app/components/sidebar-settings-nav.tsx
+++ b/app/components/sidebar-settings-nav.tsx
@@ -1,43 +1,43 @@
-"use client";
+'use client'
-import { NavLink } from "react-router";
-import { cn } from "~/lib/utils";
+import { NavLink } from 'react-router'
+import { cn } from '~/lib/utils'
interface SidebarNavProps extends React.HTMLAttributes {
- items: {
- href: string;
- title: string;
- }[];
+ items: {
+ href: string
+ title: string
+ }[]
}
export function SidebarSettingsNav({
- className,
- items,
- ...props
+ className,
+ items,
+ ...props
}: SidebarNavProps) {
- return (
-
- {items.map((item) => (
-
- isPending
- ? ""
- : isActive
- ? "bg-muted hover:bg-muted"
- : "hover:bg-transparent hover:underline"
- }
- >
- {item.title}
-
- ))}
-
- );
+ return (
+
+ {items.map((item) => (
+
+ isPending
+ ? ''
+ : isActive
+ ? 'bg-muted hover:bg-muted'
+ : 'hover:bg-transparent hover:underline'
+ }
+ >
+ {item.title}
+
+ ))}
+
+ )
}
diff --git a/app/components/spinner/index.tsx b/app/components/spinner/index.tsx
index 21c99478a..377c527da 100644
--- a/app/components/spinner/index.tsx
+++ b/app/components/spinner/index.tsx
@@ -1,44 +1,44 @@
export default function Spinner() {
- return (
- <>
-
- >
- );
+ return (
+ <>
+
+ >
+ )
}
diff --git a/app/components/ui/accordion.tsx b/app/components/ui/accordion.tsx
index 5d1474263..614d9a41f 100644
--- a/app/components/ui/accordion.tsx
+++ b/app/components/ui/accordion.tsx
@@ -1,59 +1,59 @@
-"use client"
+'use client'
-import * as AccordionPrimitive from "@radix-ui/react-accordion"
-import { ChevronDown } from "lucide-react"
-import * as React from "react"
+import * as AccordionPrimitive from '@radix-ui/react-accordion'
+import { ChevronDown } from 'lucide-react'
+import * as React from 'react'
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils'
const Accordion = AccordionPrimitive.Root
const AccordionItem = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
+
))
-AccordionItem.displayName = "AccordionItem"
+AccordionItem.displayName = 'AccordionItem'
const AccordionTrigger = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, children, ...props }, ref) => (
-
- svg]:rotate-180",
- className
- )}
- {...props}
- >
- {children}
-
-
-
+
+ svg]:rotate-180',
+ className,
+ )}
+ {...props}
+ >
+ {children}
+
+
+
))
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
const AccordionContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, children, ...props }, ref) => (
-
- {children}
-
+
+ {children}
+
))
AccordionContent.displayName = AccordionPrimitive.Content.displayName
diff --git a/app/components/ui/alert-dialog.tsx b/app/components/ui/alert-dialog.tsx
index 34f153c61..1aa744981 100644
--- a/app/components/ui/alert-dialog.tsx
+++ b/app/components/ui/alert-dialog.tsx
@@ -1,10 +1,10 @@
-"use client"
+'use client'
-import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
-import * as React from "react"
+import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'
+import * as React from 'react'
-import { buttonVariants } from "@/components/ui/button"
-import { cn } from "@/lib/utils"
+import { buttonVariants } from '@/components/ui/button'
+import { cn } from '@/lib/utils'
const AlertDialog = AlertDialogPrimitive.Root
@@ -13,129 +13,129 @@ const AlertDialogTrigger = AlertDialogPrimitive.Trigger
const AlertDialogPortal = AlertDialogPrimitive.Portal
const AlertDialogOverlay = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
+
))
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
const AlertDialogContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
-
-
-
+
+
+
+
))
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
const AlertDialogHeader = ({
- className,
- ...props
+ className,
+ ...props
}: React.HTMLAttributes) => (
-
+
)
-AlertDialogHeader.displayName = "AlertDialogHeader"
+AlertDialogHeader.displayName = 'AlertDialogHeader'
const AlertDialogFooter = ({
- className,
- ...props
+ className,
+ ...props
}: React.HTMLAttributes) => (
-
+
)
-AlertDialogFooter.displayName = "AlertDialogFooter"
+AlertDialogFooter.displayName = 'AlertDialogFooter'
const AlertDialogTitle = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
+
))
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
const AlertDialogDescription = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
+
))
AlertDialogDescription.displayName =
- AlertDialogPrimitive.Description.displayName
+ AlertDialogPrimitive.Description.displayName
const AlertDialogAction = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
+
))
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
const AlertDialogCancel = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
+
))
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
export {
- AlertDialog,
- AlertDialogPortal,
- AlertDialogOverlay,
- AlertDialogTrigger,
- AlertDialogContent,
- AlertDialogHeader,
- AlertDialogFooter,
- AlertDialogTitle,
- AlertDialogDescription,
- AlertDialogAction,
- AlertDialogCancel,
+ AlertDialog,
+ AlertDialogPortal,
+ AlertDialogOverlay,
+ AlertDialogTrigger,
+ AlertDialogContent,
+ AlertDialogHeader,
+ AlertDialogFooter,
+ AlertDialogTitle,
+ AlertDialogDescription,
+ AlertDialogAction,
+ AlertDialogCancel,
}
diff --git a/app/components/ui/alert.tsx b/app/components/ui/alert.tsx
index 7268117d6..ac13da0b5 100644
--- a/app/components/ui/alert.tsx
+++ b/app/components/ui/alert.tsx
@@ -19,15 +19,15 @@ const alertVariants = cva(
default:
'bg-white text-slate-950 dark:bg-dark-boxes dark:text-dark-text',
destructive:
- 'border-red-500/50 dark:border-red-900/50 dark:text-red-900 dark:dark:border-red-900 dark:[&>svg]:text-red-900 text-red-500 dark:border-red-500 [&>svg]:text-red-500',
- note: 'border-blue-500/50 dark:border-blue-900/50 dark:bg-blue-900/20 bg-blue-50 dark:text-blue-200 dark:[&>svg]:text-blue-400 text-blue-900 [&>svg]:text-blue-500',
+ 'border-red-500/50 dark:border-red-900/50 text-red-500 dark:border-red-500 dark:dark:border-red-900 dark:text-red-900 [&>svg]:text-red-500 dark:[&>svg]:text-red-900',
+ note: 'border-blue-500/50 dark:border-blue-900/50 dark:bg-blue-900/20 bg-blue-50 text-blue-900 dark:text-blue-200 [&>svg]:text-blue-500 dark:[&>svg]:text-blue-400',
tip: 'border-green-500/50 dark:border-green-900/50 dark:bg-green-900/20 bg-green-50 text-green-900 dark:text-green-200 [&>svg]:text-green-500 dark:[&>svg]:text-green-400',
important:
- 'border-violet-500/50 bg-violet-50 text-violet-900 dark:border-violet-900/50 dark:bg-violet-900/20 dark:text-violet-200 dark:[&>svg]:text-violet-400 [&>svg]:text-violet-500',
+ 'border-violet-500/50 dark:border-violet-900/50 dark:bg-violet-900/20 bg-violet-50 text-violet-900 dark:text-violet-200 [&>svg]:text-violet-500 dark:[&>svg]:text-violet-400',
warning:
- 'border-yellow-500/50 bg-yellow-50 text-yellow-900 dark:border-yellow-900/50 dark:bg-yellow-900/20 dark:text-yellow-200 [&>svg]:text-yellow-500 dark:[&>svg]:text-yellow-400',
+ 'border-yellow-500/50 dark:border-yellow-900/50 dark:bg-yellow-900/20 bg-yellow-50 text-yellow-900 dark:text-yellow-200 [&>svg]:text-yellow-500 dark:[&>svg]:text-yellow-400',
caution:
- 'border-red-500/50 dark:border-red-900/50 dark:bg-red-900/20 bg-red-50 text-red-900 dark:text-red-200 dark:[&>svg]:text-red-400 [&>svg]:text-red-500',
+ 'border-red-500/50 dark:border-red-900/50 dark:bg-red-900/20 bg-red-50 text-red-900 dark:text-red-200 [&>svg]:text-red-500 dark:[&>svg]:text-red-400',
},
},
defaultVariants: {
diff --git a/app/components/ui/animated-counter.tsx b/app/components/ui/animated-counter.tsx
index 8713ff6ff..9f4a179c0 100644
--- a/app/components/ui/animated-counter.tsx
+++ b/app/components/ui/animated-counter.tsx
@@ -1,48 +1,48 @@
import {
- animate,
- motion,
- useInView,
- useMotionValue,
- useTransform,
-} from "framer-motion";
-import { useEffect, useRef } from "react";
+ animate,
+ motion,
+ useInView,
+ useMotionValue,
+ useTransform,
+} from 'framer-motion'
+import { useEffect, useRef } from 'react'
type AnimatedCounterProps = {
- from: number;
- to: number;
-};
+ from: number
+ to: number
+}
function AnimatedCounter({ from, to }: AnimatedCounterProps) {
- const count = useMotionValue(from);
- const rounded = useTransform(count, (latest) => Math.round(latest));
-
- const ref = useRef(null);
- const inView = useInView(ref);
-
- useEffect(() => {
- let timeoutId: number | undefined;
-
- const updateCount = () => {
- if (count.get() !== to) {
- animate(count, to, { duration: 0.15 });
- requestAnimationFrame(updateCount);
- }
- };
-
- if (inView) {
- timeoutId = window.setTimeout(() => {
- requestAnimationFrame(updateCount);
- }, 450);
- }
-
- return () => {
- if (timeoutId) {
- clearTimeout(timeoutId);
- }
- };
- }, [count, inView, to]);
-
- return {rounded} ;
+ const count = useMotionValue(from)
+ const rounded = useTransform(count, (latest) => Math.round(latest))
+
+ const ref = useRef(null)
+ const inView = useInView(ref)
+
+ useEffect(() => {
+ let timeoutId: number | undefined
+
+ const updateCount = () => {
+ if (count.get() !== to) {
+ animate(count, to, { duration: 0.15 })
+ requestAnimationFrame(updateCount)
+ }
+ }
+
+ if (inView) {
+ timeoutId = window.setTimeout(() => {
+ requestAnimationFrame(updateCount)
+ }, 450)
+ }
+
+ return () => {
+ if (timeoutId) {
+ clearTimeout(timeoutId)
+ }
+ }
+ }, [count, inView, to])
+
+ return {rounded}
}
-export default AnimatedCounter;
+export default AnimatedCounter
diff --git a/app/components/ui/aspect-ratio.tsx b/app/components/ui/aspect-ratio.tsx
index d6a5226f5..794c6f4cd 100644
--- a/app/components/ui/aspect-ratio.tsx
+++ b/app/components/ui/aspect-ratio.tsx
@@ -1,6 +1,6 @@
-"use client"
+'use client'
-import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
+import * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio'
const AspectRatio = AspectRatioPrimitive.Root
diff --git a/app/components/ui/avatar.tsx b/app/components/ui/avatar.tsx
index 67875a403..06847b0ba 100644
--- a/app/components/ui/avatar.tsx
+++ b/app/components/ui/avatar.tsx
@@ -1,49 +1,49 @@
-"use client"
+'use client'
-import * as AvatarPrimitive from "@radix-ui/react-avatar"
-import * as React from "react"
+import * as AvatarPrimitive from '@radix-ui/react-avatar'
+import * as React from 'react'
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils'
const Avatar = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
+
))
Avatar.displayName = AvatarPrimitive.Root.displayName
const AvatarImage = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
+
))
AvatarImage.displayName = AvatarPrimitive.Image.displayName
const AvatarFallback = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
+
))
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
diff --git a/app/components/ui/badge.tsx b/app/components/ui/badge.tsx
index a3745ed5d..6d9a3dece 100644
--- a/app/components/ui/badge.tsx
+++ b/app/components/ui/badge.tsx
@@ -1,36 +1,36 @@
-import { cva, type VariantProps } from "class-variance-authority"
-import * as React from "react"
+import { cva, type VariantProps } from 'class-variance-authority'
+import * as React from 'react'
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils'
const badgeVariants = cva(
- "inline-flex items-center rounded-full border border-zinc-200 px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-zinc-400 focus:ring-offset-2 dark:border-white dark:focus:ring-zinc-800",
- {
- variants: {
- variant: {
- default:
- "border-transparent bg-slate-900 text-slate-50 hover:bg-slate-900/80 dark:bg-slate-50 dark:text-slate-900 dark:hover:bg-slate-50/80",
- secondary:
- "border-transparent bg-slate-100 text-slate-900 hover:bg-slate-100/80 dark:bg-slate-800 dark:text-slate-50 dark:hover:bg-slate-800/80",
- destructive:
- "border-transparent bg-red-500 text-slate-50 hover:bg-red-500/80 dark:bg-red-900 dark:text-red-50 dark:hover:bg-red-900/80",
- outline: "text-zinc-950 dark:text-zinc-50",
- },
- },
- defaultVariants: {
- variant: "default",
- },
- }
+ 'inline-flex items-center rounded-full border border-zinc-200 px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-zinc-400 focus:ring-offset-2 dark:border-white dark:focus:ring-zinc-800',
+ {
+ variants: {
+ variant: {
+ default:
+ 'border-transparent bg-slate-900 text-slate-50 hover:bg-slate-900/80 dark:bg-slate-50 dark:text-slate-900 dark:hover:bg-slate-50/80',
+ secondary:
+ 'border-transparent bg-slate-100 text-slate-900 hover:bg-slate-100/80 dark:bg-slate-800 dark:text-slate-50 dark:hover:bg-slate-800/80',
+ destructive:
+ 'hover:bg-red-500/80 dark:hover:bg-red-900/80 border-transparent bg-red-500 text-slate-50 dark:bg-red-900 dark:text-red-50',
+ outline: 'text-zinc-950 dark:text-zinc-50',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ },
+ },
)
export interface BadgeProps
- extends React.HTMLAttributes,
- VariantProps {}
+ extends React.HTMLAttributes,
+ VariantProps {}
function Badge({ className, variant, ...props }: BadgeProps) {
- return (
-
- )
+ return (
+
+ )
}
export { Badge, badgeVariants }
diff --git a/app/components/ui/breadcrumb.tsx b/app/components/ui/breadcrumb.tsx
index ee0a07751..4534724e7 100644
--- a/app/components/ui/breadcrumb.tsx
+++ b/app/components/ui/breadcrumb.tsx
@@ -1,115 +1,118 @@
-import { Slot } from "@radix-ui/react-slot"
-import { ChevronRight, MoreHorizontal } from "lucide-react"
-import * as React from "react"
+import { Slot } from '@radix-ui/react-slot'
+import { ChevronRight, MoreHorizontal } from 'lucide-react'
+import * as React from 'react'
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils'
const Breadcrumb = React.forwardRef<
- HTMLElement,
- React.ComponentPropsWithoutRef<"nav"> & {
- separator?: React.ReactNode
- }
+ HTMLElement,
+ React.ComponentPropsWithoutRef<'nav'> & {
+ separator?: React.ReactNode
+ }
>(({ ...props }, ref) => )
-Breadcrumb.displayName = "Breadcrumb"
+Breadcrumb.displayName = 'Breadcrumb'
const BreadcrumbList = React.forwardRef<
- HTMLOListElement,
- React.ComponentPropsWithoutRef<"ol">
+ HTMLOListElement,
+ React.ComponentPropsWithoutRef<'ol'>
>(({ className, ...props }, ref) => (
-
+
))
-BreadcrumbList.displayName = "BreadcrumbList"
+BreadcrumbList.displayName = 'BreadcrumbList'
const BreadcrumbItem = React.forwardRef<
- HTMLLIElement,
- React.ComponentPropsWithoutRef<"li">
+ HTMLLIElement,
+ React.ComponentPropsWithoutRef<'li'>
>(({ className, ...props }, ref) => (
-
+
))
-BreadcrumbItem.displayName = "BreadcrumbItem"
+BreadcrumbItem.displayName = 'BreadcrumbItem'
const BreadcrumbLink = React.forwardRef<
- HTMLAnchorElement,
- React.ComponentPropsWithoutRef<"a"> & {
- asChild?: boolean
- }
+ HTMLAnchorElement,
+ React.ComponentPropsWithoutRef<'a'> & {
+ asChild?: boolean
+ }
>(({ asChild, className, ...props }, ref) => {
- const Comp = asChild ? Slot : "a"
+ const Comp = asChild ? Slot : 'a'
- return (
-
- )
+ return (
+
+ )
})
-BreadcrumbLink.displayName = "BreadcrumbLink"
+BreadcrumbLink.displayName = 'BreadcrumbLink'
const BreadcrumbPage = React.forwardRef<
- HTMLSpanElement,
- React.ComponentPropsWithoutRef<"span">
+ HTMLSpanElement,
+ React.ComponentPropsWithoutRef<'span'>
>(({ className, ...props }, ref) => (
-
+
))
-BreadcrumbPage.displayName = "BreadcrumbPage"
+BreadcrumbPage.displayName = 'BreadcrumbPage'
const BreadcrumbSeparator = ({
- children,
- className,
- ...props
-}: React.ComponentProps<"li">) => (
- svg]:w-3.5 [&>svg]:h-3.5", className)}
- {...props}
- >
- {children ?? }
-
+ children,
+ className,
+ ...props
+}: React.ComponentProps<'li'>) => (
+ svg]:h-3.5 [&>svg]:w-3.5', className)}
+ {...props}
+ >
+ {children ?? }
+
)
-BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
+BreadcrumbSeparator.displayName = 'BreadcrumbSeparator'
const BreadcrumbEllipsis = ({
- className,
- ...props
-}: React.ComponentProps<"span">) => (
-
-
- More
-
+ className,
+ ...props
+}: React.ComponentProps<'span'>) => (
+
+
+ More
+
)
-BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
+BreadcrumbEllipsis.displayName = 'BreadcrumbElipssis'
export {
- Breadcrumb,
- BreadcrumbList,
- BreadcrumbItem,
- BreadcrumbLink,
- BreadcrumbPage,
- BreadcrumbSeparator,
- BreadcrumbEllipsis,
+ Breadcrumb,
+ BreadcrumbList,
+ BreadcrumbItem,
+ BreadcrumbLink,
+ BreadcrumbPage,
+ BreadcrumbSeparator,
+ BreadcrumbEllipsis,
}
diff --git a/app/components/ui/button.tsx b/app/components/ui/button.tsx
index 22cda64ba..efc496a2b 100644
--- a/app/components/ui/button.tsx
+++ b/app/components/ui/button.tsx
@@ -1,56 +1,58 @@
-import { Slot } from "@radix-ui/react-slot"
-import { cva, type VariantProps } from "class-variance-authority"
-import * as React from "react"
+import { Slot } from '@radix-ui/react-slot'
+import { cva, type VariantProps } from 'class-variance-authority'
+import * as React from 'react'
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils'
const buttonVariants = cva(
- "inline-flex items-center justify-center gap-2 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 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 dark:ring-offset-slate-950 dark:focus-visible:ring-slate-300",
- {
- variants: {
- variant: {
- default: "bg-slate-900 text-slate-50 hover:bg-slate-900/90 dark:bg-slate-50 dark:text-slate-900 dark:hover:bg-slate-50/90",
- destructive:
- "bg-red-500 text-slate-50 hover:bg-red-500/90 dark:bg-red-900 dark:text-slate-50 dark:hover:bg-red-900/90",
- outline:
- "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",
- secondary:
- "bg-slate-100 text-slate-900 hover:bg-slate-100/80 dark:bg-slate-800 dark:text-slate-50 dark:hover:bg-slate-800/80",
- ghost: "hover:bg-slate-100 hover:text-slate-900 dark:hover:bg-slate-800 dark:hover:text-slate-50",
- link: "text-slate-900 underline-offset-4 hover:underline dark:text-slate-50",
- },
- size: {
- default: "h-10 px-4 py-2",
- sm: "h-9 rounded-md px-3",
- lg: "h-11 rounded-md px-8",
- icon: "h-10 w-10",
- },
- },
- defaultVariants: {
- variant: "default",
- size: "default",
- },
- }
+ 'inline-flex items-center justify-center gap-2 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 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
+ {
+ variants: {
+ variant: {
+ default:
+ 'bg-slate-900 text-slate-50 hover:bg-slate-900/90 dark:bg-slate-50 dark:text-slate-900 dark:hover:bg-slate-50/90',
+ destructive:
+ 'hover:bg-red-500/90 dark:hover:bg-red-900/90 bg-red-500 text-slate-50 dark:bg-red-900 dark:text-slate-50',
+ outline:
+ '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',
+ secondary:
+ 'bg-slate-100 text-slate-900 hover:bg-slate-100/80 dark:bg-slate-800 dark:text-slate-50 dark:hover:bg-slate-800/80',
+ ghost:
+ 'hover:bg-slate-100 hover:text-slate-900 dark:hover:bg-slate-800 dark:hover:text-slate-50',
+ link: 'text-slate-900 underline-offset-4 hover:underline dark:text-slate-50',
+ },
+ size: {
+ default: 'h-10 px-4 py-2',
+ sm: 'h-9 rounded-md px-3',
+ lg: 'h-11 rounded-md px-8',
+ icon: 'h-10 w-10',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ size: 'default',
+ },
+ },
)
export interface ButtonProps
- extends React.ButtonHTMLAttributes,
- VariantProps {
- asChild?: boolean
+ extends React.ButtonHTMLAttributes,
+ VariantProps {
+ asChild?: boolean
}
const Button = React.forwardRef(
- ({ className, variant, size, asChild = false, ...props }, ref) => {
- const Comp = asChild ? Slot : "button"
- return (
-
- )
- }
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : 'button'
+ return (
+
+ )
+ },
)
-Button.displayName = "Button"
+Button.displayName = 'Button'
export { Button, buttonVariants }
diff --git a/app/components/ui/calendar.tsx b/app/components/ui/calendar.tsx
index 9ec76a5cb..29da185a4 100644
--- a/app/components/ui/calendar.tsx
+++ b/app/components/ui/calendar.tsx
@@ -1,70 +1,71 @@
-"use client"
+'use client'
-import { ChevronLeft, ChevronRight } from "lucide-react"
-import * as React from "react"
-import { DayPicker } from "react-day-picker"
+import { ChevronLeft, ChevronRight } from 'lucide-react'
+import * as React from 'react'
+import { DayPicker } from 'react-day-picker'
-import { buttonVariants } from "@/components/ui/button"
-import { cn } from "@/lib/utils"
+import { buttonVariants } from '@/components/ui/button'
+import { cn } from '@/lib/utils'
export type CalendarProps = React.ComponentProps
function Calendar({
- className,
- classNames,
- showOutsideDays = true,
- ...props
+ className,
+ classNames,
+ showOutsideDays = true,
+ ...props
}: CalendarProps) {
- return (
- (
-
- ),
- IconRight: ({ className, ...props }) => (
-
- ),
- }}
- {...props}
- />
- )
+ return (
+ (
+
+ ),
+ IconRight: ({ className, ...props }) => (
+
+ ),
+ }}
+ {...props}
+ />
+ )
}
-Calendar.displayName = "Calendar"
+Calendar.displayName = 'Calendar'
export { Calendar }
diff --git a/app/components/ui/card.tsx b/app/components/ui/card.tsx
index f1ab38aa9..4a27df212 100644
--- a/app/components/ui/card.tsx
+++ b/app/components/ui/card.tsx
@@ -1,79 +1,79 @@
-import * as React from "react"
+import * as React from 'react'
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils'
const Card = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes
+ HTMLDivElement,
+ React.HTMLAttributes
>(({ className, ...props }, ref) => (
-
+
))
-Card.displayName = "Card"
+Card.displayName = 'Card'
const CardHeader = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes
+ HTMLDivElement,
+ React.HTMLAttributes
>(({ className, ...props }, ref) => (
-
+
))
-CardHeader.displayName = "CardHeader"
+CardHeader.displayName = 'CardHeader'
const CardTitle = React.forwardRef<
- HTMLParagraphElement,
- React.HTMLAttributes
+ HTMLParagraphElement,
+ React.HTMLAttributes
>(({ className, ...props }, ref) => (
-
+
))
-CardTitle.displayName = "CardTitle"
+CardTitle.displayName = 'CardTitle'
const CardDescription = React.forwardRef<
- HTMLParagraphElement,
- React.HTMLAttributes
+ HTMLParagraphElement,
+ React.HTMLAttributes
>(({ className, ...props }, ref) => (
-
+
))
-CardDescription.displayName = "CardDescription"
+CardDescription.displayName = 'CardDescription'
const CardContent = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes
+ HTMLDivElement,
+ React.HTMLAttributes
>(({ className, ...props }, ref) => (
-
+
))
-CardContent.displayName = "CardContent"
+CardContent.displayName = 'CardContent'
const CardFooter = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes
+ HTMLDivElement,
+ React.HTMLAttributes
>(({ className, ...props }, ref) => (
-
+
))
-CardFooter.displayName = "CardFooter"
+CardFooter.displayName = 'CardFooter'
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
diff --git a/app/components/ui/checkbox.tsx b/app/components/ui/checkbox.tsx
index 51a7c5a17..a9334f0d4 100644
--- a/app/components/ui/checkbox.tsx
+++ b/app/components/ui/checkbox.tsx
@@ -1,29 +1,29 @@
-"use client"
+'use client'
-import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
-import { Check } from "lucide-react"
-import * as React from "react"
+import * as CheckboxPrimitive from '@radix-ui/react-checkbox'
+import { Check } from 'lucide-react'
+import * as React from 'react'
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils'
const Checkbox = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
-
-
-
-
+
+
+
+
+
))
Checkbox.displayName = CheckboxPrimitive.Root.displayName
diff --git a/app/components/ui/command.tsx b/app/components/ui/command.tsx
index df7b1b9ce..a3c419040 100644
--- a/app/components/ui/command.tsx
+++ b/app/components/ui/command.tsx
@@ -1,156 +1,156 @@
-"use client";
+'use client'
-import { type DialogProps } from "@radix-ui/react-dialog";
-import { Command as CommandPrimitive } from "cmdk";
-import { Search } from "lucide-react";
-import * as React from "react";
+import { type DialogProps } from '@radix-ui/react-dialog'
+import { Command as CommandPrimitive } from 'cmdk'
+import { Search } from 'lucide-react'
+import * as React from 'react'
-import { Dialog, DialogContent } from "@/components/ui/dialog";
-import { cn } from "@/lib/utils";
+import { Dialog, DialogContent } from '@/components/ui/dialog'
+import { cn } from '@/lib/utils'
const Command = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
-));
-Command.displayName = CommandPrimitive.displayName;
+
+))
+Command.displayName = CommandPrimitive.displayName
interface CommandDialogProps extends DialogProps {}
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
- return (
-
-
-
- {children}
-
-
-
- );
-};
+ return (
+
+
+
+ {children}
+
+
+
+ )
+}
const CommandInput = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
-
-
-
-));
-
-CommandInput.displayName = CommandPrimitive.Input.displayName;
+
+
+
+
+))
+
+CommandInput.displayName = CommandPrimitive.Input.displayName
const CommandList = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
-));
+
+))
-CommandList.displayName = CommandPrimitive.List.displayName;
+CommandList.displayName = CommandPrimitive.List.displayName
const CommandEmpty = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>((props, ref) => (
-
-));
+
+))
-CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
+CommandEmpty.displayName = CommandPrimitive.Empty.displayName
const CommandGroup = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
-));
-
-CommandGroup.displayName = CommandPrimitive.Group.displayName;
+
+))
+
+CommandGroup.displayName = CommandPrimitive.Group.displayName
const CommandSeparator = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
-));
-CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
+
+))
+CommandSeparator.displayName = CommandPrimitive.Separator.displayName
// TODO: Manually fixed this issue: https://github.com/shadcn-ui/ui/pull/4297
const CommandItem = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
-));
-
-CommandItem.displayName = CommandPrimitive.Item.displayName;
+
+))
+
+CommandItem.displayName = CommandPrimitive.Item.displayName
const CommandShortcut = ({
- className,
- ...props
+ className,
+ ...props
}: React.HTMLAttributes) => {
- return (
-
- );
-};
-CommandShortcut.displayName = "CommandShortcut";
+ return (
+
+ )
+}
+CommandShortcut.displayName = 'CommandShortcut'
export {
- Command,
- CommandDialog,
- CommandInput,
- CommandList,
- CommandEmpty,
- CommandGroup,
- CommandItem,
- CommandShortcut,
- CommandSeparator,
-};
+ Command,
+ CommandDialog,
+ CommandInput,
+ CommandList,
+ CommandEmpty,
+ CommandGroup,
+ CommandItem,
+ CommandShortcut,
+ CommandSeparator,
+}
diff --git a/app/components/ui/dialog.tsx b/app/components/ui/dialog.tsx
index 6d15c5ccf..72262a8b9 100644
--- a/app/components/ui/dialog.tsx
+++ b/app/components/ui/dialog.tsx
@@ -1,127 +1,127 @@
-"use client";
+'use client'
-import * as DialogPrimitive from "@radix-ui/react-dialog";
-import { X } from "lucide-react";
-import * as React from "react";
+import * as DialogPrimitive from '@radix-ui/react-dialog'
+import { X } from 'lucide-react'
+import * as React from 'react'
-import { cn } from "@/lib/utils";
+import { cn } from '@/lib/utils'
-const Dialog = DialogPrimitive.Root;
+const Dialog = DialogPrimitive.Root
-const DialogTrigger = DialogPrimitive.Trigger;
+const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = ({
- children,
- ...props
+ children,
+ ...props
}: DialogPrimitive.DialogPortalProps) => (
-
-
- {children}
-
-
-);
-DialogPortal.displayName = DialogPrimitive.Portal.displayName;
+
+
+ {children}
+
+
+)
+DialogPortal.displayName = DialogPrimitive.Portal.displayName
const DialogOverlay = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
-));
-DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
+
+))
+DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, children, ...props }, ref) => (
-
-
-
- {children}
-
-
- Close
-
-
-
-));
-DialogContent.displayName = DialogPrimitive.Content.displayName;
+
+
+
+ {children}
+
+
+ Close
+
+
+
+))
+DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
- className,
- ...props
+ className,
+ ...props
}: React.HTMLAttributes) => (
-
-);
-DialogHeader.displayName = "DialogHeader";
+
+)
+DialogHeader.displayName = 'DialogHeader'
const DialogFooter = ({
- className,
- ...props
+ className,
+ ...props
}: React.HTMLAttributes) => (
-
-);
-DialogFooter.displayName = "DialogFooter";
+
+)
+DialogFooter.displayName = 'DialogFooter'
const DialogTitle = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
-));
-DialogTitle.displayName = DialogPrimitive.Title.displayName;
+
+))
+DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
-));
-DialogDescription.displayName = DialogPrimitive.Description.displayName;
+
+))
+DialogDescription.displayName = DialogPrimitive.Description.displayName
export {
- Dialog,
- DialogTrigger,
- DialogContent,
- DialogHeader,
- DialogFooter,
- DialogTitle,
- DialogDescription,
-};
+ Dialog,
+ DialogTrigger,
+ DialogContent,
+ DialogHeader,
+ DialogFooter,
+ DialogTitle,
+ DialogDescription,
+}
diff --git a/app/components/ui/drawer.tsx b/app/components/ui/drawer.tsx
index d3ae0555e..1ccdd4d00 100644
--- a/app/components/ui/drawer.tsx
+++ b/app/components/ui/drawer.tsx
@@ -1,20 +1,20 @@
-"use client"
+'use client'
-import * as React from "react"
-import { Drawer as DrawerPrimitive } from "vaul"
+import * as React from 'react'
+import { Drawer as DrawerPrimitive } from 'vaul'
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils'
const Drawer = ({
- shouldScaleBackground = true,
- ...props
+ shouldScaleBackground = true,
+ ...props
}: React.ComponentProps) => (
-
+
)
-Drawer.displayName = "Drawer"
+Drawer.displayName = 'Drawer'
const DrawerTrigger = DrawerPrimitive.Trigger
@@ -23,96 +23,96 @@ const DrawerPortal = DrawerPrimitive.Portal
const DrawerClose = DrawerPrimitive.Close
const DrawerOverlay = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
+
))
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
const DrawerContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, children, ...props }, ref) => (
-
-
-
-
- {children}
-
-
+
+
+
+
+ {children}
+
+
))
-DrawerContent.displayName = "DrawerContent"
+DrawerContent.displayName = 'DrawerContent'
const DrawerHeader = ({
- className,
- ...props
+ className,
+ ...props
}: React.HTMLAttributes) => (
-
+
)
-DrawerHeader.displayName = "DrawerHeader"
+DrawerHeader.displayName = 'DrawerHeader'
const DrawerFooter = ({
- className,
- ...props
+ className,
+ ...props
}: React.HTMLAttributes) => (
-
+
)
-DrawerFooter.displayName = "DrawerFooter"
+DrawerFooter.displayName = 'DrawerFooter'
const DrawerTitle = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
+
))
DrawerTitle.displayName = DrawerPrimitive.Title.displayName
const DrawerDescription = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
+
))
DrawerDescription.displayName = DrawerPrimitive.Description.displayName
export {
- Drawer,
- DrawerPortal,
- DrawerOverlay,
- DrawerTrigger,
- DrawerClose,
- DrawerContent,
- DrawerHeader,
- DrawerFooter,
- DrawerTitle,
- DrawerDescription,
+ Drawer,
+ DrawerPortal,
+ DrawerOverlay,
+ DrawerTrigger,
+ DrawerClose,
+ DrawerContent,
+ DrawerHeader,
+ DrawerFooter,
+ DrawerTitle,
+ DrawerDescription,
}
diff --git a/app/components/ui/dropdown-menu.tsx b/app/components/ui/dropdown-menu.tsx
index dfbddc19b..ac57eff38 100644
--- a/app/components/ui/dropdown-menu.tsx
+++ b/app/components/ui/dropdown-menu.tsx
@@ -1,10 +1,10 @@
-"use client"
+'use client'
-import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
-import { Check, ChevronRight, Circle } from "lucide-react"
-import * as React from "react"
+import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
+import { Check, ChevronRight, Circle } from 'lucide-react'
+import * as React from 'react'
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils'
const DropdownMenu = DropdownMenuPrimitive.Root
@@ -19,182 +19,182 @@ const DropdownMenuSub = DropdownMenuPrimitive.Sub
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
const DropdownMenuSubTrigger = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & {
- inset?: boolean
- }
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
>(({ className, inset, children, ...props }, ref) => (
-
- {children}
-
-
+
+ {children}
+
+
))
DropdownMenuSubTrigger.displayName =
- DropdownMenuPrimitive.SubTrigger.displayName
+ DropdownMenuPrimitive.SubTrigger.displayName
const DropdownMenuSubContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
+
))
DropdownMenuSubContent.displayName =
- DropdownMenuPrimitive.SubContent.displayName
+ DropdownMenuPrimitive.SubContent.displayName
const DropdownMenuContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, sideOffset = 4, ...props }, ref) => (
-
-
-
+
+
+
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
const DropdownMenuItem = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & {
- inset?: boolean
- }
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
>(({ className, inset, ...props }, ref) => (
-
+
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
const DropdownMenuCheckboxItem = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, children, checked, ...props }, ref) => (
-
-
-
-
-
-
- {children}
-
+
+
+
+
+
+
+ {children}
+
))
DropdownMenuCheckboxItem.displayName =
- DropdownMenuPrimitive.CheckboxItem.displayName
+ DropdownMenuPrimitive.CheckboxItem.displayName
const DropdownMenuRadioItem = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, children, ...props }, ref) => (
-
-
-
-
-
-
- {children}
-
+
+
+
+
+
+
+ {children}
+
))
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
const DropdownMenuLabel = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & {
- inset?: boolean
- }
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
>(({ className, inset, ...props }, ref) => (
-
+
))
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
const DropdownMenuSeparator = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
+
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
const DropdownMenuShortcut = ({
- className,
- ...props
+ className,
+ ...props
}: React.HTMLAttributes) => {
- return (
-
- )
+ return (
+
+ )
}
-DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
+DropdownMenuShortcut.displayName = 'DropdownMenuShortcut'
export {
- DropdownMenu,
- DropdownMenuTrigger,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuCheckboxItem,
- DropdownMenuRadioItem,
- DropdownMenuLabel,
- DropdownMenuSeparator,
- DropdownMenuShortcut,
- DropdownMenuGroup,
- DropdownMenuPortal,
- DropdownMenuSub,
- DropdownMenuSubContent,
- DropdownMenuSubTrigger,
- DropdownMenuRadioGroup,
+ DropdownMenu,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuCheckboxItem,
+ DropdownMenuRadioItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuGroup,
+ DropdownMenuPortal,
+ DropdownMenuSub,
+ DropdownMenuSubContent,
+ DropdownMenuSubTrigger,
+ DropdownMenuRadioGroup,
}
diff --git a/app/components/ui/form.tsx b/app/components/ui/form.tsx
index 821715d72..a45a961b3 100644
--- a/app/components/ui/form.tsx
+++ b/app/components/ui/form.tsx
@@ -1,173 +1,179 @@
-import type * as LabelPrimitive from "@radix-ui/react-label";
-import { Slot } from "@radix-ui/react-slot";
-import * as React from "react";
-import { type ControllerProps, type FieldPath, type FieldValues, Controller, FormProvider, useFormContext } from "react-hook-form";
-
-import { Label } from "@/components/ui/label";
-import { cn } from "@/lib/utils";
-
-const Form = FormProvider;
+import type * as LabelPrimitive from '@radix-ui/react-label'
+import { Slot } from '@radix-ui/react-slot'
+import * as React from 'react'
+import {
+ type ControllerProps,
+ type FieldPath,
+ type FieldValues,
+ Controller,
+ FormProvider,
+ useFormContext,
+} from 'react-hook-form'
+
+import { Label } from '@/components/ui/label'
+import { cn } from '@/lib/utils'
+
+const Form = FormProvider
type FormFieldContextValue<
- TFieldValues extends FieldValues = FieldValues,
- TName extends FieldPath = FieldPath
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath,
> = {
- name: TName;
-};
+ name: TName
+}
const FormFieldContext = React.createContext(
- {} as FormFieldContextValue
-);
+ {} as FormFieldContextValue,
+)
const FormField = <
- TFieldValues extends FieldValues = FieldValues,
- TName extends FieldPath = FieldPath
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath,
>({
- ...props
+ ...props
}: ControllerProps) => {
- return (
-
-
-
- );
-};
+ return (
+
+
+
+ )
+}
const useFormField = () => {
- const fieldContext = React.useContext(FormFieldContext);
- const itemContext = React.useContext(FormItemContext);
- const { getFieldState, formState } = useFormContext();
+ const fieldContext = React.useContext(FormFieldContext)
+ const itemContext = React.useContext(FormItemContext)
+ const { getFieldState, formState } = useFormContext()
- const fieldState = getFieldState(fieldContext.name, formState);
+ const fieldState = getFieldState(fieldContext.name, formState)
- if (!fieldContext) {
- throw new Error("useFormField should be used within ");
- }
+ if (!fieldContext) {
+ throw new Error('useFormField should be used within ')
+ }
- const { id } = itemContext;
+ const { id } = itemContext
- return {
- id,
- name: fieldContext.name,
- formItemId: `${id}-form-item`,
- formDescriptionId: `${id}-form-item-description`,
- formMessageId: `${id}-form-item-message`,
- ...fieldState,
- };
-};
+ return {
+ id,
+ name: fieldContext.name,
+ formItemId: `${id}-form-item`,
+ formDescriptionId: `${id}-form-item-description`,
+ formMessageId: `${id}-form-item-message`,
+ ...fieldState,
+ }
+}
type FormItemContextValue = {
- id: string;
-};
+ id: string
+}
const FormItemContext = React.createContext(
- {} as FormItemContextValue
-);
+ {} as FormItemContextValue,
+)
const FormItem = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes
+ HTMLDivElement,
+ React.HTMLAttributes
>(({ className, ...props }, ref) => {
- const id = React.useId();
+ const id = React.useId()
- return (
-
-
-
- );
-});
-FormItem.displayName = "FormItem";
+ return (
+
+
+
+ )
+})
+FormItem.displayName = 'FormItem'
const FormLabel = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => {
- const { error, formItemId } = useFormField();
-
- return (
-
- );
-});
-FormLabel.displayName = "FormLabel";
+ const { error, formItemId } = useFormField()
+
+ return (
+
+ )
+})
+FormLabel.displayName = 'FormLabel'
const FormControl = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ ...props }, ref) => {
- const { error, formItemId, formDescriptionId, formMessageId } =
- useFormField();
-
- return (
-
- );
-});
-FormControl.displayName = "FormControl";
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
+
+ return (
+
+ )
+})
+FormControl.displayName = 'FormControl'
const FormDescription = React.forwardRef<
- HTMLParagraphElement,
- React.HTMLAttributes
+ HTMLParagraphElement,
+ React.HTMLAttributes
>(({ className, ...props }, ref) => {
- const { formDescriptionId } = useFormField();
-
- return (
-
- );
-});
-FormDescription.displayName = "FormDescription";
+ const { formDescriptionId } = useFormField()
+
+ return (
+
+ )
+})
+FormDescription.displayName = 'FormDescription'
const FormMessage = React.forwardRef<
- HTMLParagraphElement,
- React.HTMLAttributes
+ HTMLParagraphElement,
+ React.HTMLAttributes
>(({ className, children, ...props }, ref) => {
- const { error, formMessageId } = useFormField();
- const body = error ? String(error?.message) : children;
-
- if (!body) {
- return null;
- }
-
- return (
-
- {body}
-
- );
-});
-FormMessage.displayName = "FormMessage";
+ const { error, formMessageId } = useFormField()
+ const body = error ? String(error?.message) : children
+
+ if (!body) {
+ return null
+ }
+
+ return (
+
+ {body}
+
+ )
+})
+FormMessage.displayName = 'FormMessage'
export {
- useFormField,
- Form,
- FormItem,
- FormLabel,
- FormControl,
- FormDescription,
- FormMessage,
- FormField,
-};
+ useFormField,
+ Form,
+ FormItem,
+ FormLabel,
+ FormControl,
+ FormDescription,
+ FormMessage,
+ FormField,
+}
diff --git a/app/components/ui/hover-card.tsx b/app/components/ui/hover-card.tsx
index 22ee651a6..9ecb05f0d 100644
--- a/app/components/ui/hover-card.tsx
+++ b/app/components/ui/hover-card.tsx
@@ -1,28 +1,28 @@
-"use client"
+'use client'
-import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
-import * as React from "react"
+import * as HoverCardPrimitive from '@radix-ui/react-hover-card'
+import * as React from 'react'
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils'
const HoverCard = HoverCardPrimitive.Root
const HoverCardTrigger = HoverCardPrimitive.Trigger
const HoverCardContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
-
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
+
))
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
diff --git a/app/components/ui/input.tsx b/app/components/ui/input.tsx
index 3792d8bd0..b3f10173a 100644
--- a/app/components/ui/input.tsx
+++ b/app/components/ui/input.tsx
@@ -1,25 +1,25 @@
-import * as React from "react"
+import * as React from 'react'
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils'
export interface InputProps
- extends React.InputHTMLAttributes {}
+ extends React.InputHTMLAttributes {}
const Input = React.forwardRef(
- ({ className, type, ...props }, ref) => {
- return (
-
- )
- }
+ ({ className, type, ...props }, ref) => {
+ return (
+
+ )
+ },
)
-Input.displayName = "Input"
+Input.displayName = 'Input'
export { Input }
diff --git a/app/components/ui/label.tsx b/app/components/ui/label.tsx
index 173797a90..5410c7d84 100644
--- a/app/components/ui/label.tsx
+++ b/app/components/ui/label.tsx
@@ -1,25 +1,25 @@
-"use client"
+'use client'
-import * as LabelPrimitive from "@radix-ui/react-label"
-import { cva, type VariantProps } from "class-variance-authority"
-import * as React from "react"
+import * as LabelPrimitive from '@radix-ui/react-label'
+import { cva, type VariantProps } from 'class-variance-authority'
+import * as React from 'react'
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils'
const labelVariants = cva(
- "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
+ 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
)
const Label = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef &
- VariantProps
+ React.ElementRef,
+ React.ComponentPropsWithoutRef &
+ VariantProps
>(({ className, ...props }, ref) => (
-
+
))
Label.displayName = LabelPrimitive.Root.displayName
diff --git a/app/components/ui/popover.tsx b/app/components/ui/popover.tsx
index f7e0b9609..e688d2056 100644
--- a/app/components/ui/popover.tsx
+++ b/app/components/ui/popover.tsx
@@ -1,30 +1,30 @@
-"use client"
+'use client'
-import * as PopoverPrimitive from "@radix-ui/react-popover"
-import * as React from "react"
+import * as PopoverPrimitive from '@radix-ui/react-popover'
+import * as React from 'react'
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils'
const Popover = PopoverPrimitive.Root
const PopoverTrigger = PopoverPrimitive.Trigger
const PopoverContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
-
-
-
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
+
+
+
))
PopoverContent.displayName = PopoverPrimitive.Content.displayName
diff --git a/app/components/ui/radio-group.tsx b/app/components/ui/radio-group.tsx
index ce4592580..b183ad6c5 100644
--- a/app/components/ui/radio-group.tsx
+++ b/app/components/ui/radio-group.tsx
@@ -1,43 +1,43 @@
-"use client"
+'use client'
-import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
-import { Circle } from "lucide-react"
-import * as React from "react"
+import * as RadioGroupPrimitive from '@radix-ui/react-radio-group'
+import { Circle } from 'lucide-react'
+import * as React from 'react'
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils'
const RadioGroup = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => {
- return (
-
- )
+ return (
+
+ )
})
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
const RadioGroupItem = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef