import 'package:decimal/decimal.dart'; import 'package:fintracker_api/fintracker_api.dart'; import 'package:flutter/material.dart'; import '../../core/theme/chart_colors.dart'; import '../../core/utils/ru_date.dart'; /// Russian labels for the keys the API sends as stable identifiers. /// /// The backend deliberately stores keys, not labels — `bucket` is `bond`, `cash`, /// `unknown`, a country code or a currency — so the language lives here, on the one side /// that has a language at all. const assetClassLabels = { 'share': 'Акции', 'bond': 'Облигации', 'etf': 'Фонды', 'fund': 'ПИФы', 'currency': 'Валюта', 'index': 'Индексы', 'deposit': 'Депозиты', 'real_estate': 'Недвижимость', 'crypto': 'Криптовалюта', 'custom': 'Прочее', }; const _countryLabels = { 'RU': 'Россия', 'US': 'США', 'KZ': 'Казахстан', 'CY': 'Кипр', 'NL': 'Нидерланды', }; const dimensionLabels = { 'asset_class': 'Класс актива', 'sector': 'Сектор', 'country': 'Страна', 'currency': 'Валюта', }; String assetClassLabel(String? value) => assetClassLabels[value] ?? value ?? '—'; /// A bucket key as a person reads it. `cash` and `unknown` are the two literals the /// allocation step emits; everything else is the source's own value. String bucketLabel(AllocationDimension dimension, String bucket) { if (bucket == 'cash') return 'Денежные средства'; if (bucket == 'unknown') return 'Не указано'; // `.value` is the wire key; `.name` is the Dart identifier the generator invented // (`assetClass` for `asset_class`), so keying a map off `.name` silently never matches. return switch (dimension.value) { 'asset_class' => assetClassLabels[bucket] ?? bucket, 'country' => _countryLabels[bucket] ?? bucket, _ => bucket, }; } String dimensionLabel(AllocationDimension dimension) => dimensionLabels[dimension.value] ?? dimension.value; const periodLabels = { '1m': '1 мес', '3m': '3 мес', '6m': '6 мес', 'ytd': 'С начала года', '1y': '1 год', '3y': '3 года', 'all': 'Всё время', }; String periodLabel(String period) => periodLabels[period] ?? period; const eventKindLabels = { 'buy': 'Покупка', 'sell': 'Продажа', 'dividend': 'Дивиденд', 'coupon': 'Купон', 'interest': 'Проценты', 'tax': 'Налог', 'tax_refund': 'Возврат налога', 'commission': 'Комиссия', 'deposit': 'Пополнение', 'withdrawal': 'Вывод', 'transfer_in': 'Ввод бумаг', 'transfer_out': 'Вывод бумаг', 'split': 'Сплит', 'amortization': 'Амортизация', 'repayment': 'Погашение', 'fx_exchange': 'Конвертация', 'other': 'Прочее', }; String eventKindLabel(EventKind kind) => eventKindLabels[kind.value] ?? kind.value; /// `'0.1234'` as `'+12,34 %'`. Null becomes an em dash: a return nobody could compute is /// not zero percent. String formatPercent(String? value, {bool signed = true}) { if (value == null) return '—'; final pct = (Decimal.parse(value).toDouble()) * 100; final sign = signed && pct > 0 ? '+' : ''; return '$sign${pct.toStringAsFixed(2).replaceAll('.', ',')} %'; } /// `'12,5'` for a quantity, trimming the NUMERIC(24,10) trailing zeros. String formatQty(String value) { final d = Decimal.parse(value); final text = d == d.truncate() ? d.truncate().toString() : d.toString(); return text.replaceAll('.', ','); } Color? signColor(BuildContext context, String? value) { if (value == null) return null; final d = Decimal.parse(value); if (d == Decimal.zero) return null; return d > Decimal.zero ? ChartColors.gain : ChartColors.loss; } /// The price-status chip every value on screen depends on: a stale price still produces a /// number, a missing one produces nothing at all, and both must be visible. class PriceStatusChip extends StatelessWidget { const PriceStatusChip({ required this.status, required this.priceDate, super.key, }); final String status; final DateTime? priceDate; @override Widget build(BuildContext context) { if (status == 'ok') return const SizedBox.shrink(); final missing = status == 'missing'; final scheme = Theme.of(context).colorScheme; final on = priceDate == null ? '' : ' от ${ruDate(priceDate!)}'; return Tooltip( message: missing ? 'Нет цены — стоимость позиции неизвестна и не входит в итоги' : 'Цена$on устарела, оценка по последней известной', child: Icon( missing ? Icons.help_outline : Icons.schedule, size: 16, color: missing ? scheme.error : ChartColors.slot4Yellow, ), ); } }