import { useCallback } from 'react'; import * as FaIcons from 'react-icons/fa'; import { FaCube, FaLock } from 'react-icons/fa'; import { useSessionActions, useSessionSelector } from '../../context/SessionContext'; import CardFrame from '../CardFrame'; // Each control keeps its own JSX file; this panel only selects its renderer. import ReadOnlyControl from './controls/ReadOnlyControl'; import ToggleControl from './controls/ToggleControl'; import NumberControl from './controls/NumberControl'; import TextControl from './controls/TextControl'; import SelectControl from './controls/SelectControl'; import ButtonControl from './controls/ButtonControl'; const controls = { readOnly: ReadOnlyControl, toggle: ToggleControl, number: NumberControl, text: TextControl, select: SelectControl, button: ButtonControl }; function ActivityTile({ entity, connected, allowed, admin }) { const { homeAssistantActivityAct } = useSessionActions(); const onChange = useCallback((value) => homeAssistantActivityAct(entity.id, value), [homeAssistantActivityAct, entity.id]); // Resolve precisely the same Font Awesome names accepted by social links. // Unknown names remain usable with a neutral fallback rather than an error. const candidate = FaIcons[entity.icon?.trim()]; const Icon = typeof candidate === 'function' ? candidate : FaCube; // Translucent fills preserve text contrast; an amber border still identifies // locked tiles regardless of the admin's chosen color. const color = /^#[0-9a-f]{6}$/i.test(entity.color) ? entity.color : '#3b82f6'; // Ordinary users see only the value; admins retain the entity's actual input. const Control = entity.readOnly && !admin ? ReadOnlyControl : controls[entity.type] || ReadOnlyControl; const disabled = !connected || !entity.available || !allowed || ((entity.locked || entity.readOnly) && !admin); return
{!entity.available && entity.type !== 'readOnly' ? Unavailable : null}
; } export default function HomeAssistantActivitiesPanel() { const state = useSessionSelector((session) => session.session?.homeAssistantActivities); const socketConnected = useSessionSelector((session) => session.connected); const role = useSessionSelector((session) => session.session?.role); const mode = useSessionSelector((session) => session.session?.mode); const admin = role === 'admin' || role === 'lockdown'; const allowed = ['user', 'admin', 'lockdown'].includes(role) && (mode !== 'admin' || admin) && (mode !== 'lockdown' || role === 'lockdown'); if (!state?.enabled || !state.items.length) return null; // Session data survives a browser disconnect. Disable immediately so local // debounce timers are cancelled even when HA itself remains connected. const connected = state.connected && socketConnected; // Reuse the room-control auto-fit grid and compact tile rhythm, while keeping // all data and commands in the Activities namespace on both device layouts. return {connected ? 'Connected' : 'Offline'}}> {!connected ?

{socketConnected ? 'Home Assistant is offline.' : 'Server disconnected.'} Values may be out of date.

: null} {!allowed ?

Controls are read-only with your current access.

: null}
{state.items.map((entity) => )}
; }