From aa577467fbe2008a39ae61216d27eceee6bbf835 Mon Sep 17 00:00:00 2001 From: JerryVincent Date: Wed, 21 Jan 2026 15:31:34 +0100 Subject: [PATCH 1/3] This commit guides the user to select the appropriate sensor while creating a new device. --- app/components/device/new/sensors-info.tsx | 116 ++++++++++++++++++--- 1 file changed, 104 insertions(+), 12 deletions(-) diff --git a/app/components/device/new/sensors-info.tsx b/app/components/device/new/sensors-info.tsx index 1c6b77ed..fe2fa3b0 100644 --- a/app/components/device/new/sensors-info.tsx +++ b/app/components/device/new/sensors-info.tsx @@ -1,8 +1,10 @@ +import { InfoIcon } from 'lucide-react' 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 { useToast } from '~/components/ui/use-toast' import { cn } from '~/lib/utils' import { getSensorsForModel } from '~/utils/model-definitions' @@ -24,12 +26,14 @@ type SensorGroup = { export function SensorSelectionStep() { const { watch, setValue } = useFormContext() + const { toast } = useToast() const selectedDevice = watch('model') const [selectedDeviceModel, setSelectedDeviceModel] = useState( null, ) const [sensors, setSensors] = useState([]) const [selectedSensors, setSelectedSensors] = useState([]) + const [highlightedGroup, setHighlightedGroup] = useState(null) useEffect(() => { if (selectedDevice) { @@ -38,12 +42,11 @@ export function SensorSelectionStep() { : selectedDevice setSelectedDeviceModel(deviceModel) - if (deviceModel !== 'custom') { + const fetchSensors = () => { const fetchedSensors = getSensorsForModel(deviceModel) setSensors(fetchedSensors) - } else { - setSensors([]) } + fetchSensors() } }, [selectedDevice]) @@ -52,6 +55,27 @@ export function SensorSelectionStep() { setSelectedSensors(savedSelectedSensors) }, [watch]) + // Clear highlight after a delay + useEffect(() => { + if (highlightedGroup) { + const timer = setTimeout(() => { + setHighlightedGroup(null) + }, 2000) + return () => clearTimeout(timer) + } + }, [highlightedGroup]) + + 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, + ) const groupSensorsByType = (sensors: Sensor[]): SensorGroup[] => { const grouped = sensors.reduce( (acc, sensor) => { @@ -70,7 +94,14 @@ export function SensorSelectionStep() { image: sensors.find((sensor) => sensor.image)?.image, })) } + return Object.entries(grouped).map(([sensorType, sensors]) => ({ + sensorType, + sensors, + image: sensors.find((sensor) => sensor.image)?.image, + })) + } + const sensorGroups = groupSensorsByType(sensors) const sensorGroups = groupSensorsByType(sensors) const handleGroupToggle = (group: SensorGroup) => { @@ -104,6 +135,26 @@ export function SensorSelectionStep() { setValue('selectedSensors', updatedSensors) } + const handleCardClick = (group: SensorGroup) => { + if (selectedDeviceModel === 'senseBoxHomeV2') { + // For senseBoxHomeV2, clicking the card selects the whole group + handleGroupToggle(group) + } else { + // For other devices, highlight parameters and show info toast + setHighlightedGroup(group.sensorType) + toast({ + title: 'Select Parameters', + description: + 'Click on the individual parameters below to select the sensors you want to use.', + duration: 3000, + }) + } + } + + const handleSensorToggle = (sensor: Sensor) => { + const isAlreadySelected = selectedSensors.some( + (s) => s.title === sensor.title && s.sensorType === sensor.sensorType, + ) const handleSensorToggle = (sensor: Sensor) => { const isAlreadySelected = selectedSensors.some( (s) => s.title === sensor.title && s.sensorType === sensor.sensorType, @@ -119,17 +170,49 @@ export function SensorSelectionStep() { setSelectedSensors(updatedSensors) setValue('selectedSensors', updatedSensors) } + 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) { return

Please select a device first.

} - if (selectedDevice === 'custom') { + if (selectedDevice === 'Custom') { return } + const isSenseBoxHomeV2 = selectedDeviceModel === 'senseBoxHomeV2' + return (
+ {/* Instruction banner */} +
+ {isSenseBoxHomeV2 ? ( + + Click on a sensor card to select all its parameters at once. + + ) : ( + + +

+ Click on individual parameters within each card to select the + sensors you need. +

+
+ )} +
+
{sensorGroups.map((group) => { @@ -140,6 +223,7 @@ export function SensorSelectionStep() { s.sensorType === sensor.sensorType, ), ) + const isHighlighted = highlightedGroup === group.sensorType return ( handleGroupToggle(group) - : undefined - } + onClick={() => handleCardClick(group)} >

+
    + {group.sensors.map((sensor) => { + const isSelected = selectedSensors.some( + (s) => + s.title === sensor.title && + s.sensorType === sensor.sensorType, + )
      {group.sensors.map((sensor) => { const isSelected = selectedSensors.some( @@ -177,13 +265,17 @@ export function SensorSelectionStep() {
    • { e.stopPropagation() handleSensorToggle(sensor) From 9dec635dc206b0d7791e2becb5f4757a68cd17d3 Mon Sep 17 00:00:00 2001 From: JerryVincent Date: Wed, 11 Feb 2026 14:01:00 +0100 Subject: [PATCH 2/3] updated sensor-info component to fix the proper closing of tags. --- app/components/device/new/sensors-info.tsx | 56 ++-------------------- 1 file changed, 5 insertions(+), 51 deletions(-) diff --git a/app/components/device/new/sensors-info.tsx b/app/components/device/new/sensors-info.tsx index fe2fa3b0..9822065a 100644 --- a/app/components/device/new/sensors-info.tsx +++ b/app/components/device/new/sensors-info.tsx @@ -1,4 +1,3 @@ -import { InfoIcon } from 'lucide-react' import { useState, useEffect } from 'react' import { useFormContext } from 'react-hook-form' import { z } from 'zod' @@ -65,17 +64,6 @@ export function SensorSelectionStep() { } }, [highlightedGroup]) - 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, - ) const groupSensorsByType = (sensors: Sensor[]): SensorGroup[] => { const grouped = sensors.reduce( (acc, sensor) => { @@ -94,14 +82,7 @@ export function SensorSelectionStep() { image: sensors.find((sensor) => sensor.image)?.image, })) } - return Object.entries(grouped).map(([sensorType, sensors]) => ({ - sensorType, - sensors, - image: sensors.find((sensor) => sensor.image)?.image, - })) - } - const sensorGroups = groupSensorsByType(sensors) const sensorGroups = groupSensorsByType(sensors) const handleGroupToggle = (group: SensorGroup) => { @@ -151,10 +132,6 @@ export function SensorSelectionStep() { } } - const handleSensorToggle = (sensor: Sensor) => { - const isAlreadySelected = selectedSensors.some( - (s) => s.title === sensor.title && s.sensorType === sensor.sensorType, - ) const handleSensorToggle = (sensor: Sensor) => { const isAlreadySelected = selectedSensors.some( (s) => s.title === sensor.title && s.sensorType === sensor.sensorType, @@ -170,20 +147,7 @@ export function SensorSelectionStep() { setSelectedSensors(updatedSensors) setValue('selectedSensors', updatedSensors) } - 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) { return

      Please select a device first.

      } @@ -197,18 +161,15 @@ export function SensorSelectionStep() { return (
      {/* Instruction banner */} -
      +
      {isSenseBoxHomeV2 ? ( Click on a sensor card to select all its parameters at once. ) : ( - - -

      - Click on individual parameters within each card to select the - sensors you need. -

      + + Click on individual parameters within each card to select the + sensors you need. )}
      @@ -233,7 +194,7 @@ export function SensorSelectionStep() { isGroupSelected ? 'shadow-lg ring-2 ring-primary' : 'hover:shadow-md', - isHighlighted && 'ring-blue-400 bg-blue-50 ring-2', + isHighlighted && 'bg-blue-50 ring-2 ring-blue-400', )} onClick={() => handleCardClick(group)} > @@ -246,13 +207,6 @@ export function SensorSelectionStep() { {group.sensorType}

-
    - {group.sensors.map((sensor) => { - const isSelected = selectedSensors.some( - (s) => - s.title === sensor.title && - s.sensorType === sensor.sensorType, - )
      {group.sensors.map((sensor) => { const isSelected = selectedSensors.some( From a1e8499c746ec35ccfb86d7f4cc940b2e75065f8 Mon Sep 17 00:00:00 2001 From: JerryVincent Date: Wed, 11 Feb 2026 15:57:27 +0100 Subject: [PATCH 3/3] This commit will change the card appearance of the sensors to an accordion based display, that enables the users to select the parameters clicking the checkboxes associated with them. --- app/components/device/new/sensors-info.tsx | 323 +++++++++++---------- 1 file changed, 172 insertions(+), 151 deletions(-) diff --git a/app/components/device/new/sensors-info.tsx b/app/components/device/new/sensors-info.tsx index 9822065a..422f82ad 100644 --- a/app/components/device/new/sensors-info.tsx +++ b/app/components/device/new/sensors-info.tsx @@ -2,8 +2,15 @@ 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 { useToast } from '~/components/ui/use-toast' +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from '~/components/ui/accordion' +import { Badge } from '~/components/ui/badge' +import { Checkbox } from '~/components/ui/checkbox' +import { Label } from '~/components/ui/label' import { cn } from '~/lib/utils' import { getSensorsForModel } from '~/utils/model-definitions' @@ -25,14 +32,12 @@ type SensorGroup = { export function SensorSelectionStep() { const { watch, setValue } = useFormContext() - const { toast } = useToast() const selectedDevice = watch('model') const [selectedDeviceModel, setSelectedDeviceModel] = useState( null, ) const [sensors, setSensors] = useState([]) const [selectedSensors, setSelectedSensors] = useState([]) - const [highlightedGroup, setHighlightedGroup] = useState(null) useEffect(() => { if (selectedDevice) { @@ -41,11 +46,8 @@ export function SensorSelectionStep() { : selectedDevice setSelectedDeviceModel(deviceModel) - const fetchSensors = () => { - const fetchedSensors = getSensorsForModel(deviceModel) - setSensors(fetchedSensors) - } - fetchSensors() + const fetchedSensors = getSensorsForModel(deviceModel) + setSensors(fetchedSensors) } }, [selectedDevice]) @@ -54,16 +56,6 @@ export function SensorSelectionStep() { setSelectedSensors(savedSelectedSensors) }, [watch]) - // Clear highlight after a delay - useEffect(() => { - if (highlightedGroup) { - const timer = setTimeout(() => { - setHighlightedGroup(null) - }, 2000) - return () => clearTimeout(timer) - } - }, [highlightedGroup]) - const groupSensorsByType = (sensors: Sensor[]): SensorGroup[] => { const grouped = sensors.reduce( (acc, sensor) => { @@ -85,64 +77,50 @@ export function SensorSelectionStep() { 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 isSensorSelected = (sensor: Sensor) => + selectedSensors.some( + (s) => s.title === sensor.title && s.sensorType === sensor.sensorType, ) - const updatedSensors = isGroupSelected + const isGroupFullySelected = (group: SensorGroup) => + group.sensors.every((sensor) => isSensorSelected(sensor)) + + const isGroupPartiallySelected = (group: SensorGroup) => + group.sensors.some((sensor) => isSensorSelected(sensor)) && + !isGroupFullySelected(group) + + const getSelectedCountForGroup = (group: SensorGroup) => + group.sensors.filter((sensor) => isSensorSelected(sensor)).length + + const handleSensorToggle = (sensor: Sensor) => { + const isAlreadySelected = isSensorSelected(sensor) + + const updatedSensors = isAlreadySelected ? selectedSensors.filter( (s) => - !group.sensors.some( - (sensor) => - s.title === sensor.title && s.sensorType === sensor.sensorType, - ), + !(s.title === sensor.title && s.sensorType === sensor.sensorType), ) - : [ - ...selectedSensors, - ...group.sensors.filter( - (sensor) => - !selectedSensors.some( - (s) => - s.title === sensor.title && - s.sensorType === sensor.sensorType, - ), - ), - ] + : [...selectedSensors, sensor] setSelectedSensors(updatedSensors) setValue('selectedSensors', updatedSensors) } - const handleCardClick = (group: SensorGroup) => { - if (selectedDeviceModel === 'senseBoxHomeV2') { - // For senseBoxHomeV2, clicking the card selects the whole group - handleGroupToggle(group) - } else { - // For other devices, highlight parameters and show info toast - setHighlightedGroup(group.sensorType) - toast({ - title: 'Select Parameters', - description: - 'Click on the individual parameters below to select the sensors you want to use.', - duration: 3000, - }) - } - } - - const handleSensorToggle = (sensor: Sensor) => { - const isAlreadySelected = selectedSensors.some( - (s) => s.title === sensor.title && s.sensorType === sensor.sensorType, - ) + const handleGroupToggle = (group: SensorGroup) => { + const isFullySelected = isGroupFullySelected(group) - const updatedSensors = isAlreadySelected + const updatedSensors = isFullySelected ? selectedSensors.filter( (s) => - !(s.title === sensor.title && s.sensorType === sensor.sensorType), + !group.sensors.some( + (sensor) => + s.title === sensor.title && s.sensorType === sensor.sensorType, + ), ) - : [...selectedSensors, sensor] + : [ + ...selectedSensors, + ...group.sensors.filter((sensor) => !isSensorSelected(sensor)), + ] setSelectedSensors(updatedSensors) setValue('selectedSensors', updatedSensors) @@ -159,104 +137,147 @@ export function SensorSelectionStep() { const isSenseBoxHomeV2 = selectedDeviceModel === 'senseBoxHomeV2' return ( -
      - {/* Instruction banner */} -
      - {isSenseBoxHomeV2 ? ( - - Click on a sensor card to select all its parameters at once. - - ) : ( - - Click on individual parameters within each card to select the - sensors you need. - +
      + {/* Selected count summary */} +
      +

      + {selectedSensors.length} sensor + {selectedSensors.length !== 1 ? 's' : ''} selected +

      + {selectedSensors.length > 0 && ( + )}
      -
      -
      - {sensorGroups.map((group) => { - const isGroupSelected = group.sensors.every((sensor) => - selectedSensors.some( - (s) => - s.title === sensor.title && - s.sensorType === sensor.sensorType, - ), - ) - const isHighlighted = highlightedGroup === group.sensorType - - return ( - handleCardClick(group)} - > - -

      - {group.sensorType} -

      - -
        - {group.sensors.map((sensor) => { - const isSelected = selectedSensors.some( - (s) => - s.title === sensor.title && - s.sensorType === sensor.sensorType, - ) + + {sensorGroups.map((group) => { + const isFullySelected = isGroupFullySelected(group) + const isPartiallySelected = isGroupPartiallySelected(group) + const selectedCount = getSelectedCountForGroup(group) - return ( -
      • { - e.stopPropagation() - handleSensorToggle(sensor) - } - : undefined - } - > - {sensor.title} ({sensor.unit}) -
      • - ) - })} -
      -
      + return ( + + +
      +
      {group.image && ( {`${group.sensorType} )} +
      +

      {group.sensorType}

      +

      + {group.sensors.length} parameter + {group.sensors.length !== 1 ? 's' : ''} +

      +
      - - - ) - })} -
      -
      + {selectedCount > 0 && ( + + {selectedCount} selected + + )} +
      + + +
      + {/* Select All option */} +
      e.stopPropagation()} + > + handleGroupToggle(group)} + /> + +
      + + {/* Individual sensors - only show for non-senseBoxHomeV2 or always show */} + {!isSenseBoxHomeV2 && ( +
      + {group.sensors.map((sensor) => { + const isSelected = isSensorSelected(sensor) + const sensorId = `sensor-${group.sensorType}-${sensor.title}` + + return ( +
      e.stopPropagation()} + > + handleSensorToggle(sensor)} + /> + +
      + ) + })} +
      + )} + + {/* For senseBoxHomeV2, just show the parameters as info */} + {isSenseBoxHomeV2 && ( +
      +

      Includes:

      + {group.sensors.map((sensor) => ( +

      + • {sensor.title} ({sensor.unit}) +

      + ))} +
      + )} +
      +
      + + ) + })} +
      ) }