Files
Dmitry 62d36aa3e8 feat(app): новый визуальный язык, верхняя навигация, портфели, создание счетов и ручные события
Тема и общие виджеты (AssetIcon, ServiceMark, HelpTip, глоссарий), верхняя панель TopNav и вкладки Аналитики вместо NavSidebar, иконки PWA. Экраны: портфели, создание брокерского счёта, ручное событие, правка инструмента, Обзор карточками по скоупам и таблица активов, пересчёт метрик с опросом /metrics/status. design-system.md обновлён.
2026-09-19 22:14:21 +03:00

68 lines
2.3 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../core/cache/cached.dart';
import '../../core/widgets/stale_banner.dart';
import 'allocation_tab.dart';
import 'benchmarks_card.dart';
import 'holdings_tab.dart';
import 'providers.dart';
import 'scope_selector.dart';
/// Портфель: позиции и аллокация as two tabs of one screen, sharing one scope.
///
/// They are tabs rather than two navigation destinations because they answer two halves of
/// the same question, and because a tenth item in the bottom bar would leave 40 px per
/// label on a phone.
///
/// One offline banner covers both tabs (`docs/ai/offline-cache.md`) — `TabBarView` builds
/// both eagerly anyway, so watching every provider here to compute it costs nothing extra.
class PortfolioPage extends ConsumerWidget {
const PortfolioPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final stale = oldestFetch([
ref.watch(portfolioSummaryProvider).valueOrNull?.fetchedAt,
ref.watch(holdingsProvider).valueOrNull?.fetchedAt,
ref.watch(valueSeriesProvider).valueOrNull?.fetchedAt,
ref.watch(portfolioReturnsProvider).valueOrNull?.fetchedAt,
ref.watch(benchmarkRowsProvider).valueOrNull?.fetchedAt,
ref.watch(allocationProvider).valueOrNull?.fetchedAt,
]);
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: const Text('Портфель'),
actions: const [ScopeSelector(), SizedBox(width: 8)],
bottom: const TabBar(
tabs: [
Tab(text: 'Позиции'),
Tab(text: 'Аллокация'),
],
),
),
body: Column(
children: [
if (stale != null)
Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 0),
child: StaleBanner(fetchedAt: stale),
),
const Expanded(
child: TabBarView(
children: [
HoldingsTab(sections: HoldingsSections.table),
AllocationTab(),
],
),
),
],
),
),
);
}
}