first commit

This commit is contained in:
Matthias Hinrichs
2025-07-05 03:10:41 +02:00
commit 9b7bdcbc53
39 changed files with 5109 additions and 0 deletions
+300
View File
@@ -0,0 +1,300 @@
package templates
import (
"fmt"
"portfolio-tracker/internal/model"
"portfolio-tracker/internal/web/templates/components"
)
templ StockDetailsContent(stock string, data model.YahooChartResponse, portfolios []model.Portfolio) {
<div class="page-header d-print-none" aria-label="Page header">
<div class="container-fluid">
<div class="row g-2 align-items-center">
<div class="col">
<div class="page-pretitle">{ data.Chart.Result[0].Meta.Symbol }</div>
<h2 class="page-title">{ data.Chart.Result[0].Meta.LongName }</h2>
</div>
<div class="col-auto ms-auto d-print-none">
<div class="btn-list">
<span class="d-none d-sm-inline">
<a href="/portfolio" class="btn btn-outline-primary">Zum Portfolio</a>
</span>
<button type="button" class="btn btn-primary btn-5 d-none d-sm-inline-block" data-bs-toggle="modal" data-bs-target="#addToPortfolioModal">
<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>
Zu Portfolio hinzufügen
</button>
<button type="button" class="btn btn-primary btn-6 d-sm-none btn-icon" data-bs-toggle="modal" data-bs-target="#addToPortfolioModal" aria-label="Zu Portfolio hinzufügen">
<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>
</button>
</div>
</div>
</div>
</div>
</div>
<div class="container-fluid mt-4">
<div class="row">
<!-- Linke Spalte: Charts -->
<div class="col-md-8 col-12">
<div class="card mb-4">
<div class="card-header">
<h1 class="card-title">Kursentwicklung</h1>
</div>
<div class="card-body">
<div id="chart"></div>
</div>
</div>
<div class="card">
<div class="card-header">
<h1 class="card-title">Dividenden</h1>
</div>
<div class="card-body">
<div id="chart2"></div>
</div>
</div>
</div>
<!-- Rechte Spalte: Details -->
<div class="col-md-4 col-12">
<div class="card">
<div class="card-header">
<h2 class="card-title">Details</h2>
</div>
<div class="card-body">
<ul class="list-unstyled">
<li><strong>Name:</strong> { data.Chart.Result[0].Meta.LongName }</li>
<li><strong>Symbol:</strong> { data.Chart.Result[0].Meta.Symbol }</li>
<li><strong>Währung:</strong> { data.Chart.Result[0].Meta.Currency }</li>
<li><strong>Börse:</strong> { data.Chart.Result[0].Meta.Exchange }</li>
<li><strong>Zeitzone:</strong> { data.Chart.Result[0].Meta.Timezone }</li>
<li><strong>Aktueller Preis:</strong> { data.Chart.Result[0].Meta.RegularMarketPrice }</li>
<li><strong>Instrumenttyp:</strong> { data.Chart.Result[0].Meta.Instrument }</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- Add to Portfolio Modal -->
<div class="modal modal-blur fade" id="addToPortfolioModal" tabindex="-1" aria-labelledby="addToPortfolioModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="addToPortfolioModalLabel">{ data.Chart.Result[0].Meta.Symbol } zu Portfolio 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">
<div class="modal-body">
<div class="mb-3">
<label for="portfolio-select" class="form-label">Portfolio</label>
<select class="form-select" id="portfolio-select" name="portfolio_id" required>
<option value="">Wählen Sie ein Portfolio</option>
for _, portfolio := range portfolios {
<option value={ fmt.Sprintf("%d", portfolio.ID) }>
{ portfolio.Name } ({ portfolio.BaseCurrency })
</option>
}
</select>
</div>
<div class="mb-3">
<label for="transaction-type" class="form-label">Transaktionstyp</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" value={ data.Chart.Result[0].Meta.Symbol } readonly/>
<div class="form-text">{ data.Chart.Result[0].Meta.LongName }</div>
</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 pro Aktie</label>
<div class="input-group">
<input type="number" class="form-control" id="transaction-price" name="price" step="0.01" min="0" value={ data.Chart.Result[0].Meta.RegularMarketPrice } required/>
<span class="input-group-text">{ data.Chart.Result[0].Meta.Currency }</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" placeholder="z.B. Grund für Kauf/Verkauf"></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">Zu Portfolio hinzufügen</button>
</div>
</form>
</div>
</div>
</div>
<!-- Inline Scripts for Charts -->
<script>
// Stock symbol for API calls
const stockSymbol = {{ data.Chart.Result[0].Meta.Symbol }};
// Set today's date as default
document.addEventListener('DOMContentLoaded', function() {
const today = new Date().toISOString().split('T')[0];
document.getElementById('transaction-date').value = today;
// First Chart: Stock Price
fetch('/api/yahoo?stock=' + encodeURIComponent(stockSymbol))
.then(response => response.json())
.then(data => {
console.log('Stock data received:', data);
if (data.chart && data.chart.result && data.chart.result.length > 0) {
const result = data.chart.result[0];
const timestamps = result.timestamp.map(ts =>
new Date(ts * 1000).toISOString().slice(0, 10)
);
const closes = result.indicators.quote[0].close.map(v =>
v !== null ? Math.round(v * 10000) / 10000 : null
);
const options = {
chart: {
type: 'area',
height: 350,
toolbar: {
show: true
}
},
dataLabels: {
enabled: false
},
series: [{
name: 'Kurs',
data: closes
}],
xaxis: {
categories: timestamps,
type: 'datetime'
},
yaxis: {
labels: {
formatter: function(value) {
return value ? value.toFixed(2) : '';
}
}
},
tooltip: {
x: {
format: 'dd MMM yyyy'
},
y: {
formatter: function(value) {
return value ? value.toFixed(2) : '';
}
}
}
};
const chart = new ApexCharts(document.querySelector("#chart"), options);
chart.render();
}
})
.catch(error => {
console.error('Error fetching stock data:', error);
document.querySelector("#chart").innerHTML = '<p>Fehler beim Laden der Kursdaten.</p>';
});
// Second Chart: Dividends
fetch('/api/yahoomaxdividends?stock=' + encodeURIComponent(stockSymbol))
.then(response => response.json())
.then(data => {
console.log('Dividend data received:', data);
console.log('Data type:', typeof data);
console.log('Data length:', data ? data.length : 'no length');
if (data && Array.isArray(data) && data.length > 0) {
console.log('First dividend entry:', data[0]);
// Convert the data to the format ApexCharts expects
const dividendData = data.map(item => ({
x: new Date(item[0]),
y: item[1]
}));
console.log('Processed dividend data:', dividendData);
const options2 = {
chart: {
type: 'bar',
height: 350,
toolbar: {
show: true
}
},
dataLabels: {
enabled: false,
formatter: function(val) {
return val.toFixed(2);
}
},
series: [{
name: 'Dividende',
data: dividendData
}],
xaxis: {
type: 'datetime',
title: {
text: 'Datum'
}
},
yaxis: {
title: {
text: 'Dividende'
},
labels: {
formatter: function(value) {
return value ? value.toFixed(2) : '';
}
}
},
tooltip: {
x: {
format: 'dd MMM yyyy'
},
y: {
formatter: function(value) {
return value ? value.toFixed(2) : '';
}
}
}
};
const chart2 = new ApexCharts(document.querySelector("#chart2"), options2);
chart2.render();
} else {
console.log('No dividend data available or data is empty');
document.querySelector("#chart2").innerHTML = '<p>Keine Dividendendaten verfügbar für dieses Wertpapier.</p>';
}
})
.catch(error => {
console.error('Error fetching dividend data:', error);
document.querySelector("#chart2").innerHTML = '<p>Fehler beim Laden der Dividendendaten.</p>';
});
});
</script>
}
templ StockDetails(authenticated bool, username string, stock string, data model.YahooChartResponse, portfolios []model.Portfolio) {
@components.PageLayout(authenticated, username, data.Chart.Result[0].Meta.LongName, StockDetailsContent(stock, data, portfolios), portfolios)
}