feat(app): экраны импорта отчётов, целей, доходов, ребалансировки, налогов и бенчмарков

Импорт (/imports, /instruments/pending) и фаза 4 (/goals, /income,
/rebalance, /tax, аналитика-хаб с benchmarks_card) — по
docs/ai/import-contract.md и docs/ai/phase4-contract.md. file_picker для
загрузки отчёта.
This commit is contained in:
Dmitry
2026-09-19 10:44:38 +03:00
parent 15f5812ea4
commit b69bb4a0c9
52 changed files with 7404 additions and 13 deletions
+99
View File
@@ -0,0 +1,99 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'data/tax_api.dart';
import 'lots_tab.dart';
import 'providers.dart';
import 'summary_tab.dart';
/// Налоги: the year summary and the open-lot list with ЛДВ dates.
///
/// The word «оценка» is on the screen itself, not in a tooltip: the tax agent is the
/// broker, and these numbers exist so that the broker's statement can be checked against
/// something — not to replace it.
class TaxPage extends ConsumerWidget {
const TaxPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: const Text('Налоги (оценка)'),
actions: [
const _YearSelector(),
IconButton(
tooltip: 'Обновить',
icon: const Icon(Icons.refresh),
onPressed: () => invalidateTaxProviders(ref),
),
],
bottom: const TabBar(
tabs: [Tab(text: 'Сводка за год'), Tab(text: 'Лоты и ЛДВ')],
),
),
body: const Column(
children: [
EstimateBanner(),
Expanded(child: TabBarView(children: [TaxSummaryTab(), TaxLotsTab()])),
],
),
),
);
}
}
/// The disclaimer, always visible above both tabs.
class EstimateBanner extends ConsumerWidget {
const EstimateBanner({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final summary = ref.watch(taxSummaryProvider).valueOrNull;
final scheme = Theme.of(context).colorScheme;
final text = summary?.disclaimer ?? TaxSummary.defaultDisclaimer;
return Material(
color: scheme.tertiaryContainer,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
child: Row(
children: [
const Icon(Icons.info_outline, size: 18),
const SizedBox(width: 8),
Expanded(
child: Text(
text,
style: Theme.of(context).textTheme.bodySmall,
),
),
],
),
),
);
}
}
class _YearSelector extends ConsumerWidget {
const _YearSelector();
@override
Widget build(BuildContext context, WidgetRef ref) {
final year = ref.watch(taxYearProvider);
final now = DateTime.now().year;
return DropdownButtonHideUnderline(
child: DropdownButton<int>(
value: year,
borderRadius: BorderRadius.circular(8),
items: [
for (var y = now; y >= now - 6; y--)
DropdownMenuItem(value: y, child: Text('$y')),
],
onChanged: (v) {
if (v != null) ref.read(taxYearProvider.notifier).state = v;
},
),
);
}
}