Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion front/src/components/boxs/device-in-room/DeviceRow.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import AirConditioningModeDeviceFeature from './device-features/AirConditioningM
import PilotWireModeDeviceFeature from './device-features/PilotWireModeDeviceFeature';
import LMHVolumeDeviceFeature from './device-features/LMHVolumeDeviceFeature';
import PushDeviceFeature from './device-features/PushDeviceFeature';
import VacuumCleanerDockDeviceFeature from './device-features/VacuumCleanerDockDeviceFeature';
import VacuumCleanerModeDeviceFeature from './device-features/VacuumCleanerModeDeviceFeature';

const ROW_TYPE_BY_FEATURE_TYPE = {
[DEVICE_FEATURE_TYPES.LIGHT.BINARY]: BinaryDeviceFeature,
Expand Down Expand Up @@ -44,7 +46,10 @@ const ROW_TYPE_BY_FEATURE_TYPE = {
[DEVICE_FEATURE_TYPES.ELECTRICAL_VEHICLE_CLIMATE.CLIMATE_ON]: BinaryDeviceFeature,
[DEVICE_FEATURE_TYPES.ELECTRICAL_VEHICLE_CLIMATE.TARGET_TEMPERATURE]: SetpointDeviceFeature,
[DEVICE_FEATURE_TYPES.ELECTRICAL_VEHICLE_COMMAND.ALARM]: BinaryDeviceFeature,
[DEVICE_FEATURE_TYPES.ELECTRICAL_VEHICLE_COMMAND.LOCK]: BinaryDeviceFeature
[DEVICE_FEATURE_TYPES.ELECTRICAL_VEHICLE_COMMAND.LOCK]: BinaryDeviceFeature,
[DEVICE_FEATURE_TYPES.VACUUM_CLEANER.DOCK]: VacuumCleanerDockDeviceFeature,
[DEVICE_FEATURE_TYPES.VACUUM_CLEANER.RUN_MODE]: VacuumCleanerModeDeviceFeature,
[DEVICE_FEATURE_TYPES.VACUUM_CLEANER.CLEAN_MODE]: VacuumCleanerModeDeviceFeature
};

const DeviceRow = ({ children, ...props }) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Component } from 'preact';
import cx from 'classnames';
import { Text } from 'preact-i18n';
import style from './style.css';

class VacuumCleanerDockDeviceFeature extends Component {
constructor(props) {
super(props);
this.state = {
loading: false
};
}

dock = async () => {
await this.setState({ loading: true });
this.props.updateValue(this.props.deviceFeature, 1);
setTimeout(() => {
this.setState({ loading: false });
}, 350);
};
Comment on lines +14 to +20
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Preact class components, does setState return a Promise that can be awaited?

💡 Result:

No, in Preact class components, setState does not return a Promise that can be awaited. It returns void.

Citations:


Tie loading state to the actual command promise and prevent duplicate dock requests.

await this.setState(...) has no effect in Preact (setState returns void), and resetting loading after a fixed timeout (350ms) can re-enable clicks before updateValue completes or fails. The button also lacks the disabled attribute to prevent duplicate submissions during the request.

Proposed fix
 class VacuumCleanerDockDeviceFeature extends Component {
@@
   dock = async () => {
-    await this.setState({ loading: true });
-    this.props.updateValue(this.props.deviceFeature, 1);
-    setTimeout(() => {
-      this.setState({ loading: false });
-    }, 350);
+    if (this.state.loading) {
+      return;
+    }
+    this.setState({ loading: true });
+    try {
+      await this.props.updateValue(this.props.deviceFeature, 1);
+    } finally {
+      this.setState({ loading: false });
+    }
   };
@@
           <button
             onClick={this.dock}
             type="button"
+            disabled={loading}
             class={cx('btn', 'btn-outline-primary', 'btn-sm', style.btnLoading, {
               'btn-loading': loading
             })}
           >

Also applies to: 30-35

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@front/src/components/boxs/device-in-room/device-features/VacuumCleanerDockDeviceFeature.jsx`
around lines 14 - 20, The dock method currently uses await this.setState(...)
(ineffective in Preact) and a fixed 350ms timeout to clear loading, which can
re-enable the UI before the command completes and allows duplicate requests;
change dock to set loading:true synchronously via this.setState({loading:true}),
call the promise-returning this.props.updateValue(this.props.deviceFeature, 1)
and await it, catch errors, then setState({loading:false}) in finally; also
update the button/render that triggers dock to include
disabled={this.state.loading} (or equivalent) so clicks are prevented while the
request is inflight; apply the same pattern to the other similar method(s)
referenced at lines 30-35.


render(props, { loading }) {
return (
<tr>
<td>
<i class="fe fe-home" />
</td>
<td>{props.rowName}</td>
<td class="text-right">
<button
onClick={this.dock}
type="button"
class={cx('btn', 'btn-outline-primary', 'btn-sm', style.btnLoading, {
'btn-loading': loading
})}
>
<i class="fe fe-home" /> <Text id="dashboard.boxes.devicesInRoom.vacuumDock" />
</button>
</td>
</tr>
);
}
}

export default VacuumCleanerDockDeviceFeature;
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import get from 'get-value';
import { Text } from 'preact-i18n';

import { DeviceFeatureCategoriesIcon } from '../../../../utils/consts';
import { VACUUM_CLEANER_MODE } from '../../../../../../server/utils/constants';

const VacuumCleanerModeDeviceFeature = ({ children, ...props }) => {
const { deviceFeature } = props;
const { category, type } = deviceFeature;

function updateValue(e) {
props.updateValueWithDebounce(deviceFeature, e.currentTarget.value);
}
Comment on lines +11 to +13
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Cast selected mode to number before updating value.

e.currentTarget.value is a string. Forwarding it as-is can break numeric mode mapping/commands downstream.

🔧 Proposed fix
 function updateValue(e) {
-  props.updateValueWithDebounce(deviceFeature, e.currentTarget.value);
+  props.updateValueWithDebounce(deviceFeature, Number(e.currentTarget.value));
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function updateValue(e) {
props.updateValueWithDebounce(deviceFeature, e.currentTarget.value);
}
function updateValue(e) {
props.updateValueWithDebounce(deviceFeature, Number(e.currentTarget.value));
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@front/src/components/boxs/device-in-room/device-features/VacuumCleanerModeDeviceFeature.jsx`
around lines 11 - 13, The updateValue handler forwards a string from
e.currentTarget.value which should be a number; modify function updateValue to
convert e.currentTarget.value to a Number (e.g., using Number(...) or
parseInt/parseFloat as appropriate) before calling props.updateValueWithDebounce
with deviceFeature and the numeric value so downstream numeric mode
mapping/commands receive a number instead of a string.


return (
<tr>
<td>
<i class={`fe fe-${get(DeviceFeatureCategoriesIcon, `${category}.${type}`, { default: 'settings' })}`} />
</td>
<td>{props.rowName}</td>

<td class="py-0">
<div class="justify-content-end">
<div class="form-group mb-0">
<select value={props.deviceFeature.last_value} onChange={updateValue} class="form-control form-control-sm">
<option value={VACUUM_CLEANER_MODE.IDLE}>
<Text id={`deviceFeatureAction.category.vacuum-cleaner.mode.idle`} />
</option>
<option value={VACUUM_CLEANER_MODE.CLEANING}>
<Text id={`deviceFeatureAction.category.vacuum-cleaner.mode.cleaning`} />
</option>
<option value={VACUUM_CLEANER_MODE.MAPPING}>
<Text id={`deviceFeatureAction.category.vacuum-cleaner.mode.mapping`} />
</option>
</select>
</div>
</div>
</td>
</tr>
);
};

export default VacuumCleanerModeDeviceFeature;
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import NoRecentValueBadge from './NoRecentValueBadge';
import TemperatureSensorDeviceValue from './TemperatureSensorDeviceValue';
import LevelSensorDeviceValue from './LevelSensorDeviceValue';
import PressureSensorDeviceValue from './PressureSensorDeviceValue';
import VacuumCleanerStateDeviceValue from './VacuumCleanerStateDeviceValue';

const DISPLAY_BY_FEATURE_CATEGORY = {
[DEVICE_FEATURE_CATEGORIES.MOTION_SENSOR]: MotionSensorDeviceValue,
Expand Down Expand Up @@ -50,7 +51,8 @@ const DISPLAY_BY_FEATURE_TYPE = {
[DEVICE_FEATURE_TYPES.ELECTRICAL_VEHICLE_CLIMATE.INDOOR_TEMPERATURE]: TemperatureSensorDeviceValue,
[DEVICE_FEATURE_TYPES.ELECTRICAL_VEHICLE_BATTERY.BATTERY_RANGE_ESTIMATE]: DistanceSensorDeviceValue,
[DEVICE_FEATURE_TYPES.ELECTRICAL_VEHICLE_STATE.ODOMETER]: DistanceSensorDeviceValue,
[DEVICE_FEATURE_TYPES.ELECTRICAL_VEHICLE_STATE.TIRE_PRESSURE]: PressureSensorDeviceValue
[DEVICE_FEATURE_TYPES.ELECTRICAL_VEHICLE_STATE.TIRE_PRESSURE]: PressureSensorDeviceValue,
[DEVICE_FEATURE_TYPES.VACUUM_CLEANER.STATE]: VacuumCleanerStateDeviceValue
};

const DEVICE_FEATURES_WITHOUT_EXPIRATION = [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Text } from 'preact-i18n';
import cx from 'classnames';

const VacuumCleanerStateDeviceValue = props => {
const { last_value: lastValue = null } = props.deviceFeature;
const valued = lastValue !== null;

return (
<span
class={cx('badge', {
'bg-primary': valued,
'bg-secondary': !valued
})}
>
{!valued && <Text id="dashboard.boxes.devicesInRoom.noValue" />}
{valued && (
<Text id={`deviceFeatureValue.category.vacuum-cleaner.state.${lastValue}`}>
<Text id={`deviceFeatureValue.category.vacuum-cleaner.state.unknown`} fields={{ value: lastValue }} />
</Text>
)}
</span>
);
};

export default VacuumCleanerStateDeviceValue;
29 changes: 28 additions & 1 deletion front/src/config/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,8 @@
"addButton": "+",
"substractButton": "-",
"motionDetected": "Bewegung erkannt",
"pushButton": "Schieben"
"pushButton": "Schieben",
"vacuumDock": "Zurück zur Ladestation"
},
"devices": {
"editDeviceFeaturesLabel": "Wähle die Geräte aus, die du anzeigen möchtest:",
Expand Down Expand Up @@ -3373,6 +3374,13 @@
"medium": "Mittel",
"high": "Hoch"
}
},
"vacuum-cleaner": {
"mode": {
"idle": "Leerlauf",
"cleaning": "Reinigen",
"mapping": "Kartieren"
}
}
}
},
Expand Down Expand Up @@ -3577,6 +3585,18 @@
"3": "Hoch",
"4": "Netz kritisch"
}
},
"vacuum-cleaner": {
"state": {
"unknown": "{{value}} (unbekannt)",
"0": "Gestoppt",
"1": "Läuft",
"2": "Pausiert",
"3": "Fehler",
"4": "Kehrt zur Ladestation zurück",
"5": "Lädt",
"6": "Angedockt"
}
}
}
},
Expand Down Expand Up @@ -4042,6 +4062,13 @@
"unknown": {
"shortCategoryName": "Unbekannt",
"unknown": "Unbekannt"
},
"vacuum-cleaner": {
"shortCategoryName": "Saugroboter",
"state": "Betriebszustand",
"run-mode": "Betriebsmodus",
"clean-mode": "Reinigungsmodus",
"dock": "Zurück zur Ladestation"
}
},
"errorPage": {
Expand Down
29 changes: 28 additions & 1 deletion front/src/config/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,8 @@
"addButton": "+",
"substractButton": "-",
"motionDetected": "Motion detected",
"pushButton": "Push"
"pushButton": "Push",
"vacuumDock": "Return to Dock"
},
"devices": {
"editDeviceFeaturesLabel": "Select the devices you want to display:",
Expand Down Expand Up @@ -3373,6 +3374,13 @@
"medium": "Medium",
"high": "High"
}
},
"vacuum-cleaner": {
"mode": {
"idle": "Idle",
"cleaning": "Clean",
"mapping": "Map"
}
}
}
},
Expand Down Expand Up @@ -3577,6 +3585,18 @@
"3": "High",
"4": "Critical"
}
},
"vacuum-cleaner": {
"state": {
"unknown": "{{value}} (unknown)",
"0": "Stopped",
"1": "Running",
"2": "Paused",
"3": "Error",
"4": "Returning to Dock",
"5": "Charging",
"6": "Docked"
}
}
}
},
Expand Down Expand Up @@ -4042,6 +4062,13 @@
"unknown": {
"shortCategoryName": "Unknown",
"unknown": "Unknown"
},
"vacuum-cleaner": {
"shortCategoryName": "Vacuum Cleaner",
"state": "Operational State",
"run-mode": "Run Mode",
"clean-mode": "Clean Mode",
"dock": "Return to Dock"
}
},
"errorPage": {
Expand Down
29 changes: 28 additions & 1 deletion front/src/config/i18n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,8 @@
"addButton": "+",
"substractButton": "-",
"motionDetected": "Mouvement détecté",
"pushButton": "Appuyer"
"pushButton": "Appuyer",
"vacuumDock": "Retour à la base"
},
"devices": {
"editDeviceFeaturesLabel": "Vous pouvez modifier le nom affiché ici :",
Expand Down Expand Up @@ -3373,6 +3374,13 @@
"medium": "Moyen",
"high": "Fort"
}
},
"vacuum-cleaner": {
"mode": {
"idle": "Repos",
"cleaning": "Nettoyer",
"mapping": "Cartographier"
}
}
}
},
Expand Down Expand Up @@ -3577,6 +3585,18 @@
"3": "Élevé",
"4": "Critique"
}
},
"vacuum-cleaner": {
"state": {
"unknown": "{{value}} (inconnu)",
"0": "Arrêté",
"1": "En cours",
"2": "En pause",
"3": "Erreur",
"4": "Retour à la base",
"5": "En charge",
"6": "Sur la base"
}
}
}
},
Expand Down Expand Up @@ -4042,6 +4062,13 @@
"unknown": {
"shortCategoryName": "Inconnu",
"unknown": "Inconnu"
},
"vacuum-cleaner": {
"shortCategoryName": "Aspirateur Robot",
"state": "État opérationnel",
"run-mode": "Mode de fonctionnement",
"clean-mode": "Mode de nettoyage",
"dock": "Retour à la base"
}
},
"errorPage": {
Expand Down
6 changes: 6 additions & 0 deletions front/src/utils/consts.js
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,12 @@ export const DeviceFeatureCategoriesIcon = {
},
[DEVICE_FEATURE_CATEGORIES.HEPA_FILTER_MONITORING]: {
[DEVICE_FEATURE_TYPES.FILTER_MONITORING.FILTER_LIFE_REMAINING]: 'bar-chart-2'
},
[DEVICE_FEATURE_CATEGORIES.VACUUM_CLEANER]: {
[DEVICE_FEATURE_TYPES.VACUUM_CLEANER.STATE]: 'disc',
[DEVICE_FEATURE_TYPES.VACUUM_CLEANER.RUN_MODE]: 'settings',
[DEVICE_FEATURE_TYPES.VACUUM_CLEANER.CLEAN_MODE]: 'settings',
[DEVICE_FEATURE_TYPES.VACUUM_CLEANER.DOCK]: 'home'
}
};

Expand Down
Loading
Loading