feat(app): новый визуальный язык, верхняя навигация, портфели, создание счетов и ручные события

Тема и общие виджеты (AssetIcon, ServiceMark, HelpTip, глоссарий), верхняя панель TopNav и вкладки Аналитики вместо NavSidebar, иконки PWA. Экраны: портфели, создание брокерского счёта, ручное событие, правка инструмента, Обзор карточками по скоупам и таблица активов, пересчёт метрик с опросом /metrics/status. design-system.md обновлён.
This commit is contained in:
Dmitry
2026-09-19 22:14:21 +03:00
parent a559d6de3e
commit 62d36aa3e8
73 changed files with 6406 additions and 1192 deletions
@@ -42,14 +42,14 @@ class BenchmarkResult {
bool get isPriceIndex => kind == 'price';
static BenchmarkResult fromJson(Map<String, dynamic> json) => BenchmarkResult(
benchmarkId: asInt(json['benchmark_id']) ?? 0,
code: asString(json['code']) ?? '',
kind: asString(json['kind']) ?? 'total_return',
twr: asString(json['twr']),
twrAnnualized: asString(json['twr_annualized']),
daysSkipped: asInt(json['days_skipped']) ?? 0,
excess: asString(json['excess']),
);
benchmarkId: asInt(json['benchmark_id']) ?? 0,
code: asString(json['code']) ?? '',
kind: asString(json['kind']) ?? 'total_return',
twr: asString(json['twr']),
twrAnnualized: asString(json['twr_annualized']),
daysSkipped: asInt(json['days_skipped']) ?? 0,
excess: asString(json['excess']),
);
}
class BenchmarkRow {
@@ -74,18 +74,22 @@ class BenchmarkRow {
final int portfolioDaysSkipped;
final List<BenchmarkResult> benchmarks;
bool get hasSkippedDays =>
portfolioDaysSkipped > 0 || benchmarks.any((b) => b.daysSkipped > 0);
bool get hasSkippedDays => portfolioDaysSkipped > 0 || benchmarksSkipDays;
/// Only the index side: the portfolio's own gaps are already marked next to its number.
bool get benchmarksSkipDays => benchmarks.any((b) => b.daysSkipped > 0);
static BenchmarkRow fromJson(Map<String, dynamic> json) => BenchmarkRow(
period: asString(json['period']) ?? 'all',
dateFrom: asDate(json['date_from']),
dateTo: asDate(json['date_to']),
portfolioTwr: asString(json['portfolio_twr']),
portfolioTwrAnnualized: asString(json['portfolio_twr_annualized']),
portfolioDaysSkipped: asInt(json['portfolio_days_skipped']) ?? 0,
benchmarks: asObjects(json['benchmarks']).map(BenchmarkResult.fromJson).toList(),
);
period: asString(json['period']) ?? 'all',
dateFrom: asDate(json['date_from']),
dateTo: asDate(json['date_to']),
portfolioTwr: asString(json['portfolio_twr']),
portfolioTwrAnnualized: asString(json['portfolio_twr_annualized']),
portfolioDaysSkipped: asInt(json['portfolio_days_skipped']) ?? 0,
benchmarks: asObjects(json['benchmarks'])
.map(BenchmarkResult.fromJson)
.toList(),
);
}
class BenchmarksApi {
@@ -104,7 +108,9 @@ class BenchmarksApi {
// `period` is repeatable; Dio serialises a list as repeated query parameters.
queryParameters: {'scope': scope, 'period': periods},
);
final rows = asObjects((r.data ?? const {})['rows']).map(BenchmarkRow.fromJson).toList();
final rows = asObjects((r.data ?? const {})['rows'])
.map(BenchmarkRow.fromJson)
.toList();
return Cached(rows, fetchedAt: r.extra['fetchedAt'] as DateTime?);
}
}