Тема и общие виджеты (AssetIcon, ServiceMark, HelpTip, глоссарий), верхняя панель TopNav и вкладки Аналитики вместо NavSidebar, иконки PWA. Экраны: портфели, создание брокерского счёта, ручное событие, правка инструмента, Обзор карточками по скоупам и таблица активов, пересчёт метрик с опросом /metrics/status. design-system.md обновлён.
113 lines
3.7 KiB
Dart
113 lines
3.7 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
|
|
import '../portfolio/holdings_tab.dart';
|
|
import '../portfolio/overview_tiles.dart';
|
|
import '../portfolio/scope_selector.dart';
|
|
|
|
/// Аналитика → Общее. On a wide surface it is Snowball's overview — the four headline figures
|
|
/// for the chosen scope, then the value chart, returns and benchmark comparison; the other
|
|
/// analytics screens are one tab away (`AnalyticsTabs`). On a phone there is no tab strip, so
|
|
/// the same entry is a list of the sections instead.
|
|
///
|
|
/// The sections keep their own top-level routes from the contract (`/income`, `/rebalance`,
|
|
/// `/goals`, `/tax`) and are deep-linkable; `alsoMatches` in `nav_destinations.dart` keeps
|
|
/// Аналитика highlighted inside any of them.
|
|
class AnalyticsHubPage extends ConsumerWidget {
|
|
const AnalyticsHubPage({super.key});
|
|
|
|
static const _entries = [
|
|
(
|
|
path: '/income',
|
|
icon: Icons.payments_outlined,
|
|
title: 'Доходы',
|
|
subtitle: 'Календарь дивидендов и купонов, история выплат, прогноз на 12 месяцев',
|
|
),
|
|
(
|
|
path: '/rebalance',
|
|
icon: Icons.balance,
|
|
title: 'Ребалансировка',
|
|
subtitle:
|
|
'Целевые веса портфеля и рекомендации, что докупить или продать',
|
|
),
|
|
(
|
|
path: '/goals',
|
|
icon: Icons.flag_outlined,
|
|
title: 'Цели',
|
|
subtitle: 'Накопительные цели и прогноз их достижения по текущему тренду',
|
|
),
|
|
(
|
|
path: '/tax',
|
|
icon: Icons.receipt_long_outlined,
|
|
title: 'Налоги',
|
|
subtitle: 'Оценка налога за год и лоты с датой ЛДВ — для сверки со справкой брокера',
|
|
),
|
|
];
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
if (MediaQuery.sizeOf(context).width >= 600) return const _Overview();
|
|
return const _SectionList();
|
|
}
|
|
}
|
|
|
|
class _Overview extends StatelessWidget {
|
|
const _Overview();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
body: Column(
|
|
children: [
|
|
const Padding(
|
|
padding: EdgeInsets.fromLTRB(16, 8, 16, 0),
|
|
child: Align(
|
|
alignment: Alignment.centerRight,
|
|
child: ScopeSelector(),
|
|
),
|
|
),
|
|
const Expanded(
|
|
child: HoldingsTab(
|
|
sections: HoldingsSections.overview,
|
|
header: OverviewTiles(),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _SectionList extends StatelessWidget {
|
|
const _SectionList();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(title: const Text('Аналитика')),
|
|
body: ListView(
|
|
padding: const EdgeInsets.all(16),
|
|
children: [
|
|
for (final e in AnalyticsHubPage._entries)
|
|
Card(
|
|
child: ListTile(
|
|
leading: Icon(e.icon),
|
|
title: Text(e.title),
|
|
subtitle: Text(e.subtitle),
|
|
trailing: const Icon(Icons.chevron_right),
|
|
onTap: () => context.go(e.path),
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
Text(
|
|
'Сравнение с бенчмарками живёт на экране «Портфель»: обгон индекса — '
|
|
'свойство портфеля, а не отдельная тема.',
|
|
style: Theme.of(context).textTheme.bodySmall,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|