feat: more changes and fixes

This commit is contained in:
Herculino Trotta
2025-11-09 15:31:50 -03:00
parent 2afb6b1f5f
commit 7e37948616
41 changed files with 480 additions and 432 deletions

View File

@@ -38,6 +38,7 @@ class SharedObjectForm(forms.Form):
choices=SharedObject.Visibility.choices,
required=True,
label=_("Visibility"),
widget=TomSelect(clear_button=False),
help_text=_(
"Private: Only shown for the owner and shared users. Only editable by the owner."
"<br/>"
@@ -47,9 +48,6 @@ class SharedObjectForm(forms.Form):
class Meta:
fields = ["visibility", "shared_with_users"]
widgets = {
"visibility": TomSelect(clear_button=False),
}
def __init__(self, *args, **kwargs):
# Get the current user to filter available sharing options
@@ -72,7 +70,7 @@ class SharedObjectForm(forms.Form):
self.helper.layout = Layout(
Field("owner"),
Field("visibility"),
HTML("<hr>"),
HTML('<hr class="hr my-3">'),
Field("shared_with_users"),
FormActions(
NoClassSubmit("submit", _("Save"), css_class="btn btn-primary"),

View File

@@ -1,4 +1,4 @@
from django.forms import widgets, SelectMultiple
from django.forms import SelectMultiple, widgets
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
@@ -17,7 +17,7 @@ class TomSelect(widgets.Select):
checkboxes=False,
group_by=None,
*args,
**kwargs
**kwargs,
):
super().__init__(attrs, *args, **kwargs)
self.remove_button = remove_button

View File

@@ -337,7 +337,7 @@ class QuickTransactionForm(forms.ModelForm):
),
Field("is_paid", template="transactions/widgets/paid_toggle_button.html"),
"name",
HTML("<hr />"),
HTML('<hr class="hr my-3" />'),
Row(
Column("account"),
Column("entities"),
@@ -365,11 +365,8 @@ class QuickTransactionForm(forms.ModelForm):
else:
self.fields["amount"].widget = ArbitraryDecimalDisplayNumberInput()
self.helper.layout.append(
Div(
NoClassSubmit(
"submit", _("Add"), css_class="btn btn-outline-primary"
),
css_class="d-grid gap-2",
FormActions(
NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
),
)

View File

@@ -22,11 +22,14 @@ def _format_string(prefix, amount, decimal_places, suffix):
@register.simple_tag(name="currency_display")
def currency_display(amount, prefix, suffix, decimal_places):
def currency_display(amount, prefix, suffix, decimal_places, string=False):
sign, prefix, amount, suffix = _format_string(
prefix, amount, decimal_places, suffix
)
if string:
return f"{sign}{prefix}{amount}{suffix}"
return {
"sign": sign,
"prefix": prefix,

View File

@@ -20,14 +20,14 @@
<table class="table table-hover">
<thead>
<tr>
<th scope="col" class="w-auto"></th>
<th scope="col" class="table-col-auto"></th>
<th scope="col">{% translate 'Name' %}</th>
</tr>
</thead>
<tbody>
{% for account_group in account_groups %}
<tr class="account_group">
<td class="w-auto">
<td class="table-col-auto">
<div class="join" role="group" aria-label="{% translate 'Actions' %}">
<a class="btn btn-secondary btn-sm join-item"
role="button"
@@ -35,7 +35,7 @@
hx-get="{% url 'account_group_edit' pk=account_group.id %}"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-pencil fa-fw"></i></a>
<a class="btn btn-secondary btn-sm join-item text-error"
<a class="btn btn-error btn-sm join-item"
role="button"
data-tippy-content="{% translate "Delete" %}"
hx-delete="{% url 'account_group_delete' pk=account_group.id %}"

View File

@@ -11,7 +11,7 @@
{{ form.management_form }}
<div class="space-y-2" id="balanceAccordionFlush">
{% for form in form.forms %}
<div class="collapse collapse-arrow bg-base-100 border-base-300 border overflow-visible">
<div class="collapse collapse-arrow bg-base-100 border-base-300 border-2">
<input type="checkbox" />
<div class="collapse-title font-medium text-sm">
{% if form.account_group %}<span class="badge badge-primary badge-outline me-2">{{ form.account_group.name }}</span>{% endif %}{{ form.account_name }}

View File

@@ -20,7 +20,7 @@
<table class="table table-hover whitespace-nowrap">
<thead>
<tr>
<th scope="col" class="w-auto"></th>
<th scope="col" class="table-col-auto"></th>
<th scope="col">{% translate 'Name' %}</th>
<th scope="col">{% translate 'Group' %}</th>
<th scope="col">{% translate 'Currency' %}</th>
@@ -32,7 +32,7 @@
<tbody>
{% for account in accounts %}
<tr class="account">
<td class="w-auto">
<td class="table-col-auto">
<div class="join" role="group" aria-label="{% translate 'Actions' %}">
<a class="btn btn-secondary btn-sm join-item"
role="button"
@@ -40,7 +40,7 @@
hx-get="{% url 'account_edit' pk=account.id %}"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-pencil fa-fw"></i></a>
<a class="btn btn-secondary btn-sm join-item text-error"
<a class="btn btn-error btn-sm join-item"
role="button"
data-tippy-content="{% translate "Delete" %}"
hx-delete="{% url 'account_delete' pk=account.id %}"

View File

@@ -12,7 +12,7 @@
<table class="table table-hover">
<thead>
<tr>
<th scope="col" class="w-auto"></th>
<th scope="col" class="table-col-auto"></th>
<th scope="col">{% translate 'Name' %}</th>
<th scope="col">{% translate 'Muted' %}</th>
</tr>
@@ -20,7 +20,7 @@
<tbody>
{% for category in categories %}
<tr class="category">
<td class="w-auto text-center">
<td class="table-col-auto text-center">
<div class="join" role="group" aria-label="{% translate 'Actions' %}">
<a class="btn btn-secondary btn-sm join-item"
role="button"
@@ -29,7 +29,7 @@
hx-get="{% url 'category_edit' category_id=category.id %}"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-pencil fa-fw"></i></a>
<a class="btn btn-secondary btn-sm join-item text-error"
<a class="btn btn-error btn-sm join-item"
role="button"
data-tippy-content="{% translate "Delete" %}"
hx-delete="{% url 'category_delete' category_id=category.id %}"

View File

@@ -21,3 +21,5 @@
<div class="alert-warning"></div>
<div class="textarea"></div>
<div class="border-base-content/60"></div>
<div class="bg-error/20"></div>
<div class="bg-success/20"></div>

View File

@@ -1,6 +1,8 @@
<li>
<div class="flex items-center min-h-6">
{% if title %}
<span class="sidebar-menu-header text-base-content/60 text-xs font-bold uppercase mr-3">{{ title }}</span>
{% endif %}
<hr class="hr grow"/>
</div>
</li>

View File

@@ -158,7 +158,7 @@
data-confirm-text="{% translate "Yes, delete it!" %}"
_="install prompt_swal"><i class="fa-solid fa-trash fa-fw"></i>
</a>
<button class="btn btn-soft 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" 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">

View File

@@ -0,0 +1,7 @@
<div class="fab">
<button class="btn btn-lg btn-circle btn-primary"
hx-get="{{ url }}"
hx-target="{{ hx_target }}">
<i class="fa-solid fa-plus text-2xl fa-fw"></i>
</button>
</div>

View File

@@ -1,5 +1,5 @@
{% load i18n %}
<div class="sticky bottom-4 left-0 right-0 z-50 hidden mx-auto w-fit" id="actions-bar"
<div class="sticky bottom-4 left-0 right-0 z-1000 hidden mx-auto w-fit" id="actions-bar"
_="on change from #transactions-list or htmx:afterSettle from window
if #actions-bar then
if no <input[type='checkbox']:checked/> in #transactions-list
@@ -22,41 +22,43 @@
{% spaceless %}
<div class="font-bold text-md ms-2" id="selected-count">0</div>
<div class="divider divider-horizontal m-0"></div>
<div class="dropdown dropdown-top dropdown-end">
<button tabindex="0" role="button" class="btn btn-secondary btn-soft btn-sm" type="button">
<div>
<button role="button" class="btn btn-secondary btn-sm" type="button"
data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa-regular fa-square-check fa-fw"></i>
<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 p-2 shadow fixed!">
<ul class="dropdown-menu menu">
<li>
<a class="cursor-pointer"
_="on click set <#transactions-list .transaction:not([style*='display: none']) input[type='checkbox']/>'s checked to true then call me.blur() then trigger change">
<i class="fa-regular fa-square-check text-green-400 me-3"></i>{% translate 'Select All' %}
<i class="fa-regular fa-square-check text-success me-3"></i>{% translate 'Select All' %}
</a>
</li>
<li>
<a class="cursor-pointer"
_="on click set <#transactions-list input[type='checkbox']/>'s checked to false then call me.blur() then trigger change">
<i class="fa-regular fa-square text-red-400 me-3"></i>{% translate 'Unselect All' %}
<i class="fa-regular fa-square text-error me-3"></i>{% translate 'Unselect All' %}
</a>
</li>
</ul>
</div>
<div class="divider divider-horizontal m-0"></div>
<div class="join">
<button class="btn btn-secondary btn-soft join-item btn-sm"
<button class="btn btn-secondary join-item btn-sm"
hx-get="{% url 'transactions_bulk_edit' %}"
hx-target="#generic-offcanvas"
hx-include=".transaction"
data-tippy-content="{% translate 'Edit' %}">
<i class="fa-solid fa-pencil"></i>
</button>
<div class="dropdown dropdown-top dropdown-end">
<button type="button" tabindex="0" role="button" class="join-item btn btn-sm btn-secondary">
<div>
<button type="button" role="button" class="join-item btn btn-sm btn-secondary"
data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa-solid fa-chevron-down fa-xs"></i>
</button>
<ul tabindex="0" class="dropdown-content fixed! menu bg-base-300 rounded-box z-[1] w-full p-2 shadow">
<ul class="dropdown-menu menu">
<li>
<a class="cursor-pointer"
hx-get="{% url 'transactions_bulk_unpay' %}"
@@ -142,12 +144,16 @@
<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">
<button type="button" tabindex="0" role="button" class="join-item btn btn-sm btn-secondary">
<div>
<button class="join-item btn btn-sm btn-secondary"
type="button"
data-bs-toggle="dropdown"
data-bs-auto-close="outside"
aria-expanded="false">
<i class="fa-solid fa-chevron-down fa-xs"></i>
</button>
<ul tabindex="0" class="dropdown-content menu bg-base-300 rounded-box w-full shadow fixed! flex flex-col gap-1">
<ul class="dropdown-menu dropdown-menu-end menu">
<li class="cursor-pointer"
_="on click
set original_value to #calc-menu-flat-total's innerText

View File

@@ -2,6 +2,6 @@
{% if help_text_inline %}
<span id="{{ field.auto_id }}_helptext" class="text-xs text-base-content/60 text-wrap">{{ field.help_text|safe}}</span>
{% else %}
<div {% if field.auto_id %}id="{{ field.auto_id }}_helptext" {% endif %}class="text-xs text-base-content/60 mt-1 text-wrap">{{ field.help_text|safe }}</div>
<p {% if field.auto_id %}id="{{ field.auto_id }}_helptext" {% endif %}class="label">{{ field.help_text|safe }}</p>
{% endif %}
{% endif %}

View File

@@ -20,8 +20,8 @@
<table class="table table-hover">
<thead>
<tr>
<th scope="col" class="w-auto"></th>
<th scope="col" class="w-auto">{% translate 'Code' %}</th>
<th scope="col" class="table-col-auto"></th>
<th scope="col" class="table-col-auto">{% translate 'Code' %}</th>
<th scope="col">{% translate 'Name' %}</th>
<th scope="col">{% translate 'Archived' %}</th>
</tr>
@@ -29,7 +29,7 @@
<tbody>
{% for currency in currencies %}
<tr class="currency">
<td class="w-auto">
<td class="table-col-auto">
<div class="join" role="group" aria-label="{% translate 'Actions' %}">
<a class="btn btn-secondary btn-sm join-item"
role="button"
@@ -37,7 +37,7 @@
hx-get="{% url 'currency_edit' pk=currency.id %}"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-pencil fa-fw"></i></a>
<a class="btn btn-secondary btn-sm join-item text-error"
<a class="btn btn-error btn-sm join-item"
role="button"
data-tippy-content="{% translate "Delete" %}"
hx-delete="{% url 'currency_delete' pk=currency.id %}"
@@ -49,7 +49,7 @@
_="install prompt_swal"><i class="fa-solid fa-trash fa-fw"></i></a>
</div>
</td>
<td class="w-auto">{{ currency.code }}</td>
<td class="table-col-auto">{{ currency.code }}</td>
<td>{{ currency.name }}</td>
<td>{% if currency.is_archived %}<i class="fa-solid fa-solid fa-check text-success"></i>{% endif %}</td>
</tr>

View File

@@ -1,53 +1,50 @@
{% load currency_display %}
{% load i18n %}
<div class="container-fluid px-md-3 py-3 column-gap-5">
<div class="lg:flex justify-between mb-3 w-full">
<div class="text-3xl font-bold font-mono flex items-center">
{{ strategy.name }}
</div>
<div class="text-sm lg:text-right mt-2 lg:mt-0">
<div class="mb-2">
<span class="badge badge-secondary rounded-full">{{ strategy.payment_currency.name }}</span> x <span class="badge badge-secondary rounded-full">{{ strategy.target_currency.name }}</span>
<c-ui.fab-single-action
url="{% url 'dca_entry_add' strategy_id=strategy.id %}"
hx_target="#generic-offcanvas">
</c-ui.fab-single-action>
<div class="container-fluid">
<div class="row">
<div class="lg:flex justify-between mb-3 w-full">
<div class="text-3xl font-bold font-mono flex items-center">
{{ strategy.name }}
</div>
<div>
{% if strategy.current_price %}
<c-amount.display
:amount="strategy.current_price.0"
:prefix="strategy.payment_currency.prefix"
:suffix="strategy.payment_currency.suffix"
:decimal_places="strategy.payment_currency.decimal_places">
• {{ strategy.current_price.1|date:"SHORT_DATETIME_FORMAT" }}
</c-amount.display>
{% else %}
<div class="text-red-400">{% trans "No exchange rate available" %}</div>
{% endif %}
<div class="text-sm lg:text-right mt-2 lg:mt-0">
<div class="mb-2">
<span class="badge badge-secondary rounded-full">{{ strategy.payment_currency.name }}</span> x <span class="badge badge-secondary rounded-full">{{ strategy.target_currency.name }}</span>
</div>
<div>
{% if strategy.current_price %}
<c-amount.display
:amount="strategy.current_price.0"
:prefix="strategy.payment_currency.prefix"
:suffix="strategy.payment_currency.suffix"
:decimal_places="strategy.payment_currency.decimal_places">
• {{ strategy.current_price.1|date:"SHORT_DATETIME_FORMAT" }}
</c-amount.display>
{% else %}
<div class="text-error">{% trans "No exchange rate available" %}</div>
{% endif %}
</div>
</div>
</div>
</div>
<div class="grid lg:grid-cols-2 gap-3">
<div>
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<div class="row g-3">
<div class="col-12 lg:col-6">
<div class="card bg-base-100 shadow">
<div class="card-body overflow-x-auto">
{% spaceless %}
<div class="card-title text-xl">{% trans "Entries" %}<span>
<a class="no-underline p-1 category-action"
role="button"
data-tippy-content="{% translate "Add" %}"
hx-get="{% url 'dca_entry_add' strategy_id=strategy.id %}"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-circle-plus fa-fw"></i>
</a>
</span>
</div>
<div class="card-title text-xl">{% trans "Entries" %}</div>
{% endspaceless %}
{% if entries %}
<div class="overflow-x-auto">
<table class="table table-hover whitespace-nowrap">
<table class="table table-zebra">
<thead>
<tr>
<th></th>
<th class="table-col-auto"></th>
<th>{% trans "Date" %}</th>
<th>{% trans "Amount Received" %}</th>
<th>{% trans "Amount Paid" %}</th>
@@ -58,7 +55,7 @@
<tbody>
{% for entry in entries %}
<tr>
<td class="w-auto">
<td class="table-col-auto">
<div class="join" role="group" aria-label="{% translate 'Actions' %}">
<a class="btn btn-secondary btn-sm join-item"
role="button"
@@ -67,7 +64,7 @@
hx-target="#generic-offcanvas"
hx-swap="innerHTML">
<i class="fa-solid fa-pencil fa-fw"></i></a>
<a class="btn btn-secondary btn-sm join-item text-error"
<a class="btn btn-error btn-sm join-item"
role="button"
data-tippy-content="{% translate "Delete" %}"
hx-delete="{% url 'dca_entry_delete' entry_id=entry.id strategy_id=entry.strategy.id %}"
@@ -88,7 +85,7 @@
:suffix="entry.strategy.target_currency.suffix"
:decimal_places="entry.strategy.target_currency.decimal_places"></c-amount.display>
</td>
<td title="{% currency_display amount=entry.entry_price prefix=entry.strategy.payment_currency.prefix suffix=entry.strategy.payment_currency.suffix decimal_places=entry.strategy.payment_currency.decimal_places %}">
<td title="{% currency_display amount=entry.entry_price prefix=entry.strategy.payment_currency.prefix suffix=entry.strategy.payment_currency.suffix decimal_places=entry.strategy.payment_currency.decimal_places string=True %}">
<c-amount.display
:amount="entry.amount_paid"
:prefix="entry.strategy.payment_currency.prefix"
@@ -105,10 +102,10 @@
<td>
{% if entry.profit_loss_percentage > 0 %}
<span class="badge badge-success"><i
class="fa-solid fa-up-long me-2"></i>{{ entry.profit_loss_percentage|floatformat:"2g" }}%</span>
class="fa-solid fa-up-long"></i>{{ entry.profit_loss_percentage|floatformat:"2g" }}%</span>
{% elif entry.profit_loss_percentage < 0 %}
<span class="badge badge-error"><i
class="fa-solid fa-down-long me-2"></i>{{ entry.profit_loss_percentage|floatformat:"2g" }}%</span>
class="fa-solid fa-down-long"></i>{{ entry.profit_loss_percentage|floatformat:"2g" }}%</span>
{% endif %}
</td>
</tr>
@@ -124,10 +121,10 @@
</div>
</div>
</div>
<div>
<div class="col-12 lg:col-6">
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3">
<div>
<div class="card bg-base-100 shadow-xl h-full">
<div class="card bg-base-100 shadow h-full">
<div class="card-body">
<h5 class="card-title">{% trans "Total Invested" %}</h5>
<div class="text-base-content">
@@ -141,7 +138,7 @@
</div>
</div>
<div>
<div class="card bg-base-100 shadow-xl h-full">
<div class="card bg-base-100 shadow h-full">
<div class="card-body">
<h5 class="card-title">{% trans "Total Received" %}</h5>
<div class="text-base-content">
@@ -155,7 +152,7 @@
</div>
</div>
<div>
<div class="card bg-base-100 shadow-xl h-full">
<div class="card bg-base-100 shadow h-full">
<div class="card-body">
<h5 class="card-title">{% trans "Current Total Value" %}</h5>
<div class="text-base-content">
@@ -169,7 +166,7 @@
</div>
</div>
<div>
<div class="card bg-base-100 shadow-xl h-full">
<div class="card bg-base-100 shadow h-full">
<div class="card-body">
<h5 class="card-title">{% trans "Average Entry Price" %}</h5>
<div class="text-base-content">
@@ -183,11 +180,11 @@
</div>
</div>
<div>
<div class="card bg-base-100 shadow-xl h-full">
<div class="card bg-base-100 shadow h-full">
<div class="card-body">
<h5 class="card-title">{% trans "Total P/L" %}</h5>
<div
class="text-base-content {% if strategy.total_profit_loss >= 0 %}text-green-400{% else %}text-red-400{% endif %}">
class="text-base-content {% if strategy.total_profit_loss >= 0 %}text-success{% else %}text-error{% endif %}">
<c-amount.display
:amount="strategy.total_profit_loss"
:prefix="strategy.payment_currency.prefix"
@@ -199,11 +196,11 @@
</div>
</div>
<div>
<div class="card bg-base-100 shadow-xl h-full">
<div class="card bg-base-100 shadow h-full">
<div class="card-body">
<h5 class="card-title">{% trans "Total % P/L" %}</h5>
<div
class="text-base-content {% if strategy.total_profit_loss >= 0 %}text-green-400{% else %}text-red-400{% endif %}">
class="text-base-content {% if strategy.total_profit_loss >= 0 %}text-success{% else %}text-error{% endif %}">
{{ strategy.total_profit_loss_percentage|floatformat:2 }}%
</div>
</div>
@@ -216,71 +213,70 @@
set perfomancectx to #performanceChart.getContext('2d')
js(perfomancectx)
new Chart(perfomancectx, {
type: 'line',
data: {
labels: [{% for entry in entries_data %}'{{ entry.entry.date|date:"SHORT_DATE_FORMAT" }}'{% if not forloop.last %}, {% endif %}{% endfor %}],
datasets: [{
label: '{% trans "P/L %" %}',
data: [{% for entry in entries_data %}{{ entry.profit_loss_percentage|floatformat:"-40u" }}{% if not forloop.last %}, {% endif %}{% endfor %}],
stepped: true,
segment: {
borderColor: ctx => {
const gradient = ctx.chart.ctx.createLinearGradient(ctx.p0.x, 0, ctx.p1.x, 0);
if (ctx.p0.parsed.y >= 0 && ctx.p1.parsed.y >= 0) {
// Both positive - solid green
gradient.addColorStop(0, 'rgb(75, 192, 75)');
gradient.addColorStop(1, 'rgb(75, 192, 75)');
} else if (ctx.p0.parsed.y < 0 && ctx.p1.parsed.y < 0) {
// Both negative - solid red
gradient.addColorStop(0, 'rgb(255, 99, 132)');
gradient.addColorStop(1, 'rgb(255, 99, 132)');
} else if (ctx.p0.parsed.y >= 0 && ctx.p1.parsed.y < 0) {
// Positive to negative - green to red
gradient.addColorStop(0, 'rgb(75, 192, 75)');
gradient.addColorStop(1, 'rgb(255, 99, 132)');
} else {
// Negative to positive - red to green
gradient.addColorStop(0, 'rgb(255, 99, 132)');
gradient.addColorStop(1, 'rgb(75, 192, 75)');
}
return gradient;
}
},
fill: false,
borderWidth: 2
}]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: false
},
x: {
ticks: {
display: false
}
}
},
plugins: {
tooltip: {
mode: 'index',
intersect: false
},
legend: {
display: false,
},
title: {
display: false,
}
}
}
})
end
">
<div class="card bg-base-100 shadow-xl">
type: 'line',
data: {
labels: [{% for entry in entries_data %}'{{ entry.entry.date|date:"SHORT_DATE_FORMAT" }}'{% if not forloop.last %}, {% endif %}{% endfor %}],
datasets: [{
label: '{% trans "P/L %" %}',
data: [{% for entry in entries_data %}{{ entry.profit_loss_percentage|floatformat:"-40u" }}{% if not forloop.last %}, {% endif %}{% endfor %}],
stepped: true,
segment: {
borderColor: ctx => {
const gradient = ctx.chart.ctx.createLinearGradient(ctx.p0.x, 0, ctx.p1.x, 0);
if (ctx.p0.parsed.y >= 0 && ctx.p1.parsed.y >= 0) {
// Both positive - solid green
gradient.addColorStop(0, 'rgb(75, 192, 75)');
gradient.addColorStop(1, 'rgb(75, 192, 75)');
} else if (ctx.p0.parsed.y < 0 && ctx.p1.parsed.y < 0) {
// Both negative - solid red
gradient.addColorStop(0, 'rgb(255, 99, 132)');
gradient.addColorStop(1, 'rgb(255, 99, 132)');
} else if (ctx.p0.parsed.y >= 0 && ctx.p1.parsed.y < 0) {
// Positive to negative - green to red
gradient.addColorStop(0, 'rgb(75, 192, 75)');
gradient.addColorStop(1, 'rgb(255, 99, 132)');
} else {
// Negative to positive - red to green
gradient.addColorStop(0, 'rgb(255, 99, 132)');
gradient.addColorStop(1, 'rgb(75, 192, 75)');
}
return gradient;
}
},
fill: false,
borderWidth: 2
}]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: false
},
x: {
ticks: {
display: false
}
}
},
plugins: {
tooltip: {
mode: 'index',
intersect: false
},
legend: {
display: false,
},
title: {
display: false,
}
}
}
})
end">
<div class="card bg-base-100 shadow">
<div class="card-body">
<h5 class="card-title">{% trans "Performance Over Time" %}</h5>
<canvas id="performanceChart"></canvas>
@@ -295,96 +291,95 @@
set priceData to {{ price_comparison_data|safe }}
js(pricectx, priceData)
new Chart(pricectx, {
type: 'bar',
data: {
labels: priceData.labels,
datasets: [
{
label: '{% trans "Entry Price" %}',
data: priceData.entry_prices,
backgroundColor: 'rgba(54, 162, 235, 0.5)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 1,
yAxisID: 'y'
},
{
label: '{% trans "Current Price" %}',
data: priceData.current_prices,
backgroundColor: 'rgba(75, 192, 192, 0.5)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1,
yAxisID: 'y'
},
{
label: '{% trans "Amount Bought" %}',
data: priceData.amounts_bought,
type: 'line',
borderColor: 'rgba(255, 99, 132, 1)',
borderWidth: 2,
fill: false,
yAxisID: 'y1',
tension: 0.1
}
]
},
options: {
responsive: true,
interaction: {
mode: 'index',
intersect: false,
},
scales: {
x: {
grid: {
display: false
},
title: {
display: false,
}
},
y: {
type: 'linear',
display: true,
position: 'left',
beginAtZero: true,
title: {
display: false,
},
ticks: {
display: false,
format: { maximumFractionDigits: 40, minimumFractionDigits: 1 }
}
},
y1: {
type: 'linear',
display: true,
position: 'right',
beginAtZero: true,
grid: {
drawOnChartArea: false
},
title: {
display: false,
},
ticks: {
display: false,
format: { maximumFractionDigits: 40, minimumFractionDigits: 1 }
}
}
},
plugins: {
legend: {
position: 'top'
},
title: {
display: false,
}
}
}
})
end
">
<div class="card bg-base-100 shadow-xl">
type: 'bar',
data: {
labels: priceData.labels,
datasets: [
{
label: '{% trans "Entry Price" %}',
data: priceData.entry_prices,
backgroundColor: 'rgba(54, 162, 235, 0.5)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 1,
yAxisID: 'y'
},
{
label: '{% trans "Current Price" %}',
data: priceData.current_prices,
backgroundColor: 'rgba(75, 192, 192, 0.5)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1,
yAxisID: 'y'
},
{
label: '{% trans "Amount Bought" %}',
data: priceData.amounts_bought,
type: 'line',
borderColor: 'rgba(255, 99, 132, 1)',
borderWidth: 2,
fill: false,
yAxisID: 'y1',
tension: 0.1
}
]
},
options: {
responsive: true,
interaction: {
mode: 'index',
intersect: false,
},
scales: {
x: {
grid: {
display: false
},
title: {
display: false,
}
},
y: {
type: 'linear',
display: true,
position: 'left',
beginAtZero: true,
title: {
display: false,
},
ticks: {
display: false,
format: { maximumFractionDigits: 40, minimumFractionDigits: 1 }
}
},
y1: {
type: 'linear',
display: true,
position: 'right',
beginAtZero: true,
grid: {
drawOnChartArea: false
},
title: {
display: false,
},
ticks: {
display: false,
format: { maximumFractionDigits: 40, minimumFractionDigits: 1 }
}
}
},
plugins: {
legend: {
position: 'top'
},
title: {
display: false,
}
}
}
})
end">
<div class="card bg-base-100 shadow">
<div class="card-body">
<h5 class="card-title">{% trans "Entry Price vs Current Price" %}</h5>
<canvas id="priceChart"></canvas>
@@ -447,7 +442,7 @@
})
end
">
<div class="card bg-base-100 shadow-xl">
<div class="card bg-base-100 shadow">
<div class="card-body">
<h5 class="card-title">{% trans "Investment Frequency" %}</h5>
<p class="text-base-content/60">

View File

@@ -1,22 +1,19 @@
{% load i18n %}
<c-ui.fab-single-action
url="{% url 'dca_strategy_add' %}"
hx_target="#generic-offcanvas">
</c-ui.fab-single-action>
<div class="container px-md-3 py-3 column-gap-5">
<div class="text-3xl font-bold font-mono w-full mb-3">
{% spaceless %}
<div>{% translate 'Dollar Cost Average Strategies' %}<span>
<a class="no-underline text-2xl p-1 category-action"
role="button"
data-tippy-content="{% translate "Add" %}"
hx-get="{% url 'dca_strategy_add' %}"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-circle-plus fa-fw"></i></a>
</span></div>
<div>{% translate 'Dollar Cost Average Strategies' %}</div>
{% endspaceless %}
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3">
{% for strategy in strategies %}
<div>
<div class="card bg-base-100 shadow-xl h-full flex flex-col">
<div class="card bg-base-100 card-border shadow h-full flex flex-col">
<div class="card-header bg-base-200 p-4">
<span class="badge badge-secondary rounded-full">{{ strategy.payment_currency.name }}</span> x <span
class="badge badge-secondary rounded-full">{{ strategy.target_currency.name }}</span>
@@ -28,7 +25,7 @@
<div class="text-base-content/60">{{ strategy.notes }}</div>
</div>
</a>
<div class="card-footer bg-base-200 p-4 text-right">
<div class="card-footer bg-base-200 p-4 text-right cursor-pointer">
<a class="no-underline text-base-content/60 p-1"
role="button"
data-tippy-content="{% translate "Edit" %}"
@@ -36,7 +33,7 @@
hx-target="#generic-offcanvas">
<i class="fa-solid fa-pencil fa-fw"></i>
</a>
<a class="text-error no-underline p-1"
<a class="text-error no-underline p-1 cursor-pointer"
role="button"
data-tippy-content="{% translate "Delete" %}"
hx-delete="{% url 'dca_strategy_delete' strategy_id=strategy.id %}"
@@ -49,14 +46,14 @@
<i class="fa-solid fa-trash fa-fw"></i>
</a>
{% if not strategy.owner %}
<a class="text-primary no-underline p-1"
<a class="text-primary no-underline p-1 cursor-pointer"
role="button"
data-tippy-content="{% translate "Take ownership" %}"
hx-get="{% url 'dca_strategy_take_ownership' strategy_id=strategy.id %}">
<i class="fa-solid fa-crown fa-fw"></i></a>
{% endif %}
{% if user == strategy.owner %}
<a class="text-primary no-underline p-1"
<a class="text-primary no-underline p-1 cursor-pointer"
role="button"
hx-target="#generic-offcanvas"
data-tippy-content="{% translate "Share" %}"

View File

@@ -12,14 +12,14 @@
<table class="table table-hover">
<thead>
<tr>
<th scope="col" class="w-auto"></th>
<th scope="col" class="table-col-auto"></th>
<th scope="col">{% translate 'Name' %}</th>
</tr>
</thead>
<tbody>
{% for entity in entities %}
<tr class="entity">
<td class="w-auto">
<td class="table-col-auto">
<div class="join" role="group" aria-label="{% translate 'Actions' %}">
<a class="btn btn-secondary btn-sm join-item"
role="button"
@@ -28,7 +28,7 @@
hx-get="{% url 'entity_edit' entity_id=entity.id %}"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-pencil fa-fw"></i></a>
<a class="btn btn-secondary btn-sm join-item text-error"
<a class="btn btn-error btn-sm join-item"
role="button"
hx-swap="innerHTML"
data-tippy-content="{% translate "Delete" %}"

View File

@@ -6,7 +6,7 @@
<table class="table table-hover whitespace-nowrap">
<thead>
<tr>
<th scope="col" class="w-auto"></th>
<th scope="col" class="table-col-auto"></th>
<th scope="col">{% translate 'Date' %}</th>
<th scope="col">{% translate 'Pairing' %}</th>
<th scope="col">{% translate 'Rate' %}</th>
@@ -15,7 +15,7 @@
<tbody>
{% for exchange_rate in page_obj %}
<tr class="exchange-rate">
<td class="w-auto">
<td class="table-col-auto">
<div class="join" role="group" aria-label="{% translate 'Actions' %}">
<a class="btn btn-secondary btn-sm join-item"
role="button"
@@ -24,7 +24,7 @@
hx-target="#generic-offcanvas"
hx-swap="innerHTML">
<i class="fa-solid fa-pencil fa-fw"></i></a>
<a class="btn btn-secondary btn-sm join-item text-error"
<a class="btn btn-error btn-sm join-item"
role="button"
data-tippy-content="{% translate "Delete" %}"
hx-delete="{% url 'exchange_rate_delete' pk=exchange_rate.id %}"

View File

@@ -26,8 +26,8 @@
<table class="table table-hover">
<thead>
<tr>
<th scope="col" class="w-auto"></th>
<th scope="col" class="w-auto"></th>
<th scope="col" class="table-col-auto"></th>
<th scope="col" class="table-col-auto"></th>
<th scope="col" class="w-auto">{% translate 'Name' %}</th>
<th scope="col">{% translate 'Service' %}</th>
<th scope="col">{% translate 'Targeting' %}</th>
@@ -37,7 +37,7 @@
<tbody>
{% for service in services %}
<tr class="services">
<td class="w-auto">
<td class="table-col-auto">
<div class="join" role="group" aria-label="{% translate 'Actions' %}">
<a class="btn btn-secondary btn-sm join-item"
role="button"
@@ -45,7 +45,7 @@
hx-get="{% url 'automatic_exchange_rate_edit' pk=service.id %}"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-pencil fa-fw"></i></a>
<a class="btn btn-secondary btn-sm join-item text-error"
<a class="btn btn-error btn-sm join-item"
role="button"
data-tippy-content="{% translate "Delete" %}"
hx-delete="{% url 'automatic_exchange_rate_delete' pk=service.id %}"
@@ -57,9 +57,9 @@
_="install prompt_swal"><i class="fa-solid fa-trash fa-fw"></i></a>
</div>
</td>
<td class="w-auto">{% if service.is_active %}<i class="fa-solid fa-circle text-success"></i>{% else %}
<td class="table-col-auto">{% if service.is_active %}<i class="fa-solid fa-circle text-success"></i>{% else %}
<i class="fa-solid fa-circle text-error"></i>{% endif %}</td>
<td class="w-auto">{{ service.name }}</td>
<td class="table-col-auto">{{ service.name }}</td>
<td>{{ service.get_service_type_display }}</td>
<td>{{ service.target_currencies.count }} {% trans 'currencies' %}, {{ service.target_accounts.count }} {% trans 'accounts' %}</td>
<td>{{ service.last_fetch|date:"SHORT_DATETIME_FORMAT" }}</td>

View File

@@ -6,7 +6,7 @@
<table class="table table-hover whitespace-nowrap">
<thead>
<tr>
<th scope="col" class="w-auto"></th>
<th scope="col" class="table-col-auto"></th>
<th scope="col">{% translate 'Date' %}</th>
<th scope="col">{% translate 'Pairing' %}</th>
<th scope="col">{% translate 'Rate' %}</th>
@@ -15,7 +15,7 @@
<tbody>
{% for exchange_rate in page_obj %}
<tr class="exchange-rate">
<td class="w-auto">
<td class="table-col-auto">
<div class="join" role="group" aria-label="{% translate 'Actions' %}">
<a class="btn btn-secondary btn-sm join-item"
role="button"
@@ -24,7 +24,7 @@
hx-target="#generic-offcanvas"
hx-swap="innerHTML">
<i class="fa-solid fa-pencil fa-fw"></i></a>
<a class="btn btn-secondary btn-sm join-item text-error"
<a class="btn btn-error btn-sm join-item"
role="button"
data-tippy-content="{% translate "Delete" %}"
hx-delete="{% url 'exchange_rate_delete' pk=exchange_rate.id %}"

View File

@@ -1,6 +1,6 @@
{% load i18n %}
<div class="offcanvas-header flex justify-between items-center border-b border-base-content/10">
<h5 class="offcanvas-title font-medium text-xl">{% block title %}{% endblock %}</h5>
<h5 class="offcanvas-title font-medium">{% block title %}{% endblock %}</h5>
<button type="button" class="btn btn-ghost btn-sm btn-circle" aria-label="{% trans 'Close' %}" data-bs-dismiss="offcanvas"><i class="fa-solid fa-xmark"></i></button>
</div>
<div id="generic-offcanvas-body" class="offcanvas-body"

View File

@@ -29,7 +29,7 @@
<table class="table table-hover whitespace-nowrap">
<thead>
<tr>
<th scope="col" class="w-auto"></th>
<th scope="col" class="table-col-auto"></th>
<th scope="col">{% translate 'Name' %}</th>
<th scope="col">{% translate 'Version' %}</th>
</tr>
@@ -37,7 +37,7 @@
<tbody>
{% for profile in profiles %}
<tr class="profile">
<td class="w-auto">
<td class="table-col-auto">
<div class="join" role="group" aria-label="{% translate 'Actions' %}">
<a class="btn btn-secondary btn-sm join-item"
role="button"
@@ -57,7 +57,7 @@
hx-get="{% url 'import_run_add' profile_id=profile.id %}"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-file-import fa-fw"></i></a>
<a class="btn btn-secondary btn-sm join-item text-error"
<a class="btn btn-error btn-sm join-item"
role="button"
data-tippy-content="{% translate "Delete" %}"
hx-delete="{% url 'import_profile_delete' profile_id=profile.id %}"

View File

@@ -35,7 +35,7 @@
</button>
</div>
<div class="lg:hidden flex justify-between items-center p-4 text-base-content">
<div class="lg:hidden flex justify-between items-center p-4 text-base-content border-b border-base-content/10">
<a href="{% url 'index' %}" class="flex justify-start no-underline">
<div class="logo"></div>
<span class="text-2xl font-bold ml-3">WYGIWYH</span>
@@ -44,7 +44,7 @@
aria-label={% translate 'Close' %}><i class="fa-solid fa-xmark"></i></button>
</div>
<ul class="list-none p-3 flex flex-col gap-1 whitespace-nowrap lg:group-hover:animate-[disable-pointer-events] overflow-y-auto lg:overflow-y-hidden lg:hover:overflow-y-auto overflow-x-hidden"
<ul class="sidebar-item-list list-none p-3 flex flex-col gap-1 whitespace-nowrap lg:group-hover:animate-[disable-pointer-events]"
style="animation-duration: 100ms">
<c-components.sidebar-menu-item
@@ -133,9 +133,7 @@
icon="fa-solid fa-money-bill-transfer">
</c-components.sidebar-menu-item>
<div>
<hr class="hr">
</div>
<c-components.sidebar-menu-header title=""></c-components.sidebar-menu-header>
<div role="button"
data-bs-toggle="collapse"
@@ -156,10 +154,13 @@
class="bs collapse p-0 absolute bottom-0 left-0 w-full z-30 max-h-dvh {% active_link views='tags_index||entities_index||categories_index||accounts_index||account_groups_index||currencies_index||exchange_rates_index||rules_index||import_profiles_index||automatic_exchange_rates_index||export_index||users_index' css_class="show" %}">
<div class="h-dvh bg-base-300 flex flex-col">
<div
class="justify-between items-center p-4 border-b border-base-content/10 sidebar-submenu-header text-base-content">
<h5 class="text-lg font-semibold text-base-content m-0">
{% trans 'Management' %}
</h5>
class="items-center p-4 border-b border-base-content/10 sidebar-submenu-header text-base-content">
<div class="flex items-center sidebar-submenu-title">
<i class="fa-solid fa-toolbox fa-fw lg:group-hover:me-2 me-2 lg:me-0"></i>
<h5 class="text-lg font-semibold text-base-content m-0">
{% trans 'Management' %}
</h5>
</div>
<button type="button" class="btn btn-ghost btn-sm btn-circle" aria-label="{% trans 'Close' %}"
data-bs-toggle="collapse"
@@ -170,7 +171,7 @@
</button>
</div>
<ul class="list-none p-3 flex flex-col gap-1 whitespace-nowrap lg:group-hover:animate-[disable-pointer-events] overflow-y-auto lg:overflow-y-hidden lg:hover:overflow-y-auto overflow-x-hidden"
<ul class="sidebar-item-list list-none p-3 flex flex-col gap-1 whitespace-nowrap lg:group-hover:animate-[disable-pointer-events] overflow-y-auto lg:overflow-y-hidden lg:hover:overflow-y-auto overflow-x-hidden"
style="animation-duration: 100ms">
<c-components.sidebar-menu-header title="{% translate 'Transactions' %}"></c-components.sidebar-menu-header>
<c-components.sidebar-menu-item

View File

@@ -1,5 +1,5 @@
<div id="toasts">
<div class="toast toast-top toast-end toast-container" hx-trigger="load, updated from:window, toasts from:window" hx-get="{% url 'toasts' %}" hx-swap="beforeend">
<div class="toast toast-top toast-end toast-container z-1050" hx-trigger="load, updated from:window, toasts from:window" hx-get="{% url 'toasts' %}" hx-swap="beforeend">
</div>
</div>

View File

@@ -1,28 +1,28 @@
{% load i18n %}
<c-ui.fab-single-action
url="{% url 'installment_plan_add' %}"
hx_target="#generic-offcanvas">
</c-ui.fab-single-action>
<div class="container px-md-3 py-3 column-gap-5">
<div class="text-3xl font-bold font-mono w-full mb-3">
{% spaceless %}
<div>{% translate 'Installment Plans' %}<span>
<a class="no-underline text-2xl p-1 category-action"
role="button"
data-tippy-content="{% translate "Add" %}"
hx-get="{% url 'installment_plan_add' %}"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-circle-plus fa-fw"></i></a>
</span></div>
<div>{% translate 'Installment Plans' %}</div>
{% endspaceless %}
</div>
<div class="card bg-base-100 shadow-xl">
<div class="card-header bg-base-200 p-4">
<div role="tablist" class="tabs tabs-lifted">
<button class="tab tab-active" data-bs-toggle="tab" type="button" role="tab" aria-selected="true" hx-get="{% url 'active_installment_plans_list' %}" hx-trigger="load, click" hx-target="#installment-plans-table">{% translate 'All' %}</button>
<button class="tab" hx-get="{% url 'finished_installment_plans_list' %}" hx-target="#installment-plans-table" data-bs-toggle="tab" type="button" role="tab" aria-selected="false">{% translate 'Finished' %}</button>
<div class="card-header bg-base-200 p-4 rounded-box">
<div role="tablist" class="tabs tabs-border">
<input type="radio" name="installment_plan_tabs" class="tab" aria-label="{% translate 'Active' %}" checked="checked"
hx-get="{% url 'active_installment_plans_list' %}" hx-trigger="load, click" hx-target="#installment-plans-table"
hx-indicator="#installment-plans-table" />
<input type="radio" name="installment_plan_tabs" class="tab" aria-label="{% translate 'Finished' %}"
hx-get="{% url 'finished_installment_plans_list' %}" hx-trigger="click" hx-target="#installment-plans-table" hx-indicator="#installment-plans-table" />
</div>
</div>
<div class="card-body">
<div id="installment-plans-table"></div>
<div id="installment-plans-table" class="show-loading"></div>
</div>
</div>

View File

@@ -12,7 +12,7 @@
<table class="table table-zebra">
<thead>
<tr>
<th scope="col" class="w-auto"></th>
<th scope="col" class="table-col-auto"></th>
<th scope="col">{% translate 'Name' %}</th>
<th scope="col">{% translate 'Account' %}</th>
<th scope="col">{% translate 'Amount' %}</th>
@@ -21,7 +21,7 @@
<tbody>
{% for installment_plan in installment_plans %}
<tr class="installment-plan">
<td class="w-auto text-center">
<td class="table-col-auto text-center">
<div class="join" role="group" aria-label="{% translate 'Actions' %}">
<a class="btn btn-secondary btn-sm join-item"
role="button"
@@ -37,7 +37,7 @@
hx-swap="innerHTML"
hx-target="#persistent-generic-offcanvas-left">
<i class="fa-solid fa-eye fa-fw"></i></a>
<a class="btn btn-secondary btn-sm join-item text-info"
<a class="btn btn-secondary btn-sm join-item"
role="button"
data-tippy-content="{% translate "Refresh" %}"
hx-get="{% url 'installment_plan_refresh' installment_plan_id=installment_plan.id %}"
@@ -49,7 +49,7 @@
data-confirm-text="{% translate "Yes, refresh it!" %}"
_="install prompt_swal">
<i class="fa-solid fa-arrows-rotate fa-fw"></i></a>
<a class="btn btn-secondary btn-sm join-item text-error"
<a class="btn btn-error btn-sm join-item"
role="button"
data-tippy-content="{% translate "Delete" %}"
hx-delete="{% url 'installment_plan_delete' installment_plan_id=installment_plan.id %}"
@@ -62,7 +62,7 @@
</div>
</td>
<td>
<div class="{% if installment_plan.type == 'EX' %}text-red-400{% else %}text-green-400{% endif %}">
<div class="{% if installment_plan.type == 'EX' %}text-error{% else %}text-success{% endif %}">
{{ installment_plan.description }}
</div>
<div class="text-sm text-base-content/60">{{ installment_plan.notes|linebreaksbr }}</div>

View File

@@ -8,10 +8,10 @@
<div class="text-3xl font-bold font-mono w-full mb-3">
<div>{% translate 'Unit Price Calculator' %}</div>
</div>
<div class="card bg-base-100 shadow-xl mb-3 hidden" id="card-placeholder">
<div class="card-header bg-base-200 p-4 flex flex-row justify-between">
<h5 class="title flex-grow"></h5>
<button class="btn btn-secondary btn-sm text-error"
<div class="card bg-base-100 shadow mb-3 hidden" id="card-placeholder">
<div class="card-header bg-base-200 p-4 flex flex-row justify-between rounded-box">
<h5 class="title grow"></h5>
<button class="btn btn-error btn-sm"
role="button"
data-tippy-content="{% translate "Delete" %}"
_="on click remove the closest .card to me then trigger update on #items then trigger tooltips on body">
@@ -21,21 +21,23 @@
<div class="card-body">
<div class="grid lg:grid-cols-3 gap-3">
<div>
<div>
<label for="price" class="label">{% trans 'Item price' %}</label>
<fieldset class="fieldset">
<legend class="fieldset-legend">{% trans 'Item price' %}</legend>
<input type="number" inputmode="decimal" class="input input-bordered w-full item-price" id="price">
</div>
</fieldset>
</div>
<div>
<div>
<label for="amount" class="label">{% trans 'Item amount' %}</label>
<fieldset class="fieldset">
<legend class="fieldset-legend">{% trans 'Item amount' %}</legend>
<input type="number" inputmode="decimal" class="input input-bordered w-full item-amount" id="amount">
</div>
</fieldset>
</div>
<div>
<label class="label">{% trans 'Unit price' %}</label>
<div class="unit-price text-xl" data-amount="0">0</div>
</div>
<fieldset class="fieldset">
<legend class="fieldset-legend">{% trans 'Unit price' %}</legend>
<div class="unit-price text-xl" data-amount="0">0</div>
</fieldset>
</div>
</div>
</div>
</div>
@@ -89,59 +91,63 @@
end
end
end">
<div class="card bg-base-100 shadow-xl mb-3">
<div class="card-header bg-base-200 p-4">
<div class="card bg-base-100 shadow mb-3">
<div class="card-header bg-base-200 p-4 flex flex-row justify-between rounded-box">
<h5>{% trans "Item" %} A</h5>
</div>
<div class="card-body">
<div class="grid lg:grid-cols-3 gap-3">
<div>
<div>
<label for="price" class="label">{% trans 'Item price' %}</label>
<fieldset class="fieldset">
<legend class="fieldset-legend">{% trans 'Item price' %}</legend>
<input type="number" inputmode="decimal" class="input input-bordered w-full item-price" id="price">
</div>
</fieldset>
</div>
<div>
<div>
<label for="amount" class="label">{% trans 'Item amount' %}</label>
<fieldset class="fieldset">
<legend class="fieldset-legend">{% trans 'Item amount' %}</legend>
<input type="number" inputmode="decimal" class="input input-bordered w-full item-amount" id="amount">
</div>
</fieldset>
</div>
<div>
<label class="label">{% trans 'Unit price' %}</label>
<div class="unit-price text-xl" data-amount="0">0</div>
<fieldset class="fieldset">
<legend class="fieldset-legend">{% trans 'Unit price' %}</legend>
<div class="unit-price text-xl" data-amount="0">0</div>
</fieldset>
</div>
</div>
</div>
</div>
<div class="card bg-base-100 shadow-xl mb-3">
<div class="card-header bg-base-200 p-4">
<div class="card bg-base-100 shadow mb-3">
<div class="card-header bg-base-200 p-4 flex flex-row justify-between rounded-box">
<h5>{% trans "Item" %} B</h5>
</div>
<div class="card-body">
<div class="grid lg:grid-cols-3 gap-3">
<div>
<div>
<label for="price" class="label">{% trans 'Item price' %}</label>
<fieldset class="fieldset">
<legend class="fieldset-legend">{% trans 'Item price' %}</legend>
<input type="number" inputmode="decimal" class="input input-bordered w-full item-price" id="price">
</div>
</fieldset>
</div>
<div>
<div>
<label for="amount" class="label">{% trans 'Item amount' %}</label>
<fieldset class="fieldset">
<legend class="fieldset-legend">{% trans 'Item amount' %}</legend>
<input type="number" inputmode="decimal" class="input input-bordered w-full item-amount" id="amount">
</div>
</fieldset>
</div>
<div>
<label class="label">{% trans 'Unit price' %}</label>
<div class="unit-price text-xl" data-amount="0">0</div>
<fieldset class="fieldset">
<legend class="fieldset-legend">{% trans 'Unit price' %}</legend>
<div class="unit-price text-xl" data-amount="0">0</div>
</fieldset>
</div>
</div>
</div>
</div>
</div>
<div class="grid lg:grid-cols-[2fr_1fr] gap-3 mt-3">
<div class="grid lg:grid-cols-[2fr_1fr] gap-3 mt-6">
<div>
<button class="btn btn-primary w-full"
_="on click

View File

@@ -127,36 +127,37 @@
when its textContent.toLowerCase() contains my value.toLowerCase()">
{# Order by icon dropdown #}
<div class="dropdown dropdown-end">
<button tabindex="0" class="btn btn-secondary join-item" type="button"
<div>
<button class="btn btn-secondary join-item" type="button"
data-bs-toggle="dropdown" aria-expanded="false"
title="{% translate 'Order by' %}">
<i class="fa-solid fa-sort fa-fw"></i>
</button>
<ul tabindex="0" class="dropdown-content menu bg-base-300 rounded-box z-1 w-62 p-2 shadow-md mt-1">
<ul class="dropdown-menu dropdown-menu-end menu w-max relative">
<li>
<button class="{% if order == 'default' %}menu-active{% endif %}" type="button"
_="on click remove .menu-active from <li > button/> in the closest <ul/>
then add .menu-active to me
then set the value of #order to 'default'
then trigger change on #order">
then add .menu-active to me
then set the value of #order to 'default'
then trigger change on #order">
{% translate 'Default' %}
</button>
</li>
<li>
<button class="{% if order == 'older' %}menu-active{% endif %}" type="button"
_="on click remove .menu-active from <li > button/> in the closest <ul/>
then add .menu-active to me
then set the value of #order to 'older'
then trigger change on #order">
then add .menu-active to me
then set the value of #order to 'older'
then trigger change on #order">
{% translate 'Oldest first' %}
</button>
</li>
<li>
<button class="{% if order == 'newer' %}menu-active{% endif %}" type="button"
_="on click remove .menu-active from <li > button/> in the closest <ul/>
then add .menu-active to me
then set the value of #order to 'newer'
then trigger change on #order">
then add .menu-active to me
then set the value of #order to 'newer'
then trigger change on #order">
{% translate 'Newest first' %}
</button>
</li>

View File

@@ -9,7 +9,7 @@
<table class="table table-zebra">
<thead>
<tr>
<th scope="col" class="w-auto"></th>
<th scope="col" class="table-col-auto"></th>
<th scope="col">{% translate 'Name' %}</th>
<th scope="col">{% translate 'Account' %}</th>
<th scope="col">{% translate 'Amount' %}</th>
@@ -18,7 +18,7 @@
<tbody>
{% for qt in quick_transactions %}
<tr class="recurring_transaction">
<td class="w-auto text-center">
<td class="table-col-auto text-center">
<div class="join" role="group" aria-label="{% translate 'Actions' %}">
<a class="btn btn-secondary btn-sm join-item"
role="button"
@@ -27,7 +27,7 @@
hx-swap="innerHTML"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-pencil fa-fw"></i></a>
<a class="btn btn-secondary btn-sm join-item text-error"
<a class="btn btn-error btn-sm join-item"
role="button"
data-tippy-content="{% translate "Delete" %}"
hx-delete="{% url 'quick_transaction_delete' quick_transaction_id=qt.id %}"
@@ -42,14 +42,14 @@
</td>
<td>
<div
class="{% if qt.type == 'EX' %}text-red-400{% else %}text-green-400{% endif %}">
class="{% if qt.type == 'EX' %}text-error{% else %}text-success{% endif %}">
{{ qt.name }}
</div>
</td>
<td class="w-auto">
<td>
{% if qt.account.group %}{{ qt.account.group }} • {% endif %}{{ qt.account }}
</td>
<td class="w-auto">
<td>
<c-amount.display
:amount="qt.amount"
:prefix="qt.account.currency.prefix"

View File

@@ -5,17 +5,14 @@
{% block title %}{% translate 'Quick Transactions' %}{% endblock %}
{% block content %}
<c-ui.fab-single-action
url="{% url 'quick_transaction_add' %}"
hx_target="#generic-offcanvas">
</c-ui.fab-single-action>
<div class="container px-md-3 py-3 column-gap-5">
<div class="text-3xl font-bold font-mono w-full mb-3">
{% spaceless %}
<div>{% translate 'Quick Transactions' %}<span>
<a class="no-underline text-2xl p-1 category-action"
role="button"
data-tippy-content="{% translate "Add" %}"
hx-get="{% url 'quick_transaction_add' %}"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-circle-plus fa-fw"></i></a>
</span></div>
<div>{% translate 'Quick Transactions' %}</div>
{% endspaceless %}
</div>

View File

@@ -1,29 +1,29 @@
{% load i18n %}
<c-ui.fab-single-action
url="{% url 'recurring_transaction_add' %}"
hx_target="#generic-offcanvas">
</c-ui.fab-single-action>
<div class="container px-md-3 py-3 column-gap-5">
<div class="text-3xl font-bold font-mono w-full mb-3">
{% spaceless %}
<div>{% translate 'Recurring Transactions' %}<span>
<a class="no-underline text-2xl p-1 category-action"
role="button"
data-tippy-content="{% translate "Add" %}"
hx-get="{% url 'recurring_transaction_add' %}"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-circle-plus fa-fw"></i></a>
</span></div>
<div>{% translate 'Recurring Transactions' %}</div>
{% endspaceless %}
</div>
<div class="card bg-base-100 shadow-xl">
<div class="card-header bg-base-200 p-4">
<div role="tablist" class="tabs tabs-lifted">
<button class="tab tab-active" data-bs-toggle="tab" type="button" role="tab" aria-selected="true" hx-get="{% url 'active_recurring_transaction_list' %}" hx-trigger="load, click" hx-target="#recurring-transactions-table">{% translate 'Active' %}</button>
<button class="tab" hx-get="{% url 'paused_recurring_transaction_list' %}" hx-target="#recurring-transactions-table" data-bs-toggle="tab" type="button" role="tab" aria-selected="false">{% translate 'Paused' %}</button>
<button class="tab" hx-get="{% url 'finished_recurring_transaction_list' %}" hx-target="#recurring-transactions-table" data-bs-toggle="tab" type="button" role="tab" aria-selected="false">{% translate 'Finished' %}</button>
<div role="tablist" class="tabs tabs-border">
<input type="radio" name="installment_plan_tabs" class="tab" aria-label="{% translate 'Active' %}" checked="checked"
hx-get="{% url 'active_recurring_transaction_list' %}" hx-trigger="load, click" hx-target="#recurring-transactions-table" hx-indicator="#recurring-transactions-table" />
<input type="radio" name="installment_plan_tabs" class="tab" aria-label="{% translate 'Paused' %}"
hx-get="{% url 'paused_recurring_transaction_list' %}" hx-trigger="click" hx-target="#recurring-transactions-table" hx-indicator="#recurring-transactions-table" />
<input type="radio" name="installment_plan_tabs" class="tab" aria-label="{% translate 'Finished' %}"
hx-get="{% url 'finished_recurring_transaction_list' %}" hx-trigger="click" hx-target="#recurring-transactions-table" />
</div>
</div>
<div class="card-body">
<div id="recurring-transactions-table"></div>
<div id="recurring-transactions-table" class="show-loading"></div>
</div>
</div>

View File

@@ -14,7 +14,7 @@
<table class="table table-zebra">
<thead>
<tr>
<th scope="col" class="w-auto"></th>
<th scope="col" class="table-col-auto"></th>
<th scope="col">{% translate 'Name' %}</th>
<th scope="col">{% translate 'Account' %}</th>
<th scope="col">{% translate 'Amount' %}</th>
@@ -23,7 +23,7 @@
<tbody>
{% for recurring_transaction in recurring_transactions %}
<tr class="recurring_transaction">
<td class="w-auto text-center">
<td class="table-col-auto text-center">
<div class="join" role="group" aria-label="{% translate 'Actions' %}">
<a class="btn btn-secondary btn-sm join-item"
role="button"
@@ -41,7 +41,7 @@
<i class="fa-solid fa-eye fa-fw"></i></a>
{% if status != 'finished' %}
{% if recurring_transaction.is_paused %}
<a class="btn btn-secondary btn-sm join-item text-info"
<a class="btn btn-secondary btn-sm join-item"
role="button"
data-tippy-content="{% translate "Unpause" %}"
hx-get="{% url 'recurring_transaction_toggle_pause' recurring_transaction_id=recurring_transaction.id %}"
@@ -54,7 +54,7 @@
data-confirm-text="{% translate "Yes, unpause it!" %}"
_="install prompt_swal"><i class="fa-solid fa-play fa-fw"></i></a>
{% else %}
<a class="btn btn-secondary btn-sm join-item text-info"
<a class="btn btn-secondary btn-sm join-item"
role="button"
data-tippy-content="{% translate "Pause" %}"
hx-get="{% url 'recurring_transaction_toggle_pause' recurring_transaction_id=recurring_transaction.id %}"
@@ -68,7 +68,7 @@
_="install prompt_swal">
<i class="fa-solid fa-pause fa-fw"></i></a>
{% endif %}
<a class="btn btn-secondary btn-sm join-item text-info"
<a class="btn btn-secondary btn-sm join-item"
role="button"
data-tippy-content="{% translate "Finish" %}"
hx-get="{% url 'recurring_transaction_finish' recurring_transaction_id=recurring_transaction.id %}"
@@ -82,7 +82,7 @@
_="install prompt_swal">
<i class="fa-solid fa-flag-checkered fa-fw"></i></a>
{% endif %}
<a class="btn btn-secondary btn-sm join-item text-error"
<a class="btn btn-error btn-sm join-item"
role="button"
data-tippy-content="{% translate "Delete" %}"
hx-delete="{% url 'recurring_transaction_delete' recurring_transaction_id=recurring_transaction.id %}"
@@ -96,7 +96,7 @@
</div>
</td>
<td>
<div class="{% if recurring_transaction.type == 'EX' %}text-red-400{% else %}text-green-400{% endif %}">
<div class="{% if recurring_transaction.type == 'EX' %}text-error{% else %}text-success{% endif %}">
{{ recurring_transaction.description }}
</div>
<div class="text-sm text-base-content/60">{{ recurring_transaction.notes|linebreaksbr }}</div>

View File

@@ -20,16 +20,16 @@
<table class="table table-hover">
<thead>
<tr>
<th scope="col" class="w-auto"></th>
<th scope="col" class="w-auto"></th>
<th scope="col" class="w-auto">{% translate 'Order' %}</th>
<th scope="col" class="table-col-auto"></th>
<th scope="col" class="table-col-auto"></th>
<th scope="col" class="table-col-auto">{% translate 'Order' %}</th>
<th scope="col">{% translate 'Name' %}</th>
</tr>
</thead>
<tbody>
{% for rule in transaction_rules %}
<tr class="transaction_rule">
<td class="w-auto">
<td class="table-col-auto">
<div class="join" role="group" aria-label="{% translate 'Actions' %}">
<a class="btn btn-secondary btn-sm join-item"
role="button"
@@ -37,7 +37,7 @@
hx-get="{% url 'transaction_rule_view' transaction_rule_id=rule.id %}"
hx-target="#persistent-generic-offcanvas-left">
<i class="fa-solid fa-eye fa-fw"></i></a>
<a class="btn btn-secondary btn-sm join-item text-error"
<a class="btn btn-error btn-sm join-item"
role="button"
data-tippy-content="{% translate "Delete" %}"
hx-delete="{% url 'transaction_rule_delete' transaction_rule_id=rule.id %}"
@@ -65,7 +65,7 @@
{% endif %}
</div>
</td>
<td class="w-auto">
<td class="table-col-auto">
<a class="no-underline"
role="button"
data-tippy-content="
@@ -75,7 +75,7 @@
<i class="fa-solid fa-toggle-off text-red-400"></i>{% endif %}
</a>
</td>
<td class="text-center">
<td class="table-col-auto text-center">
<div>{{ rule.order }}</div>
</td>
<td>

View File

@@ -12,14 +12,14 @@
<table class="table table-hover">
<thead>
<tr>
<th scope="col" class="w-auto"></th>
<th scope="col" class="table-col-auto"></th>
<th scope="col">{% translate 'Name' %}</th>
</tr>
</thead>
<tbody>
{% for tag in tags %}
<tr class="tag">
<td class="w-auto">
<td class="table-col-auto">
<div class="join" role="group" aria-label="{% translate 'Actions' %}">
<a class="btn btn-secondary btn-sm join-item"
role="button"
@@ -28,7 +28,7 @@
hx-get="{% url 'tag_edit' tag_id=tag.id %}"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-pencil fa-fw"></i></a>
<a class="btn btn-secondary btn-sm join-item text-error"
<a class="btn btn-error btn-sm join-item"
role="button"
hx-swap="innerHTML"
data-tippy-content="{% translate "Delete" %}"

View File

@@ -78,36 +78,38 @@
when its textContent.toLowerCase() contains my value.toLowerCase()">
{# Order by icon dropdown #}
<div class="dropdown dropdown-end">
<button tabindex="0" class="btn btn-secondary join-item" type="button"
{# Order by icon dropdown #}
<div>
<button class="btn btn-secondary join-item" type="button"
data-bs-toggle="dropdown" aria-expanded="false"
title="{% translate 'Order by' %}">
<i class="fa-solid fa-sort fa-fw"></i>
</button>
<ul tabindex="0" class="dropdown-content menu bg-base-300 rounded-box z-1 w-62 p-2 shadow-md mt-1">
<ul class="dropdown-menu dropdown-menu-end menu w-max relative">
<li>
<button class="{% if order == 'default' %}menu-active{% endif %}" type="button"
_="on click remove .menu-active from <li > button/> in the closest <ul/>
then add .menu-active to me
then set the value of #order to 'default'
then trigger change on #order">
then add .menu-active to me
then set the value of #order to 'default'
then trigger change on #order">
{% translate 'Default' %}
</button>
</li>
<li>
<button class="{% if order == 'older' %}menu-active{% endif %}" type="button"
_="on click remove .menu-active from <li > button/> in the closest <ul/>
then add .menu-active to me
then set the value of #order to 'older'
then trigger change on #order">
then add .menu-active to me
then set the value of #order to 'older'
then trigger change on #order">
{% translate 'Oldest first' %}
</button>
</li>
<li>
<button class="{% if order == 'newer' %}menu-active{% endif %}" type="button"
_="on click remove .menu-active from <li > button/> in the closest <ul/>
then add .menu-active to me
then set the value of #order to 'newer'
then trigger change on #order">
then add .menu-active to me
then set the value of #order to 'newer'
then trigger change on #order">
{% translate 'Newest first' %}
</button>
</li>

View File

@@ -23,7 +23,7 @@
<table class="table table-hover">
<thead>
<tr>
<th scope="col" class="w-auto"></th>
<th scope="col" class="table-col-auto"></th>
<th scope="col">{% translate 'Active' %}</th>
<th scope="col">{% translate 'Name' %}</th>
<th scope="col">{% translate 'Email' %}</th>
@@ -33,7 +33,7 @@
<tbody>
{% for user in users %}
<tr class="tag">
<td class="w-auto">
<td class="table-col-auto">
<div class="join" role="group" aria-label="{% translate 'Actions' %}">
<a class="btn btn-secondary btn-sm join-item"
role="button"

View File

@@ -23,10 +23,11 @@ window.TomSelect = function createDynamicTomSelect(element) {
},
},
onInitialize: function () {
onDropdownOpen: function () {
// Move dropdown to body to escape stacking context issues
document.body.appendChild(this.dropdown);
this.popper = Popper.createPopper(this.control, this.dropdown, {
placement: "bottom-start",
strategy: "fixed",
@@ -58,11 +59,9 @@ window.TomSelect = function createDynamicTomSelect(element) {
});
},
onDropdownOpen: function () {
this.popper.update();
},
onDropdownClose: function () {
// Optional: move back to wrapper to keep DOM clean, but not necessary
this.popper.destroy();
this.dropdown.remove();
}
};

View File

@@ -81,15 +81,14 @@
position: relative;
top: 0;
min-height: 100px;
overflow: hidden;
&::before {
content: "";
position: absolute;
top: -10px;
left: -10px;
right: -10px;
bottom: -10px;
top: 0;
left: 0;
right: 0;
bottom: 0;
backdrop-filter: blur(4px);
z-index: 100;
animation: fade-in 0.1s ease-in forwards;
@@ -110,7 +109,7 @@
animation: spin 1s linear infinite, fade-in 0.1s ease-in forwards;
animation-delay: 200ms;
opacity: 0;
z-index: 9999;
z-index: 101;
}
}

View File

@@ -9,7 +9,7 @@ $offcanvas-z-index: 1090 !default;
$offcanvas-backdrop-z-index: 1040 !default;
$offcanvas-width: 400px !default;
$offcanvas-height: 30vh !default;
$offcanvas-padding: 1rem !default;
$offcanvas-padding: 0.5rem !default;
$offcanvas-transition-duration: 0.3s !default;
$offcanvas-backdrop-opacity: 0.5 !default;

View File

@@ -168,6 +168,10 @@
@apply text-base-content/60; /* .hr */
@apply my-3;
}
.table-col-auto {
@apply w-1;
}
}
@@ -244,19 +248,31 @@
}
.sidebar-submenu-header {
@apply flex;
@apply flex justify-between;
}
.sidebar-submenu-title {
@apply flex items-center;
}
.sidebar-floating .sidebar-submenu-header {
@apply lg:hidden lg:group-hover:flex;
@apply lg:justify-center lg:group-hover:justify-between;
}
.sidebar-floating .sidebar-submenu-header h5 {
@apply lg:invisible lg:group-hover:visible;
.sidebar-floating .sidebar-submenu-title {
@apply lg:h-8;
}
.sidebar-floating .sidebar-submenu-title i {
@apply lg:me-0 lg:group-hover:me-2;
}
.sidebar-floating .sidebar-submenu-title h5 {
@apply lg:invisible lg:w-0 lg:h-0 lg:overflow-hidden lg:group-hover:visible lg:group-hover:w-auto lg:group-hover:h-auto;
}
.sidebar-floating .sidebar-submenu-header button {
@apply lg:hidden lg:group-hover:inline;
@apply lg:hidden! lg:group-hover:flex!;
}
.sidebar-floating .list-unstyled {
@@ -267,6 +283,10 @@
@apply text-wrap lg:text-nowrap ;
}
.sidebar-floating .sidebar-item-list {
@apply overflow-y-auto lg:overflow-y-hidden lg:group-hover:overflow-y-auto overflow-x-hidden;
}
.sidebar-fixed {
/* Sets the fixed, expanded width for the container */
@apply lg:w-[17%] transition-all duration-100;
@@ -277,6 +297,10 @@
@apply lg:w-[17%] transition-all duration-100;
}
.sidebar-fixed .sidebar-item-list {
@apply overflow-y-auto overflow-x-hidden;
}
.sidebar-fixed + main {
/* Adjusts the main content margin to account for the expanded sidebar */
@apply lg:ml-[17%] transition-all duration-100;
@@ -314,7 +338,19 @@
.sidebar-fixed .sidebar-submenu-header {
/* Ensures menu headers are always visible */
@apply lg:flex;
@apply lg:flex lg:justify-between;
}
.sidebar-fixed .sidebar-submenu-title i {
@apply me-2!;
}
.sidebar-fixed .sidebar-submenu-title h5 {
@apply lg:block;
}
.sidebar-fixed .sidebar-submenu-header button {
@apply lg:inline;
}
.sidebar-fixed .list-unstyled {