Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -1910,6 +1910,61 @@
# SYSTEM LOGS
# ============================================================================

@app.route('/api/rates-history')
@login_required
def rates_history():
"""Return historical exchange rates for chart."""
from flask import jsonify
from datetime import date, timedelta
from currency_converter import get_exchange_rate

from_cur = request.args.get('from', 'USD')
to_cur = request.args.get('to', 'EUR')
period = request.args.get('period', '1m') # 1d, 1w, 1m

today = date.today()
if period == '1d':
days = 1
step = 1 # hourly not available, just show today vs yesterday
elif period == '1w':
days = 7
step = 1
else: # 1m
days = 30
step = 1

points = []
for i in range(days, -1, -step):
d = today - timedelta(days=i)
date_str = d.strftime('%Y-%m-%d')
try:
rate, _ = get_exchange_rate(date_str)
if rate and rate > 0:
# get_exchange_rate returns USD/EUR rate
# We need from_cur/to_cur
if from_cur == 'USD' and to_cur == 'EUR':
val = 1.0 / rate if rate else 0
elif from_cur == 'EUR' and to_cur == 'USD':
val = rate
else:
# For other pairs, use the currency service if available
if module_manager:
try:
converted, r, _ = module_manager.core.currency_service.convert(
1.0, from_cur, to_cur, date_str)
val = converted
except Exception:
val = None
else:
val = None
if val:
points.append({'date': d.strftime('%d/%m'), 'rate': round(val, 4)})
except Exception:

Check notice

Code scanning / CodeQL

Empty except Note

'except' clause does nothing but pass and there is no explanatory comment.
pass

return jsonify({'points': points, 'from': from_cur, 'to': to_cur})


@app.route('/logs')
@login_required
def system_logs():
Expand Down
1 change: 1 addition & 0 deletions templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<meta name="csrf-token" content="{{ csrf_token() }}">
{% endif %}
<title>{% block title %}Invoice Manager{% endblock %}</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: Arial, sans-serif; background: #f5f5f5; }
Expand Down
79 changes: 75 additions & 4 deletions templates/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,27 +46,39 @@ <h3 style="margin: 0; font-size: 16px; color: #333;">Currency & Information</h3>
</td>
<td style="width: 25%; padding: 15px; border-right: 1px solid #e0e0e0; vertical-align: top;">
<div style="color: #666; font-size: 12px; margin-bottom: 10px;">Currency Converter</div>
<div style="display: flex; gap: 10px; align-items: center; margin-bottom: 8px;">
<div style="display: flex; gap: 10px; align-items: center; margin-bottom: 4px;">
<input type="number" id="converter-amount" value="100" step="0.01"
style="width: 80px; padding: 6px; border: 1px solid #ddd; border-radius: 3px; font-size: 14px;"
oninput="convertCurrency()">
<select id="converter-from" style="flex: 1; padding: 6px; border: 1px solid #ddd; border-radius: 3px; font-size: 13px;"
onchange="convertCurrency()">
onchange="convertCurrency(); loadRateChart();">
{% for currency in tracked_currencies %}
<option value="{{ currency }}" {% if currency == 'USD' %}selected{% endif %}>{{ currency }}</option>
{% endfor %}
</select>
</div>
<div style="text-align: center; margin-bottom: 4px;">
<button type="button" onclick="swapCurrencies()" style="background:none;border:1px solid #ddd;border-radius:50%;width:24px;height:24px;cursor:pointer;font-size:12px;color:#666;" title="Swap">⇅</button>
</div>
<div style="display: flex; gap: 10px; align-items: center;">
<input type="number" id="converter-result" readonly
style="width: 80px; padding: 6px; border: 1px solid #ddd; border-radius: 3px; font-size: 14px; background: #f5f5f5;">
<select id="converter-to" style="flex: 1; padding: 6px; border: 1px solid #ddd; border-radius: 3px; font-size: 13px;"
onchange="convertCurrency()">
onchange="convertCurrency(); loadRateChart();">
{% for currency in tracked_currencies %}
<option value="{{ currency }}" {% if currency == base_currency %}selected{% endif %}>{{ currency }}</option>
{% endfor %}
</select>
</div>
<!-- Rate chart -->
<div style="margin-top: 10px;">
<div style="display: flex; gap: 4px; margin-bottom: 6px;">
<button type="button" class="chart-period active" data-period="1m" onclick="setChartPeriod('1m',this)" style="padding:2px 6px;font-size:10px;border:1px solid #ddd;border-radius:3px;background:#e3f2fd;cursor:pointer;">1M</button>
<button type="button" class="chart-period" data-period="1w" onclick="setChartPeriod('1w',this)" style="padding:2px 6px;font-size:10px;border:1px solid #ddd;border-radius:3px;background:#fff;cursor:pointer;">1W</button>
<button type="button" class="chart-period" data-period="1d" onclick="setChartPeriod('1d',this)" style="padding:2px 6px;font-size:10px;border:1px solid #ddd;border-radius:3px;background:#fff;cursor:pointer;">1D</button>
</div>
<canvas id="rate-chart" width="200" height="60" style="width:100%;height:60px;"></canvas>
</div>
</td>
<td style="width: 20%; padding: 15px; border-right: 1px solid #e0e0e0; vertical-align: top;">
<div style="color: #666; font-size: 12px; margin-bottom: 10px;">Tax Deadlines</div>
Expand Down Expand Up @@ -515,9 +527,68 @@ <h3 style="margin: 0; color: #666; font-size: 14px;">Total Tax {{ "%.4g"|format(
document.getElementById('converter-result').value = result.toFixed(2);
}

// Initialize converter on page load
function swapCurrencies() {
var from = document.getElementById('converter-from');
var to = document.getElementById('converter-to');
var tmp = from.value;
from.value = to.value;
to.value = tmp;
convertCurrency();
loadRateChart();
}

var chartPeriod = '1m';
var rateChart = null;

function setChartPeriod(period, btn) {
chartPeriod = period;
document.querySelectorAll('.chart-period').forEach(function(b) { b.style.background = '#fff'; });
btn.style.background = '#e3f2fd';
loadRateChart();
}

function loadRateChart() {
var from = document.getElementById('converter-from').value;
var to = document.getElementById('converter-to').value;
fetch('/api/rates-history?from=' + from + '&to=' + to + '&period=' + chartPeriod)
.then(function(r) { return r.ok ? r.json() : null; })
.then(function(data) {
if (!data || !data.points || !data.points.length) return;
var canvas = document.getElementById('rate-chart');
var ctx = canvas.getContext('2d');
if (rateChart) rateChart.destroy();
rateChart = new Chart(ctx, {
type: 'line',
data: {
labels: data.points.map(function(p) { return p.date; }),
datasets: [{
data: data.points.map(function(p) { return p.rate; }),
borderColor: '#5B6FD8',
borderWidth: 1.5,
pointRadius: 0,
fill: true,
backgroundColor: 'rgba(91,111,216,0.08)',
tension: 0.3
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false }, tooltip: { enabled: true } },
scales: {
x: { display: false },
y: { display: false }
}
}
});
})
.catch(function() {});
}

// Initialize converter and chart on page load
document.addEventListener('DOMContentLoaded', function() {
convertCurrency();
if (document.getElementById('rate-chart')) loadRateChart();
});
</script>

Expand Down
Loading