feat(transactions): add Convert submenu to the transaction actions menu

Replaces the "Add as quick transaction" action.
This commit is contained in:
Herculino Trotta committed 2026-10-09 23:25:20 -03:00
1 parent 8c5c299312
commit 195da63998
4 files changed
+89 -75

No files matched your search

-5
View File
@@ -397,11 +397,6 @@ urlpatterns = [
views.quick_transaction_add_as_transaction,
name="quick_transaction_add_as_transaction",
),
path(
"transactions/<int:transaction_id>/add-as-quick-transaction/",
views.quick_transaction_add_as_quick_transaction,
name="quick_transaction_add_as_quick_transaction",
),
path(
"transaction/<int:transaction_id>/convert/quick-transaction/",
views.transaction_convert_to_quick_transaction,
@@ -162,70 +162,3 @@ def quick_transaction_add_as_transaction(request, quick_transaction_id):
},
)
@only_htmx
@login_required
@require_http_methods(["GET"])
def quick_transaction_add_as_quick_transaction(request, transaction_id):
transaction: Transaction = get_object_or_404(Transaction, pk=transaction_id)
if (
transaction.description
and QuickTransaction.objects.filter(
name__startswith=transaction.description
).exists()
) or QuickTransaction.objects.filter(
name__startswith=_("Quick Transaction")
).exists():
if transaction.description:
count = QuickTransaction.objects.filter(
name__startswith=transaction.description
).count()
qt_name = transaction.description + f" ({count + 1})"
else:
count = QuickTransaction.objects.filter(
name__startswith=_("Quick Transaction")
).count()
qt_name = _("Quick Transaction") + f" ({count + 1})"
else:
qt_name = transaction.description or _("Quick Transaction")
transaction_data = model_to_dict(
transaction,
exclude=[
"id",
"name",
"owner",
"account",
"category",
"tags",
"entities",
"date",
"reference_date",
"installment_plan",
"installment_id",
"recurring_transaction",
"deleted",
"deleted_at",
"internal_id",
],
)
new_quick_transaction = QuickTransaction(**transaction_data)
new_quick_transaction.account = transaction.account
new_quick_transaction.category = transaction.category
new_quick_transaction.name = qt_name
new_quick_transaction.save()
new_quick_transaction.tags.set(transaction.tags.all())
new_quick_transaction.entities.set(transaction.entities.all())
messages.success(request, _("Item added successfully"))
return HttpResponse(
status=204,
headers={
"HX-Trigger": "toasts",
},
)
+44 -3
View File
@@ -202,9 +202,50 @@
hx-target="closest .transaction" hx-swap="outerHTML"><i
class="fa-solid fa-eye-slash fa-fw mr-2"></i>{% translate 'Hide from summaries' %}</a></li>
{% endif %}
<li><a href="#"
hx-get="{% url 'quick_transaction_add_as_quick_transaction' transaction_id=transaction.id %}"><i
class="fa-solid fa-person-running fa-fw mr-2"></i>{% translate 'Add as quick transaction' %}</a>
<li class="dropdown-submenu">
<a href="#" _="on click halt"><i
class="fa-solid fa-shuffle fa-fw mr-2"></i>{% translate 'Convert' %}<i
class="fa-solid fa-chevron-right fa-fw fa-xs ms-auto"></i></a>
<ul class="dropdown-submenu-content menu">
<li><a href="#"
hx-get="{% url 'transaction_convert_to_quick_transaction' transaction_id=transaction.id %}"
hx-target="#generic-offcanvas" hx-swap="innerHTML"><i
class="fa-solid fa-person-running fa-fw mr-2"></i>{% translate 'Quick Transaction' %}</a>
</li>
{% if transaction.installment_plan or transaction.recurring_transaction %}
<li class="menu-disabled" aria-disabled="true">
<a class="flex items-center">
<i class="fa-solid fa-repeat fa-fw mr-2"></i>
<div>
{% translate 'Recurring Transaction' %}
<div
class="block text-base-content/60 text-xs font-medium">{% translate 'Already part of a plan or recurrence' %}</div>
</div>
</a>
</li>
<li class="menu-disabled" aria-disabled="true">
<a class="flex items-center">
<i class="fa-solid fa-divide fa-fw mr-2"></i>
<div>
{% translate 'Installment Plan' %}
<div
class="block text-base-content/60 text-xs font-medium">{% translate 'Already part of a plan or recurrence' %}</div>
</div>
</a>
</li>
{% else %}
<li><a href="#"
hx-get="{% url 'transaction_convert_to_recurring_transaction' transaction_id=transaction.id %}"
hx-target="#generic-offcanvas" hx-swap="innerHTML"><i
class="fa-solid fa-repeat fa-fw mr-2"></i>{% translate 'Recurring Transaction' %}</a>
</li>
<li><a href="#"
hx-get="{% url 'transaction_convert_to_installment_plan' transaction_id=transaction.id %}"
hx-target="#generic-offcanvas" hx-swap="innerHTML"><i
class="fa-solid fa-divide fa-fw mr-2"></i>{% translate 'Installment Plan' %}</a>
</li>
{% endif %}
</ul>
</li>
<hr class="my-1 hr">
<li><a href="#"
+45
View File
@@ -402,3 +402,48 @@ $breakpoints: (
--dropdown-link-disabled-color: #adb5bd;
--dropdown-header-color: #adb5bd;
}
// Flyout submenu inside a dropdown menu, opened on hover/focus
.dropdown-submenu {
position: relative;
// Block layout (not daisyUI's wrapping flex column), so the inline submenu's
// height is measured at its real width instead of a near-zero fit-content one
display: block;
> .dropdown-submenu-content {
display: none;
// Inline (mobile) submenu fits the menu's current width instead of widening it
contain: inline-size;
width: auto; // stretch to the parent item (daisyUI's .menu is fit-content)
white-space: normal;
margin-inline-start: 0.5rem;
padding: 0;
&::before {
display: none;
}
}
&:hover > .dropdown-submenu-content,
&:focus-within > .dropdown-submenu-content {
display: block;
}
@include media-breakpoint-up(md) {
> .dropdown-submenu-content {
position: absolute;
top: calc(-1 * (var(--dropdown-padding-y) + var(--dropdown-border-width)));
left: 100%;
margin: 0;
z-index: var(--dropdown-z-index);
width: max-content;
contain: none;
white-space: nowrap;
padding: var(--dropdown-padding-y) var(--dropdown-padding-x);
background-color: var(--dropdown-bg);
border: var(--dropdown-border-width) solid var(--dropdown-border-color);
border-radius: var(--dropdown-border-radius);
box-shadow: var(--dropdown-box-shadow);
}
}
}