diff --git a/app/components/device-detail/device-detail-box.tsx b/app/components/device-detail/device-detail-box.tsx index e4f8b5b7..7b810263 100644 --- a/app/components/device-detail/device-detail-box.tsx +++ b/app/components/device-detail/device-detail-box.tsx @@ -1,688 +1,675 @@ -import clsx from 'clsx' -import { format, formatDistanceToNow } from 'date-fns' +import clsx from "clsx"; +import { format, formatDistanceToNow } from "date-fns"; import { - ChevronUp, - Minus, - Share2, - XSquare, - EllipsisVertical, - X, - ExternalLink, - Scale, - Archive, - Cpu, - Rss, - CalendarPlus, - Hash, - LandPlot, - Image as ImageIcon, -} from 'lucide-react' -import { Fragment, useEffect, useRef, useState } from 'react' -import { isTablet, isBrowser } from 'react-device-detect' -import Draggable, { type DraggableData } from 'react-draggable' + ChevronUp, + Minus, + Share2, + XSquare, + EllipsisVertical, + X, + ExternalLink, + Scale, + Archive, + Cpu, + Rss, + CalendarPlus, + Hash, + LandPlot, + Image as ImageIcon, +} from "lucide-react"; +import { Fragment, useEffect, useRef, useState } from "react"; +import { isTablet, isBrowser } from "react-device-detect"; +import Draggable, { type DraggableData } from "react-draggable"; import { - useLoaderData, - useMatches, - useNavigate, - useNavigation, - useParams, - useSearchParams, - Link, -} from 'react-router' -import SensorIcon from '../sensor-icon' -import Spinner from '../spinner' + useLoaderData, + useMatches, + useNavigate, + useNavigation, + useParams, + useSearchParams, + Link, +} from "react-router"; +import SensorIcon from "../sensor-icon"; +import Spinner from "../spinner"; import { - Accordion, - AccordionContent, - AccordionItem, - AccordionTrigger, -} from '../ui/accordion' -import { Alert, AlertDescription, AlertTitle } from '../ui/alert' + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "../ui/accordion"; +import { Alert, AlertDescription, AlertTitle } from "../ui/alert"; import { - AlertDialog, - AlertDialogCancel, - AlertDialogContent, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from '../ui/alert-dialog' -import { Badge } from '../ui/badge' -import { Button } from '../ui/button' + AlertDialog, + AlertDialogCancel, + AlertDialogContent, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "../ui/alert-dialog"; +import { Badge } from "../ui/badge"; +import { Button } from "../ui/button"; import { - Card, - CardContent, - CardFooter, - CardHeader, - CardTitle, -} from '../ui/card' + Card, + CardContent, + CardFooter, + CardHeader, + CardTitle, +} from "../ui/card"; import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from '../ui/dropdown-menu' -import { Separator } from '../ui/separator' + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "../ui/dropdown-menu"; +import { Separator } from "../ui/separator"; import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from '../ui/tooltip' -import { useToast } from '../ui/use-toast' -import EntryLogs from './entry-logs' -import ShareLink from './share-link' -import { useGlobalCompareMode } from './useGlobalCompareMode' -import { type loader } from '~/routes/explore.$deviceId' -import { type SensorWithLatestMeasurement } from '~/schema' -import { getArchiveLink } from '~/utils/device' + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "../ui/tooltip"; +import { useToast } from "../ui/use-toast"; +import EntryLogs from "./entry-logs"; +import ShareLink from "./share-link"; +import { useGlobalCompareMode } from "./useGlobalCompareMode"; +import { type loader } from "~/routes/explore.$deviceId"; +import { type SensorWithLatestMeasurement } from "~/schema"; +import { getArchiveLink } from "~/utils/device"; export interface MeasurementProps { - sensorId: string - time: Date - value: string - min_value: string - max_value: string + sensorId: string; + time: Date; + value: string; + min_value: string; + max_value: string; } export default function DeviceDetailBox() { - const navigation = useNavigation() - const navigate = useNavigate() - const matches = useMatches() - const { toast } = useToast() + const navigation = useNavigation(); + const navigate = useNavigate(); + const matches = useMatches(); + const { toast } = useToast(); - const sensorIds = new Set() + const sensorIds = new Set(); - const data = useLoaderData() - const nodeRef = useRef(null) - // state variables - const [open, setOpen] = useState(true) - const [offsetPositionX, setOffsetPositionX] = useState(0) - const [offsetPositionY, setOffsetPositionY] = useState(0) - const [compareMode, setCompareMode] = useGlobalCompareMode() - const [refreshOn] = useState(false) - const [refreshSecond, setRefreshSecond] = useState(59) + const data = useLoaderData(); + const nodeRef = useRef(null); + // state variables + const [open, setOpen] = useState(true); + const [offsetPositionX, setOffsetPositionX] = useState(0); + const [offsetPositionY, setOffsetPositionY] = useState(0); + const [compareMode, setCompareMode] = useGlobalCompareMode(); + const [refreshOn] = useState(false); + const [refreshSecond, setRefreshSecond] = useState(59); - const [sensors, setSensors] = useState() - useEffect(() => { - const sortedSensors = [...(data.sensors as any)].sort( - (a, b) => (a.id as unknown as number) - (b.id as unknown as number), - ) - setSensors(sortedSensors) - }, [data]) + const [sensors, setSensors] = useState(); + useEffect(() => { + const sortedSensors = [...(data.sensors as any)].sort( + (a, b) => (a.id as unknown as number) - (b.id as unknown as number) + ); + setSensors(sortedSensors); + }, [data]); - const [searchParams] = useSearchParams() + const [searchParams] = useSearchParams(); - const { deviceId } = useParams() // Get the deviceId from the URL params + const { deviceId } = useParams(); // Get the deviceId from the URL params - const createSensorLink = (sensorIdToBeSelected: string) => { - const lastSegment = matches[matches.length - 1]?.params?.['*'] - if (lastSegment) { - const secondLastSegment = matches[matches.length - 2]?.params?.sensorId - sensorIds.add(secondLastSegment) - sensorIds.add(lastSegment) - } else { - const lastSegment = matches[matches.length - 1]?.params?.sensorId - if (lastSegment) { - sensorIds.add(lastSegment) - } - } + const createSensorLink = (sensorIdToBeSelected: string) => { + const lastSegment = matches[matches.length - 1]?.params?.["*"]; + if (lastSegment) { + const secondLastSegment = matches[matches.length - 2]?.params?.sensorId; + sensorIds.add(secondLastSegment); + sensorIds.add(lastSegment); + } else { + const lastSegment = matches[matches.length - 1]?.params?.sensorId; + if (lastSegment) { + sensorIds.add(lastSegment); + } + } - // If sensorIdToBeSelected is second selected sensor - if (sensorIds.has(sensorIdToBeSelected) && sensorIds.size === 2) { - const clonedSet = new Set(sensorIds) - clonedSet.delete(sensorIdToBeSelected) - return `/explore/${deviceId}/${Array.from(clonedSet).join('/')}?${searchParams.toString()}` - } else if (sensorIds.has(sensorIdToBeSelected) && sensorIds.size === 1) { - return `/explore/${deviceId}?${searchParams.toString()}` - } else if (sensorIds.size === 0) { - return `/explore/${deviceId}/${sensorIdToBeSelected}?${searchParams.toString()}` - } else if (sensorIds.size === 1) { - return `/explore/${deviceId}/${Array.from(sensorIds).join('/')}/${sensorIdToBeSelected}?${searchParams.toString()}` - } + // If sensorIdToBeSelected is second selected sensor + if (sensorIds.has(sensorIdToBeSelected) && sensorIds.size === 2) { + const clonedSet = new Set(sensorIds); + clonedSet.delete(sensorIdToBeSelected); + return `/explore/${deviceId}/${Array.from(clonedSet).join("/")}?${searchParams.toString()}`; + } else if (sensorIds.has(sensorIdToBeSelected) && sensorIds.size === 1) { + return `/explore/${deviceId}?${searchParams.toString()}`; + } else if (sensorIds.size === 0) { + return `/explore/${deviceId}/${sensorIdToBeSelected}?${searchParams.toString()}`; + } else if (sensorIds.size === 1) { + return `/explore/${deviceId}/${Array.from(sensorIds).join("/")}/${sensorIdToBeSelected}?${searchParams.toString()}`; + } - return '' - } + return ""; + }; - const isSensorActive = (sensorId: string) => { - if (sensorIds.has(sensorId)) { - return 'bg-green-100 dark:bg-dark-green' - } + const isSensorActive = (sensorId: string) => { + if (sensorIds.has(sensorId)) { + return "bg-green-100 dark:bg-dark-green"; + } - return 'hover:bg-muted' - } + return "hover:bg-muted"; + }; - function handleDrag(_e: any, data: DraggableData) { - setOffsetPositionX(data.x) - setOffsetPositionY(data.y) - } + function handleDrag(_e: any, data: DraggableData) { + setOffsetPositionX(data.x); + setOffsetPositionY(data.y); + } - const addLineBreaks = (text: string) => - text.split('\\n').map((text, index) => ( - - {text} -
-
- )) + const addLineBreaks = (text: string) => + text.split("\\n").map((text, index) => ( + + {text} +
+
+ )); - useEffect(() => { - let interval: any = null - if (refreshOn) { - if (refreshSecond == 0) { - setRefreshSecond(59) - } - interval = setInterval(() => { - setRefreshSecond((refreshSecond) => refreshSecond - 1) - }, 1000) - } else if (!refreshOn) { - clearInterval(interval) - } - return () => clearInterval(interval) - }, [refreshOn, refreshSecond]) + useEffect(() => { + let interval: any = null; + if (refreshOn) { + if (refreshSecond == 0) { + setRefreshSecond(59); + } + interval = setInterval(() => { + setRefreshSecond((refreshSecond) => refreshSecond - 1); + }, 1000); + } else if (!refreshOn) { + clearInterval(interval); + } + return () => clearInterval(interval); + }, [refreshOn, refreshSecond]); - if (!data.device) return null + if (!data.device) return null; - return ( - <> - {open && ( - } - defaultPosition={{ x: offsetPositionX, y: offsetPositionY }} - onDrag={handleDrag} - bounds="#osem" - handle="#deviceDetailBoxTop" - disabled={!isBrowser && !isTablet} - > -
-
- {navigation.state === 'loading' && ( -
- -
- )} -
-
-
- {data.device.name} -
- - - - - - - Share this link - - - - Close - - - - - - - - - Actions - - - - Compare - - - - - - Archive - - - - - - - - External Link - - - - - + return ( + <> + {open && ( + } + defaultPosition={{ x: offsetPositionX, y: offsetPositionY }} + onDrag={handleDrag} + bounds="#osem" + handle="#deviceDetailBoxTop" + disabled={!isBrowser && !isTablet} + > +
+
+ {navigation.state === "loading" && ( +
+ +
+ )} +
+
+
+ {data.device.name} +
+ + + + + + + Share this link + + + + Close + + + + + + + + + Actions + + + + Compare + + + + + + Archive + + + + + + + + External Link + + + + + - setOpen(false)} - /> - { - void navigate({ - pathname: '/explore', - search: searchParams.toString(), - }) - }} - /> -
-
-
-
- {data.device.image ? ( - device_image - ) : ( -
- -
- )} -
-
- - - - - - - {data.device.expiresAt && ( - <> - - - - )} -
-
- {data.device.tags && data.device.tags.length > 0 && ( -
-
-
- Tags -
-
- -
- {data.device.tags.map((tag: string) => ( - { - event.stopPropagation() + setOpen(false)} + /> + { + void navigate({ + pathname: "/explore", + search: searchParams.toString(), + }); + }} + /> +
+
+
+
+ {data.device.image ? ( + device_image + ) : ( +
+ +
+ )} +
+
+ + + + + + + {data.device.expiresAt && ( + <> + + + + )} +
+
+ {data.device.tags && data.device.tags.length > 0 && ( +
+
+
+ Tags +
+
+ +
+ {data.device.tags.map((tag: string) => ( + { + event.stopPropagation(); - const currentParams = new URLSearchParams( - searchParams.toString(), - ) + const currentParams = new URLSearchParams( + searchParams.toString() + ); - // Safely retrieve and parse the current tags - const currentTags = - currentParams.get('tags')?.split(',') || [] + // Safely retrieve and parse the current tags + const currentTags = + currentParams.get("tags")?.split(",") || []; - // Toggle the tag in the list - const updatedTags = currentTags.includes(tag) - ? currentTags.filter((t) => t !== tag) // Remove if already present - : [...currentTags, tag] // Add if not present + // Toggle the tag in the list + const updatedTags = currentTags.includes(tag) + ? currentTags.filter((t) => t !== tag) // Remove if already present + : [...currentTags, tag]; // Add if not present - // Update the tags parameter or remove it if empty - if (updatedTags.length > 0) { - currentParams.set( - 'tags', - updatedTags.join(','), - ) - } else { - currentParams.delete('tags') - } + // Update the tags parameter or remove it if empty + if (updatedTags.length > 0) { + currentParams.set( + "tags", + updatedTags.join(",") + ); + } else { + currentParams.delete("tags"); + } - // Update the URL with the new search params - void navigate({ - search: currentParams.toString(), - }) - }} - > - {tag} - - ))} -
-
-
-
- )} - - {data.device.logEntries.length > 0 && ( - <> - - - - )} - {data.device.description && ( - - - - Description - - - {addLineBreaks(data.device.description)} - - - - )} - - - - Sensors - - -
-
- {sensors && - sensors.map( - (sensor: SensorWithLatestMeasurement) => { - const sensorLink = createSensorLink(sensor.id) - if (sensorLink === '') { - return ( - - toast({ - title: - 'Cant select more than 2 sensors', - description: - 'Deselect one sensor to select another', - variant: 'destructive', - }) - } - > - - - ) - } - return ( - - - - - - ) - }, - )} -
-
-
-
-
-
-
-
- - )} - {compareMode && ( - - { - setCompareMode(!compareMode) - setOpen(true) - }} - /> - Compare devices - - Choose a device from the map to compare with. - - - )} - {!open && ( -
{ - setOpen(true) - }} - className="absolute bottom-[10px] left-4 flex cursor-pointer rounded-xl border border-gray-100 bg-white shadow-lg transition-colors duration-300 ease-in-out hover:brightness-90 dark:bg-zinc-800 dark:text-zinc-200 dark:opacity-90 sm:bottom-[30px] sm:left-[10px]" - > - - - -
- -
-
- -

Open device details

-
-
-
-
- )} - - ) + // Update the URL with the new search params + void navigate({ + search: currentParams.toString(), + }); + }} + > + {tag} + + ))} +
+
+
+
+ )} + + {data.device.logEntries.length > 0 && ( + <> + + + + )} + {data.device.description && ( + + + + Description + + + {addLineBreaks(data.device.description)} + + + + )} + + + + Sensors + + +
+
+ {sensors && + sensors.map( + (sensor: SensorWithLatestMeasurement) => { + const sensorLink = createSensorLink(sensor.id); + if (sensorLink === "") { + return ( + + toast({ + title: + "Cant select more than 2 sensors", + description: + "Deselect one sensor to select another", + variant: "destructive", + }) + } + > + + + ); + } + return ( + + + + + + ); + } + )} +
+
+
+
+
+
+
+
+
+ )} + {compareMode && ( + + { + setCompareMode(!compareMode); + setOpen(true); + }} + /> + Compare devices + + Choose a device from the map to compare with. + + + )} + {!open && ( +
{ + setOpen(true); + }} + className="absolute bottom-[10px] left-4 flex cursor-pointer rounded-xl border border-gray-100 bg-white shadow-lg transition-colors duration-300 ease-in-out hover:brightness-90 dark:bg-zinc-800 dark:text-zinc-200 dark:opacity-90 sm:bottom-[30px] sm:left-[10px]" + > + + + +
+ +
+
+ +

Open device details

+
+
+
+
+ )} + + ); } const InfoItem = ({ - icon: Icon, - title, - text, + icon: Icon, + title, + text, }: { - icon: React.ElementType - title: string - text?: string + icon: React.ElementType; + title: string; + text?: string; }) => - text && ( -
-
{title}
-
- - {text} -
-
- ) + text && ( +
+
{title}
+
+ + {text} +
+
+ ); diff --git a/app/components/header/menu/index.tsx b/app/components/header/menu/index.tsx index 64389ff9..fe31a44a 100644 --- a/app/components/header/menu/index.tsx +++ b/app/components/header/menu/index.tsx @@ -97,7 +97,7 @@ export default function Menu() { - {t("explore_label")} + {"Explore"} )} @@ -105,7 +105,7 @@ export default function Menu() { - {t("profile_label")} + Profile )} @@ -114,7 +114,7 @@ export default function Menu() { - {t("settings_label")} + {"Settings"} diff --git a/app/components/landing/footer.tsx b/app/components/landing/footer.tsx index 2d0178f9..71388542 100644 --- a/app/components/landing/footer.tsx +++ b/app/components/landing/footer.tsx @@ -1,106 +1,100 @@ -import { useTranslation } from 'react-i18next' - export default function Footer() { - const { t } = useTranslation('footer') - return ( - - ) + return ( + + ); } diff --git a/app/components/landing/header/header.tsx b/app/components/landing/header/header.tsx index e11e23ef..cc61b57c 100644 --- a/app/components/landing/header/header.tsx +++ b/app/components/landing/header/header.tsx @@ -2,7 +2,6 @@ import { useState } from "react"; import { Link } from "react-router"; // import { ModeToggle } from "../../mode-toggle"; import LanguageSelector from "./language-selector"; -import { useTranslation } from "react-i18next"; const links = [ { @@ -34,8 +33,6 @@ const links = [ export default function Header() { const [openMenu, setOpenMenu] = useState(false); - const { t } = useTranslation("header"); - return (