import 'package:flutter/material.dart'; import '../../../core/models/models.dart'; import '../../../core/models/severity_mapping.dart'; import '../../../core/theme/theme_x.dart'; import '../../../core/widgets/empty_state.dart'; import '../../../core/widgets/status_pill.dart'; /// One card per registered node showing its live services (via /// `/nodes/aggregated`), or an inline error card if this particular node /// was unreachable — a per-entry condition the local API itself reports, /// distinct from the aggregated fetch as a whole failing. class FleetCardGrid extends StatelessWidget { const FleetCardGrid({super.key, required this.entries}); final List entries; @override Widget build(BuildContext context) { if (entries.isEmpty) { return const EmptyState( icon: Icons.hub_outlined, title: 'Nothing to show yet', message: 'Register a node to see its live services here.', ); } return GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 320, mainAxisExtent: 180, crossAxisSpacing: 12, mainAxisSpacing: 12, ), itemCount: entries.length, itemBuilder: (context, index) => _NodeCard(entry: entries[index]), ); } } class _NodeCard extends StatelessWidget { const _NodeCard({required this.entry}); final AggregatedNodeStatus entry; @override Widget build(BuildContext context) { final hasError = entry.error != null; return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: context.colors.surface, borderRadius: BorderRadius.circular(10), border: Border.all( color: hasError ? context.status.critical.withValues(alpha: 0.4) : context.colors.outlineVariant, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(entry.node.name, style: context.text.labelLarge), const SizedBox(height: 10), Expanded( child: hasError ? _ErrorCallout(message: entry.error!) : entry.services.isEmpty ? Text('No services reported.', style: context.text.bodySmall) : ListView( padding: EdgeInsets.zero, children: [ for (final service in entry.services) Padding( padding: const EdgeInsets.symmetric(vertical: 3.5), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( service.name, overflow: TextOverflow.ellipsis, style: context.text.bodyMedium, ), ), StatusPill( label: service.status, severity: severityForServiceStatus(service.status), ), ], ), ), ], ), ), ], ), ); } } class _ErrorCallout extends StatelessWidget { const _ErrorCallout({required this.message}); final String message; @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: Color.alphaBlend(context.status.critical.withValues(alpha: 0.08), context.colors.surface), borderRadius: BorderRadius.circular(8), border: Border.all(color: context.status.critical.withValues(alpha: 0.35)), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(Icons.error_outline, size: 15, color: context.status.critical), const SizedBox(width: 8), Expanded( child: Text( message, style: context.dataStyles.dataMonoSmall, maxLines: 3, overflow: TextOverflow.ellipsis, ), ), ], ), ); } }