import { useState, useEffect } from "react"; import { useShallow } from "zustand/react/shallow"; import { useMachineStore } from "../stores/useMachineStore"; import { useUIStore } from "../stores/useUIStore"; import { usePrevious } from "../hooks/usePrevious"; import { WorkflowStepper } from "./WorkflowStepper"; import { ErrorPopoverContent } from "./ErrorPopover"; import { getStateVisualInfo, getStatusIndicatorState, } from "../utils/machineStateHelpers"; import { hasError, getErrorDetails } from "../utils/errorCodeHelpers"; import { CheckCircleIcon, BoltIcon, PauseCircleIcon, ExclamationTriangleIcon, ArrowPathIcon, XMarkIcon, } from "@heroicons/react/24/solid"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import StatusIndicator from "@/components/ui/status-indicator"; import { Popover, PopoverTrigger } from "@/components/ui/popover"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; export function AppHeader() { const { isConnected, machineInfo, machineStatus, machineStatusName, machineError, error: machineErrorMessage, isPairingError, isCommunicating: isPolling, disconnect, } = useMachineStore( useShallow((state) => ({ isConnected: state.isConnected, machineInfo: state.machineInfo, machineStatus: state.machineStatus, machineStatusName: state.machineStatusName, machineError: state.machineError, error: state.error, isPairingError: state.isPairingError, isCommunicating: state.isCommunicating, disconnect: state.disconnect, })), ); const { pyodideError } = useUIStore( useShallow((state) => ({ pyodideError: state.pyodideError, })), ); // State management for error popover auto-open/close const [errorPopoverOpen, setErrorPopoverOpen] = useState(false); const [dismissedErrorCode, setDismissedErrorCode] = useState( null, ); const [wasManuallyDismissed, setWasManuallyDismissed] = useState(false); // Track previous values for comparison const prevMachineError = usePrevious(machineError); const prevErrorMessage = usePrevious(machineErrorMessage); const prevPyodideError = usePrevious(pyodideError); // Get state visual info for header status badge const stateVisual = getStateVisualInfo(machineStatus); const stateIcons = { ready: CheckCircleIcon, active: BoltIcon, waiting: PauseCircleIcon, complete: CheckCircleIcon, interrupted: PauseCircleIcon, error: ExclamationTriangleIcon, }; const StatusIcon = stateIcons[stateVisual.iconName]; // Get connection indicator state (idle when disconnected, state-dependent when connected) const connectionIndicatorState = isConnected ? getStatusIndicatorState(machineStatus) : "idle"; // Auto-open/close error popover based on error state changes /* eslint-disable react-hooks/set-state-in-effect */ useEffect(() => { // Check if there's any error now const hasAnyError = machineErrorMessage || pyodideError || hasError(machineError); // Check if there was any error before const hadAnyError = prevErrorMessage || prevPyodideError || hasError(prevMachineError); // Auto-open popover when new error appears (but not if user manually dismissed) const isNewMachineError = hasError(machineError) && machineError !== prevMachineError && machineError !== dismissedErrorCode; const isNewErrorMessage = machineErrorMessage && machineErrorMessage !== prevErrorMessage; const isNewPyodideError = pyodideError && pyodideError !== prevPyodideError; if ( !wasManuallyDismissed && (isNewMachineError || isNewErrorMessage || isNewPyodideError) ) { setErrorPopoverOpen(true); } // Auto-close popover when all errors are cleared if (!hasAnyError && hadAnyError) { setErrorPopoverOpen(false); setDismissedErrorCode(null); // Reset dismissed tracking setWasManuallyDismissed(false); // Reset manual dismissal flag } }, [ machineError, machineErrorMessage, pyodideError, dismissedErrorCode, wasManuallyDismissed, prevMachineError, prevErrorMessage, prevPyodideError, ]); /* eslint-enable react-hooks/set-state-in-effect */ // Handle manual popover dismiss const handlePopoverOpenChange = (open: boolean) => { setErrorPopoverOpen(open); // If user manually closes it while any error is present, remember this to prevent reopening if ( !open && (hasError(machineError) || machineErrorMessage || pyodideError) ) { setWasManuallyDismissed(true); // Also track the specific machine error code if present if (hasError(machineError)) { setDismissedErrorCode(machineError); } } }; return (
{/* Machine Connection Status - Responsive width column */}

Respira

{isConnected && machineInfo?.serialNumber && ( • {machineInfo.serialNumber}

Serial: {machineInfo.serialNumber}

{machineInfo.macAddress && (

MAC: {machineInfo.macAddress}

)} {machineInfo.totalCount !== undefined && (

Total stitches:{" "} {machineInfo.totalCount.toLocaleString()}

)} {machineInfo.serviceCount !== undefined && (

Stitches since service:{" "} {machineInfo.serviceCount.toLocaleString()}

)}
)} {isPolling && (

Auto-refreshing machine status

)}
{isConnected ? ( <> {machineStatusName}

{stateVisual.description}

) : (

Not Connected

)} {/* Error indicator - always render to prevent layout shift */} {/* Error popover content - unchanged */} {(machineErrorMessage || pyodideError) && ( )}
{/* Workflow Stepper - Flexible width column */}
); }