import { useMemo, useState } from 'react'; function normalizeState(value) { return String(value || '').trim(); } function humanizeUiState(value) { const state = normalizeState(value); if (!state) return '--'; if (state.includes('DOCKINGRUNNING')) return 'Returning'; if (state.includes('PAUSED')) return 'Paused'; if (state.includes('HOUSECLEANINGRUNNING')) return 'Cleaning House'; if (state.includes('SPOTCLEANINGRUNNING')) return 'Spot Cleaning'; if (state.includes('STATE_START')) return 'Starting'; if (state.includes('STATE_IDLE')) return 'Idle'; if (state.includes('STATE_STANDBY')) return 'Standby'; return state; } function statusToneClass(tone) { if (tone === 'good') return 'border-emerald-200 bg-emerald-600 text-white'; if (tone === 'warn') return 'border-amber-200 bg-amber-600 text-white'; if (tone === 'danger') return 'border-rose-200 bg-rose-600 text-white'; if (tone === 'info') return 'border-sky-200 bg-sky-600 text-white'; return 'border-slate-200/30 bg-slate-700 text-slate-100'; } function StatusRow({ label, value, tone = 'muted' }) { return (
{label} {value}
); } function MetricCard({ label, value, tone = 'muted' }) { return (
{label}
{value}
); } function ActionPill({ label, tone = 'indigo' }) { const toneClasses = tone === 'emerald' ? 'border-emerald-200/70 bg-emerald-600/70 text-emerald-50' : tone === 'amber' ? 'border-amber-200/70 bg-amber-600/70 text-amber-50' : 'border-indigo-200/70 bg-indigo-600/70 text-indigo-50'; return ( {label} ); } export default function VipNeatoCard({ neato, onStart, onSendHome, onLocate, fullWidth = false }) { const [working, setWorking] = useState(''); const [message, setMessage] = useState(''); const wrapClass = fullWidth ? 'w-full' : 'w-full max-w-xl'; const configured = Boolean(neato?.configured); const connected = Boolean(neato?.enabled && neato?.connected); const docked = Boolean(neato?.telemetry?.extPowerPresent); const charging = Boolean(neato?.telemetry?.chargingActive); const uiStateLabel = humanizeUiState(neato?.telemetry?.uiState); const battery = neato?.telemetry?.batteryPercent; const batteryLabel = Number.isFinite(battery) ? `${battery}%` : '--'; const voltage = neato?.telemetry?.batteryVoltage; const voltageLabel = Number.isFinite(voltage) ? `${voltage.toFixed(2)} V` : '--'; const robotError = normalizeState(neato?.telemetry?.robotError) || '--'; const robotAlert = normalizeState(neato?.telemetry?.robotAlert) || '--'; const hasError = robotError !== '--' && !/^no errors$/i.test(robotError) && !/^200/.test(robotError); const hasAlert = robotAlert !== '--' && !/^200/.test(robotAlert); const controls = neato?.controls || {}; const canStart = Boolean(controls?.start?.available); const canSendHome = Boolean(controls?.sendHome?.available); const canLocate = Boolean(controls?.locate?.available); const primaryAction = useMemo(() => { if (docked) { return { key: 'start', label: 'Start Cleaning', pending: 'Starting...', tone: 'emerald', canRun: canStart, fn: onStart, successMessage: 'Start cleaning command sent.', }; } return { key: 'home', label: 'Send to Dock', pending: 'Sending...', tone: 'indigo', canRun: canSendHome, fn: onSendHome, successMessage: 'Send to dock command sent.', }; }, [docked, canStart, onStart, canSendHome, onSendHome]); const canRunPrimary = configured && connected && primaryAction.canRun; const canRunLocate = configured && connected && canLocate; const runAction = async (key, fn, successMessage) => { if (!fn) return; setWorking(key); setMessage(''); try { await fn(); setMessage(successMessage); } catch (err) { setMessage(err?.message || 'Action failed.'); } finally { setWorking(''); } }; const statusText = !configured ? 'Not configured' : !connected ? 'Offline' : 'Online'; const statusTone = !configured ? 'warn' : !connected ? 'danger' : 'good'; const batteryTone = battery == null ? 'muted' : battery >= 60 ? 'good' : battery >= 25 ? 'warn' : 'danger'; return (

Neato Control Surface (Gen3)

{docked ? 'Docked State' : 'Undocked State'}

This Neato tile mirrors rover mode controls: one context-aware primary action based on dock state.

{!configured || !connected || !primaryAction.canRun || !canLocate ? (

{!configured ? 'Set homeAssistant.neato.device in server config to enable Neato controls.' : !connected ? 'Home Assistant is offline.' : 'Waiting for required Neato entities in Home Assistant.'}

) : null} {message ?
{message}
: null}
); }