import { useState } from 'react'; import { useMutation, useQueryClient, useQuery } from '@tanstack/react-query'; import { Plug, Power, PowerOff, Loader2, Trash2, Settings2, Thermometer, Clock, Wifi, WifiOff, Edit2, Bell, Calendar, LayoutGrid, ExternalLink, Home, Play, Eye } from 'lucide-react'; import { api } from '../api/client'; import type { SmartPlug, SmartPlugUpdate } from '../api/client'; import { Card, CardContent } from './Card'; import { Button } from './Button'; import { ConfirmModal } from './ConfirmModal'; import { useToast } from '../contexts/ToastContext'; interface SmartPlugCardProps { plug: SmartPlug; onEdit: (plug: SmartPlug) => void; } export function SmartPlugCard({ plug, onEdit }: SmartPlugCardProps) { const queryClient = useQueryClient(); const { showToast } = useToast(); const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const [showPowerOnConfirm, setShowPowerOnConfirm] = useState(false); const [showPowerOffConfirm, setShowPowerOffConfirm] = useState(false); const [isExpanded, setIsExpanded] = useState(false); // Fetch current status const { data: status, isLoading: statusLoading } = useQuery({ queryKey: ['smart-plug-status', plug.id], queryFn: () => api.getSmartPlugStatus(plug.id), refetchInterval: 30000, // Refresh every 30 seconds }); // Fetch printers for linking const { data: printers } = useQuery({ queryKey: ['printers'], queryFn: api.getPrinters, }); const linkedPrinter = printers?.find(p => p.id === plug.printer_id); // Control mutation with optimistic updates const controlMutation = useMutation({ mutationFn: (action: 'on' | 'off' | 'toggle') => api.controlSmartPlug(plug.id, action), onMutate: async (action) => { // Cancel any outgoing refetches await queryClient.cancelQueries({ queryKey: ['smart-plug-status', plug.id] }); // Snapshot the previous value const previousStatus = queryClient.getQueryData(['smart-plug-status', plug.id]); // Optimistically update to the new value const newState = action === 'on' ? 'ON' : action === 'off' ? 'OFF' : (status?.state === 'ON' ? 'OFF' : 'ON'); queryClient.setQueryData(['smart-plug-status', plug.id], (old: typeof status) => ({ ...old, state: newState, })); return { previousStatus }; }, onSuccess: (_data, action) => { // Show toast for script triggers const isScriptPlug = plug.plug_type === 'homeassistant' && plug.ha_entity_id?.startsWith('script.'); if (isScriptPlug && action === 'on') { showToast(`Script "${plug.name}" triggered`, 'success'); } }, onError: (_err, action, context) => { // Rollback on error if (context?.previousStatus) { queryClient.setQueryData(['smart-plug-status', plug.id], context.previousStatus); } const isScriptPlug = plug.plug_type === 'homeassistant' && plug.ha_entity_id?.startsWith('script.'); if (isScriptPlug) { showToast(`Failed to trigger script "${plug.name}"`, 'error'); } else { showToast(`Failed to turn ${action} "${plug.name}"`, 'error'); } }, onSettled: () => { // Refetch after a short delay to get actual state setTimeout(() => { queryClient.invalidateQueries({ queryKey: ['smart-plug-status', plug.id] }); queryClient.invalidateQueries({ queryKey: ['smart-plugs'] }); }, 1000); }, }); // Update mutation const updateMutation = useMutation({ mutationFn: (data: SmartPlugUpdate) => api.updateSmartPlug(plug.id, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['smart-plugs'] }); // Also invalidate printer-specific smart plug queries to keep PrintersPage in sync if (plug.printer_id) { queryClient.invalidateQueries({ queryKey: ['smartPlugByPrinter', plug.printer_id] }); } // Invalidate script plugs queries for printer cards queryClient.invalidateQueries({ predicate: (query) => Array.isArray(query.queryKey) && query.queryKey[0] === 'scriptPlugsByPrinter' }); }, }); // Delete mutation const deleteMutation = useMutation({ mutationFn: () => api.deleteSmartPlug(plug.id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['smart-plugs'] }); // Also invalidate script plugs queries for printer cards queryClient.invalidateQueries({ predicate: (query) => Array.isArray(query.queryKey) && query.queryKey[0] === 'scriptPlugsByPrinter' }); }, }); const isOn = status?.state === 'ON'; const isReachable = status?.reachable ?? false; const isPending = controlMutation.isPending; // Check if this is a HA script entity (scripts can only be triggered, not toggled) const isScript = plug.plug_type === 'homeassistant' && plug.ha_entity_id?.startsWith('script.'); // Generate admin URL with auto-login credentials (Tasmota only) const getAdminUrl = () => { if (plug.plug_type !== 'tasmota' || !plug.ip_address) return null; const ip = plug.ip_address; if (plug.username && plug.password) { // Use HTTP Basic Auth in URL for auto-login return `http://${encodeURIComponent(plug.username)}:${encodeURIComponent(plug.password)}@${ip}/`; } return `http://${ip}/`; }; const adminUrl = getAdminUrl(); return ( <> {/* Header Row */}
{isScript ? ( ) : plug.plug_type === 'homeassistant' ? ( ) : ( )}

{plug.name}

{plug.plug_type === 'homeassistant' ? plug.ha_entity_id : plug.ip_address}

{/* Status indicator */}
{statusLoading ? ( ) : isScript ? ( /* Script entities: show badge and Ready status stacked */
Script {isReachable ? 'Ready' : 'Offline'}
) : isReachable ? (
{status?.state || 'Unknown'}
) : (
Offline
)} {/* Admin page link - only for Tasmota */} {adminUrl && ( Admin )}
{/* Linked Printer */} {linkedPrinter && (
Linked to: {linkedPrinter.name}
)} {/* Feature Badges */} {(plug.power_alert_enabled || plug.schedule_enabled) && (
{plug.power_alert_enabled && ( Alerts )} {plug.schedule_enabled && ( {plug.schedule_on_time && plug.schedule_off_time ? `${plug.schedule_on_time} - ${plug.schedule_off_time}` : plug.schedule_on_time ? `On ${plug.schedule_on_time}` : `Off ${plug.schedule_off_time}`} )}
)} {/* Quick Controls */}
{isScript ? ( /* Script entities: single "Run" button */ ) : ( /* Regular entities: On/Off buttons */ <> )}
{/* Toggle Settings Panel */} {/* Expanded Settings */} {isExpanded && (
{/* Show on Printer Card Toggle - only for scripts */} {isScript && (

Show on Printer Card

Display script button on printer card

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

Show in Switchbar

Quick access from sidebar

{/* Enabled Toggle */}

Enabled

Enable automation for this plug

{/* Auto On / Run when printer turns on */}

{isScript ? 'Run when printer turns on' : 'Auto On'}

{isScript ? 'Execute script when main plug is switched on' : 'Turn on when print starts'}

{/* Auto Off / Run when printer turns off */}

{isScript ? 'Run when printer turns off' : 'Auto Off'}

{isScript ? 'Execute script when main plug is switched off' : 'Turn off when print completes (one-shot)'}

{/* Delay Mode - hidden for script entities */} {plug.auto_off && !isScript && (

Turn Off Delay Mode

{plug.off_delay_mode === 'time' ? (
updateMutation.mutate({ off_delay_minutes: parseInt(e.target.value) || 5 })} className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white text-sm focus:border-bambu-green focus:outline-none" />
) : (
updateMutation.mutate({ off_temp_threshold: parseInt(e.target.value) || 70 })} className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white text-sm focus:border-bambu-green focus:outline-none" />

Turns off when nozzle cools below this temperature

)}
)} {/* Action Buttons */}
)}
{/* Delete Confirmation */} {showDeleteConfirm && ( { deleteMutation.mutate(); setShowDeleteConfirm(false); }} onCancel={() => setShowDeleteConfirm(false)} /> )} {/* Power On / Run Script Confirmation */} {showPowerOnConfirm && ( { controlMutation.mutate('on'); setShowPowerOnConfirm(false); }} onCancel={() => setShowPowerOnConfirm(false)} /> )} {/* Power Off Confirmation */} {showPowerOffConfirm && ( { controlMutation.mutate('off'); setShowPowerOffConfirm(false); }} onCancel={() => setShowPowerOffConfirm(false)} /> )} ); }