Files
fin-tracker/app/lib/features/events/event_row.dart
T
Dmitry c588b575a7 feat(app): экран «События»
Лента брокерского леджера: фильтры по счёту, типу, датам и поиск, бесконечная
прокрутка с явной кнопкой «Ещё», карточка события в bottom sheet с переходом на
инструмент. Паттерн контроллера и пагинации взят у транзакций один в один — это тот
же список с фильтрами, и второй способ делать одно и то же был бы просто вторым
способом его чинить.

Пункт навигации стоит между «Портфелем» и «Потоками», а не рядом с «Операциями»:
это инвестиционная лента, а в операциях лежит ZenMoney.

eventKindLabels переиспользован из portfolio/labels.dart, где он уже жил ради карточки
инструмента, а не скопирован: два словаря подписей для одного енума разъезжаются на
первом же новом типе события.

Бэкенд не менялся — /events с фильтрами и пагинацией закрывает экран целиком,
клиент не перегенерировался.
2026-09-18 15:08:12 +03:00

75 lines
2.4 KiB
Dart

import 'package:fintracker_api/fintracker_api.dart';
import 'package:flutter/material.dart';
import '../../core/utils/ru_date.dart';
import '../../core/widgets/money_text.dart';
import '../portfolio/labels.dart' show formatQty, signColor;
import 'labels.dart';
/// One row of the События list: kind and ticker, the date with the account and quantity
/// behind it, and the native amount with its RUB equivalent when the currency differs.
///
/// The RUB line can be missing while the native amount is there — that is a day with no
/// rate, not a zero — so it is simply left out rather than filled in.
class EventRow extends StatelessWidget {
const EventRow({
required this.event,
required this.accountName,
required this.onTap,
super.key,
});
final EventOut event;
final String? accountName;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final e = event;
final theme = Theme.of(context);
final scheme = theme.colorScheme;
final (icon, color) = eventKindIcon(e.kind, scheme);
final showRub = e.currency != 'RUB' && e.amountRub != null;
final subtitle = [
ruDate(e.tradeDate),
?accountName,
if (e.quantity != null) '${formatQty(e.quantity!)} шт.',
if (e.status != EventStatus.confirmed) eventStatusLabel(e.status),
].join(' · ');
return ListTile(
onTap: onTap,
leading: Icon(icon, color: color),
title: Row(
children: [
Flexible(child: Text(eventTitle(e.kind, e.ticker), overflow: TextOverflow.ellipsis)),
if (e.externalFlow) ...[
const SizedBox(width: 6),
Tooltip(
message: 'Внешний поток — учитывается в XIRR',
child: Icon(Icons.swap_vert, size: 14, color: scheme.outline),
),
],
],
),
subtitle: Text(subtitle),
trailing: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
MoneyText(
e.amount,
currency: e.currency,
style: TextStyle(color: signColor(context, e.amount)),
),
if (showRub)
Text(
MoneyText.format(e.amountRub!, 'RUB'),
style: theme.textTheme.bodySmall?.copyWith(color: scheme.outline),
),
],
),
);
}
}