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
@@ -0,0 +1,73 @@
import 'package:fintracker_api/fintracker_api.dart';
import 'package:flutter/material.dart';
import '../../core/widgets/empty_state.dart';
/// Maps a `/metrics/data-quality` row's severity string to a color. Shared by
/// the Здоровье page's findings list and the dashboard's summary chip so the
/// two surfaces never disagree on what a severity looks like.
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);
}
/// Renders `DataQualityRow`s as a colored-dot list: one implementation used
/// by both the Здоровье page and (if reused) the dashboard's quick peek.
class DataQualityList extends StatelessWidget {
const DataQualityList({required this.rows, super.key});
final List<DataQualityRow> rows;
@override
Widget build(BuildContext context) {
if (rows.isEmpty) {
return const EmptyState(
icon: Icons.check_circle_outline,
message: 'Проблем не найдено.',
);
}
return Column(
children: [for (final row in rows) _DataQualityTile(row: row)],
);
}
}
class _DataQualityTile extends StatelessWidget {
const _DataQualityTile({required this.row});
final DataQualityRow row;
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: const EdgeInsets.only(top: 4, right: 10),
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),
const SizedBox(height: 2),
Text(row.detail),
],
),
),
],
),
),
);
}
}