Compare commits

..

18 Commits
0.1.8 ... 0.3.0

Author SHA1 Message Date
Herculino Trotta
2f6c396eaf Merge pull request #24
fix(transactions:action-bar): sum button not copying correctly
2025-01-05 15:20:24 -03:00
Herculino Trotta
d12b920e54 fix(transactions:action-bar): sum button not copying correctly 2025-01-05 15:19:58 -03:00
Herculino Trotta
9edbf7bd5a Merge pull request #23
feat(transactions:action-bar): add more math options in a dropdown
2025-01-05 14:36:07 -03:00
Herculino Trotta
dbd3eea29a locale(pt-BR): update translation 2025-01-05 14:35:33 -03:00
Herculino Trotta
881fed1895 feat(transactions:action-bar): add more math options in a dropdown 2025-01-05 14:35:23 -03:00
Herculino Trotta
10a0ac42a2 Merge pull request #22
feat(api): add RecurringTransaction and InstallmentPlan endpoints
2025-01-05 11:14:03 -03:00
Herculino Trotta
1b47c12a22 feat(api): add RecurringTransaction and InstallmentPlan endpoints 2025-01-05 11:13:23 -03:00
Herculino Trotta
091f73bf8d feat(api): support string name and ids for installmentplan endpoint 2025-01-05 11:07:38 -03:00
Herculino Trotta
73fe17de64 feat(api): add auth permission to all api endpoint 2025-01-05 11:04:50 -03:00
Herculino Trotta
52af1b2260 Merge pull request #21
feat(api): add API endpoints to add DCA entries and strategies
2025-01-05 10:54:55 -03:00
Herculino Trotta
8efa087aee feat(api): add API endpoints to add DCA entries and strategies 2025-01-05 10:54:31 -03:00
Herculino Trotta
6f69f15474 Merge pull request #20
feat: archived tabs for categories, tags and entities
2025-01-05 01:46:01 -03:00
Herculino Trotta
905e80cffe fix: overflowing empty message 2025-01-05 01:45:11 -03:00
Herculino Trotta
baae6bb96a feat(entities): add tab to show archived entities 2025-01-05 01:43:24 -03:00
Herculino Trotta
f5132e24bd feat(tags): add tab to show archived tags 2025-01-05 01:36:30 -03:00
Herculino Trotta
41303f39a0 fix: typo 2025-01-05 01:35:34 -03:00
Herculino Trotta
0fc8b0ee49 feat(tags): add tab to show archived tags 2025-01-05 01:35:25 -03:00
Herculino Trotta
037014d024 feat(categories): add tab to show archived categories 2025-01-05 01:22:14 -03:00
24 changed files with 836 additions and 254 deletions

View File

@@ -1,3 +1,4 @@
from .transactions import *
from .accounts import *
from .currencies import *
from .dca import *

View File

@@ -1,4 +1,5 @@
from rest_framework import serializers
from rest_framework.permissions import IsAuthenticated
from apps.api.serializers.currencies import CurrencySerializer
from apps.accounts.models import AccountGroup, Account
@@ -6,6 +7,8 @@ from apps.currencies.models import Currency
class AccountGroupSerializer(serializers.ModelSerializer):
permission_classes = [IsAuthenticated]
class Meta:
model = AccountGroup
fields = "__all__"
@@ -31,6 +34,8 @@ class AccountSerializer(serializers.ModelSerializer):
allow_null=True,
)
permission_classes = [IsAuthenticated]
class Meta:
model = Account
fields = [

View File

@@ -1,8 +1,12 @@
from rest_framework import serializers
from rest_framework.permissions import IsAuthenticated
from apps.currencies.models import Currency, ExchangeRate
class CurrencySerializer(serializers.ModelSerializer):
permission_classes = [IsAuthenticated]
class Meta:
model = Currency
fields = "__all__"
@@ -24,6 +28,8 @@ class ExchangeRateSerializer(serializers.ModelSerializer):
queryset=Currency.objects.all(), source="to_currency", write_only=True
)
permission_classes = [IsAuthenticated]
class Meta:
model = ExchangeRate
fields = "__all__"

View File

@@ -0,0 +1,85 @@
from rest_framework import serializers
from rest_framework.permissions import IsAuthenticated
from apps.dca.models import DCAEntry, DCAStrategy
class DCAEntrySerializer(serializers.ModelSerializer):
profit_loss = serializers.DecimalField(
max_digits=42, decimal_places=30, read_only=True
)
profit_loss_percentage = serializers.DecimalField(
max_digits=42, decimal_places=30, read_only=True
)
current_value = serializers.DecimalField(
max_digits=42, decimal_places=30, read_only=True
)
entry_price = serializers.DecimalField(
max_digits=42, decimal_places=30, read_only=True
)
permission_classes = [IsAuthenticated]
class Meta:
model = DCAEntry
fields = [
"id",
"strategy",
"date",
"amount_paid",
"amount_received",
"notes",
"created_at",
"updated_at",
"profit_loss",
"profit_loss_percentage",
"current_value",
"entry_price",
]
read_only_fields = ["created_at", "updated_at"]
class DCAStrategySerializer(serializers.ModelSerializer):
entries = DCAEntrySerializer(many=True, read_only=True)
total_invested = serializers.DecimalField(
max_digits=42, decimal_places=30, read_only=True
)
total_received = serializers.DecimalField(
max_digits=42, decimal_places=30, read_only=True
)
average_entry_price = serializers.DecimalField(
max_digits=42, decimal_places=30, read_only=True
)
total_entries = serializers.IntegerField(read_only=True)
current_total_value = serializers.DecimalField(
max_digits=42, decimal_places=30, read_only=True
)
total_profit_loss = serializers.DecimalField(
max_digits=42, decimal_places=30, read_only=True
)
total_profit_loss_percentage = serializers.DecimalField(
max_digits=42, decimal_places=30, read_only=True
)
permission_classes = [IsAuthenticated]
class Meta:
model = DCAStrategy
fields = [
"id",
"name",
"target_currency",
"payment_currency",
"notes",
"created_at",
"updated_at",
"entries",
"total_invested",
"total_received",
"average_entry_price",
"total_entries",
"current_total_value",
"total_profit_loss",
"total_profit_loss_percentage",
]
read_only_fields = ["created_at", "updated_at"]

View File

@@ -19,6 +19,7 @@ from apps.transactions.models import (
TransactionTag,
InstallmentPlan,
TransactionEntity,
RecurringTransaction,
)
@@ -47,11 +48,77 @@ class TransactionEntitySerializer(serializers.ModelSerializer):
class InstallmentPlanSerializer(serializers.ModelSerializer):
category = TransactionCategoryField(required=False)
tags = TransactionTagField(required=False)
entities = TransactionEntityField(required=False)
permission_classes = [IsAuthenticated]
class Meta:
model = InstallmentPlan
fields = "__all__"
fields = [
"id",
"account",
"type",
"description",
"number_of_installments",
"installment_start",
"installment_total_number",
"start_date",
"reference_date",
"end_date",
"recurrence",
"installment_amount",
"category",
"tags",
"entities",
"notes",
]
read_only_fields = ["installment_total_number", "end_date"]
def create(self, validated_data):
instance = super().create(validated_data)
instance.create_transactions()
return instance
def update(self, instance, validated_data):
instance = super().update(instance, validated_data)
instance.update_transactions()
return instance
class RecurringTransactionSerializer(serializers.ModelSerializer):
category = TransactionCategoryField(required=False)
tags = TransactionTagField(required=False)
entities = TransactionEntityField(required=False)
class Meta:
model = RecurringTransaction
fields = [
"id",
"is_paused",
"account",
"type",
"amount",
"description",
"category",
"tags",
"entities",
"notes",
"reference_date",
"start_date",
"end_date",
"recurrence_type",
"recurrence_interval",
"last_generated_date",
"last_generated_reference_date",
]
read_only_fields = ["last_generated_date", "last_generated_reference_date"]
def create(self, validated_data):
instance = super().create(validated_data)
instance.create_upcoming_transactions()
return instance
class TransactionSerializer(serializers.ModelSerializer):

View File

@@ -9,10 +9,13 @@ router.register(r"categories", views.TransactionCategoryViewSet)
router.register(r"tags", views.TransactionTagViewSet)
router.register(r"entities", views.TransactionEntityViewSet)
router.register(r"installment-plans", views.InstallmentPlanViewSet)
router.register(r"recurring-transactions", views.RecurringTransactionViewSet)
router.register(r"account-groups", views.AccountGroupViewSet)
router.register(r"accounts", views.AccountViewSet)
router.register(r"currencies", views.CurrencyViewSet)
router.register(r"exchange-rates", views.ExchangeRateViewSet)
router.register(r"dca/strategies", views.DCAStrategyViewSet)
router.register(r"dca/entries", views.DCAEntryViewSet)
urlpatterns = [
path("", include(router.urls)),

View File

@@ -1,3 +1,4 @@
from .transactions import *
from .accounts import *
from .currencies import *
from .dca import *

41
app/apps/api/views/dca.py Normal file
View File

@@ -0,0 +1,41 @@
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from apps.dca.models import DCAStrategy, DCAEntry
from apps.api.serializers import DCAStrategySerializer, DCAEntrySerializer
class DCAStrategyViewSet(viewsets.ModelViewSet):
queryset = DCAStrategy.objects.all()
serializer_class = DCAStrategySerializer
@action(detail=True, methods=["get"])
def investment_frequency(self, request, pk=None):
strategy = self.get_object()
return Response(strategy.investment_frequency_data())
@action(detail=True, methods=["get"])
def price_comparison(self, request, pk=None):
strategy = self.get_object()
return Response(strategy.price_comparison_data())
@action(detail=True, methods=["get"])
def current_price(self, request, pk=None):
strategy = self.get_object()
price_data = strategy.current_price()
if price_data:
price, date = price_data
return Response({"price": price, "date": date})
return Response({"price": None, "date": None})
class DCAEntryViewSet(viewsets.ModelViewSet):
queryset = DCAEntry.objects.all()
serializer_class = DCAEntrySerializer
def get_queryset(self):
queryset = DCAEntry.objects.all()
strategy_id = self.request.query_params.get("strategy", None)
if strategy_id is not None:
queryset = queryset.filter(strategy_id=strategy_id)
return queryset

View File

@@ -1,4 +1,4 @@
from rest_framework import permissions, viewsets
from rest_framework import viewsets
from apps.api.serializers import (
TransactionSerializer,
@@ -6,6 +6,7 @@ from apps.api.serializers import (
TransactionTagSerializer,
InstallmentPlanSerializer,
TransactionEntitySerializer,
RecurringTransactionSerializer,
)
from apps.transactions.models import (
Transaction,
@@ -13,6 +14,7 @@ from apps.transactions.models import (
TransactionTag,
InstallmentPlan,
TransactionEntity,
RecurringTransaction,
)
from apps.rules.signals import transaction_updated, transaction_created
@@ -53,10 +55,7 @@ class InstallmentPlanViewSet(viewsets.ModelViewSet):
queryset = InstallmentPlan.objects.all()
serializer_class = InstallmentPlanSerializer
def perform_create(self, serializer):
instance = serializer.save()
instance.create_transactions()
def perform_update(self, serializer):
instance = serializer.save()
instance.create_transactions()
class RecurringTransactionViewSet(viewsets.ModelViewSet):
queryset = RecurringTransaction.objects.all()
serializer_class = RecurringTransactionSerializer

View File

@@ -53,6 +53,8 @@ urlpatterns = [
),
path("tags/", views.tags_index, name="tags_index"),
path("tags/list/", views.tags_list, name="tags_list"),
path("tags/table/active/", views.tags_table_active, name="tags_table_active"),
path("tags/table/archived/", views.tags_table_archived, name="tags_table_archived"),
path("tags/add/", views.tag_add, name="tag_add"),
path(
"tags/<int:tag_id>/edit/",
@@ -66,6 +68,16 @@ urlpatterns = [
),
path("entities/", views.entities_index, name="entities_index"),
path("entities/list/", views.entities_list, name="entities_list"),
path(
"entities/table/active/",
views.entities_table_active,
name="entities_table_active",
),
path(
"entities/table/archived/",
views.entities_table_archived,
name="entities_table_archived",
),
path("entities/add/", views.entity_add, name="entity_add"),
path(
"entities/<int:entity_id>/edit/",
@@ -79,6 +91,16 @@ urlpatterns = [
),
path("categories/", views.categories_index, name="categories_index"),
path("categories/list/", views.categories_list, name="categories_list"),
path(
"categories/table/active/",
views.categories_table_active,
name="categories_table_active",
),
path(
"categories/table/archived/",
views.categories_table_archived,
name="categories_table_archived",
),
path("categories/add/", views.category_add, name="category_add"),
path(
"categories/<int:category_id>/edit/",

View File

@@ -25,11 +25,33 @@ def categories_index(request):
@login_required
@require_http_methods(["GET"])
def categories_list(request):
categories = TransactionCategory.objects.all().order_by("id")
return render(
request,
"categories/fragments/list.html",
{"categories": categories},
)
@only_htmx
@login_required
@require_http_methods(["GET"])
def categories_table_active(request):
categories = TransactionCategory.objects.filter(active=True).order_by("id")
return render(
request,
"categories/fragments/table.html",
{"categories": categories, "active": True},
)
@only_htmx
@login_required
@require_http_methods(["GET"])
def categories_table_archived(request):
categories = TransactionCategory.objects.filter(active=False).order_by("id")
return render(
request,
"categories/fragments/table.html",
{"categories": categories, "active": False},
)

View File

@@ -24,11 +24,33 @@ def entities_index(request):
@login_required
@require_http_methods(["GET"])
def entities_list(request):
entities = TransactionEntity.objects.all().order_by("id")
return render(
request,
"entities/fragments/list.html",
{"entities": entities},
)
@only_htmx
@login_required
@require_http_methods(["GET"])
def entities_table_active(request):
entities = TransactionEntity.objects.filter(active=True).order_by("id")
return render(
request,
"entities/fragments/table.html",
{"entities": entities, "active": True},
)
@only_htmx
@login_required
@require_http_methods(["GET"])
def entities_table_archived(request):
entities = TransactionEntity.objects.filter(active=False).order_by("id")
return render(
request,
"entities/fragments/table.html",
{"entities": entities, "active": False},
)

View File

@@ -24,11 +24,33 @@ def tags_index(request):
@login_required
@require_http_methods(["GET"])
def tags_list(request):
tags = TransactionTag.objects.all().order_by("id")
return render(
request,
"tags/fragments/list.html",
{"tags": tags},
)
@only_htmx
@login_required
@require_http_methods(["GET"])
def tags_table_active(request):
tags = TransactionTag.objects.filter(active=True).order_by("id")
return render(
request,
"tags/fragments/table.html",
{"tags": tags, "active": True},
)
@only_htmx
@login_required
@require_http_methods(["GET"])
def tags_table_archived(request):
tags = TransactionTag.objects.filter(active=False).order_by("id")
return render(
request,
"tags/fragments/table.html",
{"tags": tags, "active": False},
)

View File

@@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-04 21:19+0000\n"
"PO-Revision-Date: 2025-01-04 18:22-0300\n"
"POT-Creation-Date: 2025-01-05 17:33+0000\n"
"PO-Revision-Date: 2025-01-05 14:35-0300\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: pt_BR\n"
@@ -85,12 +85,13 @@ msgstr "Tags"
#: apps/transactions/models.py:39 apps/transactions/models.py:58
#: templates/account_groups/fragments/list.html:25
#: templates/accounts/fragments/list.html:25
#: templates/categories/fragments/list.html:25
#: templates/categories/fragments/table.html:16
#: templates/currencies/fragments/list.html:26
#: templates/entities/fragments/list.html:25
#: templates/entities/fragments/table.html:16
#: templates/installment_plans/fragments/table.html:16
#: templates/recurring_transactions/fragments/table.html:18
#: templates/rules/fragments/list.html:26 templates/tags/fragments/list.html:25
#: templates/rules/fragments/list.html:26
#: templates/tags/fragments/table.html:16
msgid "Name"
msgstr "Nome"
@@ -130,6 +131,9 @@ msgstr ""
"mês."
#: apps/accounts/models.py:54 templates/accounts/fragments/list.html:30
#: templates/categories/fragments/list.html:24
#: templates/entities/fragments/list.html:24
#: templates/tags/fragments/list.html:24
msgid "Archived"
msgstr "Arquivada"
@@ -213,7 +217,7 @@ msgstr "Entidade com esse ID não existe."
msgid "Invalid entity data. Provide an ID or name."
msgstr "Dados da entidade inválidos. Forneça um ID ou nome."
#: apps/api/serializers/transactions.py:96
#: apps/api/serializers/transactions.py:163
msgid "Either 'date' or 'reference_date' must be provided."
msgstr "É necessário fornecer “date” ou “reference_date”."
@@ -682,8 +686,10 @@ msgid "Mute"
msgstr "Silenciada"
#: apps/transactions/models.py:23 apps/transactions/models.py:42
#: apps/transactions/models.py:61
#: apps/transactions/models.py:61 templates/categories/fragments/list.html:21
#: templates/entities/fragments/list.html:21
#: templates/recurring_transactions/fragments/list.html:21
#: templates/tags/fragments/list.html:21
msgid "Active"
msgstr "Ativo"
@@ -867,27 +873,27 @@ msgstr "%(value)s tem muitas casas decimais. O máximo é 30."
msgid "%(value)s is not a non-negative number"
msgstr "%(value)s não é um número positivo"
#: apps/transactions/views/categories.py:44
#: apps/transactions/views/categories.py:66
msgid "Category added successfully"
msgstr "Categoria adicionada com sucesso"
#: apps/transactions/views/categories.py:72
#: apps/transactions/views/categories.py:94
msgid "Category updated successfully"
msgstr "Categoria atualizada com sucesso"
#: apps/transactions/views/categories.py:99
#: apps/transactions/views/categories.py:121
msgid "Category deleted successfully"
msgstr "Categoria apagada com sucesso"
#: apps/transactions/views/entities.py:43
#: apps/transactions/views/entities.py:65
msgid "Entity added successfully"
msgstr "Entidade adicionada com sucesso"
#: apps/transactions/views/entities.py:71
#: apps/transactions/views/entities.py:93
msgid "Entity updated successfully"
msgstr "Entidade atualizada com sucesso"
#: apps/transactions/views/entities.py:98
#: apps/transactions/views/entities.py:120
msgid "Entity deleted successfully"
msgstr "Entidade apagada com sucesso"
@@ -931,15 +937,15 @@ msgstr "Transação Recorrente finalizada com sucesso"
msgid "Recurring Transaction deleted successfully"
msgstr "Transação Recorrente apagada com sucesso"
#: apps/transactions/views/tags.py:43
#: apps/transactions/views/tags.py:65
msgid "Tag added successfully"
msgstr "Tag adicionada com sucesso"
#: apps/transactions/views/tags.py:71
#: apps/transactions/views/tags.py:93
msgid "Tag updated successfully"
msgstr "Tag atualizada com sucesso"
#: apps/transactions/views/tags.py:98
#: apps/transactions/views/tags.py:120
msgid "Tag deleted successfully"
msgstr "Tag apagada com sucesso"
@@ -1065,62 +1071,63 @@ msgstr "Editar grupo de conta"
#: templates/account_groups/fragments/list.html:32
#: templates/accounts/fragments/list.html:37
#: templates/categories/fragments/list.html:33
#: templates/categories/fragments/table.html:24
#: templates/currencies/fragments/list.html:33
#: templates/dca/fragments/strategy/details.html:63
#: templates/entities/fragments/list.html:32
#: templates/entities/fragments/table.html:23
#: templates/exchange_rates/fragments/table.html:19
#: templates/installment_plans/fragments/table.html:23
#: templates/recurring_transactions/fragments/table.html:25
#: templates/rules/fragments/list.html:33 templates/tags/fragments/list.html:32
#: templates/rules/fragments/list.html:33
#: templates/tags/fragments/table.html:23
msgid "Actions"
msgstr "Ações"
#: templates/account_groups/fragments/list.html:36
#: templates/accounts/fragments/list.html:41
#: templates/categories/fragments/list.html:37
#: templates/categories/fragments/table.html:29
#: templates/cotton/transaction/item.html:109
#: templates/currencies/fragments/list.html:37
#: templates/dca/fragments/strategy/details.html:67
#: templates/dca/fragments/strategy/list.html:34
#: templates/entities/fragments/list.html:36
#: templates/entities/fragments/table.html:28
#: templates/exchange_rates/fragments/table.html:23
#: templates/installment_plans/fragments/table.html:27
#: templates/recurring_transactions/fragments/table.html:29
#: templates/rules/fragments/transaction_rule/view.html:22
#: templates/rules/fragments/transaction_rule/view.html:48
#: templates/tags/fragments/list.html:36
#: templates/tags/fragments/table.html:28
msgid "Edit"
msgstr "Editar"
#: templates/account_groups/fragments/list.html:43
#: templates/accounts/fragments/list.html:48
#: templates/categories/fragments/list.html:44
#: templates/categories/fragments/table.html:36
#: templates/cotton/transaction/item.html:116
#: templates/cotton/ui/transactions_action_bar.html:50
#: templates/currencies/fragments/list.html:44
#: templates/dca/fragments/strategy/details.html:75
#: templates/dca/fragments/strategy/list.html:42
#: templates/entities/fragments/list.html:43
#: templates/entities/fragments/table.html:36
#: templates/exchange_rates/fragments/table.html:31
#: templates/installment_plans/fragments/table.html:56
#: templates/mini_tools/unit_price_calculator.html:18
#: templates/recurring_transactions/fragments/table.html:91
#: templates/rules/fragments/list.html:44
#: templates/rules/fragments/transaction_rule/view.html:56
#: templates/tags/fragments/list.html:43
#: templates/tags/fragments/table.html:36
msgid "Delete"
msgstr "Apagar"
#: templates/account_groups/fragments/list.html:47
#: templates/accounts/fragments/list.html:52
#: templates/categories/fragments/list.html:48
#: templates/categories/fragments/table.html:41
#: templates/cotton/transaction/item.html:120
#: templates/cotton/ui/transactions_action_bar.html:52
#: templates/currencies/fragments/list.html:48
#: templates/dca/fragments/strategy/details.html:80
#: templates/dca/fragments/strategy/list.html:46
#: templates/entities/fragments/list.html:47
#: templates/entities/fragments/table.html:40
#: templates/exchange_rates/fragments/table.html:36
#: templates/installment_plans/fragments/table.html:48
#: templates/installment_plans/fragments/table.html:60
@@ -1130,40 +1137,40 @@ msgstr "Apagar"
#: templates/recurring_transactions/fragments/table.html:96
#: templates/rules/fragments/list.html:48
#: templates/rules/fragments/transaction_rule/view.html:60
#: templates/tags/fragments/list.html:47
#: templates/tags/fragments/table.html:40
msgid "Are you sure?"
msgstr "Tem certeza?"
#: templates/account_groups/fragments/list.html:48
#: templates/accounts/fragments/list.html:53
#: templates/categories/fragments/list.html:49
#: templates/categories/fragments/table.html:42
#: templates/cotton/transaction/item.html:121
#: templates/cotton/ui/transactions_action_bar.html:53
#: templates/currencies/fragments/list.html:49
#: templates/dca/fragments/strategy/details.html:81
#: templates/dca/fragments/strategy/list.html:47
#: templates/entities/fragments/list.html:48
#: templates/entities/fragments/table.html:41
#: templates/exchange_rates/fragments/table.html:37
#: templates/rules/fragments/list.html:49
#: templates/rules/fragments/transaction_rule/view.html:61
#: templates/tags/fragments/list.html:48
#: templates/tags/fragments/table.html:41
msgid "You won't be able to revert this!"
msgstr "Você não será capaz de reverter isso!"
#: templates/account_groups/fragments/list.html:49
#: templates/accounts/fragments/list.html:54
#: templates/categories/fragments/list.html:50
#: templates/categories/fragments/table.html:43
#: templates/cotton/transaction/item.html:122
#: templates/currencies/fragments/list.html:50
#: templates/dca/fragments/strategy/details.html:82
#: templates/dca/fragments/strategy/list.html:48
#: templates/entities/fragments/list.html:49
#: templates/entities/fragments/table.html:42
#: templates/exchange_rates/fragments/table.html:38
#: templates/installment_plans/fragments/table.html:62
#: templates/recurring_transactions/fragments/table.html:98
#: templates/rules/fragments/list.html:50
#: templates/rules/fragments/transaction_rule/view.html:62
#: templates/tags/fragments/list.html:49
#: templates/tags/fragments/table.html:42
msgid "Yes, delete it!"
msgstr "Sim, apague!"
@@ -1273,11 +1280,11 @@ msgstr "Adicionar categoria"
msgid "Edit category"
msgstr "Editar categoria"
#: templates/categories/fragments/list.html:26
#: templates/categories/fragments/table.html:17
msgid "Muted"
msgstr "Silenciada"
#: templates/categories/fragments/list.html:63
#: templates/categories/fragments/table.html:57
msgid "No categories"
msgstr "Nenhum categoria"
@@ -1337,10 +1344,44 @@ msgstr "Marcar como não pago"
msgid "Yes, delete them!"
msgstr "Sim, apague!"
#: templates/cotton/ui/transactions_action_bar.html:81
#: templates/cotton/ui/transactions_action_bar.html:127
#: templates/cotton/ui/transactions_action_bar.html:149
#: templates/cotton/ui/transactions_action_bar.html:169
#: templates/cotton/ui/transactions_action_bar.html:189
#: templates/cotton/ui/transactions_action_bar.html:209
#: templates/cotton/ui/transactions_action_bar.html:229
#: templates/cotton/ui/transactions_action_bar.html:249
msgid "copied!"
msgstr "copiado!"
#: templates/cotton/ui/transactions_action_bar.html:134
msgid "Toggle Dropdown"
msgstr "Alternar menu suspenso"
#: templates/cotton/ui/transactions_action_bar.html:142
msgid "Flat Total"
msgstr "Total Fixo"
#: templates/cotton/ui/transactions_action_bar.html:162
msgid "Real Total"
msgstr "Total Real"
#: templates/cotton/ui/transactions_action_bar.html:182
msgid "Mean"
msgstr "Média"
#: templates/cotton/ui/transactions_action_bar.html:202
msgid "Max"
msgstr "Máximo"
#: templates/cotton/ui/transactions_action_bar.html:222
msgid "Min"
msgstr "Minímo"
#: templates/cotton/ui/transactions_action_bar.html:242
msgid "Count"
msgstr "Contagem"
#: templates/currencies/fragments/add.html:5
msgid "Add currency"
msgstr "Adicionar moeda"
@@ -1479,7 +1520,7 @@ msgstr "Adicionar entidade"
msgid "Edit entity"
msgstr "Editar entidade"
#: templates/entities/fragments/list.html:59
#: templates/entities/fragments/table.html:53
msgid "No entities"
msgstr "Sem entidades"
@@ -1884,7 +1925,7 @@ msgstr "Adicionar tag"
msgid "Edit tag"
msgstr "Editar tag"
#: templates/tags/fragments/list.html:59
#: templates/tags/fragments/table.html:53
msgid "No tags"
msgstr "Nenhuma tag"

View File

@@ -15,53 +15,18 @@
</div>
<div class="card">
<div class="card-body table-responsive">
{% if categories %}
<c-config.search></c-config.search>
<table class="table table-hover">
<thead>
<tr>
<th scope="col" class="col-auto"></th>
<th scope="col" class="col">{% translate 'Name' %}</th>
<th scope="col" class="col">{% translate 'Muted' %}</th>
</tr>
</thead>
<tbody>
{% for category in categories %}
<tr class="category">
<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 'category_edit' category_id=category.id %}"
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 'category_delete' category_id=category.id %}"
hx-trigger='confirmed'
data-bypass-on-ctrl="true"
data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}"
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">{{ category.name }}</td>
<td class="col">
{% if category.mute %}<i class="fa-solid fa-check text-success"></i>{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<c-msg.empty title="{% translate "No categories" %}" remove-padding></c-msg.empty>
{% endif %}
<div class="card-header">
<ul class="nav nav-pills card-header-pills" id="myTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" data-bs-toggle="tab" type="button" role="tab" aria-selected="true" hx-get="{% url 'categories_table_active' %}" hx-trigger="load, click" hx-target="#categories-table">{% translate 'Active' %}</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" hx-get="{% url 'categories_table_archived' %}" hx-target="#categories-table" data-bs-toggle="tab" type="button" role="tab" aria-selected="false">{% translate 'Archived' %}</button>
</li>
</ul>
</div>
<div class="card-body">
<div id="categories-table"></div>
</div>
</div>
</div>

View File

@@ -0,0 +1,59 @@
{% load i18n %}
{% if active %}
<div class="show-loading" hx-get="{% url 'categories_table_active' %}" hx-trigger="updated from:window"
hx-swap="outerHTML">
{% else %}
<div class="show-loading" hx-get="{% url 'categories_table_archived' %}" hx-trigger="updated from:window"
hx-swap="outerHTML">
{% endif %}
{% if categories %}
<div class="table-responsive">
<c-config.search></c-config.search>
<table class="table table-hover">
<thead>
<tr>
<th scope="col" class="col-auto"></th>
<th scope="col" class="col">{% translate 'Name' %}</th>
<th scope="col" class="col">{% translate 'Muted' %}</th>
</tr>
</thead>
<tbody>
{% for category in categories %}
<tr class="category">
<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"
hx-swap="innerHTML"
data-bs-title="{% translate "Edit" %}"
hx-get="{% url 'category_edit' category_id=category.id %}"
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 'category_delete' category_id=category.id %}"
hx-trigger='confirmed'
hx-swap="innerHTML"
data-bypass-on-ctrl="true"
data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}"
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">{{ category.name }}</td>
<td class="col">
{% if category.mute %}<i class="fa-solid fa-check text-success"></i>{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<c-msg.empty title="{% translate "No categories" %}" remove-padding></c-msg.empty>
{% endif %}
</div>

View File

@@ -4,5 +4,5 @@
{% block title %}{% translate 'Categories' %}{% endblock %}
{% block content %}
<div hx-get="{% url 'categories_list' %}" hx-trigger="load, updated from:window" class="show-loading"></div>
<div hx-get="{% url 'categories_list' %}" hx-trigger="load" class="show-loading"></div>
{% endblock %}

View File

@@ -11,78 +11,251 @@
<div class="card slide-in-left">
<div class="card-body p-2">
{% spaceless %}
<div class="btn-group" role="group">
<div class="btn-group" role="group">
<button class="btn btn-secondary btn-sm"
data-bs-toggle="tooltip"
data-bs-title="{% translate 'Select All' %}"
_="on click set <#transactions-list input[type='checkbox']/>'s checked to true then call me.blur() then trigger change">
<i class="fa-regular fa-square-check tw-text-green-400"></i>
</button>
<button class="btn btn-secondary btn-sm"
data-bs-toggle="tooltip"
data-bs-title="{% translate 'Unselect All' %}"
_="on click set <#transactions-list input[type='checkbox']/>'s checked to false then call me.blur() then trigger change">
<i class="fa-regular fa-square tw-text-red-400"></i>
</button>
</div>
<div class="vr mx-3 tw-align-middle"></div>
<div class="btn-group me-3" role="group">
<button class="btn btn-secondary btn-sm"
hx-get="{% url 'transactions_bulk_pay' %}"
hx-include=".transaction"
data-bs-toggle="tooltip"
data-bs-title="{% translate 'Mark as paid' %}">
<i class="fa-regular fa-circle-check tw-text-green-400"></i>
</button>
<button class="btn btn-secondary btn-sm"
hx-get="{% url 'transactions_bulk_unpay' %}"
hx-include=".transaction"
data-bs-toggle="tooltip"
data-bs-title="{% translate 'Mark as unpaid' %}">
<i class="fa-regular fa-circle tw-text-red-400"></i>
</button>
</div>
<button class="btn btn-secondary btn-sm"
data-bs-toggle="tooltip"
data-bs-title="{% translate 'Select All' %}"
_="on click set <#transactions-list input[type='checkbox']/>'s checked to true then call me.blur() then trigger change">
<i class="fa-regular fa-square-check tw-text-green-400"></i>
</button>
<button class="btn btn-secondary btn-sm"
data-bs-toggle="tooltip"
data-bs-title="{% translate 'Unselect All' %}"
_="on click set <#transactions-list input[type='checkbox']/>'s checked to false then call me.blur() then trigger change">
<i class="fa-regular fa-square tw-text-red-400"></i>
</button>
</div>
<div class="vr mx-3 tw-align-middle"></div>
<div class="btn-group me-3" role="group">
<button class="btn btn-secondary btn-sm"
hx-get="{% url 'transactions_bulk_pay' %}"
hx-include=".transaction"
data-bs-toggle="tooltip"
data-bs-title="{% translate 'Mark as paid' %}">
<i class="fa-regular fa-circle-check tw-text-green-400"></i>
</button>
<button class="btn btn-secondary btn-sm"
hx-get="{% url 'transactions_bulk_unpay' %}"
hx-get="{% url 'transactions_bulk_delete' %}"
hx-include=".transaction"
hx-trigger="confirmed"
data-bs-toggle="tooltip"
data-bs-title="{% translate 'Mark as unpaid' %}">
<i class="fa-regular fa-circle tw-text-red-400"></i>
data-bs-title="{% translate 'Delete' %}"
data-bypass-on-ctrl="true"
data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}"
data-confirm-text="{% translate "Yes, delete them!" %}"
_="install prompt_swal">
<i class="fa-solid fa-trash text-danger"></i>
</button>
</div>
<button class="btn btn-secondary btn-sm"
hx-get="{% url 'transactions_bulk_delete' %}"
hx-include=".transaction"
hx-trigger="confirmed"
data-bs-toggle="tooltip"
data-bs-title="{% translate 'Delete' %}"
data-bypass-on-ctrl="true"
data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}"
data-confirm-text="{% translate "Yes, delete them!" %}"
_="install prompt_swal">
<i class="fa-solid fa-trash text-danger"></i>
</button>
<div class="vr mx-3 tw-align-middle"></div>
<span _="on selected_transactions_updated from #actions-bar
set realTotal to 0.0
set flatTotal to 0.0
for transaction in <.transaction:has(input[name='transactions']:checked)/>
set amt to first <.main-amount .amount/> in transaction
set amountValue to parseFloat(amt.getAttribute('data-amount'))
if not isNaN(amountValue)
set flatTotal to flatTotal + (amountValue * 100)
<div class="vr mx-3 tw-align-middle"></div>
{# <span _="on selected_transactions_updated from #actions-bar#}
{# set realTotal to 0.0#}
{# set flatTotal to 0.0#}
{# for transaction in <.transaction:has(input[name='transactions']:checked)/>#}
{# set amt to first <.main-amount .amount/> in transaction#}
{# set amountValue to parseFloat(amt.getAttribute('data-amount'))#}
{# if not isNaN(amountValue)#}
{# set flatTotal to flatTotal + (amountValue * 100)#}
{##}
{# if transaction match .income#}
{# set realTotal to realTotal + (amountValue * 100)#}
{# else#}
{# set realTotal to realTotal - (amountValue * 100)#}
{# end#}
{# end#}
{# end#}
{# set realTotal to realTotal / 100#}
{# put realTotal.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40}) into me#}
{# end#}
{# on click#}
{# set original_value to my innerText#}
{# writeText(my innerText) on navigator.clipboard#}
{# put '{% translate "copied!" %}' into me#}
{# wait 1s#}
{# put original_value into me#}
{# end"#}
{# class="" role="button"></span>#}
<div class="btn-group"
_="on selected_transactions_updated from #actions-bar
set realTotal to 0.0
set flatTotal to 0.0
set transactions to <.transaction:has(input[name='transactions']:checked)/>
set amountValues to []
if transaction match .income
set realTotal to realTotal + (amountValue * 100)
else
set realTotal to realTotal - (amountValue * 100)
for transaction in transactions
set amt to first <.main-amount .amount/> in transaction
set amountValue to parseFloat(amt.getAttribute('data-amount'))
append amountValue to amountValues
if not isNaN(amountValue)
set flatTotal to flatTotal + (amountValue * 100)
if transaction match .income
set realTotal to realTotal + (amountValue * 100)
else
set realTotal to realTotal - (amountValue * 100)
end
end
end
end
set realTotal to realTotal / 100
put realTotal.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40}) into me
end
on click
set original_value to my innerText
writeText(my innerText) on navigator.clipboard
put '{% translate "copied!" %}' into me
wait 1s
put original_value into me
end"
class="" role="button"></span>
set realTotal to realTotal / 100
put realTotal.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40}) into #real-total-front's innerText
put realTotal.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40}) into #calc-menu-real-total's innerText
set flatTotal to flatTotal / 100
put flatTotal.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40}) into #calc-menu-flat-total's innerText
log amountValues
put Math.max.apply(Math, amountValues) into #calc-menu-max's innerText
put Math.min.apply(Math, amountValues) into #calc-menu-min's innerText
put flatTotal / amountValues.length into #calc-menu-mean's innerText
put amountValues.length into #calc-menu-count's innerText
end"
>
<button class="btn btn-secondary btn-sm" _="on click
set original_value to #real-total-front's innerText
writeText(original_value) on navigator.clipboard
put '{% translate "copied!" %}' into #real-total-front's innerText
wait 1s
put original_value into #real-total-front's innerText
end">
<i class="fa-solid fa-plus fa-fw me-2 text-primary"></i>
<span id="real-total-front">0</span>
</button>
<button type="button" class="btn btn-sm btn-secondary dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside">
<span class="visually-hidden">{% trans "Toggle Dropdown" %}</span>
</button>
<ul class="dropdown-menu">
<li>
<div class="dropdown-item-text p-0">
<div>
<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"
id="calc-menu-flat-total"
_="on click
set original_value to my innerText
writeText(my innerText) on navigator.clipboard
put '{% translate "copied!" %}' into me
wait 1s
put original_value into me
end">
0
</div>
</div>
</div>
</li>
<li>
<div class="dropdown-item-text p-0">
<div>
<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"
id="calc-menu-real-total"
_="on click
set original_value to my innerText
writeText(my innerText) on navigator.clipboard
put '{% translate "copied!" %}' into me
wait 1s
put original_value into me
end">
0
</div>
</div>
</div>
</li>
<li>
<div class="dropdown-item-text p-0">
<div>
<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"
id="calc-menu-mean"
_="on click
set original_value to my innerText
writeText(my innerText) on navigator.clipboard
put '{% translate "copied!" %}' into me
wait 1s
put original_value into me
end">
0
</div>
</div>
</div>
</li>
<li>
<div class="dropdown-item-text p-0">
<div>
<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"
id="calc-menu-max"
_="on click
set original_value to my innerText
writeText(my innerText) on navigator.clipboard
put '{% translate "copied!" %}' into me
wait 1s
put original_value into me
end">
0
</div>
</div>
</div>
</li>
<li>
<div class="dropdown-item-text p-0">
<div>
<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"
id="calc-menu-min"
_="on click
set original_value to my innerText
writeText(my innerText) on navigator.clipboard
put '{% translate "copied!" %}' into me
wait 1s
put original_value into me
end">
0
</div>
</div>
</div>
</li>
<li>
<div class="dropdown-item-text p-0">
<div>
<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"
id="calc-menu-count"
_="on click
set original_value to my innerText
writeText(my innerText) on navigator.clipboard
put '{% translate "copied!" %}' into me
wait 1s
put original_value into me
end">
0
</div>
</div>
</div>
</li>
</ul>
</div>
{% endspaceless %}
</div>
</div>

View File

@@ -15,49 +15,18 @@
</div>
<div class="card">
<div class="card-body table-responsive">
{% if entities %}
<c-config.search></c-config.search>
<table class="table table-hover">
<thead>
<tr>
<th scope="col" class="col-auto"></th>
<th scope="col" class="col">{% translate 'Name' %}</th>
</tr>
</thead>
<tbody>
{% for entity in entities %}
<tr class="entity">
<td class="col-auto">
<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 'entity_edit' entity_id=entity.id %}"
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 'entity_delete' entity_id=entity.id %}"
hx-trigger='confirmed'
data-bypass-on-ctrl="true"
data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}"
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">{{ entity.name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<c-msg.empty title="{% translate "No entities" %}" remove-padding></c-msg.empty>
{% endif %}
<div class="card-header">
<ul class="nav nav-pills card-header-pills" id="myTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" data-bs-toggle="tab" type="button" role="tab" aria-selected="true" hx-get="{% url 'entities_table_active' %}" hx-trigger="load, click" hx-target="#entities-table">{% translate 'Active' %}</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" hx-get="{% url 'entities_table_archived' %}" hx-target="#entities-table" data-bs-toggle="tab" type="button" role="tab" aria-selected="false">{% translate 'Archived' %}</button>
</li>
</ul>
</div>
<div class="card-body">
<div id="entities-table"></div>
</div>
</div>
</div>

View File

@@ -0,0 +1,55 @@
{% load i18n %}
{% if active %}
<div class="show-loading" hx-get="{% url 'entities_table_active' %}" hx-trigger="updated from:window"
hx-swap="outerHTML">
{% else %}
<div class="show-loading" hx-get="{% url 'entities_table_archived' %}" hx-trigger="updated from:window"
hx-swap="outerHTML">
{% endif %}
{% if entities %}
<div class="table-responsive">
<c-config.search></c-config.search>
<table class="table table-hover">
<thead>
<tr>
<th scope="col" class="col-auto"></th>
<th scope="col" class="col">{% translate 'Name' %}</th>
</tr>
</thead>
<tbody>
{% for entity in entities %}
<tr class="entity">
<td class="col-auto">
<div class="btn-group" role="group" aria-label="{% translate 'Actions' %}">
<a class="btn btn-secondary btn-sm"
role="button"
hx-swap="innerHTML"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Edit" %}"
hx-get="{% url 'entity_edit' entity_id=entity.id %}"
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"
hx-swap="innerHTML"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Delete" %}"
hx-delete="{% url 'entity_delete' entity_id=entity.id %}"
hx-trigger='confirmed'
data-bypass-on-ctrl="true"
data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}"
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">{{ entity.name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<c-msg.empty title="{% translate "No entities" %}" remove-padding></c-msg.empty>
{% endif %}
</div>

View File

@@ -4,5 +4,5 @@
{% block title %}{% translate 'Entities' %}{% endblock %}
{% block content %}
<div hx-get="{% url 'entities_list' %}" hx-trigger="load, updated from:window" class="show-loading"></div>
<div hx-get="{% url 'entities_list' %}" hx-trigger="load" class="show-loading"></div>
{% endblock %}

View File

@@ -15,49 +15,18 @@
</div>
<div class="card">
<div class="card-body table-responsive">
{% if tags %}
<c-config.search></c-config.search>
<table class="table table-hover">
<thead>
<tr>
<th scope="col" class="col-auto"></th>
<th scope="col" class="col">{% translate 'Name' %}</th>
</tr>
</thead>
<tbody>
{% for tag in tags %}
<tr class="tag">
<td class="col-auto">
<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 'tag_edit' tag_id=tag.id %}"
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 'tag_delete' tag_id=tag.id %}"
hx-trigger='confirmed'
data-bypass-on-ctrl="true"
data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}"
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">{{ tag.name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<c-msg.empty title="{% translate "No tags" %}" remove-padding></c-msg.empty>
{% endif %}
<div class="card-header">
<ul class="nav nav-pills card-header-pills" id="myTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" data-bs-toggle="tab" type="button" role="tab" aria-selected="true" hx-get="{% url 'tags_table_active' %}" hx-trigger="load, click" hx-target="#tags-table">{% translate 'Active' %}</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" hx-get="{% url 'tags_table_archived' %}" hx-target="#tags-table" data-bs-toggle="tab" type="button" role="tab" aria-selected="false">{% translate 'Archived' %}</button>
</li>
</ul>
</div>
<div class="card-body">
<div id="tags-table"></div>
</div>
</div>
</div>

View File

@@ -0,0 +1,55 @@
{% load i18n %}
{% if active %}
<div class="show-loading" hx-get="{% url 'tags_table_active' %}" hx-trigger="updated from:window"
hx-swap="outerHTML">
{% else %}
<div class="show-loading" hx-get="{% url 'tags_table_archived' %}" hx-trigger="updated from:window"
hx-swap="outerHTML">
{% endif %}
{% if tags %}
<div class="table-responsive">
<c-config.search></c-config.search>
<table class="table table-hover">
<thead>
<tr>
<th scope="col" class="col-auto"></th>
<th scope="col" class="col">{% translate 'Name' %}</th>
</tr>
</thead>
<tbody>
{% for tag in tags %}
<tr class="tag">
<td class="col-auto">
<div class="btn-group" role="group" aria-label="{% translate 'Actions' %}">
<a class="btn btn-secondary btn-sm"
role="button"
hx-swap="innerHTML"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Edit" %}"
hx-get="{% url 'tag_edit' tag_id=tag.id %}"
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"
hx-swap="innerHTML"
data-bs-title="{% translate "Delete" %}"
hx-delete="{% url 'tag_delete' tag_id=tag.id %}"
hx-trigger='confirmed'
data-bypass-on-ctrl="true"
data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}"
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">{{ tag.name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<c-msg.empty title="{% translate "No tags" %}" remove-padding></c-msg.empty>
{% endif %}
</div>

View File

@@ -4,5 +4,5 @@
{% block title %}{% translate 'Tags' %}{% endblock %}
{% block content %}
<div hx-get="{% url 'tags_list' %}" hx-trigger="load, updated from:window" class="show-loading"></div>
<div hx-get="{% url 'tags_list' %}" hx-trigger="load" class="show-loading"></div>
{% endblock %}