import { useState, useEffect, useRef } from 'react'; import { useMutation, useQueryClient, useQuery } from '@tanstack/react-query'; import { X, Save, Loader2, Wifi, WifiOff, CheckCircle, Bell, Clock, LayoutGrid, Search, Plug, Power, Home, Radio } from 'lucide-react'; import { api } from '../api/client'; import type { SmartPlug, SmartPlugCreate, SmartPlugUpdate, DiscoveredTasmotaDevice } from '../api/client'; import { Button } from './Button'; interface AddSmartPlugModalProps { plug?: SmartPlug | null; onClose: () => void; } export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) { const queryClient = useQueryClient(); const isEditing = !!plug; // Plug type selection const [plugType, setPlugType] = useState<'tasmota' | 'homeassistant' | 'mqtt'>(plug?.plug_type || 'tasmota'); const [name, setName] = useState(plug?.name || ''); // Tasmota fields const [ipAddress, setIpAddress] = useState(plug?.ip_address || ''); const [username, setUsername] = useState(plug?.username || ''); const [password, setPassword] = useState(plug?.password || ''); // Home Assistant fields const [haEntityId, setHaEntityId] = useState(plug?.ha_entity_id || ''); // MQTT fields - Power const [mqttPowerTopic, setMqttPowerTopic] = useState(plug?.mqtt_power_topic || plug?.mqtt_topic || ''); const [mqttPowerPath, setMqttPowerPath] = useState(plug?.mqtt_power_path || ''); const [mqttPowerMultiplier, setMqttPowerMultiplier] = useState( (plug?.mqtt_power_multiplier ?? plug?.mqtt_multiplier ?? 1).toString() ); // MQTT fields - Energy const [mqttEnergyTopic, setMqttEnergyTopic] = useState(plug?.mqtt_energy_topic || ''); const [mqttEnergyPath, setMqttEnergyPath] = useState(plug?.mqtt_energy_path || ''); const [mqttEnergyMultiplier, setMqttEnergyMultiplier] = useState( (plug?.mqtt_energy_multiplier ?? 1).toString() ); // MQTT fields - State const [mqttStateTopic, setMqttStateTopic] = useState(plug?.mqtt_state_topic || ''); const [mqttStatePath, setMqttStatePath] = useState(plug?.mqtt_state_path || ''); const [mqttStateOnValue, setMqttStateOnValue] = useState(plug?.mqtt_state_on_value || ''); // HA energy sensor entities (optional) const [haPowerEntity, setHaPowerEntity] = useState(plug?.ha_power_entity || ''); const [haEnergyTodayEntity, setHaEnergyTodayEntity] = useState(plug?.ha_energy_today_entity || ''); const [haEnergyTotalEntity, setHaEnergyTotalEntity] = useState(plug?.ha_energy_total_entity || ''); // HA entity search const [haEntitySearch, setHaEntitySearch] = useState(''); const [debouncedSearch, setDebouncedSearch] = useState(''); const [isEntityDropdownOpen, setIsEntityDropdownOpen] = useState(false); const entityDropdownRef = useRef(null); // Energy sensor search states const [powerSensorSearch, setPowerSensorSearch] = useState(''); const [isPowerDropdownOpen, setIsPowerDropdownOpen] = useState(false); const powerDropdownRef = useRef(null); const [energyTodaySearch, setEnergyTodaySearch] = useState(''); const [isEnergyTodayDropdownOpen, setIsEnergyTodayDropdownOpen] = useState(false); const energyTodayDropdownRef = useRef(null); const [energyTotalSearch, setEnergyTotalSearch] = useState(''); const [isEnergyTotalDropdownOpen, setIsEnergyTotalDropdownOpen] = useState(false); const energyTotalDropdownRef = useRef(null); const [printerId, setPrinterId] = useState(plug?.printer_id || null); const [testResult, setTestResult] = useState<{ success: boolean; state?: string | null; device_name?: string | null } | null>(null); const [error, setError] = useState(null); // Power alert settings const [powerAlertEnabled, setPowerAlertEnabled] = useState(plug?.power_alert_enabled || false); const [powerAlertHigh, setPowerAlertHigh] = useState(plug?.power_alert_high?.toString() || ''); const [powerAlertLow, setPowerAlertLow] = useState(plug?.power_alert_low?.toString() || ''); // Schedule settings const [scheduleEnabled, setScheduleEnabled] = useState(plug?.schedule_enabled || false); const [scheduleOnTime, setScheduleOnTime] = useState(plug?.schedule_on_time || ''); const [scheduleOffTime, setScheduleOffTime] = useState(plug?.schedule_off_time || ''); // Switchbar visibility const [showInSwitchbar, setShowInSwitchbar] = useState(plug?.show_in_switchbar || false); // Discovery state const [isScanning, setIsScanning] = useState(false); const [scanProgress, setScanProgress] = useState({ scanned: 0, total: 0 }); const [discoveredDevices, setDiscoveredDevices] = useState([]); const scanPollRef = useRef(null); // Fetch printers for linking const { data: printers } = useQuery({ queryKey: ['printers'], queryFn: api.getPrinters, }); // Fetch existing plugs to check for conflicts const { data: existingPlugs } = useQuery({ queryKey: ['smart-plugs'], queryFn: api.getSmartPlugs, }); // Fetch settings to check if HA is configured const { data: settings } = useQuery({ queryKey: ['settings'], queryFn: api.getSettings, }); // Check if HA is properly configured const haConfigured = !!(settings?.ha_enabled && settings?.ha_url && settings?.ha_token); // Debounce search input useEffect(() => { const timer = setTimeout(() => { setDebouncedSearch(haEntitySearch); }, 300); return () => clearTimeout(timer); }, [haEntitySearch]); // Close dropdowns when clicking outside useEffect(() => { const handleClickOutside = (e: MouseEvent) => { if (entityDropdownRef.current && !entityDropdownRef.current.contains(e.target as Node)) { setIsEntityDropdownOpen(false); } if (powerDropdownRef.current && !powerDropdownRef.current.contains(e.target as Node)) { setIsPowerDropdownOpen(false); } if (energyTodayDropdownRef.current && !energyTodayDropdownRef.current.contains(e.target as Node)) { setIsEnergyTodayDropdownOpen(false); } if (energyTotalDropdownRef.current && !energyTotalDropdownRef.current.contains(e.target as Node)) { setIsEnergyTotalDropdownOpen(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, []); // Fetch Home Assistant entities when in HA mode AND HA is configured const { data: haEntities, isLoading: haEntitiesLoading, error: haEntitiesError } = useQuery({ queryKey: ['ha-entities', debouncedSearch], queryFn: () => api.getHAEntities(debouncedSearch || undefined), enabled: plugType === 'homeassistant' && haConfigured, retry: false, staleTime: 0, }); // Fetch Home Assistant sensor entities for energy monitoring const { data: haSensorEntities } = useQuery({ queryKey: ['ha-sensor-entities'], queryFn: api.getHASensorEntities, enabled: plugType === 'homeassistant' && haConfigured, retry: false, staleTime: 0, }); // Close on Escape key and cleanup scan polling useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); }; window.addEventListener('keydown', handleKeyDown); return () => { window.removeEventListener('keydown', handleKeyDown); if (scanPollRef.current) { clearInterval(scanPollRef.current); } }; }, [onClose]); // Start scanning for Tasmota devices (auto-detects network) const startScan = async () => { setIsScanning(true); setDiscoveredDevices([]); setScanProgress({ scanned: 0, total: 0 }); setError(null); try { await api.startTasmotaScan(); // Poll function to fetch status and devices const pollStatus = async () => { try { const status = await api.getTasmotaScanStatus(); setScanProgress({ scanned: status.scanned, total: status.total }); const devices = await api.getDiscoveredTasmotaDevices(); setDiscoveredDevices(devices); if (!status.running) { setIsScanning(false); if (scanPollRef.current) { clearInterval(scanPollRef.current); scanPollRef.current = null; } } } catch (e) { console.error('Polling error:', e); } }; // Poll immediately, then every 500ms await pollStatus(); scanPollRef.current = setInterval(pollStatus, 500); } catch (err) { setIsScanning(false); const errorMsg = err instanceof Error ? err.message : (typeof err === 'string' ? err : JSON.stringify(err)); setError(errorMsg || 'Failed to start scan'); } }; // Stop scanning const stopScan = async () => { try { await api.stopTasmotaScan(); } catch { // Ignore stop errors } setIsScanning(false); if (scanPollRef.current) { clearInterval(scanPollRef.current); scanPollRef.current = null; } }; // Select a discovered device const selectDevice = (device: DiscoveredTasmotaDevice) => { setIpAddress(device.ip_address); setName(device.name); setTestResult(null); }; // Test connection mutation const testMutation = useMutation({ mutationFn: () => api.testSmartPlugConnection(ipAddress, username || null, password || null), onSuccess: (result) => { setTestResult(result); setError(null); // Auto-fill name from device if empty if (!name && result.device_name) { setName(result.device_name); } }, onError: (err: Error) => { setTestResult(null); setError(err.message); }, }); // Create mutation const createMutation = useMutation({ mutationFn: (data: SmartPlugCreate) => api.createSmartPlug(data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['smart-plugs'] }); onClose(); }, onError: (err: Error) => { setError(err.message); }, }); // Update mutation const updateMutation = useMutation({ mutationFn: (data: SmartPlugUpdate) => api.updateSmartPlug(plug!.id, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['smart-plugs'] }); onClose(); }, onError: (err: Error) => { setError(err.message); }, }); // Filter out printers that already have a plug assigned (except current plug's printer) const availablePrinters = printers?.filter(p => { const hasPlug = existingPlugs?.some(ep => ep.printer_id === p.id && ep.id !== plug?.id); return !hasPlug; }); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); setError(null); if (!name.trim()) { setError('Name is required'); return; } if (plugType === 'tasmota' && !ipAddress.trim()) { setError('IP address is required for Tasmota plugs'); return; } if (plugType === 'homeassistant' && !haEntityId) { setError('Entity is required for Home Assistant plugs'); return; } if (plugType === 'mqtt') { // Check that at least one data source is fully configured const hasPower = mqttPowerTopic.trim() && mqttPowerPath.trim(); const hasEnergy = mqttEnergyTopic.trim() && mqttEnergyPath.trim(); const hasState = mqttStateTopic.trim() && mqttStatePath.trim(); if (!hasPower && !hasEnergy && !hasState) { setError('At least one data source must be configured: power (topic + path), energy (topic + path), or state (topic + path)'); return; } } const data = { name: name.trim(), plug_type: plugType, ip_address: plugType === 'tasmota' ? ipAddress.trim() : null, ha_entity_id: plugType === 'homeassistant' ? haEntityId : null, // HA energy sensor entities (optional) ha_power_entity: plugType === 'homeassistant' ? (haPowerEntity || null) : null, ha_energy_today_entity: plugType === 'homeassistant' ? (haEnergyTodayEntity || null) : null, ha_energy_total_entity: plugType === 'homeassistant' ? (haEnergyTotalEntity || null) : null, // MQTT power fields mqtt_power_topic: plugType === 'mqtt' ? (mqttPowerTopic.trim() || null) : null, mqtt_power_path: plugType === 'mqtt' ? (mqttPowerPath.trim() || null) : null, mqtt_power_multiplier: plugType === 'mqtt' ? (parseFloat(mqttPowerMultiplier) || 1) : 1, // MQTT energy fields mqtt_energy_topic: plugType === 'mqtt' ? (mqttEnergyTopic.trim() || null) : null, mqtt_energy_path: plugType === 'mqtt' ? (mqttEnergyPath.trim() || null) : null, mqtt_energy_multiplier: plugType === 'mqtt' ? (parseFloat(mqttEnergyMultiplier) || 1) : 1, // MQTT state fields mqtt_state_topic: plugType === 'mqtt' ? (mqttStateTopic.trim() || null) : null, mqtt_state_path: plugType === 'mqtt' ? (mqttStatePath.trim() || null) : null, mqtt_state_on_value: plugType === 'mqtt' ? (mqttStateOnValue.trim() || null) : null, username: plugType === 'tasmota' ? (username.trim() || null) : null, password: plugType === 'tasmota' ? (password.trim() || null) : null, printer_id: printerId, // Power alerts power_alert_enabled: powerAlertEnabled, power_alert_high: powerAlertHigh ? parseFloat(powerAlertHigh) : null, power_alert_low: powerAlertLow ? parseFloat(powerAlertLow) : null, // Schedule schedule_enabled: scheduleEnabled, schedule_on_time: scheduleOnTime || null, schedule_off_time: scheduleOffTime || null, // Switchbar show_in_switchbar: showInSwitchbar, }; if (isEditing) { updateMutation.mutate(data); } else { createMutation.mutate(data); } }; const isPending = createMutation.isPending || updateMutation.isPending; return (
e.stopPropagation()} > {/* Header */}

{isEditing ? 'Edit Smart Plug' : 'Add Smart Plug'}

{/* Form */}
{error && (
{error}
)} {/* Plug Type Selector - only show when not editing */} {!isEditing && (
)} {/* Discovery Section - only show when not editing and Tasmota is selected */} {!isEditing && plugType === 'tasmota' && (
{/* Scan button - auto-detects network */} {isScanning ? ( ) : ( )} {/* Progress bar */} {isScanning && scanProgress.total > 0 && (
Scanning network... {scanProgress.scanned} / {scanProgress.total}
)} {/* Discovered devices */} {discoveredDevices.length > 0 && (

Found {discoveredDevices.length} device(s) - click to select:

{discoveredDevices.map((device) => ( ))}
)} {!isScanning && discoveredDevices.length === 0 && scanProgress.total > 0 && (

No Tasmota devices found on your network

)}
)} {/* Home Assistant Entity Selector - only show when HA is selected */} {plugType === 'homeassistant' && (
{/* HA not configured */} {!haConfigured && (
Home Assistant is not configured. Set it up in{' '} Settings → Network → Home Assistant
)} {/* HA configured - show loading/entities */} {haConfigured && ( <> {haEntitiesLoading && (
Loading entities...
)} {haEntitiesError && (
Failed to load entities: {(haEntitiesError as Error).message}
)} {/* Searchable Entity Dropdown */} {(() => { // Filter out entities already configured (except current plug when editing) const configuredEntityIds = existingPlugs ?.filter(p => p.ha_entity_id && p.id !== plug?.id) .map(p => p.ha_entity_id) || []; const availableEntities = (haEntities || []).filter(e => !configuredEntityIds.includes(e.entity_id)); const selectedEntity = haEntities?.find(e => e.entity_id === haEntityId); return (
{ setHaEntitySearch(e.target.value); if (!isEntityDropdownOpen) setIsEntityDropdownOpen(true); }} onFocus={() => { setIsEntityDropdownOpen(true); setHaEntitySearch(''); }} placeholder="Search entities..." className="w-full pl-9 pr-8 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none" /> {haEntityId && !isEntityDropdownOpen && ( )} {haEntitiesLoading && ( )}
{/* Dropdown */} {isEntityDropdownOpen && (
{haEntitiesLoading && (
Loading...
)} {!haEntitiesLoading && availableEntities.length === 0 && (
{debouncedSearch ? `No entities found matching "${debouncedSearch}"` : 'No entities available'}
)} {!haEntitiesLoading && availableEntities.map((entity) => ( ))}
)}

{debouncedSearch ? `Searching all entities (${availableEntities.length} found)` : `Showing switch, light, input_boolean (${availableEntities.length} available)`}

); })()} {/* Energy Monitoring Section (Optional) */} {haEntityId && haSensorEntities && haSensorEntities.length > 0 && (

Energy Monitoring (Optional)

Search and select sensors that provide power/energy data.

{/* Power Sensor (W) */} {(() => { const powerSensors = haSensorEntities.filter(s => s.unit_of_measurement === 'W' || s.unit_of_measurement === 'kW' || s.unit_of_measurement === 'mW' ); const filteredPowerSensors = powerSensorSearch ? powerSensors.filter(s => s.entity_id.toLowerCase().includes(powerSensorSearch.toLowerCase()) || s.friendly_name.toLowerCase().includes(powerSensorSearch.toLowerCase()) ) : powerSensors; const selectedPowerSensor = haSensorEntities.find(s => s.entity_id === haPowerEntity); return (
{ setPowerSensorSearch(e.target.value); if (!isPowerDropdownOpen) setIsPowerDropdownOpen(true); }} onFocus={() => { setIsPowerDropdownOpen(true); setPowerSensorSearch(''); }} placeholder="Search power sensors..." className="w-full pl-9 pr-8 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none" /> {haPowerEntity && !isPowerDropdownOpen && ( )}
{isPowerDropdownOpen && (
{filteredPowerSensors.map((sensor) => ( ))} {filteredPowerSensors.length === 0 && (
No matching sensors
)}
)}
); })()} {/* Energy Today (kWh) */} {(() => { const energySensors = haSensorEntities.filter(s => s.unit_of_measurement === 'kWh' || s.unit_of_measurement === 'Wh' || s.unit_of_measurement === 'MWh' ); const filteredEnergySensors = energyTodaySearch ? energySensors.filter(s => s.entity_id.toLowerCase().includes(energyTodaySearch.toLowerCase()) || s.friendly_name.toLowerCase().includes(energyTodaySearch.toLowerCase()) ) : energySensors; const selectedSensor = haSensorEntities.find(s => s.entity_id === haEnergyTodayEntity); return (
{ setEnergyTodaySearch(e.target.value); if (!isEnergyTodayDropdownOpen) setIsEnergyTodayDropdownOpen(true); }} onFocus={() => { setIsEnergyTodayDropdownOpen(true); setEnergyTodaySearch(''); }} placeholder="Search energy sensors..." className="w-full pl-9 pr-8 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none" /> {haEnergyTodayEntity && !isEnergyTodayDropdownOpen && ( )}
{isEnergyTodayDropdownOpen && (
{filteredEnergySensors.map((sensor) => ( ))} {filteredEnergySensors.length === 0 && (
No matching sensors
)}
)}
); })()} {/* Total Energy (kWh) */} {(() => { const energySensors = haSensorEntities.filter(s => s.unit_of_measurement === 'kWh' || s.unit_of_measurement === 'Wh' || s.unit_of_measurement === 'MWh' ); const filteredEnergySensors = energyTotalSearch ? energySensors.filter(s => s.entity_id.toLowerCase().includes(energyTotalSearch.toLowerCase()) || s.friendly_name.toLowerCase().includes(energyTotalSearch.toLowerCase()) ) : energySensors; const selectedSensor = haSensorEntities.find(s => s.entity_id === haEnergyTotalEntity); return (
{ setEnergyTotalSearch(e.target.value); if (!isEnergyTotalDropdownOpen) setIsEnergyTotalDropdownOpen(true); }} onFocus={() => { setIsEnergyTotalDropdownOpen(true); setEnergyTotalSearch(''); }} placeholder="Search energy sensors..." className="w-full pl-9 pr-8 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none" /> {haEnergyTotalEntity && !isEnergyTotalDropdownOpen && ( )}
{isEnergyTotalDropdownOpen && (
{filteredEnergySensors.map((sensor) => ( ))} {filteredEnergySensors.length === 0 && (
No matching sensors
)}
)}
); })()}
)} )}
)} {/* MQTT Configuration - only show when MQTT is selected */} {plugType === 'mqtt' && (
{/* MQTT broker not configured */} {!settings?.mqtt_broker && (
MQTT broker not configured. Set broker address in{' '} Settings → Network → MQTT Publishing {' '}(you don't need to enable publishing, just fill in the broker details).
)} {/* MQTT broker configured - show fields */} {settings?.mqtt_broker && ( <>

Monitor Only

MQTT plugs receive power/energy data via MQTT subscription. On/off control is not available - use your MQTT broker or home automation system.

{/* Power Section */}

Power Monitoring

setMqttPowerTopic(e.target.value)} placeholder="zigbee2mqtt/shelly-working-room" className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none" />
setMqttPowerPath(e.target.value)} placeholder="power_l1" className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none" />
setMqttPowerMultiplier(e.target.value)} placeholder="1" className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none" />

Use multiplier 0.001 for mW→W, 1000 for kW→W

{/* Energy Section */}

Energy Monitoring (optional)

setMqttEnergyTopic(e.target.value)} placeholder="Same as power topic, or different" className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none" />
setMqttEnergyPath(e.target.value)} placeholder="energy_l1" className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none" />
setMqttEnergyMultiplier(e.target.value)} placeholder="1" className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none" />

Use multiplier 0.001 for Wh→kWh, 1000 for MWh→kWh

{/* State Section */}

State Monitoring (optional)

setMqttStateTopic(e.target.value)} placeholder="Same as power topic, or different" className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none" />
setMqttStatePath(e.target.value)} placeholder="state_l1" className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none" />
setMqttStateOnValue(e.target.value)} placeholder="ON, true, 1" className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none" />

ON value: the exact string that means "ON". Leave empty for auto-detect (ON, true, 1)

)}
)} {/* IP Address - only show for Tasmota */} {plugType === 'tasmota' && (
{ setIpAddress(e.target.value); setTestResult(null); }} placeholder="192.168.1.100" className="flex-1 px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none" />
)} {/* Test Result - only show for Tasmota */} {plugType === 'tasmota' && testResult && (
{testResult.success ? ( <>

Connected!

{testResult.device_name && `Device: ${testResult.device_name} - `} State: {testResult.state}

) : ( <> Connection failed )}
)} {/* Name */}
setName(e.target.value)} placeholder="Living Room Plug" className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none" />
{/* Authentication (optional) - only show for Tasmota */} {plugType === 'tasmota' && ( <>
setUsername(e.target.value)} placeholder="admin" className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none" />
setPassword(e.target.value)} placeholder="********" className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none" />

Leave empty if your Tasmota device doesn't require authentication

)} {/* Link to Printer - not shown for MQTT plugs (monitor-only) */} {plugType !== 'mqtt' && (

Linking enables automatic on/off when prints start/complete

)} {/* Power Alerts */}
Power Alerts
{powerAlertEnabled && (
setPowerAlertHigh(e.target.value)} placeholder="e.g. 200" min="0" max="5000" className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none" />
setPowerAlertLow(e.target.value)} placeholder="e.g. 10" min="0" max="5000" className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none" />

Get notified when power consumption crosses these thresholds. Leave empty to disable that direction.

)}
{/* Schedule - not shown for MQTT plugs (monitor-only) */} {plugType !== 'mqtt' && (
Daily Schedule
{scheduleEnabled && (
setScheduleOnTime(e.target.value)} className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none" />
setScheduleOffTime(e.target.value)} className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none" />

Automatically turn the plug on/off at these times daily. Leave empty to skip that action.

)}
)} {/* Switchbar Visibility */}
Show in Switchbar

Quick access from sidebar

{/* Actions */}
); }