feat(app): страница здоровья вместо разрозненных /sync и data quality

/sync и блок data quality на дашборде сведены в один /health с двумя
вкладками (Источники, Качество данных); дашборд ссылается на неё вместо
собственного bottom sheet. Список находок вынесен в общий виджет
DataQualityList, чтобы не дублировать рендер.
This commit is contained in:
Dmitry
2026-09-19 12:59:27 +03:00
parent ffc5ed959a
commit 68e1e53535
6 changed files with 288 additions and 108 deletions
+8 -60
View File
@@ -15,18 +15,11 @@ import '../../core/widgets/empty_state.dart';
import '../../core/widgets/money_text.dart';
import '../../core/widgets/stale_banner.dart';
import '../../core/api/api_client.dart';
import '../health/data_quality_list.dart' show severityColor;
import 'providers.dart';
double _d(String s) => Decimal.parse(s).toDouble();
Color _severityColor(String severity) {
final s = severity.toLowerCase();
if (s.contains('crit') || s.contains('err')) return const Color(0xFFD03B3B);
if (s.contains('warn')) return const Color(0xFFFAB219);
if (s.contains('info') || s.contains('low')) return const Color(0xFF0CA30C);
return const Color(0xFFEC835A);
}
/// Обзор: the dashboard landing page — net worth, this month's cashflow,
/// runway, a net worth line chart, a 12-month income/expense bar chart, and
/// a data-quality summary linking to the findings.
@@ -53,53 +46,6 @@ class _HomePageState extends ConsumerState<HomePage> {
}
}
void _showDataQuality(List<DataQualityRow> rows) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => SafeArea(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Качество данных', style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 12),
if (rows.isEmpty) const Text('Проблем не найдено.'),
for (final row in rows)
Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: const EdgeInsets.only(top: 4, right: 8),
width: 10,
height: 10,
decoration:
BoxDecoration(color: _severityColor(row.severity), shape: BoxShape.circle),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('${row.checkName} (${row.count})',
style: Theme.of(context).textTheme.titleSmall),
Text(row.detail),
],
),
),
],
),
),
],
),
),
),
);
}
@override
Widget build(BuildContext context) {
final breakdown = ref.watch(netWorthBreakdownProvider);
@@ -166,10 +112,12 @@ class _HomePageState extends ConsumerState<HomePage> {
avatar: Icon(
rows.isEmpty ? Icons.check_circle_outline : Icons.warning_amber_outlined,
size: 18,
color: rows.isEmpty ? Colors.green : _severityColor(rows.first.severity),
color: rows.isEmpty ? Colors.green : severityColor(rows.first.severity),
),
label: Text(rows.isEmpty ? 'ок' : '${rows.length} замечаний'),
onPressed: rows.isEmpty ? null : () => _showDataQuality(rows),
// Health page renders the same findings via DataQualityList — no
// second implementation of this list here.
onPressed: rows.isEmpty ? null : () => context.go('/health?tab=quality'),
);
},
),
@@ -249,9 +197,9 @@ class _HomePageState extends ConsumerState<HomePage> {
Align(
alignment: Alignment.centerRight,
child: TextButton.icon(
onPressed: () => context.go('/sync'),
icon: const Icon(Icons.sync, size: 16),
label: const Text('Синхронизация'),
onPressed: () => context.go('/health'),
icon: const Icon(Icons.monitor_heart_outlined, size: 16),
label: const Text('Здоровье'),
),
),
],