app is now using historical exchange rates for transactions
This commit is contained in:
+47
-5
@@ -6,6 +6,7 @@ import (
|
||||
"net/http"
|
||||
"portfolio-tracker/internal/model"
|
||||
"portfolio-tracker/internal/util"
|
||||
"time"
|
||||
)
|
||||
|
||||
func JsonEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -110,7 +111,26 @@ func ClearCurrencyCacheHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
response := map[string]string{
|
||||
"status": "success",
|
||||
"message": "Currency cache cleared successfully",
|
||||
"message": "Currency cache (including historical rates) cleared successfully",
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
// CleanExpiredCacheHandler removes expired cache entries
|
||||
func CleanExpiredCacheHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Nur POST erlaubt", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
removedCount := util.CleanExpiredCache()
|
||||
|
||||
response := map[string]interface{}{
|
||||
"status": "success",
|
||||
"message": "Expired cache entries cleaned",
|
||||
"removed_count": removedCount,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
@@ -124,12 +144,34 @@ func CurrencyCacheInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
cacheInfo := util.GetCacheInfo()
|
||||
detailedCacheInfo := util.GetDetailedCacheInfo()
|
||||
|
||||
// Separate historical and current rates
|
||||
historicalRates := make(map[string]interface{})
|
||||
currentRates := make(map[string]interface{})
|
||||
|
||||
for key, cached := range detailedCacheInfo {
|
||||
rateInfo := map[string]interface{}{
|
||||
"rate": cached.Rate,
|
||||
"timestamp": cached.Timestamp,
|
||||
"date": cached.Date,
|
||||
"age_hours": time.Since(cached.Timestamp).Hours(),
|
||||
}
|
||||
|
||||
if cached.Date == cached.Timestamp.Format("2006-01-02") {
|
||||
currentRates[key] = rateInfo
|
||||
} else {
|
||||
historicalRates[key] = rateInfo
|
||||
}
|
||||
}
|
||||
|
||||
response := map[string]interface{}{
|
||||
"status": "success",
|
||||
"cache_size": len(cacheInfo),
|
||||
"entries": cacheInfo,
|
||||
"status": "success",
|
||||
"total_entries": len(detailedCacheInfo),
|
||||
"historical_rates": historicalRates,
|
||||
"current_rates": currentRates,
|
||||
"historical_count": len(historicalRates),
|
||||
"current_count": len(currentRates),
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
+230
-16
@@ -17,32 +17,56 @@ type ExchangeRateResponse struct {
|
||||
Rates map[string]float64 `json:"rates"`
|
||||
}
|
||||
|
||||
// HistoricalExchangeRateResponse represents historical rate response
|
||||
type HistoricalExchangeRateResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Historical bool `json:"historical"`
|
||||
Base string `json:"base"`
|
||||
Date string `json:"date"`
|
||||
Rates map[string]float64 `json:"rates"`
|
||||
TimeLastUpdate int64 `json:"time_last_update_unix"`
|
||||
}
|
||||
|
||||
// CachedRate stores exchange rate with timestamp
|
||||
type CachedRate struct {
|
||||
Rate float64
|
||||
Timestamp time.Time
|
||||
Date string // The date this rate is for (YYYY-MM-DD format)
|
||||
}
|
||||
|
||||
// Currency conversion cache
|
||||
var (
|
||||
exchangeRateCache = make(map[string]CachedRate)
|
||||
cacheMutex sync.RWMutex
|
||||
cacheValidDuration = 1 * time.Hour // Cache rates for 1 hour
|
||||
cacheValidDuration = 24 * time.Hour // Cache historical rates for 24 hours
|
||||
currentRateCache = 1 * time.Hour // Cache current rates for 1 hour
|
||||
)
|
||||
|
||||
// GetExchangeRate fetches the exchange rate from fromCurrency to toCurrency
|
||||
// GetExchangeRate fetches the current exchange rate from fromCurrency to toCurrency
|
||||
func GetExchangeRate(fromCurrency, toCurrency string) (float64, error) {
|
||||
return GetHistoricalExchangeRate(fromCurrency, toCurrency, time.Now())
|
||||
}
|
||||
|
||||
// GetHistoricalExchangeRate fetches the exchange rate for a specific date
|
||||
func GetHistoricalExchangeRate(fromCurrency, toCurrency string, date time.Time) (float64, error) {
|
||||
// If currencies are the same, return 1.0
|
||||
if fromCurrency == toCurrency {
|
||||
return 1.0, nil
|
||||
}
|
||||
|
||||
cacheKey := fmt.Sprintf("%s_%s", fromCurrency, toCurrency)
|
||||
dateStr := date.Format("2006-01-02")
|
||||
cacheKey := fmt.Sprintf("%s_%s_%s", fromCurrency, toCurrency, dateStr)
|
||||
|
||||
// Check cache first
|
||||
cacheMutex.RLock()
|
||||
if cached, exists := exchangeRateCache[cacheKey]; exists {
|
||||
if time.Since(cached.Timestamp) < cacheValidDuration {
|
||||
// Determine cache validity based on whether it's historical or current
|
||||
validDuration := cacheValidDuration
|
||||
if isToday(date) {
|
||||
validDuration = currentRateCache
|
||||
}
|
||||
|
||||
if time.Since(cached.Timestamp) < validDuration {
|
||||
cacheMutex.RUnlock()
|
||||
return cached.Rate, nil
|
||||
}
|
||||
@@ -50,7 +74,7 @@ func GetExchangeRate(fromCurrency, toCurrency string) (float64, error) {
|
||||
cacheMutex.RUnlock()
|
||||
|
||||
// Fetch from API if not in cache or cache is expired
|
||||
rate, err := fetchExchangeRateFromAPI(fromCurrency, toCurrency)
|
||||
rate, err := fetchHistoricalExchangeRateFromAPI(fromCurrency, toCurrency, date)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -60,16 +84,27 @@ func GetExchangeRate(fromCurrency, toCurrency string) (float64, error) {
|
||||
exchangeRateCache[cacheKey] = CachedRate{
|
||||
Rate: rate,
|
||||
Timestamp: time.Now(),
|
||||
Date: dateStr,
|
||||
}
|
||||
cacheMutex.Unlock()
|
||||
|
||||
return rate, nil
|
||||
}
|
||||
|
||||
// fetchExchangeRateFromAPI fetches exchange rate from a free API
|
||||
func fetchExchangeRateFromAPI(fromCurrency, toCurrency string) (float64, error) {
|
||||
// Using exchangerate-api.com (completely free, no API key required)
|
||||
url := fmt.Sprintf("https://api.exchangerate-api.com/v4/latest/%s", fromCurrency)
|
||||
// fetchHistoricalExchangeRateFromAPI fetches exchange rate for a specific date
|
||||
func fetchHistoricalExchangeRateFromAPI(fromCurrency, toCurrency string, date time.Time) (float64, error) {
|
||||
var url string
|
||||
dateStr := date.Format("2006-01-02")
|
||||
today := time.Now().Format("2006-01-02")
|
||||
|
||||
// Use different endpoints for historical vs current rates
|
||||
if dateStr == today {
|
||||
// Current rates
|
||||
url = fmt.Sprintf("https://api.exchangerate-api.com/v4/latest/%s", fromCurrency)
|
||||
} else {
|
||||
// Historical rates - try exchangerate-api.com historical endpoint
|
||||
url = fmt.Sprintf("https://api.exchangerate-api.com/v4/historical/%s/%s", fromCurrency, dateStr)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
@@ -78,7 +113,7 @@ func fetchExchangeRateFromAPI(fromCurrency, toCurrency string) (float64, error)
|
||||
|
||||
req.Header.Set("User-Agent", "Portfolio-Tracker/1.0")
|
||||
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
client := &http.Client{Timeout: 15 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("error fetching exchange rate: %v", err)
|
||||
@@ -86,6 +121,10 @@ func fetchExchangeRateFromAPI(fromCurrency, toCurrency string) (float64, error)
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
// If historical API fails, try fallback strategy
|
||||
if dateStr != today {
|
||||
return fetchHistoricalRateFallback(fromCurrency, toCurrency, date)
|
||||
}
|
||||
return 0, fmt.Errorf("API returned status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
@@ -94,7 +133,7 @@ func fetchExchangeRateFromAPI(fromCurrency, toCurrency string) (float64, error)
|
||||
return 0, fmt.Errorf("error reading response: %v", err)
|
||||
}
|
||||
|
||||
// Parse exchangerate-api.com response format
|
||||
// Try parsing as standard response first
|
||||
var apiResp struct {
|
||||
Base string `json:"base"`
|
||||
Date string `json:"date"`
|
||||
@@ -113,13 +152,111 @@ func fetchExchangeRateFromAPI(fromCurrency, toCurrency string) (float64, error)
|
||||
return rate, nil
|
||||
}
|
||||
|
||||
// ConvertCurrency converts an amount from one currency to another
|
||||
// fetchHistoricalRateFallback tries alternative methods for historical rates
|
||||
func fetchHistoricalRateFallback(fromCurrency, toCurrency string, date time.Time) (float64, error) {
|
||||
// Try frankfurter.app as fallback for historical rates (free and reliable)
|
||||
dateStr := date.Format("2006-01-02")
|
||||
url := fmt.Sprintf("https://api.frankfurter.app/%s?from=%s&to=%s", dateStr, fromCurrency, toCurrency)
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("error creating fallback request: %v", err)
|
||||
}
|
||||
|
||||
req.Header.Set("User-Agent", "Portfolio-Tracker/1.0")
|
||||
|
||||
client := &http.Client{Timeout: 15 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
// If historical rate fails, use current rate as last resort
|
||||
return getCurrentRateAsFallback(fromCurrency, toCurrency)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return getCurrentRateAsFallback(fromCurrency, toCurrency)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return getCurrentRateAsFallback(fromCurrency, toCurrency)
|
||||
}
|
||||
|
||||
var fallbackResp struct {
|
||||
Amount float64 `json:"amount"`
|
||||
Base string `json:"base"`
|
||||
Date string `json:"date"`
|
||||
Rates map[string]float64 `json:"rates"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &fallbackResp); err != nil {
|
||||
return getCurrentRateAsFallback(fromCurrency, toCurrency)
|
||||
}
|
||||
|
||||
if rate, exists := fallbackResp.Rates[toCurrency]; exists {
|
||||
return rate, nil
|
||||
}
|
||||
|
||||
return getCurrentRateAsFallback(fromCurrency, toCurrency)
|
||||
}
|
||||
|
||||
// getCurrentRateAsFallback gets current rate when historical rate is unavailable
|
||||
func getCurrentRateAsFallback(fromCurrency, toCurrency string) (float64, error) {
|
||||
url := fmt.Sprintf("https://api.exchangerate-api.com/v4/latest/%s", fromCurrency)
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("error creating fallback request: %v", err)
|
||||
}
|
||||
|
||||
req.Header.Set("User-Agent", "Portfolio-Tracker/1.0")
|
||||
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("error fetching fallback rate: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return 0, fmt.Errorf("fallback API returned status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("error reading fallback response: %v", err)
|
||||
}
|
||||
|
||||
var apiResp struct {
|
||||
Base string `json:"base"`
|
||||
Date string `json:"date"`
|
||||
Rates map[string]float64 `json:"rates"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &apiResp); err != nil {
|
||||
return 0, fmt.Errorf("error parsing fallback JSON: %v", err)
|
||||
}
|
||||
|
||||
rate, exists := apiResp.Rates[toCurrency]
|
||||
if !exists {
|
||||
return 0, fmt.Errorf("currency %s not found in fallback response", toCurrency)
|
||||
}
|
||||
|
||||
return rate, nil
|
||||
}
|
||||
|
||||
// ConvertCurrency converts an amount from one currency to another using current rates
|
||||
func ConvertCurrency(amount float64, fromCurrency, toCurrency string) (float64, error) {
|
||||
return ConvertCurrencyHistorical(amount, fromCurrency, toCurrency, time.Now())
|
||||
}
|
||||
|
||||
// ConvertCurrencyHistorical converts an amount using historical exchange rate for a specific date
|
||||
func ConvertCurrencyHistorical(amount float64, fromCurrency, toCurrency string, date time.Time) (float64, error) {
|
||||
if fromCurrency == toCurrency {
|
||||
return amount, nil
|
||||
}
|
||||
|
||||
rate, err := GetExchangeRate(fromCurrency, toCurrency)
|
||||
rate, err := GetHistoricalExchangeRate(fromCurrency, toCurrency, date)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -178,11 +315,16 @@ func FormatCurrencyWithSymbol(amount float64, currency string) string {
|
||||
|
||||
// ConvertAndFormat converts currency and formats it nicely
|
||||
func ConvertAndFormat(amount float64, fromCurrency, toCurrency string) string {
|
||||
return ConvertAndFormatHistorical(amount, fromCurrency, toCurrency, time.Now())
|
||||
}
|
||||
|
||||
// ConvertAndFormatHistorical converts currency using historical rate and formats it
|
||||
func ConvertAndFormatHistorical(amount float64, fromCurrency, toCurrency string, date time.Time) string {
|
||||
if fromCurrency == toCurrency {
|
||||
return FormatCurrencyWithSymbol(amount, toCurrency)
|
||||
}
|
||||
|
||||
convertedAmount, err := ConvertCurrency(amount, fromCurrency, toCurrency)
|
||||
convertedAmount, err := ConvertCurrencyHistorical(amount, fromCurrency, toCurrency, date)
|
||||
if err != nil {
|
||||
// If conversion fails, return original amount with note
|
||||
return fmt.Sprintf("%.2f %s (conv. error)", amount, fromCurrency)
|
||||
@@ -191,13 +333,18 @@ func ConvertAndFormat(amount float64, fromCurrency, toCurrency string) string {
|
||||
return FormatCurrencyWithSymbol(convertedAmount, toCurrency)
|
||||
}
|
||||
|
||||
// BatchConvertCurrency converts multiple amounts in one API call for efficiency
|
||||
// BatchConvertCurrency converts multiple amounts in one API call for efficiency using current rates
|
||||
func BatchConvertCurrency(amounts []float64, fromCurrency, toCurrency string) ([]float64, error) {
|
||||
return BatchConvertCurrencyHistorical(amounts, fromCurrency, toCurrency, time.Now())
|
||||
}
|
||||
|
||||
// BatchConvertCurrencyHistorical converts multiple amounts using historical rate for a specific date
|
||||
func BatchConvertCurrencyHistorical(amounts []float64, fromCurrency, toCurrency string, date time.Time) ([]float64, error) {
|
||||
if fromCurrency == toCurrency {
|
||||
return amounts, nil
|
||||
}
|
||||
|
||||
rate, err := GetExchangeRate(fromCurrency, toCurrency)
|
||||
rate, err := GetHistoricalExchangeRate(fromCurrency, toCurrency, date)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -228,3 +375,70 @@ func GetCacheInfo() map[string]time.Time {
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
// GetDetailedCacheInfo returns detailed information about cached exchange rates
|
||||
func GetDetailedCacheInfo() map[string]CachedRate {
|
||||
cacheMutex.RLock()
|
||||
defer cacheMutex.RUnlock()
|
||||
|
||||
info := make(map[string]CachedRate)
|
||||
for key, cached := range exchangeRateCache {
|
||||
info[key] = cached
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
// CleanExpiredCache removes expired cache entries
|
||||
func CleanExpiredCache() int {
|
||||
cacheMutex.Lock()
|
||||
defer cacheMutex.Unlock()
|
||||
|
||||
removed := 0
|
||||
now := time.Now()
|
||||
|
||||
for key, cached := range exchangeRateCache {
|
||||
// Determine if this is a current or historical rate
|
||||
validDuration := cacheValidDuration
|
||||
if isToday(parseDate(cached.Date)) {
|
||||
validDuration = currentRateCache
|
||||
}
|
||||
|
||||
if now.Sub(cached.Timestamp) > validDuration {
|
||||
delete(exchangeRateCache, key)
|
||||
removed++
|
||||
}
|
||||
}
|
||||
|
||||
return removed
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
// isToday checks if a date is today
|
||||
func isToday(date time.Time) bool {
|
||||
now := time.Now()
|
||||
return date.Year() == now.Year() && date.Month() == now.Month() && date.Day() == now.Day()
|
||||
}
|
||||
|
||||
// parseDate parses a date string in YYYY-MM-DD format
|
||||
func parseDate(dateStr string) time.Time {
|
||||
date, err := time.Parse("2006-01-02", dateStr)
|
||||
if err != nil {
|
||||
return time.Time{}
|
||||
}
|
||||
return date
|
||||
}
|
||||
|
||||
// GetExchangeRateWithFallback gets exchange rate with multiple fallback strategies
|
||||
func GetExchangeRateWithFallback(fromCurrency, toCurrency string, date time.Time) (float64, bool, error) {
|
||||
rate, err := GetHistoricalExchangeRate(fromCurrency, toCurrency, date)
|
||||
if err != nil {
|
||||
// Try with current date as fallback
|
||||
currentRate, currentErr := GetExchangeRate(fromCurrency, toCurrency)
|
||||
if currentErr != nil {
|
||||
return 0, false, fmt.Errorf("both historical (%v) and current (%v) rate fetching failed", err, currentErr)
|
||||
}
|
||||
return currentRate, false, nil // false indicates fallback was used
|
||||
}
|
||||
return rate, true, nil // true indicates historical rate was used
|
||||
}
|
||||
|
||||
+223
-14
@@ -153,13 +153,15 @@ func TestGetCacheInfo(t *testing.T) {
|
||||
// Add test data to cache
|
||||
testTime := time.Now()
|
||||
cacheMutex.Lock()
|
||||
exchangeRateCache["EUR_USD"] = CachedRate{
|
||||
exchangeRateCache["EUR_USD_2024-01-01"] = CachedRate{
|
||||
Rate: 1.2,
|
||||
Timestamp: testTime,
|
||||
Date: "2024-01-01",
|
||||
}
|
||||
exchangeRateCache["GBP_EUR"] = CachedRate{
|
||||
exchangeRateCache["GBP_EUR_2024-01-01"] = CachedRate{
|
||||
Rate: 1.15,
|
||||
Timestamp: testTime,
|
||||
Date: "2024-01-01",
|
||||
}
|
||||
cacheMutex.Unlock()
|
||||
|
||||
@@ -169,14 +171,14 @@ func TestGetCacheInfo(t *testing.T) {
|
||||
t.Errorf("Expected 2 cache entries, got %d", len(info))
|
||||
}
|
||||
|
||||
if timestamp, exists := info["EUR_USD"]; !exists {
|
||||
t.Error("Expected EUR_USD entry in cache info")
|
||||
if timestamp, exists := info["EUR_USD_2024-01-01"]; !exists {
|
||||
t.Error("Expected EUR_USD_2024-01-01 entry in cache info")
|
||||
} else if !timestamp.Equal(testTime) {
|
||||
t.Error("Expected timestamp to match test time")
|
||||
}
|
||||
|
||||
if timestamp, exists := info["GBP_EUR"]; !exists {
|
||||
t.Error("Expected GBP_EUR entry in cache info")
|
||||
if timestamp, exists := info["GBP_EUR_2024-01-01"]; !exists {
|
||||
t.Error("Expected GBP_EUR_2024-01-01 entry in cache info")
|
||||
} else if !timestamp.Equal(testTime) {
|
||||
t.Error("Expected timestamp to match test time")
|
||||
}
|
||||
@@ -186,20 +188,227 @@ func TestGetCacheInfo(t *testing.T) {
|
||||
func TestCacheExpiration(t *testing.T) {
|
||||
ClearCache()
|
||||
|
||||
// Add expired entry to cache
|
||||
expiredTime := time.Now().Add(-2 * time.Hour) // 2 hours ago
|
||||
// Add expired entry to cache with a specific rate
|
||||
expiredTime := time.Now().Add(-25 * time.Hour) // 25 hours ago (older than 24h cache)
|
||||
testDate := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
cacheKey := "USD_EUR_2024-01-01"
|
||||
expiredRate := 999.999 // Unrealistic rate to detect if cache is used
|
||||
|
||||
cacheMutex.Lock()
|
||||
exchangeRateCache["TEST_EXPIRED"] = CachedRate{
|
||||
Rate: 1.0,
|
||||
exchangeRateCache[cacheKey] = CachedRate{
|
||||
Rate: expiredRate,
|
||||
Timestamp: expiredTime,
|
||||
Date: "2024-01-01",
|
||||
}
|
||||
cacheMutex.Unlock()
|
||||
|
||||
// This should not use the expired cache (but will fail API call)
|
||||
// We're mainly testing that it doesn't return the cached expired value
|
||||
_, err := GetExchangeRate("TEST", "EXPIRED")
|
||||
// Verify the expired entry exists
|
||||
cacheMutex.RLock()
|
||||
_, exists := exchangeRateCache[cacheKey]
|
||||
cacheMutex.RUnlock()
|
||||
|
||||
if !exists {
|
||||
t.Error("Expected expired cache entry to exist before test")
|
||||
return
|
||||
}
|
||||
|
||||
// This should not use the expired cache, it should try to fetch from API
|
||||
// Even if API fails, it should not return the expired cached rate
|
||||
rate, err := GetHistoricalExchangeRate("USD", "EUR", testDate)
|
||||
|
||||
// Either:
|
||||
// 1. We get a reasonable rate from API (not the cached unrealistic rate)
|
||||
// 2. We get an error (which is fine, means cache wasn't used)
|
||||
if err == nil {
|
||||
t.Error("Expected error due to invalid currency pair, but got none")
|
||||
// If we got a rate, it should not be the expired cached rate
|
||||
if rate == expiredRate {
|
||||
t.Errorf("Function returned expired cached rate %f, should have fetched from API", expiredRate)
|
||||
}
|
||||
// A reasonable EUR/USD rate should be between 0.5 and 2.0
|
||||
if rate < 0.5 || rate > 2.0 {
|
||||
t.Errorf("Got unreasonable exchange rate %f, might be using expired cache", rate)
|
||||
}
|
||||
}
|
||||
// If err != nil, that's fine - it means the API call was attempted and failed
|
||||
// The important thing is that the expired cache was not used
|
||||
}
|
||||
|
||||
// Test historical exchange rate functionality
|
||||
func TestGetHistoricalExchangeRate_SameCurrency(t *testing.T) {
|
||||
testDate := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
rate, err := GetHistoricalExchangeRate("USD", "USD", testDate)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error for same currency, got %v", err)
|
||||
}
|
||||
if rate != 1.0 {
|
||||
t.Errorf("Expected rate 1.0 for same currency, got %f", rate)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertCurrencyHistorical_SameCurrency(t *testing.T) {
|
||||
amount := 100.0
|
||||
testDate := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
converted, err := ConvertCurrencyHistorical(amount, "EUR", "EUR", testDate)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error for same currency conversion, got %v", err)
|
||||
}
|
||||
if converted != amount {
|
||||
t.Errorf("Expected %f for same currency conversion, got %f", amount, converted)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatchConvertCurrencyHistorical_SameCurrency(t *testing.T) {
|
||||
amounts := []float64{100.0, 200.0, 300.0}
|
||||
currency := "EUR"
|
||||
testDate := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
converted, err := BatchConvertCurrencyHistorical(amounts, currency, currency, testDate)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error for same currency batch conversion, got %v", err)
|
||||
}
|
||||
|
||||
if len(converted) != len(amounts) {
|
||||
t.Errorf("Expected %d converted amounts, got %d", len(amounts), len(converted))
|
||||
}
|
||||
|
||||
for i, amount := range amounts {
|
||||
if converted[i] != amount {
|
||||
t.Errorf("Expected converted[%d] = %f, got %f", i, amount, converted[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertAndFormatHistorical_SameCurrency(t *testing.T) {
|
||||
amount := 100.0
|
||||
currency := "USD"
|
||||
testDate := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
result := ConvertAndFormatHistorical(amount, currency, currency, testDate)
|
||||
expected := FormatCurrencyWithSymbol(amount, currency)
|
||||
|
||||
if result != expected {
|
||||
t.Errorf("ConvertAndFormatHistorical(%f, %s, %s, %v) = %s, expected %s",
|
||||
amount, currency, currency, testDate, result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetExchangeRateWithFallback_SameCurrency(t *testing.T) {
|
||||
testDate := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
rate, isHistorical, err := GetExchangeRateWithFallback("USD", "USD", testDate)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error for same currency, got %v", err)
|
||||
}
|
||||
if rate != 1.0 {
|
||||
t.Errorf("Expected rate 1.0 for same currency, got %f", rate)
|
||||
}
|
||||
if !isHistorical {
|
||||
t.Error("Expected isHistorical to be true for same currency")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanExpiredCache(t *testing.T) {
|
||||
// Clear cache first
|
||||
ClearCache()
|
||||
|
||||
// Add some test data to cache
|
||||
now := time.Now()
|
||||
expiredTime := now.Add(-25 * time.Hour) // Older than 24 hours
|
||||
recentTime := now.Add(-30 * time.Minute) // Recent
|
||||
|
||||
cacheMutex.Lock()
|
||||
exchangeRateCache["OLD_RATE_2024-01-01"] = CachedRate{
|
||||
Rate: 1.0,
|
||||
Timestamp: expiredTime,
|
||||
Date: "2024-01-01",
|
||||
}
|
||||
exchangeRateCache["NEW_RATE_"+now.Format("2006-01-02")] = CachedRate{
|
||||
Rate: 1.1,
|
||||
Timestamp: recentTime,
|
||||
Date: now.Format("2006-01-02"),
|
||||
}
|
||||
cacheMutex.Unlock()
|
||||
|
||||
// Clean expired cache
|
||||
removed := CleanExpiredCache()
|
||||
|
||||
// Should have removed the old entry
|
||||
if removed != 1 {
|
||||
t.Errorf("Expected 1 removed entry, got %d", removed)
|
||||
}
|
||||
|
||||
// Verify the recent entry is still there
|
||||
info := GetCacheInfo()
|
||||
if len(info) != 1 {
|
||||
t.Errorf("Expected 1 remaining cache entry, got %d", len(info))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDetailedCacheInfo(t *testing.T) {
|
||||
// Clear cache first
|
||||
ClearCache()
|
||||
|
||||
// Add test data to cache
|
||||
testTime := time.Now()
|
||||
cacheMutex.Lock()
|
||||
exchangeRateCache["EUR_USD_2024-01-01"] = CachedRate{
|
||||
Rate: 1.2,
|
||||
Timestamp: testTime,
|
||||
Date: "2024-01-01",
|
||||
}
|
||||
cacheMutex.Unlock()
|
||||
|
||||
info := GetDetailedCacheInfo()
|
||||
|
||||
if len(info) != 1 {
|
||||
t.Errorf("Expected 1 cache entry, got %d", len(info))
|
||||
}
|
||||
|
||||
if cached, exists := info["EUR_USD_2024-01-01"]; !exists {
|
||||
t.Error("Expected EUR_USD_2024-01-01 entry in detailed cache info")
|
||||
} else {
|
||||
if cached.Rate != 1.2 {
|
||||
t.Errorf("Expected rate 1.2, got %f", cached.Rate)
|
||||
}
|
||||
if cached.Date != "2024-01-01" {
|
||||
t.Errorf("Expected date 2024-01-01, got %s", cached.Date)
|
||||
}
|
||||
if !cached.Timestamp.Equal(testTime) {
|
||||
t.Error("Expected timestamp to match test time")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsToday(t *testing.T) {
|
||||
now := time.Now()
|
||||
yesterday := now.AddDate(0, 0, -1)
|
||||
tomorrow := now.AddDate(0, 0, 1)
|
||||
|
||||
if !isToday(now) {
|
||||
t.Error("Expected isToday(now) to be true")
|
||||
}
|
||||
|
||||
if isToday(yesterday) {
|
||||
t.Error("Expected isToday(yesterday) to be false")
|
||||
}
|
||||
|
||||
if isToday(tomorrow) {
|
||||
t.Error("Expected isToday(tomorrow) to be false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDate(t *testing.T) {
|
||||
testDate := "2024-01-01"
|
||||
expected := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
result := parseDate(testDate)
|
||||
|
||||
if !result.Equal(expected) {
|
||||
t.Errorf("Expected parsed date %v, got %v", expected, result)
|
||||
}
|
||||
|
||||
// Test invalid date
|
||||
invalidResult := parseDate("invalid-date")
|
||||
if !invalidResult.IsZero() {
|
||||
t.Error("Expected zero time for invalid date")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ func calculateTotalInvested(activities []model.Activity) float64 {
|
||||
return total
|
||||
}
|
||||
|
||||
// calculateTotalInvestedInBaseCurrency calculates total invested converted to portfolio base currency
|
||||
// calculateTotalInvestedInBaseCurrency calculates total invested converted to portfolio base currency using historical rates
|
||||
func calculateTotalInvestedInBaseCurrency(activities []model.Activity, baseCurrency string) float64 {
|
||||
var total float64 = 0
|
||||
for _, activity := range activities {
|
||||
@@ -28,10 +28,17 @@ func calculateTotalInvestedInBaseCurrency(activities []model.Activity, baseCurre
|
||||
if activity.Currency == baseCurrency {
|
||||
total += activityTotal
|
||||
} else {
|
||||
convertedTotal, err := util.ConvertCurrency(activityTotal, activity.Currency, baseCurrency)
|
||||
// Use historical exchange rate for the transaction date
|
||||
convertedTotal, err := util.ConvertCurrencyHistorical(activityTotal, activity.Currency, baseCurrency, activity.Date)
|
||||
if err != nil {
|
||||
// If conversion fails, add original amount (fallback)
|
||||
total += activityTotal
|
||||
// If historical conversion fails, try current rate as fallback
|
||||
fallbackTotal, fallbackErr := util.ConvertCurrency(activityTotal, activity.Currency, baseCurrency)
|
||||
if fallbackErr != nil {
|
||||
// If all conversions fail, add original amount (last resort)
|
||||
total += activityTotal
|
||||
} else {
|
||||
total += fallbackTotal
|
||||
}
|
||||
} else {
|
||||
total += convertedTotal
|
||||
}
|
||||
@@ -55,13 +62,13 @@ func getLastBuyDate(activities []model.Activity) string {
|
||||
return lastBuy.Format("02.01.2006")
|
||||
}
|
||||
|
||||
// convertActivityPrice converts activity price to portfolio base currency if different
|
||||
// convertActivityPrice converts activity price to portfolio base currency using historical rate
|
||||
func convertActivityPrice(activity model.Activity, portfolioBaseCurrency string) (float64, string, error) {
|
||||
if activity.Currency == portfolioBaseCurrency {
|
||||
return activity.Price, "", nil // No conversion needed
|
||||
}
|
||||
|
||||
convertedPrice, err := util.ConvertCurrency(activity.Price, activity.Currency, portfolioBaseCurrency)
|
||||
convertedPrice, err := util.ConvertCurrencyHistorical(activity.Price, activity.Currency, portfolioBaseCurrency, activity.Date)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
@@ -69,13 +76,13 @@ func convertActivityPrice(activity model.Activity, portfolioBaseCurrency string)
|
||||
return convertedPrice, portfolioBaseCurrency, nil
|
||||
}
|
||||
|
||||
// formatActivityPrice formats activity price with conversion if needed
|
||||
// formatActivityPrice formats activity price with conversion if needed using historical rates
|
||||
func formatActivityPrice(activity model.Activity, portfolioBaseCurrency string) string {
|
||||
if activity.Currency == portfolioBaseCurrency {
|
||||
return fmt.Sprintf("%.2f %s", activity.Price, activity.Currency)
|
||||
}
|
||||
|
||||
convertedPrice, err := util.ConvertCurrency(activity.Price, activity.Currency, portfolioBaseCurrency)
|
||||
convertedPrice, err := util.ConvertCurrencyHistorical(activity.Price, activity.Currency, portfolioBaseCurrency, activity.Date)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("%.2f %s (conv. error)", activity.Price, activity.Currency)
|
||||
}
|
||||
@@ -83,7 +90,7 @@ func formatActivityPrice(activity model.Activity, portfolioBaseCurrency string)
|
||||
return fmt.Sprintf("%.2f %s (~%.2f %s)", activity.Price, activity.Currency, convertedPrice, portfolioBaseCurrency)
|
||||
}
|
||||
|
||||
// formatActivityTotal formats activity total with conversion if needed
|
||||
// formatActivityTotal formats activity total with conversion if needed using historical rates
|
||||
func formatActivityTotal(activity model.Activity, portfolioBaseCurrency string) string {
|
||||
total := activity.Amount * activity.Price
|
||||
|
||||
@@ -91,7 +98,7 @@ func formatActivityTotal(activity model.Activity, portfolioBaseCurrency string)
|
||||
return fmt.Sprintf("%.2f %s", total, activity.Currency)
|
||||
}
|
||||
|
||||
convertedTotal, err := util.ConvertCurrency(total, activity.Currency, portfolioBaseCurrency)
|
||||
convertedTotal, err := util.ConvertCurrencyHistorical(total, activity.Currency, portfolioBaseCurrency, activity.Date)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("%.2f %s (conv. error)", total, activity.Currency)
|
||||
}
|
||||
@@ -99,28 +106,38 @@ func formatActivityTotal(activity model.Activity, portfolioBaseCurrency string)
|
||||
return fmt.Sprintf("%.2f %s (~%.2f %s)", total, activity.Currency, convertedTotal, portfolioBaseCurrency)
|
||||
}
|
||||
|
||||
// getConvertedPrice returns the converted price or original if conversion fails
|
||||
// getConvertedPrice returns the converted price using historical exchange rate or original if conversion fails
|
||||
func getConvertedPrice(activity model.Activity, portfolioBaseCurrency string) string {
|
||||
if activity.Currency == portfolioBaseCurrency {
|
||||
return ""
|
||||
}
|
||||
|
||||
convertedPrice, err := util.ConvertCurrency(activity.Price, activity.Currency, portfolioBaseCurrency)
|
||||
// Use historical exchange rate for the transaction date
|
||||
convertedPrice, isHistorical, err := util.GetExchangeRateWithFallback(activity.Currency, portfolioBaseCurrency, activity.Date)
|
||||
if err != nil {
|
||||
return "Conv. Error"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%.2f %s", convertedPrice, portfolioBaseCurrency)
|
||||
finalPrice := activity.Price * convertedPrice
|
||||
|
||||
// Add indicator if fallback (current) rate was used instead of historical
|
||||
if !isHistorical {
|
||||
return fmt.Sprintf("%.2f %s*", finalPrice, portfolioBaseCurrency)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%.2f %s", finalPrice, portfolioBaseCurrency)
|
||||
}
|
||||
|
||||
// getConvertedTotal returns the converted total or original if conversion fails
|
||||
// getConvertedTotal returns the converted total using historical exchange rate or original if conversion fails
|
||||
func getConvertedTotal(activity model.Activity, portfolioBaseCurrency string) string {
|
||||
if activity.Currency == portfolioBaseCurrency {
|
||||
return ""
|
||||
}
|
||||
|
||||
total := activity.Amount * activity.Price
|
||||
convertedTotal, err := util.ConvertCurrency(total, activity.Currency, portfolioBaseCurrency)
|
||||
|
||||
// Use historical exchange rate for the transaction date
|
||||
convertedTotal, err := util.ConvertCurrencyHistorical(total, activity.Currency, portfolioBaseCurrency, activity.Date)
|
||||
if err != nil {
|
||||
return "Conv. Error"
|
||||
}
|
||||
@@ -128,15 +145,47 @@ func getConvertedTotal(activity model.Activity, portfolioBaseCurrency string) st
|
||||
return fmt.Sprintf("%.2f %s", convertedTotal, portfolioBaseCurrency)
|
||||
}
|
||||
|
||||
// formatTotalInvestedWithConversion formats total invested with currency info
|
||||
// getConvertedTotalWithFallbackInfo returns the converted total with information about rate source
|
||||
func getConvertedTotalWithFallbackInfo(activity model.Activity, portfolioBaseCurrency string) string {
|
||||
if activity.Currency == portfolioBaseCurrency {
|
||||
return ""
|
||||
}
|
||||
|
||||
total := activity.Amount * activity.Price
|
||||
|
||||
// Use historical exchange rate with fallback information
|
||||
rate, isHistorical, err := util.GetExchangeRateWithFallback(activity.Currency, portfolioBaseCurrency, activity.Date)
|
||||
if err != nil {
|
||||
return "Conv. Error"
|
||||
}
|
||||
|
||||
convertedTotal := total * rate
|
||||
|
||||
// Add indicator if fallback (current) rate was used instead of historical
|
||||
if !isHistorical {
|
||||
return fmt.Sprintf("%.2f %s*", convertedTotal, portfolioBaseCurrency)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%.2f %s", convertedTotal, portfolioBaseCurrency)
|
||||
}
|
||||
|
||||
// formatTotalInvestedWithConversion formats total invested with currency info and conversion indicators
|
||||
func formatTotalInvestedWithConversion(activities []model.Activity, baseCurrency string) string {
|
||||
convertedTotal := calculateTotalInvestedInBaseCurrency(activities, baseCurrency)
|
||||
|
||||
// Check if any activities have different currencies
|
||||
hasDifferentCurrencies := false
|
||||
hasHistoricalConversions := false
|
||||
|
||||
for _, activity := range activities {
|
||||
if activity.Type == model.Buy && activity.Currency != baseCurrency {
|
||||
hasDifferentCurrencies = true
|
||||
|
||||
// Check if we can get historical rate for this transaction
|
||||
_, isHistorical, err := util.GetExchangeRateWithFallback(activity.Currency, baseCurrency, activity.Date)
|
||||
if err == nil && isHistorical {
|
||||
hasHistoricalConversions = true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -145,5 +194,120 @@ func formatTotalInvestedWithConversion(activities []model.Activity, baseCurrency
|
||||
return fmt.Sprintf("%.2f %s", convertedTotal, baseCurrency)
|
||||
}
|
||||
|
||||
if hasHistoricalConversions {
|
||||
return fmt.Sprintf("%.2f %s (historical rates)", convertedTotal, baseCurrency)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%.2f %s (converted)", convertedTotal, baseCurrency)
|
||||
}
|
||||
|
||||
// calculateTotalInvestedByDate calculates total invested up to a specific date
|
||||
func calculateTotalInvestedByDate(activities []model.Activity, baseCurrency string, endDate time.Time) float64 {
|
||||
var total float64 = 0
|
||||
for _, activity := range activities {
|
||||
if activity.Type == model.Buy && activity.Date.Before(endDate.AddDate(0, 0, 1)) { // Include transactions on endDate
|
||||
activityTotal := activity.Amount * activity.Price
|
||||
|
||||
if activity.Currency == baseCurrency {
|
||||
total += activityTotal
|
||||
} else {
|
||||
// Use historical exchange rate for the transaction date
|
||||
convertedTotal, err := util.ConvertCurrencyHistorical(activityTotal, activity.Currency, baseCurrency, activity.Date)
|
||||
if err != nil {
|
||||
// If historical conversion fails, try current rate as fallback
|
||||
fallbackTotal, fallbackErr := util.ConvertCurrency(activityTotal, activity.Currency, baseCurrency)
|
||||
if fallbackErr != nil {
|
||||
// If all conversions fail, add original amount (last resort)
|
||||
total += activityTotal
|
||||
} else {
|
||||
total += fallbackTotal
|
||||
}
|
||||
} else {
|
||||
total += convertedTotal
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// getConversionInfo returns information about currency conversions used in the portfolio
|
||||
func getConversionInfo(activities []model.Activity, baseCurrency string) map[string]interface{} {
|
||||
info := map[string]interface{}{
|
||||
"hasDifferentCurrencies": false,
|
||||
"historicalConversions": 0,
|
||||
"fallbackConversions": 0,
|
||||
"errorConversions": 0,
|
||||
"currencies": make(map[string]int),
|
||||
}
|
||||
|
||||
currencies := make(map[string]int)
|
||||
historicalCount := 0
|
||||
fallbackCount := 0
|
||||
errorCount := 0
|
||||
|
||||
for _, activity := range activities {
|
||||
if activity.Type == model.Buy {
|
||||
currencies[activity.Currency]++
|
||||
|
||||
if activity.Currency != baseCurrency {
|
||||
info["hasDifferentCurrencies"] = true
|
||||
|
||||
// Check conversion status
|
||||
_, isHistorical, err := util.GetExchangeRateWithFallback(activity.Currency, baseCurrency, activity.Date)
|
||||
if err != nil {
|
||||
errorCount++
|
||||
} else if isHistorical {
|
||||
historicalCount++
|
||||
} else {
|
||||
fallbackCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info["historicalConversions"] = historicalCount
|
||||
info["fallbackConversions"] = fallbackCount
|
||||
info["errorConversions"] = errorCount
|
||||
info["currencies"] = currencies
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
// formatCurrencyWithConversionNote formats currency amount with appropriate conversion notes
|
||||
func formatCurrencyWithConversionNote(amount float64, fromCurrency, toCurrency string, transactionDate time.Time) string {
|
||||
if fromCurrency == toCurrency {
|
||||
return util.FormatCurrencyWithSymbol(amount, toCurrency)
|
||||
}
|
||||
|
||||
rate, isHistorical, err := util.GetExchangeRateWithFallback(fromCurrency, toCurrency, transactionDate)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("%.2f %s (conv. error)", amount, fromCurrency)
|
||||
}
|
||||
|
||||
convertedAmount := amount * rate
|
||||
|
||||
if isHistorical {
|
||||
return fmt.Sprintf("%.2f %s (historical rate)", convertedAmount, toCurrency)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%.2f %s (current rate)", convertedAmount, toCurrency)
|
||||
}
|
||||
|
||||
// getActivityConversionStatus returns the conversion status for a specific activity
|
||||
func getActivityConversionStatus(activity model.Activity, portfolioBaseCurrency string) string {
|
||||
if activity.Currency == portfolioBaseCurrency {
|
||||
return "same_currency"
|
||||
}
|
||||
|
||||
_, isHistorical, err := util.GetExchangeRateWithFallback(activity.Currency, portfolioBaseCurrency, activity.Date)
|
||||
if err != nil {
|
||||
return "error"
|
||||
}
|
||||
|
||||
if isHistorical {
|
||||
return "historical"
|
||||
}
|
||||
|
||||
return "fallback"
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ templ PortfolioDetailContent(portfolio model.Portfolio) {
|
||||
<td>{ fmt.Sprintf("%.2f %s", activity.Amount * activity.Price, activity.Currency) }</td>
|
||||
<td>
|
||||
if activity.Currency != portfolio.BaseCurrency {
|
||||
{ getConvertedTotal(activity, portfolio.BaseCurrency) }
|
||||
{ getConvertedTotalWithFallbackInfo(activity, portfolio.BaseCurrency) }
|
||||
} else {
|
||||
<span class="text-muted">-</span>
|
||||
}
|
||||
@@ -194,10 +194,10 @@ templ PortfolioDetailContent(portfolio model.Portfolio) {
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-fill">
|
||||
<div class="font-weight-medium">Währungsumrechnung</div>
|
||||
<div class="font-weight-medium">Historische Währungsumrechnung</div>
|
||||
<div class="text-muted small">
|
||||
Transaktionen in anderen Währungen werden automatisch in { portfolio.BaseCurrency } umgerechnet.
|
||||
Wechselkurse werden stündlich aktualisiert.
|
||||
Transaktionen werden mit den historischen Wechselkursen vom Transaktionsdatum in { portfolio.BaseCurrency } umgerechnet.
|
||||
Bei nicht verfügbaren historischen Kursen werden aktuelle Kurse verwendet (markiert mit *).
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -408,6 +408,9 @@ templ PortfolioSummary(activities []model.Activity, currency string) {
|
||||
<div class="h2 mb-0">
|
||||
{ formatTotalInvestedWithConversion(activities, currency) }
|
||||
</div>
|
||||
<div class="text-muted small">
|
||||
* = Aktueller Kurs verwendet (historischer Kurs nicht verfügbar)
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="text-muted">Letzter Kauf</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.2.778
|
||||
// templ: version: v0.3.906
|
||||
package templates
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
@@ -35,33 +35,33 @@ func PortfolioDetailContent(portfolio model.Portfolio) templ.Component {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<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\">")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<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\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 string
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(portfolio.Name)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 14, Col: 44}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 14, Col: 44}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" (")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " (")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(portfolio.BaseCurrency)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 14, Col: 72}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 14, Col: 72}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(")</h2><div class=\"page-subtitle\">")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, ")</h2><div class=\"page-subtitle\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@@ -69,175 +69,179 @@ func PortfolioDetailContent(portfolio model.Portfolio) templ.Component {
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(portfolio.Description)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 17, Col: 30}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 17, Col: 30}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("Erstellt am ")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "Erstellt am ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(portfolio.CreatedAt.Format("02.01.2006"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 19, Col: 61}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 19, Col: 61}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</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>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</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 templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if len(portfolio.Activities) > 0 {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<tr><td colspan=\"6\" class=\"text-center text-muted py-4\">Position calculation will be implemented here</td></tr>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<tr><td colspan=\"6\" class=\"text-center text-muted py-4\">Position calculation will be implemented here</td></tr>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<tr><td colspan=\"6\" class=\"text-center text-muted py-4\">Keine Positionen vorhanden. Fügen Sie Ihre erste Transaktion hinzu.</td></tr>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<tr><td colspan=\"6\" class=\"text-center text-muted py-4\">Keine Positionen vorhanden. Fügen Sie Ihre erste Transaktion hinzu.</td></tr>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</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 (")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</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 (")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(portfolio.BaseCurrency)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 91, Col: 45}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 91, Col: 45}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(")</th><th>Gesamt</th><th>Gesamt (")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, ")</th><th>Gesamt</th><th>Gesamt (")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(portfolio.BaseCurrency)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 93, Col: 46}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 93, Col: 46}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(")</th><th></th></tr></thead> <tbody>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, ")</th><th></th></tr></thead> <tbody>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if len(portfolio.Activities) > 0 {
|
||||
for i, activity := range portfolio.Activities {
|
||||
if i < 10 {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<tr><td>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<tr><td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(activity.Date.Format("02.01.2006"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 102, Col: 53}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 102, Col: 53}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</td><td>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</td><td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if activity.Type == "BUY" {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<span class=\"badge bg-green\">Kauf</span>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<span class=\"badge bg-green\">Kauf</span>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else if activity.Type == "SELL" {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<span class=\"badge bg-red\">Verkauf</span>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<span class=\"badge bg-red\">Verkauf</span>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else if activity.Type == "DIVIDEND" {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<span class=\"badge bg-blue\">Dividende</span>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<span class=\"badge bg-blue\">Dividende</span>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<span class=\"badge bg-secondary\">")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<span class=\"badge bg-secondary\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(string(activity.Type))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 111, Col: 71}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 111, Col: 71}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</span>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</td><td><a href=\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</td><td><a href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var10 templ.SafeURL = templ.URL("/details?stock=" + activity.Stock)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var10)))
|
||||
var templ_7745c5c3_Var10 templ.SafeURL
|
||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL("/details?stock=" + activity.Stock))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 115, Col: 69}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" class=\"text-decoration-none\">")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "\" class=\"text-decoration-none\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var11 string
|
||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(activity.Stock)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 116, Col: 31}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 116, Col: 31}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></td><td>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</a></td><td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var12 string
|
||||
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.3f", activity.Amount))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 119, Col: 55}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 119, Col: 55}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</td><td>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</td><td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var13 string
|
||||
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.2f %s", activity.Price, activity.Currency))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 120, Col: 76}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 120, Col: 76}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</td><td>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</td><td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@@ -245,233 +249,233 @@ func PortfolioDetailContent(portfolio model.Portfolio) templ.Component {
|
||||
var templ_7745c5c3_Var14 string
|
||||
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(getConvertedPrice(activity, portfolio.BaseCurrency))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 123, Col: 68}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 123, Col: 68}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<span class=\"text-muted\">-</span>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<span class=\"text-muted\">-</span>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</td><td>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</td><td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var15 string
|
||||
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.2f %s", activity.Amount*activity.Price, activity.Currency))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 128, Col: 94}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 128, Col: 94}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</td><td>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</td><td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if activity.Currency != portfolio.BaseCurrency {
|
||||
var templ_7745c5c3_Var16 string
|
||||
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(getConvertedTotal(activity, portfolio.BaseCurrency))
|
||||
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(getConvertedTotalWithFallbackInfo(activity, portfolio.BaseCurrency))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 131, Col: 68}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 131, Col: 84}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<span class=\"text-muted\">-</span>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<span class=\"text-muted\">-</span>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</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=\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "</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=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var17 string
|
||||
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", activity.ID))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 142, Col: 65}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 142, Col: 65}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" data-activity-type=\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "\" data-activity-type=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var18 string
|
||||
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(string(activity.Type))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 143, Col: 58}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 143, Col: 58}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" data-activity-stock=\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "\" data-activity-stock=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var19 string
|
||||
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(activity.Stock)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 144, Col: 52}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 144, Col: 52}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" data-activity-amount=\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "\" data-activity-amount=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var20 string
|
||||
templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.3f", activity.Amount))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 145, Col: 75}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 145, Col: 75}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" data-activity-price=\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "\" data-activity-price=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var21 string
|
||||
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.2f", activity.Price))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 146, Col: 73}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 146, Col: 73}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" data-activity-date=\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "\" data-activity-date=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var22 string
|
||||
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(activity.Date.Format("2006-01-02"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 147, Col: 71}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 147, Col: 71}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" data-activity-note=\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "\" data-activity-note=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var23 string
|
||||
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(activity.Note)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 148, Col: 50}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 148, Col: 50}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Bearbeiten</button> <button class=\"btn btn-sm btn-outline-danger\" data-bs-toggle=\"modal\" data-bs-target=\"#deleteTransactionModal\" data-activity-id=\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "\">Bearbeiten</button> <button class=\"btn btn-sm btn-outline-danger\" data-bs-toggle=\"modal\" data-bs-target=\"#deleteTransactionModal\" data-activity-id=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var24 string
|
||||
templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", activity.ID))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 156, Col: 65}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 156, Col: 65}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" data-activity-stock=\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "\" data-activity-stock=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var25 string
|
||||
templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(activity.Stock)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 157, Col: 52}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 157, Col: 52}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" data-activity-type=\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "\" data-activity-type=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var26 string
|
||||
templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(string(activity.Type))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 158, Col: 58}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 158, Col: 58}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" data-activity-amount=\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "\" data-activity-amount=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var27 string
|
||||
templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.3f", activity.Amount))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 159, Col: 75}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 159, Col: 75}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" data-activity-date=\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "\" data-activity-date=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var28 string
|
||||
templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(activity.Date.Format("02.01.2006"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 160, Col: 71}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 160, Col: 71}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Löschen</button></div></td></tr>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "\">Löschen</button></div></td></tr>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<tr><td colspan=\"9\" class=\"text-center text-muted py-3\">Keine Transaktionen vorhanden</td></tr>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "<tr><td colspan=\"9\" class=\"text-center text-muted py-3\">Keine Transaktionen vorhanden</td></tr>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</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 ")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "</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\">Historische Währungsumrechnung</div><div class=\"text-muted small\">Transaktionen werden mit den historischen Wechselkursen vom Transaktionsdatum in ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var29 string
|
||||
templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(portfolio.BaseCurrency)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 199, Col: 91}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 199, Col: 114}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" 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\">")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, " umgerechnet. Bei nicht verfügbaren historischen Kursen werden aktuelle Kurse verwendet (markiert mit *).</div></div></div></div></div><div class=\"card\"><div class=\"card-header\"><h3 class=\"card-title\">Portfolio-Zusammenfassung</h3></div><div class=\"card-body\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@@ -479,76 +483,76 @@ func PortfolioDetailContent(portfolio model.Portfolio) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</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=\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "</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=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var30 string
|
||||
templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", portfolio.ID))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 235, Col: 85}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 235, Col: 85}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><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\">")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "\"><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\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var31 string
|
||||
templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(portfolio.BaseCurrency)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 258, Col: 63}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 258, Col: 63}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</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=\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "</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=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var32 string
|
||||
templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", portfolio.ID))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 288, Col: 85}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 288, Col: 85}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><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\">")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "\"><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\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var33 string
|
||||
templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(portfolio.BaseCurrency)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 311, Col: 63}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 311, Col: 63}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</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=\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "</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=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var34 string
|
||||
templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", portfolio.ID))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 349, Col: 86}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 349, Col: 86}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"> <button type=\"submit\" class=\"btn btn-danger\">Löschen</button></form></div></div></div></div><script>\n\t\t// Set today's date as default\n\t\tdocument.addEventListener('DOMContentLoaded', function() {\n\t\t\tconst today = new Date().toISOString().split('T')[0];\n\t\t\tdocument.getElementById('transaction-date').value = today;\n\n\t\t\t// Handle edit button clicks\n\t\t\tdocument.addEventListener('click', function(e) {\n\t\t\t\tif (e.target.closest('[data-bs-target=\"#editTransactionModal\"]')) {\n\t\t\t\t\tconst button = e.target.closest('[data-bs-target=\"#editTransactionModal\"]');\n\n\t\t\t\t\t// Populate edit modal with transaction data\n\t\t\t\t\tdocument.getElementById('edit-activity-id').value = button.dataset.activityId;\n\t\t\t\t\tdocument.getElementById('edit-transaction-type').value = button.dataset.activityType;\n\t\t\t\t\tdocument.getElementById('edit-transaction-stock').value = button.dataset.activityStock;\n\t\t\t\t\tdocument.getElementById('edit-transaction-amount').value = button.dataset.activityAmount;\n\t\t\t\t\tdocument.getElementById('edit-transaction-price').value = button.dataset.activityPrice;\n\t\t\t\t\tdocument.getElementById('edit-transaction-date').value = button.dataset.activityDate;\n\t\t\t\t\tdocument.getElementById('edit-transaction-note').value = button.dataset.activityNote;\n\t\t\t\t}\n\n\t\t\t\t// Handle delete button clicks\n\t\t\t\tif (e.target.closest('[data-bs-target=\"#deleteTransactionModal\"]')) {\n\t\t\t\t\tconst button = e.target.closest('[data-bs-target=\"#deleteTransactionModal\"]');\n\n\t\t\t\t\t// Populate delete modal with transaction data\n\t\t\t\t\tdocument.getElementById('delete-activity-id').value = button.dataset.activityId;\n\n\t\t\t\t\t// Show transaction details in delete modal\n\t\t\t\t\tconst details = document.getElementById('delete-transaction-details');\n\t\t\t\t\tdetails.innerHTML = `\n\t\t\t\t\t\t<small>\n\t\t\t\t\t\t\t<strong>${button.dataset.activityType}</strong> - ${button.dataset.activityStock}<br>\n\t\t\t\t\t\t\t${button.dataset.activityAmount} Stück am ${button.dataset.activityDate}\n\t\t\t\t\t\t</small>\n\t\t\t\t\t`;\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t</script>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "\"> <button type=\"submit\" class=\"btn btn-danger\">Löschen</button></form></div></div></div></div><script>\n\t\t// Set today's date as default\n\t\tdocument.addEventListener('DOMContentLoaded', function() {\n\t\t\tconst today = new Date().toISOString().split('T')[0];\n\t\t\tdocument.getElementById('transaction-date').value = today;\n\n\t\t\t// Handle edit button clicks\n\t\t\tdocument.addEventListener('click', function(e) {\n\t\t\t\tif (e.target.closest('[data-bs-target=\"#editTransactionModal\"]')) {\n\t\t\t\t\tconst button = e.target.closest('[data-bs-target=\"#editTransactionModal\"]');\n\n\t\t\t\t\t// Populate edit modal with transaction data\n\t\t\t\t\tdocument.getElementById('edit-activity-id').value = button.dataset.activityId;\n\t\t\t\t\tdocument.getElementById('edit-transaction-type').value = button.dataset.activityType;\n\t\t\t\t\tdocument.getElementById('edit-transaction-stock').value = button.dataset.activityStock;\n\t\t\t\t\tdocument.getElementById('edit-transaction-amount').value = button.dataset.activityAmount;\n\t\t\t\t\tdocument.getElementById('edit-transaction-price').value = button.dataset.activityPrice;\n\t\t\t\t\tdocument.getElementById('edit-transaction-date').value = button.dataset.activityDate;\n\t\t\t\t\tdocument.getElementById('edit-transaction-note').value = button.dataset.activityNote;\n\t\t\t\t}\n\n\t\t\t\t// Handle delete button clicks\n\t\t\t\tif (e.target.closest('[data-bs-target=\"#deleteTransactionModal\"]')) {\n\t\t\t\t\tconst button = e.target.closest('[data-bs-target=\"#deleteTransactionModal\"]');\n\n\t\t\t\t\t// Populate delete modal with transaction data\n\t\t\t\t\tdocument.getElementById('delete-activity-id').value = button.dataset.activityId;\n\n\t\t\t\t\t// Show transaction details in delete modal\n\t\t\t\t\tconst details = document.getElementById('delete-transaction-details');\n\t\t\t\t\tdetails.innerHTML = `\n\t\t\t\t\t\t<small>\n\t\t\t\t\t\t\t<strong>${button.dataset.activityType}</strong> - ${button.dataset.activityStock}<br>\n\t\t\t\t\t\t\t${button.dataset.activityAmount} Stück am ${button.dataset.activityDate}\n\t\t\t\t\t\t</small>\n\t\t\t\t\t`;\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t</script>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -574,50 +578,50 @@ func PortfolioSummary(activities []model.Activity, currency string) templ.Compon
|
||||
templ_7745c5c3_Var35 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"row\"><div class=\"col-12\"><div class=\"mb-3\"><div class=\"text-muted\">Anzahl Transaktionen</div><div class=\"h3 mb-0\">")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "<div class=\"row\"><div class=\"col-12\"><div class=\"mb-3\"><div class=\"text-muted\">Anzahl Transaktionen</div><div class=\"h3 mb-0\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var36 string
|
||||
templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", len(activities)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 404, Col: 61}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 404, Col: 61}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div><div class=\"mb-3\"><div class=\"text-muted\">Gesamtwert investiert</div><div class=\"h2 mb-0\">")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "</div></div><div class=\"mb-3\"><div class=\"text-muted\">Gesamtwert investiert</div><div class=\"h2 mb-0\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var37 string
|
||||
templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(formatTotalInvestedWithConversion(activities, currency))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 409, Col: 62}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 409, Col: 62}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div><div class=\"mb-3\"><div class=\"text-muted\">Letzter Kauf</div><div class=\"h4 mb-0 text-muted\">")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "</div><div class=\"text-muted small\">* = Aktueller Kurs verwendet (historischer Kurs nicht verfügbar)</div></div><div class=\"mb-3\"><div class=\"text-muted\">Letzter Kauf</div><div class=\"h4 mb-0 text-muted\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var38 string
|
||||
templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(getLastBuyDate(activities))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/portfolio-detail.templ`, Line: 415, Col: 33}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `portfolio-detail.templ`, Line: 418, Col: 33}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div></div></div>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "</div></div></div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -646,7 +650,7 @@ func PortfolioDetail(authenticated bool, username string, portfolio model.Portfo
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user