added first implementation of position list

This commit is contained in:
Matthias Hinrichs
2025-07-05 05:10:42 +02:00
parent aef9342cc5
commit 3a1ddd88d9
9 changed files with 1348 additions and 452 deletions
+101 -20
View File
@@ -6,7 +6,7 @@ import (
"portfolio-tracker/internal/web/templates/components"
)
templ PortfolioDetailContent(portfolio model.Portfolio) {
templ PortfolioDetailContent(portfolio model.Portfolio, positions []model.Position, positionSummary model.PositionSummary) {
<div class="container-fluid mt-4">
<div class="page-header">
<div class="row g-2 align-items-center">
@@ -55,12 +55,40 @@ templ PortfolioDetailContent(portfolio model.Portfolio) {
</tr>
</thead>
<tbody>
if len(portfolio.Activities) > 0 {
<tr>
<td colspan="6" class="text-center text-muted py-4">
Position calculation will be implemented here
</td>
</tr>
if len(positions) > 0 {
for _, position := range positions {
if position.IsOpen() {
<tr>
<td>
<div class="fw-bold">{ position.Stock }</div>
<div class="text-muted small">{ position.Currency }</div>
</td>
<td>
<span class="fw-bold">{ position.FormatShares() }</span>
</td>
<td>
<span>{ position.FormatCurrency(position.AverageCostPrice) }</span>
</td>
<td>
<div>{ position.FormatCurrency(position.CurrentPrice) }</div>
<div class="text-muted small">{ position.FormatCurrency(position.CurrentValue) }</div>
</td>
<td>
<div class={ position.GetUnrealizedPLColor() }>
<strong>{ position.FormatCurrency(position.UnrealizedPL) }</strong>
</div>
<div class={ position.GetUnrealizedPLColor() + " small" }>
{ position.FormatPercentage() }
</div>
</td>
<td>
<a href={ templ.URL("/details?stock=" + position.Stock) } class="btn btn-sm btn-outline-primary">
Details
</a>
</td>
</tr>
}
}
} else {
<tr>
<td colspan="6" class="text-center text-muted py-4">
@@ -208,7 +236,7 @@ templ PortfolioDetailContent(portfolio model.Portfolio) {
<h3 class="card-title">Portfolio-Zusammenfassung</h3>
</div>
<div class="card-body">
@PortfolioSummary(portfolio.Activities, portfolio.BaseCurrency)
@PortfolioSummary(positionSummary, len(portfolio.Activities))
</div>
</div>
<div class="card mt-3">
@@ -396,32 +424,85 @@ templ PortfolioDetailContent(portfolio model.Portfolio) {
}
// Separate component for portfolio summary
templ PortfolioSummary(activities []model.Activity, currency string) {
templ PortfolioSummary(summary model.PositionSummary, transactionCount int) {
<div class="row">
<div class="col-12">
<div class="mb-3">
<div class="text-muted">Anzahl Transaktionen</div>
<div class="h3 mb-0">{ fmt.Sprintf("%d", len(activities)) }</div>
<div class="h3 mb-0">{ fmt.Sprintf("%d", transactionCount) }</div>
</div>
<div class="mb-3">
<div class="text-muted">Aktueller Portfoliowert</div>
<div class="h2 mb-0">
{ formatCurrency(summary.TotalValue, summary.Currency) }
</div>
</div>
<div class="mb-3">
<div class="text-muted">Gesamtwert investiert</div>
<div class="h2 mb-0">
{ formatTotalInvestedWithConversion(activities, currency) }
</div>
<div class="text-muted small">
* = Aktueller Kurs verwendet (historischer Kurs nicht verfügbar)
<div class="h3 mb-0">
{ formatCurrency(summary.TotalCostBasis, summary.Currency) }
</div>
</div>
<div class="mb-3">
<div class="text-muted">Letzter Kauf</div>
<div class="h4 mb-0 text-muted">
{ getLastBuyDate(activities) }
<div class="text-muted">Unrealisierte Gewinne/Verluste</div>
<div class={ getColorClass(summary.TotalUnrealizedPL) + " h3 mb-0" }>
{ formatCurrency(summary.TotalUnrealizedPL, summary.Currency) }
if summary.TotalCostBasis > 0 {
<small class="ms-2">({ formatPercentage(summary.TotalUnrealizedPL / summary.TotalCostBasis * 100) })</small>
}
</div>
</div>
<div class="mb-3">
<div class="text-muted">Dividenden erhalten</div>
<div class="h4 mb-0 text-success">
{ formatCurrency(summary.TotalDividends, summary.Currency) }
</div>
</div>
<div class="mb-3">
<div class="text-muted">Gesamtrendite</div>
<div class={ getColorClass(summary.TotalReturn) + " h3 mb-0" }>
{ formatCurrency(summary.TotalReturn, summary.Currency) }
if summary.TotalCostBasis > 0 {
<small class="ms-2">({ formatPercentage(summary.TotalReturnPct) })</small>
}
</div>
</div>
</div>
</div>
}
templ PortfolioDetail(authenticated bool, username string, portfolio model.Portfolio, portfolios []model.Portfolio) {
@components.PageLayout(authenticated, username, portfolio.Name, PortfolioDetailContent(portfolio), portfolios)
// Helper functions for formatting
func formatCurrency(value float64, currency string) string {
switch currency {
case "EUR":
return fmt.Sprintf("€%.2f", value)
case "USD":
return fmt.Sprintf("$%.2f", value)
case "GBP":
return fmt.Sprintf("£%.2f", value)
case "CHF":
return fmt.Sprintf("%.2f CHF", value)
default:
return fmt.Sprintf("%.2f %s", value, currency)
}
}
func formatPercentage(value float64) string {
if value > 0 {
return fmt.Sprintf("+%.2f%%", value)
}
return fmt.Sprintf("%.2f%%", value)
}
func getColorClass(value float64) string {
if value > 0 {
return "text-success"
} else if value < 0 {
return "text-danger"
}
return "text-muted"
}
templ PortfolioDetail(authenticated bool, username string, portfolio model.Portfolio, portfolios []model.Portfolio, positions []model.Position, positionSummary model.PositionSummary) {
@components.PageLayout(authenticated, username, portfolio.Name, PortfolioDetailContent(portfolio, positions, positionSummary), portfolios)
}