feat: automated replacement

This commit is contained in:
Herculino Trotta
2025-10-28 14:13:30 -03:00
parent dd82289488
commit e600d87968
167 changed files with 4442 additions and 2503 deletions
@@ -1,6 +1,6 @@
{% load i18n %}
{% if account_data.labels %}
<div class="chart-container" style="position: relative; height:400px; width:100%"
<div class="chart-container tw:relative tw:h-[400px] tw:w-full"
_="init call setupAccountChart() end">
<canvas id="accountChart"></canvas>
</div>
@@ -1,6 +1,6 @@
{% load i18n %}
{% if currency_data.labels %}
<div class="chart-container" style="position: relative; height:400px; width:100%"
<div class="chart-container tw:relative tw:h-[400px] tw:w-full"
_="init call setupCurrencyChart() end">
<canvas id="currencyChart"></canvas>
</div>
@@ -7,25 +7,25 @@
{% crispy category_form %}
</form>
<div class="row row-cols-1 row-cols-lg-2 gx-3 gy-3">
<div class="col">
<div class="card h-100">
<div class="card-header">
<div class="tw:grid tw:grid-cols-1 tw:lg:grid-cols-2 tw:gap-3">
<div class="tw:w-full">
<div class="tw:card tw:bg-base-100 tw:shadow-xl tw:h-full">
<div class="tw:card-header tw:bg-base-200 tw:p-4 tw:font-semibold">
{% trans "Income/Expense by Account" %}
</div>
<div class="card-body">
<div class="tw:card-body">
<div id="account-card" class="show-loading" hx-get="{% url 'category_sum_by_account' %}"
hx-trigger="updated from:window" hx-include="#category-form, #picker-form, #picker-type">
</div>
</div>
</div>
</div>
<div class="col">
<div class="card h-100">
<div class="card-header">
<div class="tw:w-full">
<div class="tw:card tw:bg-base-100 tw:shadow-xl tw:h-full">
<div class="tw:card-header tw:bg-base-200 tw:p-4 tw:font-semibold">
{% trans "Income/Expense by Currency" %}
</div>
<div class="card-body">
<div class="tw:card-body">
<div id="currency-card" class="show-loading" hx-get="{% url 'category_sum_by_currency' %}"
hx-trigger="updated from:window" hx-include="#category-form, #picker-form, #picker-type">
</div>
@@ -2,77 +2,76 @@
<div hx-get="{% url 'category_overview' %}" hx-trigger="updated from:window" class="show-loading" hx-swap="outerHTML"
hx-include="#picker-form, #picker-type, #view-type, #show-tags, #showing, #show-entities">
<div class="h-100 text-center mb-4">
<div class="btn-group gap-3" role="group" id="view-type" _="on change trigger updated">
<input type="radio" class="btn-check"
<div class="tw:h-full tw:text-center tw:mb-4">
<div class="tw:join" role="group" id="view-type" _="on change trigger updated">
<input type="radio" class="tw:join-item tw:btn tw:btn-outline tw:btn-primary tw:rounded-full"
name="view_type"
id="table-view"
autocomplete="off"
value="table"
aria-label="{% trans 'Table' %}"
{% if view_type == "table" %}checked{% endif %}>
<label class="btn btn-outline-primary rounded-5" for="table-view"><i
class="fa-solid fa-table fa-fw me-2"></i>{% trans 'Table' %}</label>
<input type="radio"
class="btn-check"
class="tw:join-item tw:btn tw:btn-outline tw:btn-primary tw:rounded-full"
name="view_type"
id="bars-view"
autocomplete="off"
value="bars"
aria-label="{% trans 'Bars' %}"
{% if view_type == "bars" %}checked{% endif %}>
<label class="btn btn-outline-primary rounded-5" for="bars-view"><i
class="fa-solid fa-chart-bar fa-fw me-2"></i>{% trans 'Bars' %}</label>
</div>
</div>
<div class="mt-3 mb-1 d-flex flex-column flex-md-row justify-content-between">
<div class="d-flex gap-4">
<div class="tw:mt-3 tw:mb-1 tw:flex tw:flex-col tw:md:flex-row tw:justify-between">
<div class="tw:flex tw:gap-4">
{% if view_type == 'table' %}
<div class="form-check form-switch" id="show-tags">
<div class="tw:form-control" id="show-tags">
<input type="hidden" name="show_tags" value="off">
<input class="form-check-input" type="checkbox" role="switch" id="show-tags-switch" name="show_tags"
_="on change trigger updated" {% if show_tags %}checked{% endif %}>
{% spaceless %}
<label class="form-check-label" for="show-tags-switch">
{% trans 'Tags' %}
</label>
<c-ui.help-icon
content="{% trans 'Transaction amounts associated with multiple tags will be counted once for each tag' %}"
icon="fa-solid fa-circle-exclamation"></c-ui.help-icon>
{% endspaceless %}
<label class="tw:label tw:cursor-pointer tw:gap-2">
<input type="checkbox" class="tw:toggle" id="show-tags-switch" name="show_tags"
_="on change trigger updated" {% if show_tags %}checked{% endif %}>
{% spaceless %}
<span class="tw:label-text">
{% trans 'Tags' %}
</span>
<c-ui.help-icon
content="{% trans 'Transaction amounts associated with multiple tags will be counted once for each tag' %}"
icon="fa-solid fa-circle-exclamation"></c-ui.help-icon>
{% endspaceless %}
</label>
</div>
<div class="form-check form-switch" id="show-entities" {% if not show_tags %}style="display: none;"{% endif %}>
<div class="tw:form-control" id="show-entities" {% if not show_tags %}style="display: none;"{% endif %}>
<input type="hidden" name="show_entities" value="off">
<input class="form-check-input" type="checkbox" role="switch" id="show-entities-switch" name="show_entities"
_="on change trigger updated" {% if show_entities %}checked{% endif %}>
{% spaceless %}
<label class="form-check-label" for="show-entities-switch">
{% trans 'Entities' %}
</label>
<c-ui.help-icon
content="{% trans 'Transaction amounts associated with multiple tags and entities will be counted once for each tag' %}"
icon="fa-solid fa-circle-exclamation"></c-ui.help-icon>
{% endspaceless %}
<label class="tw:label tw:cursor-pointer tw:gap-2">
<input type="checkbox" class="tw:toggle" id="show-entities-switch" name="show_entities"
_="on change trigger updated" {% if show_entities %}checked{% endif %}>
{% spaceless %}
<span class="tw:label-text">
{% trans 'Entities' %}
</span>
<c-ui.help-icon
content="{% trans 'Transaction amounts associated with multiple tags and entities will be counted once for each tag' %}"
icon="fa-solid fa-circle-exclamation"></c-ui.help-icon>
{% endspaceless %}
</label>
</div>
{% endif %}
</div>
<div class="btn-group btn-group-sm" role="group" id="showing" _="on change trigger updated">
<input type="radio" class="btn-check" name="showing" id="showing-projected" autocomplete="off"
<div class="tw:join" role="group" id="showing" _="on change trigger updated">
<input type="radio" class="tw:join-item tw:btn tw:btn-outline tw:btn-primary tw:btn-sm" name="showing" id="showing-projected" autocomplete="off" aria-label="{% trans 'Projected' %}"
value="projected" {% if showing == 'projected' %}checked{% endif %}>
<label class="btn btn-outline-primary" for="showing-projected">{% trans "Projected" %}</label>
<input type="radio" class="btn-check" name="showing" id="showing-current" autocomplete="off" value="current"
<input type="radio" class="tw:join-item tw:btn tw:btn-outline tw:btn-primary tw:btn-sm" name="showing" id="showing-current" autocomplete="off" value="current" aria-label="{% trans 'Current' %}"
{% if showing == 'current' %}checked{% endif %}>
<label class="btn btn-outline-primary" for="showing-current">{% trans "Current" %}</label>
<input type="radio" class="btn-check" name="showing" id="showing-final" autocomplete="off" value="final"
<input type="radio" class="tw:join-item tw:btn tw:btn-outline tw:btn-primary tw:btn-sm" name="showing" id="showing-final" autocomplete="off" value="final" aria-label="{% trans 'Final total' %}"
{% if showing == 'final' %}checked{% endif %}>
<label class="btn btn-outline-primary" for="showing-final">{% trans "Final total" %}</label>
</div>
</div>
{% if total_table %}
{% if view_type == "table" %}
<div class="table-responsive">
<table class="table table-striped table-hover table-bordered align-middle">
<div class="tw:overflow-x-auto">
<table class="tw:table tw:table-zebra">
<thead>
<tr>
<th scope="col">{% trans 'Category' %}</th>
@@ -81,10 +80,10 @@
<th scope="col">{% trans 'Total' %}</th>
</tr>
</thead>
<tbody class="table-group-divider">
<tbody>
{% for category in total_table.values %}
<!-- Category row -->
<tr class="table-group-header">
<tr class="tw:font-semibold">
<th>{% if category.name %}{{ category.name }}{% else %}{% trans 'Uncategorized' %}{% endif %}</th>
<td> {# income #}
{% for currency in category.currencies.values %}
@@ -176,9 +175,9 @@
{% if show_tags %}
{% for tag_id, tag in category.tags.items %}
{% if tag.name or not tag.name and category.tags.values|length > 1 %}
<tr class="table-row-nested">
<td class="ps-4">
<i class="fa-solid fa-hashtag fa-fw me-2 text-muted"></i>{% if tag.name %}{{ tag.name }}{% else %}{% trans 'Untagged' %}{% endif %}
<tr class="tw:bg-base-200">
<td class="tw:ps-4">
<i class="fa-solid fa-hashtag fa-fw tw:me-2 tw:text-base-content/60"></i>{% if tag.name %}{{ tag.name }}{% else %}{% trans 'Untagged' %}{% endif %}
</td>
<td>
{% for currency in tag.currencies.values %}
@@ -269,9 +268,9 @@
{% if show_entities %}
{% for entity_id, entity in tag.entities.items %}
{% if entity.name or not entity.name and tag.entities.values|length > 1 %}
<tr class="table-row-nested-2">
<td class="ps-5">
<i class="fa-solid fa-user-group fa-fw me-2 text-muted"></i>{% if entity.name %}{{ entity.name }}{% else %}{% trans 'No entity' %}{% endif %}
<tr class="tw:bg-base-300">
<td class="tw:ps-5">
<i class="fa-solid fa-user-group fa-fw tw:me-2 tw:text-base-content/60"></i>{% if entity.name %}{{ entity.name }}{% else %}{% trans 'No entity' %}{% endif %}
</td>
<td>
{% for currency in entity.currencies.values %}
@@ -372,7 +371,7 @@
{% elif view_type == "bars" %}
<div>
<div class="chart-container" _="init call setupChart() end" style="position: relative; height:78vh; width:100%">
<div class="chart-container tw:relative tw:h-[78vh] tw:w-full" _="init call setupChart() end">
<canvas id="categoryChart"></canvas>
</div>
</div>
@@ -3,68 +3,62 @@
<div hx-get="{% url 'insights_emergency_fund' %}" hx-trigger="updated from:window" class="show-loading"
hx-swap="outerHTML">
<div class="accordion" id="emergency-fund-accordion">
<div class="tw:join tw:join-vertical tw:w-full" id="emergency-fund-accordion">
{% for id, data in data.items %}
{% if data.average %}
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
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>
<div class="tw:collapse tw:collapse-arrow tw:join-item tw:border-base-300 tw:border">
<input type="radio" name="emergency-fund-accordion" />
<div class="tw:collapse-title tw:text-base tw:font-medium">
<span>
<span class="tw:text-base-content/60">{% 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"
divless></c-amount.display>
<span class="tw:text-base-content/60">{% 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-base-content/60">{% trans 'months without any income.' %}</span>
</span>
</div>
<div class="tw:collapse-content">
<div class="tw:flex tw:justify-between tw:items-baseline tw:mt-2">
<div class="tw:text-end tw:font-mono">
<div class="tw:text-base-content/60">{% translate 'average expenses' %}</div>
</div>
<div class="dotted-line tw:flex-grow"></div>
<div class="tw:text-end tw:font-mono">
<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"
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>
</button>
</h2>
<div id="flush-collapse-{{ id }}" class="accordion-collapse collapse"
data-bs-parent="#emergency-fund-accordion">
<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>
<div class="dotted-line flex-grow-1"></div>
<div class="text-end font-monospace">
<c-amount.display
:amount="data.average"
:prefix="data.currency.prefix"
:suffix="data.currency.suffix"
:decimal_places="data.currency.decimal_places"
color="red"></c-amount.display>
</div>
color="red"></c-amount.display>
</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>
<div class="dotted-line flex-grow-1"></div>
<div class="text-end font-monospace">
<c-amount.display
:amount="data.total_current"
:prefix="data.currency.prefix"
:suffix="data.currency.suffix"
:decimal_places="data.currency.decimal_places"
color="{% if data.total_current > 0 %}green{% elif data.total_current < 0 %}red{% endif %}"></c-amount.display>
</div>
</div>
<div class="tw:flex tw:justify-between tw:items-baseline tw:mt-2">
<div class="tw:text-end tw:font-mono">
<div class="tw:text-base-content/60">{% translate 'liquid total' %}</div>
</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>
<div class="dotted-line flex-grow-1"></div>
<div class="text-end font-monospace">
<span>{{ data.months }}</span>
</div>
<div class="dotted-line tw:flex-grow"></div>
<div class="tw:text-end tw:font-mono">
<c-amount.display
:amount="data.total_current"
:prefix="data.currency.prefix"
:suffix="data.currency.suffix"
:decimal_places="data.currency.decimal_places"
color="{% if data.total_current > 0 %}green{% elif data.total_current < 0 %}red{% endif %}"></c-amount.display>
</div>
</div>
<div class="tw:flex tw:justify-between tw:items-baseline tw:mt-2">
<div class="tw:text-end tw:font-mono">
<div class="tw:text-base-content/60">{% translate 'months left' %}</div>
</div>
<div class="dotted-line tw:flex-grow"></div>
<div class="tw:text-end tw:font-mono">
<span>{{ data.months }}</span>
</div>
</div>
</div>
+1 -1
View File
@@ -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 tw: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>