Compare commits

..
12 changed files with 460 additions and 324 deletions
-3
View File
@@ -160,6 +160,3 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear # and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder. # option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/ .idea/
postgres_data/
.prod.env
-1
View File
@@ -126,7 +126,6 @@ To create the first user, open the container's console using Unraid's UI, by cli
| variable | type | default | explanation | | variable | type | default | explanation |
|-------------------------------|-------------|-----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |-------------------------------|-------------|-----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| INTERNAL_PORT | int | 8000 | The port on which the app listens on. Defaults to 8000 if not set. |
| DJANGO_ALLOWED_HOSTS | string | localhost 127.0.0.1 | A list of space separated domains and IPs representing the host/domain names that WYGIWYH site can serve. [Click here](https://docs.djangoproject.com/en/5.1/ref/settings/#allowed-hosts) for more details | | DJANGO_ALLOWED_HOSTS | string | localhost 127.0.0.1 | A list of space separated domains and IPs representing the host/domain names that WYGIWYH site can serve. [Click here](https://docs.djangoproject.com/en/5.1/ref/settings/#allowed-hosts) for more details |
| HTTPS_ENABLED | true\|false | false | Whether to use secure cookies. If this is set to true, the cookie will be marked as “secure”, which means browsers may ensure that the cookie is only sent under an HTTPS connection | | HTTPS_ENABLED | true\|false | false | Whether to use secure cookies. If this is set to true, the cookie will be marked as “secure”, which means browsers may ensure that the cookie is only sent under an HTTPS connection |
| URL | string | http://localhost http://127.0.0.1 | A list of space separated domains and IPs (with the protocol) representing the trusted origins for unsafe requests (e.g. POST). [Click here](https://docs.djangoproject.com/en/5.1/ref/settings/#csrf-trusted-origins ) for more details | | URL | string | http://localhost http://127.0.0.1 | A list of space separated domains and IPs (with the protocol) representing the trusted origins for unsafe requests (e.g. POST). [Click here](https://docs.djangoproject.com/en/5.1/ref/settings/#csrf-trusted-origins ) for more details |
@@ -182,3 +182,29 @@ def calculate_historical_account_balance(queryset):
historical_account_balance[date_filter(end_date, "b Y")] = month_data historical_account_balance[date_filter(end_date, "b Y")] = month_data
return historical_account_balance return historical_account_balance
def calculate_monthly_net_worth_difference(historical_net_worth):
diff_dict = OrderedDict()
if not historical_net_worth:
return diff_dict
# Get all currencies
currencies = set()
for data in historical_net_worth.values():
currencies.update(data.keys())
# Initialize prev_values for all currencies
prev_values = {currency: Decimal("0.00") for currency in currencies}
for month, values in historical_net_worth.items():
diff_values = {}
for currency in sorted(list(currencies)):
current_val = values.get(currency, Decimal("0.00"))
prev_val = prev_values.get(currency, Decimal("0.00"))
diff_values[currency] = current_val - prev_val
diff_dict[month] = diff_values
prev_values = values.copy()
return diff_dict
+34
View File
@@ -8,6 +8,7 @@ from django.views.decorators.http import require_http_methods
from apps.net_worth.utils.calculate_net_worth import ( from apps.net_worth.utils.calculate_net_worth import (
calculate_historical_currency_net_worth, calculate_historical_currency_net_worth,
calculate_historical_account_balance, calculate_historical_account_balance,
calculate_monthly_net_worth_difference,
) )
from apps.transactions.models import Transaction from apps.transactions.models import Transaction
from apps.transactions.utils.calculations import ( from apps.transactions.utils.calculations import (
@@ -96,6 +97,38 @@ def net_worth(request):
chart_data_currency_json = json.dumps(chart_data_currency, cls=DjangoJSONEncoder) chart_data_currency_json = json.dumps(chart_data_currency, cls=DjangoJSONEncoder)
monthly_difference_data = calculate_monthly_net_worth_difference(
historical_net_worth=historical_currency_net_worth
)
diff_labels = (
list(monthly_difference_data.keys()) if monthly_difference_data else []
)
diff_currencies = (
list(monthly_difference_data[diff_labels[0]].keys())
if monthly_difference_data and diff_labels
else []
)
diff_datasets = []
for i, currency in enumerate(diff_currencies):
data = [
float(month_data.get(currency, 0))
for month_data in monthly_difference_data.values()
]
diff_datasets.append(
{
"label": currency,
"data": data,
"borderWidth": 3,
}
)
chart_data_monthly_difference = {"labels": diff_labels, "datasets": diff_datasets}
chart_data_monthly_difference_json = json.dumps(
chart_data_monthly_difference, cls=DjangoJSONEncoder
)
historical_account_balance = calculate_historical_account_balance( historical_account_balance = calculate_historical_account_balance(
queryset=transactions_account_queryset queryset=transactions_account_queryset
) )
@@ -140,6 +173,7 @@ def net_worth(request):
"chart_data_accounts_json": chart_data_accounts_json, "chart_data_accounts_json": chart_data_accounts_json,
"accounts": accounts, "accounts": accounts,
"type": view_type, "type": view_type,
"chart_data_monthly_difference_json": chart_data_monthly_difference_json,
}, },
) )
+1 -1
View File
@@ -3,7 +3,7 @@
{% if icon %}<i class="{{ icon }}"></i>{% else %}<span class="fw-bold">{{ title.0 }}</span>{% endif %} {% if icon %}<i class="{{ icon }}"></i>{% else %}<span class="fw-bold">{{ title.0 }}</span>{% endif %}
</div> </div>
<div class="card-body"> <div class="card-body">
<h5 class="tw:text-{{ color }}-400 fw-bold tw:mr-[50px]" {{ attrs }}>{{ title }}{% if help_text %}<c-ui.help-icon :content="help_text" icon=""></c-ui.help-icon>{% endif %}</h5> <h5 class="tw:text-{{ color }}-400 fw-bold tw:mr-[50px] {{ title_css_classes }}" {{ attrs }}>{{ title }}{% if help_text %}<c-ui.help-icon :content="help_text" icon=""></c-ui.help-icon>{% endif %}</h5>
{{ slot }} {{ slot }}
</div> </div>
</div> </div>
@@ -354,6 +354,7 @@
display: false, display: false,
}, },
ticks: { ticks: {
display: false,
format: { maximumFractionDigits: 40, minimumFractionDigits: 1 } format: { maximumFractionDigits: 40, minimumFractionDigits: 1 }
} }
}, },
@@ -369,6 +370,7 @@
display: false, display: false,
}, },
ticks: { ticks: {
display: false,
format: { maximumFractionDigits: 40, minimumFractionDigits: 1 } format: { maximumFractionDigits: 40, minimumFractionDigits: 1 }
} }
} }
+392 -308
View File
@@ -9,338 +9,422 @@
{% block title %}{% if type == "current" %}{% translate 'Current Net Worth' %}{% else %}{% translate 'Projected Net Worth' %}{% endif %}{% endblock %} {% block title %}{% if type == "current" %}{% translate 'Current Net Worth' %}{% else %}{% translate 'Projected Net Worth' %}{% endif %}{% endblock %}
{% block content %} {% block content %}
<div hx-trigger="every 60m, updated from:window" hx-include="#view-type" class="show-loading" hx-get="" hx-target="body"> <div hx-trigger="every 60m, updated from:window" hx-include="#view-type" class="show-loading" hx-get=""
<div class="h-100 text-center mb-4 pt-2"> hx-target="body">
<div class="btn-group gap-3" role="group" id="view-type" _="on change trigger updated"> <div class="h-100 text-center mb-4 pt-2">
<input type="radio" class="btn-check" <div class="btn-group gap-3" role="group" id="view-type" _="on change trigger updated">
name="view_type" <input type="radio" class="btn-check"
id="current-view" name="view_type"
autocomplete="off" id="current-view"
value="current" autocomplete="off"
{% if type == "current" %}checked{% endif %}> value="current"
<label class="btn btn-outline-primary rounded-5" for="current-view"><i {% if type == "current" %}checked{% endif %}>
class="fa-solid fa-sack-dollar fa-fw me-2"></i>{% trans 'Current' %}</label> <label class="btn btn-outline-primary rounded-5" for="current-view"><i
class="fa-solid fa-sack-dollar fa-fw me-2"></i>{% trans 'Current' %}</label>
<input type="radio" <input type="radio"
class="btn-check" class="btn-check"
name="view_type" name="view_type"
id="projected-view" id="projected-view"
autocomplete="off" autocomplete="off"
value="projected" value="projected"
{% if type == "projected" %}checked{% endif %}> {% if type == "projected" %}checked{% endif %}>
<label class="btn btn-outline-primary rounded-5" for="projected-view"><i <label class="btn btn-outline-primary rounded-5" for="projected-view"><i
class="fa-solid fa-rocket fa-fw me-2"></i>{% trans 'Projected' %}</label> class="fa-solid fa-rocket fa-fw me-2"></i>{% trans 'Projected' %}</label>
</div>
</div> </div>
</div> <div class="container px-md-3 py-3"
<div class="container px-md-3 py-3" _="init call initializeAccountChart() then initializeCurrencyChart() end"> _="init call initializeAccountChart() then initializeCurrencyChart() then initializeMonthlyDifferenceChart() end">
<div class="row gx-xl-4 gy-3 mb-4"> <div class="row gx-xl-4 gy-3 mb-4">
<div class="col-12 col-xl-5"> <div class="col-12 col-xl-5">
<div class="row row-cols-1 g-4"> <div class="row row-cols-1 g-4">
<div class="col"> <div class="col">
<c-ui.info-card color="yellow" icon="fa-solid fa-coins" title="{% trans 'By currency' %}" <c-ui.info-card color="yellow" icon="fa-solid fa-coins" title="{% trans 'By currency' %}"
_="on click showAllDatasetsCurrency()"> title_css_classes="tw:cursor-pointer"
{% for currency in currency_net_worth.values %} _="on click showAllDatasetsCurrency()">
<div class="d-flex justify-content-between mt-2"> {% for currency in currency_net_worth.values %}
<div class="d-flex align-items-baseline w-100"> <div class="d-flex justify-content-between mt-2">
<div class="currency-name text-start font-monospace tw:text-gray-300" <div class="d-flex align-items-baseline w-100">
_="on click showOnlyCurrencyDataset('{{ currency.currency.name }}')"> <div class="currency-name text-start font-monospace tw:text-gray-300 tw:cursor-pointer"
{{ currency.currency.name }} _="on click showOnlyCurrencyDataset('{{ currency.currency.name }}')">
{{ currency.currency.name }}
</div>
<div class="dotted-line flex-grow-1"></div>
<div>
<c-amount.display
:amount="currency.total_final"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="{% if currency.total_final > 0 %}green{% elif currency.total_final < 0 %}red{% endif %}"
text-end></c-amount.display>
</div>
</div> </div>
<div class="dotted-line flex-grow-1"></div> </div>
{% if currency.exchanged and currency.exchanged.total_final %}
<div> <div>
<c-amount.display <c-amount.display
:amount="currency.total_final" :amount="currency.exchanged.total_final"
:prefix="currency.currency.prefix" :prefix="currency.exchanged.currency.prefix"
:suffix="currency.currency.suffix" :suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.currency.decimal_places" :decimal_places="currency.exchanged.currency.decimal_places"
color="{% if currency.total_final > 0 %}green{% elif currency.total_final < 0 %}red{% endif %}" text-end
text-end></c-amount.display> color="grey"></c-amount.display>
</div> </div>
</div> {% endif %}
</div> {% if currency.consolidated and currency.consolidated.total_final != currency.total_final %}
{% if currency.exchanged and currency.exchanged.total_final %} <div class="d-flex align-items-baseline w-100">
<div> <div class="account-name text-start font-monospace tw:text-gray-300">
<c-amount.display <span class="hierarchy-line-icon"></span>{% trans 'Consolidated' %}</div>
:amount="currency.exchanged.total_final" <div class="dotted-line flex-grow-1"></div>
:prefix="currency.exchanged.currency.prefix" <div class="">
:suffix="currency.exchanged.currency.suffix" <c-amount.display
:decimal_places="currency.exchanged.currency.decimal_places"
text-end
color="grey"></c-amount.display>
</div>
{% endif %}
{% if currency.consolidated and currency.consolidated.total_final != currency.total_final %}
<div class="d-flex align-items-baseline w-100">
<div class="account-name text-start font-monospace tw:text-gray-300">
<span class="hierarchy-line-icon"></span>{% trans 'Consolidated' %}</div>
<div class="dotted-line flex-grow-1"></div>
<div class="">
<c-amount.display
:amount="currency.consolidated.total_final" :amount="currency.consolidated.total_final"
:prefix="currency.consolidated.currency.prefix" :prefix="currency.consolidated.currency.prefix"
:suffix="currency.consolidated.currency.suffix" :suffix="currency.consolidated.currency.suffix"
:decimal_places="currency.consolidated.currency.decimal_places" :decimal_places="currency.consolidated.currency.decimal_places"
color="{% if currency.consolidated.total_final > 0 %}green{% elif currency.consolidated.total_final < 0 %}red{% endif %}" color="{% if currency.consolidated.total_final > 0 %}green{% elif currency.consolidated.total_final < 0 %}red{% endif %}"
text-end></c-amount.display> text-end></c-amount.display>
</div>
</div> </div>
{% endif %} </div>
{% endfor %} {% endif %}
</c-ui.info-card> {% endfor %}
</c-ui.info-card>
</div>
</div>
</div>
<div class="col-12 col-xl-7">
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="evolution-tab" data-bs-toggle="tab"
data-bs-target="#evolution-tab-pane" type="button" role="tab" aria-controls="evolution-tab-pane"
aria-selected="true">{% trans 'Evolution' %}</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="diff-tab" data-bs-toggle="tab" data-bs-target="#diff-tab-pane" type="button"
role="tab" aria-controls="diff-tab-pane" aria-selected="false">{% trans 'Difference' %}</button>
</li>
</ul>
<div class="tab-content" id="myTabContent">
<div class="tab-pane fade show active" id="evolution-tab-pane" role="tabpanel"
aria-labelledby="evolution-tab" tabindex="0">
<div class="chart-container position-relative tw:min-h-[40vh] tw:h-full tw:w-full">
<canvas id="currencyBalanceChart"></canvas>
</div>
</div>
<div class="tab-pane fade" id="diff-tab-pane" role="tabpanel" aria-labelledby="diff-tab" tabindex="0">
<div class="chart-container position-relative tw:min-h-[40vh] tw:h-full tw:w-full">
<canvas id="monthlyDifferenceChart"></canvas>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="col-12 col-xl-7"> <hr>
<div class="chart-container position-relative tw:min-h-[40vh] tw:h-full"> <div class="row gx-xl-4 gy-3 mt-4">
<canvas id="currencyBalanceChart"></canvas> <div class="col-12 col-xl-5">
</div> <div class="row row-cols-1 g-4">
</div> <div class="col">
</div> <c-ui.info-card color="blue" icon="fa-solid fa-wallet" title="{% trans 'By account' %}"
<hr> title_css_classes="tw:cursor-pointer"
<div class="row gx-xl-4 gy-3 mt-4"> _="on click showAllDatasetsAccount()">
<div class="col-12 col-xl-5"> {% regroup account_net_worth.values by account.group as account_data %}
<div class="row row-cols-1 g-4"> {% for data in account_data %}
<div class="col"> {% if data.grouper %}
<c-ui.info-card color="blue" icon="fa-solid fa-wallet" title="{% trans 'By account' %}" <div class="d-flex justify-content-between mt-2">
_="on click showAllDatasetsAccount()"> <div class="d-flex align-items-baseline w-100">
{% regroup account_net_worth.values by account.group as account_data %} <div class="text-start font-monospace tw:text-gray-300"><span class="badge text-bg-primary">
{% for data in account_data %}
{% if data.grouper %}
<div class="d-flex justify-content-between mt-2">
<div class="d-flex align-items-baseline w-100">
<div class="text-start font-monospace tw:text-gray-300"><span class="badge text-bg-primary">
{{ data.grouper }}</span></div> {{ data.grouper }}</span></div>
</div>
</div>
{% for account in data.list %}
<div class="d-flex justify-content-between mt-2">
<div class="d-flex align-items-baseline w-100">
<div class="account-name text-start font-monospace tw:text-gray-300"
_="on click showOnlyAccountDataset('{{ account.account.name }}')">
<span class="hierarchy-line-icon"></span>{{ account.account.name }}</div>
<div class="dotted-line flex-grow-1"></div>
<div class="">
<c-amount.display
:amount="account.total_final"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="{% if account.total_final > 0 %}green{% elif account.total_final < 0 %}red{% endif %}"></c-amount.display>
</div>
</div> </div>
</div> </div>
{% if account.exchanged and account.exchanged.total_final %} {% for account in data.list %}
<c-amount.display <div class="d-flex justify-content-between mt-2">
:amount="account.exchanged.total_final" <div class="d-flex align-items-baseline w-100">
:prefix="account.exchanged.currency.prefix" <div class="account-name text-start font-monospace tw:text-gray-300 tw:cursor-pointer"
:suffix="account.exchanged.currency.suffix" _="on click showOnlyAccountDataset('{{ account.account.name }}')">
:decimal_places="account.exchanged.currency.decimal_places" <span class="hierarchy-line-icon"></span>{{ account.account.name }}</div>
color="grey" <div class="dotted-line flex-grow-1"></div>
text-end></c-amount.display> <div class="">
{% endif %} <c-amount.display
{% endfor %} :amount="account.total_final"
{% else %} :prefix="account.currency.prefix"
{% for account in data.list %} :suffix="account.currency.suffix"
<div class="d-flex justify-content-between mt-2"> :decimal_places="account.currency.decimal_places"
<div class="d-flex align-items-baseline w-100"> color="{% if account.total_final > 0 %}green{% elif account.total_final < 0 %}red{% endif %}"></c-amount.display>
<div class="account-name text-start font-monospace tw:text-gray-300" </div>
_="on click showOnlyAccountDataset('{{ account.account.name }}')">
{{ account.account.name }}
</div>
<div class="dotted-line flex-grow-1"></div>
<div>
<c-amount.display
:amount="account.total_final"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="{% if account.total_final > 0 %}green{% elif account.total_final < 0 %}red{% endif %}"></c-amount.display>
</div> </div>
</div> </div>
</div> {% if account.exchanged and account.exchanged.total_final %}
{% if account.exchanged and account.exchanged.total_final %} <c-amount.display
<c-amount.display :amount="account.exchanged.total_final"
:amount="account.exchanged.total_final" :prefix="account.exchanged.currency.prefix"
:prefix="account.exchanged.currency.prefix" :suffix="account.exchanged.currency.suffix"
:suffix="account.exchanged.currency.suffix" :decimal_places="account.exchanged.currency.decimal_places"
:decimal_places="account.exchanged.currency.decimal_places" color="grey"
color="grey" text-end></c-amount.display>
text-end></c-amount.display> {% endif %}
{% endif %} {% endfor %}
{% endfor %} {% else %}
{% endif %} {% for account in data.list %}
{% endfor %} <div class="d-flex justify-content-between mt-2">
</c-ui.info-card> <div class="d-flex align-items-baseline w-100">
<div class="account-name text-start font-monospace tw:text-gray-300 tw:cursor-pointer"
_="on click showOnlyAccountDataset('{{ account.account.name }}')">
{{ account.account.name }}
</div>
<div class="dotted-line flex-grow-1"></div>
<div>
<c-amount.display
:amount="account.total_final"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="{% if account.total_final > 0 %}green{% elif account.total_final < 0 %}red{% endif %}"></c-amount.display>
</div>
</div>
</div>
{% if account.exchanged and account.exchanged.total_final %}
<c-amount.display
:amount="account.exchanged.total_final"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"
color="grey"
text-end></c-amount.display>
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
</c-ui.info-card>
</div>
</div>
</div>
<div class="col-12 col-xl-7">
<div class="chart-container position-relative tw:min-h-[40vh] tw:h-full">
<canvas id="accountBalanceChart"></canvas>
</div> </div>
</div> </div>
</div> </div>
<div class="col-12 col-xl-7">
<div class="chart-container position-relative tw:min-h-[40vh] tw:h-full">
<canvas id="accountBalanceChart"></canvas>
</div>
</div>
</div> </div>
<script>
var currencyChart;
function initializeCurrencyChart() {
// Destroy existing chart if it exists
if (currencyChart) {
currencyChart.destroy();
}
var chartData = JSON.parse('{{ chart_data_currency_json|safe }}');
var currencies = {{ currencies|safe }};
var ctx = document.getElementById('currencyBalanceChart').getContext('2d');
currencyChart = new Chart(ctx, {
type: 'line',
data: chartData,
options: {
maintainAspectRatio: false,
responsive: true,
interaction: {
mode: 'index',
intersect: false,
},
plugins: {
title: {
display: true,
text: '{% translate 'Evolution by currency' %}'
},
tooltip: {
mode: 'index',
intersect: false
}
},
scales: {
x: {
display: true,
title: {
display: false,
}
},
...Object.fromEntries(currencies.map((currency, i) => [
`y${i}`,
{
type: 'linear',
display: true,
grid: {
drawOnChartArea: i === 0,
},
ticks: {
display: false,
format: {maximumFractionDigits: 40, minimumFractionDigits: 0}
},
border: {
display: false
}
}
]))
}
}
});
}
</script>
<script id="accountBalanceChartScript">
var accountChart;
function initializeAccountChart() {
// Destroy existing chart if it exists
if (accountChart) {
accountChart.destroy();
}
var chartData = JSON.parse('{{ chart_data_accounts_json|safe }}');
var accounts = {{ accounts|safe }};
var ctx = document.getElementById('accountBalanceChart').getContext('2d');
accountChart = new Chart(ctx, {
type: 'line',
data: chartData,
options: {
maintainAspectRatio: false,
responsive: true,
interaction: {
mode: 'index',
intersect: false,
},
stacked: false,
plugins: {
title: {
display: true,
text: '{% translate "Evolution by account" %}'
},
tooltip: {
mode: 'index',
intersect: false
}
},
scales: {
x: {
display: true,
title: {
display: false,
}
},
...Object.fromEntries(accounts.map((account, i) => [
`y-axis-${i}`,
{
type: 'linear',
display: true,
position: i % 2 === 0 ? 'left' : 'right',
grid: {
drawOnChartArea: i === 0,
},
ticks: {
display: false,
format: {maximumFractionDigits: 40, minimumFractionDigits: 0}
},
border: {
display: false
}
}
]))
}
}
});
}
</script>
<script id="monthlyDifferenceChartScript">
var monthlyDifferenceChart;
function initializeMonthlyDifferenceChart() {
if (monthlyDifferenceChart) {
monthlyDifferenceChart.destroy();
}
var chartData = JSON.parse('{{ chart_data_monthly_difference_json|safe }}');
var ctx = document.getElementById('monthlyDifferenceChart').getContext('2d');
monthlyDifferenceChart = new Chart(ctx, {
type: 'bar',
data: chartData,
options: {
maintainAspectRatio: false,
responsive: true,
interaction: {
mode: 'index',
intersect: false,
},
plugins: {
title: {
display: true,
text: '{% translate "Difference" %}'
},
tooltip: {
mode: 'index',
intersect: false
}
},
scales: {
x: {
stacked: true,
},
y: {
stacked: true,
ticks: {
display: false,
format: {maximumFractionDigits: 40, minimumFractionDigits: 0}
},
}
}
}
});
}
</script>
<script type="text/hyperscript">
def showOnlyAccountDataset(datasetName)
for dataset in accountChart.data.datasets
set isMatch to dataset.label is datasetName
call accountChart.setDatasetVisibility(accountChart.data.datasets.indexOf(dataset), isMatch)
end
call accountChart.update()
end
def showOnlyCurrencyDataset(datasetName)
for dataset in currencyChart.data.datasets
set isMatch to dataset.label is datasetName
call currencyChart.setDatasetVisibility(currencyChart.data.datasets.indexOf(dataset), isMatch)
end
call currencyChart.update()
for dataset in monthlyDifferenceChart.data.datasets
set isMatch to dataset.label is datasetName
call monthlyDifferenceChart.setDatasetVisibility(monthlyDifferenceChart.data.datasets.indexOf(dataset), isMatch)
end
call monthlyDifferenceChart.update()
end
def showAllDatasetsAccount()
for dataset in accountChart.data.datasets
call accountChart.setDatasetVisibility(accountChart.data.datasets.indexOf(dataset), true)
end
call accountChart.update()
end
def showAllDatasetsCurrency()
for dataset in currencyChart.data.datasets
call currencyChart.setDatasetVisibility(currencyChart.data.datasets.indexOf(dataset), true)
end
call currencyChart.update()
for dataset in monthlyDifferenceChart.data.datasets
call monthlyDifferenceChart.setDatasetVisibility(monthlyDifferenceChart.data.datasets.indexOf(dataset), true)
end
call monthlyDifferenceChart.update()
end
</script>
</div> </div>
<c-ui.transactions_fab></c-ui.transactions_fab>
<script>
var currencyChart;
function initializeCurrencyChart() {
// Destroy existing chart if it exists
if (currencyChart) {
currencyChart.destroy();
}
var chartData = JSON.parse('{{ chart_data_currency_json|safe }}');
var currencies = {{ currencies|safe }};
var ctx = document.getElementById('currencyBalanceChart').getContext('2d');
currencyChart = new Chart(ctx, {
type: 'line',
data: chartData,
options: {
maintainAspectRatio: false,
responsive: true,
interaction: {
mode: 'index',
intersect: false,
},
plugins: {
title: {
display: true,
text: '{% translate 'Evolution by currency' %}'
},
tooltip: {
mode: 'index',
intersect: false
}
},
scales: {
x: {
display: true,
title: {
display: false,
}
},
...Object.fromEntries(currencies.map((currency, i) => [
`y${i}`,
{
type: 'linear',
display: true,
grid: {
drawOnChartArea: i === 0,
},
ticks: {
display: false,
format: {maximumFractionDigits: 40, minimumFractionDigits: 0}
},
border: {
display: false
}
}
]))
}
}
});
}
</script>
<script id="accountBalanceChartScript">
var accountChart;
function initializeAccountChart() {
// Destroy existing chart if it exists
if (accountChart) {
accountChart.destroy();
}
var chartData = JSON.parse('{{ chart_data_accounts_json|safe }}');
var accounts = {{ accounts|safe }};
var ctx = document.getElementById('accountBalanceChart').getContext('2d');
accountChart = new Chart(ctx, {
type: 'line',
data: chartData,
options: {
maintainAspectRatio: false,
responsive: true,
interaction: {
mode: 'index',
intersect: false,
},
stacked: false,
plugins: {
title: {
display: true,
text: '{% translate "Evolution by account" %}'
},
tooltip: {
mode: 'index',
intersect: false
}
},
scales: {
x: {
display: true,
title: {
display: false,
}
},
...Object.fromEntries(accounts.map((account, i) => [
`y-axis-${i}`,
{
type: 'linear',
display: true,
position: i % 2 === 0 ? 'left' : 'right',
grid: {
drawOnChartArea: i === 0,
},
ticks: {
display: false,
format: {maximumFractionDigits: 40, minimumFractionDigits: 0}
},
border: {
display: false
}
}
]))
}
}
});
}
</script>
<script type="text/hyperscript">
def showOnlyAccountDataset(datasetName)
for dataset in accountChart.data.datasets
set isMatch to dataset.label is datasetName
call accountChart.setDatasetVisibility(accountChart.data.datasets.indexOf(dataset), isMatch)
end
call accountChart.update()
end
def showOnlyCurrencyDataset(datasetName)
for dataset in currencyChart.data.datasets
set isMatch to dataset.label is datasetName
call currencyChart.setDatasetVisibility(currencyChart.data.datasets.indexOf(dataset), isMatch)
end
call currencyChart.update()
end
def showAllDatasetsAccount()
for dataset in accountChart.data.datasets
call accountChart.setDatasetVisibility(accountChart.data.datasets.indexOf(dataset), true)
end
call accountChart.update()
end
def showAllDatasetsCurrency()
for dataset in currencyChart.data.datasets
call currencyChart.setDatasetVisibility(currencyChart.data.datasets.indexOf(dataset), true)
end
call currencyChart.update()
end
</script>
</div>
<c-ui.transactions_fab></c-ui.transactions_fab>
{% endblock %} {% endblock %}
+1 -1
View File
@@ -9,7 +9,7 @@
{% block content %} {% block content %}
<div> <div>
<div class="container"> <div class="container">
<div class="row vh-100 d-flex justify-content-center align-items-center"> <div class="row tw:h-dvh d-flex justify-content-center align-items-center">
<div class="col-md-6 col-xl-4 col-12"> <div class="col-md-6 col-xl-4 col-12">
{% settings "DEMO" as demo_mode %} {% settings "DEMO" as demo_mode %}
{% if demo_mode %} {% if demo_mode %}
+1 -1
View File
@@ -15,7 +15,7 @@ services:
- ./frontend/:/usr/src/frontend:z - ./frontend/:/usr/src/frontend:z
- wygiwyh_temp:/usr/src/app/temp/ - wygiwyh_temp:/usr/src/app/temp/
ports: ports:
- "${OUTBOUND_PORT:-8000}:${INTERNAL_PORT:-8000}" - "${OUTBOUND_PORT}:8000"
env_file: env_file:
- .env - .env
depends_on: depends_on:
+1 -1
View File
@@ -4,7 +4,7 @@ services:
container_name: ${SERVER_NAME} container_name: ${SERVER_NAME}
command: /start-single command: /start-single
ports: ports:
- "${OUTBOUND_PORT:-8000}:${INTERNAL_PORT:-8000}" - "${OUTBOUND_PORT}:8000"
env_file: env_file:
- .env - .env
depends_on: depends_on:
+1 -4
View File
@@ -4,9 +4,6 @@ set -o errexit
set -o pipefail set -o pipefail
set -o nounset set -o nounset
# Set INTERNAL_PORT with default value of 8000
INTERNAL_PORT=${INTERNAL_PORT:-8000}
rm -f /tmp/migrations_complete rm -f /tmp/migrations_complete
python manage.py migrate python manage.py migrate
@@ -16,4 +13,4 @@ touch /tmp/migrations_complete
python manage.py setup_users python manage.py setup_users
exec python manage.py runserver 0.0.0.0:$INTERNAL_PORT exec python manage.py runserver 0.0.0.0:8000
+1 -4
View File
@@ -4,9 +4,6 @@ set -o errexit
set -o pipefail set -o pipefail
set -o nounset set -o nounset
# Set INTERNAL_PORT with default value of 8000
INTERNAL_PORT=${INTERNAL_PORT:-8000}
# Remove flag file if it exists from previous run # Remove flag file if it exists from previous run
rm -f /tmp/migrations_complete rm -f /tmp/migrations_complete
@@ -18,4 +15,4 @@ touch /tmp/migrations_complete
python manage.py setup_users python manage.py setup_users
exec gunicorn WYGIWYH.wsgi:application --bind 0.0.0.0:$INTERNAL_PORT --timeout 600 exec gunicorn WYGIWYH.wsgi:application --bind 0.0.0.0:8000 --timeout 600