Files
finplan/templates/index.html

344 lines
16 KiB
HTML

<!DOCTYPE html>
<html lang="en" data-bs-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<title>Finance Form</title>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
<script src="https://code.highcharts.com/highcharts.js"></script>
<script src="https://code.highcharts.com/modules/annotations.js"></script>
<script src="https://code.highcharts.com/modules/accessibility.js"></script>
<script src="https://code.highcharts.com/themes/adaptive.js"></script>
<style>
.col-form-label { width:140px; }
html { font-size: 80%; }
</style>
</head>
<body>
<div class="containerfluid">
<h3 align="center">Finance Tracker (go to <a href="bills">Bills</a>)</h3>
<form id="vals_form" class="ms-3 mt-3" action="/update" method="POST">
{% for r in DISP %}
<div class="row align-items-center">
{% for el in r %}
{% if COMP and ( COMP['vars'][el.varname] != finance[el.varname] or
(COMP['vars'][el.datevarname] is defined and COMP['vars'][el.datevarname] != finance[el.datevarname]) ) %}
{% set extra=" text-info" %}
{% else %}
{% set extra="" %}
{% endif %}
<div class="{{el.cl}}">
<div class="input-group">
<label id="lbl-{{el.varname}}" for="{{el.varname}}"
{% if el.display=="select" %}
{% if COMP and COMP['vars'][el.varname] != finance[el.varname] %}
data-bs-toggle="tooltip" title="Comparison was: {{COMP['vars'][el.varname]}}"
{% endif %}
class="col-form-label me-2 text-end float-end {{extra}}">{{el.label}}
</label>
<select class="form-select border border-info text-info text-end" id="{{el.varname}}" name="{{el.varname}}" style="width: 120px;"
onchange="this.form.submit()">
{% for o in el.opts %}
<option value="{{o.val}}">{{o.label}}</option>
{% endfor %}
</select>
{% elif el.display=="date" %}
{% if COMP and (COMP['vars'][el.varname] != finance[el.varname] or COMP['vars'][el.datevarname] != finance[el.datevarname]) %}
data-bs-toggle="tooltip" title="Comparison was: {{COMP['vars'][el.varname]}} on {{COMP['vars'][el.datevarname]}}"
{% endif %}
class="col-form-label me-2 text-end float-end {{extra}}">{{el.label}}
</label>
<input type="number" step="any" class="form-control text-end float-end border border-info" onchange="this.form.submit()" style="max-width: 120px;"
id="{{el.varname}}" name="{{el.varname}}" value="{{ finance[el.varname] }}" {{el.display}}>
<input type="date" class="form-control text-end float-end border border-info" id="{{el.datevarname}}" style="max-width: 150px;"
name="{{el.datevarname}}" value="{{ finance[el.datevarname] }}" onchange="this.form.submit()">
{% else %}
{% if COMP and COMP['vars'][el.varname] != finance[el.varname] %}
data-bs-toggle="tooltip" title="Comparison was: {{COMP['vars'][el.varname]}}"
{% endif %}
class="col-form-label me-2 text-end float-end {{extra}}">{{el.label}}
</label>
{% if el.display== "readonly" %}
{% set bg="bg-body-tertiary" %}
{% set bd="" %}
{% else %}
{% set bg="" %}
{% set bd="border-1 border-info" %}
{% endif %}
<input type="number" step="any" class="{{bg}} form-control text-end float-end {{bd}}" onchange="this.form.submit()" style="max-width: 120px;"
id="{{el.varname}}" name="{{el.varname}}" value="{{ finance[el.varname] }}" {{el.display}}>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% endfor %}
<h5 align="center" class="mt-4">Fortnighthly Savings data:
{% if COMP %}
{# get comparison date so we can use it below in loop to know when to print it out #}
{% set comp_yr=COMP['date'][:4] %}
{% set comp_mon=COMP['date'][5:7] %}
{% set comp_day=COMP['date'][8:10 ] %}
{% set comp_done=namespace( val=0 ) %}
{% else %}
{# we dont need to do a comparison, so consider it done before we begin #}
{% set comp_done=namespace( val=1 ) %}
{% endif %}
</h5>
<div class="row">
<div class="col-auto"> <div class="pt-1 pb-1 mb-0 alert text-center bg-secondary text-light">2025</div>
{# inside started if below, we add blank lines to the start of the year so the dates line up #}
{% for _ in range( 0, padding ) %}
<br>
{% endfor %}
{% set car_done=namespace( val=0 ) %}
{% for date, dollars in savings %}
{% set yr=date[:4] %}
{% set mon=date[5:7] %}
{% set day=date[8:10 ] %}
{% set car_yr=key_dates['D_hyundai_owned'][:4] %}
{% set car_mon=key_dates['D_hyundai_owned'][5:7] %}
{% set car_day=key_dates['D_hyundai_owned'][8:10 ] %}
{% if yr|int > first_yr|int and mon == '01' and day|int <= 14 %}
</div><div class="col-auto">
<div class="pt-1 pb-1 mb-0 alert text-center bg-secondary text-light">{{yr}}</div>
{% endif %}
{% if date == key_dates['D_quit_date'] %}
<font class="text-warning">
<label data-bs-toggle="tooltip" title="D quits">
{% elif (yr == car_yr and mon == car_mon and day >= car_day and car_done.val == 0) %}
{%set car_done.val=1 %}
<font class="text-warning">
<label data-bs-toggle="tooltip" title="We own car">
{% else %}
<font class="text-secondary">
<label>
{% endif %}
{{ date }}:&nbsp;
{{ '$%0.2f' % dollars|float }}<br>
</label>
</font><br>
{% if comp_done.val == 0 and yr == comp_yr and mon == comp_mon and day|int >= comp_day|int %}
<font class="text-info">
{{ COMP['date'] }}:&nbsp;
{{ '$%0.2f' % COMP['amount']|float }}<br>
</font>
{% set comp_done.val=1 %}
{% endif %}
{% endfor %}
{% if depletion_date %}
<div class="alert alert-danger">Run out of $'s:<br>{{depletion_date}}</div>
{% else %}
<div class="alert alert-success">Super kicks in!!!</div>
{% endif %}
</div>
<div class="col-auto">
<div class="alert alert-warning">
<h6 class="alert-heading">SUMMARY/BUDGET</h6>
{% for label, value in BUDGET %}
<div>
{{label}} {{value}}
</div>
{% endfor %}
</div>
<div id="comp_col" class="col-auto">
<div class="input-group">
<button type="button" class="btn btn-info me-2 rounded" data-bs-toggle="modal" data-bs-target="#save_modal">Save</button>
<button type="submit" class="disabled btn btn-info rounded-start" onClick="$('#vals_form').submit() disabled">Compare to:</button>
<select class="form-select border border-info text-info" id="compare_to" name="compare_to" onchange="$('#vals_form').submit()">
{% for el in finance['COMP_SETS'] %}
<option value="{{el[0]}}">{{el[1]}}</option>
{% endfor %}
</select>
</div>
{% if COMP %}
<div style="display:none" id="comp_alert" class="alert alert-info mt-2">Note: {{ '$%0.2f' % COMP['amount']|float }} is the final value of the compared to data (with a buffer of: {{ '$%0.2f' % COMP['buffer']|float }}). Hover over blue labels above to see what compared to values differed</div>
{% endif %}
</div>
</div>
</div>
</form>
<div class="row mt-4 highcharts-dark" id="container" style="width:100%; height:800px;"></div>
<script type="text/javascript">
// make these global so we can also use them in the /save route (via modal)
const savingsData = JSON.parse('{{ savings | tojson }}');
const vars = JSON.parse('{{ finance | tojson }}');
window.onload = function() {
$('#Sell_shares').val( {{finance['Sell_shares']}} )
$('#compare_to').val( {{finance['compare_to']}} )
$('#Ioniq6_future').val( {{finance['Ioniq6_future']}} )
if( $("#Ioniq6_future option:selected"). text() == 'lease' )
{
// disable buyout
$('#lbl-Car_buyout').addClass('bg-body-tertiary border-secondary')
$('#Car_buyout').addClass('bg-body-tertiary border-secondary').attr('readonly', 'readonly' )
$('#Car_buyout_date').addClass('bg-body-tertiary border-secondary').attr('readonly', 'readonly' )
}
else
{
// disable lease
$('#lbl-Car_loan').addClass('bg-body-tertiary')
$('#Car_loan').addClass('bg-body-tertiary')
$('#lbl-Car_balloon').addClass('bg-body-tertiary')
$('#Car_balloon').addClass('bg-body-tertiary')
}
var tooltipTriggerList = [].slice.call(document.querySelectorAll("[data-bs-toggle='tooltip']"))
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) { return new bootstrap.Tooltip(tooltipTriggerEl) })
var parentWidth = $('#comp_col').width();
// Set the alert's `max-width` to the parent's width
$('#comp_alert').css('max-width', parentWidth + 'px');
$('#comp_alert').show()
};
document.addEventListener('DOMContentLoaded', function () {
// Parse the savings_data from Flask
const chartData = savingsData.map(entry => [new Date(entry[0]).getTime(), parseFloat(entry[1])]);
{% if COMP %}
const compSavingsData = JSON.parse('{{ COMP['savings_data'] | tojson }}');
const compChartData = compSavingsData.map(entry => [new Date(entry[0]).getTime(), parseFloat(entry[1])]);
{% endif %}
// Get the legend name and vars for the tooltip
const legendName = 'Savings';
// Tooltip content from vars
const tooltipContent = Object.entries(vars).map(([key, value]) => `${key}: ${value}`).join('<br>');
// Calculate plot bands for each year with alternating background colors
const plotBands = [];
let year = new Date(savingsData[0][0]).getFullYear();
let start = new Date(`${year}-01-01`).getTime();
let end;
for (let i = 1; i < savingsData.length; i++) {
const currentYear = new Date(savingsData[i][0]).getFullYear();
if (currentYear !== year) {
end = new Date(`${year + 1}-01-01`).getTime(); // End of the year
plotBands.push({
from: start,
to: end,
color: year % 2 === 0 ? '#101010' : 'black' // Alternate colors
});
year = currentYear;
start = new Date(`${year}-01-01`).getTime();
}
}
// Add the final year
end = new Date(`${year + 1}-01-01`).getTime();
plotBands.push({
from: start,
to: end,
color: year % 2 === 0 ? 'charcoal' : 'black'
});
const annotations = [];
// the al, x, offset are used to make the altenrate annotations be on slightly different vertical offsets (size is based on $'s)
// al alternates every 2 annotations left / right (so 2 left, then 2 right), x is just used to also move the label more left/right to get the connecting line
var offset=13
{% if not COMP %}
// Add annotations for changes greater than 5000
{% for a in finance['annotations'] %}
annotations.push({
labels: [{
point: {
x: {{a['x']}},
y: {{a['y']}},
crop: true,
xAxis: 0,
yAxis: 0
},
x: -70,
y: offset,
text: '{{a['label']}}'
}], labelOptions: { allowOverlap: true }
});
offset = ({{loop.index}} * 50 % 200) +50
{% endfor %}
document.keep = annotations
{% endif %}
// Highcharts configuration
Highcharts.chart('container', {
chart: { type: 'line' },
colors: [
'orange', // Custom color 1
'cyan', // Custom color 2
],
title: { text: 'Savings Over Time' },
xAxis: {
type: 'datetime',
title: { text: 'Date' },
plotBands: plotBands // Alternating background for years
},
yAxis: { title: { text: 'Amount ($)' } },
tooltip: {
pointFormatter: function ()
{
if( this.series.symbol == 'circle' ) { s='\u25CF' } else { s='\u2B25' }
return '<span style="color:' + this.point.color + '">' + s + '</span> <b>' + this.point.y + ':</b> ' + this.series.name + '<br>'
}, shared:true
},
annotations: annotations, // Add annotations
series: [
{ name: "Savings", data: chartData, marker: { radius: 2 } }
{% if COMP %}
,{ name: "{{COMP['vars']['name']}}", data: compChartData, marker: { radius: 2 } }
{% endif %}
]
});
});
</script>
<div id="save_modal" class="modal modal-lg" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Save </h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>Save data for future comparison</p>
<div class="input-group">
<label class="col-form-label me-2 text-end float-end">With name:</label>
<input id="save_name" type="text" class="form-control"
value="{{now}}-LE={{finance['Living_Expenses']|replace('000','k')}},Inf={{finance['Inflation']}},sell={{finance['Sell_shares']}}"
</input>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-info"
onClick="
vars['name']=$('#save_name').val();
$.ajax( {
type: 'POST',
url: '/save',
contentType: 'application/json',
data: JSON.stringify( { 'vars': vars, 'savings_data' :savingsData } ),
success: function() { $('#save_modal').modal('hide'); } } )"
>Save</button>
</div>
</div>
</div>
</div>
</body>
</html>