Skip to content

Commit 1d27200

Browse files
committed
Improved PortfolioDetail.razor documentation (comments), made code more readable and added loading indicators.
Added DecimalUtils class that offers a method for formatting a decimal value to two decimal places. Improved CurrencyUtils.
1 parent b987698 commit 1d27200

File tree

3 files changed

+109
-81
lines changed

3 files changed

+109
-81
lines changed

Utils/CurrencyUtils.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public static string GetCurrencyLabel(Currency currency)
2424

2525
public static string Format(decimal value, Currency currency)
2626
{
27-
var valueStr = String.Format("{0:.00}", Math.Abs(value));
27+
var valueStr = String.Format("{0:0.00}", Math.Abs(value));
2828
var output = "";
2929
switch (currency)
3030
{

Utils/DecimalUtils.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using System;
2+
3+
namespace Utils
4+
{
5+
public static class DecimalUtils
6+
{
7+
public static string FormatTwoDecimalPlaces(decimal value) => String.Format("{0:0.00}", Math.Abs(value));
8+
}
9+
}

WebFrontend/Pages/PortfolioDetail.razor

Lines changed: 99 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -38,47 +38,62 @@
3838

3939
<div class="mat-layout-grid-cell mat-layout-grid-cell-span-3"></div>
4040
<div class="mat-layout-grid-cell mat-layout-grid-cell-span-6">
41-
@if (activePortfolio != null)
41+
@if (ActivePortfolio != null)
4242
{
4343
<MatCard class="demo-mat-card">
4444
<MatCardContent>
4545
<div class="demo-mat-card-content">
4646
<MatHeadline6 class="clear-margin">
4747
<MatChipSet Style="align-items: center">
48-
<MatH5 Class="clear-margin">@activePortfolio.Name</MatH5>
49-
<MatChip Style="vertical-align: center" Label="@CurrencyUtils.GetCurrencyLabel(activePortfolio.Currency)"/>
48+
<MatH5 Class="clear-margin">@ActivePortfolio.Name</MatH5>
49+
<MatChip Style="vertical-align: center" Label="@CurrencyUtils.GetCurrencyLabel(ActivePortfolio.Currency)"/>
5050
</MatChipSet>
5151
</MatHeadline6>
5252
</div>
5353

5454
<MatBody2 class="demo-mat-card-content clear-margin">
5555
<div class="mat-layout-grid">
5656
<div class="mat-layout-grid-inner" style="align-items: center">
57-
<div class="mat-layout-grid-cell mat-layout-grid-cell-span-6">
58-
<MatH4 Class="clear-margin">@(CurrencyUtils.Format(portfolioSummary.MarketValue, activePortfolio.Currency))</MatH4>
59-
</div>
60-
<div class="mat-layout-grid-cell mat-layout-grid-cell-span-6" style="text-align: end">
61-
@(portfolioSummary.RelativeChange * 100m) %
62-
</div>
57+
@if (PortfolioSummary != null)
58+
{
59+
<div class="mat-layout-grid-cell mat-layout-grid-cell-span-6">
60+
61+
<MatH4 Class="clear-margin">@(CurrencyUtils.Format(PortfolioSummary.MarketValue, ActivePortfolio.Currency))</MatH4>
62+
</div>
63+
<div class="mat-layout-grid-cell mat-layout-grid-cell-span-6" style="text-align: end">
64+
@(PortfolioSummary.RelativeChange * 100m) %
65+
</div>
66+
}
67+
else
68+
{
69+
<MatProgressCircle Indeterminate="true"></MatProgressCircle>
70+
}
6371
</div>
6472
</div>
6573
</MatBody2>
6674
</MatCardContent>
6775
</MatCard>
68-
<MatTable Items="@portfolioEntryRows" Striped="true" AllowSelection="true" RowClass="tester" class="mat-elevation-z5" ShowPaging="false" PageSize="9999" SelectionChanged="SelectionChangedEvent">
69-
<MatTableHeader>
70-
<th>Coin</th>
71-
<th>Price</th>
72-
<th>Change (1h)</th>
73-
<th>Holdings</th>
74-
</MatTableHeader>
75-
<MatTableRow>
76-
<td>@context.symbol.ToUpper()</td>
77-
<td>@(CurrencyUtils.Format(context.currentPrice, activePortfolio.Currency))</td>
78-
<td style='color: @(context.relativeChange >= 0 ? "#17a104" : "#FF0000")'>@context.relativeChange%</td>
79-
<td>@context.percentage%</td>
80-
</MatTableRow>
81-
</MatTable>
76+
@if (PortfolioEntryRows != null)
77+
{
78+
<MatTable Items="@PortfolioEntryRows" Striped="true" AllowSelection="true" RowClass="tester" class="mat-elevation-z5" ShowPaging="false" PageSize="9999" SelectionChanged="SelectionChangedEvent">
79+
<MatTableHeader>
80+
<th>Coin</th>
81+
<th>Price</th>
82+
<th>Change (1h)</th>
83+
<th>Holdings</th>
84+
</MatTableHeader>
85+
<MatTableRow>
86+
<td>@context.symbol.ToUpper()</td>
87+
<td>@(CurrencyUtils.Format(context.currentPrice, ActivePortfolio.Currency))</td>
88+
<td style='color: @(context.relativeChange >= 0 ? "#17a104" : "#FF0000")'>@DecimalUtils.FormatTwoDecimalPlaces(context.relativeChange)%</td>
89+
<td>@context.percentage%</td>
90+
</MatTableRow>
91+
</MatTable>
92+
}
93+
else
94+
{
95+
<MatProgressBar Indeterminate="true"></MatProgressBar>
96+
}
8297
}
8398
else
8499
{
@@ -88,94 +103,98 @@
88103
<div class="mat-layout-grid-cell mat-layout-grid-cell-span-3"></div>
89104
</div>
90105
</div>
91-
<MatFAB Class="app-fab--absolute" Icon="@MatIconNames.Add" Label="Add a new entry" OnClick='() => { NavigationManager.NavigateTo($"newportfolioentry/{activePortfolio.Id}"); }'></MatFAB>
106+
<MatFAB Class="app-fab--absolute" Icon="@MatIconNames.Add" Label="Add a new entry" OnClick='() => { NavigationManager.NavigateTo($"newportfolioentry/{ActivePortfolio.Id}"); }'></MatFAB>
92107

93108

94109
@code
95110
{
96111
[Parameter]
97112
public int PortfolioId { get; set; }
98113

99-
protected Portfolio activePortfolio;
114+
protected Portfolio ActivePortfolio;
100115

101-
protected ISummaryService.Summary portfolioSummary = new(1341m, 1.8m, 9982.489m, 1000m);
116+
protected ISummaryService.Summary PortfolioSummary = null;
102117

103-
protected List<PortfolioEntry> activePortfolioEntries = new List<PortfolioEntry>()
104-
{
105-
new("btc", 1, 1),
106-
new("ada", 1, 2),
107-
new("eth", 1, 3),
108-
new("ltc", 1, 4),
109-
new("link", 1, 5),
110-
};
111-
112-
protected List<decimal> portfolioHoldings = new()
113-
{
114-
44.8886m,
115-
28.18m,
116-
10.116m,
117-
9.38m,
118-
2.70m,
119-
};
120-
121-
protected List<PortfolioEntryRow> portfolioEntryRows = new()
122-
{
123-
new("btc", 57644.42m, 1.35m, 44.76m),
124-
new("ada", 1.36m, 0.58m, 28.18m),
125-
new("eth", 3279.64m, 10.95m, 27.11m),
126-
new("ltc", 291.55m, 7.20m, 9.38m),
127-
new("link", 42.20m, -5.19m, 2.70m)
128-
};
118+
protected List<PortfolioEntry> ActivePortfolioEntries;
119+
120+
protected List<PortfolioEntryRow> PortfolioEntryRows;
129121

130122
protected record PortfolioEntryRow(string symbol, decimal currentPrice, decimal relativeChange, decimal percentage);
131123

132124
protected override void OnInitialized()
133125
{
134-
activePortfolio = PortfolioService.GetPortfolio(PortfolioId);
135-
activePortfolioEntries = PortfolioEntryService.GetPortfolioEntries(PortfolioId);
126+
ActivePortfolio = PortfolioService.GetPortfolio(PortfolioId);
127+
ActivePortfolioEntries = PortfolioEntryService.GetPortfolioEntries(PortfolioId);
128+
_loadEntryInfo();
136129
}
137130

138-
protected override async Task OnInitializedAsync()
131+
132+
private async void _loadEntryInfo()
139133
{
140-
var marketEntries = (await CryptoStatsSource.GetMarketEntries(
141-
CurrencyUtils.GetCurrencyLabel(activePortfolio.Currency).ToLower(),
142-
await Task.WhenAll(activePortfolioEntries.Select(async entry => (await CryptoNameResolver.Resolve(entry.Symbol)).ToLower()))
143-
)).ToDictionary(entry => entry.Symbol, entry => entry);
134+
// resolve names of all portfolio entries
135+
var portfolioEntriesNames = await Task.WhenAll(ActivePortfolioEntries.Select(async entry => (await CryptoNameResolver.Resolve(entry.Symbol)).ToLower()));
144136

137+
// fetch market entries of all entries of the portfolio
138+
var marketEntries = await CryptoStatsSource.GetMarketEntries(
139+
CurrencyUtils.GetCurrencyLabel(ActivePortfolio.Currency).ToLower(), portfolioEntriesNames
140+
);
145141

146-
List<ISummaryService.Summary> summaries = new List<ISummaryService.Summary>();
147-
foreach (var portfolioEntry in activePortfolioEntries)
148-
{
149-
var marketEntry = marketEntries.GetValueOrDefault(portfolioEntry.Symbol);
142+
// create a dictionary where a symbol is mapped to a market entry
143+
var symbolsToMarketEntries = marketEntries.ToDictionary(entry => entry.Symbol, entry => entry);
150144

151-
if (marketEntry == null)
145+
// compute portfolio entry summaries
146+
var entrySummaries = ActivePortfolioEntries.Select(
147+
portfolioEntry =>
152148
{
153-
// TODO market entry is null
149+
// find the evaluation of the entry's asset
150+
var marketEntry = symbolsToMarketEntries.GetValueOrDefault(portfolioEntry.Symbol);
151+
152+
if (marketEntry == null)
153+
{
154+
// TODO market entry is null
155+
}
156+
157+
// fetch all orders of the currently iterated portfolio entry
158+
var entryMarketOrders = MarketOrderService.GetPortfolioEntryOrders(portfolioEntry.Id);
159+
160+
// compute the summary of the entry based on market orders
161+
return SummaryService.GetPortfolioEntrySummary(entryMarketOrders, marketEntry.CurrentPrice);
154162
}
155-
var entryOrders = MarketOrderService.GetPortfolioEntryOrders(portfolioEntry.Id);
156-
summaries.Add(SummaryService.GetPortfolioEntrySummary(entryOrders, marketEntry.CurrentPrice));
157-
}
158-
portfolioSummary = SummaryService.GetPortfolioSummary(summaries);
163+
).ToList();
164+
165+
// compute portfolio's summary based on summaries of it's entries
166+
PortfolioSummary = SummaryService.GetPortfolioSummary(entrySummaries);
159167

160-
if (portfolioSummary.Cost == 0)
168+
// if the cost of the summary is zero, set the relative change to zero
169+
if (PortfolioSummary.Cost == 0)
161170
{
162-
portfolioSummary = portfolioSummary with{
171+
PortfolioSummary = PortfolioSummary with {
163172
RelativeChange = 0
164-
};
173+
};
165174
}
166175

167-
var totalMarketValue = summaries.Sum(summary => summary.MarketValue);
168-
169-
170-
portfolioEntryRows = summaries.Zip(activePortfolioEntries).Select((summary) => new PortfolioEntryRow(summary.Second.Symbol, marketEntries[summary.Second.Symbol].CurrentPrice, new decimal(marketEntries[summary.Second.Symbol].PriceChangePercentage24H), totalMarketValue > 0 ? (summary.First.MarketValue / totalMarketValue) * 100 : 0)).ToList();
176+
// compute the total value of the portfolio by summing market values of all entries
177+
var portfolioTotalMarketValue = entrySummaries.Sum(summary => summary.MarketValue);
178+
179+
// create portfolio entry table rows
180+
PortfolioEntryRows = entrySummaries.Zip(ActivePortfolioEntries).Select(
181+
(tuple) => new PortfolioEntryRow(
182+
// symbol of the portfolio entry
183+
tuple.Second.Symbol,
184+
// current price of the entry's asset
185+
symbolsToMarketEntries[tuple.Second.Symbol].CurrentPrice,
186+
// asset's price change since the last 24h
187+
new decimal(symbolsToMarketEntries[tuple.Second.Symbol].PriceChangePercentage24H),
188+
// percentage within the portfolio
189+
portfolioTotalMarketValue > 0 ? (tuple.First.MarketValue / portfolioTotalMarketValue) * 100 : 0)
190+
).ToList();
191+
192+
StateHasChanged();
171193
}
172194

173195
public void SelectionChangedEvent(object row)
174196
{
175-
if (row == null)
176-
{
177-
}
178-
else
197+
if (row != null)
179198
{
180199
NavigationManager.NavigateTo($"entrydetail");
181200
}

0 commit comments

Comments
 (0)