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 import template
from django.utils.formats import number_format from django.utils.formats import number_format
register = template.Library() 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 value=abs(amount), decimal_pos=decimal_places, force_grouping=True
) )
if amount < 0: if amount < 0:
return "-", prefix, formatted_amount, suffix
return f"-{prefix}{formatted_amount}{suffix}" return f"-{prefix}{formatted_amount}{suffix}"
else: else:
return "", prefix, formatted_amount, suffix
return f"{prefix}{formatted_amount}{suffix}" return f"{prefix}{formatted_amount}{suffix}"
else: else:
return "ERR" return "", "", "ERR", ""
@register.simple_tag(name="currency_display") @register.simple_tag(name="currency_display")
def currency_display(amount, prefix, suffix, decimal_places): 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 %} {% load currency_display %}
{% currency_display amount=amount prefix=prefix suffix=suffix decimal_places=decimal_places as formatted_amount %}
{% if not divless %} {% if not divless %}
<div class="{% if text_end %}text-end{% elif text_start %}text-start{% endif %}"> <div class="{% if text_end %}text-end{% elif text_start %}text-start{% endif %}">
{% 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 }}" <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" }}"> data-amount="{{ amount|floatformat:"-40u" }}">
</span><span>{{ slot }}</span> </span><span>{{ slot }}</span>
{% if not divless %} {% if not divless %}

View File

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

View File

@@ -1,7 +1,7 @@
{% load markdown %} {% load markdown %}
{% load i18n %} {% load i18n %}
<div <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"> <div class="flex my-1">
{% if not disable_selection or not dummy %} {% if not disable_selection or not dummy %}
<label class="px-3 flex! items-center justify-center"> <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="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"> <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 %} {% 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 %}" title="{% if transaction.is_paid %}{% trans 'Paid' %}{% else %}{% trans 'Projected' %}{% endif %}"
role="button" role="button"
{% if not dummy %} {% if not dummy %}
@@ -27,7 +27,7 @@
class="fa-regular fa-circle"></i>{% endif %} class="fa-regular fa-circle"></i>{% endif %}
</a> </a>
{% else %} {% 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 %}"> 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 {% if transaction.is_paid %}<i class="fa-regular fa-circle-check"></i>{% else %}<i
class="fa-regular fa-circle"></i>{% endif %} class="fa-regular fa-circle"></i>{% endif %}
@@ -138,16 +138,16 @@
<div class="z-1000"> <div class="z-1000">
{# Item actions#} {# Item actions#}
<div <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"> <div class="card-body p-1 shadow-lg flex flex-row gap-1">
{% if not transaction.deleted %} {% if not transaction.deleted %}
<a class="btn btn-neutral btn-sm transaction-action" <a class="btn btn-soft btn-sm transaction-action"
role="button" role="button"
hx-get="{% url 'transaction_edit' transaction_id=transaction.id %}" hx-get="{% url 'transaction_edit' transaction_id=transaction.id %}"
hx-target="#generic-offcanvas" hx-swap="innerHTML" hx-target="#generic-offcanvas" hx-swap="innerHTML"
data-tippy-content="{% translate "Edit" %}"> data-tippy-content="{% translate "Edit" %}">
<i class="fa-solid fa-pencil fa-fw"></i></a> <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" role="button"
hx-delete="{% url 'transaction_delete' transaction_id=transaction.id %}" hx-delete="{% url 'transaction_delete' transaction_id=transaction.id %}"
hx-trigger='confirmed' hx-trigger='confirmed'
@@ -156,32 +156,32 @@
data-title="{% translate "Are you sure?" %}" data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}" data-text="{% translate "You won't be able to revert this!" %}"
data-confirm-text="{% translate "Yes, delete it!" %}" 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> </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> <i class="fa-solid fa-ellipsis fa-fw"></i>
</button> </button>
<ul class="dropdown-menu dropdown-menu-end dropdown-menu-md-start menu w-max relative"> <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> <div class="absolute inset-[0rem_-3rem_-3rem_-3rem] pointer-events-auto -z-10"></div>
{% if transaction.account.is_untracked_by %} {% if transaction.account.is_untracked_by %}
<li> <li class="menu-disabled" aria-disabled="true">
<a class="disabled flex items-center" aria-disabled="true"> <a class="flex items-center">
<i class="fa-solid fa-eye fa-fw mr-2"></i> <i class="fa-solid fa-eye fa-fw mr-2"></i>
<div> <div>
{% translate 'Show on summaries' %} {% translate 'Show on summaries' %}
<div <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> </div>
</a> </a>
</li> </li>
{% elif transaction.category.mute %} {% elif transaction.category.mute %}
<li> <li class="menu-disabled" aria-disabled="true">
<a class="disabled flex items-center" aria-disabled="true"> <a class="flex items-center">
<i class="fa-solid fa-eye fa-fw mr-2"></i> <i class="fa-solid fa-eye fa-fw mr-2"></i>
<div> <div>
{% translate 'Show on summaries' %} {% translate 'Show on summaries' %}
<div <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> </div>
</a> </a>
</li> </li>
@@ -218,13 +218,13 @@
</ul> </ul>
{% else %} {% else %}
<div class="tooltip" data-tippy-content="{% translate "Restore" %}"> <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" role="button"
hx-get="{% url 'transaction_undelete' transaction_id=transaction.id %}"><i hx-get="{% url 'transaction_undelete' transaction_id=transaction.id %}"><i
class="fa-solid fa-trash-arrow-up"></i></a> class="fa-solid fa-trash-arrow-up"></i></a>
</div> </div>
<div class="tooltip" data-tippy-content="{% translate "Delete" %}"> <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" role="button"
hx-delete="{% url 'transaction_delete' transaction_id=transaction.id %}" hx-delete="{% url 'transaction_delete' transaction_id=transaction.id %}"
hx-trigger='confirmed' hx-trigger='confirmed'

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,13 +11,22 @@
set elements to <.amount/> in me set elements to <.amount/> in me
for el in elements for el in elements
set el.textContent to el.dataset.originalValue 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
end end
on click[target matches .amount] if I include #settings-hide-amounts on click[target matches .amount or target.parentNode 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 if event.target matches .amount
else set event.target.textContent to '•••••••••••' end set el to event.target
then toggle .revealed on 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
end end
</script> </script>

View File

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

View File

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

View File

@@ -1,37 +1,45 @@
{% load i18n %} {% load i18n %}
{% load crispy_forms_tags %} {% load crispy_forms_tags %}
<form _="install init_tom_select <div class="flex flex-col gap-3">
on change trigger updated <div class="row">
init trigger updated" id="category-form"> <div class="col-12">
{% crispy category_form %} <div class="card card-border bg-base-100">
</form> <div class="card-body">
<form _="install init_tom_select
<div class="grid grid-cols-1 lg:grid-cols-2 gap-3"> on change trigger updated
<div class="w-full"> init trigger updated" id="category-form">
<div class="card bg-base-100 shadow-xl h-full"> {% crispy category_form %}
<div class="card-header bg-base-200 p-4 font-semibold"> </form>
{% 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> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="row">
</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 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" <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"> hx-include="#picker-form, #picker-type, #view-type, #show-tags, #showing, #show-entities">
<div class="h-full text-center mb-4"> <div class="h-full text-center mb-4">
<div class="join" role="group" id="view-type" _="on change trigger updated"> <div class="tabs tabs-box mx-auto w-fit" role="group" id="view-type" _="on change trigger updated">
<input type="radio" class="join-item btn btn-outline btn-primary rounded-full" <label class="tab">
name="view_type" <input type="radio"
id="table-view" name="view_type"
autocomplete="off" id="table-view"
value="table" autocomplete="off"
aria-label="{% trans 'Table' %}" value="table"
{% if view_type == "table" %}checked{% endif %}> aria-label="{% trans 'Table' %}"
{% if view_type == "table" %}checked{% endif %}>
<input type="radio" <i class="fa-solid fa-table fa-fw me-2"></i>
class="join-item btn btn-outline btn-primary rounded-full" {% trans 'Table' %}
name="view_type" </label>
id="bars-view" <label class="tab">
autocomplete="off" <input type="radio"
value="bars" name="view_type"
aria-label="{% trans 'Bars' %}" id="bars-view"
{% if view_type == "bars" %}checked{% endif %}> 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> </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"> <div class="flex gap-4">
{% if view_type == 'table' %} {% if view_type == 'table' %}
<div class="form-control" id="show-tags"> <div id="show-tags">
<input type="hidden" name="show_tags" value="off"> <label class="label">
<label class="label cursor-pointer gap-2"> <input type="hidden" name="show_tags" value="off">
<input type="checkbox" class="toggle" id="show-tags-switch" name="show_tags" <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 %}> _="on change trigger updated" {% if show_tags %}checked{% endif %}>
{% spaceless %} <span>
<span class="label-text">
{% trans 'Tags' %} {% trans 'Tags' %}
</span> </span>
<c-ui.help-icon <c-ui.help-icon
content="{% trans 'Transaction amounts associated with multiple tags will be counted once for each tag' %}" 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> icon="fa-solid fa-circle-exclamation"></c-ui.help-icon>
{% endspaceless %}
</label> </label>
</div> </div>
<div class="form-control" id="show-entities" {% if not show_tags %}style="display: none;"{% endif %}> <div id="show-entities" class="{% if not show_tags %}hidden{% endif %}">
<input type="hidden" name="show_entities" value="off"> <label class="label">
<label class="label cursor-pointer gap-2"> <input type="hidden" name="show_entities" value="off">
<input type="checkbox" class="toggle" id="show-entities-switch" name="show_entities" <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 %}> _="on change trigger updated" {% if show_entities %}checked{% endif %}>
{% spaceless %} <span>
<span class="label-text">
{% trans 'Entities' %} {% trans 'Entities' %}
</span> </span>
<c-ui.help-icon <c-ui.help-icon
content="{% trans 'Transaction amounts associated with multiple tags and entities will be counted once for each tag' %}" 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> icon="fa-solid fa-circle-exclamation"></c-ui.help-icon>
{% endspaceless %}
</label> </label>
</div> </div>
{% endif %} {% endif %}
@@ -70,8 +72,10 @@
</div> </div>
{% if total_table %} {% if total_table %}
{% if view_type == "table" %} {% if view_type == "table" %}
<div class="overflow-x-auto"> <div class="card bg-base-100 card-border">
<table class="table table-zebra"> <c-config.search></c-config.search>
<div class="overflow-x-auto">
<table class="table">
<thead> <thead>
<tr> <tr>
<th scope="col">{% trans 'Category' %}</th> <th scope="col">{% trans 'Category' %}</th>
@@ -176,7 +180,7 @@
{% for tag_id, tag in category.tags.items %} {% for tag_id, tag in category.tags.items %}
{% if tag.name or not tag.name and category.tags.values|length > 1 %} {% if tag.name or not tag.name and category.tags.values|length > 1 %}
<tr class="bg-base-200"> <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 %} <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>
<td> <td>
@@ -269,7 +273,7 @@
{% for entity_id, entity in tag.entities.items %} {% for entity_id, entity in tag.entities.items %}
{% if entity.name or not entity.name and tag.entities.values|length > 1 %} {% if entity.name or not entity.name and tag.entities.values|length > 1 %}
<tr class="bg-base-300"> <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 %} <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>
<td> <td>
@@ -366,13 +370,16 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div>
</div> </div>
{% elif view_type == "bars" %} {% elif view_type == "bars" %}
<div> <div class="card bg-base-100 card-border">
<div class="chart-container relative h-[78vh] w-full" _="init call setupChart() end"> <div class="card-body">
<canvas id="categoryChart"></canvas> <div class="chart-container relative h-[75vh] w-full" _="init call setupChart() end">
<canvas id="categoryChart"></canvas>
</div>
</div> </div>
</div> </div>

View File

@@ -1,7 +1,7 @@
{% load i18n %} {% 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"> id="transactions-list" hx-swap="outerHTML">
{% if transactions %} {% if transactions %}
{% for transaction in 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" <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"> hx-swap="outerHTML" hx-include="#picker-form, #picker-type">
{% endif %} {% 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" id="sankeyContainer"
_="init call setupSankeyChart() end"> _="init call setupSankeyChart() end">
<canvas id="sankeyChart"></canvas> <canvas id="sankeyChart"></canvas>
@@ -15,7 +15,14 @@
</div> </div>
<script> <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 }}; var data = {{ sankey_data|safe }};
console.log(convertColorToRgba(incomeColor))
function setupSankeyChart(chartId = 'sankeyChart') { function setupSankeyChart(chartId = 'sankeyChart') {
function formatCurrency(value, currency) { function formatCurrency(value, currency) {
@@ -35,11 +42,11 @@
const colors = {}; const colors = {};
data.nodes.forEach(node => { data.nodes.forEach(node => {
if (node.id.startsWith('income_')) { 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_')) { } else if (node.id.startsWith('expense_')) {
colors[node.id] = '#f87171'; // Red for expenses colors[node.id] = convertColorToRgba(expenseColor); // Red for expenses
} else { } else {
colors[node.id] = '#fbb700'; // Primary for others colors[node.id] = convertColorToRgba(primaryColor); // Primary for others
} }
}); });
@@ -63,7 +70,7 @@
colorMode: 'gradient', colorMode: 'gradient',
alpha: 0.5, alpha: 0.5,
size: 'max', size: 'max',
color: "white", color: contentColor,
nodePadding: 30, nodePadding: 30,
priority: data.nodes.reduce((acc, node) => { priority: data.nodes.reduce((acc, node) => {
acc[node.id] = node.priority; acc[node.id] = node.priority;

View File

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

View File

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

View File

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

View File

@@ -44,7 +44,7 @@
_="on click _="on click
remove .btn-active from <button/> in #filter-pills remove .btn-active from <button/> in #filter-pills
add .btn-active to me"> 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> </button>
{% endfor %} {% endfor %}
</div> </div>

View File

@@ -11,7 +11,7 @@
{% endblock %} {% endblock %}
{% block content %} {% 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"> <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> <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' %} <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 'bootstrap/js/dist/dropdown';
import Toast from 'bootstrap/js/dist/toast'; import Toast from 'bootstrap/js/dist/toast';
import 'bootstrap/js/dist/dropdown'; import 'bootstrap/js/dist/dropdown';

View File

@@ -1,4 +1,4 @@
import '@fontsource-variable/jetbrains-mono/wght-italic.css'; import '@fontsource-variable/jetbrains-mono/wght-italic.css';
import '@fontsource-variable/jetbrains-mono'; import '@fontsource-variable/jetbrains-mono';
import './styles/tailwind.css'; import '../styles/tailwind.css';
import './styles/style.scss'; 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 { .text-exchange-rate {
@apply text-base-content/60; @apply text-base-content/60 text-xs;
} }
.text-subtle { .text-subtle {
@apply text-base-content/80; @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 { @layer components {
.fab { .fab {
@layer daisyui.component { @layer daisyui.component {
> :nth-child(n+7) { > :nth-child(n+7) {
transition-delay: 150ms; transition-delay: 150ms;
}
} }
}
}
}
/* === Sidebar styles === */ /* === Sidebar styles === */
.sidebar-active { .sidebar-active {
@@ -173,7 +195,7 @@
.sidebar-floating { .sidebar-floating {
/* Establishes the hover group and sets the collapsed/hover widths for the container */ /* 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 { .sidebar-floating #sidebar {

View File

@@ -10,14 +10,7 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename); const __dirname = dirname(__filename);
const rollupInputs = { const rollupInputs = {
autosize: resolve(__dirname, 'src/autosize.js'), main: resolve(__dirname, 'src/main.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'),
}; };
@@ -42,6 +35,7 @@ export default defineConfig({
usePolling: true, usePolling: true,
disableGlobbing: false, disableGlobbing: false,
}, },
hmr: false,
cors: true, cors: true,
origin: 'http://100.118.164.62:5173' origin: 'http://100.118.164.62:5173'
}, },