feat: add Unit Price Calculator

This commit is contained in:
Herculino Trotta
2024-11-15 11:57:42 -03:00
parent c2f9f7f70f
commit 7f6e6514d5
14 changed files with 356 additions and 70 deletions

View File

@@ -46,4 +46,5 @@ urlpatterns = [
path("", include("apps.rules.urls")), path("", include("apps.rules.urls")),
path("", include("apps.calendar_view.urls")), path("", include("apps.calendar_view.urls")),
path("", include("apps.dca.urls")), path("", include("apps.dca.urls")),
path("", include("apps.mini_tools.urls")),
] ]

View File

View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class MiniToolsConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "apps.mini_tools"

View File

@@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View File

@@ -0,0 +1,11 @@
from django.urls import path
from . import views
urlpatterns = [
path(
"tools/unit-price-calculator/",
views.unit_price_calculator,
name="unit_price_calculator",
),
]

View File

@@ -0,0 +1,7 @@
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
@login_required
def unit_price_calculator(request):
return render(request, "mini_tools/unit_price_calculator.html")

View File

@@ -8,8 +8,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-11-12 14:32+0000\n" "POT-Creation-Date: 2024-11-15 14:47+0000\n"
"PO-Revision-Date: 2024-11-12 11:39-0300\n" "PO-Revision-Date: 2024-11-15 11:47-0300\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: \n" "Language-Team: \n"
"Language: pt_BR\n" "Language: pt_BR\n"
@@ -42,10 +42,11 @@ msgstr "Atualizar"
#: templates/accounts/fragments/list.html:9 #: templates/accounts/fragments/list.html:9
#: templates/categories/fragments/list.html:9 #: templates/categories/fragments/list.html:9
#: templates/currencies/fragments/list.html:9 #: templates/currencies/fragments/list.html:9
#: templates/dca/fragments/strategy/details.html:101 #: templates/dca/fragments/strategy/details.html:16
#: templates/dca/fragments/strategy/list.html:9 #: templates/dca/fragments/strategy/list.html:9
#: templates/exchange_rates/fragments/list.html:10 #: templates/exchange_rates/fragments/list.html:10
#: templates/installment_plans/fragments/list.html:9 #: templates/installment_plans/fragments/list.html:9
#: templates/mini_tools/unit_price_calculator.html:174
#: templates/recurring_transactions/fragments/list.html:9 #: templates/recurring_transactions/fragments/list.html:9
#: templates/rules/fragments/list.html:9 templates/tags/fragments/list.html:9 #: templates/rules/fragments/list.html:9 templates/tags/fragments/list.html:9
msgid "Add" msgid "Add"
@@ -77,7 +78,7 @@ msgstr "Categoria"
msgid "Tags" msgid "Tags"
msgstr "Tags" msgstr "Tags"
#: apps/accounts/models.py:8 apps/accounts/models.py:20 apps/dca/models.py:11 #: apps/accounts/models.py:8 apps/accounts/models.py:20 apps/dca/models.py:14
#: apps/rules/models.py:9 apps/transactions/models.py:19 #: apps/rules/models.py:9 apps/transactions/models.py:19
#: apps/transactions/models.py:32 #: apps/transactions/models.py:32
#: templates/account_groups/fragments/list.html:23 #: templates/account_groups/fragments/list.html:23
@@ -279,6 +280,7 @@ msgid "Remove"
msgstr "Remover" msgstr "Remover"
#: apps/common/widgets/tom_select.py:14 #: apps/common/widgets/tom_select.py:14
#: templates/mini_tools/unit_price_calculator.html:186
#: templates/transactions/pages/transactions.html:18 #: templates/transactions/pages/transactions.html:18
msgid "Clear" msgid "Clear"
msgstr "Limpar" msgstr "Limpar"
@@ -359,60 +361,60 @@ msgstr "Taxa de câmbio atualizada com sucesso"
msgid "Exchange rate deleted successfully" msgid "Exchange rate deleted successfully"
msgstr "Taxa de câmbio apagada com sucesso" msgstr "Taxa de câmbio apagada com sucesso"
#: apps/dca/models.py:14 #: apps/dca/models.py:17
msgid "Target Currency" msgid "Target Currency"
msgstr "Moeda de destino" msgstr "Moeda de destino"
#: apps/dca/models.py:20 #: apps/dca/models.py:23
msgid "Payment Currency" msgid "Payment Currency"
msgstr "Moeda de pagamento" msgstr "Moeda de pagamento"
#: apps/dca/models.py:24 apps/dca/models.py:98 apps/rules/models.py:26 #: apps/dca/models.py:27 apps/dca/models.py:167 apps/rules/models.py:26
#: apps/transactions/forms.py:209 apps/transactions/models.py:72 #: apps/transactions/forms.py:209 apps/transactions/models.py:72
#: apps/transactions/models.py:188 apps/transactions/models.py:356 #: apps/transactions/models.py:188 apps/transactions/models.py:356
msgid "Notes" msgid "Notes"
msgstr "Notas" msgstr "Notas"
#: apps/dca/models.py:29 #: apps/dca/models.py:32
msgid "DCA Strategy" msgid "DCA Strategy"
msgstr "Estratégia CMP" msgstr "Estratégia CMP"
#: apps/dca/models.py:30 #: apps/dca/models.py:33
msgid "DCA Strategies" msgid "DCA Strategies"
msgstr "Estratégias CMP" msgstr "Estratégias CMP"
#: apps/dca/models.py:73 #: apps/dca/models.py:142
msgid "Strategy" msgid "Strategy"
msgstr "Estratégia" msgstr "Estratégia"
#: apps/dca/models.py:75 apps/rules/models.py:22 apps/transactions/forms.py:197 #: apps/dca/models.py:144 apps/rules/models.py:22
#: apps/transactions/models.py:61 #: apps/transactions/forms.py:197 apps/transactions/models.py:61
#: templates/dca/fragments/strategy/details.html:116 #: templates/dca/fragments/strategy/details.html:31
#: templates/exchange_rates/fragments/table.html:14 #: templates/exchange_rates/fragments/table.html:14
msgid "Date" msgid "Date"
msgstr "Data" msgstr "Data"
#: apps/dca/models.py:77 templates/dca/fragments/strategy/details.html:117 #: apps/dca/models.py:146 templates/dca/fragments/strategy/details.html:32
msgid "Amount Paid" msgid "Amount Paid"
msgstr "Quantia paga" msgstr "Quantia paga"
#: apps/dca/models.py:80 templates/dca/fragments/strategy/details.html:118 #: apps/dca/models.py:149 templates/dca/fragments/strategy/details.html:33
msgid "Amount Received" msgid "Amount Received"
msgstr "Quantia recebida" msgstr "Quantia recebida"
#: apps/dca/models.py:88 #: apps/dca/models.py:157
msgid "Expense Transaction" msgid "Expense Transaction"
msgstr "Transação de saída" msgstr "Transação de saída"
#: apps/dca/models.py:96 #: apps/dca/models.py:165
msgid "Income Transaction" msgid "Income Transaction"
msgstr "Transação de entrada" msgstr "Transação de entrada"
#: apps/dca/models.py:103 #: apps/dca/models.py:172
msgid "DCA Entry" msgid "DCA Entry"
msgstr "Entrada CMP" msgstr "Entrada CMP"
#: apps/dca/models.py:104 #: apps/dca/models.py:173
msgid "DCA Entries" msgid "DCA Entries"
msgstr "Entradas CMP" msgstr "Entradas CMP"
@@ -428,15 +430,15 @@ msgstr "Estratégia CMP atualizada com sucesso"
msgid "DCA strategy deleted successfully" msgid "DCA strategy deleted successfully"
msgstr "Estratégia CMP apagada com sucesso" msgstr "Estratégia CMP apagada com sucesso"
#: apps/dca/views.py:169 #: apps/dca/views.py:172
msgid "Entry added successfully" msgid "Entry added successfully"
msgstr "Entrada adicionada com sucesso" msgstr "Entrada adicionada com sucesso"
#: apps/dca/views.py:196 #: apps/dca/views.py:199
msgid "Entry updated successfully" msgid "Entry updated successfully"
msgstr "Entrada atualizada com sucesso" msgstr "Entrada atualizada com sucesso"
#: apps/dca/views.py:223 #: apps/dca/views.py:226
msgid "Entry deleted successfully" msgid "Entry deleted successfully"
msgstr "Entrada apagada com sucesso" msgstr "Entrada apagada com sucesso"
@@ -798,27 +800,27 @@ msgstr "Parcelamento atualizado com sucesso"
msgid "Installment Plan deleted successfully" msgid "Installment Plan deleted successfully"
msgstr "Parcelamento apagado com sucesso" msgstr "Parcelamento apagado com sucesso"
#: apps/transactions/views/recurring_transactions.py:115 #: apps/transactions/views/recurring_transactions.py:114
msgid "Recurring Transaction added successfully" msgid "Recurring Transaction added successfully"
msgstr "Transação Recorrente adicionada com sucesso" msgstr "Transação Recorrente adicionada com sucesso"
#: apps/transactions/views/recurring_transactions.py:145 #: apps/transactions/views/recurring_transactions.py:144
msgid "Recurring Transaction updated successfully" msgid "Recurring Transaction updated successfully"
msgstr "Transação Recorrente atualizada com sucesso" msgstr "Transação Recorrente atualizada com sucesso"
#: apps/transactions/views/recurring_transactions.py:175 #: apps/transactions/views/recurring_transactions.py:174
msgid "Recurring transaction unpaused successfully" msgid "Recurring transaction unpaused successfully"
msgstr "Transação Recorrente despausada com sucesso" msgstr "Transação Recorrente despausada com sucesso"
#: apps/transactions/views/recurring_transactions.py:178 #: apps/transactions/views/recurring_transactions.py:177
msgid "Recurring transaction paused successfully" msgid "Recurring transaction paused successfully"
msgstr "Transação Recorrente pausada com sucesso" msgstr "Transação Recorrente pausada com sucesso"
#: apps/transactions/views/recurring_transactions.py:201 #: apps/transactions/views/recurring_transactions.py:200
msgid "Recurring transaction finished successfully" msgid "Recurring transaction finished successfully"
msgstr "Transação Recorrente finalizada com sucesso" msgstr "Transação Recorrente finalizada com sucesso"
#: apps/transactions/views/recurring_transactions.py:222 #: apps/transactions/views/recurring_transactions.py:221
msgid "Recurring Transaction deleted successfully" msgid "Recurring Transaction deleted successfully"
msgstr "Transação Recorrente apagada com sucesso" msgstr "Transação Recorrente apagada com sucesso"
@@ -959,7 +961,7 @@ msgstr "Editar grupo de conta"
#: templates/accounts/fragments/list.html:34 #: templates/accounts/fragments/list.html:34
#: templates/categories/fragments/list.html:31 #: templates/categories/fragments/list.html:31
#: templates/currencies/fragments/list.html:31 #: templates/currencies/fragments/list.html:31
#: templates/dca/fragments/strategy/details.html:127 #: templates/dca/fragments/strategy/details.html:42
#: templates/exchange_rates/fragments/table.html:23 #: templates/exchange_rates/fragments/table.html:23
#: templates/installment_plans/fragments/table.html:21 #: templates/installment_plans/fragments/table.html:21
#: templates/recurring_transactions/fragments/table.html:23 #: templates/recurring_transactions/fragments/table.html:23
@@ -972,7 +974,7 @@ msgstr "Ações"
#: templates/categories/fragments/list.html:35 #: templates/categories/fragments/list.html:35
#: templates/cotton/transaction/item.html:99 #: templates/cotton/transaction/item.html:99
#: templates/currencies/fragments/list.html:35 #: templates/currencies/fragments/list.html:35
#: templates/dca/fragments/strategy/details.html:131 #: templates/dca/fragments/strategy/details.html:46
#: templates/dca/fragments/strategy/list.html:31 #: templates/dca/fragments/strategy/list.html:31
#: templates/exchange_rates/fragments/table.html:27 #: templates/exchange_rates/fragments/table.html:27
#: templates/installment_plans/fragments/table.html:25 #: templates/installment_plans/fragments/table.html:25
@@ -989,10 +991,11 @@ msgstr "Editar"
#: templates/cotton/transaction/item.html:106 #: templates/cotton/transaction/item.html:106
#: templates/cotton/ui/transactions_action_bar.html:50 #: templates/cotton/ui/transactions_action_bar.html:50
#: templates/currencies/fragments/list.html:42 #: templates/currencies/fragments/list.html:42
#: templates/dca/fragments/strategy/details.html:139 #: templates/dca/fragments/strategy/details.html:54
#: templates/dca/fragments/strategy/list.html:39 #: templates/dca/fragments/strategy/list.html:39
#: templates/exchange_rates/fragments/table.html:35 #: templates/exchange_rates/fragments/table.html:35
#: templates/installment_plans/fragments/table.html:54 #: templates/installment_plans/fragments/table.html:54
#: templates/mini_tools/unit_price_calculator.html:18
#: templates/recurring_transactions/fragments/table.html:89 #: templates/recurring_transactions/fragments/table.html:89
#: templates/rules/fragments/list.html:42 #: templates/rules/fragments/list.html:42
#: templates/rules/fragments/transaction_rule/view.html:56 #: templates/rules/fragments/transaction_rule/view.html:56
@@ -1006,7 +1009,7 @@ msgstr "Apagar"
#: templates/cotton/transaction/item.html:110 #: templates/cotton/transaction/item.html:110
#: templates/cotton/ui/transactions_action_bar.html:52 #: templates/cotton/ui/transactions_action_bar.html:52
#: templates/currencies/fragments/list.html:46 #: templates/currencies/fragments/list.html:46
#: templates/dca/fragments/strategy/details.html:144 #: templates/dca/fragments/strategy/details.html:59
#: templates/dca/fragments/strategy/list.html:43 #: templates/dca/fragments/strategy/list.html:43
#: templates/exchange_rates/fragments/table.html:40 #: templates/exchange_rates/fragments/table.html:40
#: templates/installment_plans/fragments/table.html:46 #: templates/installment_plans/fragments/table.html:46
@@ -1027,7 +1030,7 @@ msgstr "Tem certeza?"
#: templates/cotton/transaction/item.html:111 #: templates/cotton/transaction/item.html:111
#: templates/cotton/ui/transactions_action_bar.html:53 #: templates/cotton/ui/transactions_action_bar.html:53
#: templates/currencies/fragments/list.html:47 #: templates/currencies/fragments/list.html:47
#: templates/dca/fragments/strategy/details.html:145 #: templates/dca/fragments/strategy/details.html:60
#: templates/dca/fragments/strategy/list.html:44 #: templates/dca/fragments/strategy/list.html:44
#: templates/exchange_rates/fragments/table.html:41 #: templates/exchange_rates/fragments/table.html:41
#: templates/rules/fragments/list.html:47 #: templates/rules/fragments/list.html:47
@@ -1041,7 +1044,7 @@ msgstr "Você não será capaz de reverter isso!"
#: templates/categories/fragments/list.html:48 #: templates/categories/fragments/list.html:48
#: templates/cotton/transaction/item.html:112 #: templates/cotton/transaction/item.html:112
#: templates/currencies/fragments/list.html:48 #: templates/currencies/fragments/list.html:48
#: templates/dca/fragments/strategy/details.html:146 #: templates/dca/fragments/strategy/details.html:61
#: templates/dca/fragments/strategy/list.html:45 #: templates/dca/fragments/strategy/list.html:45
#: templates/exchange_rates/fragments/table.html:42 #: templates/exchange_rates/fragments/table.html:42
#: templates/installment_plans/fragments/table.html:60 #: templates/installment_plans/fragments/table.html:60
@@ -1198,7 +1201,7 @@ msgstr "Marcar como não pago"
msgid "Yes, delete them!" msgid "Yes, delete them!"
msgstr "Sim, apague!" msgstr "Sim, apague!"
#: templates/cotton/ui/transactions_action_bar.html:72 #: templates/cotton/ui/transactions_action_bar.html:73
msgid "copied!" msgid "copied!"
msgstr "copiado!" msgstr "copiado!"
@@ -1230,62 +1233,91 @@ msgstr "Editar entrada CMP"
msgid "Add DCA strategy" msgid "Add DCA strategy"
msgstr "Adicionar estratégia CMP" msgstr "Adicionar estratégia CMP"
#: templates/dca/fragments/strategy/details.html:11 #: templates/dca/fragments/strategy/details.html:12
msgid "Total Invested"
msgstr "Total investido"
#: templates/dca/fragments/strategy/details.html:25
msgid "Total Received"
msgstr "Total recebido"
#: templates/dca/fragments/strategy/details.html:39
msgid "Average Entry Price"
msgstr "Preço médio de entrada"
#: templates/dca/fragments/strategy/details.html:53
msgid "Current Total Value"
msgstr "Valor total atual"
#: templates/dca/fragments/strategy/details.html:67
msgid "Total P/L"
msgstr "P/L total"
#: templates/dca/fragments/strategy/details.html:82
#, python-format
msgid "Total %% P/L"
msgstr "P/L%% Total"
#: templates/dca/fragments/strategy/details.html:97
msgid "Entries" msgid "Entries"
msgstr "Entradas" msgstr "Entradas"
#: templates/dca/fragments/strategy/details.html:119 #: templates/dca/fragments/strategy/details.html:34
msgid "Current Value" msgid "Current Value"
msgstr "Valor atual" msgstr "Valor atual"
#: templates/dca/fragments/strategy/details.html:120 #: templates/dca/fragments/strategy/details.html:35
msgid "P/L" msgid "P/L"
msgstr "P/L" msgstr "P/L"
#: templates/dca/fragments/strategy/details.html:180 #: templates/dca/fragments/strategy/details.html:103
msgid "No entries for this DCA" msgid "No entries for this DCA"
msgstr "Nenhuma entrada neste CMP" msgstr "Nenhuma entrada neste CMP"
#: templates/dca/fragments/strategy/details.html:181 #: templates/dca/fragments/strategy/details.html:104
#: templates/monthly_overview/fragments/list.html:41 #: templates/monthly_overview/fragments/list.html:41
#: templates/transactions/fragments/list_all.html:40 #: templates/transactions/fragments/list_all.html:40
msgid "Try adding one" msgid "Try adding one"
msgstr "Tente adicionar uma" msgstr "Tente adicionar uma"
#: templates/dca/fragments/strategy/details.html:192 #: templates/dca/fragments/strategy/details.html:114
msgid "Performance Over Time" msgid "Total Invested"
msgstr "Desempenho ao longo do tempo" msgstr "Total investido"
#: templates/dca/fragments/strategy/details.html:208 #: templates/dca/fragments/strategy/details.html:128
msgid "Total Received"
msgstr "Total recebido"
#: templates/dca/fragments/strategy/details.html:142
msgid "Current Total Value"
msgstr "Valor total atual"
#: templates/dca/fragments/strategy/details.html:156
msgid "Average Entry Price"
msgstr "Preço médio de entrada"
#: templates/dca/fragments/strategy/details.html:170
msgid "Total P/L"
msgstr "P/L total"
#: templates/dca/fragments/strategy/details.html:186
#, python-format
msgid "Total %% P/L"
msgstr "P/L%% Total"
#: templates/dca/fragments/strategy/details.html:205
#, python-format #, python-format
msgid "P/L %%" msgid "P/L %%"
msgstr "P/L %%" msgstr "P/L %%"
#: templates/dca/fragments/strategy/details.html:267
msgid "Performance Over Time"
msgstr "Desempenho ao longo do tempo"
#: templates/dca/fragments/strategy/details.html:285
msgid "Entry Price"
msgstr "Preço de Entrada"
#: templates/dca/fragments/strategy/details.html:293
msgid "Current Price"
msgstr "Preço atual"
#: templates/dca/fragments/strategy/details.html:301
msgid "Amount Bought"
msgstr "Quantia comprada"
#: templates/dca/fragments/strategy/details.html:369
msgid "Entry Price vs Current Price"
msgstr "Preço de Entrada vs Preço Atual"
#: templates/dca/fragments/strategy/details.html:385
msgid "Days Between Investments"
msgstr "Dias entre investimentos"
#: templates/dca/fragments/strategy/details.html:432
msgid "Investment Frequency"
msgstr "Frequência de Investimento"
#: templates/dca/fragments/strategy/details.html:434
msgid "The straighter the blue line, the more consistent your DCA strategy is."
msgstr ""
"Quanto mais reta for a linha azul, mais consistente é sua estratégia de CMP."
#: templates/dca/fragments/strategy/edit.html:5 #: templates/dca/fragments/strategy/edit.html:5
msgid "Edit DCA strategy" msgid "Edit DCA strategy"
msgstr "Editar estratégia CMP" msgstr "Editar estratégia CMP"
@@ -1295,6 +1327,10 @@ msgstr "Editar estratégia CMP"
msgid "Dollar Cost Average Strategies" msgid "Dollar Cost Average Strategies"
msgstr "Estratégias de Custo Médio Ponderado" msgstr "Estratégias de Custo Médio Ponderado"
#: templates/dca/pages/strategy_detail_index.html:4
msgid "Dollar Cost Average Strategy"
msgstr "Estratégia de Custo Médio Ponderado"
#: templates/exchange_rates/fragments/add.html:5 #: templates/exchange_rates/fragments/add.html:5
msgid "Add exchange rate" msgid "Add exchange rate"
msgstr "Adicionar taxa de câmbio" msgstr "Adicionar taxa de câmbio"
@@ -1426,6 +1462,35 @@ msgstr ""
"Algo deu errado ao carregar seus dados. Tente recarregar a página ou " "Algo deu errado ao carregar seus dados. Tente recarregar a página ou "
"verifique o console para obter mais informações." "verifique o console para obter mais informações."
#: templates/mini_tools/unit_price_calculator.html:5
#: templates/mini_tools/unit_price_calculator.html:10
msgid "Unit Price Calculator"
msgstr "Calculadora de preço unitário"
#: templates/mini_tools/unit_price_calculator.html:27
#: templates/mini_tools/unit_price_calculator.html:112
#: templates/mini_tools/unit_price_calculator.html:137
msgid "Item price"
msgstr "Preço"
#: templates/mini_tools/unit_price_calculator.html:33
#: templates/mini_tools/unit_price_calculator.html:118
#: templates/mini_tools/unit_price_calculator.html:143
msgid "Item amount"
msgstr "Quantidade"
#: templates/mini_tools/unit_price_calculator.html:38
#: templates/mini_tools/unit_price_calculator.html:123
#: templates/mini_tools/unit_price_calculator.html:148
msgid "Unit price"
msgstr "Preço unitário"
#: templates/mini_tools/unit_price_calculator.html:106
#: templates/mini_tools/unit_price_calculator.html:131
#: templates/mini_tools/unit_price_calculator.html:170
msgid "Item"
msgstr "Item"
#: templates/monthly_overview/fragments/list.html:40 #: templates/monthly_overview/fragments/list.html:40
msgid "No transactions this month" msgid "No transactions this month"
msgstr "Nenhuma transação neste mês" msgstr "Nenhuma transação neste mês"

View File

@@ -57,7 +57,7 @@
</ul> </ul>
</li> </li>
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle {% active_link views='dca_strategy_index||dca_strategy_detail_index' %}" <a class="nav-link dropdown-toggle {% active_link views='dca_strategy_index||dca_strategy_detail_index||unit_price_calculator' %}"
href="#" role="button" href="#" role="button"
data-bs-toggle="dropdown" data-bs-toggle="dropdown"
aria-expanded="false"> aria-expanded="false">
@@ -67,6 +67,9 @@
<li><a class="dropdown-item {% active_link views='dca_strategy_index||dca_strategy_detail_index' %}" <li><a class="dropdown-item {% active_link views='dca_strategy_index||dca_strategy_detail_index' %}"
href="{% url 'dca_strategy_index' %}">{% translate 'Dollar Cost Average Tracker' %}</a></li> href="{% url 'dca_strategy_index' %}">{% translate 'Dollar Cost Average Tracker' %}</a></li>
<li> <li>
<li><a class="dropdown-item {% active_link views='unit_price_calculator' %}"
href="{% url 'unit_price_calculator' %}">{% translate 'Unit Price Calculator' %}</a></li>
<li>
</ul> </ul>
</li> </li>
<li class="nav-item dropdown"> <li class="nav-item dropdown">

View File

@@ -13,4 +13,8 @@ end
on htmx:afterSettle on htmx:afterSettle
call initTooltips(event.detail.target) call initTooltips(event.detail.target)
end end
on tooltips
call initTooltips(body)
end
</script> </script>

View File

@@ -0,0 +1,179 @@
{% extends "layouts/base.html" %}
{% load i18n %}
{% load webpack_loader %}
{% block title %}{% translate 'Unit Price Calculator' %}{% 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">
<div>{% translate 'Unit Price Calculator' %}</div>
</div>
<div class="card mb-3 d-none" id="card-placeholder">
<div class="card-header d-flex flex-row justify-content-between">
<h5 class="title flex-grow-1"></h5>
<button class="btn btn-secondary btn-sm text-danger"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Delete" %}"
_="on click remove the closest .card to me then trigger update on #items then call bootstrap.Tooltip.getOrCreateInstance(me).dispose()">
<i class="fa-solid fa-trash fa-fw"></i>
</button>
</div>
<div class="card-body">
<div class="row gy-3">
<div class="col-lg">
<div>
<label for="price" class="form-label">{% trans 'Item price' %}</label>
<input type="number" inputmode="decimal" class="form-control item-price" id="price">
</div>
</div>
<div class="col-lg">
<div>
<label for="amount" class="form-label">{% trans 'Item amount' %}</label>
<input type="number" inputmode="decimal" class="form-control item-amount" id="amount">
</div>
</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>
</div>
</div>
</div>
<div id="items" _="on input or update
for card in <.card />
set price to card.querySelector('.item-price').value
set amount to card.querySelector('.item-amount').value
// Calculate and format unit price
if amount > 0 set unitPrice to (price / amount) else set unitPrice to 0 end
set formattedUnitPrice to unitPrice.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40})
set card.querySelector('.unit-price').innerHTML to formattedUnitPrice
call card.querySelector('.unit-price').setAttribute('data-amount', unitPrice)
end
then
// Remove existing highlight classes from all unit prices
for unitPriceEl in <.unit-price/>
remove .bg-danger-subtle from the closest .card to unitPriceEl
remove .bg-success-subtle from the closest .card to unitPriceEl
end
// Get all unit prices and find min/max
set unitPrices to <.card:not(#card-placeholder) .unit-price/>
set unitPricesAmounts to <.card:not(#card-placeholder) .unit-price/> @data-amount
js(unitPricesAmounts)
unitPricesAmounts = unitPricesAmounts.filter(element => element !== '0')
return Math.min(...unitPricesAmounts)
end
set minAmount to it
js(unitPricesAmounts)
unitPricesAmounts = unitPricesAmounts.filter(element => element !== '0')
return Math.max(...unitPricesAmounts)
end
set maxAmount to it
if maxAmount and minAmount
for unitPriceEl in unitPrices
set amount to parseFloat(unitPriceEl.getAttribute('data-amount'))
if amount == minAmount
add .bg-success-subtle to the closest .card to unitPriceEl
continue
end
if amount == maxAmount
add .bg-danger-subtle to the closest .card to unitPriceEl
end
end
end
end">
<div class="card mb-3">
<div class="card-header">
<h5>{% trans "Item" %} A</h5>
</div>
<div class="card-body">
<div class="row gy-3">
<div class="col-lg">
<div>
<label for="price" class="form-label">{% trans 'Item price' %}</label>
<input type="number" inputmode="decimal" class="form-control item-price" id="price">
</div>
</div>
<div class="col-lg">
<div>
<label for="amount" class="form-label">{% trans 'Item amount' %}</label>
<input type="number" inputmode="decimal" class="form-control item-amount" id="amount">
</div>
</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>
</div>
</div>
</div>
<div class="card mb-3">
<div class="card-header">
<h5>{% trans "Item" %} B</h5>
</div>
<div class="card-body">
<div class="row gy-3">
<div class="col-lg">
<div>
<label for="price" class="form-label">{% trans 'Item price' %}</label>
<input type="number" inputmode="decimal" class="form-control item-price" id="price">
</div>
</div>
<div class="col-lg">
<div>
<label for="amount" class="form-label">{% trans 'Item amount' %}</label>
<input type="number" inputmode="decimal" class="form-control item-amount" id="amount">
</div>
</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>
</div>
</div>
</div>
</div>
<div class="row mt-3">
<div class="col-lg-8">
<button class="btn btn-outline-primary w-100"
_="on click
get #card-placeholder
set newCard to it.cloneNode(true)
remove @id from newCard
remove .d-none from newCard then
set itemCount to <#items .card/>'s length
if itemCount < 26
set letter to String.fromCharCode(65 + itemCount)
else
set letter to String.fromCharCode(65 + Math.floor((itemCount - 26) / 26)) + String.fromCharCode(65 + ((itemCount - 26) mod 26))
end
set newCard.querySelector('.title').innerHTML to `{% trans "Item" %} ${letter}`
put newCard as HTML at the end of #items
trigger tooltips on body
end">
{% trans 'Add' %}
</button>
</div>
<div class="col-lg-4 mt-3 mt-lg-0">
<button class="btn btn-outline-danger w-100"
_="on click
for el in <.item-price, .item-amount />
set card to the closest .card to el
set el's value to ''
end
trigger update on #items
end">
{% trans 'Clear' %}
</button>
</div>
</div>
</div>
{% endblock %}

View File

@@ -4,6 +4,7 @@ import Alpine from "alpinejs";
import mask from '@alpinejs/mask'; import mask from '@alpinejs/mask';
window.Alpine = Alpine; window.Alpine = Alpine;
window._hyperscript = _hyperscript;
Alpine.plugin(mask); Alpine.plugin(mask);
Alpine.start(); Alpine.start();