Compare commits

..

43 Commits
0.7.1 ... 0.7.4

Author SHA1 Message Date
Herculino Trotta
fcb54a0af2 Merge pull request #79 from DragonHeart69/main
Add new Dutch translations for v0.7.2
2025-01-26 11:20:35 -03:00
Herculino Trotta
eec2ced481 refactor(settings): drop SQL_ENGINE env variable as only postgres is supported 2025-01-26 11:19:38 -03:00
Herculino Trotta
58a6048857 fix(settings): respect SQL_PORT env variable, defaulting to 5432 if not available 2025-01-26 11:17:38 -03:00
Herculino Trotta
93774cca64 docker: update python image from slim-buster to slim-bookworm 2025-01-26 11:16:39 -03:00
Dimitri Decrock
679f49badc Add new Dutch translations for v0.7.2 2025-01-26 13:37:06 +01:00
Herculino Trotta
b535a12014 feat: enable Dutch (Nederlands) language choice 2025-01-25 15:55:42 -03:00
Herculino Trotta
72876bff43 Merge pull request #76 from DragonHeart69/main
1st edition of the Dutch translation
2025-01-25 15:36:38 -03:00
Dimitri Decrock
4411022027 delete merge 2025-01-25 19:36:51 +01:00
Dimitri Decrock
086210b39d Merge branch 'eitchtee-main' 2025-01-25 19:29:07 +01:00
Dimitri Decrock
73cb2d861b update 2025-01-25 19:26:37 +01:00
Dimitri Decrock
1c479ef85a Merge branch 'main' of https://github.com/eitchtee/WYGIWYH into eitchtee-main 2025-01-25 19:25:56 +01:00
Dimitri Decrock
51b2b11825 final translation Dutch 1st publication 2025-01-25 18:44:53 +01:00
Herculino Trotta
c9d1b5b5f3 Merge pull request #75
locale: update locales
2025-01-25 13:55:09 -03:00
Herculino Trotta
a22a95cb9f locale: update locales 2025-01-25 13:54:10 -03:00
Herculino Trotta
5c46a2c15e feat: pluralize toast for bulk edit 2025-01-25 13:48:32 -03:00
Herculino Trotta
4f091c601e Merge pull request #73
feat: add bulk duplicate action and toasts for existing actions
2025-01-25 13:44:55 -03:00
Herculino Trotta
0fac78d15a feat: add bulk duplicate action and toasts for existing actions 2025-01-25 13:44:39 -03:00
Herculino Trotta
aa171c0e76 Merge pull request #72
fix: clear internal_id when duplicating
2025-01-25 13:42:54 -03:00
Herculino Trotta
73ca418dc8 fix: clear internal_id when duplicating 2025-01-25 13:42:23 -03:00
Herculino Trotta
7c34f36ffb Merge pull request #71 from eitchtee/dev
feat: tidy up transactions action bar
2025-01-25 12:44:48 -03:00
Herculino Trotta
2b6be8c6ac feat: tidy up transactions action bar 2025-01-25 12:43:53 -03:00
Herculino Trotta
f643c41cf1 Merge pull request #70
feat: bulk edit selected transactions
2025-01-25 12:42:36 -03:00
Herculino Trotta
1ef7a780fb feat: bulk edit selected transactions 2025-01-25 12:41:55 -03:00
Herculino Trotta
c3a753d221 Merge pull request #69 from eitchtee/dev
feat: add new animation to transactions action bar
2025-01-25 12:39:51 -03:00
Herculino Trotta
c474b6cda9 feat: add new animation to transactions action bar 2025-01-25 12:37:30 -03:00
Herculino Trotta
aff3aa7ed2 feat: add new animation to transactions action bar 2025-01-25 12:37:24 -03:00
Dimitri Decrock
414a9bb88a 4d part Dutch translation 2025-01-25 14:23:23 +01:00
Herculino Trotta
5f202a3820 Merge pull request #68
feat(transactions): proper clear button for filters
2025-01-25 01:30:43 -03:00
Herculino Trotta
e71775292a feat(transactions): proper clear button for filters 2025-01-25 01:30:24 -03:00
Herculino Trotta
01aa8acb71 Merge pull request #67 from eitchtee/dev
refactor: add end slashes for some urls without
2025-01-24 22:56:20 -03:00
Herculino Trotta
d030f9686b refactor: add end slashes for some urls without 2025-01-24 22:55:36 -03:00
Herculino Trotta
56d7e41bc5 Merge pull request #66
feat: add new /add/ endpoint for quickly adding new transactions
2025-01-24 22:52:17 -03:00
Herculino Trotta
0857b44fc3 feat: add new /add/ endpoint for quickly adding new transactions 2025-01-24 22:50:39 -03:00
Herculino Trotta
d4b5afd8b2 Merge pull request #65
fix(transactions): unaligned type button
2025-01-24 22:49:42 -03:00
Herculino Trotta
9c4ba3a6de fix(transactions): unaligned type button 2025-01-24 22:48:24 -03:00
Herculino Trotta
ec8b0e21d8 Merge pull request #63
feat(transactions): new is_paid switch
2025-01-24 22:47:20 -03:00
Herculino Trotta
6c60c3659c feat(transactions): new is_paid switch 2025-01-24 22:47:00 -03:00
Herculino Trotta
a040b8acd2 Merge pull request #62
fix(transactions:filter): unaligned filter buttons
2025-01-24 22:42:20 -03:00
Herculino Trotta
e72d6cd1ea fix(transactions:filter): unaligned filter buttons 2025-01-24 22:42:01 -03:00
Dimitri Decrock
f6d1a42b35 Merge branch 'eitchtee:main' into main 2025-01-24 19:22:03 +01:00
Dimitri Decrock
eb25f8aeb3 3d part Dutch translation 2025-01-24 19:22:01 +01:00
Dimitri Decrock
2ee64a534e 2nd part Dutch translation 2025-01-23 07:13:15 +01:00
Dimitri Decrock
14073d3555 Start with Dutch translation 2025-01-22 19:36:13 +01:00
25 changed files with 1341 additions and 718 deletions

View File

@@ -9,7 +9,6 @@ SECRET_KEY=<GENERATE A SAFE SECRET KEY AND PLACE IT HERE>
DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
OUTBOUND_PORT=9005
SQL_ENGINE=django.db.backends.postgresql
SQL_DATABASE=wygiwyh
SQL_USER=wygiwyh
SQL_PASSWORD=<INSERT A SAFE PASSWORD HERE>

View File

@@ -126,12 +126,12 @@ WSGI_APPLICATION = "WYGIWYH.wsgi.application"
DATABASES = {
"default": {
"ENGINE": os.environ.get("SQL_ENGINE", "django.db.backends.sqlite3"),
"NAME": os.environ.get("SQL_DATABASE", BASE_DIR / "db.sqlite3"),
"ENGINE": "django.db.backends.postgresql",
"NAME": os.environ.get("SQL_DATABASE"),
"USER": os.environ.get("SQL_USER", "user"),
"PASSWORD": os.environ.get("SQL_PASSWORD", "password"),
"HOST": os.environ.get("SQL_HOST", "localhost"),
"PORT": "5432",
"PORT": os.environ.get("SQL_PORT", "5432"),
}
}
@@ -163,7 +163,7 @@ AUTH_USER_MODEL = "users.User"
LANGUAGE_CODE = "en"
LANGUAGES = (
("en", "English"),
# ("nl", "Nederlands"),
("nl", "Nederlands"),
("pt-br", "Português (Brasil)"),
)
@@ -363,7 +363,13 @@ PWA_APP_SPLASH_SCREEN = [
]
PWA_APP_DIR = "ltr"
PWA_APP_LANG = "en-US"
PWA_APP_SHORTCUTS = []
PWA_APP_SHORTCUTS = [
{
"name": "New Transaction",
"url": "/add/",
"description": "Add new transaction",
}
]
PWA_APP_SCREENSHOTS = [
{
"src": "/static/img/pwa/splash-750x1334.png",

View File

@@ -52,19 +52,4 @@ urlpatterns = [
views.transaction_rule_action_delete,
name="transaction_rule_action_delete",
),
# path(
# "rules/<int:installment_plan_id>/transactions/",
# views.installment_plan_transactions,
# name="rule_view",
# ),
# path(
# "rules/<int:installment_plan_id>/edit/",
# views.installment_plan_edit,
# name="rule_edit",
# ),
# path(
# "rules/<int:installment_plan_id>/delete/",
# views.installment_plan_delete,
# name="rule_delete",
# ),
]

View File

@@ -1,5 +1,5 @@
from crispy_bootstrap5.bootstrap5 import Switch
from crispy_forms.bootstrap import FormActions
from crispy_bootstrap5.bootstrap5 import Switch, BS5Accordion
from crispy_forms.bootstrap import FormActions, AccordionGroup
from crispy_forms.helper import FormHelper
from crispy_forms.layout import (
Layout,
@@ -115,7 +115,7 @@ class TransactionForm(forms.ModelForm):
"type",
template="transactions/widgets/income_expense_toggle_buttons.html",
),
Switch("is_paid"),
Field("is_paid", template="transactions/widgets/paid_toggle_button.html"),
Row(
Column("account", css_class="form-group col-md-6 mb-0"),
Column("entities", css_class="form-group col-md-6 mb-0"),
@@ -136,6 +136,46 @@ class TransactionForm(forms.ModelForm):
"notes",
)
self.helper_simple = FormHelper()
self.helper_simple.form_tag = False
self.helper_simple.form_method = "post"
self.helper_simple.layout = Layout(
Field(
"type",
template="transactions/widgets/income_expense_toggle_buttons.html",
),
Field("is_paid", template="transactions/widgets/paid_toggle_button.html"),
"account",
Row(
Column(Field("date"), css_class="form-group col-md-6 mb-0"),
Column(Field("reference_date"), css_class="form-group col-md-6 mb-0"),
css_class="form-row",
),
"description",
Field("amount", inputmode="decimal"),
BS5Accordion(
AccordionGroup(
_("More"),
"entities",
Row(
Column("category", css_class="form-group col-md-6 mb-0"),
Column("tags", css_class="form-group col-md-6 mb-0"),
css_class="form-row",
),
"notes",
active=False,
),
flush=False,
always_open=False,
css_class="mb-3",
),
FormActions(
NoClassSubmit(
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
),
)
self.fields["reference_date"].required = False
self.fields["date"].widget = AirDatePickerInput(clear_button=False, user=user)
@@ -183,6 +223,43 @@ class TransactionForm(forms.ModelForm):
return instance
class BulkEditTransactionForm(TransactionForm):
is_paid = forms.NullBooleanField(required=False)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Make all fields optional
for field_name, field in self.fields.items():
field.required = False
del self.helper.layout[-1] # Remove button
del self.helper.layout[0:2] # Remove type, is_paid field
self.helper.layout.insert(
0,
Field(
"type",
template="transactions/widgets/unselectable_income_expense_toggle_buttons.html",
),
)
self.helper.layout.insert(
1,
Field(
"is_paid",
template="transactions/widgets/unselectable_paid_toggle_button.html",
),
)
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
),
)
class TransferForm(forms.Form):
from_account = forms.ModelChoiceField(
queryset=Account.objects.filter(is_archived=False),

View File

@@ -12,7 +12,7 @@ urlpatterns = [
name="transactions_all_summary",
),
path(
"transactions/actions/pay",
"transactions/actions/pay/",
views.bulk_pay_transactions,
name="transactions_bulk_pay",
),
@@ -27,32 +27,47 @@ urlpatterns = [
name="transactions_bulk_delete",
),
path(
"transaction/<int:transaction_id>/pay",
"transactions/actions/duplicate/",
views.bulk_clone_transactions,
name="transactions_bulk_clone",
),
path(
"transaction/<int:transaction_id>/pay/",
views.transaction_pay,
name="transaction_pay",
),
path(
"transaction/<int:transaction_id>/delete",
"transaction/<int:transaction_id>/delete/",
views.transaction_delete,
name="transaction_delete",
),
path(
"transaction/<int:transaction_id>/edit",
"transaction/<int:transaction_id>/edit/",
views.transaction_edit,
name="transaction_edit",
),
path(
"transaction/<int:transaction_id>/clone",
"transactions/bulk-edit/",
views.transactions_bulk_edit,
name="transactions_bulk_edit",
),
path(
"transaction/<int:transaction_id>/clone/",
views.transaction_clone,
name="transaction_clone",
),
path(
"transaction/add",
"transaction/add/",
views.transaction_add,
name="transaction_add",
),
path(
"transactions/transfer",
"add/",
views.transaction_simple_add,
name="transaction_simple_add",
),
path(
"transactions/transfer/",
views.transactions_transfer,
name="transactions_transfer",
),

View File

@@ -1,5 +1,9 @@
from copy import deepcopy
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.utils.translation import gettext_lazy as _, ngettext_lazy
from apps.common.decorators.htmx import only_htmx
from apps.transactions.models import Transaction
@@ -9,7 +13,19 @@ from apps.transactions.models import Transaction
@login_required
def bulk_pay_transactions(request):
selected_transactions = request.GET.getlist("transactions", [])
Transaction.objects.filter(id__in=selected_transactions).update(is_paid=True)
transactions = Transaction.objects.filter(id__in=selected_transactions)
count = transactions.count()
transactions.update(is_paid=True)
messages.success(
request,
ngettext_lazy(
"%(count)s transaction marked as paid",
"%(count)s transactions marked as paid",
count,
)
% {"count": count},
)
return HttpResponse(
status=204,
@@ -21,7 +37,19 @@ def bulk_pay_transactions(request):
@login_required
def bulk_unpay_transactions(request):
selected_transactions = request.GET.getlist("transactions", [])
Transaction.objects.filter(id__in=selected_transactions).update(is_paid=False)
transactions = Transaction.objects.filter(id__in=selected_transactions)
count = transactions.count()
transactions.update(is_paid=False)
messages.success(
request,
ngettext_lazy(
"%(count)s transaction marked as not paid",
"%(count)s transactions marked as not paid",
count,
)
% {"count": count},
)
return HttpResponse(
status=204,
@@ -33,7 +61,54 @@ def bulk_unpay_transactions(request):
@login_required
def bulk_delete_transactions(request):
selected_transactions = request.GET.getlist("transactions", [])
Transaction.objects.filter(id__in=selected_transactions).delete()
transactions = Transaction.objects.filter(id__in=selected_transactions)
count = transactions.count()
transactions.delete()
messages.success(
request,
ngettext_lazy(
"%(count)s transaction deleted successfully",
"%(count)s transactions deleted successfully",
count,
)
% {"count": count},
)
return HttpResponse(
status=204,
headers={"HX-Trigger": "updated"},
)
@only_htmx
@login_required
def bulk_clone_transactions(request):
selected_transactions = request.GET.getlist("transactions", [])
transactions = Transaction.objects.filter(id__in=selected_transactions)
count = transactions.count()
for transaction in transactions:
new_transaction = deepcopy(transaction)
new_transaction.pk = None
new_transaction.installment_plan = None
new_transaction.installment_id = None
new_transaction.recurring_transaction = None
new_transaction.internal_id = None
new_transaction.save()
new_transaction.tags.add(*transaction.tags.all())
new_transaction.entities.add(*transaction.entities.all())
messages.success(
request,
ngettext_lazy(
"%(count)s transaction duplicated successfully",
"%(count)s transactions duplicated successfully",
count,
)
% {"count": count},
)
return HttpResponse(
status=204,

View File

@@ -7,14 +7,18 @@ from django.core.paginator import Paginator
from django.http import HttpResponse
from django.shortcuts import render, get_object_or_404
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext_lazy as _, ngettext_lazy
from django.views.decorators.http import require_http_methods
from apps.common.decorators.htmx import only_htmx
from apps.common.utils.dicts import remove_falsey_entries
from apps.rules.signals import transaction_created
from apps.rules.signals import transaction_created, transaction_updated
from apps.transactions.filters import TransactionsFilter
from apps.transactions.forms import TransactionForm, TransferForm
from apps.transactions.forms import (
TransactionForm,
TransferForm,
BulkEditTransactionForm,
)
from apps.transactions.models import Transaction
from apps.transactions.utils.calculations import (
calculate_currency_totals,
@@ -65,6 +69,50 @@ def transaction_add(request):
)
@login_required
@require_http_methods(["GET", "POST"])
def transaction_simple_add(request):
month = int(request.GET.get("month", timezone.localdate(timezone.now()).month))
year = int(request.GET.get("year", timezone.localdate(timezone.now()).year))
transaction_type = Transaction.Type(request.GET.get("type", "IN"))
now = timezone.localdate(timezone.now())
expected_date = datetime.datetime(
day=now.day if month == now.month and year == now.year else 1,
month=month,
year=year,
).date()
if request.method == "POST":
form = TransactionForm(request.POST, user=request.user)
if form.is_valid():
form.save()
messages.success(request, _("Transaction added successfully"))
form = TransactionForm(
user=request.user,
initial={
"date": expected_date,
"type": transaction_type,
},
)
else:
form = TransactionForm(
user=request.user,
initial={
"date": expected_date,
"type": transaction_type,
},
)
return render(
request,
"transactions/pages/add.html",
{"form": form},
)
@only_htmx
@login_required
@require_http_methods(["GET", "POST"])
@@ -91,6 +139,62 @@ def transaction_edit(request, transaction_id, **kwargs):
)
@only_htmx
@login_required
@require_http_methods(["GET", "POST"])
def transactions_bulk_edit(request):
# Get selected transaction IDs from the URL parameter
transaction_ids = request.GET.getlist("transactions") or request.POST.getlist(
"transactions"
)
# Load the selected transactions
transactions = Transaction.objects.filter(id__in=transaction_ids)
count = transactions.count()
if request.method == "POST":
form = BulkEditTransactionForm(request.POST, user=request.user)
if form.is_valid():
# Apply changes from the form to all selected transactions
for transaction in transactions:
for field_name, value in form.cleaned_data.items():
if value or isinstance(
value, bool
): # Only update fields that have been filled in the form
if field_name == "tags":
transaction.tags.set(value)
elif field_name == "entities":
transaction.entities.set(value)
else:
setattr(transaction, field_name, value)
transaction.save()
transaction_updated.send(sender=transaction)
messages.success(
request,
ngettext_lazy(
"%(count)s transaction updated successfully",
"%(count)s transactions updated successfully",
count,
)
% {"count": count},
)
return HttpResponse(
status=204,
headers={"HX-Trigger": "updated, hide_offcanvas"},
)
else:
form = BulkEditTransactionForm(
initial={"is_paid": None, "type": None}, user=request.user
)
context = {
"form": form,
"transactions": transactions,
}
return render(request, "transactions/fragments/bulk_edit.html", context)
@only_htmx
@login_required
@require_http_methods(["GET", "POST"])
@@ -101,6 +205,7 @@ def transaction_clone(request, transaction_id, **kwargs):
new_transaction.installment_plan = None
new_transaction.installment_id = None
new_transaction.recurring_transaction = None
new_transaction.internal_id = None
new_transaction.save()
new_transaction.tags.add(*transaction.tags.all())

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.5 on 2025-01-25 18:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0015_alter_usersettings_language'),
]
operations = [
migrations.AlterField(
model_name='usersettings',
name='language',
field=models.CharField(choices=[('auto', 'Auto'), ('en', 'English'), ('nl', 'Nederlands'), ('pt-br', 'Português (Brasil)')], default='auto', max_length=10, verbose_name='Language'),
),
]

File diff suppressed because it is too large Load Diff

View File

@@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-24 19:24+0000\n"
"PO-Revision-Date: 2025-01-24 16:25-0300\n"
"POT-Creation-Date: 2025-01-25 16:51+0000\n"
"PO-Revision-Date: 2025-01-25 13:53-0300\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: pt_BR\n"
@@ -26,10 +26,10 @@ msgstr "Nome do grupo"
#: apps/accounts/forms.py:40 apps/accounts/forms.py:96
#: apps/currencies/forms.py:52 apps/currencies/forms.py:92 apps/dca/forms.py:41
#: apps/dca/forms.py:93 apps/import_app/forms.py:34 apps/rules/forms.py:45
#: apps/rules/forms.py:87 apps/transactions/forms.py:150
#: apps/transactions/forms.py:506 apps/transactions/forms.py:549
#: apps/transactions/forms.py:581 apps/transactions/forms.py:616
#: apps/transactions/forms.py:754
#: apps/rules/forms.py:87 apps/transactions/forms.py:190
#: apps/transactions/forms.py:257 apps/transactions/forms.py:583
#: apps/transactions/forms.py:626 apps/transactions/forms.py:658
#: apps/transactions/forms.py:693 apps/transactions/forms.py:831
msgid "Update"
msgstr "Atualizar"
@@ -37,9 +37,10 @@ msgstr "Atualizar"
#: apps/common/widgets/tom_select.py:12 apps/currencies/forms.py:60
#: apps/currencies/forms.py:100 apps/dca/forms.py:49 apps/dca/forms.py:102
#: apps/import_app/forms.py:42 apps/rules/forms.py:53 apps/rules/forms.py:95
#: apps/transactions/forms.py:159 apps/transactions/forms.py:514
#: apps/transactions/forms.py:557 apps/transactions/forms.py:589
#: apps/transactions/forms.py:624 apps/transactions/forms.py:762
#: apps/transactions/forms.py:174 apps/transactions/forms.py:199
#: apps/transactions/forms.py:591 apps/transactions/forms.py:634
#: apps/transactions/forms.py:666 apps/transactions/forms.py:701
#: apps/transactions/forms.py:839
#: templates/account_groups/fragments/list.html:9
#: templates/accounts/fragments/list.html:9
#: templates/categories/fragments/list.html:9
@@ -66,17 +67,17 @@ msgid "New balance"
msgstr "Novo saldo"
#: apps/accounts/forms.py:119 apps/rules/models.py:27
#: apps/transactions/forms.py:39 apps/transactions/forms.py:214
#: apps/transactions/forms.py:221 apps/transactions/forms.py:401
#: apps/transactions/forms.py:648 apps/transactions/models.py:159
#: apps/transactions/forms.py:39 apps/transactions/forms.py:291
#: apps/transactions/forms.py:298 apps/transactions/forms.py:478
#: apps/transactions/forms.py:725 apps/transactions/models.py:159
#: apps/transactions/models.py:311 apps/transactions/models.py:491
msgid "Category"
msgstr "Categoria"
#: apps/accounts/forms.py:126 apps/rules/models.py:28
#: apps/transactions/filters.py:74 apps/transactions/forms.py:47
#: apps/transactions/forms.py:230 apps/transactions/forms.py:238
#: apps/transactions/forms.py:394 apps/transactions/forms.py:641
#: apps/transactions/forms.py:307 apps/transactions/forms.py:315
#: apps/transactions/forms.py:471 apps/transactions/forms.py:718
#: apps/transactions/models.py:165 apps/transactions/models.py:313
#: apps/transactions/models.py:495 templates/includes/navbar.html:98
#: templates/tags/fragments/list.html:5 templates/tags/pages/index.html:4
@@ -148,8 +149,8 @@ msgstr ""
"Contas arquivadas não aparecem nem contam para o seu patrimônio líquido"
#: apps/accounts/models.py:59 apps/rules/models.py:19
#: apps/transactions/forms.py:59 apps/transactions/forms.py:386
#: apps/transactions/forms.py:633 apps/transactions/models.py:132
#: apps/transactions/forms.py:59 apps/transactions/forms.py:463
#: apps/transactions/forms.py:710 apps/transactions/models.py:132
#: apps/transactions/models.py:271 apps/transactions/models.py:473
msgid "Account"
msgstr "Conta"
@@ -333,7 +334,8 @@ msgstr "Remover"
#: apps/common/widgets/tom_select.py:14
#: templates/mini_tools/unit_price_calculator.html:174
#: templates/transactions/pages/transactions.html:18
#: templates/monthly_overview/pages/overview.html:132
#: templates/transactions/pages/transactions.html:17
msgid "Clear"
msgstr "Limpar"
@@ -350,7 +352,7 @@ msgid "Suffix"
msgstr "Sufixo"
#: apps/currencies/forms.py:68 apps/dca/models.py:156 apps/rules/models.py:22
#: apps/transactions/forms.py:63 apps/transactions/forms.py:242
#: apps/transactions/forms.py:63 apps/transactions/forms.py:319
#: apps/transactions/models.py:142
#: templates/dca/fragments/strategy/details.html:53
#: templates/exchange_rates/fragments/table.html:11
@@ -440,7 +442,7 @@ msgid "Payment Currency"
msgstr "Moeda de pagamento"
#: apps/dca/models.py:27 apps/dca/models.py:179 apps/rules/models.py:26
#: apps/transactions/forms.py:256 apps/transactions/models.py:155
#: apps/transactions/forms.py:333 apps/transactions/models.py:155
#: apps/transactions/models.py:320 apps/transactions/models.py:501
msgid "Notes"
msgstr "Notas"
@@ -515,11 +517,6 @@ msgstr "Selecione um arquivo"
msgid "Import"
msgstr "Importar"
#: apps/import_app/models.py:12
#, python-brace-format
msgid "Version {number}"
msgstr "Versão {number}"
#: apps/import_app/models.py:15
msgid "YAML Configuration"
msgstr "Configuração YAML"
@@ -529,33 +526,38 @@ msgstr "Configuração YAML"
msgid "Version"
msgstr "Versão"
#: apps/import_app/models.py:35
#: apps/import_app/models.py:30
#, python-brace-format
msgid "Version {number}"
msgstr "Versão {number}"
#: apps/import_app/models.py:39
msgid "Invalid YAML Configuration: "
msgstr "Configuração YAML inválida: "
#: apps/import_app/models.py:41
#: apps/import_app/models.py:45
msgid "Queued"
msgstr "Na fila"
#: apps/import_app/models.py:42
#: apps/import_app/models.py:46
msgid "Processing"
msgstr "Processando"
#: apps/import_app/models.py:43
#: apps/import_app/models.py:47
msgid "Failed"
msgstr "Falhou"
#: apps/import_app/models.py:44
#: apps/import_app/models.py:48
#: templates/installment_plans/fragments/list.html:24
#: templates/recurring_transactions/fragments/list.html:27
msgid "Finished"
msgstr "Finalizado"
#: apps/import_app/models.py:50
#: apps/import_app/models.py:54
msgid "Status"
msgstr "Status"
#: apps/import_app/models.py:58
#: apps/import_app/models.py:62
msgid "File name"
msgstr "Nome do Arquivo"
@@ -604,7 +606,7 @@ msgid "A value for this field already exists in the rule."
msgstr "Já existe um valor para esse campo na regra."
#: apps/rules/models.py:10 apps/rules/models.py:25
#: apps/transactions/forms.py:248 apps/transactions/models.py:153
#: apps/transactions/forms.py:325 apps/transactions/models.py:153
#: apps/transactions/models.py:278 apps/transactions/models.py:487
msgid "Description"
msgstr "Descrição"
@@ -620,11 +622,13 @@ msgstr "Tipo"
#: apps/rules/models.py:21 apps/transactions/filters.py:23
#: apps/transactions/models.py:141
#: templates/transactions/widgets/paid_toggle_button.html:12
#: templates/transactions/widgets/unselectable_paid_toggle_button.html:16
msgid "Paid"
msgstr "Pago"
#: apps/rules/models.py:23 apps/transactions/forms.py:66
#: apps/transactions/forms.py:245 apps/transactions/forms.py:415
#: apps/transactions/forms.py:322 apps/transactions/forms.py:492
#: apps/transactions/models.py:143 apps/transactions/models.py:294
#: apps/transactions/models.py:503
msgid "Reference Date"
@@ -636,8 +640,8 @@ msgid "Amount"
msgstr "Quantia"
#: apps/rules/models.py:29 apps/transactions/filters.py:81
#: apps/transactions/forms.py:55 apps/transactions/forms.py:409
#: apps/transactions/forms.py:656 apps/transactions/models.py:117
#: apps/transactions/forms.py:55 apps/transactions/forms.py:486
#: apps/transactions/forms.py:733 apps/transactions/models.py:117
#: apps/transactions/models.py:170 apps/transactions/models.py:316
#: apps/transactions/models.py:498 templates/entities/fragments/list.html:5
#: templates/entities/pages/index.html:4 templates/includes/navbar.html:100
@@ -685,6 +689,8 @@ msgid "Action deleted successfully"
msgstr "Ação apagada com sucesso"
#: apps/transactions/filters.py:24 templates/includes/navbar.html:45
#: templates/transactions/widgets/paid_toggle_button.html:8
#: templates/transactions/widgets/unselectable_paid_toggle_button.html:12
msgid "Projected"
msgstr "Previsto"
@@ -721,23 +727,27 @@ msgstr "Quantia miníma"
msgid "Amount max"
msgstr "Quantia máxima"
#: apps/transactions/forms.py:189
#: apps/transactions/forms.py:158
msgid "More"
msgstr "Mais"
#: apps/transactions/forms.py:266
msgid "From Account"
msgstr "Conta de origem"
#: apps/transactions/forms.py:194
#: apps/transactions/forms.py:271
msgid "To Account"
msgstr "Conta de destino"
#: apps/transactions/forms.py:201
#: apps/transactions/forms.py:278
msgid "From Amount"
msgstr "Quantia de origem"
#: apps/transactions/forms.py:206
#: apps/transactions/forms.py:283
msgid "To Amount"
msgstr "Quantia de destino"
#: apps/transactions/forms.py:321
#: apps/transactions/forms.py:398
#: templates/calendar_view/pages/calendar.html:84
#: templates/monthly_overview/pages/overview.html:84
#: templates/yearly_overview/pages/overview_by_account.html:79
@@ -745,27 +755,27 @@ msgstr "Quantia de destino"
msgid "Transfer"
msgstr "Transferir"
#: apps/transactions/forms.py:336
#: apps/transactions/forms.py:413
msgid "From and To accounts must be different."
msgstr "As contas De e Para devem ser diferentes."
#: apps/transactions/forms.py:535
#: apps/transactions/forms.py:612
msgid "Tag name"
msgstr "Nome da Tag"
#: apps/transactions/forms.py:567
#: apps/transactions/forms.py:644
msgid "Entity name"
msgstr "Nome da entidade"
#: apps/transactions/forms.py:599
#: apps/transactions/forms.py:676
msgid "Category name"
msgstr "Nome da Categoria"
#: apps/transactions/forms.py:601
#: apps/transactions/forms.py:678
msgid "Muted categories won't count towards your monthly total"
msgstr "As categorias silenciadas não serão contabilizadas em seu total mensal"
#: apps/transactions/forms.py:773
#: apps/transactions/forms.py:850
msgid "End date should be after the start date"
msgstr "Data final deve ser após data inicial"
@@ -977,6 +987,34 @@ msgstr "%(value)s tem muitas casas decimais. O máximo é 30."
msgid "%(value)s is not a non-negative number"
msgstr "%(value)s não é um número positivo"
#: apps/transactions/views/actions.py:23
#, python-format
msgid "%(count)s transaction marked as paid"
msgid_plural "%(count)s transactions marked as paid"
msgstr[0] "%(count)s transação marcada como paga"
msgstr[1] "%(count)s transações marcadas como paga"
#: apps/transactions/views/actions.py:47
#, python-format
msgid "%(count)s transaction marked as not paid"
msgid_plural "%(count)s transactions marked as not paid"
msgstr[0] "%(count)s transação marcada como não paga"
msgstr[1] "%(count)s transações marcadas como não paga"
#: apps/transactions/views/actions.py:71
#, python-format
msgid "%(count)s transaction deleted successfully"
msgid_plural "%(count)s transactions deleted successfully"
msgstr[0] "%(count)s transação apagada com sucesso"
msgstr[1] "%(count)s transações apagadas com sucesso"
#: apps/transactions/views/actions.py:106
#, python-format
msgid "%(count)s transaction duplicated successfully"
msgid_plural "%(count)s transactions duplicated successfully"
msgstr[0] "%(count)s transação duplicada com sucesso"
msgstr[1] "%(count)s transações duplicadas com sucesso"
#: apps/transactions/views/categories.py:64
msgid "Category added successfully"
msgstr "Categoria adicionada com sucesso"
@@ -1053,23 +1091,31 @@ msgstr "Tag atualizada com sucesso"
msgid "Tag deleted successfully"
msgstr "Tag apagada com sucesso"
#: apps/transactions/views/transactions.py:46
#: apps/transactions/views/transactions.py:50
#: apps/transactions/views/transactions.py:90
msgid "Transaction added successfully"
msgstr "Transação adicionada com sucesso"
#: apps/transactions/views/transactions.py:78
#: apps/transactions/views/transactions.py:126
msgid "Transaction updated successfully"
msgstr "Transação atualizada com sucesso"
#: apps/transactions/views/transactions.py:109
#: apps/transactions/views/transactions.py:176
#, python-format
msgid "%(count)s transaction updated successfully"
msgid_plural "%(count)s transactions updated successfully"
msgstr[0] "%(count)s transação atualizada com sucesso"
msgstr[1] "%(count)s transações atualizadas com sucesso"
#: apps/transactions/views/transactions.py:214
msgid "Transaction duplicated successfully"
msgstr "Transação duplicada com sucesso"
#: apps/transactions/views/transactions.py:151
#: apps/transactions/views/transactions.py:256
msgid "Transaction deleted successfully"
msgstr "Transação apagada com sucesso"
#: apps/transactions/views/transactions.py:177
#: apps/transactions/views/transactions.py:282
msgid "Transfer added successfully"
msgstr "Transferência adicionada com sucesso"
@@ -1111,7 +1157,7 @@ msgstr "Essa conta está desativada"
#: apps/users/forms.py:50 apps/users/forms.py:63
#: templates/monthly_overview/pages/overview.html:116
#: templates/transactions/pages/transactions.html:36
#: templates/transactions/pages/transactions.html:35
msgid "Default"
msgstr "Padrão"
@@ -1210,6 +1256,7 @@ msgstr "Ações"
#: templates/accounts/fragments/list.html:41
#: templates/categories/fragments/table.html:29
#: templates/cotton/transaction/item.html:110
#: templates/cotton/ui/transactions_action_bar.html:43
#: templates/currencies/fragments/list.html:37
#: templates/dca/fragments/strategy/details.html:68
#: templates/dca/fragments/strategy/list.html:34
@@ -1228,7 +1275,7 @@ msgstr "Editar"
#: templates/accounts/fragments/list.html:48
#: templates/categories/fragments/table.html:36
#: templates/cotton/transaction/item.html:125
#: templates/cotton/ui/transactions_action_bar.html:50
#: templates/cotton/ui/transactions_action_bar.html:80
#: templates/currencies/fragments/list.html:44
#: templates/dca/fragments/strategy/details.html:76
#: templates/dca/fragments/strategy/list.html:42
@@ -1249,7 +1296,7 @@ msgstr "Apagar"
#: templates/accounts/fragments/list.html:52
#: templates/categories/fragments/table.html:41
#: templates/cotton/transaction/item.html:129
#: templates/cotton/ui/transactions_action_bar.html:52
#: templates/cotton/ui/transactions_action_bar.html:82
#: templates/currencies/fragments/list.html:48
#: templates/dca/fragments/strategy/details.html:81
#: templates/dca/fragments/strategy/list.html:46
@@ -1273,7 +1320,7 @@ msgstr "Tem certeza?"
#: templates/accounts/fragments/list.html:53
#: templates/categories/fragments/table.html:42
#: templates/cotton/transaction/item.html:130
#: templates/cotton/ui/transactions_action_bar.html:53
#: templates/cotton/ui/transactions_action_bar.html:83
#: templates/currencies/fragments/list.html:49
#: templates/dca/fragments/strategy/details.html:82
#: templates/dca/fragments/strategy/list.html:47
@@ -1437,6 +1484,7 @@ msgid "Select"
msgstr "Selecionar"
#: templates/cotton/transaction/item.html:117
#: templates/cotton/ui/transactions_action_bar.html:72
msgid "Duplicate"
msgstr "Duplicar"
@@ -1460,61 +1508,62 @@ msgstr "Despesas Previstas"
msgid "Current Expenses"
msgstr "Despesas Atuais"
#: templates/cotton/ui/transactions_action_bar.html:17
#: templates/cotton/ui/transactions_action_bar.html:25
msgid "Select All"
msgstr "Selecionar todos"
#: templates/cotton/ui/transactions_action_bar.html:23
#: templates/cotton/ui/transactions_action_bar.html:31
msgid "Unselect All"
msgstr "Desmarcar todos"
#: templates/cotton/ui/transactions_action_bar.html:34
msgid "Mark as paid"
msgstr "Marcar como pago"
#: templates/cotton/ui/transactions_action_bar.html:41
msgid "Mark as unpaid"
msgstr "Marcar como não pago"
#: templates/cotton/ui/transactions_action_bar.html:54
msgid "Yes, delete them!"
msgstr "Sim, apague!"
#: templates/cotton/ui/transactions_action_bar.html:101
#: templates/cotton/ui/transactions_action_bar.html:125
#: templates/cotton/ui/transactions_action_bar.html:145
#: templates/cotton/ui/transactions_action_bar.html:165
#: templates/cotton/ui/transactions_action_bar.html:185
#: templates/cotton/ui/transactions_action_bar.html:205
#: templates/cotton/ui/transactions_action_bar.html:225
msgid "copied!"
msgstr "copiado!"
#: templates/cotton/ui/transactions_action_bar.html:110
#: templates/cotton/ui/transactions_action_bar.html:48
#: templates/cotton/ui/transactions_action_bar.html:139
msgid "Toggle Dropdown"
msgstr "Alternar menu suspenso"
#: templates/cotton/ui/transactions_action_bar.html:118
#: templates/cotton/ui/transactions_action_bar.html:56
msgid "Mark as unpaid"
msgstr "Marcar como não pago"
#: templates/cotton/ui/transactions_action_bar.html:63
msgid "Mark as paid"
msgstr "Marcar como pago"
#: templates/cotton/ui/transactions_action_bar.html:84
msgid "Yes, delete them!"
msgstr "Sim, apague!"
#: templates/cotton/ui/transactions_action_bar.html:130
#: templates/cotton/ui/transactions_action_bar.html:154
#: templates/cotton/ui/transactions_action_bar.html:174
#: templates/cotton/ui/transactions_action_bar.html:194
#: templates/cotton/ui/transactions_action_bar.html:214
#: templates/cotton/ui/transactions_action_bar.html:234
#: templates/cotton/ui/transactions_action_bar.html:254
msgid "copied!"
msgstr "copiado!"
#: templates/cotton/ui/transactions_action_bar.html:147
msgid "Flat Total"
msgstr "Total Fixo"
#: templates/cotton/ui/transactions_action_bar.html:138
#: templates/cotton/ui/transactions_action_bar.html:167
msgid "Real Total"
msgstr "Total Real"
#: templates/cotton/ui/transactions_action_bar.html:158
#: templates/cotton/ui/transactions_action_bar.html:187
msgid "Mean"
msgstr "Média"
#: templates/cotton/ui/transactions_action_bar.html:178
#: templates/cotton/ui/transactions_action_bar.html:207
msgid "Max"
msgstr "Máximo"
#: templates/cotton/ui/transactions_action_bar.html:198
#: templates/cotton/ui/transactions_action_bar.html:227
msgid "Min"
msgstr "Minímo"
#: templates/cotton/ui/transactions_action_bar.html:218
#: templates/cotton/ui/transactions_action_bar.html:247
msgid "Count"
msgstr "Contagem"
@@ -1981,17 +2030,17 @@ msgid "Filter transactions"
msgstr "Filtrar transações"
#: templates/monthly_overview/pages/overview.html:114
#: templates/transactions/pages/transactions.html:34
#: templates/transactions/pages/transactions.html:33
msgid "Order by"
msgstr "Ordernar por"
#: templates/monthly_overview/pages/overview.html:117
#: templates/transactions/pages/transactions.html:37
#: templates/transactions/pages/transactions.html:36
msgid "Oldest first"
msgstr "Mais antigas primeiro"
#: templates/monthly_overview/pages/overview.html:118
#: templates/transactions/pages/transactions.html:38
#: templates/transactions/pages/transactions.html:37
msgid "Newest first"
msgstr "Mais novas primeiro"
@@ -2151,6 +2200,7 @@ msgid "No tags"
msgstr "Nenhuma tag"
#: templates/transactions/fragments/add.html:5
#: templates/transactions/pages/add.html:5
msgid "New transaction"
msgstr "Nova transação"
@@ -2158,6 +2208,18 @@ msgstr "Nova transação"
msgid "Add Installment Plan"
msgstr "Adicionar parcelamento"
#: templates/transactions/fragments/bulk_edit.html:5
msgid "Bulk Editing"
msgstr "Edição em massa"
#: templates/transactions/fragments/bulk_edit.html:8
msgid "Editing"
msgstr "Editando"
#: templates/transactions/fragments/bulk_edit.html:8
msgid "transactions"
msgstr "transações"
#: templates/transactions/fragments/edit.html:5
#: templates/transactions/fragments/edit_installment_plan.html:5
msgid "Edit transaction"
@@ -2223,6 +2285,11 @@ msgstr "Nova transferência"
msgid "Filter"
msgstr "Filtro"
#: templates/transactions/widgets/unselectable_income_expense_toggle_buttons.html:14
#: templates/transactions/widgets/unselectable_paid_toggle_button.html:8
msgid "Unchanged"
msgstr "Inalterado"
#: templates/users/generic/hide_amounts.html:2
msgid "Hide amounts"
msgstr "Esconder valores"
@@ -2249,6 +2316,11 @@ msgstr "Visão Anual"
msgid "Year"
msgstr "Ano"
#, fuzzy
#~| msgid "Transaction updated successfully"
#~ msgid "{count} transactions updated successfully"
#~ msgstr "Transação atualizada com sucesso"
#, fuzzy
#~| msgid "Important dates"
#~ msgid "Import Runs"

View File

@@ -2,46 +2,76 @@
<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"
_="on change from #transactions-list or htmx:afterSettle from window
if no <input[type='checkbox']:checked/> in #transactions-list
add .tw-hidden to #actions-bar
add .slide-in-bottom-reverse then settle
then add .tw-hidden to #actions-bar
then remove .slide-in-bottom-reverse
else
remove .tw-hidden from #actions-bar
then trigger selected_transactions_updated
end
end">
<div class="card slide-in-left">
<div class="card-body p-2">
<div class="card slide-in-bottom">
<div class="card-body p-2 d-flex justify-content-between align-items-center gap-3">
{% spaceless %}
<div class="btn-group" role="group">
<button class="btn btn-secondary btn-sm"
data-bs-toggle="tooltip"
data-bs-title="{% translate 'Select All' %}"
_="on click set <#transactions-list input[type='checkbox']/>'s checked to true then call me.blur() then trigger change">
<i class="fa-regular fa-square-check tw-text-green-400"></i>
</button>
<button class="btn btn-secondary btn-sm"
data-bs-toggle="tooltip"
data-bs-title="{% translate 'Unselect All' %}"
_="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 tw-text-red-400"></i>
<div class="dropdown">
<button class="btn btn-secondary btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown"
aria-expanded="false">
<i class="fa-regular fa-square-check fa-fw"></i>
</button>
<ul class="dropdown-menu">
<li>
<div class="dropdown-item px-3 tw-cursor-pointer"
_="on click set <#transactions-list input[type='checkbox']/>'s checked to true then call me.blur() then trigger change">
<i class="fa-regular fa-square-check tw-text-green-400 me-3"></i>{% translate 'Select All' %}
</div>
</li>
<li>
<div class="dropdown-item px-3 tw-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 tw-text-red-400 me-3"></i>{% translate 'Unselect All' %}
</div>
</li>
</ul>
</div>
<div class="vr mx-3 tw-align-middle"></div>
<div class="btn-group me-3" role="group">
<div class="vr tw-align-middle"></div>
<div class="btn-group">
<button class="btn btn-secondary btn-sm"
hx-get="{% url 'transactions_bulk_pay' %}"
hx-get="{% url 'transactions_bulk_edit' %}"
hx-target="#generic-offcanvas"
hx-include=".transaction"
data-bs-toggle="tooltip"
data-bs-title="{% translate 'Mark as paid' %}">
<i class="fa-regular fa-circle-check tw-text-green-400"></i>
data-bs-title="{% translate 'Edit' %}">
<i class="fa-solid fa-pencil"></i>
</button>
<button class="btn btn-secondary btn-sm"
hx-get="{% url 'transactions_bulk_unpay' %}"
hx-include=".transaction"
data-bs-toggle="tooltip"
data-bs-title="{% translate 'Mark as unpaid' %}">
<i class="fa-regular fa-circle tw-text-red-400"></i>
<button type="button" class="btn btn-sm btn-secondary dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside">
<span class="visually-hidden">{% trans "Toggle Dropdown" %}</span>
</button>
<ul class="dropdown-menu">
<li>
<div class="dropdown-item px-3 tw-cursor-pointer"
hx-get="{% url 'transactions_bulk_unpay' %}"
hx-include=".transaction">
<i class="fa-regular fa-circle tw-text-red-400 fa-fw me-3"></i>{% translate 'Mark as unpaid' %}
</div>
</li>
<li>
<div class="dropdown-item px-3 tw-cursor-pointer"
hx-get="{% url 'transactions_bulk_pay' %}"
hx-include=".transaction">
<i class="fa-regular fa-circle-check tw-text-green-400 fa-fw me-3"></i>{% translate 'Mark as paid' %}
</div>
</li>
</ul>
</div>
<button class="btn btn-secondary btn-sm"
hx-get="{% url 'transactions_bulk_clone' %}"
hx-include=".transaction"
data-bs-toggle="tooltip"
data-bs-title="{% translate 'Duplicate' %}">
<i class="fa-solid fa-clone fa-fw"></i>
</button>
<button class="btn btn-secondary btn-sm"
hx-get="{% url 'transactions_bulk_delete' %}"
hx-include=".transaction"
@@ -55,9 +85,9 @@
_="install prompt_swal">
<i class="fa-solid fa-trash text-danger"></i>
</button>
<div class="vr mx-3 tw-align-middle"></div>
<div class="vr tw-align-middle"></div>
<div class="btn-group"
_="on selected_transactions_updated from #actions-bar
_="on selected_transactions_updated from #actions-bar
set realTotal to math.bignumber(0)
set flatTotal to math.bignumber(0)
set transactions to <.transaction:has(input[name='transactions']:checked)/>
@@ -93,8 +123,7 @@
put Math.min.apply(Math, realAmountValues).toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40}) into #calc-menu-min's innerText
put mean.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40}) into #calc-menu-mean's innerText
put flatAmountValues.length.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40}) into #calc-menu-count's innerText
end"
>
end">
<button class="btn btn-secondary btn-sm" _="on click
set original_value to #real-total-front's innerText
writeText(original_value) on navigator.clipboard
@@ -102,8 +131,8 @@
wait 1s
put original_value into #real-total-front's innerText
end">
<i class="fa-solid fa-plus fa-fw me-2 text-primary"></i>
<span id="real-total-front">0</span>
<i class="fa-solid fa-plus fa-fw me-md-2 text-primary"></i>
<span class="d-none d-md-inline-block" id="real-total-front">0</span>
</button>
<button type="button" class="btn btn-sm btn-secondary dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside">

View File

@@ -9,4 +9,10 @@
end
end
end
on reset
for elm in <select/> in event.target
call elm.tomselect.clear()
end
end
</script>

View File

@@ -129,6 +129,7 @@
id="filter">
{% crispy filter.form %}
</form>
<button class="btn btn-outline-danger btn-sm" _="on click call #filter.reset() then trigger change on #filter">{% translate 'Clear' %}</button>
</div>
</div>
{# Transactions list#}

View File

@@ -0,0 +1,17 @@
{% extends 'extends/offcanvas.html' %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block title %}{% translate 'Bulk Editing' %}{% endblock %}
{% block body %}
<p>{% trans 'Editing' %} {{ transactions|length }} {% trans 'transactions' %}</p>
<div class="editing-transactions">
{% for transaction in transactions %}
<input type="hidden" name="transactions" value="{{ transaction.id }}"/>
{% endfor %}
</div>
<form hx-post="{% url 'transactions_bulk_edit' %}" hx-target="#generic-offcanvas" hx-include=".editing-transactions" novalidate>
{% crispy form %}
</form>
{% endblock %}

View File

@@ -0,0 +1,15 @@
{% extends 'layouts/base.html' %}
{% load crispy_forms_tags %}
{% load i18n %}
{% block title %}{% translate 'New transaction' %}{% endblock %}
{% block content %}
<div class="container py-3 column-gap-5"
_="install init_tom_select
install init_datepicker">
<form hx-post="{% url 'transaction_simple_add' %}" hx-swap="outerHTML" hx-target="body" novalidate>
{% crispy form form.helper_simple %}
</form>
</div>
{% endblock %}

View File

@@ -14,8 +14,7 @@
<div class="d-flex mb-3 align-self-center">
<div class="me-auto"><h4><i class="fa-solid fa-filter me-2"></i>{% translate 'Filter' %}</h4></div>
<div class="align-self-center">
<a href="{% url 'transactions_all_index' %}" type="button" class="btn btn-outline-danger btn-sm"
hx-target="body" hx-boost="true">{% translate 'Clear' %}</a>
<button class="btn btn-outline-danger btn-sm" _="on click call #filter.reset() then trigger change on #filter">{% translate 'Clear' %}</button>
</div>
</div>
<hr>

View File

@@ -9,7 +9,7 @@
id="{{ field.html_name }}_{{ forloop.counter }}_tr"
value="{{ choice.0 }}"
{% if choice.0 == field.value %}checked{% endif %}>
<label class="btn {% if forloop.first %}btn-outline-success{% elif forloop.last %}btn-outline-danger{% else %}btn-outline-primary{% endif %} {% if field.errors %}is-invalid{% endif %}"
<label class="btn {% if forloop.first %}btn-outline-success{% elif forloop.last %}btn-outline-danger{% else %}btn-outline-primary{% endif %} {% if field.errors %}is-invalid{% endif %} w-100"
for="{{ field.html_name }}_{{ forloop.counter }}_tr">
{{ choice.1 }}
</label>

View File

@@ -0,0 +1,18 @@
{% load i18n %}
{% load crispy_forms_field %}
<div class="form-group mb-3">
<div class="btn-group w-100" role="group" aria-label="{{ field.label }}">
<input type="radio" class="btn-check" name="{{ field.name }}" id="{{ field.id_for_label }}_false"
value="false" {% if not field.value %}checked{% endif %}>
<label class="btn btn-outline-primary w-50" for="{{ field.id_for_label }}_false">{% trans 'Projected' %}</label>
<input type="radio" class="btn-check" name="{{ field.name }}" id="{{ field.id_for_label }}_true"
value="true" {% if field.value %}checked{% endif %}>
<label class="btn btn-outline-success w-50" for="{{ field.id_for_label }}_true">{% trans 'Paid' %}</label>
</div>
{% if field.help_text %}
<div class="help-text">{{ field.help_text|safe }}</div>
{% endif %}
</div>

View File

@@ -15,7 +15,7 @@
id="{{ field.html_name }}_{{ forloop.counter }}"
value="{{ choice.0 }}"
{% if choice.0 in field.value %}checked{% endif %}>
<label class="btn btn-outline-dark"
<label class="btn btn-outline-dark w-100"
for="{{ field.html_name }}_{{ forloop.counter }}">
{{ choice.1 }}
</label>

View File

@@ -0,0 +1,40 @@
{% load i18n %}
{% load crispy_forms_field %}
<div class="form-group mb-3">
<div class="btn-group w-100" role="group" aria-label="{{ field.label }}">
<input type="radio"
class="btn-check"
name="{{ field.html_name }}"
id="{{ field.html_name }}_none_tr"
value=""
{% if field.value is None %}checked{% endif %}>
<label class="btn btn-outline-secondary {% if field.errors %}is-invalid{% endif %} w-100"
for="{{ field.html_name }}_none_tr">
{% trans 'Unchanged' %}
</label>
{% for choice in field.field.choices %}
<input type="radio"
class="btn-check"
name="{{ field.html_name }}"
id="{{ field.html_name }}_{{ forloop.counter }}_tr"
value="{{ choice.0 }}"
{% if choice.0 == field.value %}checked{% endif %}>
<label class="btn {% if forloop.first %}btn-outline-success{% elif forloop.last %}btn-outline-danger{% else %}btn-outline-primary{% endif %} {% if field.errors %}is-invalid{% endif %} w-100"
for="{{ field.html_name }}_{{ forloop.counter }}_tr">
{{ choice.1 }}
</label>
{% endfor %}
</div>
{% if field.errors %}
<div class="invalid-feedback d-block">
{% for error in field.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
{% if field.help_text %}
<small class="form-text text-muted">{{ field.help_text }}</small>
{% endif %}
</div>

View File

@@ -0,0 +1,22 @@
{% load i18n %}
{% load crispy_forms_field %}
<div class="form-group mb-3">
<div class="btn-group w-100" role="group" aria-label="{{ field.label }}">
<input type="radio" class="btn-check" name="{{ field.name }}" id="{{ field.id_for_label }}_null"
value="" {% if field.value is None %}checked{% endif %}>
<label class="btn btn-outline-secondary w-100" for="{{ field.id_for_label }}_null">{% trans 'Unchanged' %}</label>
<input type="radio" class="btn-check" name="{{ field.name }}" id="{{ field.id_for_label }}_false"
value="false" {% if field.value is False %}checked{% endif %}">
<label class="btn btn-outline-primary w-100" for="{{ field.id_for_label }}_false">{% trans 'Projected' %}</label>
<input type="radio" class="btn-check" name="{{ field.name }}" id="{{ field.id_for_label }}_true"
value="true" {% if field.value is True %}checked{% endif %}>
<label class="btn btn-outline-success w-100" for="{{ field.id_for_label }}_true">{% trans 'Paid' %}</label>
</div>
{% if field.help_text %}
<div class="help-text">{{ field.help_text|safe }}</div>
{% endif %}
</div>

View File

@@ -1,4 +1,4 @@
FROM python:3.11-slim-buster AS python-build-stage
FROM python:3.11-slim-bookworm AS python-build-stage
RUN apt-get update && apt-get install --no-install-recommends -y \
build-essential \
@@ -8,7 +8,7 @@ RUN apt-get update && apt-get install --no-install-recommends -y \
COPY ../requirements.txt .
RUN pip wheel --wheel-dir /usr/src/app/wheels -r requirements.txt
FROM python:3.11-slim-buster AS python-run-stage
FROM python:3.11-slim-bookworm AS python-run-stage
WORKDIR /usr/src/app

View File

@@ -1,4 +1,4 @@
FROM python:3.11-slim-buster AS python-build-stage
FROM python:3.11-slim-bookworm AS python-build-stage
RUN apt-get update && apt-get install --no-install-recommends -y \
build-essential \
@@ -17,7 +17,7 @@ RUN --mount=type=cache,target=/root/.npm \
npm install --verbose && \
npm run build
FROM python:3.11-slim-buster AS python-run-stage
FROM python:3.11-slim-bookworm AS python-run-stage
COPY --from=webpack_build /usr/src/frontend/build /usr/src/frontend/build
WORKDIR /usr/src/app

View File

@@ -1,11 +1,13 @@
import AirDatepicker from 'air-datepicker';
import en from 'air-datepicker/locale/en';
import ptBr from 'air-datepicker/locale/pt-BR';
import nl from 'air-datepicker/locale/nl';
import {createPopper} from '@popperjs/core';
const locales = {
'pt': ptBr,
'en': en
'en': en,
'nl': nl
};
function isMobileDevice() {

View File

@@ -205,3 +205,35 @@
.flashing {
animation: flash 1s infinite;
}
.slide-in-bottom {
animation: slide-in-bottom 0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
}
.slide-in-bottom-reverse {
animation: slide-in-bottom 0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) reverse both;
}
/* ----------------------------------------------
* Generated by Animista on 2025-1-25 12:30:4
* Licensed under FreeBSD License.
* See http://animista.net/license for more info.
* w: http://animista.net, t: @cssanimista
* ---------------------------------------------- */
/**
* ----------------------------------------
* animation slide-in-bottom
* ----------------------------------------
*/
@keyframes slide-in-bottom {
0% {
transform: translateY(1000px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}