import 'package:flutter/material.dart'; import 'iop_agent_panel.dart'; import 'iop_console_contract.dart'; enum IopConsoleSection { overview, edges, nodes, runtime, executionLogs, maintenance, agent, settings, } class IopConsoleShell extends StatefulWidget { final Widget overview; final Widget? edges; final Widget? nodes; final Widget? runtime; final Widget? executionLogs; final Widget? maintenance; final Widget? agent; final Widget? settings; final IopConsoleSection initialSection; // Injected Contracts final IopConsoleConfig? config; final IopConsoleThemeAdapter? themeAdapter; final IopConsoleNavigationCallback? onNavigate; final IopCapabilityPack? capabilities; const IopConsoleShell({ super.key, required this.overview, this.edges, this.nodes, this.runtime, this.executionLogs, this.maintenance, this.agent, this.settings, this.initialSection = IopConsoleSection.overview, this.config, this.themeAdapter, this.onNavigate, this.capabilities, }); @override State createState() => _IopConsoleShellState(); } class _IopConsoleShellState extends State { late IopConsoleSection _section = widget.initialSection; @override Widget build(BuildContext context) { final theme = widget.themeAdapter ?? const IopConsoleThemeAdapter(); return Scaffold( backgroundColor: theme.backgroundColor, body: SafeArea( child: Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _IopConsoleRail( selected: _section, onSelect: _select, theme: theme, ), Expanded(child: _buildContent()), ], ), ), ); } void _select(IopConsoleSection section) { setState(() { _section = section; }); widget.onNavigate?.call(section); } Widget _buildContent() { final theme = widget.themeAdapter ?? const IopConsoleThemeAdapter(); return switch (_section) { IopConsoleSection.overview => widget.overview, IopConsoleSection.edges => widget.edges ?? _IopPlaceholder( icon: Icons.device_hub_outlined, title: 'Edges', subtitle: 'Edge group status and command surfaces mount here.', theme: theme, ), IopConsoleSection.nodes => widget.nodes ?? _IopPlaceholder( icon: Icons.memory_outlined, title: 'Nodes', subtitle: 'Node health, runtime inventory, and execution lanes mount here.', theme: theme, ), IopConsoleSection.runtime => widget.runtime ?? _IopPlaceholder( icon: Icons.speed_outlined, title: 'Runtime', subtitle: 'Adapter, target, profile, and routing controls mount here.', theme: theme, ), IopConsoleSection.executionLogs => widget.executionLogs ?? _IopPlaceholder( icon: Icons.list_alt_outlined, title: 'Execution & Logs', subtitle: 'Live session logs, active traces, and execution timelines mount here.', theme: theme, ), IopConsoleSection.maintenance => widget.maintenance ?? _IopPlaceholder( icon: Icons.build_circle_outlined, title: 'Maintenance', subtitle: 'Field diagnostics, bootstrap, and repair commands mount here.', theme: theme, ), IopConsoleSection.agent => widget.agent ?? IopAgentPanel(capabilities: widget.capabilities), IopConsoleSection.settings => widget.settings ?? _IopPlaceholder( icon: Icons.tune, title: 'Settings', subtitle: 'Control Plane endpoint and operator preferences mount here.', theme: theme, ), }; } } class _IopConsoleRail extends StatelessWidget { final IopConsoleSection selected; final ValueChanged onSelect; final IopConsoleThemeAdapter theme; const _IopConsoleRail({ required this.selected, required this.onSelect, required this.theme, }); @override Widget build(BuildContext context) { return Container( width: 60, decoration: BoxDecoration( color: theme.railBackgroundColor, border: Border(right: BorderSide(color: theme.borderColor)), ), child: Column( children: [ const SizedBox(height: 8), _RailButton( tooltip: 'Overview', icon: Icons.dashboard_outlined, selected: selected == IopConsoleSection.overview, onPressed: () => onSelect(IopConsoleSection.overview), theme: theme, ), _RailButton( tooltip: 'Edges', icon: Icons.device_hub_outlined, selected: selected == IopConsoleSection.edges, onPressed: () => onSelect(IopConsoleSection.edges), theme: theme, ), _RailButton( tooltip: 'Nodes', icon: Icons.memory_outlined, selected: selected == IopConsoleSection.nodes, onPressed: () => onSelect(IopConsoleSection.nodes), theme: theme, ), _RailButton( tooltip: 'Runtime', icon: Icons.speed_outlined, selected: selected == IopConsoleSection.runtime, onPressed: () => onSelect(IopConsoleSection.runtime), theme: theme, ), _RailButton( tooltip: 'Execution & Logs', icon: Icons.list_alt_outlined, selected: selected == IopConsoleSection.executionLogs, onPressed: () => onSelect(IopConsoleSection.executionLogs), theme: theme, ), _RailButton( tooltip: 'Maintenance', icon: Icons.build_circle_outlined, selected: selected == IopConsoleSection.maintenance, onPressed: () => onSelect(IopConsoleSection.maintenance), theme: theme, ), const Spacer(), _RailButton( tooltip: 'Agent', icon: Icons.smart_toy_outlined, selected: selected == IopConsoleSection.agent, onPressed: () => onSelect(IopConsoleSection.agent), theme: theme, ), _RailButton( tooltip: 'Settings', icon: Icons.tune, selected: selected == IopConsoleSection.settings, onPressed: () => onSelect(IopConsoleSection.settings), theme: theme, ), const SizedBox(height: 8), ], ), ); } } class _RailButton extends StatelessWidget { final String tooltip; final IconData icon; final bool selected; final VoidCallback onPressed; final IopConsoleThemeAdapter theme; const _RailButton({ required this.tooltip, required this.icon, required this.selected, required this.onPressed, required this.theme, }); @override Widget build(BuildContext context) { final color = selected ? theme.primaryColor : theme.textSecondaryColor; return Tooltip( message: tooltip, waitDuration: const Duration(milliseconds: 500), child: SizedBox( width: 56, height: 48, child: IconButton( onPressed: onPressed, icon: Icon(icon, color: color), style: IconButton.styleFrom( backgroundColor: selected ? theme.primaryColor.withValues(alpha: 0.1) : Colors.transparent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), ), ), ); } } class _IopPlaceholder extends StatelessWidget { final IconData icon; final String title; final String subtitle; final IopConsoleThemeAdapter theme; const _IopPlaceholder({ required this.icon, required this.title, required this.subtitle, required this.theme, }); @override Widget build(BuildContext context) { return Container( color: theme.backgroundColor, child: Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 500), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, color: theme.primaryColor, size: 40), const SizedBox(height: 16), Text( title, textAlign: TextAlign.center, style: TextStyle( color: theme.textColor, fontSize: 22, fontWeight: FontWeight.w800, ), ), const SizedBox(height: 8), Text( subtitle, textAlign: TextAlign.center, style: TextStyle(color: theme.textSecondaryColor, fontSize: 14), ), ], ), ), ), ); } }