diff --git a/frontend/src/pages/integrations/alice/components/smart-device/smart-device.tsx b/frontend/src/pages/integrations/alice/components/smart-device/smart-device.tsx index 60b39f1b0..d05495925 100644 --- a/frontend/src/pages/integrations/alice/components/smart-device/smart-device.tsx +++ b/frontend/src/pages/integrations/alice/components/smart-device/smart-device.tsx @@ -1,5 +1,5 @@ import { observer } from 'mobx-react-lite'; -import { FormEvent, useCallback, useEffect, useState } from 'react'; +import React, { FormEvent, useCallback, useEffect, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import CopyIcon from '@/assets/icons/copy.svg'; import EditSquareIcon from '@/assets/icons/edit-square.svg'; @@ -28,6 +28,104 @@ export const SmartDevice = observer(({ id, deviceStore, onSave, onDelete, onOpen const [isDeleteDevice, setIsDeleteDevice] = useState(false); const [data, setData] = useState>({ capabilities: [], properties: [] }); + const renderDropdownOptionWithIcon = (icon: React.ReactNode, text: string) => ( +
+ {icon ?? '๐Ÿ“ฆ'} + {text} +
+ ); + + // --- Icon maps (emoji now; later can be replaced with SVG components) + // Category keys = deviceTypes keys + const CategoryIcon: Record = { + sensor: '๐Ÿ“Ÿ', + smart_meter: 'โฒ', + media: '๐Ÿ“บ', + cooking: '๐Ÿณ', + appliances: '๐Ÿ ', + pet: '๐Ÿพ', + climate: '๐ŸŒก๏ธ', + electrics: '๐Ÿ”Œ', + openable: '๐Ÿšช', + other: 'โš™๏ธ', + default: '๐Ÿ“ฆ', + }; + + // Type keys = strings from deviceTypes[category] + const TypeIcon: Record = { + // --- sensor --- + 'devices.types.sensor': '๐Ÿ‘€', + 'devices.types.sensor.button': '๐Ÿ”˜', + 'devices.types.sensor.climate': '๐ŸŒก๏ธ', + 'devices.types.sensor.gas': '๐Ÿ’จ', + 'devices.types.sensor.illumination': 'โ˜€๏ธ', + 'devices.types.sensor.motion': '๐Ÿƒโ€โ™‚๏ธ', + 'devices.types.sensor.open': '๐Ÿšช', + 'devices.types.sensor.smoke': '๐Ÿšญ', + 'devices.types.sensor.vibration': 'ใ€ฐ๏ธ', + 'devices.types.sensor.water_leak': '๐Ÿ’ง', + + // --- smart_meter --- + 'devices.types.smart_meter': 'โฒ', + 'devices.types.smart_meter.cold_water': '๐Ÿ’ง', + 'devices.types.smart_meter.electricity': 'โšก', + 'devices.types.smart_meter.gas': '๐Ÿ”ฅ', + 'devices.types.smart_meter.heat': '๐ŸŒก๏ธ', + 'devices.types.smart_meter.hot_water': '๐Ÿšฟ', + + // --- media --- + 'devices.types.camera': '๐Ÿ“ท', + 'devices.types.media_device': '๐ŸŽฌ', + 'devices.types.media_device.receiver': '๐Ÿ“ก', + 'devices.types.media_device.tv': '๐Ÿ“บ', + 'devices.types.media_device.tv_box': '๐Ÿ“ฆ', + + // --- cooking --- + 'devices.types.cooking': '๐Ÿ‘จโ€๐Ÿณ', + 'devices.types.cooking.coffee_maker': 'โ˜•', + 'devices.types.cooking.kettle': '๐Ÿซ–', + 'devices.types.cooking.multicooker': '๐Ÿฒ', + 'devices.types.dishwasher': '๐Ÿฝ๏ธ', + + // --- appliances --- + 'devices.types.iron': '๐Ÿงผ', + 'devices.types.vacuum_cleaner': '๐Ÿงน', + 'devices.types.washing_machine': '๐Ÿ‘•', + + // --- pet --- + 'devices.types.pet_drinking_fountain': 'โ›ฒ', + 'devices.types.pet_feeder': '๐Ÿ–', + + // --- climate --- + 'devices.types.humidifier': '๐Ÿ’ฆ', + 'devices.types.purifier': '๐Ÿซง', + 'devices.types.thermostat': '๐ŸŒก๏ธ', + 'devices.types.thermostat.ac': 'โ„๏ธ', + 'devices.types.ventilation': '๐ŸŒฌ๏ธ', + 'devices.types.ventilation.fan': '๐Ÿชญ', + + // --- electrics --- + 'devices.types.light': '๐Ÿ’ก', + 'devices.types.light.lamp': '๐Ÿ›‹๏ธ', + 'devices.types.light.ceiling': '๐Ÿชฉ', + 'devices.types.light.strip': '๐ŸŒˆ', + 'devices.types.socket': '๐Ÿ”Œ', + 'devices.types.switch': 'โป', + 'devices.types.switch.relay': '๐Ÿ”€', + + // --- openable --- + 'devices.types.openable': '๐Ÿšช', + 'devices.types.openable.curtain': '๐ŸชŸ', + 'devices.types.openable.valve': '๐Ÿšฐ', + + // --- other --- + 'devices.types.other': 'โš™๏ธ', + + // fallback + default: '๐Ÿ“Ÿ', + }; + + useEffect(() => { setIsEditingTitle(!id); const cat = id @@ -164,7 +262,13 @@ export const SmartDevice = observer(({ id, deviceStore, onSave, onDelete, onOpen
{t('alice.labels.device-category')}
({ label: t(`alice.device-types.${val}`), value: val }))} + options={Object.keys(deviceTypes).map((val) => ({ + label: renderDropdownOptionWithIcon( + CategoryIcon[val] ?? CategoryIcon.default, + t(`alice.device-types.${val}`) + ), + value: val + }))} onChange={({ value }) => { setCategory(value as string); setData({ ...data, type: deviceTypes[value as string].at(0) }); @@ -176,7 +280,14 @@ export const SmartDevice = observer(({ id, deviceStore, onSave, onDelete, onOpen ({ label: value, value }))} + options={ + !category + ? [] + : deviceTypes[category].map((value) => ({ + label: renderDropdownOptionWithIcon(TypeIcon[value] ?? TypeIcon.default, value), + value + })) + } onChange={(option) => setData({ ...data, type: option.value as string })} /> diff --git a/frontend/src/pages/integrations/alice/components/smart-device/styles.css b/frontend/src/pages/integrations/alice/components/smart-device/styles.css index 65640a9ad..ec13214da 100644 --- a/frontend/src/pages/integrations/alice/components/smart-device/styles.css +++ b/frontend/src/pages/integrations/alice/components/smart-device/styles.css @@ -11,3 +11,23 @@ display: flex; gap: 12px; } + +/* --- Generic inline icon + label for Dropdown options --- */ +.aliceOptionWithIcon{ + display:flex; + align-items:center; + gap:8px; + line-height:1.2; +} +.aliceOptionWithIcon-icon{ + display:inline-flex; + width:16px; + height:16px; + font-size:16px; + flex:0 0 16px; +} +.aliceOptionWithIcon-label{ + white-space:nowrap; + overflow:hidden; + text-overflow:ellipsis; +}