import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import '../../../core/models/models.dart'; import '../../../core/theme/status_colors.dart'; import '../../../core/theme/theme_x.dart'; import '../../../core/utils/relative_time.dart'; import '../../../core/widgets/data_table_scaffold.dart'; import '../../../core/widgets/empty_state.dart'; import '../../../core/widgets/mono_text.dart'; import '../../../core/widgets/status_pill.dart'; class FleetTable extends StatelessWidget { const FleetTable({ super.key, required this.nodes, required this.aggregated, required this.onEdit, required this.onDelete, }); final List nodes; /// Null while the aggregated fetch is still loading/erroring — reachability /// then shows as "unknown" rather than guessing. final List? aggregated; final void Function(RemoteNode node) onEdit; final void Function(RemoteNode node) onDelete; ({String label, Severity severity}) _reachability(RemoteNode node) { final entry = aggregated?.where((a) => a.node.id == node.id).firstOrNull; if (entry == null) return (label: 'unknown', severity: Severity.neutral); return entry.error == null ? (label: 'online', severity: Severity.good) : (label: 'unreachable', severity: Severity.critical); } @override Widget build(BuildContext context) { return DataTableScaffold( empty: const EmptyState( icon: Icons.hub_outlined, title: 'No nodes registered', message: 'Register a sibling NodeMaster host to see it here.', ), columns: const [ DataColumn(label: Text('Node')), DataColumn(label: Text('URL')), DataColumn(label: Text('Tags')), DataColumn(label: Text('Status')), DataColumn(label: Text('Last seen')), DataColumn(label: Text('')), ], rows: [ for (final node in nodes) DataRow( cells: [ DataCell( Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text(node.name, style: context.text.labelLarge), if (node.description?.isNotEmpty == true) Text(node.description!, style: context.text.bodySmall), ], ), ), DataCell(MonoText(node.url)), DataCell( Wrap( spacing: 4, children: [ for (final tag in node.tags) Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: context.colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(4), ), child: Text(tag, style: context.dataStyles.dataMonoSmall), ), ], ), ), DataCell(Builder( builder: (context) { final r = _reachability(node); return StatusPill(label: r.label, severity: r.severity); }, )), DataCell( Text( node.lastSeen == null ? '—' : formatRelative(node.lastSeen!.toLocal()), style: context.text.bodySmall, ), ), DataCell( Row( mainAxisSize: MainAxisSize.min, children: [ IconButton( tooltip: 'Edit', icon: const Icon(Icons.edit_outlined, size: 18), onPressed: () => onEdit(node), ), IconButton( tooltip: 'Remove', icon: const Icon(Icons.delete_outline, size: 18), onPressed: () => onDelete(node), ), ], ), ), ], ), ], ); } }