mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-02-25 08:54:52 +01:00
Compare commits
54 Commits
0.12.11
...
feat/auto-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d81d89d9f6 | ||
|
|
6826cfe79a | ||
|
|
0832ec75ca | ||
|
|
3090f632de | ||
|
|
6b4fbee7a6 | ||
|
|
e7fe6622cd | ||
|
|
3017593ed5 | ||
|
|
ceb8e9ea97 | ||
|
|
9b5b7683dd | ||
|
|
514600e34a | ||
|
|
07dd805b07 | ||
|
|
905e9b4c54 | ||
|
|
60d367dec5 | ||
|
|
6e0842a697 | ||
|
|
858934b7c5 | ||
|
|
47d9e4078c | ||
|
|
fa6f3e87c0 | ||
|
|
5f101af879 | ||
|
|
b27633a28e | ||
|
|
7716eee0b3 | ||
|
|
37c447ae0a | ||
|
|
e544d7068b | ||
|
|
8d93da99c1 | ||
|
|
cc87477a2e | ||
|
|
e86e0b8c08 | ||
|
|
eb0c872c50 | ||
|
|
b4578df242 | ||
|
|
756de12835 | ||
|
|
d573d02657 | ||
|
|
250b352217 | ||
|
|
b4e9446cf6 | ||
|
|
90944f0179 | ||
|
|
008d34b1d0 | ||
|
|
46dfc7dad4 | ||
|
|
22900b5d9e | ||
|
|
0c48e9fe3d | ||
|
|
b2e100d1b0 | ||
|
|
e49b38a442 | ||
|
|
1f2902eea9 | ||
|
|
7d60db8716 | ||
|
|
873b0baed7 | ||
|
|
2313c97761 | ||
|
|
9cd7337153 | ||
|
|
d3b354e2b8 | ||
|
|
e137666e99 | ||
|
|
4291a5b97d | ||
|
|
c8d316857f | ||
|
|
3395a96949 | ||
|
|
8ab9624619 | ||
|
|
f9056c3a45 | ||
|
|
a9df684ee2 | ||
|
|
e4d07c94d4 | ||
|
|
5d5d172b3b | ||
|
|
99f746b6be |
@@ -31,3 +31,10 @@ ENABLE_SOFT_DELETE=false
|
||||
KEEP_DELETED_TRANSACTIONS_FOR=365
|
||||
|
||||
TASK_WORKERS=1 # This only work if you're using the single container option. Increase to have more open queues via procrastinate, you probably don't need to increase this.
|
||||
|
||||
# OIDC Configuration. Uncomment the lines below if you want to add OIDC login to your instance
|
||||
#OIDC_CLIENT_NAME=""
|
||||
#OIDC_CLIENT_ID=""
|
||||
#OIDC_CLIENT_SECRET=""
|
||||
#OIDC_SERVER_URL=""
|
||||
#OIDC_ALLOW_SIGNUP=true
|
||||
|
||||
25
README.md
25
README.md
@@ -144,6 +144,31 @@ To create the first user, open the container's console using Unraid's UI, by cli
|
||||
| ADMIN_EMAIL | string | None | Automatically creates an admin account with this email. Must have `ADMIN_PASSWORD` also set. |
|
||||
| ADMIN_PASSWORD | string | None | Automatically creates an admin account with this password. Must have `ADMIN_EMAIL` also set. |
|
||||
|
||||
## OIDC Configuration
|
||||
|
||||
WYGIWYH supports login via OpenID Connect (OIDC) through `django-allauth`. This allows users to authenticate using an external OIDC provider.
|
||||
|
||||
> [!NOTE]
|
||||
> Currently only OpenID Connect is supported as a provider, open an issue if you need something else.
|
||||
|
||||
To configure OIDC, you need to set the following environment variables:
|
||||
|
||||
| Variable | Description |
|
||||
|----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `OIDC_CLIENT_NAME` | The name of the provider. will be displayed in the login page. Defaults to `OpenID Connect` |
|
||||
| `OIDC_CLIENT_ID` | The Client ID provided by your OIDC provider. |
|
||||
| `OIDC_CLIENT_SECRET` | The Client Secret provided by your OIDC provider. |
|
||||
| `OIDC_SERVER_URL` | The base URL of your OIDC provider's discovery document or authorization server (e.g., `https://your-provider.com/auth/realms/your-realm`). `django-allauth` will use this to discover the necessary endpoints (authorization, token, userinfo, etc.). |
|
||||
| `OIDC_ALLOW_SIGNUP` | Allow the automatic creation of inexistent accounts on a successfull authentication. Defaults to `true`. |
|
||||
|
||||
**Callback URL (Redirect URI):**
|
||||
|
||||
When configuring your OIDC provider, you will need to provide a callback URL (also known as a Redirect URI). For WYGIWYH, the default callback URL is:
|
||||
|
||||
`https://your.wygiwyh.domain/auth/oidc/<OIDC_CLIENT_NAME>/login/callback/`
|
||||
|
||||
Replace `https://your.wygiwyh.domain` with the actual URL where your WYGIWYH instance is accessible. And `<OIDC_CLIENT_NAME>` with the slugfied value set in OIDC_CLIENT_NAME or the default `openid-connect` if you haven't set this variable.
|
||||
|
||||
# How it works
|
||||
|
||||
Check out our [Wiki](https://github.com/eitchtee/WYGIWYH/wiki) for more information.
|
||||
|
||||
@@ -14,6 +14,7 @@ import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from django.utils.text import slugify
|
||||
|
||||
SITE_TITLE = "WYGIWYH"
|
||||
TITLE_SEPARATOR = "::"
|
||||
@@ -42,6 +43,7 @@ INSTALLED_APPS = [
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.sites",
|
||||
"whitenoise.runserver_nostatic",
|
||||
"django.contrib.staticfiles",
|
||||
"webpack_boilerplate",
|
||||
@@ -61,7 +63,6 @@ INSTALLED_APPS = [
|
||||
"apps.transactions.apps.TransactionsConfig",
|
||||
"apps.currencies.apps.CurrenciesConfig",
|
||||
"apps.accounts.apps.AccountsConfig",
|
||||
"apps.common.apps.CommonConfig",
|
||||
"apps.net_worth.apps.NetWorthConfig",
|
||||
"apps.import_app.apps.ImportConfig",
|
||||
"apps.export_app.apps.ExportConfig",
|
||||
@@ -74,8 +75,15 @@ INSTALLED_APPS = [
|
||||
"apps.calendar_view.apps.CalendarViewConfig",
|
||||
"apps.dca.apps.DcaConfig",
|
||||
"pwa",
|
||||
"allauth",
|
||||
"allauth.account",
|
||||
"allauth.socialaccount",
|
||||
"allauth.socialaccount.providers.openid_connect",
|
||||
"apps.common.apps.CommonConfig",
|
||||
]
|
||||
|
||||
SITE_ID = 1
|
||||
|
||||
MIDDLEWARE = [
|
||||
"django_browser_reload.middleware.BrowserReloadMiddleware",
|
||||
"apps.common.middleware.thread_local.ThreadLocalMiddleware",
|
||||
@@ -91,6 +99,7 @@ MIDDLEWARE = [
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
"hijack.middleware.HijackUserMiddleware",
|
||||
"allauth.account.middleware.AccountMiddleware",
|
||||
]
|
||||
|
||||
ROOT_URLCONF = "WYGIWYH.urls"
|
||||
@@ -307,6 +316,42 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||
|
||||
LOGIN_REDIRECT_URL = "/"
|
||||
LOGIN_URL = "/login/"
|
||||
LOGOUT_REDIRECT_URL = "/login/"
|
||||
|
||||
# Allauth settings
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
"django.contrib.auth.backends.ModelBackend", # Keep default
|
||||
"allauth.account.auth_backends.AuthenticationBackend",
|
||||
]
|
||||
|
||||
SOCIALACCOUNT_PROVIDERS = {"openid_connect": {"APPS": []}}
|
||||
|
||||
if (
|
||||
os.getenv("OIDC_CLIENT_ID")
|
||||
and os.getenv("OIDC_CLIENT_SECRET")
|
||||
and os.getenv("OIDC_SERVER_URL")
|
||||
):
|
||||
SOCIALACCOUNT_PROVIDERS["openid_connect"]["APPS"].append(
|
||||
{
|
||||
"provider_id": slugify(os.getenv("OIDC_CLIENT_NAME", "OpenID Connect")),
|
||||
"name": os.getenv("OIDC_CLIENT_NAME", "OpenID Connect"),
|
||||
"client_id": os.getenv("OIDC_CLIENT_ID"),
|
||||
"secret": os.getenv("OIDC_CLIENT_SECRET"),
|
||||
"settings": {
|
||||
"server_url": os.getenv("OIDC_SERVER_URL"),
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
ACCOUNT_LOGIN_METHODS = {"email"}
|
||||
ACCOUNT_SIGNUP_FIELDS = ["email*", "password1*", "password2*"]
|
||||
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
|
||||
ACCOUNT_EMAIL_VERIFICATION = "none"
|
||||
SOCIALACCOUNT_LOGIN_ON_GET = True
|
||||
SOCIALACCOUNT_ONLY = True
|
||||
SOCIALACCOUNT_AUTO_SIGNUP = os.getenv("OIDC_ALLOW_SIGNUP", "true").lower() == "true"
|
||||
ACCOUNT_ADAPTER = "allauth.account.adapter.DefaultAccountAdapter"
|
||||
SOCIALACCOUNT_ADAPTER = "allauth.socialaccount.adapter.DefaultSocialAccountAdapter"
|
||||
|
||||
# CRISPY FORMS
|
||||
CRISPY_ALLOWED_TEMPLATE_PACKS = ["bootstrap5", "crispy_forms/pure_text"]
|
||||
|
||||
@@ -21,6 +21,8 @@ from drf_spectacular.views import (
|
||||
SpectacularAPIView,
|
||||
SpectacularSwaggerView,
|
||||
)
|
||||
from allauth.socialaccount.providers.openid_connect.views import login, callback
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path("admin/", admin.site.urls),
|
||||
@@ -36,6 +38,13 @@ urlpatterns = [
|
||||
SpectacularSwaggerView.as_view(url_name="schema"),
|
||||
name="swagger-ui",
|
||||
),
|
||||
path("auth/", include("allauth.urls")), # allauth urls
|
||||
# path("auth/oidc/<str:provider_id>/login/", login, name="openid_connect_login"),
|
||||
# path(
|
||||
# "auth/oidc/<str:provider_id>/login/callback/",
|
||||
# callback,
|
||||
# name="openid_connect_callback",
|
||||
# ),
|
||||
path("", include("apps.transactions.urls")),
|
||||
path("", include("apps.common.urls")),
|
||||
path("", include("apps.users.urls")),
|
||||
|
||||
@@ -4,3 +4,17 @@ from django.apps import AppConfig
|
||||
class CommonConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "apps.common"
|
||||
|
||||
def ready(self):
|
||||
from django.contrib import admin
|
||||
from django.contrib.sites.models import Site
|
||||
from allauth.socialaccount.models import (
|
||||
SocialAccount,
|
||||
SocialApp,
|
||||
SocialToken,
|
||||
)
|
||||
|
||||
admin.site.unregister(Site)
|
||||
admin.site.unregister(SocialAccount)
|
||||
admin.site.unregister(SocialApp)
|
||||
admin.site.unregister(SocialToken)
|
||||
|
||||
@@ -5,7 +5,7 @@ from django.utils.formats import get_format as original_get_format
|
||||
def get_format(format_type=None, lang=None, use_l10n=None):
|
||||
user = get_current_user()
|
||||
|
||||
if user and user.is_authenticated and hasattr(user, "settings"):
|
||||
if user and user.is_authenticated and hasattr(user, "settings") and use_l10n:
|
||||
user_settings = user.settings
|
||||
if format_type == "THOUSAND_SEPARATOR":
|
||||
number_format = getattr(user_settings, "number_format", None)
|
||||
|
||||
@@ -65,6 +65,18 @@ class SharedObject(models.Model):
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class OwnedObjectManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
"""Return only objects the user can access"""
|
||||
user = get_current_user()
|
||||
base_qs = super().get_queryset()
|
||||
|
||||
if user and user.is_authenticated:
|
||||
return base_qs.filter(Q(owner=user) | Q(owner=None)).distinct()
|
||||
|
||||
return base_qs
|
||||
|
||||
|
||||
class OwnedObject(models.Model):
|
||||
owner = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
|
||||
@@ -37,7 +37,9 @@ class AirDatePickerInput(widgets.DateInput):
|
||||
def _get_current_language():
|
||||
"""Get current language code in format compatible with AirDatepicker"""
|
||||
lang_code = translation.get_language()
|
||||
# AirDatepicker uses simple language codes
|
||||
# AirDatepicker uses simple language codes, except for pt-br
|
||||
if lang_code.lower() == "pt-br":
|
||||
return "pt-BR"
|
||||
return lang_code.split("-")[0]
|
||||
|
||||
def _get_format(self):
|
||||
|
||||
@@ -37,6 +37,7 @@ class ArbitraryDecimalDisplayNumberInput(forms.TextInput):
|
||||
"x-data": "",
|
||||
"x-mask:dynamic": f"$money($input, '{get_format('DECIMAL_SEPARATOR')}', "
|
||||
f"'{get_format('THOUSAND_SEPARATOR')}', '30')",
|
||||
"x-on:keyup": "$el.dispatchEvent(new Event('input'))",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ from crispy_forms.layout import (
|
||||
Column,
|
||||
Field,
|
||||
Div,
|
||||
HTML,
|
||||
)
|
||||
from django import forms
|
||||
from django.db.models import Q
|
||||
@@ -29,8 +30,8 @@ from apps.transactions.models import (
|
||||
InstallmentPlan,
|
||||
RecurringTransaction,
|
||||
TransactionEntity,
|
||||
QuickTransaction,
|
||||
)
|
||||
from apps.common.middleware.thread_local import get_current_user
|
||||
|
||||
|
||||
class TransactionForm(forms.ModelForm):
|
||||
@@ -247,6 +248,140 @@ class TransactionForm(forms.ModelForm):
|
||||
return instance
|
||||
|
||||
|
||||
class QuickTransactionForm(forms.ModelForm):
|
||||
category = DynamicModelChoiceField(
|
||||
create_field="name",
|
||||
model=TransactionCategory,
|
||||
required=False,
|
||||
label=_("Category"),
|
||||
queryset=TransactionCategory.objects.filter(active=True),
|
||||
)
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
model=TransactionTag,
|
||||
to_field_name="name",
|
||||
create_field="name",
|
||||
required=False,
|
||||
label=_("Tags"),
|
||||
queryset=TransactionTag.objects.filter(active=True),
|
||||
)
|
||||
entities = DynamicModelMultipleChoiceField(
|
||||
model=TransactionEntity,
|
||||
to_field_name="name",
|
||||
create_field="name",
|
||||
required=False,
|
||||
label=_("Entities"),
|
||||
)
|
||||
account = forms.ModelChoiceField(
|
||||
queryset=Account.objects.filter(is_archived=False),
|
||||
label=_("Account"),
|
||||
widget=TomSelect(clear_button=False, group_by="group"),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = QuickTransaction
|
||||
fields = [
|
||||
"name",
|
||||
"account",
|
||||
"type",
|
||||
"is_paid",
|
||||
"amount",
|
||||
"description",
|
||||
"notes",
|
||||
"category",
|
||||
"tags",
|
||||
"entities",
|
||||
]
|
||||
widgets = {
|
||||
"notes": forms.Textarea(attrs={"rows": 3}),
|
||||
"account": TomSelect(clear_button=False, group_by="group"),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# if editing a transaction display non-archived items and it's own item even if it's archived
|
||||
if self.instance.id:
|
||||
self.fields["account"].queryset = Account.objects.filter(
|
||||
Q(is_archived=False) | Q(transactions=self.instance.id),
|
||||
)
|
||||
|
||||
self.fields["category"].queryset = TransactionCategory.objects.filter(
|
||||
Q(active=True) | Q(transaction=self.instance.id)
|
||||
)
|
||||
|
||||
self.fields["tags"].queryset = TransactionTag.objects.filter(
|
||||
Q(active=True) | Q(transaction=self.instance.id)
|
||||
)
|
||||
|
||||
self.fields["entities"].queryset = TransactionEntity.objects.filter(
|
||||
Q(active=True) | Q(transactions=self.instance.id)
|
||||
)
|
||||
else:
|
||||
self.fields["account"].queryset = Account.objects.filter(
|
||||
is_archived=False,
|
||||
)
|
||||
|
||||
self.fields["category"].queryset = TransactionCategory.objects.filter(
|
||||
active=True
|
||||
)
|
||||
self.fields["tags"].queryset = TransactionTag.objects.filter(active=True)
|
||||
self.fields["entities"].queryset = TransactionEntity.objects.all()
|
||||
|
||||
self.helper = FormHelper()
|
||||
self.helper.form_tag = False
|
||||
self.helper.form_method = "post"
|
||||
self.helper.layout = Layout(
|
||||
Field(
|
||||
"type",
|
||||
template="transactions/widgets/income_expense_toggle_buttons.html",
|
||||
),
|
||||
Field("is_paid", template="transactions/widgets/paid_toggle_button.html"),
|
||||
"name",
|
||||
HTML("<hr />"),
|
||||
Row(
|
||||
Column("account", css_class="form-group col-md-6 mb-0"),
|
||||
Column("entities", css_class="form-group col-md-6 mb-0"),
|
||||
css_class="form-row",
|
||||
),
|
||||
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"),
|
||||
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",
|
||||
)
|
||||
|
||||
if self.instance and self.instance.pk:
|
||||
decimal_places = self.instance.account.currency.decimal_places
|
||||
self.fields["amount"].widget = ArbitraryDecimalDisplayNumberInput(
|
||||
decimal_places=decimal_places
|
||||
)
|
||||
self.helper.layout.append(
|
||||
FormActions(
|
||||
NoClassSubmit(
|
||||
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
|
||||
),
|
||||
),
|
||||
)
|
||||
else:
|
||||
self.fields["amount"].widget = ArbitraryDecimalDisplayNumberInput()
|
||||
self.helper.layout.append(
|
||||
Div(
|
||||
NoClassSubmit(
|
||||
"submit", _("Add"), css_class="btn btn-outline-primary"
|
||||
),
|
||||
css_class="d-grid gap-2",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class BulkEditTransactionForm(TransactionForm):
|
||||
is_paid = forms.NullBooleanField(required=False)
|
||||
|
||||
|
||||
45
app/apps/transactions/migrations/0043_quicktransaction.py
Normal file
45
app/apps/transactions/migrations/0043_quicktransaction.py
Normal file
@@ -0,0 +1,45 @@
|
||||
# Generated by Django 5.1.11 on 2025-06-20 03:57
|
||||
|
||||
import apps.transactions.validators
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('accounts', '0014_alter_account_options_alter_accountgroup_options'),
|
||||
('transactions', '0042_alter_transactioncategory_options_and_more'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='QuickTransaction',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100, verbose_name='Name')),
|
||||
('type', models.CharField(choices=[('IN', 'Income'), ('EX', 'Expense')], default='EX', max_length=2, verbose_name='Type')),
|
||||
('is_paid', models.BooleanField(default=True, verbose_name='Paid')),
|
||||
('amount', models.DecimalField(decimal_places=30, max_digits=42, validators=[apps.transactions.validators.validate_non_negative, apps.transactions.validators.validate_decimal_places], verbose_name='Amount')),
|
||||
('description', models.CharField(blank=True, max_length=500, verbose_name='Description')),
|
||||
('notes', models.TextField(blank=True, verbose_name='Notes')),
|
||||
('internal_note', models.TextField(blank=True, verbose_name='Internal Note')),
|
||||
('internal_id', models.TextField(blank=True, null=True, unique=True, verbose_name='Internal ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='quick_transactions', to='accounts.account', verbose_name='Account')),
|
||||
('category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='transactions.transactioncategory', verbose_name='Category')),
|
||||
('entities', models.ManyToManyField(blank=True, related_name='quick_transactions', to='transactions.transactionentity', verbose_name='Entities')),
|
||||
('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_owned', to=settings.AUTH_USER_MODEL)),
|
||||
('tags', models.ManyToManyField(blank=True, to='transactions.transactiontag', verbose_name='Tags')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Quick Transaction',
|
||||
'verbose_name_plural': 'Quick Transactions',
|
||||
'db_table': 'quick_transactions',
|
||||
'default_manager_name': 'objects',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.1.11 on 2025-06-20 04:02
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('transactions', '0043_quicktransaction'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterUniqueTogether(
|
||||
name='quicktransaction',
|
||||
unique_together={('name', 'owner')},
|
||||
),
|
||||
]
|
||||
@@ -16,7 +16,12 @@ from apps.common.templatetags.decimal import localize_number, drop_trailing_zero
|
||||
from apps.currencies.utils.convert import convert
|
||||
from apps.transactions.validators import validate_decimal_places, validate_non_negative
|
||||
from apps.common.middleware.thread_local import get_current_user
|
||||
from apps.common.models import SharedObject, SharedObjectManager, OwnedObject
|
||||
from apps.common.models import (
|
||||
SharedObject,
|
||||
SharedObjectManager,
|
||||
OwnedObject,
|
||||
OwnedObjectManager,
|
||||
)
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
@@ -886,3 +891,86 @@ class RecurringTransaction(models.Model):
|
||||
"""
|
||||
today = timezone.localdate(timezone.now())
|
||||
self.transactions.filter(is_paid=False, date__gt=today).delete()
|
||||
|
||||
|
||||
class QuickTransaction(OwnedObject):
|
||||
class Type(models.TextChoices):
|
||||
INCOME = "IN", _("Income")
|
||||
EXPENSE = "EX", _("Expense")
|
||||
|
||||
name = models.CharField(
|
||||
max_length=100,
|
||||
null=False,
|
||||
blank=False,
|
||||
verbose_name=_("Name"),
|
||||
)
|
||||
|
||||
account = models.ForeignKey(
|
||||
"accounts.Account",
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name=_("Account"),
|
||||
related_name="quick_transactions",
|
||||
)
|
||||
type = models.CharField(
|
||||
max_length=2,
|
||||
choices=Type,
|
||||
default=Type.EXPENSE,
|
||||
verbose_name=_("Type"),
|
||||
)
|
||||
is_paid = models.BooleanField(default=True, verbose_name=_("Paid"))
|
||||
|
||||
amount = models.DecimalField(
|
||||
max_digits=42,
|
||||
decimal_places=30,
|
||||
verbose_name=_("Amount"),
|
||||
validators=[validate_non_negative, validate_decimal_places],
|
||||
)
|
||||
|
||||
description = models.CharField(
|
||||
max_length=500, verbose_name=_("Description"), blank=True
|
||||
)
|
||||
notes = models.TextField(blank=True, verbose_name=_("Notes"))
|
||||
category = models.ForeignKey(
|
||||
TransactionCategory,
|
||||
on_delete=models.SET_NULL,
|
||||
verbose_name=_("Category"),
|
||||
blank=True,
|
||||
null=True,
|
||||
)
|
||||
tags = models.ManyToManyField(
|
||||
TransactionTag,
|
||||
verbose_name=_("Tags"),
|
||||
blank=True,
|
||||
)
|
||||
entities = models.ManyToManyField(
|
||||
TransactionEntity,
|
||||
verbose_name=_("Entities"),
|
||||
blank=True,
|
||||
related_name="quick_transactions",
|
||||
)
|
||||
|
||||
internal_note = models.TextField(blank=True, verbose_name=_("Internal Note"))
|
||||
internal_id = models.TextField(
|
||||
blank=True, null=True, unique=True, verbose_name=_("Internal ID")
|
||||
)
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
objects = OwnedObjectManager()
|
||||
all_objects = models.Manager() # Unfiltered manager
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Quick Transaction")
|
||||
verbose_name_plural = _("Quick Transactions")
|
||||
unique_together = ("name", "owner")
|
||||
db_table = "quick_transactions"
|
||||
default_manager_name = "objects"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.amount = truncate_decimal(
|
||||
value=self.amount, decimal_places=self.account.currency.decimal_places
|
||||
)
|
||||
|
||||
self.full_clean()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@@ -307,4 +307,39 @@ urlpatterns = [
|
||||
views.recurring_transaction_finish,
|
||||
name="recurring_transaction_finish",
|
||||
),
|
||||
path(
|
||||
"quick-transactions/",
|
||||
views.quick_transactions_index,
|
||||
name="quick_transactions_index",
|
||||
),
|
||||
path(
|
||||
"quick-transactions/list/",
|
||||
views.quick_transactions_list,
|
||||
name="quick_transactions_list",
|
||||
),
|
||||
path(
|
||||
"quick-transactions/add/",
|
||||
views.quick_transaction_add,
|
||||
name="quick_transaction_add",
|
||||
),
|
||||
path(
|
||||
"quick-transactions/<int:quick_transaction_id>/edit/",
|
||||
views.quick_transaction_edit,
|
||||
name="quick_transaction_edit",
|
||||
),
|
||||
path(
|
||||
"quick-transactions/<int:quick_transaction_id>/delete/",
|
||||
views.quick_transaction_delete,
|
||||
name="quick_transaction_delete",
|
||||
),
|
||||
path(
|
||||
"quick-transactions/create-menu/",
|
||||
views.quick_transactions_create_menu,
|
||||
name="quick_transactions_create_menu",
|
||||
),
|
||||
path(
|
||||
"quick-transactions/<int:quick_transaction_id>/create/",
|
||||
views.quick_transaction_add_as_transaction,
|
||||
name="quick_transaction_add_as_transaction",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -5,3 +5,4 @@ from .categories import *
|
||||
from .actions import *
|
||||
from .installment_plans import *
|
||||
from .recurring_transactions import *
|
||||
from .quick_transactions import *
|
||||
|
||||
154
app/apps/transactions/views/quick_transactions.py
Normal file
154
app/apps/transactions/views/quick_transactions.py
Normal file
@@ -0,0 +1,154 @@
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.forms import model_to_dict
|
||||
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.views.decorators.http import require_http_methods
|
||||
|
||||
from apps.common.decorators.htmx import only_htmx
|
||||
from apps.transactions.forms import QuickTransactionForm
|
||||
from apps.transactions.models import QuickTransaction, transaction_created
|
||||
from apps.transactions.models import Transaction
|
||||
|
||||
|
||||
@login_required
|
||||
@require_http_methods(["GET"])
|
||||
def quick_transactions_index(request):
|
||||
return render(
|
||||
request,
|
||||
"quick_transactions/pages/index.html",
|
||||
)
|
||||
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
@require_http_methods(["GET"])
|
||||
def quick_transactions_list(request):
|
||||
quick_transactions = QuickTransaction.objects.all().order_by("name")
|
||||
return render(
|
||||
request,
|
||||
"quick_transactions/fragments/list.html",
|
||||
context={"quick_transactions": quick_transactions},
|
||||
)
|
||||
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
@require_http_methods(["GET", "POST"])
|
||||
def quick_transaction_add(request):
|
||||
if request.method == "POST":
|
||||
form = QuickTransactionForm(request.POST)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, _("Item added successfully"))
|
||||
|
||||
return HttpResponse(
|
||||
status=204,
|
||||
headers={
|
||||
"HX-Trigger": "updated, hide_offcanvas",
|
||||
},
|
||||
)
|
||||
else:
|
||||
form = QuickTransactionForm()
|
||||
|
||||
return render(
|
||||
request,
|
||||
"quick_transactions/fragments/add.html",
|
||||
{"form": form},
|
||||
)
|
||||
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
@require_http_methods(["GET", "POST"])
|
||||
def quick_transaction_edit(request, quick_transaction_id):
|
||||
quick_transaction = get_object_or_404(QuickTransaction, id=quick_transaction_id)
|
||||
|
||||
if request.method == "POST":
|
||||
form = QuickTransactionForm(request.POST, instance=quick_transaction)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, _("Item updated successfully"))
|
||||
|
||||
return HttpResponse(
|
||||
status=204,
|
||||
headers={
|
||||
"HX-Trigger": "updated, hide_offcanvas",
|
||||
},
|
||||
)
|
||||
else:
|
||||
form = QuickTransactionForm(instance=quick_transaction)
|
||||
|
||||
return render(
|
||||
request,
|
||||
"quick_transactions/fragments/edit.html",
|
||||
{"form": form, "quick_transaction": quick_transaction},
|
||||
)
|
||||
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
@require_http_methods(["DELETE"])
|
||||
def quick_transaction_delete(request, quick_transaction_id):
|
||||
quick_transaction = get_object_or_404(QuickTransaction, id=quick_transaction_id)
|
||||
|
||||
quick_transaction.delete()
|
||||
|
||||
messages.success(request, _("Item deleted successfully"))
|
||||
|
||||
return HttpResponse(
|
||||
status=204,
|
||||
headers={
|
||||
"HX-Trigger": "updated, hide_offcanvas",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
@require_http_methods(["GET"])
|
||||
def quick_transactions_create_menu(request):
|
||||
quick_transactions = QuickTransaction.objects.all().order_by("name")
|
||||
return render(
|
||||
request,
|
||||
"quick_transactions/fragments/create_menu.html",
|
||||
context={"quick_transactions": quick_transactions},
|
||||
)
|
||||
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
@require_http_methods(["GET"])
|
||||
def quick_transaction_add_as_transaction(request, quick_transaction_id):
|
||||
quick_transaction: QuickTransaction = get_object_or_404(
|
||||
QuickTransaction, id=quick_transaction_id
|
||||
)
|
||||
today = timezone.localdate(timezone.now())
|
||||
|
||||
quick_transaction_data = model_to_dict(
|
||||
quick_transaction,
|
||||
exclude=["id", "name", "owner", "account", "category", "tags", "entities"],
|
||||
)
|
||||
|
||||
new_transaction = Transaction(**quick_transaction_data)
|
||||
new_transaction.account = quick_transaction.account
|
||||
new_transaction.category = quick_transaction.category
|
||||
|
||||
new_transaction.date = today
|
||||
new_transaction.reference_date = today.replace(day=1)
|
||||
new_transaction.save()
|
||||
new_transaction.tags.set(quick_transaction.tags.all())
|
||||
new_transaction.entities.set(quick_transaction.entities.all())
|
||||
|
||||
transaction_created.send(sender=new_transaction)
|
||||
|
||||
messages.success(request, _("Transaction added successfully"))
|
||||
|
||||
return HttpResponse(
|
||||
status=204,
|
||||
headers={
|
||||
"HX-Trigger": "updated, hide_offcanvas",
|
||||
},
|
||||
)
|
||||
@@ -2,7 +2,7 @@ from crispy_forms.bootstrap import (
|
||||
FormActions,
|
||||
)
|
||||
from crispy_forms.helper import FormHelper
|
||||
from crispy_forms.layout import Layout, Submit, Row, Column, Field, Div
|
||||
from crispy_forms.layout import Layout, Submit, Row, Column, Field, Div, HTML
|
||||
from django import forms
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.forms import (
|
||||
@@ -115,6 +115,7 @@ class UserSettingsForm(forms.ModelForm):
|
||||
"date_format",
|
||||
"datetime_format",
|
||||
"number_format",
|
||||
"volume",
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -126,10 +127,14 @@ class UserSettingsForm(forms.ModelForm):
|
||||
self.helper.layout = Layout(
|
||||
"language",
|
||||
"timezone",
|
||||
HTML("<hr />"),
|
||||
"date_format",
|
||||
"datetime_format",
|
||||
"number_format",
|
||||
HTML("<hr />"),
|
||||
"start_page",
|
||||
HTML("<hr />"),
|
||||
"volume",
|
||||
FormActions(
|
||||
NoClassSubmit(
|
||||
"submit", _("Save"), css_class="btn btn-outline-primary w-100"
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,479 @@
|
||||
# Generated by Django 5.1.1 on 2025-06-29 00:48
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("users", "0021_alter_usersettings_timezone"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="usersettings",
|
||||
name="volume",
|
||||
field=models.PositiveIntegerField(
|
||||
default=10,
|
||||
validators=[
|
||||
django.core.validators.MinValueValidator(1),
|
||||
django.core.validators.MaxValueValidator(10),
|
||||
],
|
||||
verbose_name="Volume",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="usersettings",
|
||||
name="timezone",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("auto", "Auto"),
|
||||
("Africa/Abidjan", "Africa/Abidjan"),
|
||||
("Africa/Accra", "Africa/Accra"),
|
||||
("Africa/Addis_Ababa", "Africa/Addis_Ababa"),
|
||||
("Africa/Algiers", "Africa/Algiers"),
|
||||
("Africa/Asmara", "Africa/Asmara"),
|
||||
("Africa/Bamako", "Africa/Bamako"),
|
||||
("Africa/Bangui", "Africa/Bangui"),
|
||||
("Africa/Banjul", "Africa/Banjul"),
|
||||
("Africa/Bissau", "Africa/Bissau"),
|
||||
("Africa/Blantyre", "Africa/Blantyre"),
|
||||
("Africa/Brazzaville", "Africa/Brazzaville"),
|
||||
("Africa/Bujumbura", "Africa/Bujumbura"),
|
||||
("Africa/Cairo", "Africa/Cairo"),
|
||||
("Africa/Casablanca", "Africa/Casablanca"),
|
||||
("Africa/Ceuta", "Africa/Ceuta"),
|
||||
("Africa/Conakry", "Africa/Conakry"),
|
||||
("Africa/Dakar", "Africa/Dakar"),
|
||||
("Africa/Dar_es_Salaam", "Africa/Dar_es_Salaam"),
|
||||
("Africa/Djibouti", "Africa/Djibouti"),
|
||||
("Africa/Douala", "Africa/Douala"),
|
||||
("Africa/El_Aaiun", "Africa/El_Aaiun"),
|
||||
("Africa/Freetown", "Africa/Freetown"),
|
||||
("Africa/Gaborone", "Africa/Gaborone"),
|
||||
("Africa/Harare", "Africa/Harare"),
|
||||
("Africa/Johannesburg", "Africa/Johannesburg"),
|
||||
("Africa/Juba", "Africa/Juba"),
|
||||
("Africa/Kampala", "Africa/Kampala"),
|
||||
("Africa/Khartoum", "Africa/Khartoum"),
|
||||
("Africa/Kigali", "Africa/Kigali"),
|
||||
("Africa/Kinshasa", "Africa/Kinshasa"),
|
||||
("Africa/Lagos", "Africa/Lagos"),
|
||||
("Africa/Libreville", "Africa/Libreville"),
|
||||
("Africa/Lome", "Africa/Lome"),
|
||||
("Africa/Luanda", "Africa/Luanda"),
|
||||
("Africa/Lubumbashi", "Africa/Lubumbashi"),
|
||||
("Africa/Lusaka", "Africa/Lusaka"),
|
||||
("Africa/Malabo", "Africa/Malabo"),
|
||||
("Africa/Maputo", "Africa/Maputo"),
|
||||
("Africa/Maseru", "Africa/Maseru"),
|
||||
("Africa/Mbabane", "Africa/Mbabane"),
|
||||
("Africa/Mogadishu", "Africa/Mogadishu"),
|
||||
("Africa/Monrovia", "Africa/Monrovia"),
|
||||
("Africa/Nairobi", "Africa/Nairobi"),
|
||||
("Africa/Ndjamena", "Africa/Ndjamena"),
|
||||
("Africa/Niamey", "Africa/Niamey"),
|
||||
("Africa/Nouakchott", "Africa/Nouakchott"),
|
||||
("Africa/Ouagadougou", "Africa/Ouagadougou"),
|
||||
("Africa/Porto-Novo", "Africa/Porto-Novo"),
|
||||
("Africa/Sao_Tome", "Africa/Sao_Tome"),
|
||||
("Africa/Tripoli", "Africa/Tripoli"),
|
||||
("Africa/Tunis", "Africa/Tunis"),
|
||||
("Africa/Windhoek", "Africa/Windhoek"),
|
||||
("America/Adak", "America/Adak"),
|
||||
("America/Anchorage", "America/Anchorage"),
|
||||
("America/Anguilla", "America/Anguilla"),
|
||||
("America/Antigua", "America/Antigua"),
|
||||
("America/Araguaina", "America/Araguaina"),
|
||||
(
|
||||
"America/Argentina/Buenos_Aires",
|
||||
"America/Argentina/Buenos_Aires",
|
||||
),
|
||||
("America/Argentina/Catamarca", "America/Argentina/Catamarca"),
|
||||
("America/Argentina/Cordoba", "America/Argentina/Cordoba"),
|
||||
("America/Argentina/Jujuy", "America/Argentina/Jujuy"),
|
||||
("America/Argentina/La_Rioja", "America/Argentina/La_Rioja"),
|
||||
("America/Argentina/Mendoza", "America/Argentina/Mendoza"),
|
||||
(
|
||||
"America/Argentina/Rio_Gallegos",
|
||||
"America/Argentina/Rio_Gallegos",
|
||||
),
|
||||
("America/Argentina/Salta", "America/Argentina/Salta"),
|
||||
("America/Argentina/San_Juan", "America/Argentina/San_Juan"),
|
||||
("America/Argentina/San_Luis", "America/Argentina/San_Luis"),
|
||||
("America/Argentina/Tucuman", "America/Argentina/Tucuman"),
|
||||
("America/Argentina/Ushuaia", "America/Argentina/Ushuaia"),
|
||||
("America/Aruba", "America/Aruba"),
|
||||
("America/Asuncion", "America/Asuncion"),
|
||||
("America/Atikokan", "America/Atikokan"),
|
||||
("America/Bahia", "America/Bahia"),
|
||||
("America/Bahia_Banderas", "America/Bahia_Banderas"),
|
||||
("America/Barbados", "America/Barbados"),
|
||||
("America/Belem", "America/Belem"),
|
||||
("America/Belize", "America/Belize"),
|
||||
("America/Blanc-Sablon", "America/Blanc-Sablon"),
|
||||
("America/Boa_Vista", "America/Boa_Vista"),
|
||||
("America/Bogota", "America/Bogota"),
|
||||
("America/Boise", "America/Boise"),
|
||||
("America/Cambridge_Bay", "America/Cambridge_Bay"),
|
||||
("America/Campo_Grande", "America/Campo_Grande"),
|
||||
("America/Cancun", "America/Cancun"),
|
||||
("America/Caracas", "America/Caracas"),
|
||||
("America/Cayenne", "America/Cayenne"),
|
||||
("America/Cayman", "America/Cayman"),
|
||||
("America/Chicago", "America/Chicago"),
|
||||
("America/Chihuahua", "America/Chihuahua"),
|
||||
("America/Ciudad_Juarez", "America/Ciudad_Juarez"),
|
||||
("America/Costa_Rica", "America/Costa_Rica"),
|
||||
("America/Creston", "America/Creston"),
|
||||
("America/Cuiaba", "America/Cuiaba"),
|
||||
("America/Curacao", "America/Curacao"),
|
||||
("America/Danmarkshavn", "America/Danmarkshavn"),
|
||||
("America/Dawson", "America/Dawson"),
|
||||
("America/Dawson_Creek", "America/Dawson_Creek"),
|
||||
("America/Denver", "America/Denver"),
|
||||
("America/Detroit", "America/Detroit"),
|
||||
("America/Dominica", "America/Dominica"),
|
||||
("America/Edmonton", "America/Edmonton"),
|
||||
("America/Eirunepe", "America/Eirunepe"),
|
||||
("America/El_Salvador", "America/El_Salvador"),
|
||||
("America/Fort_Nelson", "America/Fort_Nelson"),
|
||||
("America/Fortaleza", "America/Fortaleza"),
|
||||
("America/Glace_Bay", "America/Glace_Bay"),
|
||||
("America/Goose_Bay", "America/Goose_Bay"),
|
||||
("America/Grand_Turk", "America/Grand_Turk"),
|
||||
("America/Grenada", "America/Grenada"),
|
||||
("America/Guadeloupe", "America/Guadeloupe"),
|
||||
("America/Guatemala", "America/Guatemala"),
|
||||
("America/Guayaquil", "America/Guayaquil"),
|
||||
("America/Guyana", "America/Guyana"),
|
||||
("America/Halifax", "America/Halifax"),
|
||||
("America/Havana", "America/Havana"),
|
||||
("America/Hermosillo", "America/Hermosillo"),
|
||||
("America/Indiana/Indianapolis", "America/Indiana/Indianapolis"),
|
||||
("America/Indiana/Knox", "America/Indiana/Knox"),
|
||||
("America/Indiana/Marengo", "America/Indiana/Marengo"),
|
||||
("America/Indiana/Petersburg", "America/Indiana/Petersburg"),
|
||||
("America/Indiana/Tell_City", "America/Indiana/Tell_City"),
|
||||
("America/Indiana/Vevay", "America/Indiana/Vevay"),
|
||||
("America/Indiana/Vincennes", "America/Indiana/Vincennes"),
|
||||
("America/Indiana/Winamac", "America/Indiana/Winamac"),
|
||||
("America/Inuvik", "America/Inuvik"),
|
||||
("America/Iqaluit", "America/Iqaluit"),
|
||||
("America/Jamaica", "America/Jamaica"),
|
||||
("America/Juneau", "America/Juneau"),
|
||||
("America/Kentucky/Louisville", "America/Kentucky/Louisville"),
|
||||
("America/Kentucky/Monticello", "America/Kentucky/Monticello"),
|
||||
("America/Kralendijk", "America/Kralendijk"),
|
||||
("America/La_Paz", "America/La_Paz"),
|
||||
("America/Lima", "America/Lima"),
|
||||
("America/Los_Angeles", "America/Los_Angeles"),
|
||||
("America/Lower_Princes", "America/Lower_Princes"),
|
||||
("America/Maceio", "America/Maceio"),
|
||||
("America/Managua", "America/Managua"),
|
||||
("America/Manaus", "America/Manaus"),
|
||||
("America/Marigot", "America/Marigot"),
|
||||
("America/Martinique", "America/Martinique"),
|
||||
("America/Matamoros", "America/Matamoros"),
|
||||
("America/Mazatlan", "America/Mazatlan"),
|
||||
("America/Menominee", "America/Menominee"),
|
||||
("America/Merida", "America/Merida"),
|
||||
("America/Metlakatla", "America/Metlakatla"),
|
||||
("America/Mexico_City", "America/Mexico_City"),
|
||||
("America/Miquelon", "America/Miquelon"),
|
||||
("America/Moncton", "America/Moncton"),
|
||||
("America/Monterrey", "America/Monterrey"),
|
||||
("America/Montevideo", "America/Montevideo"),
|
||||
("America/Montserrat", "America/Montserrat"),
|
||||
("America/Nassau", "America/Nassau"),
|
||||
("America/New_York", "America/New_York"),
|
||||
("America/Nome", "America/Nome"),
|
||||
("America/Noronha", "America/Noronha"),
|
||||
("America/North_Dakota/Beulah", "America/North_Dakota/Beulah"),
|
||||
("America/North_Dakota/Center", "America/North_Dakota/Center"),
|
||||
(
|
||||
"America/North_Dakota/New_Salem",
|
||||
"America/North_Dakota/New_Salem",
|
||||
),
|
||||
("America/Nuuk", "America/Nuuk"),
|
||||
("America/Ojinaga", "America/Ojinaga"),
|
||||
("America/Panama", "America/Panama"),
|
||||
("America/Paramaribo", "America/Paramaribo"),
|
||||
("America/Phoenix", "America/Phoenix"),
|
||||
("America/Port-au-Prince", "America/Port-au-Prince"),
|
||||
("America/Port_of_Spain", "America/Port_of_Spain"),
|
||||
("America/Porto_Velho", "America/Porto_Velho"),
|
||||
("America/Puerto_Rico", "America/Puerto_Rico"),
|
||||
("America/Punta_Arenas", "America/Punta_Arenas"),
|
||||
("America/Rankin_Inlet", "America/Rankin_Inlet"),
|
||||
("America/Recife", "America/Recife"),
|
||||
("America/Regina", "America/Regina"),
|
||||
("America/Resolute", "America/Resolute"),
|
||||
("America/Rio_Branco", "America/Rio_Branco"),
|
||||
("America/Santarem", "America/Santarem"),
|
||||
("America/Santiago", "America/Santiago"),
|
||||
("America/Santo_Domingo", "America/Santo_Domingo"),
|
||||
("America/Sao_Paulo", "America/Sao_Paulo"),
|
||||
("America/Scoresbysund", "America/Scoresbysund"),
|
||||
("America/Sitka", "America/Sitka"),
|
||||
("America/St_Barthelemy", "America/St_Barthelemy"),
|
||||
("America/St_Johns", "America/St_Johns"),
|
||||
("America/St_Kitts", "America/St_Kitts"),
|
||||
("America/St_Lucia", "America/St_Lucia"),
|
||||
("America/St_Thomas", "America/St_Thomas"),
|
||||
("America/St_Vincent", "America/St_Vincent"),
|
||||
("America/Swift_Current", "America/Swift_Current"),
|
||||
("America/Tegucigalpa", "America/Tegucigalpa"),
|
||||
("America/Thule", "America/Thule"),
|
||||
("America/Tijuana", "America/Tijuana"),
|
||||
("America/Toronto", "America/Toronto"),
|
||||
("America/Tortola", "America/Tortola"),
|
||||
("America/Vancouver", "America/Vancouver"),
|
||||
("America/Whitehorse", "America/Whitehorse"),
|
||||
("America/Winnipeg", "America/Winnipeg"),
|
||||
("America/Yakutat", "America/Yakutat"),
|
||||
("Antarctica/Casey", "Antarctica/Casey"),
|
||||
("Antarctica/Davis", "Antarctica/Davis"),
|
||||
("Antarctica/DumontDUrville", "Antarctica/DumontDUrville"),
|
||||
("Antarctica/Macquarie", "Antarctica/Macquarie"),
|
||||
("Antarctica/Mawson", "Antarctica/Mawson"),
|
||||
("Antarctica/McMurdo", "Antarctica/McMurdo"),
|
||||
("Antarctica/Palmer", "Antarctica/Palmer"),
|
||||
("Antarctica/Rothera", "Antarctica/Rothera"),
|
||||
("Antarctica/Syowa", "Antarctica/Syowa"),
|
||||
("Antarctica/Troll", "Antarctica/Troll"),
|
||||
("Antarctica/Vostok", "Antarctica/Vostok"),
|
||||
("Arctic/Longyearbyen", "Arctic/Longyearbyen"),
|
||||
("Asia/Aden", "Asia/Aden"),
|
||||
("Asia/Almaty", "Asia/Almaty"),
|
||||
("Asia/Amman", "Asia/Amman"),
|
||||
("Asia/Anadyr", "Asia/Anadyr"),
|
||||
("Asia/Aqtau", "Asia/Aqtau"),
|
||||
("Asia/Aqtobe", "Asia/Aqtobe"),
|
||||
("Asia/Ashgabat", "Asia/Ashgabat"),
|
||||
("Asia/Atyrau", "Asia/Atyrau"),
|
||||
("Asia/Baghdad", "Asia/Baghdad"),
|
||||
("Asia/Bahrain", "Asia/Bahrain"),
|
||||
("Asia/Baku", "Asia/Baku"),
|
||||
("Asia/Bangkok", "Asia/Bangkok"),
|
||||
("Asia/Barnaul", "Asia/Barnaul"),
|
||||
("Asia/Beirut", "Asia/Beirut"),
|
||||
("Asia/Bishkek", "Asia/Bishkek"),
|
||||
("Asia/Brunei", "Asia/Brunei"),
|
||||
("Asia/Chita", "Asia/Chita"),
|
||||
("Asia/Colombo", "Asia/Colombo"),
|
||||
("Asia/Damascus", "Asia/Damascus"),
|
||||
("Asia/Dhaka", "Asia/Dhaka"),
|
||||
("Asia/Dili", "Asia/Dili"),
|
||||
("Asia/Dubai", "Asia/Dubai"),
|
||||
("Asia/Dushanbe", "Asia/Dushanbe"),
|
||||
("Asia/Famagusta", "Asia/Famagusta"),
|
||||
("Asia/Gaza", "Asia/Gaza"),
|
||||
("Asia/Hebron", "Asia/Hebron"),
|
||||
("Asia/Ho_Chi_Minh", "Asia/Ho_Chi_Minh"),
|
||||
("Asia/Hong_Kong", "Asia/Hong_Kong"),
|
||||
("Asia/Hovd", "Asia/Hovd"),
|
||||
("Asia/Irkutsk", "Asia/Irkutsk"),
|
||||
("Asia/Jakarta", "Asia/Jakarta"),
|
||||
("Asia/Jayapura", "Asia/Jayapura"),
|
||||
("Asia/Jerusalem", "Asia/Jerusalem"),
|
||||
("Asia/Kabul", "Asia/Kabul"),
|
||||
("Asia/Kamchatka", "Asia/Kamchatka"),
|
||||
("Asia/Karachi", "Asia/Karachi"),
|
||||
("Asia/Kathmandu", "Asia/Kathmandu"),
|
||||
("Asia/Khandyga", "Asia/Khandyga"),
|
||||
("Asia/Kolkata", "Asia/Kolkata"),
|
||||
("Asia/Krasnoyarsk", "Asia/Krasnoyarsk"),
|
||||
("Asia/Kuala_Lumpur", "Asia/Kuala_Lumpur"),
|
||||
("Asia/Kuching", "Asia/Kuching"),
|
||||
("Asia/Kuwait", "Asia/Kuwait"),
|
||||
("Asia/Macau", "Asia/Macau"),
|
||||
("Asia/Magadan", "Asia/Magadan"),
|
||||
("Asia/Makassar", "Asia/Makassar"),
|
||||
("Asia/Manila", "Asia/Manila"),
|
||||
("Asia/Muscat", "Asia/Muscat"),
|
||||
("Asia/Nicosia", "Asia/Nicosia"),
|
||||
("Asia/Novokuznetsk", "Asia/Novokuznetsk"),
|
||||
("Asia/Novosibirsk", "Asia/Novosibirsk"),
|
||||
("Asia/Omsk", "Asia/Omsk"),
|
||||
("Asia/Oral", "Asia/Oral"),
|
||||
("Asia/Phnom_Penh", "Asia/Phnom_Penh"),
|
||||
("Asia/Pontianak", "Asia/Pontianak"),
|
||||
("Asia/Pyongyang", "Asia/Pyongyang"),
|
||||
("Asia/Qatar", "Asia/Qatar"),
|
||||
("Asia/Qostanay", "Asia/Qostanay"),
|
||||
("Asia/Qyzylorda", "Asia/Qyzylorda"),
|
||||
("Asia/Riyadh", "Asia/Riyadh"),
|
||||
("Asia/Sakhalin", "Asia/Sakhalin"),
|
||||
("Asia/Samarkand", "Asia/Samarkand"),
|
||||
("Asia/Seoul", "Asia/Seoul"),
|
||||
("Asia/Shanghai", "Asia/Shanghai"),
|
||||
("Asia/Singapore", "Asia/Singapore"),
|
||||
("Asia/Srednekolymsk", "Asia/Srednekolymsk"),
|
||||
("Asia/Taipei", "Asia/Taipei"),
|
||||
("Asia/Tashkent", "Asia/Tashkent"),
|
||||
("Asia/Tbilisi", "Asia/Tbilisi"),
|
||||
("Asia/Tehran", "Asia/Tehran"),
|
||||
("Asia/Thimphu", "Asia/Thimphu"),
|
||||
("Asia/Tokyo", "Asia/Tokyo"),
|
||||
("Asia/Tomsk", "Asia/Tomsk"),
|
||||
("Asia/Ulaanbaatar", "Asia/Ulaanbaatar"),
|
||||
("Asia/Urumqi", "Asia/Urumqi"),
|
||||
("Asia/Ust-Nera", "Asia/Ust-Nera"),
|
||||
("Asia/Vientiane", "Asia/Vientiane"),
|
||||
("Asia/Vladivostok", "Asia/Vladivostok"),
|
||||
("Asia/Yakutsk", "Asia/Yakutsk"),
|
||||
("Asia/Yangon", "Asia/Yangon"),
|
||||
("Asia/Yekaterinburg", "Asia/Yekaterinburg"),
|
||||
("Asia/Yerevan", "Asia/Yerevan"),
|
||||
("Atlantic/Azores", "Atlantic/Azores"),
|
||||
("Atlantic/Bermuda", "Atlantic/Bermuda"),
|
||||
("Atlantic/Canary", "Atlantic/Canary"),
|
||||
("Atlantic/Cape_Verde", "Atlantic/Cape_Verde"),
|
||||
("Atlantic/Faroe", "Atlantic/Faroe"),
|
||||
("Atlantic/Madeira", "Atlantic/Madeira"),
|
||||
("Atlantic/Reykjavik", "Atlantic/Reykjavik"),
|
||||
("Atlantic/South_Georgia", "Atlantic/South_Georgia"),
|
||||
("Atlantic/St_Helena", "Atlantic/St_Helena"),
|
||||
("Atlantic/Stanley", "Atlantic/Stanley"),
|
||||
("Australia/Adelaide", "Australia/Adelaide"),
|
||||
("Australia/Brisbane", "Australia/Brisbane"),
|
||||
("Australia/Broken_Hill", "Australia/Broken_Hill"),
|
||||
("Australia/Darwin", "Australia/Darwin"),
|
||||
("Australia/Eucla", "Australia/Eucla"),
|
||||
("Australia/Hobart", "Australia/Hobart"),
|
||||
("Australia/Lindeman", "Australia/Lindeman"),
|
||||
("Australia/Lord_Howe", "Australia/Lord_Howe"),
|
||||
("Australia/Melbourne", "Australia/Melbourne"),
|
||||
("Australia/Perth", "Australia/Perth"),
|
||||
("Australia/Sydney", "Australia/Sydney"),
|
||||
("Canada/Atlantic", "Canada/Atlantic"),
|
||||
("Canada/Central", "Canada/Central"),
|
||||
("Canada/Eastern", "Canada/Eastern"),
|
||||
("Canada/Mountain", "Canada/Mountain"),
|
||||
("Canada/Newfoundland", "Canada/Newfoundland"),
|
||||
("Canada/Pacific", "Canada/Pacific"),
|
||||
("Europe/Amsterdam", "Europe/Amsterdam"),
|
||||
("Europe/Andorra", "Europe/Andorra"),
|
||||
("Europe/Astrakhan", "Europe/Astrakhan"),
|
||||
("Europe/Athens", "Europe/Athens"),
|
||||
("Europe/Belgrade", "Europe/Belgrade"),
|
||||
("Europe/Berlin", "Europe/Berlin"),
|
||||
("Europe/Bratislava", "Europe/Bratislava"),
|
||||
("Europe/Brussels", "Europe/Brussels"),
|
||||
("Europe/Bucharest", "Europe/Bucharest"),
|
||||
("Europe/Budapest", "Europe/Budapest"),
|
||||
("Europe/Busingen", "Europe/Busingen"),
|
||||
("Europe/Chisinau", "Europe/Chisinau"),
|
||||
("Europe/Copenhagen", "Europe/Copenhagen"),
|
||||
("Europe/Dublin", "Europe/Dublin"),
|
||||
("Europe/Gibraltar", "Europe/Gibraltar"),
|
||||
("Europe/Guernsey", "Europe/Guernsey"),
|
||||
("Europe/Helsinki", "Europe/Helsinki"),
|
||||
("Europe/Isle_of_Man", "Europe/Isle_of_Man"),
|
||||
("Europe/Istanbul", "Europe/Istanbul"),
|
||||
("Europe/Jersey", "Europe/Jersey"),
|
||||
("Europe/Kaliningrad", "Europe/Kaliningrad"),
|
||||
("Europe/Kirov", "Europe/Kirov"),
|
||||
("Europe/Kyiv", "Europe/Kyiv"),
|
||||
("Europe/Lisbon", "Europe/Lisbon"),
|
||||
("Europe/Ljubljana", "Europe/Ljubljana"),
|
||||
("Europe/London", "Europe/London"),
|
||||
("Europe/Luxembourg", "Europe/Luxembourg"),
|
||||
("Europe/Madrid", "Europe/Madrid"),
|
||||
("Europe/Malta", "Europe/Malta"),
|
||||
("Europe/Mariehamn", "Europe/Mariehamn"),
|
||||
("Europe/Minsk", "Europe/Minsk"),
|
||||
("Europe/Monaco", "Europe/Monaco"),
|
||||
("Europe/Moscow", "Europe/Moscow"),
|
||||
("Europe/Oslo", "Europe/Oslo"),
|
||||
("Europe/Paris", "Europe/Paris"),
|
||||
("Europe/Podgorica", "Europe/Podgorica"),
|
||||
("Europe/Prague", "Europe/Prague"),
|
||||
("Europe/Riga", "Europe/Riga"),
|
||||
("Europe/Rome", "Europe/Rome"),
|
||||
("Europe/Samara", "Europe/Samara"),
|
||||
("Europe/San_Marino", "Europe/San_Marino"),
|
||||
("Europe/Sarajevo", "Europe/Sarajevo"),
|
||||
("Europe/Saratov", "Europe/Saratov"),
|
||||
("Europe/Simferopol", "Europe/Simferopol"),
|
||||
("Europe/Skopje", "Europe/Skopje"),
|
||||
("Europe/Sofia", "Europe/Sofia"),
|
||||
("Europe/Stockholm", "Europe/Stockholm"),
|
||||
("Europe/Tallinn", "Europe/Tallinn"),
|
||||
("Europe/Tirane", "Europe/Tirane"),
|
||||
("Europe/Ulyanovsk", "Europe/Ulyanovsk"),
|
||||
("Europe/Vaduz", "Europe/Vaduz"),
|
||||
("Europe/Vatican", "Europe/Vatican"),
|
||||
("Europe/Vienna", "Europe/Vienna"),
|
||||
("Europe/Vilnius", "Europe/Vilnius"),
|
||||
("Europe/Volgograd", "Europe/Volgograd"),
|
||||
("Europe/Warsaw", "Europe/Warsaw"),
|
||||
("Europe/Zagreb", "Europe/Zagreb"),
|
||||
("Europe/Zurich", "Europe/Zurich"),
|
||||
("GMT", "GMT"),
|
||||
("Indian/Antananarivo", "Indian/Antananarivo"),
|
||||
("Indian/Chagos", "Indian/Chagos"),
|
||||
("Indian/Christmas", "Indian/Christmas"),
|
||||
("Indian/Cocos", "Indian/Cocos"),
|
||||
("Indian/Comoro", "Indian/Comoro"),
|
||||
("Indian/Kerguelen", "Indian/Kerguelen"),
|
||||
("Indian/Mahe", "Indian/Mahe"),
|
||||
("Indian/Maldives", "Indian/Maldives"),
|
||||
("Indian/Mauritius", "Indian/Mauritius"),
|
||||
("Indian/Mayotte", "Indian/Mayotte"),
|
||||
("Indian/Reunion", "Indian/Reunion"),
|
||||
("Pacific/Apia", "Pacific/Apia"),
|
||||
("Pacific/Auckland", "Pacific/Auckland"),
|
||||
("Pacific/Bougainville", "Pacific/Bougainville"),
|
||||
("Pacific/Chatham", "Pacific/Chatham"),
|
||||
("Pacific/Chuuk", "Pacific/Chuuk"),
|
||||
("Pacific/Easter", "Pacific/Easter"),
|
||||
("Pacific/Efate", "Pacific/Efate"),
|
||||
("Pacific/Fakaofo", "Pacific/Fakaofo"),
|
||||
("Pacific/Fiji", "Pacific/Fiji"),
|
||||
("Pacific/Funafuti", "Pacific/Funafuti"),
|
||||
("Pacific/Galapagos", "Pacific/Galapagos"),
|
||||
("Pacific/Gambier", "Pacific/Gambier"),
|
||||
("Pacific/Guadalcanal", "Pacific/Guadalcanal"),
|
||||
("Pacific/Guam", "Pacific/Guam"),
|
||||
("Pacific/Honolulu", "Pacific/Honolulu"),
|
||||
("Pacific/Kanton", "Pacific/Kanton"),
|
||||
("Pacific/Kiritimati", "Pacific/Kiritimati"),
|
||||
("Pacific/Kosrae", "Pacific/Kosrae"),
|
||||
("Pacific/Kwajalein", "Pacific/Kwajalein"),
|
||||
("Pacific/Majuro", "Pacific/Majuro"),
|
||||
("Pacific/Marquesas", "Pacific/Marquesas"),
|
||||
("Pacific/Midway", "Pacific/Midway"),
|
||||
("Pacific/Nauru", "Pacific/Nauru"),
|
||||
("Pacific/Niue", "Pacific/Niue"),
|
||||
("Pacific/Norfolk", "Pacific/Norfolk"),
|
||||
("Pacific/Noumea", "Pacific/Noumea"),
|
||||
("Pacific/Pago_Pago", "Pacific/Pago_Pago"),
|
||||
("Pacific/Palau", "Pacific/Palau"),
|
||||
("Pacific/Pitcairn", "Pacific/Pitcairn"),
|
||||
("Pacific/Pohnpei", "Pacific/Pohnpei"),
|
||||
("Pacific/Port_Moresby", "Pacific/Port_Moresby"),
|
||||
("Pacific/Rarotonga", "Pacific/Rarotonga"),
|
||||
("Pacific/Saipan", "Pacific/Saipan"),
|
||||
("Pacific/Tahiti", "Pacific/Tahiti"),
|
||||
("Pacific/Tarawa", "Pacific/Tarawa"),
|
||||
("Pacific/Tongatapu", "Pacific/Tongatapu"),
|
||||
("Pacific/Wake", "Pacific/Wake"),
|
||||
("Pacific/Wallis", "Pacific/Wallis"),
|
||||
("US/Alaska", "US/Alaska"),
|
||||
("US/Arizona", "US/Arizona"),
|
||||
("US/Central", "US/Central"),
|
||||
("US/Eastern", "US/Eastern"),
|
||||
("US/Hawaii", "US/Hawaii"),
|
||||
("US/Mountain", "US/Mountain"),
|
||||
("US/Pacific", "US/Pacific"),
|
||||
("UTC", "UTC"),
|
||||
],
|
||||
default="auto",
|
||||
max_length=50,
|
||||
verbose_name="Time Zone",
|
||||
),
|
||||
),
|
||||
]
|
||||
File diff suppressed because one or more lines are too long
@@ -2,11 +2,449 @@ import pytz
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import AbstractUser, Group
|
||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from apps.users.managers import UserManager
|
||||
|
||||
timezones = [
|
||||
("auto", _("Auto")),
|
||||
("Africa/Abidjan", "Africa/Abidjan"),
|
||||
("Africa/Accra", "Africa/Accra"),
|
||||
("Africa/Addis_Ababa", "Africa/Addis_Ababa"),
|
||||
("Africa/Algiers", "Africa/Algiers"),
|
||||
("Africa/Asmara", "Africa/Asmara"),
|
||||
("Africa/Bamako", "Africa/Bamako"),
|
||||
("Africa/Bangui", "Africa/Bangui"),
|
||||
("Africa/Banjul", "Africa/Banjul"),
|
||||
("Africa/Bissau", "Africa/Bissau"),
|
||||
("Africa/Blantyre", "Africa/Blantyre"),
|
||||
("Africa/Brazzaville", "Africa/Brazzaville"),
|
||||
("Africa/Bujumbura", "Africa/Bujumbura"),
|
||||
("Africa/Cairo", "Africa/Cairo"),
|
||||
("Africa/Casablanca", "Africa/Casablanca"),
|
||||
("Africa/Ceuta", "Africa/Ceuta"),
|
||||
("Africa/Conakry", "Africa/Conakry"),
|
||||
("Africa/Dakar", "Africa/Dakar"),
|
||||
("Africa/Dar_es_Salaam", "Africa/Dar_es_Salaam"),
|
||||
("Africa/Djibouti", "Africa/Djibouti"),
|
||||
("Africa/Douala", "Africa/Douala"),
|
||||
("Africa/El_Aaiun", "Africa/El_Aaiun"),
|
||||
("Africa/Freetown", "Africa/Freetown"),
|
||||
("Africa/Gaborone", "Africa/Gaborone"),
|
||||
("Africa/Harare", "Africa/Harare"),
|
||||
("Africa/Johannesburg", "Africa/Johannesburg"),
|
||||
("Africa/Juba", "Africa/Juba"),
|
||||
("Africa/Kampala", "Africa/Kampala"),
|
||||
("Africa/Khartoum", "Africa/Khartoum"),
|
||||
("Africa/Kigali", "Africa/Kigali"),
|
||||
("Africa/Kinshasa", "Africa/Kinshasa"),
|
||||
("Africa/Lagos", "Africa/Lagos"),
|
||||
("Africa/Libreville", "Africa/Libreville"),
|
||||
("Africa/Lome", "Africa/Lome"),
|
||||
("Africa/Luanda", "Africa/Luanda"),
|
||||
("Africa/Lubumbashi", "Africa/Lubumbashi"),
|
||||
("Africa/Lusaka", "Africa/Lusaka"),
|
||||
("Africa/Malabo", "Africa/Malabo"),
|
||||
("Africa/Maputo", "Africa/Maputo"),
|
||||
("Africa/Maseru", "Africa/Maseru"),
|
||||
("Africa/Mbabane", "Africa/Mbabane"),
|
||||
("Africa/Mogadishu", "Africa/Mogadishu"),
|
||||
("Africa/Monrovia", "Africa/Monrovia"),
|
||||
("Africa/Nairobi", "Africa/Nairobi"),
|
||||
("Africa/Ndjamena", "Africa/Ndjamena"),
|
||||
("Africa/Niamey", "Africa/Niamey"),
|
||||
("Africa/Nouakchott", "Africa/Nouakchott"),
|
||||
("Africa/Ouagadougou", "Africa/Ouagadougou"),
|
||||
("Africa/Porto-Novo", "Africa/Porto-Novo"),
|
||||
("Africa/Sao_Tome", "Africa/Sao_Tome"),
|
||||
("Africa/Tripoli", "Africa/Tripoli"),
|
||||
("Africa/Tunis", "Africa/Tunis"),
|
||||
("Africa/Windhoek", "Africa/Windhoek"),
|
||||
("America/Adak", "America/Adak"),
|
||||
("America/Anchorage", "America/Anchorage"),
|
||||
("America/Anguilla", "America/Anguilla"),
|
||||
("America/Antigua", "America/Antigua"),
|
||||
("America/Araguaina", "America/Araguaina"),
|
||||
("America/Argentina/Buenos_Aires", "America/Argentina/Buenos_Aires"),
|
||||
("America/Argentina/Catamarca", "America/Argentina/Catamarca"),
|
||||
("America/Argentina/Cordoba", "America/Argentina/Cordoba"),
|
||||
("America/Argentina/Jujuy", "America/Argentina/Jujuy"),
|
||||
("America/Argentina/La_Rioja", "America/Argentina/La_Rioja"),
|
||||
("America/Argentina/Mendoza", "America/Argentina/Mendoza"),
|
||||
("America/Argentina/Rio_Gallegos", "America/Argentina/Rio_Gallegos"),
|
||||
("America/Argentina/Salta", "America/Argentina/Salta"),
|
||||
("America/Argentina/San_Juan", "America/Argentina/San_Juan"),
|
||||
("America/Argentina/San_Luis", "America/Argentina/San_Luis"),
|
||||
("America/Argentina/Tucuman", "America/Argentina/Tucuman"),
|
||||
("America/Argentina/Ushuaia", "America/Argentina/Ushuaia"),
|
||||
("America/Aruba", "America/Aruba"),
|
||||
("America/Asuncion", "America/Asuncion"),
|
||||
("America/Atikokan", "America/Atikokan"),
|
||||
("America/Bahia", "America/Bahia"),
|
||||
("America/Bahia_Banderas", "America/Bahia_Banderas"),
|
||||
("America/Barbados", "America/Barbados"),
|
||||
("America/Belem", "America/Belem"),
|
||||
("America/Belize", "America/Belize"),
|
||||
("America/Blanc-Sablon", "America/Blanc-Sablon"),
|
||||
("America/Boa_Vista", "America/Boa_Vista"),
|
||||
("America/Bogota", "America/Bogota"),
|
||||
("America/Boise", "America/Boise"),
|
||||
("America/Cambridge_Bay", "America/Cambridge_Bay"),
|
||||
("America/Campo_Grande", "America/Campo_Grande"),
|
||||
("America/Cancun", "America/Cancun"),
|
||||
("America/Caracas", "America/Caracas"),
|
||||
("America/Cayenne", "America/Cayenne"),
|
||||
("America/Cayman", "America/Cayman"),
|
||||
("America/Chicago", "America/Chicago"),
|
||||
("America/Chihuahua", "America/Chihuahua"),
|
||||
("America/Ciudad_Juarez", "America/Ciudad_Juarez"),
|
||||
("America/Costa_Rica", "America/Costa_Rica"),
|
||||
("America/Coyhaique", "America/Coyhaique"),
|
||||
("America/Creston", "America/Creston"),
|
||||
("America/Cuiaba", "America/Cuiaba"),
|
||||
("America/Curacao", "America/Curacao"),
|
||||
("America/Danmarkshavn", "America/Danmarkshavn"),
|
||||
("America/Dawson", "America/Dawson"),
|
||||
("America/Dawson_Creek", "America/Dawson_Creek"),
|
||||
("America/Denver", "America/Denver"),
|
||||
("America/Detroit", "America/Detroit"),
|
||||
("America/Dominica", "America/Dominica"),
|
||||
("America/Edmonton", "America/Edmonton"),
|
||||
("America/Eirunepe", "America/Eirunepe"),
|
||||
("America/El_Salvador", "America/El_Salvador"),
|
||||
("America/Fort_Nelson", "America/Fort_Nelson"),
|
||||
("America/Fortaleza", "America/Fortaleza"),
|
||||
("America/Glace_Bay", "America/Glace_Bay"),
|
||||
("America/Goose_Bay", "America/Goose_Bay"),
|
||||
("America/Grand_Turk", "America/Grand_Turk"),
|
||||
("America/Grenada", "America/Grenada"),
|
||||
("America/Guadeloupe", "America/Guadeloupe"),
|
||||
("America/Guatemala", "America/Guatemala"),
|
||||
("America/Guayaquil", "America/Guayaquil"),
|
||||
("America/Guyana", "America/Guyana"),
|
||||
("America/Halifax", "America/Halifax"),
|
||||
("America/Havana", "America/Havana"),
|
||||
("America/Hermosillo", "America/Hermosillo"),
|
||||
("America/Indiana/Indianapolis", "America/Indiana/Indianapolis"),
|
||||
("America/Indiana/Knox", "America/Indiana/Knox"),
|
||||
("America/Indiana/Marengo", "America/Indiana/Marengo"),
|
||||
("America/Indiana/Petersburg", "America/Indiana/Petersburg"),
|
||||
("America/Indiana/Tell_City", "America/Indiana/Tell_City"),
|
||||
("America/Indiana/Vevay", "America/Indiana/Vevay"),
|
||||
("America/Indiana/Vincennes", "America/Indiana/Vincennes"),
|
||||
("America/Indiana/Winamac", "America/Indiana/Winamac"),
|
||||
("America/Inuvik", "America/Inuvik"),
|
||||
("America/Iqaluit", "America/Iqaluit"),
|
||||
("America/Jamaica", "America/Jamaica"),
|
||||
("America/Juneau", "America/Juneau"),
|
||||
("America/Kentucky/Louisville", "America/Kentucky/Louisville"),
|
||||
("America/Kentucky/Monticello", "America/Kentucky/Monticello"),
|
||||
("America/Kralendijk", "America/Kralendijk"),
|
||||
("America/La_Paz", "America/La_Paz"),
|
||||
("America/Lima", "America/Lima"),
|
||||
("America/Los_Angeles", "America/Los_Angeles"),
|
||||
("America/Lower_Princes", "America/Lower_Princes"),
|
||||
("America/Maceio", "America/Maceio"),
|
||||
("America/Managua", "America/Managua"),
|
||||
("America/Manaus", "America/Manaus"),
|
||||
("America/Marigot", "America/Marigot"),
|
||||
("America/Martinique", "America/Martinique"),
|
||||
("America/Matamoros", "America/Matamoros"),
|
||||
("America/Mazatlan", "America/Mazatlan"),
|
||||
("America/Menominee", "America/Menominee"),
|
||||
("America/Merida", "America/Merida"),
|
||||
("America/Metlakatla", "America/Metlakatla"),
|
||||
("America/Mexico_City", "America/Mexico_City"),
|
||||
("America/Miquelon", "America/Miquelon"),
|
||||
("America/Moncton", "America/Moncton"),
|
||||
("America/Monterrey", "America/Monterrey"),
|
||||
("America/Montevideo", "America/Montevideo"),
|
||||
("America/Montserrat", "America/Montserrat"),
|
||||
("America/Nassau", "America/Nassau"),
|
||||
("America/New_York", "America/New_York"),
|
||||
("America/Nome", "America/Nome"),
|
||||
("America/Noronha", "America/Noronha"),
|
||||
("America/North_Dakota/Beulah", "America/North_Dakota/Beulah"),
|
||||
("America/North_Dakota/Center", "America/North_Dakota/Center"),
|
||||
("America/North_Dakota/New_Salem", "America/North_Dakota/New_Salem"),
|
||||
("America/Nuuk", "America/Nuuk"),
|
||||
("America/Ojinaga", "America/Ojinaga"),
|
||||
("America/Panama", "America/Panama"),
|
||||
("America/Paramaribo", "America/Paramaribo"),
|
||||
("America/Phoenix", "America/Phoenix"),
|
||||
("America/Port-au-Prince", "America/Port-au-Prince"),
|
||||
("America/Port_of_Spain", "America/Port_of_Spain"),
|
||||
("America/Porto_Velho", "America/Porto_Velho"),
|
||||
("America/Puerto_Rico", "America/Puerto_Rico"),
|
||||
("America/Punta_Arenas", "America/Punta_Arenas"),
|
||||
("America/Rankin_Inlet", "America/Rankin_Inlet"),
|
||||
("America/Recife", "America/Recife"),
|
||||
("America/Regina", "America/Regina"),
|
||||
("America/Resolute", "America/Resolute"),
|
||||
("America/Rio_Branco", "America/Rio_Branco"),
|
||||
("America/Santarem", "America/Santarem"),
|
||||
("America/Santiago", "America/Santiago"),
|
||||
("America/Santo_Domingo", "America/Santo_Domingo"),
|
||||
("America/Sao_Paulo", "America/Sao_Paulo"),
|
||||
("America/Scoresbysund", "America/Scoresbysund"),
|
||||
("America/Sitka", "America/Sitka"),
|
||||
("America/St_Barthelemy", "America/St_Barthelemy"),
|
||||
("America/St_Johns", "America/St_Johns"),
|
||||
("America/St_Kitts", "America/St_Kitts"),
|
||||
("America/St_Lucia", "America/St_Lucia"),
|
||||
("America/St_Thomas", "America/St_Thomas"),
|
||||
("America/St_Vincent", "America/St_Vincent"),
|
||||
("America/Swift_Current", "America/Swift_Current"),
|
||||
("America/Tegucigalpa", "America/Tegucigalpa"),
|
||||
("America/Thule", "America/Thule"),
|
||||
("America/Tijuana", "America/Tijuana"),
|
||||
("America/Toronto", "America/Toronto"),
|
||||
("America/Tortola", "America/Tortola"),
|
||||
("America/Vancouver", "America/Vancouver"),
|
||||
("America/Whitehorse", "America/Whitehorse"),
|
||||
("America/Winnipeg", "America/Winnipeg"),
|
||||
("America/Yakutat", "America/Yakutat"),
|
||||
("Antarctica/Casey", "Antarctica/Casey"),
|
||||
("Antarctica/Davis", "Antarctica/Davis"),
|
||||
("Antarctica/DumontDUrville", "Antarctica/DumontDUrville"),
|
||||
("Antarctica/Macquarie", "Antarctica/Macquarie"),
|
||||
("Antarctica/Mawson", "Antarctica/Mawson"),
|
||||
("Antarctica/McMurdo", "Antarctica/McMurdo"),
|
||||
("Antarctica/Palmer", "Antarctica/Palmer"),
|
||||
("Antarctica/Rothera", "Antarctica/Rothera"),
|
||||
("Antarctica/Syowa", "Antarctica/Syowa"),
|
||||
("Antarctica/Troll", "Antarctica/Troll"),
|
||||
("Antarctica/Vostok", "Antarctica/Vostok"),
|
||||
("Arctic/Longyearbyen", "Arctic/Longyearbyen"),
|
||||
("Asia/Aden", "Asia/Aden"),
|
||||
("Asia/Almaty", "Asia/Almaty"),
|
||||
("Asia/Amman", "Asia/Amman"),
|
||||
("Asia/Anadyr", "Asia/Anadyr"),
|
||||
("Asia/Aqtau", "Asia/Aqtau"),
|
||||
("Asia/Aqtobe", "Asia/Aqtobe"),
|
||||
("Asia/Ashgabat", "Asia/Ashgabat"),
|
||||
("Asia/Atyrau", "Asia/Atyrau"),
|
||||
("Asia/Baghdad", "Asia/Baghdad"),
|
||||
("Asia/Bahrain", "Asia/Bahrain"),
|
||||
("Asia/Baku", "Asia/Baku"),
|
||||
("Asia/Bangkok", "Asia/Bangkok"),
|
||||
("Asia/Barnaul", "Asia/Barnaul"),
|
||||
("Asia/Beirut", "Asia/Beirut"),
|
||||
("Asia/Bishkek", "Asia/Bishkek"),
|
||||
("Asia/Brunei", "Asia/Brunei"),
|
||||
("Asia/Chita", "Asia/Chita"),
|
||||
("Asia/Colombo", "Asia/Colombo"),
|
||||
("Asia/Damascus", "Asia/Damascus"),
|
||||
("Asia/Dhaka", "Asia/Dhaka"),
|
||||
("Asia/Dili", "Asia/Dili"),
|
||||
("Asia/Dubai", "Asia/Dubai"),
|
||||
("Asia/Dushanbe", "Asia/Dushanbe"),
|
||||
("Asia/Famagusta", "Asia/Famagusta"),
|
||||
("Asia/Gaza", "Asia/Gaza"),
|
||||
("Asia/Hebron", "Asia/Hebron"),
|
||||
("Asia/Ho_Chi_Minh", "Asia/Ho_Chi_Minh"),
|
||||
("Asia/Hong_Kong", "Asia/Hong_Kong"),
|
||||
("Asia/Hovd", "Asia/Hovd"),
|
||||
("Asia/Irkutsk", "Asia/Irkutsk"),
|
||||
("Asia/Jakarta", "Asia/Jakarta"),
|
||||
("Asia/Jayapura", "Asia/Jayapura"),
|
||||
("Asia/Jerusalem", "Asia/Jerusalem"),
|
||||
("Asia/Kabul", "Asia/Kabul"),
|
||||
("Asia/Kamchatka", "Asia/Kamchatka"),
|
||||
("Asia/Karachi", "Asia/Karachi"),
|
||||
("Asia/Kathmandu", "Asia/Kathmandu"),
|
||||
("Asia/Khandyga", "Asia/Khandyga"),
|
||||
("Asia/Kolkata", "Asia/Kolkata"),
|
||||
("Asia/Krasnoyarsk", "Asia/Krasnoyarsk"),
|
||||
("Asia/Kuala_Lumpur", "Asia/Kuala_Lumpur"),
|
||||
("Asia/Kuching", "Asia/Kuching"),
|
||||
("Asia/Kuwait", "Asia/Kuwait"),
|
||||
("Asia/Macau", "Asia/Macau"),
|
||||
("Asia/Magadan", "Asia/Magadan"),
|
||||
("Asia/Makassar", "Asia/Makassar"),
|
||||
("Asia/Manila", "Asia/Manila"),
|
||||
("Asia/Muscat", "Asia/Muscat"),
|
||||
("Asia/Nicosia", "Asia/Nicosia"),
|
||||
("Asia/Novokuznetsk", "Asia/Novokuznetsk"),
|
||||
("Asia/Novosibirsk", "Asia/Novosibirsk"),
|
||||
("Asia/Omsk", "Asia/Omsk"),
|
||||
("Asia/Oral", "Asia/Oral"),
|
||||
("Asia/Phnom_Penh", "Asia/Phnom_Penh"),
|
||||
("Asia/Pontianak", "Asia/Pontianak"),
|
||||
("Asia/Pyongyang", "Asia/Pyongyang"),
|
||||
("Asia/Qatar", "Asia/Qatar"),
|
||||
("Asia/Qostanay", "Asia/Qostanay"),
|
||||
("Asia/Qyzylorda", "Asia/Qyzylorda"),
|
||||
("Asia/Riyadh", "Asia/Riyadh"),
|
||||
("Asia/Sakhalin", "Asia/Sakhalin"),
|
||||
("Asia/Samarkand", "Asia/Samarkand"),
|
||||
("Asia/Seoul", "Asia/Seoul"),
|
||||
("Asia/Shanghai", "Asia/Shanghai"),
|
||||
("Asia/Singapore", "Asia/Singapore"),
|
||||
("Asia/Srednekolymsk", "Asia/Srednekolymsk"),
|
||||
("Asia/Taipei", "Asia/Taipei"),
|
||||
("Asia/Tashkent", "Asia/Tashkent"),
|
||||
("Asia/Tbilisi", "Asia/Tbilisi"),
|
||||
("Asia/Tehran", "Asia/Tehran"),
|
||||
("Asia/Thimphu", "Asia/Thimphu"),
|
||||
("Asia/Tokyo", "Asia/Tokyo"),
|
||||
("Asia/Tomsk", "Asia/Tomsk"),
|
||||
("Asia/Ulaanbaatar", "Asia/Ulaanbaatar"),
|
||||
("Asia/Urumqi", "Asia/Urumqi"),
|
||||
("Asia/Ust-Nera", "Asia/Ust-Nera"),
|
||||
("Asia/Vientiane", "Asia/Vientiane"),
|
||||
("Asia/Vladivostok", "Asia/Vladivostok"),
|
||||
("Asia/Yakutsk", "Asia/Yakutsk"),
|
||||
("Asia/Yangon", "Asia/Yangon"),
|
||||
("Asia/Yekaterinburg", "Asia/Yekaterinburg"),
|
||||
("Asia/Yerevan", "Asia/Yerevan"),
|
||||
("Atlantic/Azores", "Atlantic/Azores"),
|
||||
("Atlantic/Bermuda", "Atlantic/Bermuda"),
|
||||
("Atlantic/Canary", "Atlantic/Canary"),
|
||||
("Atlantic/Cape_Verde", "Atlantic/Cape_Verde"),
|
||||
("Atlantic/Faroe", "Atlantic/Faroe"),
|
||||
("Atlantic/Madeira", "Atlantic/Madeira"),
|
||||
("Atlantic/Reykjavik", "Atlantic/Reykjavik"),
|
||||
("Atlantic/South_Georgia", "Atlantic/South_Georgia"),
|
||||
("Atlantic/St_Helena", "Atlantic/St_Helena"),
|
||||
("Atlantic/Stanley", "Atlantic/Stanley"),
|
||||
("Australia/Adelaide", "Australia/Adelaide"),
|
||||
("Australia/Brisbane", "Australia/Brisbane"),
|
||||
("Australia/Broken_Hill", "Australia/Broken_Hill"),
|
||||
("Australia/Darwin", "Australia/Darwin"),
|
||||
("Australia/Eucla", "Australia/Eucla"),
|
||||
("Australia/Hobart", "Australia/Hobart"),
|
||||
("Australia/Lindeman", "Australia/Lindeman"),
|
||||
("Australia/Lord_Howe", "Australia/Lord_Howe"),
|
||||
("Australia/Melbourne", "Australia/Melbourne"),
|
||||
("Australia/Perth", "Australia/Perth"),
|
||||
("Australia/Sydney", "Australia/Sydney"),
|
||||
("Canada/Atlantic", "Canada/Atlantic"),
|
||||
("Canada/Central", "Canada/Central"),
|
||||
("Canada/Eastern", "Canada/Eastern"),
|
||||
("Canada/Mountain", "Canada/Mountain"),
|
||||
("Canada/Newfoundland", "Canada/Newfoundland"),
|
||||
("Canada/Pacific", "Canada/Pacific"),
|
||||
("Europe/Amsterdam", "Europe/Amsterdam"),
|
||||
("Europe/Andorra", "Europe/Andorra"),
|
||||
("Europe/Astrakhan", "Europe/Astrakhan"),
|
||||
("Europe/Athens", "Europe/Athens"),
|
||||
("Europe/Belgrade", "Europe/Belgrade"),
|
||||
("Europe/Berlin", "Europe/Berlin"),
|
||||
("Europe/Bratislava", "Europe/Bratislava"),
|
||||
("Europe/Brussels", "Europe/Brussels"),
|
||||
("Europe/Bucharest", "Europe/Bucharest"),
|
||||
("Europe/Budapest", "Europe/Budapest"),
|
||||
("Europe/Busingen", "Europe/Busingen"),
|
||||
("Europe/Chisinau", "Europe/Chisinau"),
|
||||
("Europe/Copenhagen", "Europe/Copenhagen"),
|
||||
("Europe/Dublin", "Europe/Dublin"),
|
||||
("Europe/Gibraltar", "Europe/Gibraltar"),
|
||||
("Europe/Guernsey", "Europe/Guernsey"),
|
||||
("Europe/Helsinki", "Europe/Helsinki"),
|
||||
("Europe/Isle_of_Man", "Europe/Isle_of_Man"),
|
||||
("Europe/Istanbul", "Europe/Istanbul"),
|
||||
("Europe/Jersey", "Europe/Jersey"),
|
||||
("Europe/Kaliningrad", "Europe/Kaliningrad"),
|
||||
("Europe/Kirov", "Europe/Kirov"),
|
||||
("Europe/Kyiv", "Europe/Kyiv"),
|
||||
("Europe/Lisbon", "Europe/Lisbon"),
|
||||
("Europe/Ljubljana", "Europe/Ljubljana"),
|
||||
("Europe/London", "Europe/London"),
|
||||
("Europe/Luxembourg", "Europe/Luxembourg"),
|
||||
("Europe/Madrid", "Europe/Madrid"),
|
||||
("Europe/Malta", "Europe/Malta"),
|
||||
("Europe/Mariehamn", "Europe/Mariehamn"),
|
||||
("Europe/Minsk", "Europe/Minsk"),
|
||||
("Europe/Monaco", "Europe/Monaco"),
|
||||
("Europe/Moscow", "Europe/Moscow"),
|
||||
("Europe/Oslo", "Europe/Oslo"),
|
||||
("Europe/Paris", "Europe/Paris"),
|
||||
("Europe/Podgorica", "Europe/Podgorica"),
|
||||
("Europe/Prague", "Europe/Prague"),
|
||||
("Europe/Riga", "Europe/Riga"),
|
||||
("Europe/Rome", "Europe/Rome"),
|
||||
("Europe/Samara", "Europe/Samara"),
|
||||
("Europe/San_Marino", "Europe/San_Marino"),
|
||||
("Europe/Sarajevo", "Europe/Sarajevo"),
|
||||
("Europe/Saratov", "Europe/Saratov"),
|
||||
("Europe/Simferopol", "Europe/Simferopol"),
|
||||
("Europe/Skopje", "Europe/Skopje"),
|
||||
("Europe/Sofia", "Europe/Sofia"),
|
||||
("Europe/Stockholm", "Europe/Stockholm"),
|
||||
("Europe/Tallinn", "Europe/Tallinn"),
|
||||
("Europe/Tirane", "Europe/Tirane"),
|
||||
("Europe/Ulyanovsk", "Europe/Ulyanovsk"),
|
||||
("Europe/Vaduz", "Europe/Vaduz"),
|
||||
("Europe/Vatican", "Europe/Vatican"),
|
||||
("Europe/Vienna", "Europe/Vienna"),
|
||||
("Europe/Vilnius", "Europe/Vilnius"),
|
||||
("Europe/Volgograd", "Europe/Volgograd"),
|
||||
("Europe/Warsaw", "Europe/Warsaw"),
|
||||
("Europe/Zagreb", "Europe/Zagreb"),
|
||||
("Europe/Zurich", "Europe/Zurich"),
|
||||
("GMT", "GMT"),
|
||||
("Indian/Antananarivo", "Indian/Antananarivo"),
|
||||
("Indian/Chagos", "Indian/Chagos"),
|
||||
("Indian/Christmas", "Indian/Christmas"),
|
||||
("Indian/Cocos", "Indian/Cocos"),
|
||||
("Indian/Comoro", "Indian/Comoro"),
|
||||
("Indian/Kerguelen", "Indian/Kerguelen"),
|
||||
("Indian/Mahe", "Indian/Mahe"),
|
||||
("Indian/Maldives", "Indian/Maldives"),
|
||||
("Indian/Mauritius", "Indian/Mauritius"),
|
||||
("Indian/Mayotte", "Indian/Mayotte"),
|
||||
("Indian/Reunion", "Indian/Reunion"),
|
||||
("Pacific/Apia", "Pacific/Apia"),
|
||||
("Pacific/Auckland", "Pacific/Auckland"),
|
||||
("Pacific/Bougainville", "Pacific/Bougainville"),
|
||||
("Pacific/Chatham", "Pacific/Chatham"),
|
||||
("Pacific/Chuuk", "Pacific/Chuuk"),
|
||||
("Pacific/Easter", "Pacific/Easter"),
|
||||
("Pacific/Efate", "Pacific/Efate"),
|
||||
("Pacific/Fakaofo", "Pacific/Fakaofo"),
|
||||
("Pacific/Fiji", "Pacific/Fiji"),
|
||||
("Pacific/Funafuti", "Pacific/Funafuti"),
|
||||
("Pacific/Galapagos", "Pacific/Galapagos"),
|
||||
("Pacific/Gambier", "Pacific/Gambier"),
|
||||
("Pacific/Guadalcanal", "Pacific/Guadalcanal"),
|
||||
("P2025-06-29T01:43:14.671389745Z acific/Guam", "Pacific/Guam"),
|
||||
("Pacific/Honolulu", "Pacific/Honolulu"),
|
||||
("Pacific/Kanton", "Pacific/Kanton"),
|
||||
("Pacific/Kiritimati", "Pacific/Kiritimati"),
|
||||
("Pacific/Kosrae", "Pacific/Kosrae"),
|
||||
("Pacific/Kwajalein", "Pacific/Kwajalein"),
|
||||
("Pacific/Majuro", "Pacific/Majuro"),
|
||||
("Pacific/Marquesas", "Pacific/Marquesas"),
|
||||
("Pacific/Midway", "Pacific/Midway"),
|
||||
("Pacific/Nauru", "Pacific/Nauru"),
|
||||
("Pacific/Niue", "Pacific/Niue"),
|
||||
("Pacific/Norfolk", "Pacific/Norfolk"),
|
||||
("Pacific/Noumea", "Pacific/Noumea"),
|
||||
("Pacific/Pago_Pago", "Pacific/Pago_Pago"),
|
||||
("Pacific/Palau", "Pacific/Palau"),
|
||||
("Pacific/Pitcairn", "Pacific/Pitcairn"),
|
||||
("Pacific/Pohnpei", "Pacific/Pohnpei"),
|
||||
("Pacific/Port_Moresby", "Pacific/Port_Moresby"),
|
||||
("Pacific/Rarotonga", "Pacific/Rarotonga"),
|
||||
("Pacific/Saipan", "Pacific/Saipan"),
|
||||
("Pacific/Tahiti", "Pacific/Tahiti"),
|
||||
("Pacific/Tarawa", "Pacific/Tarawa"),
|
||||
("Pacific/Tongatapu", "Pacific/Tongatapu"),
|
||||
("Pacific/Wake", "Pacific/Wake"),
|
||||
("Pacific/Wallis", "Pacific/Wallis"),
|
||||
("US/Alaska", "US/Alaska"),
|
||||
("US/Arizona", "US/Arizona"),
|
||||
("US/Central", "US/Central"),
|
||||
("US/Eastern", "US/Eastern"),
|
||||
("US/Hawaii", "US/Hawaii"),
|
||||
("US/Mountain", "US/Mountain"),
|
||||
("US/Pacific", "US/Pacific"),
|
||||
("UTC", "UTC"),
|
||||
]
|
||||
|
||||
|
||||
class User(AbstractUser):
|
||||
username = None
|
||||
@@ -36,6 +474,11 @@ class UserSettings(models.Model):
|
||||
)
|
||||
hide_amounts = models.BooleanField(default=False)
|
||||
mute_sounds = models.BooleanField(default=False)
|
||||
volume = models.PositiveIntegerField(
|
||||
default=10,
|
||||
validators=[MinValueValidator(1), MaxValueValidator(10)],
|
||||
verbose_name=_("Volume"),
|
||||
)
|
||||
|
||||
date_format = models.CharField(
|
||||
max_length=100, default="SHORT_DATE_FORMAT", verbose_name=_("Date Format")
|
||||
@@ -57,7 +500,7 @@ class UserSettings(models.Model):
|
||||
)
|
||||
timezone = models.CharField(
|
||||
max_length=50,
|
||||
choices=[("auto", _("Auto"))] + [(tz, tz) for tz in pytz.common_timezones],
|
||||
choices=timezones,
|
||||
default="auto",
|
||||
verbose_name=_("Time Zone"),
|
||||
)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,9 @@
|
||||
{% load i18n %}
|
||||
<div class="container px-md-3 py-3 column-gap-5">
|
||||
<div class="tw-text-3xl fw-bold font-monospace tw-w-full mb-3">
|
||||
<div class="tw:text-3xl fw-bold font-monospace tw:w-full mb-3">
|
||||
{% spaceless %}
|
||||
<div>{% translate 'Account Groups' %}<span>
|
||||
<a class="text-decoration-none tw-text-2xl p-1 category-action"
|
||||
<a class="text-decoration-none tw:text-2xl p-1 category-action"
|
||||
role="button"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-title="{% translate "Add" %}"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{% load i18n %}
|
||||
<div class="container px-md-3 py-3 column-gap-5">
|
||||
<div class="tw-text-3xl fw-bold font-monospace tw-w-full mb-3">
|
||||
<div class="tw:text-3xl fw-bold font-monospace tw:w-full mb-3">
|
||||
{% spaceless %}
|
||||
<div>{% translate 'Accounts' %}<span>
|
||||
<a class="text-decoration-none tw-text-2xl p-1 category-action"
|
||||
<a class="text-decoration-none tw:text-2xl p-1 category-action"
|
||||
role="button"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-title="{% translate "Add" %}"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{% load i18n %}
|
||||
|
||||
<div>
|
||||
<div class="tw-hidden lg:tw-grid lg:tw-grid-cols-7 tw-gap-4 lg:tw-gap-0">
|
||||
<div class="tw:hidden tw:lg:grid tw:lg:grid-cols-7 tw:gap-4 tw:lg:gap-0">
|
||||
<div class="border-start border-top border-bottom p-2 text-center">
|
||||
{% translate 'MON' %}
|
||||
</div>
|
||||
@@ -25,44 +25,44 @@
|
||||
{% translate 'SUN' %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-grid-rows-1 lg:tw-grid-cols-7 lg:tw-grid-rows-6 tw-gap-4 lg:tw-gap-0">
|
||||
<div class="tw:grid tw:grid-cols-1 tw:grid-rows-1 tw:lg:grid-cols-7 tw:lg:grid-rows-6 tw:gap-4 tw:lg:gap-0">
|
||||
{% for date in dates %}
|
||||
{% if date %}
|
||||
<div class="card h-100 hover:tw-bg-zinc-900 rounded-0{% if not date.transactions %} !tw-hidden lg:!tw-flex{% endif %}{% if today == date.date %} tw-border-yellow-300 border-primary{% endif %} " role="button"
|
||||
<div class="card h-100 tw:hover:bg-zinc-900! rounded-0{% if not date.transactions %} tw:hidden! tw:lg:flex!{% endif %}{% if today == date.date %} tw:border-yellow-300 border-primary{% endif %} " role="button"
|
||||
hx-get="{% url 'calendar_transactions_list' day=date.date.day month=date.date.month year=date.date.year %}"
|
||||
hx-target="#persistent-generic-offcanvas-left">
|
||||
<div class="card-header border-0 bg-transparent text-end tw-flex justify-content-between p-2 w-100">
|
||||
<div class="lg:tw-hidden text-start w-100">{{ date.date|date:"l"|lower }}</div>
|
||||
<div class="card-header border-0 bg-transparent text-end tw:flex justify-content-between p-2 w-100">
|
||||
<div class="tw:lg:hidden text-start w-100">{{ date.date|date:"l"|lower }}</div>
|
||||
<div class="text-end w-100">{{ date.day }}</div>
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
{% for transaction in date.transactions %}
|
||||
{% if transaction.is_paid %}
|
||||
{% if transaction.type == "IN" and not transaction.account.is_asset %}
|
||||
<i class="fa-solid fa-circle-check tw-text-green-400" data-bs-toggle="tooltip" data-bs-title="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Income' %}{% endif %}"></i>
|
||||
<i class="fa-solid fa-circle-check tw:text-green-400" data-bs-toggle="tooltip" data-bs-title="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Income' %}{% endif %}"></i>
|
||||
{% elif transaction.type == "IN" and transaction.account.is_asset %}
|
||||
<i class="fa-solid fa-circle-check tw-text-green-300" data-bs-toggle="tooltip" data-bs-title="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Income' %}{% endif %}"></i>
|
||||
<i class="fa-solid fa-circle-check tw:text-green-300" data-bs-toggle="tooltip" data-bs-title="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Income' %}{% endif %}"></i>
|
||||
{% elif transaction.type == "EX" and not transaction.account.is_asset %}
|
||||
<i class="fa-solid fa-circle-check tw-text-red-400" data-bs-toggle="tooltip" data-bs-title="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Expense' %}{% endif %}"></i>
|
||||
<i class="fa-solid fa-circle-check tw:text-red-400" data-bs-toggle="tooltip" data-bs-title="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Expense' %}{% endif %}"></i>
|
||||
{% elif transaction.type == "EX" and transaction.account.is_asset %}
|
||||
<i class="fa-solid fa-circle-check tw-text-red-300" data-bs-toggle="tooltip" data-bs-title="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Expense' %}{% endif %}"></i>
|
||||
<i class="fa-solid fa-circle-check tw:text-red-300" data-bs-toggle="tooltip" data-bs-title="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Expense' %}{% endif %}"></i>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if transaction.type == "IN" and not transaction.account.is_asset %}
|
||||
<i class="fa-regular fa-circle tw-text-green-400" data-bs-toggle="tooltip" data-bs-title="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Income' %}{% endif %}"></i>
|
||||
<i class="fa-regular fa-circle tw:text-green-400" data-bs-toggle="tooltip" data-bs-title="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Income' %}{% endif %}"></i>
|
||||
{% elif transaction.type == "IN" and transaction.account.is_asset %}
|
||||
<i class="fa-regular fa-circle tw-text-green-300" data-bs-toggle="tooltip" data-bs-title="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Income' %}{% endif %}"></i>
|
||||
<i class="fa-regular fa-circle tw:text-green-300" data-bs-toggle="tooltip" data-bs-title="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Income' %}{% endif %}"></i>
|
||||
{% elif transaction.type == "EX" and not transaction.account.is_asset %}
|
||||
<i class="fa-regular fa-circle tw-text-red-400" data-bs-toggle="tooltip" data-bs-title="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Expense' %}{% endif %}"></i>
|
||||
<i class="fa-regular fa-circle tw:text-red-400" data-bs-toggle="tooltip" data-bs-title="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Expense' %}{% endif %}"></i>
|
||||
{% elif transaction.type == "EX" and transaction.account.is_asset %}
|
||||
<i class="fa-regular fa-circle tw-text-red-300" data-bs-toggle="tooltip" data-bs-title="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Expense' %}{% endif %}"></i>
|
||||
<i class="fa-regular fa-circle tw:text-red-300" data-bs-toggle="tooltip" data-bs-title="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Expense' %}{% endif %}"></i>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="!tw-hidden lg:!tw-block card h-100 rounded-0"></div>
|
||||
<div class="tw:hidden! tw:lg:block! card h-100 rounded-0"></div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
@@ -13,45 +13,47 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container px-md-3 py-3 column-gap-5">
|
||||
<div class="row mb-3 gx-xl-4 gy-3 mb-4">
|
||||
{# Date picker#}
|
||||
<div class="col-12 col-xl-4 flex-row align-items-center d-flex">
|
||||
<div class="tw-text-base h-100 align-items-center d-flex">
|
||||
<a role="button"
|
||||
class="pe-4 py-2"
|
||||
hx-boost="true"
|
||||
hx-trigger="click, previous_month from:window"
|
||||
href="{% url 'calendar' month=previous_month year=previous_year %}"><i
|
||||
class="fa-solid fa-chevron-left"></i></a>
|
||||
<div class="container px-md-3 py-3 column-gap-5">
|
||||
<div class="row mb-3 gx-xl-4 gy-3 mb-4">
|
||||
{# Date picker#}
|
||||
<div class="col-12 col-xl-4 flex-row align-items-center d-flex">
|
||||
<div class="tw:text-base h-100 align-items-center d-flex">
|
||||
<a role="button"
|
||||
class="pe-4 py-2"
|
||||
hx-boost="true"
|
||||
hx-trigger="click, previous_month from:window"
|
||||
href="{% url 'calendar' month=previous_month year=previous_year %}"><i
|
||||
class="fa-solid fa-chevron-left"></i></a>
|
||||
</div>
|
||||
<div class="tw:text-3xl fw-bold font-monospace tw:w-full text-center"
|
||||
hx-get="{% url 'month_year_picker' %}"
|
||||
hx-target="#generic-offcanvas-left"
|
||||
hx-trigger="click, date_picker from:window"
|
||||
hx-vals='{"month": {{ month }}, "year": {{ year }}, "for": "calendar", "field": "date"}' role="button">
|
||||
{{ month|month_name }} {{ year }}
|
||||
</div>
|
||||
<div class="tw:text-base mx-2 h-100 align-items-center d-flex">
|
||||
<a role="button"
|
||||
class="ps-3 py-2"
|
||||
hx-boost="true"
|
||||
hx-trigger="click, next_month from:window"
|
||||
href="{% url 'calendar' month=next_month year=next_year %}">
|
||||
<i class="fa-solid fa-chevron-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tw-text-3xl fw-bold font-monospace tw-w-full text-center"
|
||||
hx-get="{% url 'month_year_picker' %}"
|
||||
hx-target="#generic-offcanvas-left"
|
||||
hx-trigger="click, date_picker from:window"
|
||||
hx-vals='{"month": {{ month }}, "year": {{ year }}, "for": "calendar", "field": "date"}' role="button">
|
||||
{{ month|month_name }} {{ year }}
|
||||
</div>
|
||||
<div class="tw-text-base mx-2 h-100 align-items-center d-flex">
|
||||
<a role="button"
|
||||
class="ps-3 py-2"
|
||||
hx-boost="true"
|
||||
hx-trigger="click, next_month from:window"
|
||||
href="{% url 'calendar' month=next_month year=next_year %}">
|
||||
<i class="fa-solid fa-chevron-right"></i>
|
||||
</a>
|
||||
{# Action buttons#}
|
||||
<div class="col-12 col-xl-8">
|
||||
{# <c-ui.quick-transactions-buttons#}
|
||||
{# :year="year"#}
|
||||
{# :month="month"#}
|
||||
{# ></c-ui.quick-transactions-buttons>#}
|
||||
</div>
|
||||
</div>
|
||||
{# Action buttons#}
|
||||
<div class="col-12 col-xl-8">
|
||||
<c-ui.quick-transactions-buttons
|
||||
:year="year"
|
||||
:month="month"
|
||||
></c-ui.quick-transactions-buttons>
|
||||
<div class="row">
|
||||
<div class="show-loading" hx-get="{% url 'calendar_list' month=month year=year %}"
|
||||
hx-trigger="load, updated from:window, selective_update from:window, every 10m"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="show-loading" hx-get="{% url 'calendar_list' month=month year=year %}" hx-trigger="load, updated from:window, selective_update from:window"></div>
|
||||
</div>
|
||||
</div>
|
||||
<c-ui.transactions_fab></c-ui.transactions_fab>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{% load i18n %}
|
||||
<div class="container px-md-3 py-3 column-gap-5">
|
||||
<div class="tw-text-3xl fw-bold font-monospace tw-w-full mb-3">
|
||||
<div class="tw:text-3xl fw-bold font-monospace tw:w-full mb-3">
|
||||
{% spaceless %}
|
||||
<div>{% translate 'Categories' %}<span>
|
||||
<a class="text-decoration-none tw-text-2xl p-1 category-action"
|
||||
<a class="text-decoration-none tw:text-2xl p-1 category-action"
|
||||
role="button"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-title="{% translate "Add" %}"
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
tabindex="0">
|
||||
<ul class="list-group list-group-flush" id="month-year-list">
|
||||
{% for month_data in x.list %}
|
||||
<li class="list-group-item hover:tw-bg-zinc-900
|
||||
<li class="list-group-item tw:hover:bg-zinc-900
|
||||
{% if month_data.month == current_month and month_data.year == current_year %} disabled bg-primary{% endif %}"
|
||||
{% if month_data.month == current_month and month_data.year == current_year %}aria-disabled="true"{% endif %}>
|
||||
<div class="d-flex justify-content-between">
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
{% if not divless %}
|
||||
<div class="{% if text_end %}text-end{% elif text_start %}text-start{% endif %}">
|
||||
{% endif %}
|
||||
<span class="amount{% if color == 'grey' or color == "gray" %} tw-text-gray-500{% elif color == 'green' %} tw-text-green-400{% elif color == 'red' %} tw-text-red-400{% endif %} {{ custom_class }}"
|
||||
<span class="amount{% if color == 'grey' or color == "gray" %} tw:text-gray-500{% elif color == 'green' %} tw:text-green-400{% elif color == 'red' %} tw:text-red-400{% endif %} {{ custom_class }}"
|
||||
data-original-value="{% currency_display amount=amount prefix=prefix suffix=suffix decimal_places=decimal_places %}"
|
||||
data-amount="{{ amount|floatformat:"-40u" }}">
|
||||
</span><span>{{ slot }}</span>
|
||||
{% if not divless %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
33
app/templates/cotton/components/fab.html
Normal file
33
app/templates/cotton/components/fab.html
Normal file
@@ -0,0 +1,33 @@
|
||||
<div class="tw:min-h-16">
|
||||
<div
|
||||
id="fab-wrapper"
|
||||
class="tw:fixed tw:bottom-5 tw:right-5 tw:ml-auto tw:w-max tw:flex tw:flex-col tw:items-end mt-5">
|
||||
<div
|
||||
id="menu"
|
||||
class="tw:flex tw:flex-col tw:items-end tw:space-y-6 tw:transition-all tw:duration-300 tw:ease-in-out tw:opacity-0 tw:invisible tw:hidden tw:mb-2">
|
||||
|
||||
{{ slot }}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="btn btn-primary rounded-circle p-0 tw:w-12 tw:h-12 tw:flex tw:items-center tw:justify-center tw:shadow-lg tw:hover:shadow-xl tw:focus:shadow-xl tw:transition-all tw:duration-300 tw:ease-in-out"
|
||||
_="
|
||||
on click or focusout
|
||||
if #menu.classList.contains('tw:invisible') and event.type === 'click'
|
||||
add .{'tw:rotate-45'} to #fab-icon
|
||||
remove .{'tw:invisible'} from #menu
|
||||
remove .{'tw:hidden'} from #menu
|
||||
remove .{'tw:opacity-0'} from #menu
|
||||
else
|
||||
wait 0.2s
|
||||
remove .{'tw:rotate-45'} from #fab-icon
|
||||
add .{'tw:invisible'} to #menu
|
||||
add .{'tw:hidden'} to #menu
|
||||
add .{'tw:opacity-0'} to #menu
|
||||
end
|
||||
"
|
||||
>
|
||||
<i id="fab-icon" class="fa-solid fa-plus tw:text-3xl tw:transition-transform tw:duration-300 tw:ease-in-out"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
11
app/templates/cotton/components/fab_menu_button.html
Normal file
11
app/templates/cotton/components/fab_menu_button.html
Normal file
@@ -0,0 +1,11 @@
|
||||
{% load i18n %}
|
||||
<div class="tw:relative fab-item">
|
||||
<button class="btn btn-sm btn-{{ color }}"
|
||||
hx-get="{{ url }}"
|
||||
hx-trigger="{{ hx_trigger }}"
|
||||
hx-target="{{ hx_target }}"
|
||||
hx-vals='{{ hx_vals }}'>
|
||||
<i class="{{ icon }} me-2"></i>
|
||||
{{ title }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -1,9 +1,9 @@
|
||||
<div class="row {% if not remove_padding %}p-5{% endif %}">
|
||||
<div class="col {% if not remove_padding %}p-5{% endif %}">
|
||||
<div class="text-center">
|
||||
<i class="{% if icon %}{{ icon }}{% else %}fa-solid fa-circle-xmark{% endif %} tw-text-6xl"></i>
|
||||
<i class="{% if icon %}{{ icon }}{% else %}fa-solid fa-circle-xmark{% endif %} tw:text-6xl"></i>
|
||||
<p class="lead mt-4 mb-0">{{ title }}</p>
|
||||
<p class="tw-text-gray-500">{{ subtitle }}</p>
|
||||
<p class="tw:text-gray-500">{{ subtitle }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,16 +8,17 @@
|
||||
id="check-{{ transaction.id }}" aria-label="{% translate 'Select' %}" hx-preserve>
|
||||
</label>
|
||||
{% endif %}
|
||||
<div class="tw-border-s-6 tw-border-e-0 tw-border-t-0 tw-border-b-0 border-bottom
|
||||
hover:tw-bg-zinc-900 p-2 {% if transaction.account.is_asset %}tw-border-dashed{% else %}tw-border-solid{% endif %}
|
||||
{% if transaction.type == "EX" %}tw-border-red-500{% else %}tw-border-green-500{% endif %} tw-relative
|
||||
<div class="tw:border-s-4 tw:border-e-0 tw:border-t-0 tw:border-b-0 border-bottom
|
||||
tw:hover:bg-zinc-900 p-2 {% if transaction.account.is_asset %}tw:border-dashed{% else %}tw:border-solid{% endif %}
|
||||
{% if transaction.type == "EX" %}tw:border-red-500{% else %}tw:border-green-500{% endif %} tw:relative
|
||||
w-100 transaction-item"
|
||||
_="on mouseover remove .tw-invisible from the first .transaction-actions in me end
|
||||
on mouseout add .tw-invisible to the first .transaction-actions in me end">
|
||||
<div class="row font-monospace tw-text-sm align-items-center">
|
||||
<div class="col-lg-auto col-12 d-flex align-items-center tw-text-2xl lg:tw-text-xl text-lg-center text-center p-0 ps-1">
|
||||
_="on mouseover remove .{'tw:invisible'} from the first .transaction-actions in me end
|
||||
on mouseout add .{'tw:invisible'} to the first .transaction-actions in me end">
|
||||
<div class="row font-monospace tw:text-sm align-items-center">
|
||||
<div
|
||||
class="col-lg-auto col-12 d-flex align-items-center tw:text-2xl tw:lg:text-xl text-lg-center text-center p-0 ps-1">
|
||||
{% if not transaction.deleted %}
|
||||
<a class="text-decoration-none p-3 tw-text-gray-500"
|
||||
<a class="text-decoration-none p-3 tw:text-gray-500!"
|
||||
title="{% if transaction.is_paid %}{% trans 'Paid' %}{% else %}{% trans 'Projected' %}{% endif %}"
|
||||
role="button"
|
||||
hx-get="{% url 'transaction_pay' transaction_id=transaction.id %}"
|
||||
@@ -27,7 +28,7 @@
|
||||
class="fa-regular fa-circle"></i>{% endif %}
|
||||
</a>
|
||||
{% else %}
|
||||
<div class="text-decoration-none p-3 tw-text-gray-500"
|
||||
<div class="text-decoration-none p-3 tw:text-gray-500!"
|
||||
title="{% if transaction.is_paid %}{% trans 'Paid' %}{% else %}{% trans 'Projected' %}{% endif %}">
|
||||
{% if transaction.is_paid %}<i class="fa-regular fa-circle-check"></i>{% else %}<i
|
||||
class="fa-regular fa-circle"></i>{% endif %}
|
||||
@@ -36,13 +37,13 @@
|
||||
</div>
|
||||
<div class="col-lg col-12">
|
||||
{# Date#}
|
||||
<div class="row mb-2 mb-lg-1 tw-text-gray-400">
|
||||
<div class="row mb-2 mb-lg-1 tw:text-gray-400">
|
||||
<div class="col-auto pe-1"><i class="fa-solid fa-calendar fa-fw me-1 fa-xs"></i></div>
|
||||
<div
|
||||
class="col ps-0">{{ transaction.date|date:"SHORT_DATE_FORMAT" }} • {{ transaction.reference_date|date:"b/Y" }}</div>
|
||||
</div>
|
||||
{# Description#}
|
||||
<div class="mb-2 mb-lg-1 text-white tw-text-base">
|
||||
<div class="mb-2 mb-lg-1 text-body tw:text-base">
|
||||
{% spaceless %}
|
||||
<span class="{% if transaction.description %}me-2{% endif %}">{{ transaction.description }}</span>
|
||||
{% if transaction.installment_plan and transaction.installment_id %}
|
||||
@@ -50,18 +51,18 @@
|
||||
class="badge text-bg-secondary">{{ transaction.installment_id }}/{{ transaction.installment_plan.installment_total_number }}</span>
|
||||
{% endif %}
|
||||
{% if transaction.recurring_transaction %}
|
||||
<span class="text-primary tw-text-xs"><i class="fa-solid fa-arrows-rotate fa-fw"></i></span>
|
||||
<span class="text-primary tw:text-xs"><i class="fa-solid fa-arrows-rotate fa-fw"></i></span>
|
||||
{% endif %}
|
||||
{% if transaction.dca_expense_entries.all or transaction.dca_income_entries.all %}
|
||||
<span class="badge text-bg-secondary">{% trans 'DCA' %}</span>
|
||||
{% endif %}
|
||||
{% endspaceless %}
|
||||
</div>
|
||||
<div class="tw-text-gray-400 tw-text-sm">
|
||||
<div class="tw:text-gray-400 tw:text-sm">
|
||||
{# Entities #}
|
||||
{% with transaction.entities.all as entities %}
|
||||
{% if entities %}
|
||||
<div class="row mb-2 mb-lg-1 tw-text-gray-400">
|
||||
<div class="row mb-2 mb-lg-1 tw:text-gray-400">
|
||||
<div class="col-auto pe-1"><i class="fa-solid fa-user-group fa-fw me-1 fa-xs"></i></div>
|
||||
<div class="col ps-0">{{ entities|join:", " }}</div>
|
||||
</div>
|
||||
@@ -69,14 +70,14 @@
|
||||
{% endwith %}
|
||||
{# Notes#}
|
||||
{% if transaction.notes %}
|
||||
<div class="row mb-2 mb-lg-1 tw-text-gray-400">
|
||||
<div class="row mb-2 mb-lg-1 tw:text-gray-400">
|
||||
<div class="col-auto pe-1"><i class="fa-solid fa-align-left fa-fw me-1 fa-xs"></i></div>
|
||||
<div class="col ps-0">{{ transaction.notes | limited_markdown | linebreaksbr }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{# Category#}
|
||||
{% if transaction.category %}
|
||||
<div class="row mb-2 mb-lg-1 tw-text-gray-400">
|
||||
<div class="row mb-2 mb-lg-1 tw:text-gray-400">
|
||||
<div class="col-auto pe-1"><i class="fa-solid fa-icons fa-fw me-1 fa-xs"></i></div>
|
||||
<div class="col ps-0">{{ transaction.category.name }}</div>
|
||||
</div>
|
||||
@@ -84,7 +85,7 @@
|
||||
{# Tags#}
|
||||
{% with transaction.tags.all as tags %}
|
||||
{% if tags %}
|
||||
<div class="row mb-2 mb-lg-1 tw-text-gray-400">
|
||||
<div class="row mb-2 mb-lg-1 tw:text-gray-400">
|
||||
<div class="col-auto pe-1"><i class="fa-solid fa-hashtag fa-fw me-1 fa-xs"></i></div>
|
||||
<div class="col ps-0">{{ tags|join:", " }}</div>
|
||||
</div>
|
||||
@@ -121,7 +122,7 @@
|
||||
<div>
|
||||
{# Item actions#}
|
||||
<div
|
||||
class="transaction-actions !tw-absolute tw-left-1/2 tw-top-0 tw--translate-x-1/2 tw--translate-y-1/2 tw-invisible d-flex flex-row card">
|
||||
class="transaction-actions tw:absolute! tw:left-1/2 tw:top-0 tw:-translate-x-1/2 tw:-translate-y-1/2 tw:invisible d-flex flex-row card">
|
||||
<div class="card-body p-1 shadow-lg">
|
||||
{% if not transaction.deleted %}
|
||||
<a class="btn btn-secondary btn-sm transaction-action"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<div class="card mb-2 transaction-item">
|
||||
<div class="card-body p-2 tw-flex tw-items-center tw-gap-3" data-bs-toggle="collapse" data-bs-target="#{{ transaction.id }}" role="button" aria-expanded="false" aria-controls="{{ transaction.id }}">
|
||||
<div class="card-body p-2 tw:flex tw:items-center tw:gap-3" data-bs-toggle="collapse" data-bs-target="#{{ transaction.id }}" role="button" aria-expanded="false" aria-controls="{{ transaction.id }}">
|
||||
<!-- Main visible content -->
|
||||
<div class="tw-flex flex-lg-row flex-column lg:tw-items-center tw-w-full tw-gap-3">
|
||||
<div class="tw:flex flex-lg-row flex-column tw:lg:items-center tw:w-full tw:gap-3">
|
||||
<!-- Type indicator -->
|
||||
<div class="tw-w-8">
|
||||
<div class="tw:w-8">
|
||||
{% if transaction.type == 'IN' %}
|
||||
<span class="badge bg-success">↑</span>
|
||||
{% else %}
|
||||
@@ -12,7 +12,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Payment status -->
|
||||
<div class="tw-w-8">
|
||||
<div class="tw:w-8">
|
||||
{% if transaction.is_paid %}
|
||||
<span class="badge bg-success">✓</span>
|
||||
{% else %}
|
||||
@@ -21,13 +21,13 @@
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<div class="tw-flex-grow">
|
||||
<span class="tw-font-medium">{{ transaction.description }}</span>
|
||||
<div class="tw:flex-grow">
|
||||
<span class="tw:font-medium">{{ transaction.description }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Amount -->
|
||||
<div class="tw-text-right tw-whitespace-nowrap">
|
||||
<span class="{% if transaction.type == 'IN' %}tw-text-green-400{% else %}tw-text-red-400{% endif %}">
|
||||
<div class="tw:text-right tw:whitespace-nowrap">
|
||||
<span class="{% if transaction.type == 'IN' %}tw:text-green-400{% else %}tw:text-red-400{% endif %}">
|
||||
{{ transaction.amount }}
|
||||
</span>
|
||||
{% if transaction.exchanged_amount %}
|
||||
@@ -91,4 +91,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="col card shadow">
|
||||
<div class="card-body">
|
||||
{% if account.account.group %}
|
||||
<div class="tw-text-sm mb-2">
|
||||
<div class="tw:text-sm mb-2">
|
||||
<span class="badge text-bg-primary ">{{ account.account.group }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -12,11 +12,11 @@
|
||||
</h5>
|
||||
<div class="d-flex justify-content-between align-items-baseline mt-2">
|
||||
<div class="text-end font-monospace">
|
||||
<div class="tw-text-gray-400">{% translate 'projected income' %}</div>
|
||||
<div class="tw:text-gray-400">{% translate 'projected income' %}</div>
|
||||
</div>
|
||||
<div class="dotted-line flex-grow-1"></div>
|
||||
{% if account.income_projected != 0 %}
|
||||
<div class="text-end font-monospace tw-text-green-400">
|
||||
<div class="text-end font-monospace tw:text-green-400">
|
||||
<c-amount.display
|
||||
:amount="account.income_projected"
|
||||
:prefix="account.currency.prefix"
|
||||
@@ -28,7 +28,7 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if account.exchanged and account.exchanged.income_projected %}
|
||||
<div class="text-end font-monospace tw-text-gray-500">
|
||||
<div class="text-end font-monospace tw:text-gray-500">
|
||||
<c-amount.display
|
||||
:amount="account.exchanged.income_projected"
|
||||
:prefix="account.exchanged.currency.prefix"
|
||||
@@ -38,12 +38,12 @@
|
||||
{% endif %}
|
||||
<div class="d-flex justify-content-between align-items-baseline mt-2">
|
||||
<div class="text-end font-monospace">
|
||||
<div class="tw-text-gray-400">{% translate 'projected expenses' %}</div>
|
||||
<div class="tw:text-gray-400">{% translate 'projected expenses' %}</div>
|
||||
</div>
|
||||
<div class="dotted-line flex-grow-1"></div>
|
||||
<div>
|
||||
{% if account.expense_projected != 0 %}
|
||||
<div class="text-end font-monospace tw-text-red-400">
|
||||
<div class="text-end font-monospace tw:text-red-400">
|
||||
<c-amount.display
|
||||
:amount="account.expense_projected"
|
||||
:prefix="account.currency.prefix"
|
||||
@@ -56,7 +56,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{% if account.exchanged and account.exchanged.expense_projected %}
|
||||
<div class="text-end font-monospace tw-text-gray-500">
|
||||
<div class="text-end font-monospace tw:text-gray-500">
|
||||
<c-amount.display
|
||||
:amount="account.exchanged.expense_projected"
|
||||
:prefix="account.exchanged.currency.prefix"
|
||||
@@ -66,7 +66,7 @@
|
||||
{% endif %}
|
||||
<div class="d-flex justify-content-between align-items-baseline mt-2">
|
||||
<div class="text-end font-monospace">
|
||||
<div class="tw-text-gray-400">{% translate 'projected total' %}</div>
|
||||
<div class="tw:text-gray-400">{% translate 'projected total' %}</div>
|
||||
</div>
|
||||
<div class="dotted-line flex-grow-1"></div>
|
||||
<div
|
||||
@@ -80,7 +80,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{% if account.exchanged.total_projected and account.exchanged.total_projected %}
|
||||
<div class="text-end font-monospace tw-text-gray-500">
|
||||
<div class="text-end font-monospace tw:text-gray-500">
|
||||
<c-amount.display
|
||||
:amount="account.exchanged.total_projected"
|
||||
:prefix="account.exchanged.currency.prefix"
|
||||
@@ -91,11 +91,11 @@
|
||||
<hr class="my-3">
|
||||
<div class="d-flex justify-content-between align-items-baseline mt-2">
|
||||
<div class="text-end font-monospace">
|
||||
<div class="tw-text-gray-400">{% translate 'current income' %}</div>
|
||||
<div class="tw:text-gray-400">{% translate 'current income' %}</div>
|
||||
</div>
|
||||
<div class="dotted-line flex-grow-1"></div>
|
||||
{% if account.income_current != 0 %}
|
||||
<div class="text-end font-monospace tw-text-green-400">
|
||||
<div class="text-end font-monospace tw:text-green-400">
|
||||
<c-amount.display
|
||||
:amount="account.income_current"
|
||||
:prefix="account.currency.prefix"
|
||||
@@ -107,7 +107,7 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if account.exchanged and account.exchanged.income_current %}
|
||||
<div class="text-end font-monospace tw-text-gray-500">
|
||||
<div class="text-end font-monospace tw:text-gray-500">
|
||||
<c-amount.display
|
||||
:amount="account.exchanged.income_current"
|
||||
:prefix="account.exchanged.currency.prefix"
|
||||
@@ -117,11 +117,11 @@
|
||||
{% endif %}
|
||||
<div class="d-flex justify-content-between align-items-baseline mt-2">
|
||||
<div class="text-end font-monospace">
|
||||
<div class="tw-text-gray-400">{% translate 'current expenses' %}</div>
|
||||
<div class="tw:text-gray-400">{% translate 'current expenses' %}</div>
|
||||
</div>
|
||||
<div class="dotted-line flex-grow-1"></div>
|
||||
{% if account.expense_current != 0 %}
|
||||
<div class="text-end font-monospace tw-text-red-400">
|
||||
<div class="text-end font-monospace tw:text-red-400">
|
||||
<c-amount.display
|
||||
:amount="account.expense_current"
|
||||
:prefix="account.currency.prefix"
|
||||
@@ -133,7 +133,7 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if account.exchanged and account.exchanged.expense_current %}
|
||||
<div class="text-end font-monospace tw-text-gray-500">
|
||||
<div class="text-end font-monospace tw:text-gray-500">
|
||||
<c-amount.display
|
||||
:amount="account.exchanged.expense_current"
|
||||
:prefix="account.exchanged.currency.prefix"
|
||||
@@ -143,7 +143,7 @@
|
||||
{% endif %}
|
||||
<div class="d-flex justify-content-between align-items-baseline mt-2">
|
||||
<div class="text-end font-monospace">
|
||||
<div class="tw-text-gray-400">{% translate 'current total' %}</div>
|
||||
<div class="tw:text-gray-400">{% translate 'current total' %}</div>
|
||||
</div>
|
||||
<div class="dotted-line flex-grow-1"></div>
|
||||
<div class="text-end font-monospace">
|
||||
@@ -156,7 +156,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{% if account.exchanged and account.exchanged.total_current %}
|
||||
<div class="text-end font-monospace tw-text-gray-500">
|
||||
<div class="text-end font-monospace tw:text-gray-500">
|
||||
<c-amount.display
|
||||
:amount="account.exchanged.total_current"
|
||||
:prefix="account.exchanged.currency.prefix"
|
||||
@@ -168,7 +168,7 @@
|
||||
<hr class="my-3">
|
||||
<div class="d-flex justify-content-between align-items-baseline mt-2">
|
||||
<div class="text-end font-monospace">
|
||||
<div class="tw-text-gray-400">{% translate 'final total' %}</div>
|
||||
<div class="tw:text-gray-400">{% translate 'final total' %}</div>
|
||||
</div>
|
||||
<div class="dotted-line flex-grow-1"></div>
|
||||
<div class="text-end font-monospace">
|
||||
@@ -181,7 +181,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{% if account.exchanged and account.exchanged.total_final %}
|
||||
<div class="text-end font-monospace tw-text-gray-500">
|
||||
<div class="text-end font-monospace tw:text-gray-500">
|
||||
<c-amount.display
|
||||
:amount="account.exchanged.total_final"
|
||||
:prefix="account.exchanged.currency.prefix"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div class="card tw-relative h-100 shadow">
|
||||
<div class="card tw:relative h-100 shadow">
|
||||
<div class="card-body">
|
||||
{{ slot }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
</h5>
|
||||
<div class="d-flex justify-content-between align-items-baseline mt-2">
|
||||
<div class="text-end font-monospace">
|
||||
<div class="tw-text-gray-400">{% translate 'projected income' %}</div>
|
||||
<div class="tw:text-gray-400">{% translate 'projected income' %}</div>
|
||||
</div>
|
||||
<div class="dotted-line flex-grow-1"></div>
|
||||
{% if currency.income_projected != 0 %}
|
||||
<div class="text-end font-monospace tw-text-green-400">
|
||||
<div class="text-end font-monospace tw:text-green-400">
|
||||
<c-amount.display
|
||||
:amount="currency.income_projected"
|
||||
:prefix="currency.currency.prefix"
|
||||
@@ -23,7 +23,7 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if currency.exchanged and currency.exchanged.income_projected %}
|
||||
<div class="text-end font-monospace tw-text-gray-500">
|
||||
<div class="text-end font-monospace tw:text-gray-500">
|
||||
<c-amount.display
|
||||
:amount="currency.exchanged.income_projected"
|
||||
:prefix="currency.exchanged.currency.prefix"
|
||||
@@ -33,12 +33,12 @@
|
||||
{% endif %}
|
||||
<div class="d-flex justify-content-between align-items-baseline mt-2">
|
||||
<div class="text-end font-monospace">
|
||||
<div class="tw-text-gray-400">{% translate 'projected expenses' %}</div>
|
||||
<div class="tw:text-gray-400">{% translate 'projected expenses' %}</div>
|
||||
</div>
|
||||
<div class="dotted-line flex-grow-1"></div>
|
||||
<div>
|
||||
{% if currency.expense_projected != 0 %}
|
||||
<div class="text-end font-monospace tw-text-red-400">
|
||||
<div class="text-end font-monospace tw:text-red-400">
|
||||
<c-amount.display
|
||||
:amount="currency.expense_projected"
|
||||
:prefix="currency.currency.prefix"
|
||||
@@ -51,7 +51,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{% if currency.exchanged and currency.exchanged.expense_projected %}
|
||||
<div class="text-end font-monospace tw-text-gray-500">
|
||||
<div class="text-end font-monospace tw:text-gray-500">
|
||||
<c-amount.display
|
||||
:amount="currency.exchanged.expense_projected"
|
||||
:prefix="currency.exchanged.currency.prefix"
|
||||
@@ -61,7 +61,7 @@
|
||||
{% endif %}
|
||||
<div class="d-flex justify-content-between align-items-baseline mt-2">
|
||||
<div class="text-end font-monospace">
|
||||
<div class="tw-text-gray-400">{% translate 'projected total' %}</div>
|
||||
<div class="tw:text-gray-400">{% translate 'projected total' %}</div>
|
||||
</div>
|
||||
<div class="dotted-line flex-grow-1"></div>
|
||||
<div class="text-end font-monospace">
|
||||
@@ -74,7 +74,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{% if currency.exchanged.total_projected and currency.exchanged.total_projected %}
|
||||
<div class="text-end font-monospace tw-text-gray-500">
|
||||
<div class="text-end font-monospace tw:text-gray-500">
|
||||
<c-amount.display
|
||||
:amount="currency.exchanged.total_projected"
|
||||
:prefix="currency.exchanged.currency.prefix"
|
||||
@@ -85,11 +85,11 @@
|
||||
<hr class="my-3">
|
||||
<div class="d-flex justify-content-between align-items-baseline mt-2">
|
||||
<div class="text-end font-monospace">
|
||||
<div class="tw-text-gray-400">{% translate 'current income' %}</div>
|
||||
<div class="tw:text-gray-400">{% translate 'current income' %}</div>
|
||||
</div>
|
||||
<div class="dotted-line flex-grow-1"></div>
|
||||
{% if currency.income_current != 0 %}
|
||||
<div class="text-end font-monospace tw-text-green-400">
|
||||
<div class="text-end font-monospace tw:text-green-400">
|
||||
<c-amount.display
|
||||
:amount="currency.income_current"
|
||||
:prefix="currency.currency.prefix"
|
||||
@@ -101,7 +101,7 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if currency.exchanged and currency.exchanged.income_current %}
|
||||
<div class="text-end font-monospace tw-text-gray-500">
|
||||
<div class="text-end font-monospace tw:text-gray-500">
|
||||
<c-amount.display
|
||||
:amount="currency.exchanged.income_current"
|
||||
:prefix="currency.exchanged.currency.prefix"
|
||||
@@ -111,11 +111,11 @@
|
||||
{% endif %}
|
||||
<div class="d-flex justify-content-between align-items-baseline mt-2">
|
||||
<div class="text-end font-monospace">
|
||||
<div class="tw-text-gray-400">{% translate 'current expenses' %}</div>
|
||||
<div class="tw:text-gray-400">{% translate 'current expenses' %}</div>
|
||||
</div>
|
||||
<div class="dotted-line flex-grow-1"></div>
|
||||
{% if currency.expense_current != 0 %}
|
||||
<div class="text-end font-monospace tw-text-red-400">
|
||||
<div class="text-end font-monospace tw:text-red-400">
|
||||
<c-amount.display
|
||||
:amount="currency.expense_current"
|
||||
:prefix="currency.currency.prefix"
|
||||
@@ -127,7 +127,7 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if currency.exchanged and currency.exchanged.expense_current %}
|
||||
<div class="text-end font-monospace tw-text-gray-500">
|
||||
<div class="text-end font-monospace tw:text-gray-500">
|
||||
<c-amount.display
|
||||
:amount="currency.exchanged.expense_current"
|
||||
:prefix="currency.exchanged.currency.prefix"
|
||||
@@ -137,7 +137,7 @@
|
||||
{% endif %}
|
||||
<div class="d-flex justify-content-between align-items-baseline mt-2">
|
||||
<div class="text-end font-monospace">
|
||||
<div class="tw-text-gray-400">{% translate 'current total' %}</div>
|
||||
<div class="tw:text-gray-400">{% translate 'current total' %}</div>
|
||||
</div>
|
||||
<div class="dotted-line flex-grow-1"></div>
|
||||
<div class="text-end font-monospace">
|
||||
@@ -150,7 +150,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{% if currency.exchanged and currency.exchanged.total_current %}
|
||||
<div class="text-end font-monospace tw-text-gray-500">
|
||||
<div class="text-end font-monospace tw:text-gray-500">
|
||||
<c-amount.display
|
||||
:amount="currency.exchanged.total_current"
|
||||
:prefix="currency.exchanged.currency.prefix"
|
||||
@@ -162,7 +162,7 @@
|
||||
<hr class="my-3">
|
||||
<div class="d-flex justify-content-between align-items-baseline mt-2">
|
||||
<div class="text-end font-monospace">
|
||||
<div class="tw-text-gray-400">{% translate 'final total' %}</div>
|
||||
<div class="tw:text-gray-400">{% translate 'final total' %}</div>
|
||||
</div>
|
||||
<div class="dotted-line flex-grow-1"></div>
|
||||
<div class="text-end font-monospace">
|
||||
@@ -175,7 +175,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{% if currency.exchanged and currency.exchanged.total_final %}
|
||||
<div class="text-end font-monospace tw-text-gray-500">
|
||||
<div class="text-end font-monospace tw:text-gray-500">
|
||||
<c-amount.display
|
||||
:amount="currency.exchanged.total_final"
|
||||
:prefix="currency.exchanged.currency.prefix"
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
{% load i18n %}
|
||||
<div class="tw-sticky tw-bottom-4 tw-left-0 tw-right-0 tw-z-50 tw-hidden mx-auto tw-w-fit" id="actions-bar"
|
||||
<div class="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 #actions-bar then
|
||||
if no <input[type='checkbox']:checked/> in #transactions-list
|
||||
if #actions-bar
|
||||
add .slide-in-bottom-reverse then settle
|
||||
then add .tw-hidden to #actions-bar
|
||||
then add .tw:hidden to #actions-bar
|
||||
then remove .slide-in-bottom-reverse
|
||||
end
|
||||
else
|
||||
if #actions-bar
|
||||
remove .tw-hidden from #actions-bar
|
||||
remove .tw:hidden from #actions-bar
|
||||
then trigger selected_transactions_updated
|
||||
end
|
||||
end
|
||||
@@ -26,20 +26,20 @@
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<div class="dropdown-item px-3 tw-cursor-pointer"
|
||||
<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' %}
|
||||
<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"
|
||||
<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' %}
|
||||
<i class="fa-regular fa-square tw:text-red-400 me-3"></i>{% translate 'Unselect All' %}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="vr tw-align-middle"></div>
|
||||
<div class="vr tw:align-middle"></div>
|
||||
<button class="btn btn-secondary btn-sm"
|
||||
hx-get="{% url 'transactions_bulk_undelete' %}"
|
||||
hx-include=".transaction"
|
||||
@@ -60,7 +60,7 @@
|
||||
_="install prompt_swal">
|
||||
<i class="fa-solid fa-trash text-danger"></i>
|
||||
</button>
|
||||
<div class="vr tw-align-middle"></div>
|
||||
<div class="vr tw:align-middle"></div>
|
||||
<div class="btn-group"
|
||||
_="on selected_transactions_updated from #actions-bar
|
||||
set realTotal to math.bignumber(0)
|
||||
@@ -118,10 +118,10 @@
|
||||
<li>
|
||||
<div class="dropdown-item-text p-0">
|
||||
<div>
|
||||
<div class="text-body-secondary tw-text-xs tw-font-medium px-3">
|
||||
<div class="text-body-secondary tw:text-xs tw:font-medium px-3">
|
||||
{% trans "Flat Total" %}
|
||||
</div>
|
||||
<div class="dropdown-item px-3 tw-cursor-pointer"
|
||||
<div class="dropdown-item px-3 tw:cursor-pointer"
|
||||
id="calc-menu-flat-total"
|
||||
_="on click
|
||||
set original_value to my innerText
|
||||
@@ -138,10 +138,10 @@
|
||||
<li>
|
||||
<div class="dropdown-item-text p-0">
|
||||
<div>
|
||||
<div class="text-body-secondary tw-text-xs tw-font-medium px-3">
|
||||
<div class="text-body-secondary tw:text-xs tw:font-medium px-3">
|
||||
{% trans "Real Total" %}
|
||||
</div>
|
||||
<div class="dropdown-item px-3 tw-cursor-pointer"
|
||||
<div class="dropdown-item px-3 tw:cursor-pointer"
|
||||
id="calc-menu-real-total"
|
||||
_="on click
|
||||
set original_value to my innerText
|
||||
@@ -158,10 +158,10 @@
|
||||
<li>
|
||||
<div class="dropdown-item-text p-0">
|
||||
<div>
|
||||
<div class="text-body-secondary tw-text-xs tw-font-medium px-3">
|
||||
<div class="text-body-secondary tw:text-xs tw:font-medium px-3">
|
||||
{% trans "Mean" %}
|
||||
</div>
|
||||
<div class="dropdown-item px-3 tw-cursor-pointer"
|
||||
<div class="dropdown-item px-3 tw:cursor-pointer"
|
||||
id="calc-menu-mean"
|
||||
_="on click
|
||||
set original_value to my innerText
|
||||
@@ -178,10 +178,10 @@
|
||||
<li>
|
||||
<div class="dropdown-item-text p-0">
|
||||
<div>
|
||||
<div class="text-body-secondary tw-text-xs tw-font-medium px-3">
|
||||
<div class="text-body-secondary tw:text-xs tw:font-medium px-3">
|
||||
{% trans "Max" %}
|
||||
</div>
|
||||
<div class="dropdown-item px-3 tw-cursor-pointer"
|
||||
<div class="dropdown-item px-3 tw:cursor-pointer"
|
||||
id="calc-menu-max"
|
||||
_="on click
|
||||
set original_value to my innerText
|
||||
@@ -198,10 +198,10 @@
|
||||
<li>
|
||||
<div class="dropdown-item-text p-0">
|
||||
<div>
|
||||
<div class="text-body-secondary tw-text-xs tw-font-medium px-3">
|
||||
<div class="text-body-secondary tw:text-xs tw:font-medium px-3">
|
||||
{% trans "Min" %}
|
||||
</div>
|
||||
<div class="dropdown-item px-3 tw-cursor-pointer"
|
||||
<div class="dropdown-item px-3 tw:cursor-pointer"
|
||||
id="calc-menu-min"
|
||||
_="on click
|
||||
set original_value to my innerText
|
||||
@@ -218,10 +218,10 @@
|
||||
<li>
|
||||
<div class="dropdown-item-text p-0">
|
||||
<div>
|
||||
<div class="text-body-secondary tw-text-xs tw-font-medium px-3">
|
||||
<div class="text-body-secondary tw:text-xs tw:font-medium px-3">
|
||||
{% trans "Count" %}
|
||||
</div>
|
||||
<div class="dropdown-item px-3 tw-cursor-pointer"
|
||||
<div class="dropdown-item px-3 tw:cursor-pointer"
|
||||
id="calc-menu-count"
|
||||
_="on click
|
||||
set original_value to my innerText
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% spaceless %}
|
||||
{% load i18n %}
|
||||
<span class="tw-text-xs text-white-50 mx-1"
|
||||
<span class="tw:text-xs text-white-50 mx-1"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-title="{{ content }}">
|
||||
<i class="{% if not icon %}fa-solid fa-circle-question{% else %}{{ icon }}{% endif %} fa-fw"></i>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<div class="card tw-relative h-100 shadow">
|
||||
<div class="tw-absolute tw-h-8 tw-w-8 tw-right-2 tw-top-2 tw-bg-{{ color }}-300 tw-text-{{ color }}-800 text-center align-items-center d-flex justify-content-center rounded-2">
|
||||
<div class="card tw:relative h-100 shadow">
|
||||
<div class="tw:absolute tw:h-8 tw:w-8 tw:right-2 tw:top-2 tw:bg-{{ color }}-300 tw:text-{{ color }}-800 text-center align-items-center d-flex justify-content-center rounded-2">
|
||||
{% if icon %}<i class="{{ icon }}"></i>{% else %}<span class="fw-bold">{{ title.0 }}</span>{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h5 class="tw-text-{{ color }}-400 fw-bold tw-mr-[50px]" {{ attrs }}>{{ title }}{% if help_text %}<c-ui.help-icon :content="help_text" icon=""></c-ui.help-icon>{% endif %}</h5>
|
||||
<h5 class="tw:text-{{ color }}-400 fw-bold tw:mr-[50px]" {{ attrs }}>{{ title }}{% if help_text %}<c-ui.help-icon :content="help_text" icon=""></c-ui.help-icon>{% endif %}</h5>
|
||||
{{ slot }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
{% load i18n %}
|
||||
<div class="progress-stacked">
|
||||
<div class="progress position-relative" role="progressbar" aria-label="{% trans 'Projected Income' %} ({{ percentage.percentages.income_projected|floatformat:2 }}%)" aria-valuenow="{{ percentage.percentages.expense_projected|floatformat:0 }}" aria-valuemin="0" aria-valuemax="100" style="width: {{ percentage.percentages.income_projected|floatformat:"2u" }}%">
|
||||
<div class="progress-bar progress-bar-striped !tw-bg-green-300"
|
||||
<div class="progress-bar progress-bar-striped tw:bg-green-300!"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="{% trans 'Projected Income' %} ({{ percentage.percentages.income_projected|floatformat:2 }}%)">
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress position-relative" role="progressbar" aria-label="{% trans 'Current Income' %} ({{ percentage.percentages.income_current|floatformat:2 }}%)" aria-valuenow="{{ percentage.percentages.expense_projected|floatformat:0 }}" aria-valuemin="0" aria-valuemax="100" style="width: {{ percentage.percentages.income_current|floatformat:"2u" }}%">
|
||||
<div class="progress-bar !tw-bg-green-400"
|
||||
<div class="progress-bar tw:bg-green-400!"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="{% trans 'Current Income' %} ({{ p.percentages.income_current|floatformat:2 }}%)">
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress position-relative" role="progressbar" aria-label="{% trans 'Projected Expenses' %} ({{ percentage.percentages.expense_projected|floatformat:2 }}%)" aria-valuenow="{{ percentage.percentages.expense_projected|floatformat:0 }}" aria-valuemin="0" aria-valuemax="100" style="width: {{ percentage.percentages.expense_projected|floatformat:"2u" }}%">
|
||||
<div class="progress-bar progress-bar-striped !tw-bg-red-300"
|
||||
<div class="progress-bar progress-bar-striped tw:bg-red-300!"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="{% trans 'Projected Expenses' %} ({{ percentage.percentages.expense_projected|floatformat:2 }}%)">
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress position-relative" role="progressbar" aria-label="{% trans 'Current Expenses' %} ({{ percentage.percentages.expense_current|floatformat:2 }}%)" aria-valuenow="{{ percentage.percentages.expense_projected|floatformat:0 }}" aria-valuemin="0" aria-valuemax="100" style="width: {{ percentage.percentages.expense_current|floatformat:"2u" }}%">
|
||||
<div class="progress-bar !tw-bg-red-400"
|
||||
<div class="progress-bar tw:bg-red-400!"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="{% trans 'Current Expenses' %} ({{ percentage.percentages.expense_current|floatformat:2 }}%)">
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
{% load i18n %}
|
||||
<div class="tw-sticky tw-bottom-4 tw-left-0 tw-right-0 tw-z-50 tw-hidden mx-auto tw-w-fit" id="actions-bar"
|
||||
<div class="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 #actions-bar then
|
||||
if no <input[type='checkbox']:checked/> in #transactions-list
|
||||
if #actions-bar
|
||||
add .slide-in-bottom-reverse then settle
|
||||
then add .tw-hidden to #actions-bar
|
||||
then add .tw:hidden to #actions-bar
|
||||
then remove .slide-in-bottom-reverse
|
||||
end
|
||||
else
|
||||
if #actions-bar
|
||||
remove .tw-hidden from #actions-bar
|
||||
remove .tw:hidden from #actions-bar
|
||||
then trigger selected_transactions_updated
|
||||
end
|
||||
end
|
||||
@@ -26,20 +26,20 @@
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<div class="dropdown-item px-3 tw-cursor-pointer"
|
||||
<div class="dropdown-item px-3 tw:cursor-pointer"
|
||||
_="on click set <#transactions-list .transaction:not([style*='display: none']) input[type='checkbox']/>'s checked to true then call me.blur() then trigger change">
|
||||
<i class="fa-regular fa-square-check tw-text-green-400 me-3"></i>{% translate 'Select All' %}
|
||||
<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"
|
||||
<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' %}
|
||||
<i class="fa-regular fa-square tw:text-red-400 me-3"></i>{% translate 'Unselect All' %}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="vr tw-align-middle"></div>
|
||||
<div class="vr tw:align-middle"></div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-secondary btn-sm"
|
||||
hx-get="{% url 'transactions_bulk_edit' %}"
|
||||
@@ -56,17 +56,17 @@
|
||||
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<div class="dropdown-item px-3 tw-cursor-pointer"
|
||||
<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' %}
|
||||
<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"
|
||||
<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' %}
|
||||
<i class="fa-regular fa-circle-check tw:text-green-400 fa-fw me-3"></i>{% translate 'Mark as paid' %}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -91,7 +91,7 @@
|
||||
_="install prompt_swal">
|
||||
<i class="fa-solid fa-trash text-danger"></i>
|
||||
</button>
|
||||
<div class="vr tw-align-middle"></div>
|
||||
<div class="vr tw:align-middle"></div>
|
||||
<div class="btn-group"
|
||||
_="on selected_transactions_updated from #actions-bar
|
||||
set realTotal to math.bignumber(0)
|
||||
@@ -149,10 +149,10 @@
|
||||
<li>
|
||||
<div class="dropdown-item-text p-0">
|
||||
<div>
|
||||
<div class="text-body-secondary tw-text-xs tw-font-medium px-3">
|
||||
<div class="text-body-secondary tw:text-xs tw:font-medium px-3">
|
||||
{% trans "Flat Total" %}
|
||||
</div>
|
||||
<div class="dropdown-item px-3 tw-cursor-pointer"
|
||||
<div class="dropdown-item px-3 tw:cursor-pointer"
|
||||
id="calc-menu-flat-total"
|
||||
_="on click
|
||||
set original_value to my innerText
|
||||
@@ -169,10 +169,10 @@
|
||||
<li>
|
||||
<div class="dropdown-item-text p-0">
|
||||
<div>
|
||||
<div class="text-body-secondary tw-text-xs tw-font-medium px-3">
|
||||
<div class="text-body-secondary tw:text-xs tw:font-medium px-3">
|
||||
{% trans "Real Total" %}
|
||||
</div>
|
||||
<div class="dropdown-item px-3 tw-cursor-pointer"
|
||||
<div class="dropdown-item px-3 tw:cursor-pointer"
|
||||
id="calc-menu-real-total"
|
||||
_="on click
|
||||
set original_value to my innerText
|
||||
@@ -189,10 +189,10 @@
|
||||
<li>
|
||||
<div class="dropdown-item-text p-0">
|
||||
<div>
|
||||
<div class="text-body-secondary tw-text-xs tw-font-medium px-3">
|
||||
<div class="text-body-secondary tw:text-xs tw:font-medium px-3">
|
||||
{% trans "Mean" %}
|
||||
</div>
|
||||
<div class="dropdown-item px-3 tw-cursor-pointer"
|
||||
<div class="dropdown-item px-3 tw:cursor-pointer"
|
||||
id="calc-menu-mean"
|
||||
_="on click
|
||||
set original_value to my innerText
|
||||
@@ -209,10 +209,10 @@
|
||||
<li>
|
||||
<div class="dropdown-item-text p-0">
|
||||
<div>
|
||||
<div class="text-body-secondary tw-text-xs tw-font-medium px-3">
|
||||
<div class="text-body-secondary tw:text-xs tw:font-medium px-3">
|
||||
{% trans "Max" %}
|
||||
</div>
|
||||
<div class="dropdown-item px-3 tw-cursor-pointer"
|
||||
<div class="dropdown-item px-3 tw:cursor-pointer"
|
||||
id="calc-menu-max"
|
||||
_="on click
|
||||
set original_value to my innerText
|
||||
@@ -229,10 +229,10 @@
|
||||
<li>
|
||||
<div class="dropdown-item-text p-0">
|
||||
<div>
|
||||
<div class="text-body-secondary tw-text-xs tw-font-medium px-3">
|
||||
<div class="text-body-secondary tw:text-xs tw:font-medium px-3">
|
||||
{% trans "Min" %}
|
||||
</div>
|
||||
<div class="dropdown-item px-3 tw-cursor-pointer"
|
||||
<div class="dropdown-item px-3 tw:cursor-pointer"
|
||||
id="calc-menu-min"
|
||||
_="on click
|
||||
set original_value to my innerText
|
||||
@@ -249,10 +249,10 @@
|
||||
<li>
|
||||
<div class="dropdown-item-text p-0">
|
||||
<div>
|
||||
<div class="text-body-secondary tw-text-xs tw-font-medium px-3">
|
||||
<div class="text-body-secondary tw:text-xs tw:font-medium px-3">
|
||||
{% trans "Count" %}
|
||||
</div>
|
||||
<div class="dropdown-item px-3 tw-cursor-pointer"
|
||||
<div class="dropdown-item px-3 tw:cursor-pointer"
|
||||
id="calc-menu-count"
|
||||
_="on click
|
||||
set original_value to my innerText
|
||||
|
||||
60
app/templates/cotton/ui/transactions_fab.html
Normal file
60
app/templates/cotton/ui/transactions_fab.html
Normal file
@@ -0,0 +1,60 @@
|
||||
{% load i18n %}
|
||||
<c-components.fab>
|
||||
<c-components.fab_menu_button
|
||||
color="success"
|
||||
hx_target="#generic-offcanvas"
|
||||
hx_trigger="click, add_income from:window"
|
||||
hx_vals='{"year": {{ year }}, {% if month %}"month": {{ month }},{% endif %} "type": "IN"}'
|
||||
url="{% url 'transaction_add' %}"
|
||||
icon="fa-solid fa-arrow-right-to-bracket"
|
||||
title="{% translate "Income" %}"></c-components.fab_menu_button>
|
||||
|
||||
<c-components.fab_menu_button
|
||||
color="danger"
|
||||
hx_target="#generic-offcanvas"
|
||||
hx_trigger="click, add_income from:window"
|
||||
hx_vals='{"year": {{ year }}, {% if month %}"month": {{ month }},{% endif %} "type": "EX"}'
|
||||
url="{% url 'transaction_add' %}"
|
||||
icon="fa-solid fa-arrow-right-from-bracket"
|
||||
title="{% translate "Expense" %}"></c-components.fab_menu_button>
|
||||
|
||||
<c-components.fab_menu_button
|
||||
color="warning"
|
||||
hx_target="#generic-offcanvas"
|
||||
hx_trigger="click, installment from:window"
|
||||
url="{% url 'installment_plan_add' %}"
|
||||
icon="fa-solid fa-divide"
|
||||
title="{% translate "Installment" %}"></c-components.fab_menu_button>
|
||||
|
||||
<c-components.fab_menu_button
|
||||
color="warning"
|
||||
hx_target="#generic-offcanvas"
|
||||
hx_trigger="click, recurring from:window"
|
||||
url="{% url 'recurring_transaction_add' %}"
|
||||
icon="fa-solid fa-repeat"
|
||||
title="{% translate "Recurring" %}"></c-components.fab_menu_button>
|
||||
|
||||
<c-components.fab_menu_button
|
||||
color="info"
|
||||
hx_target="#generic-offcanvas"
|
||||
hx_trigger="click, transfer from:window"
|
||||
hx_vals='{"year": {{ year }} {% if month %}, "month": {{ month }}{% endif %}}'
|
||||
url="{% url 'transactions_transfer' %}"
|
||||
icon="fa-solid fa-money-bill-transfer"
|
||||
title="{% translate "Transfer" %}"></c-components.fab_menu_button>
|
||||
|
||||
<c-components.fab_menu_button
|
||||
color="info"
|
||||
hx_target="#generic-offcanvas"
|
||||
hx_trigger="click, balance from:window"
|
||||
url="{% url 'account_reconciliation' %}"
|
||||
icon="fa-solid fa-scale-balanced"
|
||||
title="{% translate "Balance" %}"></c-components.fab_menu_button>
|
||||
<c-components.fab_menu_button
|
||||
color="secondary"
|
||||
hx_target="#generic-offcanvas"
|
||||
hx_trigger="click, quick_transaction from:window"
|
||||
url="{% url 'quick_transactions_create_menu' %}"
|
||||
icon="fa-solid fa-person-running"
|
||||
title="{% translate "Quick Transaction" %}"></c-components.fab_menu_button>
|
||||
</c-components.fab>
|
||||
@@ -1,9 +1,9 @@
|
||||
{% load i18n %}
|
||||
<div class="container px-md-3 py-3 column-gap-5">
|
||||
<div class="tw-text-3xl fw-bold font-monospace tw-w-full mb-3">
|
||||
<div class="tw:text-3xl fw-bold font-monospace tw:w-full mb-3">
|
||||
{% spaceless %}
|
||||
<div>{% translate 'Currencies' %}<span>
|
||||
<a class="text-decoration-none tw-text-2xl p-1 category-action"
|
||||
<a class="text-decoration-none tw:text-2xl p-1 category-action"
|
||||
role="button"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-title="{% translate "Add" %}"
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
{% load i18n %}
|
||||
<div class="container-fluid px-md-3 py-3 column-gap-5">
|
||||
<div class="d-lg-flex justify-content-between mb-3 w-100">
|
||||
<div class="tw-text-3xl fw-bold font-monospace d-flex align-items-center">
|
||||
<div class="tw:text-3xl fw-bold font-monospace d-flex align-items-center">
|
||||
{{ strategy.name }}
|
||||
</div>
|
||||
<div class="tw-text-sm text-lg-end mt-2 mt-lg-0">
|
||||
<div class="tw:text-sm text-lg-end mt-2 mt-lg-0">
|
||||
<div class="mb-2">
|
||||
<span class="badge rounded-pill text-bg-secondary">{{ strategy.payment_currency.name }}</span> x <span class="badge rounded-pill text-bg-secondary">{{ strategy.target_currency.name }}</span>
|
||||
</div>
|
||||
@@ -19,7 +19,7 @@
|
||||
• {{ strategy.current_price.1|date:"SHORT_DATETIME_FORMAT" }}
|
||||
</c-amount.display>
|
||||
{% else %}
|
||||
<div class="tw-text-red-400">{% trans "No exchange rate available" %}</div>
|
||||
<div class="tw:text-red-400">{% trans "No exchange rate available" %}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -30,7 +30,7 @@
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
{% spaceless %}
|
||||
<div class="card-title tw-text-xl">{% trans "Entries" %}<span>
|
||||
<div class="card-title tw:text-xl">{% trans "Entries" %}<span>
|
||||
<a class="text-decoration-none p-1 category-action"
|
||||
role="button"
|
||||
data-bs-toggle="tooltip"
|
||||
@@ -190,7 +190,7 @@
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{% trans "Total P/L" %}</h5>
|
||||
<div
|
||||
class="card-text {% if strategy.total_profit_loss >= 0 %}tw-text-green-400{% else %}tw-text-red-400{% endif %}">
|
||||
class="card-text {% if strategy.total_profit_loss >= 0 %}tw:text-green-400{% else %}tw:text-red-400{% endif %}">
|
||||
<c-amount.display
|
||||
:amount="strategy.total_profit_loss"
|
||||
:prefix="strategy.payment_currency.prefix"
|
||||
@@ -206,7 +206,7 @@
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{% trans "Total % P/L" %}</h5>
|
||||
<div
|
||||
class="card-text {% if strategy.total_profit_loss >= 0 %}tw-text-green-400{% else %}tw-text-red-400{% endif %}">
|
||||
class="card-text {% if strategy.total_profit_loss >= 0 %}tw:text-green-400{% else %}tw:text-red-400{% endif %}">
|
||||
{{ strategy.total_profit_loss_percentage|floatformat:2 }}%
|
||||
</div>
|
||||
</div>
|
||||
@@ -451,7 +451,7 @@
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{% trans "Investment Frequency" %}</h5>
|
||||
<p class="card-text tw-text-gray-400">
|
||||
<p class="card-text tw:text-gray-400">
|
||||
{% trans "The straighter the blue line, the more consistent your DCA strategy is." %}
|
||||
</p>
|
||||
<canvas id="frequencyChart"></canvas>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{% load i18n %}
|
||||
<div class="container px-md-3 py-3 column-gap-5">
|
||||
<div class="tw-text-3xl fw-bold font-monospace tw-w-full mb-3">
|
||||
<div class="tw:text-3xl fw-bold font-monospace tw:w-full mb-3">
|
||||
{% spaceless %}
|
||||
<div>{% translate 'Dollar Cost Average Strategies' %}<span>
|
||||
<a class="text-decoration-none tw-text-2xl p-1 category-action"
|
||||
<a class="text-decoration-none tw:text-2xl p-1 category-action"
|
||||
role="button"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-title="{% translate "Add" %}"
|
||||
@@ -25,12 +25,12 @@
|
||||
<a href="{% url 'dca_strategy_detail_index' strategy_id=strategy.id %}" hx-boost="true"
|
||||
class="text-decoration-none card-body">
|
||||
<div class="">
|
||||
<div class="card-title tw-text-xl">{{ strategy.name }}</div>
|
||||
<div class="card-text tw-text-gray-400">{{ strategy.notes }}</div>
|
||||
<div class="card-title tw:text-xl">{{ strategy.name }}</div>
|
||||
<div class="card-text tw:text-gray-400">{{ strategy.notes }}</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="card-footer text-end">
|
||||
<a class="text-decoration-none tw-text-gray-400 p-1"
|
||||
<a class="text-decoration-none tw:text-gray-400 p-1"
|
||||
role="button"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-title="{% translate "Edit" %}"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{% load i18n %}
|
||||
<div class="container px-md-3 py-3 column-gap-5">
|
||||
<div class="tw-text-3xl fw-bold font-monospace tw-w-full mb-3">
|
||||
<div class="tw:text-3xl fw-bold font-monospace tw:w-full mb-3">
|
||||
{% spaceless %}
|
||||
<div>{% translate 'Entities' %}<span>
|
||||
<a class="text-decoration-none tw-text-2xl p-1 category-action"
|
||||
<a class="text-decoration-none tw:text-2xl p-1 category-action"
|
||||
role="button"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-title="{% translate "Add" %}"
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{% load currency_display %}
|
||||
{% load i18n %}
|
||||
<div class="container px-md-3 py-3 column-gap-5">
|
||||
<div class="tw-text-3xl fw-bold font-monospace tw-w-full mb-3">
|
||||
<div class="tw:text-3xl fw-bold font-monospace tw:w-full mb-3">
|
||||
{% spaceless %}
|
||||
<div>{% translate 'Exchange Rates' %}<span>
|
||||
<a class="text-decoration-none tw-text-2xl p-1 category-action"
|
||||
<a class="text-decoration-none tw:text-2xl p-1 category-action"
|
||||
role="button"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-title="{% translate "Add" %}"
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
<nav aria-label="{% translate 'Page navigation' %}">
|
||||
<ul class="pagination justify-content-center mt-5">
|
||||
<li class="page-item">
|
||||
<a class="page-link tw-cursor-pointer {% if not page_obj.has_previous %}disabled{% endif %}"
|
||||
<a class="page-link tw:cursor-pointer {% if not page_obj.has_previous %}disabled{% endif %}"
|
||||
hx-get="{% if page_obj.has_previous %}{% url 'exchange_rates_list_pair' %}{% endif %}"
|
||||
hx-vals='{"page": 1, "from": "{{ from_currency|default_if_none:"" }}", "to": "{{ to_currency|default_if_none:"" }}"}'
|
||||
hx-include="#filter, #order"
|
||||
@@ -79,13 +79,13 @@
|
||||
{% if page_number <= page_obj.number|add:3 and page_number >= page_obj.number|add:-3 %}
|
||||
{% if page_obj.number == page_number %}
|
||||
<li class="page-item active">
|
||||
<a class="page-link tw-cursor-pointer">
|
||||
<a class="page-link tw:cursor-pointer">
|
||||
{{ page_number }}
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item">
|
||||
<a class="page-link tw-cursor-pointer"
|
||||
<a class="page-link tw:cursor-pointer"
|
||||
hx-get="{% url 'exchange_rates_list_pair' %}"
|
||||
hx-vals='{"page": {{ page_number }}, "from": "{{ from_currency|default_if_none:"" }}", "to": "{{ to_currency|default_if_none:"" }}"}'
|
||||
hx-target="#exchange-rates-table"
|
||||
@@ -104,7 +104,7 @@
|
||||
</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link tw-cursor-pointer"
|
||||
<a class="page-link tw:cursor-pointer"
|
||||
hx-get="{% url 'exchange_rates_list_pair' %}" hx-target="#exchange-rates-table"
|
||||
hx-vals='{"page": {{ page_obj.paginator.num_pages }}, "from": "{{ from_currency|default_if_none:"" }}", "to": "{{ to_currency|default_if_none:"" }}"}'
|
||||
hx-include="#filter, #order"
|
||||
@@ -115,7 +115,7 @@
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="page-item">
|
||||
<a class="page-link {% if not page_obj.has_next %}disabled{% endif %} tw-cursor-pointer"
|
||||
<a class="page-link {% if not page_obj.has_next %}disabled{% endif %} tw:cursor-pointer"
|
||||
hx-get="{% if page_obj.has_next %}{% url 'exchange_rates_list_pair' %}{% endif %}"
|
||||
hx-vals='{"page": {{ page_obj.paginator.num_pages }}, "from": "{{ from_currency|default_if_none:"" }}", "to": "{{ to_currency|default_if_none:"" }}"}'
|
||||
hx-include="#filter, #order"
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{% load currency_display %}
|
||||
{% load i18n %}
|
||||
<div class="container px-md-3 py-3 column-gap-5">
|
||||
<div class="tw-text-3xl fw-bold font-monospace tw-w-full mb-3">
|
||||
<div class="tw:text-3xl fw-bold font-monospace tw:w-full mb-3">
|
||||
{% spaceless %}
|
||||
<div>{% translate 'Automatic Exchange Rates' %}<span>
|
||||
<a class="text-decoration-none tw-text-2xl p-1 category-action"
|
||||
<a class="text-decoration-none tw:text-2xl p-1 category-action"
|
||||
role="button"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-title="{% translate "Add" %}"
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
<nav aria-label="{% translate 'Page navigation' %}">
|
||||
<ul class="pagination justify-content-center mt-5">
|
||||
<li class="page-item">
|
||||
<a class="page-link tw-cursor-pointer {% if not page_obj.has_previous %}disabled{% endif %}"
|
||||
<a class="page-link tw:cursor-pointer {% if not page_obj.has_previous %}disabled{% endif %}"
|
||||
hx-get="{% if page_obj.has_previous %}{% url 'exchange_rates_list_pair' %}{% endif %}"
|
||||
hx-vals='{"page": 1, "from": "{{ from_currency|default_if_none:"" }}", "to": "{{ to_currency|default_if_none:"" }}"}'
|
||||
hx-include="#filter, #order"
|
||||
@@ -79,13 +79,13 @@
|
||||
{% if page_number <= page_obj.number|add:3 and page_number >= page_obj.number|add:-3 %}
|
||||
{% if page_obj.number == page_number %}
|
||||
<li class="page-item active">
|
||||
<a class="page-link tw-cursor-pointer">
|
||||
<a class="page-link tw:cursor-pointer">
|
||||
{{ page_number }}
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item">
|
||||
<a class="page-link tw-cursor-pointer"
|
||||
<a class="page-link tw:cursor-pointer"
|
||||
hx-get="{% url 'exchange_rates_list_pair' %}"
|
||||
hx-vals='{"page": {{ page_number }}, "from": "{{ from_currency|default_if_none:"" }}", "to": "{{ to_currency|default_if_none:"" }}"}'
|
||||
hx-target="#exchange-rates-table"
|
||||
@@ -104,7 +104,7 @@
|
||||
</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link tw-cursor-pointer"
|
||||
<a class="page-link tw:cursor-pointer"
|
||||
hx-get="{% url 'exchange_rates_list_pair' %}" hx-target="#exchange-rates-table"
|
||||
hx-vals='{"page": {{ page_obj.paginator.num_pages }}, "from": "{{ from_currency|default_if_none:"" }}", "to": "{{ to_currency|default_if_none:"" }}"}'
|
||||
hx-include="#filter, #order"
|
||||
@@ -115,7 +115,7 @@
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="page-item">
|
||||
<a class="page-link {% if not page_obj.has_next %}disabled{% endif %} tw-cursor-pointer"
|
||||
<a class="page-link {% if not page_obj.has_next %}disabled{% endif %} tw:cursor-pointer"
|
||||
hx-get="{% if page_obj.has_next %}{% url 'exchange_rates_list_pair' %}{% endif %}"
|
||||
hx-vals='{"page": {{ page_obj.paginator.num_pages }}, "from": "{{ from_currency|default_if_none:"" }}", "to": "{{ to_currency|default_if_none:"" }}"}'
|
||||
hx-include="#filter, #order"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
{% block body %}
|
||||
{% if message %}
|
||||
<div class="alert alert-info" role="alert" id="msg" hx-preserve="true">
|
||||
<h6 class="alert-heading tw-italic tw-font-bold">{% trans 'A message from the author' %}</h6>
|
||||
<h6 class="alert-heading tw:italic tw:font-bold">{% trans 'A message from the author' %}</h6>
|
||||
<hr>
|
||||
<p class="mb-0">{{ message|linebreaksbr }}</p>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{% load i18n %}
|
||||
<div class="container px-md-3 py-3 column-gap-5">
|
||||
<div class="tw-text-3xl fw-bold font-monospace tw-w-full mb-3">
|
||||
<div class="tw:text-3xl fw-bold font-monospace tw:w-full mb-3">
|
||||
{% spaceless %}
|
||||
<div>{% translate 'Import Profiles' %}<span>
|
||||
<span class="dropdown" data-bs-toggle="tooltip"
|
||||
data-bs-title="{% translate "Add" %}">
|
||||
<a class="text-decoration-none tw-text-2xl p-1" role="button"
|
||||
<a class="text-decoration-none tw:text-2xl p-1" role="button"
|
||||
data-bs-toggle="dropdown"
|
||||
data-bs-title="{% translate "Add" %}" aria-expanded="false">
|
||||
<i class="fa-solid fa-circle-plus fa-fw"></i>
|
||||
|
||||
@@ -15,20 +15,20 @@
|
||||
{% for run in runs %}
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-header tw-text-sm {% if run.status == run.Status.QUEUED %}tw-text-white{% elif run.status == run.Status.PROCESSING %}text-warning{% elif run.status == run.Status.FINISHED %}text-success{% else %}text-danger{% endif %}">
|
||||
<div class="card-header tw:text-sm {% if run.status == run.Status.QUEUED %}text-body{% elif run.status == run.Status.PROCESSING %}text-warning{% elif run.status == run.Status.FINISHED %}text-success{% else %}text-danger{% endif %}">
|
||||
<span><i class="fa-solid {% if run.status == run.Status.QUEUED %}fa-hourglass-half{% elif run.status == run.Status.PROCESSING %}fa-spinner{% elif run.status == run.Status.FINISHED %}fa-check{% else %}fa-xmark{% endif %} fa-fw me-2"></i>{{ run.get_status_display }}</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><i class="fa-solid fa-hashtag me-1 tw-text-xs tw-text-gray-400"></i>{{ run.id }}<span class="tw-text-xs tw-text-gray-400 ms-1">({{ run.file_name }})</span></h5>
|
||||
<h5 class="card-title"><i class="fa-solid fa-hashtag me-1 tw:text-xs tw:text-gray-400"></i>{{ run.id }}<span class="tw:text-xs tw:text-gray-400 ms-1">({{ run.file_name }})</span></h5>
|
||||
<hr>
|
||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 w-100 g-4">
|
||||
<div class="col">
|
||||
<div class="d-flex flex-row">
|
||||
<div class="d-flex flex-column">
|
||||
<div class="text-body-secondary tw-text-xs tw-font-medium">
|
||||
<div class="text-body-secondary tw:text-xs tw:font-medium">
|
||||
{% trans 'Total Items' %}
|
||||
</div>
|
||||
<div class="tw-text-sm">
|
||||
<div class="tw:text-sm">
|
||||
{{ run.total_rows }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -38,10 +38,10 @@
|
||||
<div class="col">
|
||||
<div class="d-flex flex-row">
|
||||
<div class="d-flex flex-column">
|
||||
<div class="text-body-secondary tw-text-xs tw-font-medium">
|
||||
<div class="text-body-secondary tw:text-xs tw:font-medium">
|
||||
{% trans 'Processed Items' %}
|
||||
</div>
|
||||
<div class="tw-text-sm">
|
||||
<div class="tw:text-sm">
|
||||
{{ run.processed_rows }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -51,10 +51,10 @@
|
||||
<div class="col">
|
||||
<div class="d-flex flex-row">
|
||||
<div class="d-flex flex-column">
|
||||
<div class="text-body-secondary tw-text-xs tw-font-medium">
|
||||
<div class="text-body-secondary tw:text-xs tw:font-medium">
|
||||
{% trans 'Skipped Items' %}
|
||||
</div>
|
||||
<div class="tw-text-sm">
|
||||
<div class="tw:text-sm">
|
||||
{{ run.skipped_rows }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -64,10 +64,10 @@
|
||||
<div class="col">
|
||||
<div class="d-flex flex-row">
|
||||
<div class="d-flex flex-column">
|
||||
<div class="text-body-secondary tw-text-xs tw-font-medium">
|
||||
<div class="text-body-secondary tw:text-xs tw:font-medium">
|
||||
{% trans 'Failed Items' %}
|
||||
</div>
|
||||
<div class="tw-text-sm">
|
||||
<div class="tw:text-sm">
|
||||
{{ run.failed_rows }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -77,10 +77,10 @@
|
||||
<div class="col">
|
||||
<div class="d-flex flex-row">
|
||||
<div class="d-flex flex-column">
|
||||
<div class="text-body-secondary tw-text-xs tw-font-medium">
|
||||
<div class="text-body-secondary tw:text-xs tw:font-medium">
|
||||
{% trans 'Successful Items' %}
|
||||
</div>
|
||||
<div class="tw-text-sm">
|
||||
<div class="tw:text-sm">
|
||||
{{ run.successful_rows }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
{% block title %}{% translate 'Logs for' %} #{{ run.id }}{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="card tw-max-h-full tw-overflow-auto">
|
||||
<div class="card tw:max-h-full tw:overflow-auto">
|
||||
<div class="card-body">
|
||||
{{ run.logs|linebreaks }}
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarContent">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0 nav-underline" hx-push-url="true">
|
||||
<ul class="navbar-nav me-auto mb-3 mb-lg-0 nav-underline" hx-push-url="true">
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle {% active_link views='monthly_overview||yearly_overview_currency||yearly_overview_account||calendar' %}"
|
||||
href="#"
|
||||
@@ -50,7 +50,7 @@
|
||||
<a class="nav-link {% active_link views='insights_index' %}" href="{% url 'insights_index' %}">{% trans 'Insights' %}</a>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle {% active_link views='installment_plans_index||recurring_trasanctions_index||transactions_all_index||transactions_trash_index' %}"
|
||||
<a class="nav-link dropdown-toggle {% active_link views='installment_plans_index||quick_transactions_index||recurring_trasanctions_index||transactions_all_index||transactions_trash_index' %}"
|
||||
href="#" role="button"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false">
|
||||
@@ -68,6 +68,8 @@
|
||||
{% endif %}
|
||||
<hr class="dropdown-divider">
|
||||
</li>
|
||||
<li><a class="dropdown-item {% active_link views='quick_transactions_index' %}"
|
||||
href="{% url 'quick_transactions_index' %}">{% translate 'Quick Transactions' %}</a></li>
|
||||
<li><a class="dropdown-item {% active_link views='installment_plans_index' %}"
|
||||
href="{% url 'installment_plans_index' %}">{% translate 'Installment Plans' %}</a></li>
|
||||
<li><a class="dropdown-item {% active_link views='recurring_trasanctions_index' %}"
|
||||
@@ -159,16 +161,16 @@
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="navbar-nav mt-3 mb-2 mb-lg-0 mt-lg-0">
|
||||
<li class="nav-item text-center w-100">
|
||||
<a class="nav-item tw-text-2xl tw-cursor-pointer me-lg-4"
|
||||
<ul class="navbar-nav mb-2 mb-lg-0 gap-3">
|
||||
<li class="nav-item">
|
||||
<div class="nav-link tw:lg:text-2xl! tw:cursor-pointer"
|
||||
data-bs-toggle="tooltip" data-bs-placement="left" data-bs-title="{% trans "Calculator" %}"
|
||||
_="on click trigger show on #calculator">
|
||||
<i class="fa-solid fa-calculator"></i>
|
||||
</a>
|
||||
<span class="d-lg-none d-inline">{% trans "Calculator" %}</span>
|
||||
</div>
|
||||
</li>
|
||||
<li class="text-center w-100">{% include 'includes/navbar/user_menu.html' %}</li>
|
||||
|
||||
<li class="w-100">{% include 'includes/navbar/user_menu.html' %}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
{% load settings %}
|
||||
{% load i18n %}
|
||||
<div class="dropdown">
|
||||
<a class="tw-text-2xl" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<div class="nav-link tw:lg:text-2xl! tw:cursor-pointer" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="fa-solid fa-user"></i>
|
||||
</a>
|
||||
<span class="d-lg-none d-inline">{% trans "Profile" %}</span>
|
||||
</div>
|
||||
<ul class="dropdown-menu dropdown-menu-start dropdown-menu-lg-end">
|
||||
<li class="dropdown-item-text">{{ user.email }}</li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{# We use this to preload dynamically generated tailwind classes so the compiler can build them ahead of time #}
|
||||
|
||||
<div class="tw-text-blue-800"></div>
|
||||
<div class="tw-text-yellow-800"></div>
|
||||
<div class="tw-text-red-800"></div>
|
||||
<div class="tw-text-green-800"></div>
|
||||
<div class="tw-text-blue-400"></div>
|
||||
<div class="tw-text-yellow-400"></div>
|
||||
<div class="tw-text-red-400"></div>
|
||||
<div class="tw-text-green-400"></div>
|
||||
<div class="tw-bg-blue-300"></div>
|
||||
<div class="tw-bg-yellow-300"></div>
|
||||
<div class="tw-bg-red-300"></div>
|
||||
<div class="tw-bg-green-300"></div>
|
||||
<div class="tw:text-blue-800"></div>
|
||||
<div class="tw:text-yellow-800"></div>
|
||||
<div class="tw:text-red-800"></div>
|
||||
<div class="tw:text-green-800"></div>
|
||||
<div class="tw:text-blue-400"></div>
|
||||
<div class="tw:text-yellow-400"></div>
|
||||
<div class="tw:text-red-400"></div>
|
||||
<div class="tw:text-green-400"></div>
|
||||
<div class="tw:bg-blue-300"></div>
|
||||
<div class="tw:bg-yellow-300"></div>
|
||||
<div class="tw:bg-red-300"></div>
|
||||
<div class="tw:bg-green-300"></div>
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
<script type="text/hyperscript">
|
||||
on paid if body do not include #settings-mute-sound
|
||||
js
|
||||
volume = JSON.parse(document.getElementById('volume').textContent) / 10
|
||||
paidSound.pause()
|
||||
paidSound.currentTime = 0
|
||||
paidSound.volume = volume
|
||||
paidSound.play()
|
||||
end
|
||||
end
|
||||
on unpaid if body do not include #settings-mute-sound
|
||||
js
|
||||
volume = JSON.parse(document.getElementById('volume').textContent) / 10
|
||||
unpaidSound.pause()
|
||||
unpaidSound.currentTime = 0
|
||||
unpaidSound.volume = volume
|
||||
unpaidSound.play()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
{% load webpack_loader %}
|
||||
|
||||
{% stylesheet_pack 'style' %}
|
||||
{#{% stylesheet_pack 'select' %}#}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<div id="toasts">
|
||||
<div class="toast-container position-fixed bottom-0 end-0 p-3" hx-trigger="load, updated from:window, toasts from:window" hx-get="{% url 'toasts' %}" hx-swap="beforeend">
|
||||
<div class="toast-container position-fixed bottom-0 start-50 translate-middle-x p-3" hx-trigger="load, updated from:window, toasts from:window" hx-get="{% url 'toasts' %}" hx-swap="beforeend">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% load formats %}
|
||||
<div class="tw-hidden tw-w-[60vw] lg:tw-w-[30vw] xl:tw-w-[20vw] position-fixed shadow rounded-3 bg-body tw-border-gray-700 tw-border tw-border-solid tw-text-center tw-align-middle tw-z-[2000] tw-touch-none user-select-none"
|
||||
<div class="tw:hidden tw:w-[60vw] tw:lg:w-[30vw] tw:xl:w-[20vw] position-fixed shadow rounded-3 bg-body tw:border-gray-700 tw:border tw:border-solid tw:text-center tw:align-middle tw:z-[2000] tw:touch-none user-select-none"
|
||||
id="calculator"
|
||||
hx-preserve
|
||||
_="
|
||||
@@ -13,8 +13,8 @@
|
||||
on focusin halt the event end -- this prevents bootstrap's static offcanvas from hijacking the focus from the input when open end
|
||||
|
||||
on show or keyup[code is 'KeyC' and altKey is true] from body
|
||||
if my.classList.contains('tw-hidden')
|
||||
remove .tw-hidden from me
|
||||
if my.classList.contains('tw:hidden')
|
||||
remove .{'tw:hidden'} from me
|
||||
measure my width, height
|
||||
set xoff to (window.innerWidth/2) - (width/2)
|
||||
set yoff to (window.innerHeight/2) - (height)
|
||||
@@ -23,7 +23,7 @@
|
||||
then call #calculator-input.focus()
|
||||
else
|
||||
add .scale-out-center to me then wait for animationend then remove .scale-out-center from me
|
||||
add .tw-hidden to me
|
||||
add .{'tw:hidden'} to me
|
||||
end
|
||||
end
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
end">
|
||||
|
||||
<div id="calculator-handle"
|
||||
class="position-absolute bg-secondary rounded-top-2 tw-cursor-move d-flex align-items-center justify-content-center tw-top-[-20px] tw-left-[3px] tw-w-[2em] tw-h-[20px]">
|
||||
class="position-absolute bg-secondary rounded-top-2 tw:cursor-move d-flex align-items-center justify-content-center tw:top-[-20px] tw:left-[3px] tw:w-[2em] tw:h-[20px]">
|
||||
<i class="fa-solid fa-grip"></i>
|
||||
</div>
|
||||
|
||||
@@ -73,31 +73,31 @@
|
||||
end
|
||||
then set localizedResult to it
|
||||
set #calculator-result.innerText to localizedResult
|
||||
then remove .tw-hidden from #calculator-result-container
|
||||
then remove .{'tw:hidden'} from #calculator-result-container
|
||||
then add .swing-in-top-fwd to #calculator-result-container
|
||||
then settle
|
||||
then remove .swing-in-top-fwd from #calculator-result-container
|
||||
else
|
||||
add .swing-out-top-bck to #calculator-result-container
|
||||
then settle
|
||||
then add .tw-hidden to #calculator-result-container
|
||||
then add .{'tw:hidden'} to #calculator-result-container
|
||||
then remove .swing-out-top-bck from #calculator-result-container
|
||||
end
|
||||
catch e
|
||||
add .swing-out-top-bck to #calculator-result-container
|
||||
then settle
|
||||
then add .tw-hidden to #calculator-result-container
|
||||
then add .{'tw:hidden'} to #calculator-result-container
|
||||
then remove .swing-out-top-bck from #calculator-result-container
|
||||
end"
|
||||
placeholder="2 + 2">
|
||||
<div class="tw-hidden" id="calculator-result-container">
|
||||
<div class="tw:hidden" id="calculator-result-container">
|
||||
<div class="d-flex flex-row p-2 justify-content-between">
|
||||
<div class="tw-text-gray-400">=</div>
|
||||
<div class="tw:text-gray-400">=</div>
|
||||
<div id="calculator-result" class="user-select-all"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="position-absolute tw-cursor-pointer top-0 start-100 translate-middle tw-p-0 text-bg-primary border border-light rounded-circle tw-flex tw-items-center tw-justify-center tw-w-5 tw-h-5"
|
||||
<div class="position-absolute tw:cursor-pointer top-0 start-100 translate-middle tw:p-0 text-bg-primary border border-light rounded-circle tw:flex tw:items-center tw:justify-center tw:w-5 tw:h-5"
|
||||
_="on click trigger show on #calculator">
|
||||
<i class="fa-solid fa-xmark tw-flex tw-items-center tw-justify-center tw-w-full tw-h-full"></i>
|
||||
<i class="fa-solid fa-xmark tw:flex tw:items-center tw:justify-center tw:w-full tw:h-full"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,17 +12,17 @@
|
||||
data-bs-target="#flush-collapse-{{ id }}" aria-expanded="false"
|
||||
aria-controls="flush-collapse-{{ id }}">
|
||||
<span>
|
||||
<span class="tw-text-gray-300">{% trans "You've spent an average of" %}</span>
|
||||
<span class="tw:text-gray-300">{% trans "You've spent an average of" %}</span>
|
||||
<c-amount.display
|
||||
:amount="data.average"
|
||||
:prefix="data.currency.prefix"
|
||||
:suffix="data.currency.suffix"
|
||||
:decimal_places="data.currency.decimal_places"
|
||||
custom_class="tw-text-3xl"
|
||||
custom_class="tw:text-3xl"
|
||||
divless></c-amount.display>
|
||||
<span class="tw-text-gray-300">{% trans 'on the last 12 months, at this rate you could go by' %}</span>
|
||||
<span class="tw-text-3xl">{{ data.months }}</span>
|
||||
<span class="tw-text-gray-300">{% trans 'months without any income.' %}</span>
|
||||
<span class="tw:text-gray-300">{% trans 'on the last 12 months, at this rate you could go by' %}</span>
|
||||
<span class="tw:text-3xl">{{ data.months }}</span>
|
||||
<span class="tw:text-gray-300">{% trans 'months without any income.' %}</span>
|
||||
</span>
|
||||
</button>
|
||||
</h2>
|
||||
@@ -31,7 +31,7 @@
|
||||
<div class="accordion-body">
|
||||
<div class="d-flex justify-content-between align-items-baseline mt-2">
|
||||
<div class="text-end font-monospace">
|
||||
<div class="tw-text-gray-400">{% translate 'average expenses' %}</div>
|
||||
<div class="tw:text-gray-400">{% translate 'average expenses' %}</div>
|
||||
</div>
|
||||
<div class="dotted-line flex-grow-1"></div>
|
||||
<div class="text-end font-monospace">
|
||||
@@ -45,7 +45,7 @@
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-baseline mt-2">
|
||||
<div class="text-end font-monospace">
|
||||
<div class="tw-text-gray-400">{% translate 'liquid total' %}</div>
|
||||
<div class="tw:text-gray-400">{% translate 'liquid total' %}</div>
|
||||
</div>
|
||||
<div class="dotted-line flex-grow-1"></div>
|
||||
<div class="text-end font-monospace">
|
||||
@@ -59,7 +59,7 @@
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-baseline mt-2">
|
||||
<div class="text-end font-monospace">
|
||||
<div class="tw-text-gray-400">{% translate 'months left' %}</div>
|
||||
<div class="tw:text-gray-400">{% translate 'months left' %}</div>
|
||||
</div>
|
||||
<div class="dotted-line flex-grow-1"></div>
|
||||
<div class="text-end font-monospace">
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<div class="show-loading" hx-get="{% url 'insights_sankey_by_currency' %}" hx-trigger="updated from:window"
|
||||
hx-swap="outerHTML" hx-include="#picker-form, #picker-type">
|
||||
{% endif %}
|
||||
<div class="chart-container position-relative tw-min-h-[85vh] tw-max-h-[85vh] tw-h-full tw-w-full"
|
||||
<div class="chart-container position-relative tw:min-h-[85vh] tw:max-h-[85vh] tw:h-full tw:w-full"
|
||||
id="sankeyContainer"
|
||||
_="init call setupSankeyChart() end">
|
||||
<canvas id="sankeyChart"></canvas>
|
||||
|
||||
@@ -8,27 +8,27 @@
|
||||
<div class="container-fluid">
|
||||
<div class="row my-3 h-100">
|
||||
<div class="col-lg-2 col-md-3 mb-3 mb-md-0">
|
||||
<div class="position-sticky tw-top-3">
|
||||
<div class="position-sticky tw:top-3">
|
||||
<div class="">
|
||||
<div class="mb-2 w-100 d-lg-inline-flex d-grid gap-2 flex-wrap justify-content-lg-center" role="group"
|
||||
_="on change
|
||||
set type to event.target.value
|
||||
add .tw-hidden to <#picker-form > div:not(.tw-hidden)/>
|
||||
add .tw:hidden to <#picker-form > div:not(.tw:hidden)/>
|
||||
|
||||
if type == 'month'
|
||||
remove .tw-hidden from #month-form
|
||||
remove .tw:hidden from #month-form
|
||||
end
|
||||
if type == 'year'
|
||||
remove .tw-hidden from #year-form
|
||||
remove .tw:hidden from #year-form
|
||||
end
|
||||
if type == 'month-range'
|
||||
remove .tw-hidden from #month-range-form
|
||||
remove .tw:hidden from #month-range-form
|
||||
end
|
||||
if type == 'year-range'
|
||||
remove .tw-hidden from #year-range-form
|
||||
remove .tw:hidden from #year-range-form
|
||||
end
|
||||
if type == 'date-range'
|
||||
remove .tw-hidden from #date-range-form
|
||||
remove .tw:hidden from #date-range-form
|
||||
end
|
||||
then trigger updated"
|
||||
id="picker-type">
|
||||
@@ -60,16 +60,16 @@
|
||||
<div id="month-form" class="">
|
||||
{% crispy month_form %}
|
||||
</div>
|
||||
<div id="year-form" class="tw-hidden">
|
||||
<div id="year-form" class="tw:hidden">
|
||||
{% crispy year_form %}
|
||||
</div>
|
||||
<div id="month-range-form" class="tw-hidden">
|
||||
<div id="month-range-form" class="tw:hidden">
|
||||
{% crispy month_range_form %}
|
||||
</div>
|
||||
<div id="year-range-form" class="tw-hidden">
|
||||
<div id="year-range-form" class="tw:hidden">
|
||||
{% crispy year_range_form %}
|
||||
</div>
|
||||
<div id="date-range-form" class="tw-hidden">
|
||||
<div id="date-range-form" class="tw:hidden">
|
||||
{% crispy date_range_form %}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{% load i18n %}
|
||||
<div class="container px-md-3 py-3 column-gap-5">
|
||||
<div class="tw-text-3xl fw-bold font-monospace tw-w-full mb-3">
|
||||
<div class="tw:text-3xl fw-bold font-monospace tw:w-full mb-3">
|
||||
{% spaceless %}
|
||||
<div>{% translate 'Installment Plans' %}<span>
|
||||
<a class="text-decoration-none tw-text-2xl p-1 category-action"
|
||||
<a class="text-decoration-none tw:text-2xl p-1 category-action"
|
||||
role="button"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-title="{% translate "Add" %}"
|
||||
|
||||
@@ -64,10 +64,10 @@
|
||||
</div>
|
||||
</td>
|
||||
<td class="col">
|
||||
<div class="{% if installment_plan.type == 'EX' %}tw-text-red-400{% else %}tw-text-green-400{% endif %}">
|
||||
<div class="{% if installment_plan.type == 'EX' %}tw:text-red-400{% else %}tw:text-green-400{% endif %}">
|
||||
{{ installment_plan.description }}
|
||||
</div>
|
||||
<div class="tw-text-sm tw-text-gray-400">{{ installment_plan.notes|linebreaksbr }}</div>
|
||||
<div class="tw:text-sm tw:text-gray-400">{{ installment_plan.notes|linebreaksbr }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<div class="container px-md-3 py-3 column-gap-5"
|
||||
_="install init_tom_select
|
||||
install init_datepicker">
|
||||
<div class="tw-text-3xl fw-bold font-monospace tw-w-full mb-3">
|
||||
<div class="tw:text-3xl fw-bold font-monospace tw:w-full mb-3">
|
||||
<div>{% translate 'Currency Converter' %}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
@@ -27,7 +27,7 @@
|
||||
</div>
|
||||
<div>{{ form.from_currency|as_crispy_field }}</div>
|
||||
</div>
|
||||
<div class="col text-primary tw-flex tw-items-center tw-justify-center my-3 my-lg-0">
|
||||
<div class="col text-primary tw:flex tw:items-center tw:justify-center my-3 my-lg-0">
|
||||
<i class="fa-solid fa-equals"></i>
|
||||
</div>
|
||||
<div class="col-12 col-lg-5">
|
||||
@@ -45,7 +45,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="tw-cursor-pointer text-primary text-end"
|
||||
<div class="tw:cursor-pointer text-primary text-end"
|
||||
_="on click
|
||||
set from_value to #id_from_currency's value
|
||||
set to_value to #id_to_currency's value
|
||||
@@ -66,7 +66,7 @@
|
||||
{% for rate in data.rates.values %}
|
||||
<div class="d-flex justify-content-between align-items-baseline mt-2">
|
||||
<div class="text-end font-monospace">
|
||||
<div class="tw-text-gray-400">
|
||||
<div class="tw:text-gray-400">
|
||||
{# <c-amount.display#}
|
||||
{# :amount="1"#}
|
||||
{# :prefix="data.prefix"#}
|
||||
@@ -76,7 +76,7 @@
|
||||
</div>
|
||||
<div class="dotted-line flex-grow-1"></div>
|
||||
{% if currency.income_projected != 0 %}
|
||||
<div class="text-end font-monospace tw-text-green-400">
|
||||
<div class="text-end font-monospace tw:text-green-400">
|
||||
<c-amount.display
|
||||
:amount="rate.rate"
|
||||
:prefix="rate.prefix"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="container px-md-3 py-3 column-gap-5">
|
||||
<div class="tw-text-3xl fw-bold font-monospace tw-w-full mb-3">
|
||||
<div class="tw:text-3xl fw-bold font-monospace tw:w-full mb-3">
|
||||
<div>{% translate 'Unit Price Calculator' %}</div>
|
||||
</div>
|
||||
<div class="card mb-3 d-none" id="card-placeholder">
|
||||
@@ -36,7 +36,7 @@
|
||||
</div>
|
||||
<div class="col-lg">
|
||||
<label class="form-label">{% trans 'Unit price' %}</label>
|
||||
<div class="unit-price tw-text-xl" data-amount="0">0</div>
|
||||
<div class="unit-price tw:text-xl" data-amount="0">0</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -109,7 +109,7 @@
|
||||
</div>
|
||||
<div class="col-lg">
|
||||
<label class="form-label">{% trans 'Unit price' %}</label>
|
||||
<div class="unit-price tw-text-xl" data-amount="0">0</div>
|
||||
<div class="unit-price tw:text-xl" data-amount="0">0</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -134,7 +134,7 @@
|
||||
</div>
|
||||
<div class="col-lg">
|
||||
<label class="form-label">{% trans 'Unit price' %}</label>
|
||||
<div class="unit-price tw-text-xl" data-amount="0">0</div>
|
||||
<div class="unit-price tw:text-xl" data-amount="0">0</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
{% for x in transactions_by_date %}
|
||||
<div id="{{ x.grouper|slugify }}" class="transactions-divider"
|
||||
_="on htmx:afterSwap from #transactions if sessionStorage.getItem(my id) is null then sessionStorage.setItem(my id, 'true')">
|
||||
<div class="mt-3 mb-1 w-100 tw-text-base border-bottom bg-body transactions-divider-title">
|
||||
<div class="mt-3 mb-1 w-100 tw:text-base border-bottom bg-body transactions-divider-title">
|
||||
<a class="text-decoration-none d-inline-block w-100"
|
||||
role="button"
|
||||
data-bs-toggle="collapse"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<c-ui.info-card color="yellow" icon="fa-solid fa-calendar-day" title="{% trans 'Daily Spending Allowance' %}" help_text={% trans "This is the final total divided by the remaining days in the month" %}>
|
||||
<div class="d-flex justify-content-between mt-3">
|
||||
<div class="text-end font-monospace">
|
||||
<div class="tw-text-gray-400">{% translate 'today' %}</div>
|
||||
<div class="tw:text-gray-400">{% translate 'today' %}</div>
|
||||
</div>
|
||||
<div class="text-end font-monospace">
|
||||
{% for currency in daily_spending_allowance.values %}
|
||||
@@ -39,7 +39,7 @@
|
||||
<c-ui.info-card color="green" icon="fa-solid fa-arrow-right-to-bracket" title="{% trans 'Income' %}">
|
||||
<div class="d-flex justify-content-between mt-3">
|
||||
<div class="text-end font-monospace">
|
||||
<div class="tw-text-gray-400">{% translate 'current' %}</div>
|
||||
<div class="tw:text-gray-400">{% translate 'current' %}</div>
|
||||
</div>
|
||||
<div class="text-end font-monospace">
|
||||
{% for currency in income_current.values %}
|
||||
@@ -69,7 +69,7 @@
|
||||
<hr class="my-1">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div class="text-end font-monospace">
|
||||
<div class="tw-text-gray-400">{% translate 'projected' %}</div>
|
||||
<div class="tw:text-gray-400">{% translate 'projected' %}</div>
|
||||
</div>
|
||||
<div class="text-end font-monospace">
|
||||
{% for currency in income_projected.values %}
|
||||
@@ -103,7 +103,7 @@
|
||||
<c-ui.info-card color="red" icon="fa-solid fa-arrow-right-from-bracket" title="{% trans 'Expenses' %}">
|
||||
<div class="d-flex justify-content-between mt-3">
|
||||
<div class="text-end font-monospace">
|
||||
<div class="tw-text-gray-400">{% translate 'current' %}</div>
|
||||
<div class="tw:text-gray-400">{% translate 'current' %}</div>
|
||||
</div>
|
||||
<div class="text-end font-monospace">
|
||||
{% for currency in expense_current.values %}
|
||||
@@ -133,7 +133,7 @@
|
||||
<hr class="my-1">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div class="text-end font-monospace">
|
||||
<div class="tw-text-gray-400">{% translate 'projected' %}</div>
|
||||
<div class="tw:text-gray-400">{% translate 'projected' %}</div>
|
||||
</div>
|
||||
<div class="text-end font-monospace">
|
||||
{% for currency in expense_projected.values %}
|
||||
@@ -167,7 +167,7 @@
|
||||
<c-ui.info-card color="blue" icon="fa-solid fa-scale-balanced" title="{% trans 'Total' %}">
|
||||
<div class="d-flex justify-content-between mt-3">
|
||||
<div class="text-end font-monospace">
|
||||
<div class="tw-text-gray-400">{% translate 'current' %}</div>
|
||||
<div class="tw:text-gray-400">{% translate 'current' %}</div>
|
||||
</div>
|
||||
<div class="text-end font-monospace">
|
||||
{% for currency in total_current.values %}
|
||||
@@ -196,7 +196,7 @@
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mt-3">
|
||||
<div class="text-end font-monospace">
|
||||
<div class="tw-text-gray-400">{% translate 'projected' %}</div>
|
||||
<div class="tw:text-gray-400">{% translate 'projected' %}</div>
|
||||
</div>
|
||||
<div class="text-end font-monospace">
|
||||
{% for currency in total_projected.values %}
|
||||
@@ -256,7 +256,7 @@
|
||||
<div class="col">
|
||||
<c-ui.info-card color="yellow" icon="fa-solid fa-percent" title="{% trans 'Distribution' %}">
|
||||
{% for p in percentages.values %}
|
||||
<p class="tw-text-gray-400 mb-2 {% if not forloop.first %}mt-3{% endif %}">{{ p.currency.name }}</p>
|
||||
<p class="tw:text-gray-400 mb-2 {% if not forloop.first %}mt-3{% endif %}">{{ p.currency.name }}</p>
|
||||
<c-ui.percentage-distribution :percentage="p"></c-ui.percentage-distribution>
|
||||
{% endfor %}
|
||||
</c-ui.info-card>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<div class="row mb-3 gx-xl-4 gy-3 mb-4">
|
||||
{# Date picker#}
|
||||
<div class="col-12 col-xl-4 flex-row align-items-center d-flex">
|
||||
<div class="tw-text-base h-100 align-items-center d-flex">
|
||||
<div class="tw:text-base h-100 align-items-center d-flex">
|
||||
<a role="button"
|
||||
class="pe-4 py-2"
|
||||
hx-boost="true"
|
||||
@@ -25,7 +25,7 @@
|
||||
href="{% url 'monthly_overview' month=previous_month year=previous_year %}"><i
|
||||
class="fa-solid fa-chevron-left"></i></a>
|
||||
</div>
|
||||
<div class="tw-text-3xl fw-bold font-monospace tw-w-full text-center"
|
||||
<div class="tw:text-3xl fw-bold font-monospace tw:w-full text-center"
|
||||
hx-get="{% url 'month_year_picker' %}"
|
||||
hx-target="#generic-offcanvas-left"
|
||||
hx-trigger="click, date_picker from:window"
|
||||
@@ -33,7 +33,7 @@
|
||||
role="button">
|
||||
{{ month|month_name }} {{ year }}
|
||||
</div>
|
||||
<div class="tw-text-base mx-2 h-100 align-items-center d-flex">
|
||||
<div class="tw:text-base mx-2 h-100 align-items-center d-flex">
|
||||
<a role="button"
|
||||
class="ps-3 py-2"
|
||||
hx-boost="true"
|
||||
@@ -44,12 +44,12 @@
|
||||
</div>
|
||||
</div>
|
||||
{# Action buttons#}
|
||||
<div class="col-12 col-xl-8">
|
||||
<c-ui.quick-transactions-buttons
|
||||
:year="year"
|
||||
:month="month"
|
||||
></c-ui.quick-transactions-buttons>
|
||||
</div>
|
||||
{# <div class="col-12 col-xl-8">#}
|
||||
{# <c-ui.quick-transactions-buttons#}
|
||||
{# :year="year"#}
|
||||
{# :month="month"#}
|
||||
{# ></c-ui.quick-transactions-buttons>#}
|
||||
{# </div>#}
|
||||
</div>
|
||||
{# Monthly summary#}
|
||||
<div class="row gx-xl-4 gy-3">
|
||||
@@ -104,7 +104,7 @@
|
||||
<div id="summary"
|
||||
hx-get="{% url 'monthly_summary' month=month year=year %}"
|
||||
class="show-loading"
|
||||
hx-trigger="load, updated from:window, selective_update from:window">
|
||||
hx-trigger="load, updated from:window, selective_update from:window, every 10m">
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade {% if summary_tab == 'currency' %}show active{% endif %}"
|
||||
@@ -115,7 +115,7 @@
|
||||
<div id="currency-summary"
|
||||
hx-get="{% url 'monthly_currency_summary' month=month year=year %}"
|
||||
class="show-loading"
|
||||
hx-trigger="load, updated from:window, selective_update from:window">
|
||||
hx-trigger="load, updated from:window, selective_update from:window, every 10m">
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade {% if summary_tab == 'account' %}show active{% endif %}"
|
||||
@@ -126,7 +126,7 @@
|
||||
<div id="account-summary"
|
||||
hx-get="{% url 'monthly_account_summary' month=month year=year %}"
|
||||
class="show-loading"
|
||||
hx-trigger="load, updated from:window, selective_update from:window">
|
||||
hx-trigger="load, updated from:window, selective_update from:window, every 10m">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -143,11 +143,11 @@
|
||||
</button>
|
||||
</div>
|
||||
{# Ordering button#}
|
||||
<div class="col-sm-6 col-12 tw-content-center my-3 my-sm-0">
|
||||
<div class="col-sm-6 col-12 tw:content-center my-3 my-sm-0">
|
||||
<div class="text-sm-end" _="on change trigger updated on window">
|
||||
<label for="order">{% translate "Order by" %}</label>
|
||||
<select
|
||||
class="tw-border-0 focus-visible:tw-outline-0 w-full pe-2 tw-leading-normal text-bg-tertiary tw-font-medium rounded"
|
||||
class="tw:border-0 tw:focus-visible:outline-0 w-full pe-2 tw:leading-normal text-bg-tertiary tw:font-medium rounded bg-body text-body"
|
||||
name="order" id="order">
|
||||
<option value="default"
|
||||
{% if order == 'default' %}selected{% endif %}>{% translate 'Default' %}</option>
|
||||
@@ -174,8 +174,9 @@
|
||||
</div>
|
||||
<div id="search" class="my-3">
|
||||
<label class="w-100">
|
||||
<input type="search" class="form-control" placeholder="{% translate 'Search' %}" hx-preserve id="quick-search"
|
||||
_="on input or search or htmx:afterSwap from window
|
||||
<input type="search" class="form-control" placeholder="{% translate 'Search' %}" hx-preserve
|
||||
id="quick-search"
|
||||
_="on input or search or htmx:afterSwap from window
|
||||
if my value is empty
|
||||
trigger toggle on <.transactions-divider-collapse/>
|
||||
else
|
||||
@@ -190,9 +191,12 @@
|
||||
<div id="transactions"
|
||||
class="show-loading"
|
||||
hx-get="{% url 'monthly_transactions_list' month=month year=year %}"
|
||||
hx-trigger="load, updated from:window" hx-include="#filter, #order">
|
||||
hx-trigger="load, updated from:window, every 10m" hx-include="#filter, #order">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<c-ui.transactions_fab></c-ui.transactions_fab>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
{% block title %}{% if type == "current" %}{% translate 'Current Net Worth' %}{% else %}{% translate 'Projected Net Worth' %}{% endif %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div hx-trigger="every 60m" class="show-loading" hx-get="" hx-target="body">
|
||||
<div class="container px-md-3 py-3" _="init call initializeAccountChart() then initializeCurrencyChart() end">
|
||||
<div class="row gx-xl-4 gy-3 mb-4">
|
||||
<div class="col-12 col-xl-5">
|
||||
@@ -19,7 +20,7 @@
|
||||
{% for currency in currency_net_worth.values %}
|
||||
<div class="d-flex justify-content-between mt-2">
|
||||
<div class="d-flex align-items-baseline w-100">
|
||||
<div class="currency-name text-start font-monospace tw-text-gray-300"
|
||||
<div class="currency-name text-start font-monospace tw:text-gray-300"
|
||||
_="on click showOnlyCurrencyDataset('{{ currency.currency.name }}')">
|
||||
{{ currency.currency.name }}
|
||||
</div>
|
||||
@@ -48,7 +49,7 @@
|
||||
{% endif %}
|
||||
{% if currency.consolidated and currency.consolidated.total_final != currency.total_final %}
|
||||
<div class="d-flex align-items-baseline w-100">
|
||||
<div class="account-name text-start font-monospace tw-text-gray-300">
|
||||
<div class="account-name text-start font-monospace tw:text-gray-300">
|
||||
<span class="hierarchy-line-icon"></span>{% trans 'Consolidated' %}</div>
|
||||
<div class="dotted-line flex-grow-1"></div>
|
||||
<div class="">
|
||||
@@ -68,7 +69,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-xl-7">
|
||||
<div class="chart-container position-relative tw-min-h-[40vh] tw-h-full">
|
||||
<div class="chart-container position-relative tw:min-h-[40vh] tw:h-full">
|
||||
<canvas id="currencyBalanceChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,14 +86,14 @@
|
||||
{% if data.grouper %}
|
||||
<div class="d-flex justify-content-between mt-2">
|
||||
<div class="d-flex align-items-baseline w-100">
|
||||
<div class="text-start font-monospace tw-text-gray-300"><span class="badge text-bg-primary">
|
||||
<div class="text-start font-monospace tw:text-gray-300"><span class="badge text-bg-primary">
|
||||
{{ data.grouper }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
{% for account in data.list %}
|
||||
<div class="d-flex justify-content-between mt-2">
|
||||
<div class="d-flex align-items-baseline w-100">
|
||||
<div class="account-name text-start font-monospace tw-text-gray-300"
|
||||
<div class="account-name text-start font-monospace tw:text-gray-300"
|
||||
_="on click showOnlyAccountDataset('{{ account.account.name }}')">
|
||||
<span class="hierarchy-line-icon"></span>{{ account.account.name }}</div>
|
||||
<div class="dotted-line flex-grow-1"></div>
|
||||
@@ -120,7 +121,7 @@
|
||||
{% for account in data.list %}
|
||||
<div class="d-flex justify-content-between mt-2">
|
||||
<div class="d-flex align-items-baseline w-100">
|
||||
<div class="account-name text-start font-monospace tw-text-gray-300"
|
||||
<div class="account-name text-start font-monospace tw:text-gray-300"
|
||||
_="on click showOnlyAccountDataset('{{ account.account.name }}')">
|
||||
{{ account.account.name }}
|
||||
</div>
|
||||
@@ -152,7 +153,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-xl-7">
|
||||
<div class="chart-container position-relative tw-min-h-[40vh] tw-h-full">
|
||||
<div class="chart-container position-relative tw:min-h-[40vh] tw:h-full">
|
||||
<canvas id="accountBalanceChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
@@ -318,5 +319,5 @@
|
||||
call currencyChart.update()
|
||||
end
|
||||
</script>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
11
app/templates/quick_transactions/fragments/add.html
Normal file
11
app/templates/quick_transactions/fragments/add.html
Normal file
@@ -0,0 +1,11 @@
|
||||
{% extends 'extends/offcanvas.html' %}
|
||||
{% load i18n %}
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
{% block title %}{% translate 'Add quick transaction' %}{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<form hx-post="{% url 'quick_transaction_add' %}" hx-target="#generic-offcanvas" novalidate>
|
||||
{% crispy form %}
|
||||
</form>
|
||||
{% endblock %}
|
||||
17
app/templates/quick_transactions/fragments/create_menu.html
Normal file
17
app/templates/quick_transactions/fragments/create_menu.html
Normal file
@@ -0,0 +1,17 @@
|
||||
{% extends 'extends/offcanvas.html' %}
|
||||
{% load i18n %}
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
{% block title %}{% translate 'Add quick transaction' %}{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="list-group list-group-flush">
|
||||
{% for qt in quick_transactions %}
|
||||
<a hx-get="{% url 'quick_transaction_add_as_transaction' quick_transaction_id=qt.id %}"
|
||||
class="list-group-item list-group-item-action tw:cursor-pointer {% if qt.type == 'EX' %}tw:text-red-400!{% else %}tw:text-green-400!{% endif %}">{{ qt.name }}</a>
|
||||
{% empty %}
|
||||
<c-msg.empty title="{% translate "Nothing to see here..." %}" remove-padding></c-msg.empty>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
13
app/templates/quick_transactions/fragments/edit.html
Normal file
13
app/templates/quick_transactions/fragments/edit.html
Normal file
@@ -0,0 +1,13 @@
|
||||
{% extends 'extends/offcanvas.html' %}
|
||||
{% load i18n %}
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
{% block title %}{% translate 'Edit quick transaction' %}{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<form hx-post="{% url 'quick_transaction_edit' quick_transaction_id=quick_transaction.id %}"
|
||||
hx-target="#generic-offcanvas"
|
||||
novalidate>
|
||||
{% crispy form %}
|
||||
</form>
|
||||
{% endblock %}
|
||||
59
app/templates/quick_transactions/fragments/list.html
Normal file
59
app/templates/quick_transactions/fragments/list.html
Normal file
@@ -0,0 +1,59 @@
|
||||
{% load i18n %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div id="quick-transactions-table">
|
||||
{% if quick_transactions %}
|
||||
<c-config.search></c-config.search>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="col-auto"></th>
|
||||
<th scope="col" class="col">{% translate 'Name' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for qt in quick_transactions %}
|
||||
<tr class="recurring_transaction">
|
||||
<td class="col-auto text-center">
|
||||
<div class="btn-group" role="group" aria-label="{% translate 'Actions' %}">
|
||||
<a class="btn btn-secondary btn-sm"
|
||||
role="button"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-title="{% translate "Edit" %}"
|
||||
hx-get="{% url 'quick_transaction_edit' quick_transaction_id=qt.id %}"
|
||||
hx-swap="innerHTML"
|
||||
hx-target="#generic-offcanvas">
|
||||
<i class="fa-solid fa-pencil fa-fw"></i></a>
|
||||
<a class="btn btn-secondary btn-sm text-danger"
|
||||
role="button"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-title="{% translate "Delete" %}"
|
||||
hx-delete="{% url 'quick_transaction_delete' quick_transaction_id=qt.id %}"
|
||||
hx-trigger='confirmed'
|
||||
hx-swap="innerHTML"
|
||||
data-bypass-on-ctrl="true"
|
||||
data-title="{% translate "Are you sure?" %}"
|
||||
data-text="{% translate "This will delete this item" %}"
|
||||
data-confirm-text="{% translate "Yes, delete it!" %}"
|
||||
_="install prompt_swal"><i class="fa-solid fa-trash fa-fw"></i></a>
|
||||
</div>
|
||||
</td>
|
||||
<td class="col">
|
||||
<div
|
||||
class="{% if qt.type == 'EX' %}tw:text-red-400{% else %}tw:text-green-400{% endif %}">
|
||||
{{ qt.name }}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<c-msg.empty title="{% translate "Nothing to see here..." %}" remove-padding></c-msg.empty>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
25
app/templates/quick_transactions/pages/index.html
Normal file
25
app/templates/quick_transactions/pages/index.html
Normal file
@@ -0,0 +1,25 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% translate 'Quick Transactions' %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container px-md-3 py-3 column-gap-5">
|
||||
<div class="tw:text-3xl fw-bold font-monospace tw:w-full mb-3">
|
||||
{% spaceless %}
|
||||
<div>{% translate 'Quick Transactions' %}<span>
|
||||
<a class="text-decoration-none tw:text-2xl p-1 category-action"
|
||||
role="button"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-title="{% translate "Add" %}"
|
||||
hx-get="{% url 'quick_transaction_add' %}"
|
||||
hx-target="#generic-offcanvas">
|
||||
<i class="fa-solid fa-circle-plus fa-fw"></i></a>
|
||||
</span></div>
|
||||
{% endspaceless %}
|
||||
</div>
|
||||
|
||||
<div id="quick-transactions-table" class="show-loading" hx-get="{% url 'quick_transactions_list' %}" hx-trigger="load, updated from:window"></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,9 +1,9 @@
|
||||
{% load i18n %}
|
||||
<div class="container px-md-3 py-3 column-gap-5">
|
||||
<div class="tw-text-3xl fw-bold font-monospace tw-w-full mb-3">
|
||||
<div class="tw:text-3xl fw-bold font-monospace tw:w-full mb-3">
|
||||
{% spaceless %}
|
||||
<div>{% translate 'Recurring Transactions' %}<span>
|
||||
<a class="text-decoration-none tw-text-2xl p-1 category-action"
|
||||
<a class="text-decoration-none tw:text-2xl p-1 category-action"
|
||||
role="button"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-title="{% translate "Add" %}"
|
||||
|
||||
@@ -100,10 +100,10 @@
|
||||
</div>
|
||||
</td>
|
||||
<td class="col">
|
||||
<div class="{% if recurring_transaction.type == 'EX' %}tw-text-red-400{% else %}tw-text-green-400{% endif %}">
|
||||
<div class="{% if recurring_transaction.type == 'EX' %}tw:text-red-400{% else %}tw:text-green-400{% endif %}">
|
||||
{{ recurring_transaction.description }}
|
||||
</div>
|
||||
<div class="tw-text-sm tw-text-gray-400">{{ recurring_transaction.notes|linebreaksbr }}</div>
|
||||
<div class="tw:text-sm tw:text-gray-400">{{ recurring_transaction.notes|linebreaksbr }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{% load i18n %}
|
||||
<div class="container px-md-3 py-3 column-gap-5">
|
||||
<div class="tw-text-3xl fw-bold font-monospace tw-w-full mb-3">
|
||||
<div class="tw:text-3xl fw-bold font-monospace tw:w-full mb-3">
|
||||
{% spaceless %}
|
||||
<div>{% translate 'Rules' %}<span>
|
||||
<a class="text-decoration-none tw-text-2xl p-1 category-action"
|
||||
<a class="text-decoration-none tw:text-2xl p-1 category-action"
|
||||
role="button"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-title="{% translate "Add" %}"
|
||||
@@ -76,13 +76,13 @@
|
||||
data-bs-title="
|
||||
{% if rule.active %}{% translate "Deactivate" %}{% else %}{% translate "Activate" %}{% endif %}"
|
||||
hx-get="{% url 'transaction_rule_toggle_activity' transaction_rule_id=rule.id %}">
|
||||
{% if rule.active %}<i class="fa-solid fa-toggle-on tw-text-green-400"></i>{% else %}
|
||||
<i class="fa-solid fa-toggle-off tw-text-red-400"></i>{% endif %}
|
||||
{% if rule.active %}<i class="fa-solid fa-toggle-on tw:text-green-400"></i>{% else %}
|
||||
<i class="fa-solid fa-toggle-off tw:text-red-400"></i>{% endif %}
|
||||
</a>
|
||||
</td>
|
||||
<td class="col">
|
||||
<div>{{ rule.name }}</div>
|
||||
<div class="tw-text-gray-400">{{ rule.description }}</div>
|
||||
<div class="tw:text-gray-400">{{ rule.description }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
@@ -7,17 +7,17 @@
|
||||
{% block body %}
|
||||
<div hx-get="{% url 'transaction_rule_view' transaction_rule_id=transaction_rule.id %}"
|
||||
hx-trigger="updated from:window" hx-target="closest .offcanvas" class="show-loading">
|
||||
<div class="tw-text-2xl">{{ transaction_rule.name }}</div>
|
||||
<div class="tw-text-base tw-text-gray-400">{{ transaction_rule.description }}</div>
|
||||
<div class="tw:text-2xl">{{ transaction_rule.name }}</div>
|
||||
<div class="tw:text-base tw:text-gray-400">{{ transaction_rule.description }}</div>
|
||||
<hr>
|
||||
<div class="my-3">
|
||||
<div class="tw-text-xl mb-2">{% translate 'If transaction...' %}</div>
|
||||
<div class="tw:text-xl mb-2">{% translate 'If transaction...' %}</div>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
{{ transaction_rule.trigger }}
|
||||
</div>
|
||||
<div class="card-footer text-end">
|
||||
<a class="text-decoration-none tw-text-gray-400 p-1"
|
||||
<a class="text-decoration-none tw:text-gray-400 p-1"
|
||||
role="button"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-title="{% translate "Edit" %}"
|
||||
@@ -29,7 +29,7 @@
|
||||
</div>
|
||||
|
||||
<div class="my-3">
|
||||
<div class="tw-text-xl mb-2">{% translate 'Then...' %}</div>
|
||||
<div class="tw:text-xl mb-2">{% translate 'Then...' %}</div>
|
||||
{% for action in transaction_rule.transaction_actions.all %}
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
@@ -41,7 +41,7 @@
|
||||
<div class="text-bg-secondary rounded-3 mt-3 p-2">{{ action.value }}</div>
|
||||
</div>
|
||||
<div class="card-footer text-end">
|
||||
<a class="text-decoration-none tw-text-gray-400 p-1"
|
||||
<a class="text-decoration-none tw:text-gray-400 p-1"
|
||||
role="button"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-title="{% translate "Edit" %}"
|
||||
@@ -74,7 +74,7 @@
|
||||
<div>{% trans 'Edit to view' %}</div>
|
||||
</div>
|
||||
<div class="card-footer text-end">
|
||||
<a class="text-decoration-none tw-text-gray-400 p-1"
|
||||
<a class="text-decoration-none tw:text-gray-400 p-1"
|
||||
role="button"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-title="{% translate "Edit" %}"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{% load i18n %}
|
||||
<div class="container px-md-3 py-3 column-gap-5">
|
||||
<div class="tw-text-3xl fw-bold font-monospace tw-w-full mb-3">
|
||||
<div class="tw:text-3xl fw-bold font-monospace tw:w-full mb-3">
|
||||
{% spaceless %}
|
||||
<div>{% translate 'Tags' %}<span>
|
||||
<a class="text-decoration-none tw-text-2xl p-1 category-action"
|
||||
<a class="text-decoration-none tw:text-2xl p-1 category-action"
|
||||
role="button"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-title="{% translate "Add" %}"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
{% for x in transactions_by_date %}
|
||||
<div id="{{ x.grouper|slugify }}"
|
||||
_="on htmx:afterSettle from #transactions if sessionStorage.getItem(my id) is null then sessionStorage.setItem(my id, 'true')">
|
||||
<div class="mt-3 mb-1 w-100 tw-text-base border-bottom bg-body">
|
||||
<div class="mt-3 mb-1 w-100 tw:text-base border-bottom bg-body">
|
||||
<a class="text-decoration-none d-inline-block w-100"
|
||||
role="button"
|
||||
data-bs-toggle="collapse"
|
||||
@@ -47,7 +47,7 @@
|
||||
<nav aria-label="{% translate 'Page navigation' %}">
|
||||
<ul class="pagination justify-content-center mt-5">
|
||||
<li class="page-item">
|
||||
<a class="page-link tw-cursor-pointer {% if not page_obj.has_previous %}disabled{% endif %}"
|
||||
<a class="page-link tw:cursor-pointer {% if not page_obj.has_previous %}disabled{% endif %}"
|
||||
hx-get="{% if page_obj.has_previous %}{% url 'transactions_all_list' %}{% endif %}"
|
||||
hx-vals='{"page": 1}'
|
||||
hx-include="#filter, #order"
|
||||
@@ -68,13 +68,13 @@
|
||||
{% if page_number <= page_obj.number|add:3 and page_number >= page_obj.number|add:-3 %}
|
||||
{% if page_obj.number == page_number %}
|
||||
<li class="page-item active">
|
||||
<a class="page-link tw-cursor-pointer">
|
||||
<a class="page-link tw:cursor-pointer">
|
||||
{{ page_number }}
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item">
|
||||
<a class="page-link tw-cursor-pointer"
|
||||
<a class="page-link tw:cursor-pointer"
|
||||
hx-get="{% url 'transactions_all_list' %}"
|
||||
hx-vals='{"page": {{ page_number }}}'
|
||||
hx-include="#filter, #order"
|
||||
@@ -94,7 +94,7 @@
|
||||
</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link tw-cursor-pointer"
|
||||
<a class="page-link tw:cursor-pointer"
|
||||
hx-get="{% url 'transactions_all_list' %}" hx-target="#transactions-list"
|
||||
hx-vals='{"page": {{ page_obj.paginator.num_pages }}}'
|
||||
hx-include="#filter, #order"
|
||||
@@ -105,7 +105,7 @@
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="page-item">
|
||||
<a class="page-link {% if not page_obj.has_next %}disabled{% endif %} tw-cursor-pointer"
|
||||
<a class="page-link {% if not page_obj.has_next %}disabled{% endif %} tw:cursor-pointer"
|
||||
hx-get="{% if page_obj.has_next %}{% url 'transactions_all_list' %}{% endif %}"
|
||||
hx-vals='{"page": {{ page_obj.paginator.num_pages }}}'
|
||||
hx-include="#filter, #order"
|
||||
|
||||
@@ -28,10 +28,10 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-xl-6 order-2 order-xl-1">
|
||||
<div class="text-end tw-justify-end tw-flex tw-text-sm mb-3">
|
||||
<div class="tw-content-center" _="on change trigger updated on window">
|
||||
<div class="text-end tw:justify-end tw:flex tw:text-sm mb-3">
|
||||
<div class="tw:content-center" _="on change trigger updated on window">
|
||||
<label for="order">{% translate "Order by" %}</label>
|
||||
<select class="tw-border-0 focus-visible:tw-outline-0 w-full pe-2 tw-leading-normal text-bg-tertiary tw-font-medium rounded" name="order" id="order">
|
||||
<select class="tw:border-0 tw:focus-visible:outline-0 w-full pe-2 tw:leading-normal text-bg-tertiary tw:font-medium rounded bg-body text-body" name="order" id="order">
|
||||
<option value="default" {% if order == 'default' %}selected{% endif %}>{% translate 'Default' %}</option>
|
||||
<option value="older" {% if order == 'older' %}selected{% endif %}>{% translate 'Oldest first' %}</option>
|
||||
<option value="newer" {% if order == 'newer' %}selected{% endif %}>{% translate 'Newest first' %}</option>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="container px-md-3 py-3 column-gap-5">
|
||||
<div class="tw-text-3xl fw-bold font-monospace tw-w-full mb-3">
|
||||
<div class="tw:text-3xl fw-bold font-monospace tw:w-full mb-3">
|
||||
<div>{% translate 'Deleted transactions' %}</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{% load hijack %}
|
||||
{% load i18n %}
|
||||
<div class="container px-md-3 py-3 column-gap-5">
|
||||
<div class="tw-text-3xl fw-bold font-monospace tw-w-full mb-3">
|
||||
<div class="tw:text-3xl fw-bold font-monospace tw:w-full mb-3">
|
||||
{% spaceless %}
|
||||
<div>{% translate 'Users' %}<span>
|
||||
<a class="text-decoration-none tw-text-2xl p-1 category-action"
|
||||
<a class="text-decoration-none tw:text-2xl p-1 category-action"
|
||||
role="button"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-title="{% translate "Add" %}"
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
{% load i18n %}
|
||||
<i class="fa-solid fa-volume-xmark me-2 fa-fw"></i>{% translate 'Mute sounds' %}
|
||||
{{ user.settings.volume|json_script:"volume" }}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
{% load i18n %}
|
||||
<i class="fa-solid fa-volume-low me-2 fa-fw"></i>{% translate 'Play sounds' %}
|
||||
<span id="settings-mute-sound" class="d-inline tw-invisible"></span>
|
||||
<span id="settings-mute-sound" class="d-inline tw:invisible"></span>
|
||||
{{ user.settings.volume|json_script:"volume" }}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{% load i18n %}
|
||||
<i class="fa-solid fa-eye me-2 fa-fw"></i>{% translate 'Show amounts' %}
|
||||
<span id="settings-hide-amounts" class="d-inline tw-invisible"></span>
|
||||
<span id="settings-hide-amounts" class="d-inline tw:invisible"></span>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
{% load i18n %}
|
||||
{% load settings %}
|
||||
{% load crispy_forms_tags %}
|
||||
{% load socialaccount %}
|
||||
|
||||
{% block title %}Login{% endblock %}
|
||||
|
||||
@@ -25,6 +26,24 @@
|
||||
<div class="card-body">
|
||||
<h1 class="h2 card-title text-center mb-4">Login</h1>
|
||||
{% crispy form %}
|
||||
|
||||
{% get_providers as socialaccount_providers %}
|
||||
{% if socialaccount_providers %}
|
||||
<div class="mt-3">
|
||||
<hr>
|
||||
<ul class="socialaccount_providers list-unstyled">
|
||||
{% for provider in socialaccount_providers %}
|
||||
<li class="mt-2">
|
||||
<a title="{{ provider.name }}"
|
||||
class="btn btn-outline-primary w-100 socialaccount_provider {{ provider.id }}"
|
||||
href="{% provider_login_url provider %}">
|
||||
{% translate 'Login with' %} {{ provider.name }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,55 +12,56 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container px-md-3 py-3 column-gap-5">
|
||||
<div class="row mb-3 gx-xl-4 gy-3 mb-4">
|
||||
{# Date picker#}
|
||||
<div class="col-12 col-xl-2 flex-row align-items-center d-flex">
|
||||
<div class="tw-text-base h-100 align-items-center d-flex">
|
||||
<a role="button"
|
||||
class="pe-4 py-2"
|
||||
hx-boost="true"
|
||||
hx-trigger="click, previous_year from:window"
|
||||
href="{% url 'yearly_overview_account' year=previous_year %}">
|
||||
<i class="fa-solid fa-chevron-left"></i></a>
|
||||
<div class="container px-md-3 py-3 column-gap-5">
|
||||
<div class="row mb-3 gx-xl-4 gy-3 mb-4">
|
||||
{# Date picker#}
|
||||
<div class="col-12 col-xl-2 flex-row align-items-center d-flex">
|
||||
<div class="tw:text-base h-100 align-items-center d-flex">
|
||||
<a role="button"
|
||||
class="pe-4 py-2"
|
||||
hx-boost="true"
|
||||
hx-trigger="click, previous_year from:window"
|
||||
href="{% url 'yearly_overview_account' year=previous_year %}">
|
||||
<i class="fa-solid fa-chevron-left"></i></a>
|
||||
</div>
|
||||
<div class="tw:text-3xl fw-bold font-monospace tw:w-full text-center">
|
||||
{{ year }}
|
||||
</div>
|
||||
<div class="tw:text-base mx-2 h-100 align-items-center d-flex">
|
||||
<a role="button"
|
||||
class="ps-3 py-2"
|
||||
hx-boost="true"
|
||||
hx-trigger="click, next_year from:window"
|
||||
href="{% url 'yearly_overview_account' year=next_year %}">
|
||||
<i class="fa-solid fa-chevron-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tw-text-3xl fw-bold font-monospace tw-w-full text-center">
|
||||
{{ year }}
|
||||
</div>
|
||||
<div class="tw-text-base mx-2 h-100 align-items-center d-flex">
|
||||
<a role="button"
|
||||
class="ps-3 py-2"
|
||||
hx-boost="true"
|
||||
hx-trigger="click, next_year from:window"
|
||||
href="{% url 'yearly_overview_account' year=next_year %}">
|
||||
<i class="fa-solid fa-chevron-right"></i>
|
||||
</a>
|
||||
{# Action buttons#}
|
||||
<div class="col-12 col-xl-10">
|
||||
{# <c-ui.quick-transactions-buttons#}
|
||||
{# :year="year"#}
|
||||
{# :month="month"#}
|
||||
{# ></c-ui.quick-transactions-buttons>#}
|
||||
</div>
|
||||
</div>
|
||||
{# Action buttons#}
|
||||
<div class="col-12 col-xl-10">
|
||||
<c-ui.quick-transactions-buttons
|
||||
:year="year"
|
||||
:month="month"
|
||||
></c-ui.quick-transactions-buttons>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-2">
|
||||
<div class="nav flex-column nav-pills" id="month-pills" role="tablist" aria-orientation="vertical" hx-indicator="#data-content">
|
||||
<input type="hidden" name="month" value="">
|
||||
<button class="nav-link active"
|
||||
role="tab"
|
||||
data-bs-toggle="pill"
|
||||
hx-get="{% url 'yearly_overview_account_data' year=year %}"
|
||||
hx-target="#data-content"
|
||||
hx-trigger="click"
|
||||
hx-include="[name='account'], [name='month']"
|
||||
hx-swap="innerHTML"
|
||||
onclick="document.querySelector('[name=month]').value = ''">
|
||||
{% translate 'Year' %}
|
||||
</button>
|
||||
{% for month in months %}
|
||||
<div class="row">
|
||||
<div class="col-lg-2">
|
||||
<div class="nav flex-column nav-pills" id="month-pills" role="tablist" aria-orientation="vertical"
|
||||
hx-indicator="#data-content">
|
||||
<input type="hidden" name="month" value="">
|
||||
<button class="nav-link active"
|
||||
role="tab"
|
||||
data-bs-toggle="pill"
|
||||
hx-get="{% url 'yearly_overview_account_data' year=year %}"
|
||||
hx-target="#data-content"
|
||||
hx-trigger="click"
|
||||
hx-include="[name='account'], [name='month']"
|
||||
hx-swap="innerHTML"
|
||||
onclick="document.querySelector('[name=month]').value = ''">
|
||||
{% translate 'Year' %}
|
||||
</button>
|
||||
{% for month in months %}
|
||||
<button class="nav-link"
|
||||
role="tab"
|
||||
data-bs-toggle="pill"
|
||||
@@ -70,28 +71,29 @@
|
||||
hx-include="[name='account'], [name='month']"
|
||||
hx-swap="innerHTML"
|
||||
onclick="document.querySelector('[name=month]').value = '{{ month }}'">
|
||||
{{ month|month_name }}
|
||||
{{ month|month_name }}
|
||||
</button>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4 d-block d-lg-none">
|
||||
<div class="col-lg-3">
|
||||
<div class="nav flex-column nav-pills" id="currency-pills" role="tablist" aria-orientation="vertical" hx-indicator="#data-content">
|
||||
<input type="hidden" name="account" value="">
|
||||
<button class="nav-link active"
|
||||
role="tab"
|
||||
data-bs-toggle="pill"
|
||||
hx-get="{% url 'yearly_overview_account_data' year=year %}"
|
||||
hx-target="#data-content"
|
||||
hx-trigger="click"
|
||||
hx-include="[name='account'], [name='month']"
|
||||
hx-swap="innerHTML"
|
||||
onclick="document.querySelector('[name=account]').value = ''">
|
||||
{% translate 'All' %}
|
||||
</button>
|
||||
{% for account in accounts %}
|
||||
<hr class="my-4 d-block d-lg-none">
|
||||
<div class="col-lg-3">
|
||||
<div class="nav flex-column nav-pills" id="currency-pills" role="tablist" aria-orientation="vertical"
|
||||
hx-indicator="#data-content">
|
||||
<input type="hidden" name="account" value="">
|
||||
<button class="nav-link active"
|
||||
role="tab"
|
||||
data-bs-toggle="pill"
|
||||
hx-get="{% url 'yearly_overview_account_data' year=year %}"
|
||||
hx-target="#data-content"
|
||||
hx-trigger="click"
|
||||
hx-include="[name='account'], [name='month']"
|
||||
hx-swap="innerHTML"
|
||||
onclick="document.querySelector('[name=account]').value = ''">
|
||||
{% translate 'All' %}
|
||||
</button>
|
||||
{% for account in accounts %}
|
||||
<button class="nav-link"
|
||||
role="tab"
|
||||
data-bs-toggle="pill"
|
||||
@@ -101,21 +103,22 @@
|
||||
hx-include="[name='account'], [name='month']"
|
||||
hx-swap="innerHTML"
|
||||
onclick="document.querySelector('[name=account]').value = '{{ account.id }}'">
|
||||
<span class="badge text-bg-primary me-2">{{ account.group.name }}</span>{{ account.name }}
|
||||
<span class="badge text-bg-primary me-2">{{ account.group.name }}</span>{{ account.name }}
|
||||
</button>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-7">
|
||||
<div class="col-lg-7">
|
||||
<div id="data-content"
|
||||
class="show-loading"
|
||||
hx-get="{% url 'yearly_overview_account_data' year=year %}"
|
||||
hx-trigger="load"
|
||||
hx-trigger="load, every 10m"
|
||||
hx-include="[name='account'], [name='month']"
|
||||
hx-swap="innerHTML">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<c-ui.transactions_fab></c-ui.transactions_fab>
|
||||
{% endblock %}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user