mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-07-06 04:45:10 +02:00
feat: add actions
This commit is contained in:
@@ -2,6 +2,21 @@ from django.urls import path
|
|||||||
import apps.transactions.views as views
|
import apps.transactions.views as views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
path(
|
||||||
|
"transactions/actions/pay",
|
||||||
|
views.bulk_pay_transactions,
|
||||||
|
name="transactions_bulk_pay",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"transactions/actions/unpay/",
|
||||||
|
views.bulk_unpay_transactions,
|
||||||
|
name="transactions_bulk_unpay",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"transactions/actions/delete/",
|
||||||
|
views.bulk_delete_transactions,
|
||||||
|
name="transactions_bulk_delete",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"transaction/<int:transaction_id>/pay",
|
"transaction/<int:transaction_id>/pay",
|
||||||
views.transaction_pay,
|
views.transaction_pay,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
from .transactions import *
|
from .transactions import *
|
||||||
from .tags import *
|
from .tags import *
|
||||||
from .categories import *
|
from .categories import *
|
||||||
|
from .actions import *
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.http import HttpResponse
|
||||||
|
|
||||||
|
from apps.common.decorators.htmx import only_htmx
|
||||||
|
from apps.transactions.models import Transaction
|
||||||
|
|
||||||
|
|
||||||
|
@only_htmx
|
||||||
|
@login_required
|
||||||
|
def bulk_pay_transactions(request):
|
||||||
|
selected_transactions = request.GET.getlist("transactions", [])
|
||||||
|
Transaction.objects.filter(id__in=selected_transactions).update(is_paid=True)
|
||||||
|
|
||||||
|
return HttpResponse(
|
||||||
|
status=204,
|
||||||
|
headers={"HX-Trigger": "updated, toast, paid"},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@only_htmx
|
||||||
|
@login_required
|
||||||
|
def bulk_unpay_transactions(request):
|
||||||
|
selected_transactions = request.GET.getlist("transactions", [])
|
||||||
|
Transaction.objects.filter(id__in=selected_transactions).update(is_paid=False)
|
||||||
|
|
||||||
|
return HttpResponse(
|
||||||
|
status=204,
|
||||||
|
headers={"HX-Trigger": "updated, toast, unpaid"},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@only_htmx
|
||||||
|
@login_required
|
||||||
|
def bulk_delete_transactions(request):
|
||||||
|
selected_transactions = request.GET.getlist("transactions", [])
|
||||||
|
Transaction.objects.filter(
|
||||||
|
id__in=selected_transactions, installment_plan__isnull=True
|
||||||
|
).delete()
|
||||||
|
|
||||||
|
return HttpResponse(
|
||||||
|
status=204,
|
||||||
|
headers={"HX-Trigger": "updated, toast"},
|
||||||
|
)
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
{% include 'includes/scripts/hyperscript/hide_amount.html' %}
|
{% include 'includes/scripts/hyperscript/hide_amount.html' %}
|
||||||
{% include 'includes/scripts/hyperscript/tooltip.html' %}
|
{% include 'includes/scripts/hyperscript/tooltip.html' %}
|
||||||
{% include 'includes/scripts/hyperscript/htmx_error_handler.html' %}
|
{% include 'includes/scripts/hyperscript/htmx_error_handler.html' %}
|
||||||
|
{% include 'includes/scripts/hyperscript/sounds.html' %}
|
||||||
|
|
||||||
{% javascript_pack 'htmx' attrs="defer" %}
|
{% javascript_pack 'htmx' attrs="defer" %}
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<script type="text/hyperscript">
|
||||||
|
on paid if body do not include #settings-mute-sound
|
||||||
|
js
|
||||||
|
paidSound.pause()
|
||||||
|
paidSound.currentTime = 0
|
||||||
|
paidSound.play()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
on unpaid if body do not include #settings-mute-sound
|
||||||
|
js
|
||||||
|
unpaidSound.pause()
|
||||||
|
unpaidSound.currentTime = 0
|
||||||
|
unpaidSound.play()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
</script>
|
||||||
@@ -1,6 +1,18 @@
|
|||||||
<script type="text/hyperscript">
|
<script type="text/hyperscript">
|
||||||
behavior tooltip
|
def initTooltips
|
||||||
on mouseenter call bootstrap.Tooltip.getOrCreateInstance(me).show() end
|
-- Destroy existing tooltips
|
||||||
on mouseleave call bootstrap.Tooltip.getOrCreateInstance(me).dispose() end
|
for tooltipTriggerEl in <[data-bs-toggle="tooltip"]/>
|
||||||
|
call bootstrap.Tooltip.getOrCreateInstance(tooltipTriggerEl).dispose()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Initialize new tooltips
|
||||||
|
for tooltipTriggerEl in <[data-bs-toggle="tooltip"]/>
|
||||||
|
call bootstrap.Tooltip.getOrCreateInstance(tooltipTriggerEl)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Initialize tooltips on page load
|
||||||
|
on load or htmx:afterSettle
|
||||||
|
call initTooltips()
|
||||||
|
end
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -2,7 +2,14 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% regroup transactions by date|customnaturaldate as transactions_by_date %}
|
{% regroup transactions by date|customnaturaldate as transactions_by_date %}
|
||||||
|
|
||||||
<div>
|
<div id="transactions-list"
|
||||||
|
_="on change from <input[type='checkbox']/> in me
|
||||||
|
if no <input[type='checkbox']:checked/> in me
|
||||||
|
add .tw-hidden to #actions-bar
|
||||||
|
else
|
||||||
|
remove .tw-hidden from #actions-bar
|
||||||
|
end
|
||||||
|
end">
|
||||||
{% for x in transactions_by_date %}
|
{% for x in transactions_by_date %}
|
||||||
<div>
|
<div>
|
||||||
<div class="my-3 w-100 tw-text-base border-bottom bg-body">
|
<div class="my-3 w-100 tw-text-base border-bottom bg-body">
|
||||||
@@ -17,7 +24,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="collapse show" id="{{ x.grouper|slugify }}">
|
<div class="collapse show" id="{{ x.grouper|slugify }}">
|
||||||
<div class="ps-3">
|
<div class="">
|
||||||
{% for trans in x.list %}
|
{% for trans in x.list %}
|
||||||
{% include 'transactions/fragments/item.html' with transaction=trans %}
|
{% include 'transactions/fragments/item.html' with transaction=trans %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -36,4 +43,34 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
<!-- Floating bar -->
|
||||||
|
<div class="tw-sticky tw-bottom-4 tw-left-0 tw-right-0 tw-z-50 tw-hidden mx-auto tw-w-fit" id="actions-bar">
|
||||||
|
<div class="card slide-in-left">
|
||||||
|
<div class="card-body p-2">
|
||||||
|
<button class="btn btn-secondary me-3"
|
||||||
|
hx-get="{% url 'transactions_bulk_pay' %}"
|
||||||
|
hx-include=".transaction"
|
||||||
|
_="install tooltip"
|
||||||
|
data-bs-toggle="tooltip"
|
||||||
|
data-bs-title="{% translate 'Mark as paid' %}">
|
||||||
|
<i class="fa-regular fa-circle-check tw-text-green-400"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-secondary me-3"
|
||||||
|
hx-get="{% url 'transactions_bulk_unpay' %}"
|
||||||
|
hx-include=".transaction"
|
||||||
|
_="install tooltip"
|
||||||
|
data-bs-toggle="tooltip"
|
||||||
|
data-bs-title="{% translate 'Mark as unpaid' %}">
|
||||||
|
<i class="fa-regular fa-circle tw-text-red-400"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-secondary"
|
||||||
|
hx-get="{% url 'transactions_bulk_delete' %}"
|
||||||
|
hx-include=".transaction"
|
||||||
|
_="install tooltip"
|
||||||
|
data-bs-toggle="tooltip"
|
||||||
|
data-bs-title="{% translate 'Delete' %}"><i class="fa-solid fa-trash text-danger"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
<div class="d-flex justify-content-between mt-2">
|
<div class="d-flex justify-content-between mt-2">
|
||||||
<div class="d-flex align-items-baseline w-100">
|
<div class="d-flex align-items-baseline w-100">
|
||||||
<div class="text-start font-monospace tw-text-gray-300">
|
<div class="text-start font-monospace tw-text-gray-300">
|
||||||
<span class="hierarchy-line-icon-4"></span>{{ account_data.name }}</div>
|
<span class="hierarchy-line-icon"></span>{{ account_data.name }}</div>
|
||||||
<div class="dotted-line flex-grow-1"></div>
|
<div class="dotted-line flex-grow-1"></div>
|
||||||
<div class="amount" data-original-value="{% currency_display amount=account_data.balance prefix=account_data.currency.prefix suffix=account_data.currency.suffix decimal_places=account_data.currency.decimal_places%}"></div>
|
<div class="amount" data-original-value="{% currency_display amount=account_data.balance prefix=account_data.currency.prefix suffix=account_data.currency.suffix decimal_places=account_data.currency.decimal_places%}"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,32 +1,22 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load currency_display %}
|
{% load currency_display %}
|
||||||
<div class="tw-border-s-6 tw-border-e-0 tw-border-t-0 tw-border-b-0 border-bottom
|
<div class="transaction d-flex my-3">
|
||||||
hover:tw-bg-zinc-900 p-2 {% if transaction.account.is_asset %}tw-border-dashed{% else %}tw-border-solid{% endif %}
|
<label class="px-3 d-flex align-items-center justify-content-center">
|
||||||
{% if transaction.type == "EX" %}tw-border-red-500{% else %}tw-border-green-500{% endif %} transaction tw-relative
|
<input class="form-check-input" type="checkbox" name="transactions" value="{{ transaction.id }}" aria-label="{% translate 'Select' %}">
|
||||||
my-3"
|
</label>
|
||||||
|
<div class="tw-border-s-6 tw-border-e-0 tw-border-t-0 tw-border-b-0 border-bottom
|
||||||
|
hover:tw-bg-zinc-900 p-2 {% if transaction.account.is_asset %}tw-border-dashed{% else %}tw-border-solid{% endif %}
|
||||||
|
{% if transaction.type == "EX" %}tw-border-red-500{% else %}tw-border-green-500{% endif %} transaction tw-relative
|
||||||
|
w-100 transaction-item"
|
||||||
_="on mouseover remove .tw-invisible from the first .transaction-actions in me end
|
_="on mouseover remove .tw-invisible from the first .transaction-actions in me end
|
||||||
on mouseout add .tw-invisible to the first .transaction-actions in me end">
|
on mouseout add .tw-invisible to the first .transaction-actions in me end">
|
||||||
<div class="row font-monospace tw-text-sm align-items-center">
|
<div class="row font-monospace tw-text-sm align-items-center">
|
||||||
<div class="col-lg-1 col-12 d-flex align-items-center tw-text-2xl lg:tw-text-xl text-lg-center pay-button">
|
<div class="col-lg-1 col-12 d-flex align-items-center tw-text-2xl lg:tw-text-xl text-lg-center text-center">
|
||||||
<a class="text-decoration-none my-2 w-100"
|
<a class="text-decoration-none my-lg-3 mx-lg-3 mx-2 my-2"
|
||||||
role="button"
|
role="button"
|
||||||
hx-get="{% url 'transaction_pay' transaction_id=transaction.id %}"
|
hx-get="{% url 'transaction_pay' transaction_id=transaction.id %}"
|
||||||
hx-target="closest .transaction"
|
hx-target="closest .transaction"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML">
|
||||||
_="on paid if body do not include #settings-mute-sound
|
|
||||||
js
|
|
||||||
paidSound.pause()
|
|
||||||
paidSound.currentTime = 0
|
|
||||||
paidSound.play()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
on unpaid if body do not include #settings-mute-sound
|
|
||||||
js
|
|
||||||
unpaidSound.pause()
|
|
||||||
unpaidSound.currentTime = 0
|
|
||||||
unpaidSound.play()
|
|
||||||
end
|
|
||||||
end">
|
|
||||||
{% if transaction.is_paid %}<i class="fa-regular fa-circle-check tw-text-green-400"></i>{% else %}<i
|
{% if transaction.is_paid %}<i class="fa-regular fa-circle-check tw-text-green-400"></i>{% else %}<i
|
||||||
class="fa-regular fa-circle tw-text-red-400"></i>{% endif %}
|
class="fa-regular fa-circle tw-text-red-400"></i>{% endif %}
|
||||||
</a>
|
</a>
|
||||||
@@ -154,3 +144,4 @@ my-3"
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -48,6 +48,49 @@
|
|||||||
animation: swing-in-top-fwd 0.5s cubic-bezier(0.175, 0.885, 0.320, 1.275) both;
|
animation: swing-in-top-fwd 0.5s cubic-bezier(0.175, 0.885, 0.320, 1.275) both;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------
|
||||||
|
* Generated by Animista on 2024-10-14 20:30:58
|
||||||
|
* Licensed under FreeBSD License.
|
||||||
|
* See http://animista.net/license for more info.
|
||||||
|
* w: http://animista.net, t: @cssanimista
|
||||||
|
* ---------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ----------------------------------------
|
||||||
|
* animation slide-in-left
|
||||||
|
* ----------------------------------------
|
||||||
|
*/
|
||||||
|
@-webkit-keyframes slide-in-left {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: translateX(-1000px);
|
||||||
|
transform: translateX(-1000px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
-webkit-transform: translateX(0);
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes slide-in-left {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: translateX(-1000px);
|
||||||
|
transform: translateX(-1000px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
-webkit-transform: translateX(0);
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.slide-in-left {
|
||||||
|
-webkit-animation: slide-in-left 0.2s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
|
||||||
|
animation: slide-in-left 0.2s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
|
||||||
|
}
|
||||||
|
|
||||||
//HTMX Loading
|
//HTMX Loading
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
0% { transform: rotate(0deg); }
|
0% { transform: rotate(0deg); }
|
||||||
@@ -66,13 +109,13 @@
|
|||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
backdrop-filter: blur(4px);
|
backdrop-filter: blur(4px);
|
||||||
z-index: 1;
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 10%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
@@ -82,6 +125,6 @@
|
|||||||
border-top: 4px solid $primary;
|
border-top: 4px solid $primary;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
animation: spin 1s linear infinite;
|
animation: spin 1s linear infinite;
|
||||||
z-index: 2;
|
z-index: 1001;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ select[multiple] {
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hierarchy-line-icon-4 {
|
.hierarchy-line-icon {
|
||||||
width: 8px;
|
width: 8px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTMiIGhlaWdodD0iNSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHN0cm9rZS1saW5lY2FwPSJzcXVhcmUiPjxwYXRoIHN0cm9rZT0iIzlCOUI5QiIgc3Ryb2tlLWRhc2hhcnJheT0iNCIgZD0iTS41IDQuNWgxMiIvPjxwYXRoIHN0cm9rZT0iIzk3OTc5NyIgZD0iTS41IDQuNXYtNCIvPjwvZz48L3N2Zz4=);
|
background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTMiIGhlaWdodD0iNSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHN0cm9rZS1saW5lY2FwPSJzcXVhcmUiPjxwYXRoIHN0cm9rZT0iIzlCOUI5QiIgc3Ryb2tlLWRhc2hhcnJheT0iNCIgZD0iTS41IDQuNWgxMiIvPjxwYXRoIHN0cm9rZT0iIzk3OTc5NyIgZD0iTS41IDQuNXYtNCIvPjwvZz48L3N2Zz4=);
|
||||||
@@ -44,3 +44,8 @@ select[multiple] {
|
|||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
padding: 0 8px !important;
|
padding: 0 8px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.transaction:has(input[type="checkbox"]:checked) > .transaction-item {
|
||||||
|
background-color: $primary-bg-subtle-dark; /* Change this to your desired background color */
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user