import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:uto_manager/application/blocs/personnel_cubit.dart'; import 'package:uto_manager/application/blocs/unit_tree_cubit.dart'; import 'package:uto_manager/domain/entities/personnel_record.dart'; import 'package:uto_manager/domain/entities/position_slot.dart'; import 'package:uto_manager/presentation/widgets/personnel_list_tile.dart'; import 'package:uto_manager/presentation/widgets/position_slot_card.dart'; import 'package:uto_manager/presentation/widgets/uto_canvas_widget.dart'; /// The main three-pane home screen of the UTO Manager. /// /// Layout: /// - Left panel: Personnel roster (unassigned soldiers, draggable) /// - Center: Interactive org chart canvas /// - Right panel: Unit inspector (selected unit's position slots) class HomeScreen extends StatelessWidget { const HomeScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: _buildAppBar(context), body: _buildBody(context), bottomNavigationBar: _buildStatusBar(context), ); } PreferredSizeWidget _buildAppBar(BuildContext context) { return AppBar( title: const Row( children: [ Icon(Icons.military_tech, size: 20), SizedBox(width: 8), Text('UTO Manager', style: TextStyle(fontWeight: FontWeight.bold)), SizedBox(width: 8), Text( 'U.S. Army 2026 Doctrine', style: TextStyle(fontSize: 12, fontWeight: FontWeight.normal), ), ], ), backgroundColor: const Color(0xFF1B3A6B), foregroundColor: Colors.white, actions: [ IconButton( icon: const Icon(Icons.delete_sweep_outlined), tooltip: 'Clear all units', onPressed: () => _confirmClearAll(context), ), const SizedBox(width: 8), ], ); } Widget _buildBody(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { final isWide = constraints.maxWidth > 900; if (isWide) { return Row( children: [ // Left panel: Personnel roster SizedBox( width: 260, child: _buildPersonnelPanel(context), ), const VerticalDivider(width: 1), // Center: Canvas Expanded( child: _buildCanvas(context), ), const VerticalDivider(width: 1), // Right panel: Unit inspector SizedBox( width: 280, child: _buildInspectorPanel(context), ), ], ); } // Narrow layout: canvas only, with drawer for panels return _buildCanvas(context); }, ); } // --------------------------------------------------------------------------- // Left Panel — Personnel Roster // --------------------------------------------------------------------------- Widget _buildPersonnelPanel(BuildContext context) { final theme = Theme.of(context); return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), color: theme.colorScheme.surfaceContainerHigh, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'PERSONNEL ROSTER', style: theme.textTheme.labelSmall?.copyWith( fontWeight: FontWeight.bold, letterSpacing: 1.2, color: theme.colorScheme.onSurfaceVariant, ), ), const SizedBox(height: 6), BlocBuilder( builder: (context, state) { return TextField( decoration: InputDecoration( hintText: 'Search by name, MOS, rank…', hintStyle: theme.textTheme.labelSmall, prefixIcon: const Icon(Icons.search, size: 16), isDense: true, contentPadding: const EdgeInsets.symmetric(vertical: 8), border: OutlineInputBorder(borderRadius: BorderRadius.circular(6)), ), style: theme.textTheme.bodySmall, onChanged: (q) => context.read().setFilter(q), ); }, ), ], ), ), Expanded( child: BlocBuilder( builder: (context, state) { if (state is PersonnelLoading) { return const Center(child: CircularProgressIndicator()); } if (state is PersonnelLoaded) { final soldiers = state.filteredUnassigned; if (soldiers.isEmpty) { return Center( child: Text( 'No unassigned soldiers', style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onSurfaceVariant, ), ), ); } return ListView.builder( itemCount: soldiers.length, itemBuilder: (context, i) => PersonnelListTile(record: soldiers[i]), ); } return const SizedBox.shrink(); }, ), ), ], ); } // --------------------------------------------------------------------------- // Center — Canvas // --------------------------------------------------------------------------- Widget _buildCanvas(BuildContext context) { return BlocBuilder( builder: (context, state) { if (state is UnitTreeLoading) { return const Center(child: CircularProgressIndicator()); } if (state is UnitTreeLoaded) { return UtoCanvasWidget( roots: state.roots, selectedUnitId: state.selectedUnitId, onUnitSelected: (id) => context.read().selectUnit(id), onReparent: (unitId, newParentId) => context.read().reparentUnit(unitId, newParentId), ); } if (state is UnitTreeError) { return Center(child: Text('Error: ${state.message}')); } return const Center(child: CircularProgressIndicator()); }, ); } // --------------------------------------------------------------------------- // Right Panel — Unit Inspector // --------------------------------------------------------------------------- Widget _buildInspectorPanel(BuildContext context) { final theme = Theme.of(context); return BlocBuilder( builder: (context, treeState) { if (treeState is! UnitTreeLoaded) { return const SizedBox.shrink(); } final selectedUnit = treeState.selectedUnit; if (selectedUnit == null) { return Center( child: Padding( padding: const EdgeInsets.all(16), child: Text( 'Select a unit on the canvas to inspect its position slots.', style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onSurfaceVariant, ), textAlign: TextAlign.center, ), ), ); } final slots = treeState.selectedUnitSlots; final personnelById = { for (final p in (context.read().state is PersonnelLoaded ? (context.read().state as PersonnelLoaded).allPersonnel : [])) p.soldierId: p, }; return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Inspector header Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), color: theme.colorScheme.surfaceContainerHigh, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'UNIT INSPECTOR', style: theme.textTheme.labelSmall?.copyWith( fontWeight: FontWeight.bold, letterSpacing: 1.2, color: theme.colorScheme.onSurfaceVariant, ), ), const SizedBox(height: 4), Text( selectedUnit.name, style: theme.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.bold), ), Text( '${selectedUnit.echelon.displayName} • ${selectedUnit.branch.displayName}', style: theme.textTheme.labelSmall?.copyWith( color: theme.colorScheme.onSurfaceVariant, ), ), ], ), ), // Slots list Expanded( child: slots.isEmpty ? Center( child: Text( 'No position slots defined', style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onSurfaceVariant, ), ), ) : ListView.builder( itemCount: slots.length, itemBuilder: (context, i) { final slot = slots[i]; final soldier = slot.assignedPersonnelId != null ? personnelById[slot.assignedPersonnelId] : null; return PositionSlotCard( slot: slot, assignedSoldier: soldier, onUnassign: slot.isFilled ? () => _unassign(context, slot) : null, onAcceptDrop: (soldier) => _assign(context, soldier, slot), ); }, ), ), ], ); }, ); } // --------------------------------------------------------------------------- // Status Bar // --------------------------------------------------------------------------- Widget _buildStatusBar(BuildContext context) { final theme = Theme.of(context); return Container( height: 28, color: const Color(0xFF1B3A6B), padding: const EdgeInsets.symmetric(horizontal: 12), child: BlocBuilder( builder: (context, state) { if (state is! UnitTreeLoaded) { return const SizedBox.shrink(); } final unitCount = state.units.length; final slotCount = state.slots.length; final filledCount = state.slots.where((s) => s.isFilled).length; final fillPct = slotCount == 0 ? 0 : (filledCount * 100 ~/ slotCount); return Row( children: [ _statusChip(theme, Icons.account_tree, '$unitCount units'), const SizedBox(width: 16), _statusChip(theme, Icons.badge, '$filledCount / $slotCount slots ($fillPct%)'), const Spacer(), Text( 'Phase 2 — Canvas Engine', style: theme.textTheme.labelSmall?.copyWith(color: Colors.white54, fontSize: 10), ), ], ); }, ), ); } Widget _statusChip(ThemeData theme, IconData icon, String label) { return Row( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, size: 12, color: Colors.white70), const SizedBox(width: 4), Text( label, style: theme.textTheme.labelSmall?.copyWith(color: Colors.white70, fontSize: 10), ), ], ); } // --------------------------------------------------------------------------- // Actions // --------------------------------------------------------------------------- void _assign(BuildContext context, PersonnelRecord soldier, PositionSlot slot) { context.read().assignSoldierToSlot(soldier, slot); } void _unassign(BuildContext context, PositionSlot slot) { context.read().unassignFromSlot(slot); } void _confirmClearAll(BuildContext context) { showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('Clear All Units?'), content: const Text( 'This will delete all units, position slots, and personnel assignments. ' 'This action cannot be undone.', ), actions: [ TextButton( onPressed: () => Navigator.of(ctx).pop(), child: const Text('Cancel'), ), FilledButton( onPressed: () { Navigator.of(ctx).pop(); context.read().clearAll(); }, child: const Text('Clear All'), ), ], ), ); } }