mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-02-25 08:54:52 +01:00
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3fcd5fe7e | ||
|
|
b0a3acbdde | ||
|
|
33ce38d74c | ||
|
|
fa51a7fef9 | ||
|
|
d7c072a35c | ||
|
|
c88a6dcf3a | ||
|
|
fcb54a0af2 | ||
|
|
eec2ced481 | ||
|
|
58a6048857 | ||
|
|
93774cca64 | ||
|
|
679f49badc | ||
|
|
b535a12014 | ||
|
|
72876bff43 | ||
|
|
4411022027 | ||
|
|
086210b39d | ||
|
|
73cb2d861b | ||
|
|
1c479ef85a | ||
|
|
51b2b11825 | ||
|
|
c9d1b5b5f3 | ||
|
|
a22a95cb9f | ||
|
|
5c46a2c15e | ||
|
|
4f091c601e | ||
|
|
0fac78d15a | ||
|
|
aa171c0e76 | ||
|
|
73ca418dc8 | ||
|
|
7c34f36ffb | ||
|
|
2b6be8c6ac | ||
|
|
f643c41cf1 | ||
|
|
1ef7a780fb | ||
|
|
c3a753d221 | ||
|
|
c474b6cda9 | ||
|
|
aff3aa7ed2 | ||
|
|
414a9bb88a | ||
|
|
5f202a3820 | ||
|
|
e71775292a | ||
|
|
01aa8acb71 | ||
|
|
d030f9686b | ||
|
|
56d7e41bc5 | ||
|
|
0857b44fc3 | ||
|
|
d4b5afd8b2 | ||
|
|
9c4ba3a6de | ||
|
|
ec8b0e21d8 | ||
|
|
6c60c3659c | ||
|
|
a040b8acd2 | ||
|
|
e72d6cd1ea | ||
|
|
f6d1a42b35 | ||
|
|
eb25f8aeb3 | ||
|
|
2ee64a534e | ||
|
|
14073d3555 |
@@ -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>
|
||||
|
||||
25
README.md
25
README.md
@@ -95,31 +95,14 @@ You can now access localhost:OUTBOUND_PORT
|
||||
> [!NOTE]
|
||||
> If you're going to use another IP that isn't localhost, add it to `DJANGO_ALLOWED_HOSTS`, without `http://`
|
||||
|
||||
|
||||
## Building from source
|
||||
Features are only added to main when ready, if you want to run the latest version, you must build from source.
|
||||
|
||||
Features are only added to `main` when ready, if you want to run the latest version, you must build from source.
|
||||
All the required Dockerfiles are [here](https://github.com/eitchtee/WYGIWYH/tree/main/docker/prod).
|
||||
|
||||
```bash
|
||||
# Create a folder for WYGIWYH (optional)
|
||||
$ mkdir WYGIWYH
|
||||
## Unraid
|
||||
|
||||
# Go into the folder
|
||||
$ cd WYGIWYH
|
||||
|
||||
# Clone this repository
|
||||
$ git clone https://github.com/eitchtee/WYGIWYH.git .
|
||||
|
||||
$ cp docker-compose.prod.yml docker-compose.yml
|
||||
$ cp .env.example .env
|
||||
# Now edit both files as you see fit
|
||||
|
||||
# Run the app
|
||||
$ docker compose up -d --build
|
||||
|
||||
# Create the first admin account
|
||||
$ docker compose exec -it web python manage.py createsuperuser
|
||||
```
|
||||
[nwithan8](https://github.com/nwithan8) has kindly provided a Unraid template for WYGIWYH, have a look at the [unraid_templates](https://github.com/nwithan8/unraid_templates) repo.
|
||||
|
||||
# How it works
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -35,7 +35,7 @@ def django_to_python_datetime(django_format):
|
||||
def django_to_airdatepicker_datetime(django_format):
|
||||
format_map = {
|
||||
# Time
|
||||
"h": "h", # Hour (12-hour)
|
||||
"h": "hh", # Hour (12-hour)
|
||||
"H": "H", # Hour (24-hour)
|
||||
"i": "m", # Minutes
|
||||
"A": "AA", # AM/PM uppercase
|
||||
@@ -76,7 +76,7 @@ def django_to_airdatepicker_datetime(django_format):
|
||||
def django_to_airdatepicker_datetime_separated(django_format):
|
||||
format_map = {
|
||||
# Time formats
|
||||
"h": "hH", # Hour (12-hour)
|
||||
"h": "hh", # Hour (12-hour)
|
||||
"H": "HH", # Hour (24-hour)
|
||||
"i": "mm", # Minutes
|
||||
"A": "AA", # AM/PM uppercase
|
||||
|
||||
@@ -148,9 +148,14 @@ class AirDateTimePickerInput(widgets.DateTimeInput):
|
||||
|
||||
def format_value(self, value):
|
||||
"""Format the value for display in the widget."""
|
||||
if value:
|
||||
if value and isinstance(value, (datetime.date, datetime.datetime)):
|
||||
self.attrs["data-value"] = datetime.datetime.strftime(
|
||||
value, "%Y-%m-%d %H:%M:00"
|
||||
value, "%Y-%m-%dT%H:%M:00"
|
||||
)
|
||||
elif value and isinstance(value, str):
|
||||
value = datetime.datetime.strptime(value, "%Y-%m-%d %H:%M:00")
|
||||
self.attrs["data-value"] = datetime.datetime.strftime(
|
||||
value, "%Y-%m-%dT%H:%M:00"
|
||||
)
|
||||
|
||||
if value is None:
|
||||
@@ -195,6 +200,7 @@ class AirMonthYearPickerInput(AirDatePickerInput):
|
||||
|
||||
# Add data attributes for AirDatepicker configuration
|
||||
attrs["data-now-button-txt"] = _("Today")
|
||||
attrs["data-date-format"] = "MMMM yyyy"
|
||||
|
||||
return attrs
|
||||
|
||||
|
||||
@@ -72,7 +72,9 @@ class ExchangeRate(models.Model):
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
if self.from_currency == self.to_currency:
|
||||
raise ValidationError(
|
||||
{"to_currency": _("From and To currencies cannot be the same.")}
|
||||
)
|
||||
# Check if the attributes exist before comparing them
|
||||
if hasattr(self, "from_currency") and hasattr(self, "to_currency"):
|
||||
if self.from_currency == self.to_currency:
|
||||
raise ValidationError(
|
||||
{"to_currency": _("From and To currencies cannot be the same.")}
|
||||
)
|
||||
|
||||
@@ -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",
|
||||
# ),
|
||||
]
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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",
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -2,46 +2,80 @@
|
||||
<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
|
||||
if #actions-bar
|
||||
add .slide-in-bottom-reverse then settle
|
||||
then add .tw-hidden to #actions-bar
|
||||
then remove .slide-in-bottom-reverse
|
||||
end
|
||||
else
|
||||
remove .tw-hidden from #actions-bar
|
||||
then trigger selected_transactions_updated
|
||||
if #actions-bar
|
||||
remove .tw-hidden from #actions-bar
|
||||
then trigger selected_transactions_updated
|
||||
end
|
||||
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 +89,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 +127,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 +135,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">
|
||||
|
||||
@@ -9,4 +9,10 @@
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
on reset
|
||||
for elm in <select/> in event.target
|
||||
call elm.tomselect.clear()
|
||||
end
|
||||
end
|
||||
</script>
|
||||
|
||||
@@ -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#}
|
||||
|
||||
17
app/templates/transactions/fragments/bulk_edit.html
Normal file
17
app/templates/transactions/fragments/bulk_edit.html
Normal 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 %}
|
||||
15
app/templates/transactions/pages/add.html
Normal file
15
app/templates/transactions/pages/add.html
Normal 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 %}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
18
app/templates/transactions/widgets/paid_toggle_button.html
Normal file
18
app/templates/transactions/widgets/paid_toggle_button.html
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
@@ -161,8 +163,8 @@ window.MonthYearPicker = function createDynamicDatePicker(element) {
|
||||
let opts = {...baseOpts, ...positionConfig};
|
||||
|
||||
if (element.dataset.value) {
|
||||
opts["selectedDates"] = [element.dataset.value];
|
||||
opts["startDate"] = [element.dataset.value];
|
||||
opts["selectedDates"] = [new Date(element.dataset.value + "T00:00:00")];
|
||||
opts["startDate"] = [new Date(element.dataset.value + "T00:00:00")];
|
||||
}
|
||||
return new AirDatepicker(element, opts);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user