feat: more changes and fixes

This commit is contained in:
Herculino Trotta
2025-11-05 13:09:31 -03:00
parent 0a4d4c12b9
commit a878af28f1
34 changed files with 595 additions and 556 deletions

View File

@@ -3,7 +3,6 @@ from decimal import Decimal
from django import template
from django.utils.formats import number_format
register = template.Library()
@@ -13,13 +12,24 @@ def _format_string(prefix, amount, decimal_places, suffix):
value=abs(amount), decimal_pos=decimal_places, force_grouping=True
)
if amount < 0:
return "-", prefix, formatted_amount, suffix
return f"-{prefix}{formatted_amount}{suffix}"
else:
return "", prefix, formatted_amount, suffix
return f"{prefix}{formatted_amount}{suffix}"
else:
return "ERR"
return "", "", "ERR", ""
@register.simple_tag(name="currency_display")
def currency_display(amount, prefix, suffix, decimal_places):
return _format_string(prefix, amount, decimal_places, suffix)
sign, prefix, amount, suffix = _format_string(
prefix, amount, decimal_places, suffix
)
return {
"sign": sign,
"prefix": prefix,
"amount": amount,
"suffix": suffix,
}

View File

@@ -1,10 +1,15 @@
{% load currency_display %}
{% currency_display amount=amount prefix=prefix suffix=suffix decimal_places=decimal_places as formatted_amount %}
{% if not divless %}
<div class="{% if text_end %}text-end{% elif text_start %}text-start{% endif %}">
{% endif %}
<span class="amount{% if color == 'grey' or color == "gray" %} text-exchange-rate{% elif color == 'green' %} text-income {% elif color == 'red' %} text-expense{% endif %} font-medium {{ custom_class }}"
data-original-value="{% currency_display amount=amount prefix=prefix suffix=suffix decimal_places=decimal_places %}"
data-original-sign="{{ formatted_amount.sign }}"
data-original-prefix="{{ formatted_amount.prefix }}"
data-original-amount="{{ formatted_amount.amount }}"
data-original-suffix="{{ formatted_amount.suffix }}"
data-amount="{{ amount|floatformat:"-40u" }}">
</span><span>{{ slot }}</span>
{% if not divless %}

View File

@@ -3,7 +3,7 @@
<div class="text-center">
<i class="{% if icon %}{{ icon }}{% else %}fa-solid fa-circle-xmark{% endif %} text-6xl"></i>
<p class="text-lg mt-4 mb-0">{{ title }}</p>
<p class="text-gray-500">{{ subtitle }}</p>
<p class="text-subtle">{{ subtitle }}</p>
</div>
</div>
</div>

View File

@@ -1,7 +1,7 @@
{% load markdown %}
{% load i18n %}
<div
class="transaction {% if transaction.type == "EX" %}expense{% else %}income{% endif %} group/transaction relative group-hover/transaction:z-50 hover:z-50">
class="transaction {% if transaction.type == "EX" %}expense{% else %}income{% endif %} group/transaction relative">
<div class="flex my-1">
{% if not disable_selection or not dummy %}
<label class="px-3 flex! items-center justify-center">
@@ -16,7 +16,7 @@
<div class="flex flex-wrap font-mono text-sm items-center">
<div class="lg:w-auto w-full flex items-center text-2xl lg:text-xl lg:text-center text-center p-0 cursor-pointer">
{% if not transaction.deleted %}
<a class="no-underline p-3 text-gray-500!"
<a class="no-underline p-3 text-base-content/50"
title="{% if transaction.is_paid %}{% trans 'Paid' %}{% else %}{% trans 'Projected' %}{% endif %}"
role="button"
{% if not dummy %}
@@ -27,7 +27,7 @@
class="fa-regular fa-circle"></i>{% endif %}
</a>
{% else %}
<div class="no-underline p-3 text-gray-500!"
<div class="no-underline p-3 text-base-content/50"
title="{% if transaction.is_paid %}{% trans 'Paid' %}{% else %}{% trans 'Projected' %}{% endif %}">
{% if transaction.is_paid %}<i class="fa-regular fa-circle-check"></i>{% else %}<i
class="fa-regular fa-circle"></i>{% endif %}
@@ -138,16 +138,16 @@
<div class="z-1000">
{# Item actions#}
<div
class="card transaction-actions absolute! left-1/2 -translate-x-1/2 -translate-y-1/2 top-0 invisible group-hover/transaction:visible! flex flex-row bg-base-300">
class="card card-border-base-100 transaction-actions absolute left-1/2 -translate-x-1/2 -translate-y-1/2 top-0 invisible group-hover/transaction:visible flex flex-row bg-base-200">
<div class="card-body p-1 shadow-lg flex flex-row gap-1">
{% if not transaction.deleted %}
<a class="btn btn-neutral btn-sm transaction-action"
<a class="btn btn-soft btn-sm transaction-action"
role="button"
hx-get="{% url 'transaction_edit' transaction_id=transaction.id %}"
hx-target="#generic-offcanvas" hx-swap="innerHTML"
data-tippy-content="{% translate "Edit" %}">
<i class="fa-solid fa-pencil fa-fw"></i></a>
<a class="btn btn-neutral btn-sm transaction-action"
<a class="btn btn-error btn-soft btn-sm transaction-action"
role="button"
hx-delete="{% url 'transaction_delete' transaction_id=transaction.id %}"
hx-trigger='confirmed'
@@ -156,32 +156,32 @@
data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}"
data-confirm-text="{% translate "Yes, delete it!" %}"
_="install prompt_swal"><i class="fa-solid fa-trash fa-fw text-error"></i>
_="install prompt_swal"><i class="fa-solid fa-trash fa-fw"></i>
</a>
<button class="btn btn-neutral btn-sm transaction-action" data-bs-toggle="dropdown" data-bs-container="body" aria-expanded="false">
<button class="btn btn-soft btn-sm transaction-action" data-bs-toggle="dropdown" data-bs-container="body" aria-expanded="false">
<i class="fa-solid fa-ellipsis fa-fw"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end dropdown-menu-md-start menu w-max relative">
<div class="absolute inset-[0rem_-3rem_-3rem_-3rem] pointer-events-auto -z-10"></div>
{% if transaction.account.is_untracked_by %}
<li>
<a class="disabled flex items-center" aria-disabled="true">
<li class="menu-disabled" aria-disabled="true">
<a class="flex items-center">
<i class="fa-solid fa-eye fa-fw mr-2"></i>
<div>
{% translate 'Show on summaries' %}
<div
class="block text-gray-500 text-xs font-medium">{% translate 'Controlled by account' %}</div>
class="block text-base-content/60 text-xs font-medium">{% translate 'Controlled by account' %}</div>
</div>
</a>
</li>
{% elif transaction.category.mute %}
<li>
<a class="disabled flex items-center" aria-disabled="true">
<li class="menu-disabled" aria-disabled="true">
<a class="flex items-center">
<i class="fa-solid fa-eye fa-fw mr-2"></i>
<div>
{% translate 'Show on summaries' %}
<div
class="block text-gray-500 text-xs font-medium">{% translate 'Controlled by category' %}</div>
class="block text-base-content/60 text-xs font-medium">{% translate 'Controlled by category' %}</div>
</div>
</a>
</li>
@@ -218,13 +218,13 @@
</ul>
{% else %}
<div class="tooltip" data-tippy-content="{% translate "Restore" %}">
<a class="btn btn-secondary btn-sm transaction-action"
<a class="btn btn-success btn-soft btn-sm transaction-action"
role="button"
hx-get="{% url 'transaction_undelete' transaction_id=transaction.id %}"><i
class="fa-solid fa-trash-arrow-up"></i></a>
</div>
<div class="tooltip" data-tippy-content="{% translate "Delete" %}">
<a class="btn btn-secondary btn-sm transaction-action"
<a class="btn btn-error btn-soft btn-sm transaction-action"
role="button"
hx-delete="{% url 'transaction_delete' transaction_id=transaction.id %}"
hx-trigger='confirmed'

View File

@@ -2,204 +2,165 @@
{% load i18n %}
<div class="card bg-base-100 shadow-md card-border border-base-300">
<div class="card-body">
{% if account.account.group %}
<div class="text-sm mb-2">
<span class="badge badge-primary">{{ account.account.group }}</span>
</div>
{% endif %}
<h5 class="card-title">
{{ account.account.name }}
<h5 class="card-title mb-4">
{% if account.account.group %}<span class="badge badge-primary badge-outline">{{ account.account.group }}</span>{% endif %} {{ account.account.name }}
</h5>
<div class="flex justify-between items-baseline mt-2">
<div class="text-end ">
<div class="text-subtle">{% translate 'projected income' %}</div>
<div class="card-data-section">
<div class="card-data-row">
<span class="card-data-label">{% translate 'projected income' %}</span>
<div class="card-data-values">
{% if account.income_projected != 0 %}
<c-amount.display
:amount="account.income_projected"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="green"></c-amount.display>
{% if account.exchanged and account.exchanged.income_projected %}
<c-amount.display
:amount="account.exchanged.income_projected"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
{% endif %}
{% else %}
<span class="font-semibold">-</span>
{% endif %}
</div>
</div>
<div class="dotted-line grow"></div>
{% if account.income_projected != 0 %}
<div class="text-end">
<c-amount.display
:amount="account.income_projected"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="green"></c-amount.display>
<div class="card-data-row">
<span class="card-data-label">{% translate 'projected expenses' %}</span>
<div class="card-data-values">
{% if account.expense_projected != 0 %}
<c-amount.display
:amount="account.expense_projected"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="red"></c-amount.display>
{% if account.exchanged and account.exchanged.expense_projected %}
<c-amount.display
:amount="account.exchanged.expense_projected"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
{% endif %}
{% else %}
<span class="font-semibold">-</span>
{% endif %}
</div>
</div>
{% else %}
<div class="text-end ">-</div>
{% endif %}
</div>
{% if account.exchanged and account.exchanged.income_projected %}
<div class="text-end">
<c-amount.display
:amount="account.exchanged.income_projected"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
</div>
{% endif %}
<div class="flex justify-between items-baseline mt-2">
<div class="text-end ">
<div class="text-subtle">{% translate 'projected expenses' %}</div>
</div>
<div class="dotted-line grow"></div>
<div>
{% if account.expense_projected != 0 %}
<div class="text-end">
<div class="card-data-row">
<span class="card-data-label">{% translate 'projected total' %}</span>
<div class="card-data-values">
<c-amount.display
:amount="account.expense_projected"
:amount="account.total_projected"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="red"></c-amount.display>
color="{% if account.total_projected > 0 %}green{% elif account.total_projected < 0 %}red{% endif %}"></c-amount.display>
{% if account.exchanged.total_projected and account.exchanged.total_projected %}
<c-amount.display
:amount="account.exchanged.total_projected"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
{% endif %}
</div>
{% else %}
<div class="text-end ">-</div>
{% endif %}
</div>
</div>
{% if account.exchanged and account.exchanged.expense_projected %}
<div class="text-end">
<c-amount.display
:amount="account.exchanged.expense_projected"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
</div>
{% endif %}
<div class="flex justify-between items-baseline mt-2">
<div class="text-end ">
<div class="text-subtle">{% translate 'projected total' %}</div>
</div>
<div class="dotted-line grow"></div>
<div
class="text-end ">
<c-amount.display
:amount="account.total_projected"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="{% if account.total_projected > 0 %}green{% elif account.total_projected < 0 %}red{% endif %}"></c-amount.display>
</div>
</div>
{% if account.exchanged.total_projected and account.exchanged.total_projected %}
<div class="text-end">
<c-amount.display
:amount="account.exchanged.total_projected"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
</div>
{% endif %}
<hr class="my-3">
<div class="flex justify-between items-baseline mt-2">
<div class="text-end ">
<div class="text-subtle">{% translate 'current income' %}</div>
</div>
<div class="dotted-line grow"></div>
{% if account.income_current != 0 %}
<div class="text-end">
<c-amount.display
:amount="account.income_current"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="green"></c-amount.display>
</div>
{% else %}
<div class="text-end ">-</div>
{% endif %}
</div>
{% if account.exchanged and account.exchanged.income_current %}
<div class="text-end">
<c-amount.display
:amount="account.exchanged.income_current"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
</div>
{% endif %}
<div class="flex justify-between items-baseline mt-2">
<div class="text-end ">
<div class="text-subtle">{% translate 'current expenses' %}</div>
</div>
<div class="dotted-line"></div>
{% if account.expense_current != 0 %}
<div class="text-end">
<c-amount.display
:amount="account.expense_current"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="red"></c-amount.display>
</div>
{% else %}
<div class="text-end ">-</div>
{% endif %}
</div>
{% if account.exchanged and account.exchanged.expense_current %}
<div class="text-end">
<c-amount.display
:amount="account.exchanged.expense_current"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
</div>
{% endif %}
<div class="flex justify-between items-baseline mt-2">
<div class="text-end ">
<div class="text-subtle">{% translate 'current total' %}</div>
</div>
<div class="dotted-linegrow"></div>
<div class="text-end ">
<c-amount.display
:amount="account.total_current"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="{% if account.total_current > 0 %}green{% elif account.total_current < 0 %}red{% endif %}"></c-amount.display>
</div>
</div>
{% if account.exchanged and account.exchanged.total_current %}
<div class="text-end">
<c-amount.display
:amount="account.exchanged.total_current"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
</div>
{% endif %}
<div>
<hr class="my-3">
<div class="flex justify-between items-baseline mt-2">
<div class="text-end ">
<div class="text-subtle">{% translate 'final total' %}</div>
<hr class="card-data-divider" />
<div class="card-data-section">
<div class="card-data-row">
<span class="card-data-label">{% translate 'current income' %}</span>
<div class="card-data-values">
{% if account.income_current != 0 %}
<c-amount.display
:amount="account.income_current"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="green"></c-amount.display>
{% if account.exchanged and account.exchanged.income_current %}
<c-amount.display
:amount="account.exchanged.income_current"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
{% endif %}
{% else %}
<span class="font-semibold">-</span>
{% endif %}
</div>
<div class="dotted-line grow"></div>
<div class="text-end ">
</div>
<div class="card-data-row">
<span class="card-data-label">{% translate 'current expenses' %}</span>
<div class="card-data-values">
{% if account.expense_current != 0 %}
<c-amount.display
:amount="account.expense_current"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="red"></c-amount.display>
{% if account.exchanged and account.exchanged.expense_current %}
<c-amount.display
:amount="account.exchanged.expense_current"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
{% endif %}
{% else %}
<span class="font-semibold">-</span>
{% endif %}
</div>
</div>
<div class="card-data-row">
<span class="card-data-label">{% translate 'current total' %}</span>
<div class="card-data-values">
<c-amount.display
:amount="account.total_current"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="{% if account.total_current > 0 %}green{% elif account.total_current < 0 %}red{% endif %}"></c-amount.display>
{% if account.exchanged and account.exchanged.total_current %}
<c-amount.display
:amount="account.exchanged.total_current"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
{% endif %}
</div>
</div>
</div>
<hr class="card-data-divider" />
<div class="card-data-section">
<div class="card-data-row">
<span class="card-data-label">{% translate 'final total' %}</span>
<div class="card-data-values">
<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>
{% 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="gray"></c-amount.display>
{% endif %}
</div>
</div>
{% if account.exchanged and account.exchanged.total_final %}
<div class="text-end">
<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="gray"></c-amount.display>
</div>
{% endif %}
</div>
{% with p=percentages|get_dict_item:account_id %}
<div class="my-3">

View File

@@ -2,198 +2,165 @@
{% load i18n %}
<div class="col card bg-base-100 shadow card-border">
<div class="card-body">
<h5 class="card-title">
<h5 class="card-title mb-4">
{{ currency.currency.name }}
</h5>
<div class="flex justify-between items-baseline mt-2">
<div class="text-end">
<div class="text-subtle">{% translate 'projected income' %}</div>
<div class="card-data-section">
<div class="card-data-row">
<span class="card-data-label">{% translate 'projected income' %}</span>
<div class="card-data-values">
{% if currency.income_projected != 0 %}
<c-amount.display
:amount="currency.income_projected"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="green"></c-amount.display>
{% if currency.exchanged and currency.exchanged.income_projected %}
<c-amount.display
:amount="currency.exchanged.income_projected"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
{% endif %}
{% else %}
<span class="font-semibold">-</span>
{% endif %}
</div>
</div>
<div class="dotted-line grow"></div>
{% if currency.income_projected != 0 %}
<div class="text-end">
<c-amount.display
:amount="currency.income_projected"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="green"></c-amount.display>
<div class="card-data-row">
<span class="card-data-label">{% translate 'projected expenses' %}</span>
<div class="card-data-values">
{% if currency.expense_projected != 0 %}
<c-amount.display
:amount="currency.expense_projected"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="red"></c-amount.display>
{% if currency.exchanged and currency.exchanged.expense_projected %}
<c-amount.display
:amount="currency.exchanged.expense_projected"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
{% endif %}
{% else %}
<span class="font-semibold">-</span>
{% endif %}
</div>
</div>
{% else %}
<div class="text-end">-</div>
{% endif %}
</div>
{% if currency.exchanged and currency.exchanged.income_projected %}
<div class="text-end">
<c-amount.display
:amount="currency.exchanged.income_projected"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
</div>
{% endif %}
<div class="flex justify-between items-baseline mt-2">
<div class="text-end">
<div class="text-subtle">{% translate 'projected expenses' %}</div>
</div>
<div class="dotted-line grow"></div>
<div>
{% if currency.expense_projected != 0 %}
<div class="text-end">
<div class="card-data-row">
<span class="card-data-label">{% translate 'projected total' %}</span>
<div class="card-data-values">
<c-amount.display
:amount="currency.expense_projected"
:amount="currency.total_projected"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="red"></c-amount.display>
color="{% if currency.total_projected > 0 %}green{% elif currency.total_projected < 0 %}red{% endif %}"></c-amount.display>
{% if currency.exchanged.total_projected and currency.exchanged.total_projected %}
<c-amount.display
:amount="currency.exchanged.total_projected"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
{% endif %}
</div>
{% else %}
<div class="text-end">-</div>
{% endif %}
</div>
</div>
{% if currency.exchanged and currency.exchanged.expense_projected %}
<div class="text-end">
<c-amount.display
:amount="currency.exchanged.expense_projected"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
</div>
{% endif %}
<div class="flex justify-between items-baseline mt-2">
<div class="text-end">
<div class="text-subtle">{% translate 'projected total' %}</div>
</div>
<div class="dotted-linegrow"></div>
<div class="text-end">
<c-amount.display
:amount="currency.total_projected"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="{% if currency.total_projected > 0 %}green{% elif currency.total_projected < 0 %}red{% endif %}"></c-amount.display>
</div>
</div>
{% if currency.exchanged.total_projected and currency.exchanged.total_projected %}
<div class="text-end">
<c-amount.display
:amount="currency.exchanged.total_projected"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
</div>
{% endif %}
<hr class="my-3 hr">
<div class="flex justify-between items-baseline mt-2">
<div class="text-end">
<div class="text-subtle">{% translate 'current income' %}</div>
</div>
<div class="dotted-line grow"></div>
{% if currency.income_current != 0 %}
<div class="text-end">
<c-amount.display
:amount="currency.income_current"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="green"></c-amount.display>
</div>
{% else %}
<div class="text-end">-</div>
{% endif %}
</div>
{% if currency.exchanged and currency.exchanged.income_current %}
<div class="text-end">
<c-amount.display
:amount="currency.exchanged.income_current"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
</div>
{% endif %}
<div class="flex justify-between items-baseline mt-2">
<div class="text-end">
<div class="text-subtle">{% translate 'current expenses' %}</div>
</div>
<div class="dotted-line grow"></div>
{% if currency.expense_current != 0 %}
<div class="text-end">
<c-amount.display
:amount="currency.expense_current"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="red"></c-amount.display>
</div>
{% else %}
<div class="text-end">-</div>
{% endif %}
</div>
{% if currency.exchanged and currency.exchanged.expense_current %}
<div class="text-end">
<c-amount.display
:amount="currency.exchanged.expense_current"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
</div>
{% endif %}
<div class="flex justify-between items-baseline mt-2">
<div class="text-end">
<div class="text-subtle">{% translate 'current total' %}</div>
</div>
<div class="dotted-line grow"></div>
<div class="text-end">
<c-amount.display
:amount="currency.total_current"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="{% if currency.total_current > 0 %}green{% elif currency.total_current < 0 %}red{% endif %}"></c-amount.display>
</div>
</div>
{% if currency.exchanged and currency.exchanged.total_current %}
<div class="text-end">
<c-amount.display
:amount="currency.exchanged.total_current"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
</div>
{% endif %}
<div>
<hr class="my-3 hr">
<div class="flex justify-between items-baseline mt-2">
<div class="text-end">
<div class="text-subtle">{% translate 'final total' %}</div>
<hr class="card-data-divider" />
<div class="card-data-section">
<div class="card-data-row">
<span class="card-data-label">{% translate 'current income' %}</span>
<div class="card-data-values">
{% if currency.income_current != 0 %}
<c-amount.display
:amount="currency.income_current"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="green"></c-amount.display>
{% if currency.exchanged and currency.exchanged.income_current %}
<c-amount.display
:amount="currency.exchanged.income_current"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
{% endif %}
{% else %}
<span class="font-semibold">-</span>
{% endif %}
</div>
<div class="dotted-line grow"></div>
<div class="text-end">
</div>
<div class="card-data-row">
<span class="card-data-label">{% translate 'current expenses' %}</span>
<div class="card-data-values">
{% if currency.expense_current != 0 %}
<c-amount.display
:amount="currency.expense_current"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="red"></c-amount.display>
{% if currency.exchanged and currency.exchanged.expense_current %}
<c-amount.display
:amount="currency.exchanged.expense_current"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
{% endif %}
{% else %}
<span class="font-semibold">-</span>
{% endif %}
</div>
</div>
<div class="card-data-row">
<span class="card-data-label">{% translate 'current total' %}</span>
<div class="card-data-values">
<c-amount.display
:amount="currency.total_current"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="{% if currency.total_current > 0 %}green{% elif currency.total_current < 0 %}red{% endif %}"></c-amount.display>
{% if currency.exchanged and currency.exchanged.total_current %}
<c-amount.display
:amount="currency.exchanged.total_current"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
{% endif %}
</div>
</div>
</div>
<hr class="card-data-divider" />
<div class="card-data-section">
<div class="card-data-row">
<span class="card-data-label">{% translate 'final total' %}</span>
<div class="card-data-values">
<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 %}"></c-amount.display>
{% if currency.exchanged and currency.exchanged.total_final %}
<c-amount.display
:amount="currency.exchanged.total_final"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
{% endif %}
</div>
</div>
{% if currency.exchanged and currency.exchanged.total_final %}
<div class="text-end">
<c-amount.display
:amount="currency.exchanged.total_final"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
</div>
{% endif %}
</div>
{% with p=percentages|get_dict_item:currency_id %}
<div class="my-3">

View File

@@ -80,7 +80,7 @@
data-tippy-content="{% translate 'Duplicate' %}">
<i class="fa-solid fa-clone fa-fw"></i>
</button>
<button class="btn btn-secondary btn-sm"
<button class="btn btn-error btn-sm"
hx-get="{% url 'transactions_bulk_delete' %}"
hx-include=".transaction"
hx-trigger="confirmed"
@@ -90,7 +90,7 @@
data-text="{% translate "You won't be able to revert this!" %}"
data-confirm-text="{% translate "Yes, delete them!" %}"
_="install prompt_swal">
<i class="fa-solid fa-trash text-error"></i>
<i class="fa-solid fa-trash"></i>
</button>
<div class="divider divider-horizontal m-0"></div>
<div class="join"
@@ -139,7 +139,7 @@
wait 1s
put original_value into #real-total-front's innerText
end">
<i class="fa-solid fa-plus fa-fw me-md-2 text-primary"></i>
<i class="fa-solid fa-plus fa-fw me-md-2"></i>
<span class="hidden md:inline-block" id="real-total-front">0</span>
</button>
<div class="dropdown dropdown-end dropdown-top">
@@ -147,7 +147,7 @@
<i class="fa-solid fa-chevron-down fa-xs"></i>
</button>
<ul tabindex="0" class="dropdown-content menu bg-base-300 rounded-box z-[1] w-full shadow fixed!">
<ul tabindex="0" class="dropdown-content menu bg-base-300 rounded-box w-full shadow fixed! flex flex-col gap-1">
<li class="cursor-pointer"
_="on click
set original_value to #calc-menu-flat-total's innerText
@@ -156,13 +156,12 @@
wait 1s
put original_value into #calc-menu-flat-total
end">
<div class="p-0">
<div class="py-1 px-3">
<div>
<div class="text-base-content/60 text-xs font-medium px-3">
<div class="text-base-content/60 text-xs font-medium">
{% trans "Flat Total" %}
</div>
<div class="px-3"
id="calc-menu-flat-total">
<div id="calc-menu-flat-total">
0
</div>
</div>
@@ -176,13 +175,12 @@
wait 1s
put original_value into #calc-menu-real-total
end">
<div class="p-0">
<div class="py-1 px-3">
<div>
<div class="text-base-content/60 text-xs font-medium px-3">
<div class="text-base-content/60 text-xs font-medium">
{% trans "Real Total" %}
</div>
<div class="px-3"
id="calc-menu-real-total">
<div id="calc-menu-real-total">
0
</div>
</div>
@@ -196,13 +194,12 @@
wait 1s
put original_value into #calc-menu-mean
end">
<div class="p-0">
<div class="p-1 px-3">
<div>
<div class="text-base-content/60 text-xs font-medium px-3">
<div class="text-base-content/60 text-xs font-medium">
{% trans "Mean" %}
</div>
<div class="px-3"
id="calc-menu-mean">
<div id="calc-menu-mean">
0
</div>
</div>
@@ -216,13 +213,12 @@
wait 1s
put original_value into #calc-menu-max
end">
<div class="p-0">
<div class="py-1 px-3">
<div>
<div class="text-base-content/60 text-xs font-medium px-3">
<div class="text-base-content/60 text-xs font-medium">
{% trans "Max" %}
</div>
<div class="px-3"
id="calc-menu-max">
<div id="calc-menu-max">
0
</div>
</div>
@@ -236,13 +232,12 @@
wait 1s
put original_value into #calc-menu-min
end">
<div class="p-0">
<div class="py-1 px-3">
<div>
<div class="text-base-content/60 text-xs font-medium px-3">
<div class="text-base-content/60 text-xs font-medium">
{% trans "Min" %}
</div>
<div class="px-3"
id="calc-menu-min">
<div id="calc-menu-min">
0
</div>
</div>
@@ -256,13 +251,12 @@
wait 1s
put original_value into #calc-menu-count
end">
<div class="p-0">
<div class="py-1 px-3">
<div>
<div class="text-base-content/60 text-xs font-medium px-3">
<div class="text-base-content/60 text-xs font-medium">
{% trans "Count" %}
</div>
<div class="px-3"
id="calc-menu-count">
<div id="calc-menu-count">
0
</div>
</div>

View File

@@ -2,11 +2,11 @@
{% vite_hmr_client %}
{% vite_asset 'bootstrap.js' defer=True %}
{% vite_asset 'sweetalert2.js' defer=True %}
{% vite_asset 'main.js' defer=True %}
{% comment %} {% vite_asset 'sweetalert2.js' defer=True %}
{% vite_asset 'select.js' defer=True %}
{% vite_asset 'datepicker.js' %}
{% vite_asset 'autosize.js' defer=True %}
{% vite_asset 'autosize.js' defer=True %} {% endcomment %}
{% include 'includes/scripts/hyperscript/init_tom_select.html' %}
{% include 'includes/scripts/hyperscript/init_date_picker.html' %}
@@ -16,10 +16,10 @@
{% include 'includes/scripts/hyperscript/sounds.html' %}
{% include 'includes/scripts/hyperscript/swal.html' %}
{% vite_asset 'htmx.js' defer=True %}
{% comment %} {% vite_asset 'htmx.js' defer=True %}
{% vite_asset 'charts.js' %}
{% vite_asset 'style.js' %}
{% vite_asset 'style.js' %} {% endcomment %}
<script>
let tz = Intl.DateTimeFormat().resolvedOptions().timeZone;

View File

@@ -11,13 +11,22 @@
set elements to <.amount/> in me
for el in elements
set el.textContent to el.dataset.originalValue
set el.innerHTML to `<span>${el.dataset.originalSign}</span><span>${el.dataset.originalPrefix}</span><span>${el.dataset.originalAmount}</span><span>${el.dataset.originalSuffix}</span>`
end
end
on click[target matches .amount] if I include #settings-hide-amounts
if event.target do not matches .revealed then set event.target.textContent to event.target.dataset.originalValue
else set event.target.textContent to '•••••••••••' end
then toggle .revealed on event.target
on click[target matches .amount or target.parentNode matches .amount] if I include #settings-hide-amounts
if event.target matches .amount
set el to event.target
else
set el to event.target.parentNode
end
if el do not matches .revealed
then set el.innerHTML to `<span>${el.dataset.originalSign}</span><span>${el.dataset.originalPrefix}</span><span>${el.dataset.originalAmount}</span><span>${el.dataset.originalSuffix}</span>`
else
set el.textContent to '•••••••••••' end
then toggle .revealed on el
end
end
</script>

View File

@@ -59,7 +59,7 @@
{
label: "{% trans 'Projected Expenses' %}",
data: accountData.datasets[3].data,
backgroundColor: '#f8717180', // Added transparency
backgroundColor: '#f8717180',
stack: 'stack0'
},
{
@@ -77,7 +77,7 @@
{
label: "{% trans 'Projected Income' %}",
data: accountData.datasets[2].data,
backgroundColor: '#4dde8080', // Added transparency
backgroundColor: '#4dde8080',
stack: 'stack0'
},

View File

@@ -59,7 +59,7 @@
{
label: "{% trans 'Projected Expenses' %}",
data: currencyData.datasets[3].data,
backgroundColor: '#f8717180', // Added transparency
backgroundColor: '#f8717180',
stack: 'stack0'
},
{
@@ -77,7 +77,7 @@
{
label: "{% trans 'Projected Income' %}",
data: currencyData.datasets[2].data,
backgroundColor: '#4dde8080', // Added transparency
backgroundColor: '#4dde8080',
stack: 'stack0'
},

View File

@@ -1,37 +1,45 @@
{% load i18n %}
{% load crispy_forms_tags %}
<form _="install init_tom_select
on change trigger updated
init trigger updated" id="category-form">
{% crispy category_form %}
</form>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-3">
<div class="w-full">
<div class="card bg-base-100 shadow-xl h-full">
<div class="card-header bg-base-200 p-4 font-semibold">
{% trans "Income/Expense by Account" %}
</div>
<div class="card-body">
<div id="account-card" class="show-loading" hx-get="{% url 'category_sum_by_account' %}"
hx-trigger="updated from:window" hx-include="#category-form, #picker-form, #picker-type">
</div>
</div>
</div>
</div>
<div class="w-full">
<div class="card bg-base-100 shadow-xl h-full">
<div class="card-header bg-base-200 p-4 font-semibold">
{% trans "Income/Expense by Currency" %}
</div>
<div class="card-body">
<div id="currency-card" class="show-loading" hx-get="{% url 'category_sum_by_currency' %}"
hx-trigger="updated from:window" hx-include="#category-form, #picker-form, #picker-type">
<div class="flex flex-col gap-3">
<div class="row">
<div class="col-12">
<div class="card card-border bg-base-100">
<div class="card-body">
<form _="install init_tom_select
on change trigger updated
init trigger updated" id="category-form">
{% crispy category_form %}
</form>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12 lg:col-6">
<div class="card bg-base-100 shadow-xl h-full">
<div class="card-header bg-base-200 p-4 font-semibold rounded-box shadow">
{% trans "Income/Expense by Account" %}
</div>
<div class="card-body">
<div id="account-card" class="show-loading" hx-get="{% url 'category_sum_by_account' %}"
hx-trigger="updated from:window" hx-include="#category-form, #picker-form, #picker-type">
</div>
</div>
</div>
</div>
<div class="col-12 lg:col-6">
<div class="card bg-base-100 shadow-xl h-full">
<div class="card-header bg-base-200 p-4 font-semibold rounded-box shadow">
{% trans "Income/Expense by Currency" %}
</div>
<div class="card-body">
<div id="currency-card" class="show-loading" hx-get="{% url 'category_sum_by_currency' %}"
hx-trigger="updated from:window" hx-include="#category-form, #picker-form, #picker-type">
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -3,56 +3,58 @@
<div hx-get="{% url 'category_overview' %}" hx-trigger="updated from:window" class="show-loading" hx-swap="outerHTML"
hx-include="#picker-form, #picker-type, #view-type, #show-tags, #showing, #show-entities">
<div class="h-full text-center mb-4">
<div class="join" role="group" id="view-type" _="on change trigger updated">
<input type="radio" class="join-item btn btn-outline btn-primary rounded-full"
name="view_type"
id="table-view"
autocomplete="off"
value="table"
aria-label="{% trans 'Table' %}"
{% if view_type == "table" %}checked{% endif %}>
<input type="radio"
class="join-item btn btn-outline btn-primary rounded-full"
name="view_type"
id="bars-view"
autocomplete="off"
value="bars"
aria-label="{% trans 'Bars' %}"
{% if view_type == "bars" %}checked{% endif %}>
<div class="tabs tabs-box mx-auto w-fit" role="group" id="view-type" _="on change trigger updated">
<label class="tab">
<input type="radio"
name="view_type"
id="table-view"
autocomplete="off"
value="table"
aria-label="{% trans 'Table' %}"
{% if view_type == "table" %}checked{% endif %}>
<i class="fa-solid fa-table fa-fw me-2"></i>
{% trans 'Table' %}
</label>
<label class="tab">
<input type="radio"
name="view_type"
id="bars-view"
autocomplete="off"
value="bars"
aria-label="{% trans 'Bars' %}"
{% if view_type == "bars" %}checked{% endif %}>
<i class="fa-solid fa-chart-bar fa-fw me-2"></i>
{% trans 'Bars' %}
</label>
</div>
</div>
<div class="mt-3 mb-1 flex flex-col md:flex-row justify-between">
<div class="my-3 flex flex-col gap-3 md:flex-row justify-between">
<div class="flex gap-4">
{% if view_type == 'table' %}
<div class="form-control" id="show-tags">
<input type="hidden" name="show_tags" value="off">
<label class="label cursor-pointer gap-2">
<input type="checkbox" class="toggle" id="show-tags-switch" name="show_tags"
<div id="show-tags">
<label class="label">
<input type="hidden" name="show_tags" value="off">
<input type="checkbox" class="toggle toggle-primary toggle-sm" id="show-tags-switch" name="show_tags"
_="on change trigger updated" {% if show_tags %}checked{% endif %}>
{% spaceless %}
<span class="label-text">
<span>
{% trans 'Tags' %}
</span>
<c-ui.help-icon
content="{% trans 'Transaction amounts associated with multiple tags will be counted once for each tag' %}"
icon="fa-solid fa-circle-exclamation"></c-ui.help-icon>
{% endspaceless %}
</span>
<c-ui.help-icon
content="{% trans 'Transaction amounts associated with multiple tags will be counted once for each tag' %}"
icon="fa-solid fa-circle-exclamation"></c-ui.help-icon>
</label>
</div>
<div class="form-control" id="show-entities" {% if not show_tags %}style="display: none;"{% endif %}>
<input type="hidden" name="show_entities" value="off">
<label class="label cursor-pointer gap-2">
<input type="checkbox" class="toggle" id="show-entities-switch" name="show_entities"
<div id="show-entities" class="{% if not show_tags %}hidden{% endif %}">
<label class="label">
<input type="hidden" name="show_entities" value="off">
<input type="checkbox" class="toggle toggle-primary toggle-sm" id="show-entities-switch" name="show_entities"
_="on change trigger updated" {% if show_entities %}checked{% endif %}>
{% spaceless %}
<span class="label-text">
<span>
{% trans 'Entities' %}
</span>
<c-ui.help-icon
content="{% trans 'Transaction amounts associated with multiple tags and entities will be counted once for each tag' %}"
icon="fa-solid fa-circle-exclamation"></c-ui.help-icon>
{% endspaceless %}
</span>
<c-ui.help-icon
content="{% trans 'Transaction amounts associated with multiple tags will be counted once for each tag' %}"
icon="fa-solid fa-circle-exclamation"></c-ui.help-icon>
</label>
</div>
{% endif %}
@@ -70,8 +72,10 @@
</div>
{% if total_table %}
{% if view_type == "table" %}
<div class="overflow-x-auto">
<table class="table table-zebra">
<div class="card bg-base-100 card-border">
<c-config.search></c-config.search>
<div class="overflow-x-auto">
<table class="table">
<thead>
<tr>
<th scope="col">{% trans 'Category' %}</th>
@@ -176,7 +180,7 @@
{% for tag_id, tag in category.tags.items %}
{% if tag.name or not tag.name and category.tags.values|length > 1 %}
<tr class="bg-base-200">
<td class="ps-4">
<td class="ps-6">
<i class="fa-solid fa-hashtag fa-fw me-2 text-base-content/60"></i>{% if tag.name %}{{ tag.name }}{% else %}{% trans 'Untagged' %}{% endif %}
</td>
<td>
@@ -269,7 +273,7 @@
{% for entity_id, entity in tag.entities.items %}
{% if entity.name or not entity.name and tag.entities.values|length > 1 %}
<tr class="bg-base-300">
<td class="ps-5">
<td class="ps-10">
<i class="fa-solid fa-user-group fa-fw me-2 text-base-content/60"></i>{% if entity.name %}{{ entity.name }}{% else %}{% trans 'No entity' %}{% endif %}
</td>
<td>
@@ -366,13 +370,16 @@
{% endfor %}
</tbody>
</table>
</div>
</div>
{% elif view_type == "bars" %}
<div>
<div class="chart-container relative h-[78vh] w-full" _="init call setupChart() end">
<canvas id="categoryChart"></canvas>
<div class="card bg-base-100 card-border">
<div class="card-body">
<div class="chart-container relative h-[75vh] w-full" _="init call setupChart() end">
<canvas id="categoryChart"></canvas>
</div>
</div>
</div>

View File

@@ -1,7 +1,7 @@
{% load i18n %}
<div hx-get="{% url 'insights_late_transactions' %}" hx-trigger="updated from:window" class="show-loading"
<div hx-get="{% url 'insights_latest_transactions' %}" hx-trigger="updated from:window" class="show-loading"
id="transactions-list" hx-swap="outerHTML">
{% if transactions %}
{% for transaction in transactions %}

View File

@@ -7,7 +7,7 @@
<div class="show-loading" hx-get="{% url 'insights_sankey_by_currency' %}" hx-trigger="updated from:window"
hx-swap="outerHTML" hx-include="#picker-form, #picker-type">
{% endif %}
<div class="chart-container relative min-h-[85vh] max-h-[85vh] h-full w-full"
<div class="card bg-base-100 card-border chart-container relative min-h-[85vh] max-h-[85vh] h-full w-full"
id="sankeyContainer"
_="init call setupSankeyChart() end">
<canvas id="sankeyChart"></canvas>
@@ -15,7 +15,14 @@
</div>
<script>
var style = getComputedStyle(document.body);
var incomeColor = style.getPropertyValue('--color-success');
var expenseColor = style.getPropertyValue('--color-error');
var primaryColor = style.getPropertyValue('--color-primary');
var contentColor = style.getPropertyValue('--color-base-content');
console.log('Sankey colors:', incomeColor, expenseColor, primaryColor, contentColor);
var data = {{ sankey_data|safe }};
console.log(convertColorToRgba(incomeColor))
function setupSankeyChart(chartId = 'sankeyChart') {
function formatCurrency(value, currency) {
@@ -35,11 +42,11 @@
const colors = {};
data.nodes.forEach(node => {
if (node.id.startsWith('income_')) {
colors[node.id] = '#4dde80'; // Green for income
colors[node.id] = convertColorToRgba(incomeColor); // Green for income
} else if (node.id.startsWith('expense_')) {
colors[node.id] = '#f87171'; // Red for expenses
colors[node.id] = convertColorToRgba(expenseColor); // Red for expenses
} else {
colors[node.id] = '#fbb700'; // Primary for others
colors[node.id] = convertColorToRgba(primaryColor); // Primary for others
}
});
@@ -63,7 +70,7 @@
colorMode: 'gradient',
alpha: 0.5,
size: 'max',
color: "white",
color: contentColor,
nodePadding: 30,
priority: data.nodes.reduce((acc, node) => {
acc[node.id] = node.priority;

View File

@@ -6,8 +6,8 @@
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-12 md:col-3">
<div class="row gy-3">
<div class="col-12 md:col-3 gy-3">
<div class="flex flex-col gap-3">
<div class="card bg-base-100 card-borderS">
<div class="card-body">
@@ -73,22 +73,22 @@
remove .btn-active from <.insights-list button/>
add .btn-active to event.target
set event.target's @aria-selected to 'true'">
<button class="btn btn-ghost justify-start"
<button class="btn btn-ghost justify-start text-start"
type="button" role="tab" aria-controls="v-pills-content" aria-selected="false"
hx-get="{% url 'insights_sankey_by_account' %}">
{% trans 'Account Flow' %}
</button>
<button class="btn btn-ghost justify-start" data-bs-target="#v-pills-content"
<button class="btn btn-ghost justify-start text-start" data-bs-target="#v-pills-content"
type="button" role="tab" aria-controls="v-pills-content" aria-selected="false"
hx-get="{% url 'insights_sankey_by_currency' %}"
>{% trans 'Currency Flow' %}
</button>
<button class="btn btn-ghost justify-start" data-bs-target="#v-pills-content"
<button class="btn btn-ghost justify-start text-start" data-bs-target="#v-pills-content"
type="button" role="tab" aria-controls="v-pills-content" aria-selected="false"
hx-get="{% url 'category_explorer_index' %}">
{% trans 'Category Explorer' %}
</button>
<button class="btn btn-ghost justify-start" data-bs-target="#v-pills-content"
<button class="btn btn-ghost justify-start text-start" data-bs-target="#v-pills-content"
type="button" role="tab" aria-controls="v-pills-content" aria-selected="false"
hx-get="{% url 'category_overview' %}"
>{% trans 'Categories Overview' %}
@@ -106,17 +106,17 @@
remove .btn-active from <.insights-list button/>
add .btn-active to event.target
set event.target's @aria-selected to 'true'">
<button class="btn btn-ghost justify-start" data-bs-target="#v-pills-content"
<button class="btn btn-ghost justify-start text-start" data-bs-target="#v-pills-content"
type="button" role="tab" aria-controls="v-pills-content" aria-selected="false"
hx-get="{% url 'insights_late_transactions' %}">
{% trans 'Late Transactions' %}
</button>
<button class="btn btn-ghost justify-start" data-bs-target="#v-pills-content"
<button class="btn btn-ghost justify-start text-start" data-bs-target="#v-pills-content"
type="button" role="tab" aria-controls="v-pills-content" aria-selected="false"
hx-get="{% url 'insights_latest_transactions' %}">
{% trans 'Latest Transactions' %}
</button>
<button class="btn btn-ghost justify-start" data-bs-target="#v-pills-content"
<button class="btn btn-ghost justify-start text-start" data-bs-target="#v-pills-content"
type="button" role="tab" aria-controls="v-pills-content" aria-selected="false"
hx-get="{% url 'insights_emergency_fund' %}">
{% trans 'Emergency Fund' %}
@@ -126,7 +126,7 @@
</div>
</div>
</div>
<div class="col-12 md:col-9">
<div class="col-12 md:col-9 gy-3">
<div id="tab-content" class="show-loading"></div>
</div>
</div>

View File

@@ -47,7 +47,7 @@
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
{% include 'includes/mobile_navbar.html' %}
{% include 'includes/sidebar.html' %}
<main class="my-4 px-3">
<main class="my-8 px-3">
{% settings "DEMO" as demo_mode %}
{% if demo_mode %}
<div class="px-3 m-0" id="demo-mode-alert" hx-preserve>

View File

@@ -17,22 +17,31 @@
class="show-loading"
hx-get=""
hx-target="body">
<div class="h-full text-center mb-4 pt-2 w-full">
<div class="h-full text-center mb-4 w-full">
<div class="tabs tabs-box mx-auto w-fit"
id="view-type"
_="on change trigger updated">
<input type="radio"
name="view_type"
class="tab"
aria-label="{% trans "Current" %}"
value="current"
{% if type == "current" %}checked{% endif %} />
<input type="radio"
name="view_type"
class="tab"
aria-label="{% trans "Projected" %}"
value="projected"
{% if type == "projected" %}checked{% endif %} />
<label class="tab">
<input type="radio"
name="view_type"
class="tab"
aria-label="{% trans "Current" %}"
value="current"
{% if type == "current" %}checked{% endif %} />
<i class="fa-solid fa-sack-dollar fa-fw me-2"></i>
{% trans "Current" %}
</label>
<label class="tab">
<input type="radio"
name="view_type"
class="tab"
aria-label="{% trans "Projected" %}"
value="projected"
{% if type == "projected" %}checked{% endif %} />
<i class="fa-solid fa-rocket fa-fw me-2"></i>
{% trans "Projected" %}
</label>
</div>
</div>
<div class="container px-md-3 py-3"
@@ -45,10 +54,10 @@
{% for currency in currency_net_worth.values %}
<li>
{% if currency.consolidated and currency.consolidated.total_final != currency.total_final %}
<a class="cursor-pointer flex justify-between items-center w-full"
<a class="cursor-pointer select-auto flex justify-between items-center w-full"
_="on click showOnlyCurrencyDataset('{{ currency.currency.name }}')">
<span class="currency-name text-start font-mono flex-shrink text-ellipsis">{{ currency.currency.name }}</span>
<span class="text-end flex-shrink-0">
<span class="currency-name text-start font-mono shrink text-ellipsis">{{ currency.currency.name }}</span>
<span class="text-end shrink-0">
<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>
@@ -61,19 +70,19 @@
</a>
<ul>
<li>
<a class="text-base-content/60">
<span class="text-start font-mono flex-shrink">{% trans "Consolidated" %}</span>
<span class="text-end flex-shrink-0">
<a class="text-base-content/60 select-auto">
<span class="text-start shrink">{% trans "Consolidated" %}</span>
<span class="text-end shrink-0">
<c-amount.display :amount="currency.consolidated.total_final" :prefix="currency.consolidated.currency.prefix" :suffix="currency.consolidated.currency.suffix" :decimal_places="currency.consolidated.currency.decimal_places" color="{% if currency.consolidated.total_final > 0 %}green{% elif currency.consolidated.total_final < 0 %}red{% endif %}" text-end></c-amount.display>
</span>
</a>
</li>
</ul>
{% else %}
<a class="cursor-pointer flex justify-between items-center w-full"
<a class="cursor-pointer select-auto flex justify-between items-center w-full"
_="on click showOnlyCurrencyDataset('{{ currency.currency.name }}')">
<span class="currency-name text-start font-mono flex-shrink">{{ currency.currency.name }}</span>
<span class="text-end flex-shrink-0">
<span class="currency-name text-start font-mono shrink">{{ currency.currency.name }}</span>
<span class="text-end shrink-0">
<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>
@@ -134,16 +143,16 @@
{% if data.grouper %}
<li>
<details open>
<summary class="font-mono">
<summary class="select-auto">
<span class="badge badge-primary">{{ data.grouper }}</span>
</summary>
<ul>
{% for account in data.list %}
<li>
<a class="cursor-pointer flex justify-between items-center w-full"
<a class="cursor-pointer select-auto flex justify-between items-center w-full"
_="on click showOnlyAccountDataset('{{ account.account.name }}')">
<span class="account-name text-start font-mono flex-shrink text-ellipsis">{{ account.account.name }}</span>
<span class="text-end flex-shrink-0">
<span class="account-name text-start font-mono shrink text-ellipsis">{{ account.account.name }}</span>
<span class="text-end shrink-0">
<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>
@@ -162,10 +171,10 @@
{% else %}
{% for account in data.list %}
<li>
<a class="cursor-pointer flex justify-between items-center w-full"
<a class="cursor-pointer flex select-auto justify-between items-center w-full"
_="on click showOnlyAccountDataset('{{ account.account.name }}')">
<span class="account-name text-start font-mono flex-shrink">{{ account.account.name }}</span>
<span class="text-end flex-shrink-0">
<span class="account-name text-start font-mono shrink">{{ account.account.name }}</span>
<span class="text-end shrink-0">
<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>

View File

@@ -44,7 +44,7 @@
_="on click
remove .btn-active from <button/> in #filter-pills
add .btn-active to me">
{% if account.group.name %}<span class="badge badge-primary me-2">{{ account.group.name }}</span>{% endif %} {{ account.name }}
{% if account.group.name %}<span class="badge badge-primary badge-outline">{{ account.group.name }}</span>{% endif %} {{ account.name }}
</button>
{% endfor %}
</div>

View File

@@ -11,7 +11,7 @@
{% endblock %}
{% block content %}
<div class="h-full text-center mb-4 pt-2 w-full">
<div class="h-full text-center mb-4 w-full">
<div role="tablist" class="tabs tabs-box mx-auto w-fit">
<a href="{% url 'yearly_overview_currency' year=year %}" class="tab {% if type == 'currency' %}tab-active{% endif %}" hx-boost>
<i class="fa-solid fa-solid fa-coins fa-fw me-2"></i>{% trans 'Currency' %}

37
frontend/src/js/_utils.js Normal file
View File

@@ -0,0 +1,37 @@
/**
* Converts ANY valid CSS color string (oklch, hex, hsl, etc.)
* into a standard RGBA string that Chart.js can understand.
* This method uses a canvas to force the browser to compute the color.
* @param {string} colorString The color string to convert.
* @returns {string} The computed 'rgba(r, g, b, a)' string.
*/
window.convertColorToRgba = function convertColorToRgba(colorString) {
if (!colorString) return 'rgba(0,0,0,0.1)'; // Fallback
console.log(colorString)
// Create a 1x1 pixel canvas
let canvas = document.createElement('canvas');
canvas.width = 1;
canvas.height = 1;
let ctx = canvas.getContext('2d');
// Set the fillStyle to the color string
// The browser MUST parse the oklch string here
ctx.fillStyle = colorString.trim();
// Draw the pixel
ctx.fillRect(0, 0, 1, 1);
// Get the pixel data. This is ALWAYS returned as [R, G, B, A]
// with values from 0-255.
const data = ctx.getImageData(0, 0, 1, 1).data;
// Convert the 0-255 alpha to a 0-1 float
const a = data[3] / 255;
console.log(data)
// Return the standard rgba string
return `rgba(${data[0]}, ${data[1]}, ${data[2]}, ${a})`;
}

View File

@@ -1,4 +1,4 @@
import './js/_tooltip.js';
import './_tooltip.js';
import 'bootstrap/js/dist/dropdown';
import Toast from 'bootstrap/js/dist/toast';
import 'bootstrap/js/dist/dropdown';

View File

@@ -1,4 +1,4 @@
import '@fontsource-variable/jetbrains-mono/wght-italic.css';
import '@fontsource-variable/jetbrains-mono';
import './styles/tailwind.css';
import './styles/style.scss';
import '../styles/tailwind.css';
import '../styles/style.scss';

9
frontend/src/main.js Normal file
View File

@@ -0,0 +1,9 @@
import './js/bootstrap.js';
import './js/datepicker.js';
import './js/htmx.js';
import './js/select.js';
import './js/charts.js';
import './js/autosize.js';
import './js/sweetalert2.js';
import './js/style.js';
import './js/_utils.js';

View File

@@ -139,24 +139,46 @@
}
.text-exchange-rate {
@apply text-base-content/60;
@apply text-base-content/60 text-xs;
}
.text-subtle {
@apply text-base-content/80;
}
/* Card Data Display Styles */
.card-data-section {
@apply space-y-1;
}
.card-data-row {
@apply flex justify-between py-1 px-3 rounded-lg transition-colors duration-150 hover:bg-base-200/50 items-center;
}
.card-data-label {
@apply text-base-content/80; /* .text-subtle */
@apply text-sm font-medium;
}
.card-data-values {
@apply flex flex-col items-end gap-0.5;
}
.card-data-divider {
@apply text-base-content/60; /* .hr */
@apply my-3;
}
}
@layer components {
.fab {
@layer daisyui.component {
> :nth-child(n+7) {
transition-delay: 150ms;
@layer daisyui.component {
> :nth-child(n+7) {
transition-delay: 150ms;
}
}
}
}
}
/* === Sidebar styles === */
.sidebar-active {
@@ -173,7 +195,7 @@
.sidebar-floating {
/* Establishes the hover group and sets the collapsed/hover widths for the container */
@apply lg:w-16 lg:hover:w-112;
@apply lg:w-16 lg:hover:w-md;
}
.sidebar-floating #sidebar {

View File

@@ -10,14 +10,7 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const rollupInputs = {
autosize: resolve(__dirname, 'src/autosize.js'),
charts: resolve(__dirname, 'src/charts.js'),
datepicker: resolve(__dirname, 'src/datepicker.js'),
bootstrap: resolve(__dirname, 'src/bootstrap.js'),
htmx: resolve(__dirname, 'src/htmx.js'),
select: resolve(__dirname, 'src/select.js'),
style: resolve(__dirname, 'src/style.js'),
sweetalert2: resolve(__dirname, 'src/sweetalert2.js'),
main: resolve(__dirname, 'src/main.js'),
};
@@ -42,6 +35,7 @@ export default defineConfig({
usePolling: true,
disableGlobbing: false,
},
hmr: false,
cors: true,
origin: 'http://100.118.164.62:5173'
},