Waaaay big enchancements
This commit is contained in:
208
lib/screens/stats_screen.dart
Normal file
208
lib/screens/stats_screen.dart
Normal file
@@ -0,0 +1,208 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../providers/providers.dart';
|
||||
|
||||
/// Экран просмотра статистики.
|
||||
/// Показывает сводку по группам за выбранный период.
|
||||
class StatsScreen extends ConsumerStatefulWidget {
|
||||
const StatsScreen({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<StatsScreen> createState() => _StatsScreenState();
|
||||
}
|
||||
|
||||
class _StatsScreenState extends ConsumerState<StatsScreen> {
|
||||
bool _loading = true;
|
||||
int _days = 7;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_load();
|
||||
}
|
||||
|
||||
Future<void> _load() async {
|
||||
setState(() => _loading = true);
|
||||
await ref.read(statsProvider.notifier).load(days: _days);
|
||||
if (mounted) setState(() => _loading = false);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final stats = ref.watch(statsProvider);
|
||||
final groups = (stats['groups'] as List<dynamic>?) ?? [];
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('СТАТИСТИКА'),
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
// ─── Переключатель периода ───
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
const Text('Период:', style: TextStyle(color: Colors.white54)),
|
||||
const SizedBox(width: 12),
|
||||
...[1, 7, 14, 30].map(
|
||||
(d) => Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: ChoiceChip(
|
||||
label: Text('$d д.'),
|
||||
selected: _days == d,
|
||||
selectedColor: Colors.deepOrange,
|
||||
onSelected: (_) {
|
||||
setState(() => _days = d);
|
||||
_load();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// ─── Содержимое ───
|
||||
Expanded(
|
||||
child: _loading
|
||||
? const Center(
|
||||
child: CircularProgressIndicator(color: Colors.deepOrange),
|
||||
)
|
||||
: groups.isEmpty
|
||||
? const Center(
|
||||
child: Text(
|
||||
'Нет данных',
|
||||
style: TextStyle(color: Colors.white54),
|
||||
),
|
||||
)
|
||||
: RefreshIndicator(
|
||||
color: Colors.deepOrange,
|
||||
onRefresh: _load,
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.all(12),
|
||||
itemCount: groups.length,
|
||||
itemBuilder: (context, index) {
|
||||
final g = groups[index];
|
||||
return _StatsCard(data: g is Map
|
||||
? Map<String, dynamic>.from(g)
|
||||
: {});
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Карточка статистики одной группы
|
||||
class _StatsCard extends StatelessWidget {
|
||||
final Map<String, dynamic> data;
|
||||
|
||||
const _StatsCard({required this.data});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final targetId = (data['target_id'] ?? data['group_id'] ?? data['id'] ?? '').toString();
|
||||
final name = (data['name'] ?? targetId).toString();
|
||||
final totalCommands = data['total_commands'] ?? 0;
|
||||
final togglesOn = data['toggles_on'] ?? 0;
|
||||
final togglesOff = data['toggles_off'] ?? 0;
|
||||
final estimatedHours = data['estimated_hours'];
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
name,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_StatRow(
|
||||
icon: Icons.touch_app,
|
||||
label: 'Всего команд',
|
||||
value: totalCommands.toString(),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
_StatRow(
|
||||
icon: Icons.power_settings_new,
|
||||
label: 'Включений',
|
||||
value: togglesOn.toString(),
|
||||
color: Colors.green,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
_StatRow(
|
||||
icon: Icons.power_off,
|
||||
label: 'Выключений',
|
||||
value: togglesOff.toString(),
|
||||
color: Colors.redAccent,
|
||||
),
|
||||
if (estimatedHours != null) ...[
|
||||
const SizedBox(height: 4),
|
||||
_StatRow(
|
||||
icon: Icons.access_time,
|
||||
label: 'Примерное время работы',
|
||||
value: _formatHours(estimatedHours),
|
||||
color: Colors.amber,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _formatHours(dynamic hours) {
|
||||
final h = (hours is num) ? hours.toDouble() : 0.0;
|
||||
if (h < 1) return '${(h * 60).round()} мин';
|
||||
return '${h.toStringAsFixed(1)} ч';
|
||||
}
|
||||
}
|
||||
|
||||
/// Строка с иконкой, меткой и значением
|
||||
class _StatRow extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String label;
|
||||
final String value;
|
||||
final Color? color;
|
||||
|
||||
const _StatRow({
|
||||
required this.icon,
|
||||
required this.label,
|
||||
required this.value,
|
||||
this.color,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Icon(icon, size: 16, color: color ?? Colors.white38),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
label,
|
||||
style: const TextStyle(fontSize: 13, color: Colors.white54),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
value,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: color ?? Colors.white70,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user