153 lines
4.6 KiB
Dart
153 lines
4.6 KiB
Dart
import 'package:flutter/material.dart';
|
|
import '../about/internal/application/TextType.dart';
|
|
|
|
import '../about/internal/application/ServerProfile.dart';
|
|
import '../about/internal/application/DeploymentRecord.dart';
|
|
import '../utils/Colors.dart';
|
|
import 'text/Text.dart';
|
|
|
|
/// Warm gradient "insights" card that surfaces computed tips from the data.
|
|
class InsightsCard extends StatelessWidget {
|
|
final List<ServerProfile> servers;
|
|
final List<DeploymentRecord> history;
|
|
final VoidCallback onViewHistory;
|
|
|
|
const InsightsCard({
|
|
super.key,
|
|
required this.servers,
|
|
required this.history,
|
|
required this.onViewHistory,
|
|
});
|
|
|
|
List<(IconData, String)> _tips() {
|
|
final tips = <(IconData, String)>[];
|
|
final failed = history
|
|
.where((h) => h.status == DeployStatus.failure)
|
|
.length;
|
|
final totalServices = servers.fold(0, (n, s) => n + s.services.length);
|
|
|
|
if (history.isEmpty) {
|
|
tips.add((
|
|
Icons.rocket_launch,
|
|
'Add a service and run your first deploy to unlock insights.',
|
|
));
|
|
}
|
|
if (failed > 0) {
|
|
tips.add((
|
|
Icons.warning_amber,
|
|
'$failed failed ${failed == 1 ? 'deploy' : 'deploys'} — open the service logs to investigate.',
|
|
));
|
|
}
|
|
// Busiest server
|
|
if (history.isNotEmpty) {
|
|
final counts = <String, int>{};
|
|
for (final h in history) {
|
|
counts[h.serverName] = (counts[h.serverName] ?? 0) + 1;
|
|
}
|
|
final busiest = counts.entries.reduce(
|
|
(a, b) => a.value >= b.value ? a : b,
|
|
);
|
|
tips.add((
|
|
Icons.trending_up,
|
|
'${busiest.key} is your busiest server (${busiest.value} deploys).',
|
|
));
|
|
}
|
|
final emptyServers = servers.where((s) => s.services.isEmpty).length;
|
|
if (emptyServers > 0) {
|
|
tips.add((
|
|
Icons.dns,
|
|
'$emptyServers ${emptyServers == 1 ? 'server has' : 'servers have'} no services yet — add one to start deploying.',
|
|
));
|
|
}
|
|
if (totalServices > 0) {
|
|
tips.add((
|
|
Icons.health_and_safety,
|
|
'Open Health to check the live status of your $totalServices ${totalServices == 1 ? 'service' : 'services'}.',
|
|
));
|
|
}
|
|
return tips.take(3).toList();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
const ink = PeachTheme.ink;
|
|
final tips = _tips();
|
|
return Container(
|
|
padding: const EdgeInsets.all(18),
|
|
decoration: BoxDecoration(
|
|
gradient: PeachTheme.peachGradient,
|
|
borderRadius: BorderRadius.circular(20),
|
|
boxShadow: PeachTheme.glow(PeachTheme.seed, opacity: 0.18, blur: 30),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
const Icon(Icons.auto_awesome, size: 18, color: ink),
|
|
const SizedBox(width: 8),
|
|
text(
|
|
'Insights',
|
|
16,
|
|
TextType.Bold,
|
|
color: ink,
|
|
weight: FontWeight.w800,
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 14),
|
|
for (final (icon, tip) in tips) ...[
|
|
_tip(icon, tip),
|
|
const SizedBox(height: 8),
|
|
],
|
|
const SizedBox(height: 4),
|
|
Align(
|
|
alignment: Alignment.centerRight,
|
|
child: FilledButton.icon(
|
|
onPressed: onViewHistory,
|
|
icon: const Icon(Icons.timeline, size: 16),
|
|
label: const Text('View history'),
|
|
style: FilledButton.styleFrom(
|
|
backgroundColor: ink,
|
|
foregroundColor: Colors.white,
|
|
elevation: 0,
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 16,
|
|
vertical: 12,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _tip(IconData icon, String tip) {
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 11),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withValues(alpha: 0.55),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Icon(icon, size: 16, color: PeachTheme.ink.withValues(alpha: 0.7)),
|
|
const SizedBox(width: 10),
|
|
Expanded(
|
|
child: text(
|
|
tip,
|
|
12.5,
|
|
TextType.Regular,
|
|
color: PeachTheme.ink.withValues(alpha: 0.85),
|
|
height: 1.35,
|
|
weight: FontWeight.w600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|