import 'package:flutter/material.dart'; import '../about/internal/application/TextType.dart'; import '../about/internal/application/DeploymentRecord.dart'; import '../utils/Colors.dart'; import 'text/Text.dart'; import '../utils/CommonUtils.dart'; /// Deployment metrics: success-rate ring plus stat tiles. class MetricsPanel extends StatelessWidget { final List history; const MetricsPanel({super.key, required this.history}); @override Widget build(BuildContext context) { final jp = context.jp; final total = history.length; final success = history .where((h) => h.status == DeployStatus.success) .length; final failed = history .where((h) => h.status == DeployStatus.failure) .length; final reverted = history .where((h) => h.status == DeployStatus.reverted) .length; final attempts = success + failed; final rate = attempts == 0 ? 0.0 : success / attempts; final last = history.isNotEmpty ? history.first : null; return Container( padding: const EdgeInsets.all(16), decoration: PeachTheme.panel(context, glowColor: PeachTheme.seed), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ text( 'METRICS', 12, TextType.Regular, color: jp.textDim, letterSpacing: 1.2, weight: FontWeight.w700, ), const SizedBox(height: 14), Row( children: [ _rateRing(jp, rate, attempts), const SizedBox(width: 18), Expanded( child: Wrap( spacing: 10, runSpacing: 10, children: [ _tile(jp, 'Total', '$total', jp.text), _tile(jp, 'Success', '$success', PeachTheme.seed), _tile(jp, 'Failed', '$failed', PeachTheme.danger), _tile(jp, 'Reverted', '$reverted', PeachTheme.warn), ], ), ), ], ), if (last != null) ...[ const SizedBox(height: 14), Divider(height: 1, color: jp.border), const SizedBox(height: 12), Row( children: [ Icon(Icons.schedule, size: 14, color: jp.textFaint), const SizedBox(width: 6), Expanded( child: text( 'Last: ${last.serverName} · ${last.serviceLabel} · ${timeAgo(last.time)}', 12.5, TextType.Regular, color: jp.textDim, overflow: TextOverflow.ellipsis, weight: FontWeight.w400, ), ), _statusDot(last.status), ], ), ], ], ), ); } Widget _rateRing(JpColors jp, double rate, int attempts) { final pct = (rate * 100).round(); final color = rate >= 0.8 ? PeachTheme.seed : rate >= 0.5 ? PeachTheme.warn : PeachTheme.danger; return SizedBox( width: 84, height: 84, child: Stack( alignment: Alignment.center, children: [ SizedBox( width: 84, height: 84, child: CircularProgressIndicator( value: attempts == 0 ? 0 : rate, strokeWidth: 8, backgroundColor: jp.surface2, valueColor: AlwaysStoppedAnimation(color), strokeCap: StrokeCap.round, ), ), Column( mainAxisSize: MainAxisSize.min, children: [ text( attempts == 0 ? '—' : '$pct%', 20, TextType.Regular, color: color, weight: FontWeight.w800, ), text( 'SUCCESS', 8, TextType.Regular, color: jp.textFaint, letterSpacing: 1, weight: FontWeight.w700, ), ], ), ], ), ); } Widget _tile(JpColors jp, String label, String value, Color color) { return Container( width: 92, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), decoration: BoxDecoration( color: jp.bg.withValues(alpha: 0.5), borderRadius: BorderRadius.circular(12), border: Border.all(color: jp.border), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ text( value, 20, TextType.Regular, color: color, weight: FontWeight.w800, ), text( label.toUpperCase(), 9, TextType.Regular, color: jp.textFaint, letterSpacing: 0.8, weight: FontWeight.w700, ), ], ), ); } Widget _statusDot(DeployStatus s) { final color = switch (s) { DeployStatus.success => PeachTheme.seed, DeployStatus.failure => PeachTheme.danger, DeployStatus.reverted => PeachTheme.warn, }; return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( color: color.withValues(alpha: 0.16), borderRadius: BorderRadius.circular(20), ), child: text( s.label, 10, TextType.Regular, color: color, weight: FontWeight.w700, ), ); } }