first commit
This commit is contained in:
@@ -0,0 +1,424 @@
|
||||
package templates
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"portfolio-tracker/internal/model"
|
||||
"portfolio-tracker/internal/web/templates/components"
|
||||
)
|
||||
|
||||
templ PortfolioDetailContent(portfolio model.Portfolio) {
|
||||
<div class="container-fluid mt-4">
|
||||
<div class="page-header">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col">
|
||||
<h2 class="page-title">{ portfolio.Name } ({ portfolio.BaseCurrency })</h2>
|
||||
<div class="page-subtitle">
|
||||
if portfolio.Description != "" {
|
||||
{ portfolio.Description }
|
||||
} else {
|
||||
Erstellt am { portfolio.CreatedAt.Format("02.01.2006") }
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto ms-auto d-print-none">
|
||||
<div class="btn-list">
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addTransactionModal">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M12 5l0 14"></path>
|
||||
<path d="M5 12l14 0"></path>
|
||||
</svg>
|
||||
Transaktion hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<!-- Portfolio Overview -->
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Positionen</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-vcenter">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Wertpapier</th>
|
||||
<th>Anzahl</th>
|
||||
<th>Ø Einkaufspreis</th>
|
||||
<th>Aktueller Wert</th>
|
||||
<th>+/- Gesamt</th>
|
||||
<th></th>
|
||||
</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>
|
||||
} else {
|
||||
<tr>
|
||||
<td colspan="6" class="text-center text-muted py-4">
|
||||
Keine Positionen vorhanden. Fügen Sie Ihre erste Transaktion hinzu.
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Recent Activities -->
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Letzte Transaktionen</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Datum</th>
|
||||
<th>Typ</th>
|
||||
<th>Wertpapier</th>
|
||||
<th>Anzahl</th>
|
||||
<th>Preis</th>
|
||||
<th>Preis ({ portfolio.BaseCurrency })</th>
|
||||
<th>Gesamt</th>
|
||||
<th>Gesamt ({ portfolio.BaseCurrency })</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
if len(portfolio.Activities) > 0 {
|
||||
for i, activity := range portfolio.Activities {
|
||||
if i < 10 {
|
||||
<tr>
|
||||
<td>{ activity.Date.Format("02.01.2006") }</td>
|
||||
<td>
|
||||
if activity.Type == "BUY" {
|
||||
<span class="badge bg-green">Kauf</span>
|
||||
} else if activity.Type == "SELL" {
|
||||
<span class="badge bg-red">Verkauf</span>
|
||||
} else if activity.Type == "DIVIDEND" {
|
||||
<span class="badge bg-blue">Dividende</span>
|
||||
} else {
|
||||
<span class="badge bg-secondary">{ string(activity.Type) }</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<a href={ templ.URL("/details?stock=" + activity.Stock) } class="text-decoration-none">
|
||||
{ activity.Stock }
|
||||
</a>
|
||||
</td>
|
||||
<td>{ fmt.Sprintf("%.3f", activity.Amount) }</td>
|
||||
<td>{ fmt.Sprintf("%.2f %s", activity.Price, activity.Currency) }</td>
|
||||
<td>
|
||||
if activity.Currency != portfolio.BaseCurrency {
|
||||
{ getConvertedPrice(activity, portfolio.BaseCurrency) }
|
||||
} else {
|
||||
<span class="text-muted">-</span>
|
||||
}
|
||||
</td>
|
||||
<td>{ fmt.Sprintf("%.2f %s", activity.Amount * activity.Price, activity.Currency) }</td>
|
||||
<td>
|
||||
if activity.Currency != portfolio.BaseCurrency {
|
||||
{ getConvertedTotal(activity, portfolio.BaseCurrency) }
|
||||
} else {
|
||||
<span class="text-muted">-</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-list flex-nowrap">
|
||||
<button
|
||||
class="btn btn-sm btn-outline-primary"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#editTransactionModal"
|
||||
data-activity-id={ fmt.Sprintf("%d", activity.ID) }
|
||||
data-activity-type={ string(activity.Type) }
|
||||
data-activity-stock={ activity.Stock }
|
||||
data-activity-amount={ fmt.Sprintf("%.3f", activity.Amount) }
|
||||
data-activity-price={ fmt.Sprintf("%.2f", activity.Price) }
|
||||
data-activity-date={ activity.Date.Format("2006-01-02") }
|
||||
data-activity-note={ activity.Note }
|
||||
>
|
||||
Bearbeiten
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm btn-outline-danger"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#deleteTransactionModal"
|
||||
data-activity-id={ fmt.Sprintf("%d", activity.ID) }
|
||||
data-activity-stock={ activity.Stock }
|
||||
data-activity-type={ string(activity.Type) }
|
||||
data-activity-amount={ fmt.Sprintf("%.3f", activity.Amount) }
|
||||
data-activity-date={ activity.Date.Format("02.01.2006") }
|
||||
>
|
||||
Löschen
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
} else {
|
||||
<tr>
|
||||
<td colspan="9" class="text-center text-muted py-3">
|
||||
Keine Transaktionen vorhanden
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Portfolio Summary -->
|
||||
<div class="col-md-4">
|
||||
<!-- Currency Conversion Info -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="me-3">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon text-blue" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<circle cx="12" cy="12" r="9"></circle>
|
||||
<path d="M12 8h.01"></path>
|
||||
<path d="M11 12h1v4h1"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-fill">
|
||||
<div class="font-weight-medium">Währungsumrechnung</div>
|
||||
<div class="text-muted small">
|
||||
Transaktionen in anderen Währungen werden automatisch in { portfolio.BaseCurrency } umgerechnet.
|
||||
Wechselkurse werden stündlich aktualisiert.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Portfolio-Zusammenfassung</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@PortfolioSummary(portfolio.Activities, portfolio.BaseCurrency)
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Allokation</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="allocationChart"></div>
|
||||
<p class="text-muted text-center">Allokations-Chart wird hier implementiert</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Add Transaction Modal -->
|
||||
<div class="modal modal-blur fade" id="addTransactionModal" tabindex="-1" aria-labelledby="addTransactionModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="addTransactionModalLabel">Transaktion hinzufügen</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
|
||||
</div>
|
||||
<form method="post" action="/portfolio/transaction">
|
||||
<input type="hidden" name="portfolio_id" value={ fmt.Sprintf("%d", portfolio.ID) }/>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label for="transaction-type" class="form-label">Typ</label>
|
||||
<select class="form-select" id="transaction-type" name="type" required>
|
||||
<option value="">Wählen Sie einen Typ</option>
|
||||
<option value="BUY">Kauf</option>
|
||||
<option value="SELL">Verkauf</option>
|
||||
<option value="DIVIDEND">Dividende</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="transaction-stock" class="form-label">Wertpapier</label>
|
||||
<input type="text" class="form-control" id="transaction-stock" name="stock" placeholder="z.B. AAPL" required/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="transaction-amount" class="form-label">Anzahl</label>
|
||||
<input type="number" class="form-control" id="transaction-amount" name="amount" step="0.001" min="0" required/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="transaction-price" class="form-label">Preis</label>
|
||||
<div class="input-group">
|
||||
<input type="number" class="form-control" id="transaction-price" name="price" step="0.01" min="0" required/>
|
||||
<span class="input-group-text">{ portfolio.BaseCurrency }</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="transaction-date" class="form-label">Datum</label>
|
||||
<input type="date" class="form-control" id="transaction-date" name="date" required/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="transaction-note" class="form-label">Notiz (optional)</label>
|
||||
<textarea class="form-control" id="transaction-note" name="note" rows="2"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
||||
<button type="submit" class="btn btn-primary">Speichern</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Edit Transaction Modal -->
|
||||
<div class="modal modal-blur fade" id="editTransactionModal" tabindex="-1" aria-labelledby="editTransactionModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="editTransactionModalLabel">Transaktion bearbeiten</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
|
||||
</div>
|
||||
<form method="post" action="/portfolio/transaction/edit">
|
||||
<input type="hidden" name="activity_id" id="edit-activity-id"/>
|
||||
<input type="hidden" name="portfolio_id" value={ fmt.Sprintf("%d", portfolio.ID) }/>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label for="edit-transaction-type" class="form-label">Typ</label>
|
||||
<select class="form-select" id="edit-transaction-type" name="type" required>
|
||||
<option value="">Wählen Sie einen Typ</option>
|
||||
<option value="BUY">Kauf</option>
|
||||
<option value="SELL">Verkauf</option>
|
||||
<option value="DIVIDEND">Dividende</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="edit-transaction-stock" class="form-label">Wertpapier</label>
|
||||
<input type="text" class="form-control" id="edit-transaction-stock" name="stock" placeholder="z.B. AAPL" required/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="edit-transaction-amount" class="form-label">Anzahl</label>
|
||||
<input type="number" class="form-control" id="edit-transaction-amount" name="amount" step="0.001" min="0" required/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="edit-transaction-price" class="form-label">Preis</label>
|
||||
<div class="input-group">
|
||||
<input type="number" class="form-control" id="edit-transaction-price" name="price" step="0.01" min="0" required/>
|
||||
<span class="input-group-text">{ portfolio.BaseCurrency }</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="edit-transaction-date" class="form-label">Datum</label>
|
||||
<input type="date" class="form-control" id="edit-transaction-date" name="date" required/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="edit-transaction-note" class="form-label">Notiz (optional)</label>
|
||||
<textarea class="form-control" id="edit-transaction-note" name="note" rows="2"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
||||
<button type="submit" class="btn btn-primary">Speichern</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Delete Transaction Modal -->
|
||||
<div class="modal modal-blur fade" id="deleteTransactionModal" tabindex="-1" aria-labelledby="deleteTransactionModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-sm">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="deleteTransactionModalLabel">Transaktion löschen</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Möchten Sie diese Transaktion wirklich löschen?</p>
|
||||
<div class="text-muted" id="delete-transaction-details">
|
||||
<small>Diese Aktion kann nicht rückgängig gemacht werden.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
||||
<form method="post" action="/portfolio/transaction/delete" style="display: inline;">
|
||||
<input type="hidden" name="activity_id" id="delete-activity-id"/>
|
||||
<input type="hidden" name="portfolio_id" value={ fmt.Sprintf("%d", portfolio.ID) }/>
|
||||
<button type="submit" class="btn btn-danger">Löschen</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
// Set today's date as default
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
document.getElementById('transaction-date').value = today;
|
||||
|
||||
// Handle edit button clicks
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.closest('[data-bs-target="#editTransactionModal"]')) {
|
||||
const button = e.target.closest('[data-bs-target="#editTransactionModal"]');
|
||||
|
||||
// Populate edit modal with transaction data
|
||||
document.getElementById('edit-activity-id').value = button.dataset.activityId;
|
||||
document.getElementById('edit-transaction-type').value = button.dataset.activityType;
|
||||
document.getElementById('edit-transaction-stock').value = button.dataset.activityStock;
|
||||
document.getElementById('edit-transaction-amount').value = button.dataset.activityAmount;
|
||||
document.getElementById('edit-transaction-price').value = button.dataset.activityPrice;
|
||||
document.getElementById('edit-transaction-date').value = button.dataset.activityDate;
|
||||
document.getElementById('edit-transaction-note').value = button.dataset.activityNote;
|
||||
}
|
||||
|
||||
// Handle delete button clicks
|
||||
if (e.target.closest('[data-bs-target="#deleteTransactionModal"]')) {
|
||||
const button = e.target.closest('[data-bs-target="#deleteTransactionModal"]');
|
||||
|
||||
// Populate delete modal with transaction data
|
||||
document.getElementById('delete-activity-id').value = button.dataset.activityId;
|
||||
|
||||
// Show transaction details in delete modal
|
||||
const details = document.getElementById('delete-transaction-details');
|
||||
details.innerHTML = `
|
||||
<small>
|
||||
<strong>${button.dataset.activityType}</strong> - ${button.dataset.activityStock}<br>
|
||||
${button.dataset.activityAmount} Stück am ${button.dataset.activityDate}
|
||||
</small>
|
||||
`;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
}
|
||||
|
||||
// Separate component for portfolio summary
|
||||
templ PortfolioSummary(activities []model.Activity, currency string) {
|
||||
<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>
|
||||
<div class="mb-3">
|
||||
<div class="text-muted">Gesamtwert investiert</div>
|
||||
<div class="h2 mb-0">
|
||||
{ formatTotalInvestedWithConversion(activities, currency) }
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="text-muted">Letzter Kauf</div>
|
||||
<div class="h4 mb-0 text-muted">
|
||||
{ getLastBuyDate(activities) }
|
||||
</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)
|
||||
}
|
||||
Reference in New Issue
Block a user