Compare commits

...

71 Commits

Author SHA1 Message Date
Herculino Trotta 32747071fe Merge remote-tracking branch 'origin/feat/replace-webpack+bootstrap' into feat/replace-webpack+bootstrap 2025-11-23 23:20:48 -03:00
Herculino Trotta 24fa9cde51 style: fix deleted transactions styling 2025-11-23 23:19:23 -03:00
Herculino Trotta 372ec2f30f Merge branch 'main' into feat/replace-webpack+bootstrap 2025-11-23 23:08:02 -03:00
Herculino Trotta fffba037a6 Merge pull request #403 from eitchtee/internal_port
feat: add internal_port env var
2025-11-23 23:07:29 -03:00
Herculino Trotta 43488147d8 ci: try to improve build times using uv 2025-11-23 22:55:53 -03:00
Herculino Trotta 31a31e9922 ci: try to improve build times using uv 2025-11-23 22:54:17 -03:00
Herculino Trotta 7af6280b29 ci: try to improve build times using uv 2025-11-23 22:48:20 -03:00
Herculino Trotta 40389396e3 ci: try to improve build times using uv 2025-11-23 22:31:52 -03:00
Herculino Trotta 21845d501e style: remove scrollbar-gutter due to weird behavior 2025-11-23 22:28:45 -03:00
Herculino Trotta 5f098e11a3 ci: try to improve build times 2025-11-23 22:08:39 -03:00
Herculino Trotta d2de0684fb feat: use scrollbar-gutter to prevent layout from shifting on dynamic loads 2025-11-23 20:27:12 -03:00
Herculino Trotta eb4723e890 feat: cleanup vite configs 2025-11-23 20:15:06 -03:00
Herculino Trotta 890cc90420 Merge pull request #398 from eitchtee/weblate
Translations update from Weblate
2025-11-22 13:01:58 -03:00
Herculino Trotta 307af9e40a feat: theme selection and remove unused styling 2025-11-22 03:06:22 -03:00
Herculino Trotta 1eeb0b0f5e feat: theme toasts and move elements styling to their js 2025-11-22 03:04:00 -03:00
Herculino Trotta 605ece705e feat: fixes 2025-11-22 01:30:43 -03:00
Herculino Trotta 2ae57e83cb feat: fixes 2025-11-22 01:10:01 -03:00
Herculino Trotta af72e3f44e fix(quick_transactions): ignore internal_id 2025-11-22 00:44:57 -03:00
Herculino Trotta e2e1c5cff5 feat: changes and fixes 2025-11-18 01:04:39 -03:00
Herculino Trotta ed3d58f1fd fix: slow down when page is loaded 2025-11-15 14:55:37 -03:00
Ursuleac Zsolt b58f894dc6 locale(Hungarian): update translation
Currently translated at 4.7% (33 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/hu/
2025-11-13 16:20:41 +00:00
Ursuleac Zsolt 2ed7fa44c0 locale((Hungarian)): added translation using Weblate 2025-11-13 15:27:11 +00:00
Herculino Trotta 7c3120cd43 fix: general javascript improvements 2025-11-13 11:12:43 -03:00
Herculino Trotta 2bc5e24e51 fix: theme toggle not saving correctly 2025-11-12 00:10:42 -03:00
Herculino Trotta d3f8a637bc feat: changes 2025-11-11 23:25:08 -03:00
Herculino Trotta b02b6451d2 fix: alpine incompatibility 2025-11-11 23:22:51 -03:00
Herculino Trotta 0b0d760bab feat: guess what, more changes 2025-11-11 20:21:01 -03:00
Herculino Trotta b38ed37bc5 feat: oh look, more changes 2025-11-10 00:28:16 -03:00
Herculino Trotta 5d7dd622f5 feat: add internal_port env var 2025-11-09 15:42:42 -03:00
Herculino Trotta 7e37948616 feat: more changes and fixes 2025-11-09 15:31:50 -03:00
Herculino Trotta 2afb6b1f5f feat: more changes and fixes 2025-11-08 14:21:36 -03:00
Herculino Trotta cd54df6f2d feat: more changes and fixes 2025-11-08 14:06:01 -03:00
Marcin Kisielewski 3e4ace8993 locale(Polish): update translation
Currently translated at 1.5% (11 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/pl/
2025-11-08 12:20:40 +00:00
Herculino Trotta a878af28f1 feat: more changes and fixes 2025-11-05 13:09:31 -03:00
Herculino Trotta 0a4d4c12b9 feat: another batch of fixes 2025-11-04 10:29:40 -03:00
Herculino Trotta 9ade58a003 feat: another batch 2025-11-03 01:40:13 -03:00
Herculino Trotta 89b2d0118d feat: another batch of fixes 2025-11-02 03:03:22 -03:00
Marcin Kisielewski 232d5003b8 locale(Polish): update translation
Currently translated at 1.4% (10 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/pl/
2025-11-01 12:17:43 +00:00
Marcin Kisielewski 133d70d3d1 locale(Polish): update translation
Currently translated at 1.2% (9 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/pl/
2025-11-01 11:17:43 +00:00
Marcin Kisielewski e70608eaaf locale((Polish)): added translation using Weblate 2025-11-01 11:08:00 +00:00
Herculino Trotta a63367a772 feat: first batch of work 2025-11-01 03:15:44 -03:00
mlystopad baef86b6cb locale(Ukrainian): update translation
Currently translated at 24.1% (168 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/uk/
2025-11-01 01:17:45 +00:00
mlystopad 3011b32fa6 locale(German): update translation
Currently translated at 93.9% (653 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/de/
2025-11-01 01:17:44 +00:00
mlystopad 910decfe00 locale(Ukrainian): update translation
Currently translated at 14.5% (101 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/uk/
2025-11-01 00:17:43 +00:00
Herculino Trotta e600d87968 feat: automated replacement 2025-10-28 14:13:30 -03:00
Herculino Trotta dd82289488 feat: automated replacement 2025-10-28 14:13:16 -03:00
Juan David Afanador 1e816ec80a locale(Spanish): update translation
Currently translated at 100.0% (695 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/es/
2025-10-27 14:17:43 +00:00
Juan David Afanador 3b5626cbd1 locale(Spanish): update translation
Currently translated at 88.0% (612 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/es/
2025-10-27 13:17:43 +00:00
Jorge Andres Marles Florez a819ceaa43 locale(Spanish): update translation
Currently translated at 82.3% (572 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/es/
2025-10-26 20:17:43 +00:00
Herculino Trotta de28dbb0f0 Merge pull request #393 from eitchtee/weblate
Translations update from Weblate
2025-10-25 11:05:50 -03:00
Juan David Afanador cfb34a4dc3 locale(Spanish): update translation
Currently translated at 80.0% (556 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/es/
2025-10-25 05:17:43 +00:00
Jorge Andres Marles Florez efdcfc192a locale(Spanish): update translation
Currently translated at 74.6% (519 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/es/
2025-10-25 04:17:43 +00:00
Juan David Afanador a7856a6671 locale(Spanish): update translation
Currently translated at 74.6% (519 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/es/
2025-10-25 04:17:43 +00:00
Juan David Afanador 7b8e3b528a locale(Spanish): update translation
Currently translated at 54.2% (377 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/es/
2025-10-24 22:23:31 +00:00
Juan David Afanador cc3244a034 locale(Spanish): update translation
Currently translated at 52.0% (362 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/es/
2025-10-24 22:17:43 +00:00
Juan David Afanador 2121a68c82 locale(Spanish): update translation
Currently translated at 41.0% (285 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/es/
2025-10-24 21:32:16 +00:00
Juan David Afanador f35002f862 locale(Spanish): update translation
Currently translated at 38.5% (268 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/es/
2025-10-24 21:22:00 +00:00
Juan David Afanador 73a992256d locale(Spanish): update translation
Currently translated at 38.5% (268 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/es/
2025-10-24 21:19:22 +00:00
Juan David Afanador 9f1098d6b9 locale(Spanish): update translation
Currently translated at 38.4% (267 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/es/
2025-10-24 21:17:43 +00:00
Juan David Afanador 2c0936b7e5 locale(Spanish): update translation
Currently translated at 34.9% (243 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/es/
2025-10-24 21:07:54 +00:00
Juan David Afanador 5fb717c3fe locale(Spanish): update translation
Currently translated at 32.6% (227 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/es/
2025-10-24 20:57:46 +00:00
Juan David Afanador c5f94fb34d locale(Spanish): update translation
Currently translated at 31.7% (221 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/es/
2025-10-24 20:55:19 +00:00
Juan David Afanador 29cdec4577 locale(Spanish): update translation
Currently translated at 31.0% (216 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/es/
2025-10-24 20:53:56 +00:00
Juan David Afanador 82efd48e53 locale(Spanish): update translation
Currently translated at 22.5% (157 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/es/
2025-10-24 20:17:43 +00:00
Juan David Afanador 5a3a0b7e5c locale(Spanish): update translation
Currently translated at 21.7% (151 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/es/
2025-10-24 20:11:34 +00:00
Juan David Afanador 41a5900f12 locale(Spanish): update translation
Currently translated at 20.7% (144 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/es/
2025-10-24 20:03:31 +00:00
Juan David Afanador 2dbdd02350 locale(Spanish): update translation
Currently translated at 20.2% (141 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/es/
2025-10-24 20:00:40 +00:00
Juan David Afanador fa0cde1a4e locale(Spanish): update translation
Currently translated at 20.1% (140 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/es/
2025-10-24 19:57:30 +00:00
doody 623d91d26f locale(Chinese (Traditional Han script)): update translation
Currently translated at 100.0% (695 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/zh_Hant/
2025-10-08 16:17:43 +00:00
Erwan Colin 57200437dc locale(French): update translation
Currently translated at 100.0% (695 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/fr/
2025-10-07 20:17:43 +00:00
doody 6f4a2b687c locale(Chinese (Traditional Han script)): update translation
Currently translated at 100.0% (695 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/zh_Hant/
2025-10-07 04:17:43 +00:00
248 changed files with 16097 additions and 16700 deletions
+29 -45
View File
@@ -12,7 +12,7 @@ on:
required: true required: true
type: string type: string
ref: ref:
description: 'Git ref to checkout (branch, tag, or SHA)' description: 'Git ref to checkout'
required: true required: true
default: 'main' default: 'main'
type: string type: string
@@ -29,73 +29,57 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: read contents: read
packages: write # Needed if you switch to GHCR, good practice
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
ref: ${{ github.event.inputs.ref }} ref: ${{ inputs.ref || github.ref }}
if: github.event_name == 'workflow_dispatch'
- name: Checkout code (non-manual)
uses: actions/checkout@v4
if: github.event_name != 'workflow_dispatch'
- name: Log in to Docker Hub - name: Log in to Docker Hub
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 uses: docker/login-action@v3
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
# This action handles all the logic for tags (nightly vs release vs custom)
- name: Docker Metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}
tags: |
# Logic for Push to Main -> nightly
type=raw,value=nightly,enable=${{ github.event_name == 'push' }}
# Logic for Release -> semver and latest
type=semver,pattern={{version}},enable=${{ github.event_name == 'release' }}
type=raw,value=latest,enable=${{ github.event_name == 'release' }}
# Logic for Manual Dispatch -> custom input
type=raw,value=${{ inputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }}
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: Build and push nightly image - name: Build and push
if: github.event_name == 'push'
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
with: with:
context: . context: .
file: ./docker/prod/django/Dockerfile file: ./docker/prod/django/Dockerfile
push: true push: true
provenance: false provenance: false
# Pass the calculated tags from the meta step
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: | build-args: |
VERSION=nightly VERSION=${{ steps.meta.outputs.version }}
tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:nightly
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Build and push release image # --- CACHE CONFIGURATION ---
if: github.event_name == 'release' # We set a specific 'scope' key.
uses: docker/build-push-action@v6 # This allows the Release tag to see the cache created by the Main branch.
with: cache-from: type=gha,scope=build-cache
context: . cache-to: type=gha,mode=max,scope=build-cache
file: ./docker/prod/django/Dockerfile
push: true
provenance: false
build-args: |
VERSION=${{ github.event.release.tag_name }}
tags: |
${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:latest
${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:${{ github.event.release.tag_name }}
platforms: linux/amd64,linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Build and push custom image
if: github.event_name == 'workflow_dispatch'
uses: docker/build-push-action@v6
with:
context: .
file: ./docker/prod/django/Dockerfile
push: true
provenance: false
build-args: |
VERSION=${{ github.event.inputs.tag }}
tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:${{ github.event.inputs.tag }}
platforms: linux/amd64,linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max
+5
View File
@@ -123,6 +123,7 @@ celerybeat.pid
# Environments # Environments
.env .env
.prod.env
.venv .venv
env/ env/
venv/ venv/
@@ -160,3 +161,7 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear # and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder. # option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/ .idea/
node_modules/
postgres_data/
.prod.env
+8
View File
@@ -0,0 +1,8 @@
{
"djlint.showInstallError": false,
"files.associations": {
"*.css": "tailwindcss"
},
"tailwindCSS.experimental.configFile": "frontend/src/styles/tailwind.css",
"djlint.profile": "django",
}
+1
View File
@@ -127,6 +127,7 @@ To create the first user, open the container's console using Unraid's UI, by cli
| variable | type | default | explanation | | variable | type | default | explanation |
|-------------------------------|-------------|-----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |-------------------------------|-------------|-----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| INTERNAL_PORT | int | 8000 | The port on which the app listens on. Defaults to 8000 if not set. |
| DJANGO_ALLOWED_HOSTS | string | localhost 127.0.0.1 | A list of space separated domains and IPs representing the host/domain names that WYGIWYH site can serve. [Click here](https://docs.djangoproject.com/en/5.1/ref/settings/#allowed-hosts) for more details | | DJANGO_ALLOWED_HOSTS | string | localhost 127.0.0.1 | A list of space separated domains and IPs representing the host/domain names that WYGIWYH site can serve. [Click here](https://docs.djangoproject.com/en/5.1/ref/settings/#allowed-hosts) for more details |
| HTTPS_ENABLED | true\|false | false | Whether to use secure cookies. If this is set to true, the cookie will be marked as “secure”, which means browsers may ensure that the cookie is only sent under an HTTPS connection | | HTTPS_ENABLED | true\|false | false | Whether to use secure cookies. If this is set to true, the cookie will be marked as “secure”, which means browsers may ensure that the cookie is only sent under an HTTPS connection |
| URL | string | http://localhost http://127.0.0.1 | A list of space separated domains and IPs (with the protocol) representing the trusted origins for unsafe requests (e.g. POST). [Click here](https://docs.djangoproject.com/en/5.1/ref/settings/#csrf-trusted-origins ) for more details | | URL | string | http://localhost http://127.0.0.1 | A list of space separated domains and IPs (with the protocol) representing the trusted origins for unsafe requests (e.g. POST). [Click here](https://docs.djangoproject.com/en/5.1/ref/settings/#csrf-trusted-origins ) for more details |
+21 -7
View File
@@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/5.1/ref/settings/
""" """
import os import os
import re
import sys import sys
from pathlib import Path from pathlib import Path
@@ -46,7 +47,7 @@ INSTALLED_APPS = [
"django.contrib.sites", "django.contrib.sites",
"whitenoise.runserver_nostatic", "whitenoise.runserver_nostatic",
"django.contrib.staticfiles", "django.contrib.staticfiles",
"webpack_boilerplate", "django_vite",
"django.contrib.humanize", "django.contrib.humanize",
"django.contrib.postgres", "django.contrib.postgres",
"django_browser_reload", "django_browser_reload",
@@ -128,6 +129,14 @@ STORAGES = {
WHITENOISE_MANIFEST_STRICT = False WHITENOISE_MANIFEST_STRICT = False
def immutable_file_test(path, url):
# Match vite (rollup)-generated hashes, à la, `some_file-CSliV9zW.js`
return re.match(r"^.+[.-][0-9a-zA-Z_-]{8,12}\..+$", url)
WHITENOISE_IMMUTABLE_FILE_TEST = immutable_file_test
WSGI_APPLICATION = "WYGIWYH.wsgi.application" WSGI_APPLICATION = "WYGIWYH.wsgi.application"
@@ -289,7 +298,7 @@ STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "static_files" STATIC_ROOT = BASE_DIR / "static_files"
STATICFILES_DIRS = [ STATICFILES_DIRS = [
ROOT_DIR / "frontend/build", ROOT_DIR / "frontend" / "build",
BASE_DIR / "static", BASE_DIR / "static",
] ]
@@ -305,9 +314,11 @@ CACHES = {
} }
} }
WEBPACK_LOADER = { DJANGO_VITE_ASSETS_PATH = STATIC_ROOT
"MANIFEST_FILE": ROOT_DIR / "frontend/build/manifest.json", DJANGO_VITE_MANIFEST_PATH = DJANGO_VITE_ASSETS_PATH / "manifest.json"
} DJANGO_VITE_DEV_MODE = DEBUG
DJANGO_VITE_DEV_SERVER_PORT = 5173
DJANGO_VITE_DEV_SERVER_HOST = "localhost"
# Default primary key field type # Default primary key field type
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field # https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
@@ -354,8 +365,11 @@ ACCOUNT_ADAPTER = "allauth.account.adapter.DefaultAccountAdapter"
SOCIALACCOUNT_ADAPTER = "allauth.socialaccount.adapter.DefaultSocialAccountAdapter" SOCIALACCOUNT_ADAPTER = "allauth.socialaccount.adapter.DefaultSocialAccountAdapter"
# CRISPY FORMS # CRISPY FORMS
CRISPY_ALLOWED_TEMPLATE_PACKS = ["bootstrap5", "crispy_forms/pure_text"] CRISPY_ALLOWED_TEMPLATE_PACKS = [
CRISPY_TEMPLATE_PACK = "bootstrap5" "crispy_forms/pure_text",
"crispy-daisyui",
]
CRISPY_TEMPLATE_PACK = "crispy-daisyui"
SESSION_EXPIRE_AT_BROWSER_CLOSE = False SESSION_EXPIRE_AT_BROWSER_CLOSE = False
SESSION_COOKIE_AGE = int(os.getenv("SESSION_EXPIRY_TIME", 2678400)) # 31 days SESSION_COOKIE_AGE = int(os.getenv("SESSION_EXPIRY_TIME", 2678400)) # 31 days
+18 -29
View File
@@ -1,23 +1,21 @@
from crispy_bootstrap5.bootstrap5 import Switch from apps.accounts.models import Account, AccountGroup
from apps.common.fields.forms.dynamic_select import (
DynamicModelChoiceField,
DynamicModelMultipleChoiceField,
)
from apps.common.widgets.crispy.daisyui import Switch
from apps.common.widgets.crispy.submit import NoClassSubmit
from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput
from apps.common.widgets.tom_select import TomSelect
from apps.currencies.models import Currency
from apps.transactions.models import TransactionCategory, TransactionTag
from crispy_forms.bootstrap import FormActions from crispy_forms.bootstrap import FormActions
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Field, Column, Row from crispy_forms.layout import Column, Field, Layout, Row
from django import forms from django import forms
from django.db.models import Q from django.db.models import Q
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from apps.accounts.models import Account
from apps.accounts.models import AccountGroup
from apps.common.fields.forms.dynamic_select import (
DynamicModelMultipleChoiceField,
DynamicModelChoiceField,
)
from apps.common.widgets.crispy.submit import NoClassSubmit
from apps.common.widgets.tom_select import TomSelect
from apps.transactions.models import TransactionCategory, TransactionTag
from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput
from apps.currencies.models import Currency
class AccountGroupForm(forms.ModelForm): class AccountGroupForm(forms.ModelForm):
class Meta: class Meta:
@@ -38,17 +36,13 @@ class AccountGroupForm(forms.ModelForm):
if self.instance and self.instance.pk: if self.instance and self.instance.pk:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
else: else:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
@@ -108,17 +102,13 @@ class AccountForm(forms.ModelForm):
if self.instance and self.instance.pk: if self.instance and self.instance.pk:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
else: else:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
@@ -156,9 +146,8 @@ class AccountBalanceForm(forms.Form):
self.helper.layout = Layout( self.helper.layout = Layout(
"new_balance", "new_balance",
Row( Row(
Column("category", css_class="form-group col-md-6 mb-0"), Column("category"),
Column("tags", css_class="form-group col-md-6 mb-0"), Column("tags"),
css_class="form-row",
), ),
Field("account_id"), Field("account_id"),
) )
+11 -16
View File
@@ -1,14 +1,13 @@
from crispy_forms.bootstrap import FormActions
from django import forms
from django.contrib.auth import get_user_model
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Field, Submit, Div, HTML
from apps.common.widgets.tom_select import TomSelect, TomSelectMultiple
from apps.common.models import SharedObject from apps.common.models import SharedObject
from apps.common.widgets.crispy.submit import NoClassSubmit from apps.common.widgets.crispy.submit import NoClassSubmit
from apps.common.widgets.tom_select import TomSelect, TomSelectMultiple
from crispy_forms.bootstrap import FormActions
from crispy_forms.helper import FormHelper
from crispy_forms.layout import HTML, Div, Field, Layout, Submit
from django import forms
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
User = get_user_model() User = get_user_model()
@@ -39,6 +38,7 @@ class SharedObjectForm(forms.Form):
choices=SharedObject.Visibility.choices, choices=SharedObject.Visibility.choices,
required=True, required=True,
label=_("Visibility"), label=_("Visibility"),
widget=TomSelect(clear_button=False),
help_text=_( help_text=_(
"Private: Only shown for the owner and shared users. Only editable by the owner." "Private: Only shown for the owner and shared users. Only editable by the owner."
"<br/>" "<br/>"
@@ -48,9 +48,6 @@ class SharedObjectForm(forms.Form):
class Meta: class Meta:
fields = ["visibility", "shared_with_users"] fields = ["visibility", "shared_with_users"]
widgets = {
"visibility": TomSelect(clear_button=False),
}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
# Get the current user to filter available sharing options # Get the current user to filter available sharing options
@@ -73,12 +70,10 @@ class SharedObjectForm(forms.Form):
self.helper.layout = Layout( self.helper.layout = Layout(
Field("owner"), Field("owner"),
Field("visibility"), Field("visibility"),
HTML("<hr>"), HTML('<hr class="hr my-3">'),
Field("shared_with_users"), Field("shared_with_users"),
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Save"), css_class="btn btn-primary"),
"submit", _("Save"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
@@ -0,0 +1,13 @@
from django import forms, template
register = template.Library()
@register.filter
def is_input(field):
return isinstance(field.field.widget, forms.TextInput)
@register.filter
def is_textarea(field):
return isinstance(field.field.widget, forms.Textarea)
+1 -1
View File
@@ -11,7 +11,7 @@ def toast_bg(tags):
elif "warning" in tags: elif "warning" in tags:
return "warning" return "warning"
elif "error" in tags: elif "error" in tags:
return "danger" return "error"
elif "info" in tags: elif "info" in tags:
return "info" return "info"
@@ -0,0 +1,5 @@
from crispy_forms.layout import Field
class Switch(Field):
template = "crispy-daisyui/layout/switch.html"
+7 -6
View File
@@ -1,15 +1,14 @@
import datetime import datetime
from django.forms import widgets from apps.common.functions.format import get_format
from django.utils import formats, translation, dates
from django.utils.translation import gettext_lazy as _
from apps.common.utils.django import ( from apps.common.utils.django import (
django_to_python_datetime,
django_to_airdatepicker_datetime, django_to_airdatepicker_datetime,
django_to_airdatepicker_datetime_separated, django_to_airdatepicker_datetime_separated,
django_to_python_datetime,
) )
from apps.common.functions.format import get_format from django.forms import widgets
from django.utils import dates, formats, translation
from django.utils.translation import gettext_lazy as _
class AirDatePickerInput(widgets.DateInput): class AirDatePickerInput(widgets.DateInput):
@@ -52,6 +51,8 @@ class AirDatePickerInput(widgets.DateInput):
def build_attrs(self, base_attrs, extra_attrs=None): def build_attrs(self, base_attrs, extra_attrs=None):
attrs = super().build_attrs(base_attrs, extra_attrs) attrs = super().build_attrs(base_attrs, extra_attrs)
attrs["class"] = attrs.get("class", "") + " input"
attrs["data-now-button-txt"] = _("Today") attrs["data-now-button-txt"] = _("Today")
attrs["data-auto-close"] = str(self.auto_close).lower() attrs["data-auto-close"] = str(self.auto_close).lower()
attrs["data-clear-button"] = str(self.clear_button).lower() attrs["data-clear-button"] = str(self.clear_button).lower()
+2 -2
View File
@@ -1,4 +1,4 @@
from django.forms import widgets, SelectMultiple from django.forms import SelectMultiple, widgets
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@@ -17,7 +17,7 @@ class TomSelect(widgets.Select):
checkboxes=False, checkboxes=False,
group_by=None, group_by=None,
*args, *args,
**kwargs **kwargs,
): ):
super().__init__(attrs, *args, **kwargs) super().__init__(attrs, *args, **kwargs)
self.remove_button = remove_button self.remove_button = remove_button
+15 -28
View File
@@ -1,16 +1,15 @@
from crispy_bootstrap5.bootstrap5 import Switch from apps.common.widgets.crispy.daisyui import Switch
from crispy_forms.bootstrap import FormActions
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Row, Column
from django import forms
from django.forms import CharField
from django.utils.translation import gettext_lazy as _
from apps.common.widgets.crispy.submit import NoClassSubmit from apps.common.widgets.crispy.submit import NoClassSubmit
from apps.common.widgets.datepicker import AirDateTimePickerInput from apps.common.widgets.datepicker import AirDateTimePickerInput
from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput
from apps.common.widgets.tom_select import TomSelect from apps.common.widgets.tom_select import TomSelect
from apps.currencies.models import Currency, ExchangeRate, ExchangeRateService from apps.currencies.models import Currency, ExchangeRate, ExchangeRateService
from crispy_forms.bootstrap import FormActions
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Column, Layout, Row
from django import forms
from django.forms import CharField
from django.utils.translation import gettext_lazy as _
class CurrencyForm(forms.ModelForm): class CurrencyForm(forms.ModelForm):
@@ -51,17 +50,13 @@ class CurrencyForm(forms.ModelForm):
if self.instance and self.instance.pk: if self.instance and self.instance.pk:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
else: else:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
@@ -89,17 +84,13 @@ class ExchangeRateForm(forms.ModelForm):
if self.instance and self.instance.pk: if self.instance and self.instance.pk:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
else: else:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
@@ -132,8 +123,8 @@ class ExchangeRateServiceForm(forms.ModelForm):
Switch("singleton"), Switch("singleton"),
"api_key", "api_key",
Row( Row(
Column("interval_type", css_class="form-group col-md-6"), Column("interval_type"),
Column("fetch_interval", css_class="form-group col-md-6"), Column("fetch_interval"),
), ),
"target_currencies", "target_currencies",
"target_accounts", "target_accounts",
@@ -142,16 +133,12 @@ class ExchangeRateServiceForm(forms.ModelForm):
if self.instance and self.instance.pk: if self.instance and self.instance.pk:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
else: else:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
+25 -47
View File
@@ -1,22 +1,20 @@
from crispy_bootstrap5.bootstrap5 import Switch, BS5Accordion
from crispy_forms.bootstrap import FormActions, AccordionGroup
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Row, Column, HTML
from django import forms
from django.utils.translation import gettext_lazy as _
from apps.accounts.models import Account from apps.accounts.models import Account
from apps.common.widgets.crispy.submit import NoClassSubmit
from apps.common.widgets.datepicker import AirDatePickerInput
from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput
from apps.common.widgets.tom_select import TomSelect
from apps.dca.models import DCAStrategy, DCAEntry
from apps.common.widgets.tom_select import TransactionSelect
from apps.transactions.models import Transaction, TransactionTag, TransactionCategory
from apps.common.fields.forms.dynamic_select import ( from apps.common.fields.forms.dynamic_select import (
DynamicModelChoiceField, DynamicModelChoiceField,
DynamicModelMultipleChoiceField, DynamicModelMultipleChoiceField,
) )
from apps.common.widgets.crispy.daisyui import Switch
from apps.common.widgets.crispy.submit import NoClassSubmit
from apps.common.widgets.datepicker import AirDatePickerInput
from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput
from apps.common.widgets.tom_select import TomSelect, TransactionSelect
from apps.dca.models import DCAEntry, DCAStrategy
from apps.transactions.models import Transaction, TransactionCategory, TransactionTag
from crispy_forms.bootstrap import AccordionGroup, FormActions, Accordion
from crispy_forms.helper import FormHelper
from crispy_forms.layout import HTML, Column, Layout, Row
from django import forms
from django.utils.translation import gettext_lazy as _
class DCAStrategyForm(forms.ModelForm): class DCAStrategyForm(forms.ModelForm):
@@ -36,8 +34,8 @@ class DCAStrategyForm(forms.ModelForm):
self.helper.layout = Layout( self.helper.layout = Layout(
"name", "name",
Row( Row(
Column("payment_currency", css_class="form-group col-md-6"), Column("payment_currency"),
Column("target_currency", css_class="form-group col-md-6"), Column("target_currency"),
), ),
"notes", "notes",
) )
@@ -45,17 +43,13 @@ class DCAStrategyForm(forms.ModelForm):
if self.instance and self.instance.pk: if self.instance and self.instance.pk:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
else: else:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
@@ -155,11 +149,11 @@ class DCAEntryForm(forms.ModelForm):
self.helper.layout = Layout( self.helper.layout = Layout(
"date", "date",
Row( Row(
Column("amount_paid", css_class="form-group col-md-6"), Column("amount_paid"),
Column("amount_received", css_class="form-group col-md-6"), Column("amount_received"),
), ),
"notes", "notes",
BS5Accordion( Accordion(
AccordionGroup( AccordionGroup(
_("Create transaction"), _("Create transaction"),
Switch("create_transaction"), Switch("create_transaction"),
@@ -168,19 +162,11 @@ class DCAEntryForm(forms.ModelForm):
Row( Row(
Column( Column(
"from_account", "from_account",
css_class="form-group",
), ),
css_class="form-row",
), ),
Row( Row(
Column( Column("from_category"),
"from_category", Column("from_tags"),
css_class="form-group col-md-6 mb-0",
),
Column(
"from_tags", css_class="form-group col-md-6 mb-0"
),
css_class="form-row",
), ),
), ),
css_class="p-1 mx-1 my-3 border rounded-3", css_class="p-1 mx-1 my-3 border rounded-3",
@@ -192,14 +178,10 @@ class DCAEntryForm(forms.ModelForm):
"to_account", "to_account",
css_class="form-group", css_class="form-group",
), ),
css_class="form-row",
), ),
Row( Row(
Column( Column("to_category"),
"to_category", css_class="form-group col-md-6 mb-0" Column("to_tags"),
),
Column("to_tags", css_class="form-group col-md-6 mb-0"),
css_class="form-row",
), ),
), ),
css_class="p-1 mx-1 my-3 border rounded-3", css_class="p-1 mx-1 my-3 border rounded-3",
@@ -220,17 +202,13 @@ class DCAEntryForm(forms.ModelForm):
if self.instance and self.instance.pk: if self.instance and self.instance.pk:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
else: else:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
+5 -10
View File
@@ -1,11 +1,10 @@
from apps.common.widgets.crispy.submit import NoClassSubmit
from crispy_forms.bootstrap import FormActions from crispy_forms.bootstrap import FormActions
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, HTML from crispy_forms.layout import HTML, Layout
from django import forms from django import forms
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from apps.common.widgets.crispy.submit import NoClassSubmit
class ExportForm(forms.Form): class ExportForm(forms.Form):
users = forms.BooleanField( users = forms.BooleanField(
@@ -115,9 +114,7 @@ class ExportForm(forms.Form):
"dca", "dca",
"import_profiles", "import_profiles",
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Export"), css_class="btn btn-primary"),
"submit", _("Export"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
@@ -162,7 +159,7 @@ class RestoreForm(forms.Form):
self.helper.form_method = "post" self.helper.form_method = "post"
self.helper.layout = Layout( self.helper.layout = Layout(
"zip_file", "zip_file",
HTML("<hr />"), HTML('<hr class="hr my-3"/>'),
"users", "users",
"accounts", "accounts",
"currencies", "currencies",
@@ -181,9 +178,7 @@ class RestoreForm(forms.Form):
"dca_entries", "dca_entries",
"import_profiles", "import_profiles",
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Restore"), css_class="btn btn-primary"),
"submit", _("Restore"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
+5 -12
View File
@@ -1,3 +1,5 @@
from apps.common.widgets.crispy.submit import NoClassSubmit
from apps.import_app.models import ImportProfile
from crispy_forms.bootstrap import FormActions from crispy_forms.bootstrap import FormActions
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import ( from crispy_forms.layout import (
@@ -6,9 +8,6 @@ from crispy_forms.layout import (
from django import forms from django import forms
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from apps.import_app.models import ImportProfile
from apps.common.widgets.crispy.submit import NoClassSubmit
class ImportProfileForm(forms.ModelForm): class ImportProfileForm(forms.ModelForm):
class Meta: class Meta:
@@ -30,17 +29,13 @@ class ImportProfileForm(forms.ModelForm):
if self.instance and self.instance.pk: if self.instance and self.instance.pk:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
else: else:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
@@ -57,8 +52,6 @@ class ImportRunFileUploadForm(forms.Form):
self.helper.layout = Layout( self.helper.layout = Layout(
"file", "file",
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Import"), css_class="btn btn-primary"),
"submit", _("Import"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
+12 -13
View File
@@ -1,15 +1,14 @@
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Field, Row, Column
from django import forms
from django.utils.translation import gettext_lazy as _
from apps.common.widgets.datepicker import ( from apps.common.widgets.datepicker import (
AirDatePickerInput,
AirMonthYearPickerInput, AirMonthYearPickerInput,
AirYearPickerInput, AirYearPickerInput,
AirDatePickerInput,
) )
from apps.transactions.models import TransactionCategory
from apps.common.widgets.tom_select import TomSelect from apps.common.widgets.tom_select import TomSelect
from apps.transactions.models import TransactionCategory
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Column, Field, Layout, Row
from django import forms
from django.utils.translation import gettext_lazy as _
class SingleMonthForm(forms.Form): class SingleMonthForm(forms.Form):
@@ -59,8 +58,8 @@ class MonthRangeForm(forms.Form):
self.helper.layout = Layout( self.helper.layout = Layout(
Row( Row(
Column("month_from", css_class="form-group col-md-6"), Column("month_from"),
Column("month_to", css_class="form-group col-md-6"), Column("month_to"),
), ),
) )
@@ -82,8 +81,8 @@ class YearRangeForm(forms.Form):
self.helper.layout = Layout( self.helper.layout = Layout(
Row( Row(
Column("year_from", css_class="form-group col-md-6"), Column("year_from"),
Column("year_to", css_class="form-group col-md-6"), Column("year_to"),
), ),
) )
@@ -105,8 +104,8 @@ class DateRangeForm(forms.Form):
self.helper.layout = Layout( self.helper.layout = Layout(
Row( Row(
Column("date_from", css_class="form-group col-md-6"), Column("date_from"),
Column("date_to", css_class="form-group col-md-6"), Column("date_to"),
css_class="mb-0", css_class="mb-0",
), ),
) )
+52 -69
View File
@@ -1,20 +1,21 @@
from crispy_bootstrap5.bootstrap5 import Switch, BS5Accordion from apps.common.fields.forms.dynamic_select import DynamicModelChoiceField
from crispy_forms.bootstrap import FormActions, AccordionGroup from apps.common.widgets.crispy.daisyui import Switch
from apps.common.widgets.crispy.submit import NoClassSubmit
from apps.common.widgets.tom_select import TomSelect, TransactionSelect
from apps.rules.models import (
TransactionRule,
TransactionRuleAction,
UpdateOrCreateTransactionRuleAction,
)
from apps.transactions.forms import BulkEditTransactionForm
from apps.transactions.models import Transaction
from crispy_forms.bootstrap import AccordionGroup, FormActions, Accordion
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Field, Row, Column, HTML from crispy_forms.layout import HTML, Column, Field, Layout, Row
from django import forms from django import forms
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from apps.common.widgets.crispy.submit import NoClassSubmit
from apps.common.widgets.crispy.submit import NoClassSubmit
from apps.common.widgets.tom_select import TomSelect, TransactionSelect
from apps.rules.models import TransactionRule, UpdateOrCreateTransactionRuleAction
from apps.rules.models import TransactionRuleAction
from apps.common.fields.forms.dynamic_select import DynamicModelChoiceField
from apps.transactions.forms import BulkEditTransactionForm
from apps.transactions.models import Transaction
class TransactionRuleForm(forms.ModelForm): class TransactionRuleForm(forms.ModelForm):
class Meta: class Meta:
@@ -53,17 +54,13 @@ class TransactionRuleForm(forms.ModelForm):
if self.instance and self.instance.pk: if self.instance and self.instance.pk:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
else: else:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
@@ -97,17 +94,13 @@ class TransactionRuleActionForm(forms.ModelForm):
if self.instance and self.instance.pk: if self.instance and self.instance.pk:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
else: else:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
@@ -214,148 +207,148 @@ class UpdateOrCreateTransactionRuleActionForm(forms.ModelForm):
self.helper.layout = Layout( self.helper.layout = Layout(
"order", "order",
BS5Accordion( Accordion(
AccordionGroup( AccordionGroup(
_("Search Criteria"), _("Search Criteria"),
Field("filter", rows=1), Field("filter", rows=1),
Row( Row(
Column( Column(
Field("search_type_operator"), Field("search_type_operator"),
css_class="form-group col-md-4", css_class="col-span-12 md:col-span-4",
), ),
Column( Column(
Field("search_type", rows=1), Field("search_type", rows=1),
css_class="form-group col-md-8", css_class="col-span-12 md:col-span-8",
), ),
), ),
Row( Row(
Column( Column(
Field("search_is_paid_operator"), Field("search_is_paid_operator"),
css_class="form-group col-md-4", css_class="col-span-12 md:col-span-4",
), ),
Column( Column(
Field("search_is_paid", rows=1), Field("search_is_paid", rows=1),
css_class="form-group col-md-8", css_class="col-span-12 md:col-span-8",
), ),
), ),
Row( Row(
Column( Column(
Field("search_mute_operator"), Field("search_mute_operator"),
css_class="form-group col-md-4", css_class="col-span-12 md:col-span-4",
), ),
Column( Column(
Field("search_mute", rows=1), Field("search_mute", rows=1),
css_class="form-group col-md-8", css_class="col-span-12 md:col-span-8",
), ),
), ),
Row( Row(
Column( Column(
Field("search_account_operator"), Field("search_account_operator"),
css_class="form-group col-md-4", css_class="col-span-12 md:col-span-4",
), ),
Column( Column(
Field("search_account", rows=1), Field("search_account", rows=1),
css_class="form-group col-md-8", css_class="col-span-12 md:col-span-8",
), ),
), ),
Row( Row(
Column( Column(
Field("search_entities_operator"), Field("search_entities_operator"),
css_class="form-group col-md-4", css_class="col-span-12 md:col-span-4",
), ),
Column( Column(
Field("search_entities", rows=1), Field("search_entities", rows=1),
css_class="form-group col-md-8", css_class="col-span-12 md:col-span-8",
), ),
), ),
Row( Row(
Column( Column(
Field("search_date_operator"), Field("search_date_operator"),
css_class="form-group col-md-4", css_class="col-span-12 md:col-span-4",
), ),
Column( Column(
Field("search_date", rows=1), Field("search_date", rows=1),
css_class="form-group col-md-8", css_class="col-span-12 md:col-span-8",
), ),
), ),
Row( Row(
Column( Column(
Field("search_reference_date_operator"), Field("search_reference_date_operator"),
css_class="form-group col-md-4", css_class="col-span-12 md:col-span-4",
), ),
Column( Column(
Field("search_reference_date", rows=1), Field("search_reference_date", rows=1),
css_class="form-group col-md-8", css_class="col-span-12 md:col-span-8",
), ),
), ),
Row( Row(
Column( Column(
Field("search_description_operator"), Field("search_description_operator"),
css_class="form-group col-md-4", css_class="col-span-12 md:col-span-4",
), ),
Column( Column(
Field("search_description", rows=1), Field("search_description", rows=1),
css_class="form-group col-md-8", css_class="col-span-12 md:col-span-8",
), ),
), ),
Row( Row(
Column( Column(
Field("search_amount_operator"), Field("search_amount_operator"),
css_class="form-group col-md-4", css_class="col-span-12 md:col-span-4",
), ),
Column( Column(
Field("search_amount", rows=1), Field("search_amount", rows=1),
css_class="form-group col-md-8", css_class="col-span-12 md:col-span-8",
), ),
), ),
Row( Row(
Column( Column(
Field("search_category_operator"), Field("search_category_operator"),
css_class="form-group col-md-4", css_class="col-span-12 md:col-span-4",
), ),
Column( Column(
Field("search_category", rows=1), Field("search_category", rows=1),
css_class="form-group col-md-8", css_class="col-span-12 md:col-span-8",
), ),
), ),
Row( Row(
Column( Column(
Field("search_tags_operator"), Field("search_tags_operator"),
css_class="form-group col-md-4", css_class="col-span-12 md:col-span-4",
), ),
Column( Column(
Field("search_tags", rows=1), Field("search_tags", rows=1),
css_class="form-group col-md-8", css_class="col-span-12 md:col-span-8",
), ),
), ),
Row( Row(
Column( Column(
Field("search_notes_operator"), Field("search_notes_operator"),
css_class="form-group col-md-4", css_class="col-span-12 md:col-span-4",
), ),
Column( Column(
Field("search_notes", rows=1), Field("search_notes", rows=1),
css_class="form-group col-md-8", css_class="col-span-12 md:col-span-8",
), ),
), ),
Row( Row(
Column( Column(
Field("search_internal_note_operator"), Field("search_internal_note_operator"),
css_class="form-group col-md-4", css_class="col-span-12 md:col-span-4",
), ),
Column( Column(
Field("search_internal_note", rows=1), Field("search_internal_note", rows=1),
css_class="form-group col-md-8", css_class="col-span-12 md:col-span-8",
), ),
), ),
Row( Row(
Column( Column(
Field("search_internal_id_operator"), Field("search_internal_id_operator"),
css_class="form-group col-md-4", css_class="col-span-12 md:col-span-4",
), ),
Column( Column(
Field("search_internal_id", rows=1), Field("search_internal_id", rows=1),
css_class="form-group col-md-8", css_class="col-span-12 md:col-span-8",
), ),
), ),
active=True, active=True,
@@ -386,17 +379,13 @@ class UpdateOrCreateTransactionRuleActionForm(forms.ModelForm):
if self.instance and self.instance.pk: if self.instance and self.instance.pk:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
else: else:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
@@ -427,9 +416,7 @@ class DryRunCreatedTransacion(forms.Form):
self.helper.layout = Layout( self.helper.layout = Layout(
"transaction", "transaction",
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Test"), css_class="btn btn-primary"),
"submit", _("Test"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
@@ -464,9 +451,7 @@ class DryRunDeletedTransacion(forms.Form):
self.helper.layout = Layout( self.helper.layout = Layout(
"transaction", "transaction",
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Test"), css_class="btn btn-primary"),
"submit", _("Test"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
@@ -496,13 +481,11 @@ class DryRunUpdatedTransactionForm(BulkEditTransactionForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.helper.layout.insert(0, "transaction") self.helper.layout.insert(0, "transaction")
self.helper.layout.insert(1, HTML("<hr/>")) self.helper.layout.insert(1, HTML('<hr class="hr my-3" />'))
# Change submit button # Change submit button
self.helper.layout[-1] = FormActions( self.helper.layout[-1] = FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Test"), css_class="btn btn-primary")
"submit", _("Test"), css_class="btn btn-outline-primary w-100"
)
) )
if self.data.get("transaction"): if self.data.get("transaction"):
+1 -1
View File
@@ -564,7 +564,7 @@ def dry_run_rule_updated(request, pk):
response = render( response = render(
request, request,
"rules/fragments/transaction_rule/dry_run/created.html", "rules/fragments/transaction_rule/dry_run/updated.html",
{"form": form, "rule": rule, "logs": logs, "results": results}, {"form": form, "rule": rule, "logs": logs, "results": results},
) )
+11 -14
View File
@@ -1,11 +1,4 @@
import django_filters import django_filters
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Field, Row, Column
from django import forms
from django.db.models import Q
from django.utils.translation import gettext_lazy as _
from django_filters import Filter
from apps.accounts.models import Account from apps.accounts.models import Account
from apps.common.fields.month_year import MonthYearFormField from apps.common.fields.month_year import MonthYearFormField
from apps.common.widgets.datepicker import AirDatePickerInput from apps.common.widgets.datepicker import AirDatePickerInput
@@ -15,9 +8,15 @@ from apps.currencies.models import Currency
from apps.transactions.models import ( from apps.transactions.models import (
Transaction, Transaction,
TransactionCategory, TransactionCategory,
TransactionTag,
TransactionEntity, TransactionEntity,
TransactionTag,
) )
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Column, Field, Layout, Row
from django import forms
from django.db.models import Q
from django.utils.translation import gettext_lazy as _
from django_filters import Filter
SITUACAO_CHOICES = ( SITUACAO_CHOICES = (
("1", _("Paid")), ("1", _("Paid")),
@@ -159,14 +158,12 @@ class TransactionsFilter(django_filters.FilterSet):
Field("description"), Field("description"),
Row(Column("date_start"), Column("date_end")), Row(Column("date_start"), Column("date_end")),
Row( Row(
Column("reference_date_start", css_class="form-group col-md-6 mb-0"), Column("reference_date_start"),
Column("reference_date_end", css_class="form-group col-md-6 mb-0"), Column("reference_date_end"),
css_class="form-row",
), ),
Row( Row(
Column("from_amount", css_class="form-group col-md-6 mb-0"), Column("from_amount"),
Column("to_amount", css_class="form-group col-md-6 mb-0"), Column("to_amount"),
css_class="form-row",
), ),
Field("account", size=1), Field("account", size=1),
Field("currency", size=1), Field("currency", size=1),
+88 -168
View File
@@ -1,39 +1,38 @@
from copy import deepcopy from copy import deepcopy
from crispy_bootstrap5.bootstrap5 import Switch, BS5Accordion
from crispy_forms.bootstrap import FormActions, AccordionGroup, AppendedText
from crispy_forms.helper import FormHelper
from crispy_forms.layout import (
Layout,
Row,
Column,
Field,
Div,
HTML,
)
from django import forms
from django.db.models import Q
from django.utils.translation import gettext_lazy as _
from apps.accounts.models import Account from apps.accounts.models import Account
from apps.common.fields.forms.dynamic_select import ( from apps.common.fields.forms.dynamic_select import (
DynamicModelChoiceField, DynamicModelChoiceField,
DynamicModelMultipleChoiceField, DynamicModelMultipleChoiceField,
) )
from apps.common.widgets.crispy.daisyui import Switch
from apps.common.widgets.crispy.submit import NoClassSubmit from apps.common.widgets.crispy.submit import NoClassSubmit
from apps.common.widgets.datepicker import AirDatePickerInput, AirMonthYearPickerInput from apps.common.widgets.datepicker import AirDatePickerInput, AirMonthYearPickerInput
from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput
from apps.common.widgets.tom_select import TomSelect from apps.common.widgets.tom_select import TomSelect
from apps.rules.signals import transaction_created, transaction_updated from apps.rules.signals import transaction_created, transaction_updated
from apps.transactions.models import ( from apps.transactions.models import (
InstallmentPlan,
QuickTransaction,
RecurringTransaction,
Transaction, Transaction,
TransactionCategory, TransactionCategory,
TransactionTag,
InstallmentPlan,
RecurringTransaction,
TransactionEntity, TransactionEntity,
QuickTransaction, TransactionTag,
) )
from crispy_forms.bootstrap import AccordionGroup, AppendedText, FormActions, Accordion
from crispy_forms.helper import FormHelper
from crispy_forms.layout import (
HTML,
Column,
Div,
Field,
Layout,
Row,
)
from django import forms
from django.db.models import Q
from django.utils.translation import gettext_lazy as _
class TransactionForm(forms.ModelForm): class TransactionForm(forms.ModelForm):
@@ -134,21 +133,18 @@ class TransactionForm(forms.ModelForm):
), ),
Field("is_paid", template="transactions/widgets/paid_toggle_button.html"), Field("is_paid", template="transactions/widgets/paid_toggle_button.html"),
Row( Row(
Column("account", css_class="form-group col-md-6 mb-0"), Column("account"),
Column("entities", css_class="form-group col-md-6 mb-0"), Column("entities"),
css_class="form-row",
), ),
Row( Row(
Column(Field("date"), css_class="form-group col-md-6 mb-0"), Column(Field("date")),
Column(Field("reference_date"), css_class="form-group col-md-6 mb-0"), Column(Field("reference_date")),
css_class="form-row",
), ),
"description", "description",
Field("amount", inputmode="decimal"), Field("amount", inputmode="decimal"),
Row( Row(
Column("category", css_class="form-group col-md-6 mb-0"), Column("category"),
Column("tags", css_class="form-group col-md-6 mb-0"), Column("tags"),
css_class="form-row",
), ),
"notes", "notes",
) )
@@ -164,20 +160,18 @@ class TransactionForm(forms.ModelForm):
Field("is_paid", template="transactions/widgets/paid_toggle_button.html"), Field("is_paid", template="transactions/widgets/paid_toggle_button.html"),
"account", "account",
Row( Row(
Column(Field("date"), css_class="form-group col-md-6 mb-0"), Column(Field("date")),
Column(Field("reference_date"), css_class="form-group col-md-6 mb-0"), Column(Field("reference_date")),
css_class="form-row",
), ),
"description", "description",
Field("amount", inputmode="decimal"), Field("amount", inputmode="decimal"),
BS5Accordion( Accordion(
AccordionGroup( AccordionGroup(
_("More"), _("More"),
"entities", "entities",
Row( Row(
Column("category", css_class="form-group col-md-6 mb-0"), Column("category"),
Column("tags", css_class="form-group col-md-6 mb-0"), Column("tags"),
css_class="form-row",
), ),
"notes", "notes",
active=False, active=False,
@@ -187,9 +181,7 @@ class TransactionForm(forms.ModelForm):
css_class="mb-3", css_class="mb-3",
), ),
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
@@ -202,29 +194,25 @@ class TransactionForm(forms.ModelForm):
) )
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
else: else:
self.fields["amount"].widget = ArbitraryDecimalDisplayNumberInput() self.fields["amount"].widget = ArbitraryDecimalDisplayNumberInput()
self.helper.layout.append( self.helper.layout.append(
Div( Div(
NoClassSubmit( NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
"submit", _("Add"), css_class="btn btn-outline-primary"
),
NoClassSubmit( NoClassSubmit(
"submit_and_similar", "submit_and_similar",
_("Save and add similar"), _("Save and add similar"),
css_class="btn btn-outline-primary", css_class="btn btn-primary btn-soft",
), ),
NoClassSubmit( NoClassSubmit(
"submit_and_another", "submit_and_another",
_("Save and add another"), _("Save and add another"),
css_class="btn btn-outline-primary", css_class="btn btn-primary btn-soft",
), ),
css_class="d-grid gap-2", css_class="flex flex-col gap-2 mt-3",
), ),
) )
@@ -348,18 +336,16 @@ class QuickTransactionForm(forms.ModelForm):
), ),
Field("is_paid", template="transactions/widgets/paid_toggle_button.html"), Field("is_paid", template="transactions/widgets/paid_toggle_button.html"),
"name", "name",
HTML("<hr />"), HTML('<hr class="hr my-3" />'),
Row( Row(
Column("account", css_class="form-group col-md-6 mb-0"), Column("account"),
Column("entities", css_class="form-group col-md-6 mb-0"), Column("entities"),
css_class="form-row",
), ),
"description", "description",
Field("amount", inputmode="decimal"), Field("amount", inputmode="decimal"),
Row( Row(
Column("category", css_class="form-group col-md-6 mb-0"), Column("category"),
Column("tags", css_class="form-group col-md-6 mb-0"), Column("tags"),
css_class="form-row",
), ),
"notes", "notes",
Switch("mute"), Switch("mute"),
@@ -372,19 +358,14 @@ class QuickTransactionForm(forms.ModelForm):
) )
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
else: else:
self.fields["amount"].widget = ArbitraryDecimalDisplayNumberInput() self.fields["amount"].widget = ArbitraryDecimalDisplayNumberInput()
self.helper.layout.append( self.helper.layout.append(
Div( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
"submit", _("Add"), css_class="btn btn-outline-primary"
),
css_class="d-grid gap-2",
), ),
) )
@@ -481,27 +462,22 @@ class BulkEditTransactionForm(forms.Form):
template="transactions/widgets/unselectable_paid_toggle_button.html", template="transactions/widgets/unselectable_paid_toggle_button.html",
), ),
Row( Row(
Column("account", css_class="form-group col-md-6 mb-0"), Column("account"),
Column("entities", css_class="form-group col-md-6 mb-0"), Column("entities"),
css_class="form-row",
), ),
Row( Row(
Column(Field("date"), css_class="form-group col-md-6 mb-0"), Column(Field("date")),
Column(Field("reference_date"), css_class="form-group col-md-6 mb-0"), Column(Field("reference_date")),
css_class="form-row",
), ),
"description", "description",
Field("amount", inputmode="decimal"), Field("amount", inputmode="decimal"),
Row( Row(
Column("category", css_class="form-group col-md-6 mb-0"), Column("category"),
Column("tags", css_class="form-group col-md-6 mb-0"), Column("tags"),
css_class="form-row",
), ),
"notes", "notes",
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
@@ -600,62 +576,34 @@ class TransferForm(forms.Form):
self.helper.layout = Layout( self.helper.layout = Layout(
Row( Row(
Column(Field("date"), css_class="form-group col-md-6 mb-0"), Column(Field("date")),
Column( Column(
Field("reference_date"), Field("reference_date"),
css_class="form-group col-md-6 mb-0",
), ),
css_class="form-row",
), ),
Field("description"), Field("description"),
Field("notes"), Field("notes"),
Switch("mute"), Switch("mute"),
Row( Row(
Column( Column("from_account"),
Row( Column(Field("from_amount")),
Column( Column("from_category"),
"from_account", Column("from_tags"),
css_class="form-group col-md-6 mb-0", css_class="bg-base-100 rounded-box p-4 border-base-content/60 border my-3",
), ),
Column(
Field("from_amount"),
css_class="form-group col-md-6 mb-0",
),
css_class="form-row",
),
Row(
Column("from_category", css_class="form-group col-md-6 mb-0"),
Column("from_tags", css_class="form-group col-md-6 mb-0"),
css_class="form-row",
),
),
css_class="p-1 mx-1 my-3 border rounded-3",
),
Row(
Column(
Row( Row(
Column( Column(
"to_account", "to_account",
css_class="form-group col-md-6 mb-0",
), ),
Column( Column(
Field("to_amount"), Field("to_amount"),
css_class="form-group col-md-6 mb-0",
), ),
css_class="form-row", Column("to_category"),
), Column("to_tags"),
Row( css_class="bg-base-100 rounded-box p-4 border-base-content/60 border",
Column("to_category", css_class="form-group col-md-6 mb-0"),
Column("to_tags", css_class="form-group col-md-6 mb-0"),
css_class="form-row",
),
),
css_class="p-1 mx-1 my-3 border rounded-3",
), ),
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Transfer"), css_class="btn btn-primary"),
"submit", _("Transfer"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
@@ -841,30 +789,26 @@ class InstallmentPlanForm(forms.ModelForm):
template="transactions/widgets/income_expense_toggle_buttons.html", template="transactions/widgets/income_expense_toggle_buttons.html",
), ),
Row( Row(
Column("account", css_class="form-group col-md-6 mb-0"), Column("account"),
Column("entities", css_class="form-group col-md-6 mb-0"), Column("entities"),
css_class="form-row",
), ),
"description", "description",
Switch("add_description_to_transaction"), Switch("add_description_to_transaction"),
"notes", "notes",
Switch("add_notes_to_transaction"), Switch("add_notes_to_transaction"),
Row( Row(
Column("number_of_installments", css_class="form-group col-md-6 mb-0"), Column("number_of_installments"),
Column("installment_start", css_class="form-group col-md-6 mb-0"), Column("installment_start"),
css_class="form-row",
), ),
Row( Row(
Column("start_date", css_class="form-group col-md-4 mb-0"), Column("start_date", css_class="col-span-12 md:col-span-4"),
Column("reference_date", css_class="form-group col-md-4 mb-0"), Column("reference_date", css_class="col-span-12 md:col-span-4"),
Column("recurrence", css_class="form-group col-md-4 mb-0"), Column("recurrence", css_class="col-span-12 md:col-span-4"),
css_class="form-row",
), ),
"installment_amount", "installment_amount",
Row( Row(
Column("category", css_class="form-group col-md-6 mb-0"), Column("category"),
Column("tags", css_class="form-group col-md-6 mb-0"), Column("tags"),
css_class="form-row",
), ),
) )
@@ -874,17 +818,13 @@ class InstallmentPlanForm(forms.ModelForm):
if self.instance and self.instance.pk: if self.instance and self.instance.pk:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
else: else:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
@@ -917,17 +857,13 @@ class TransactionTagForm(forms.ModelForm):
if self.instance and self.instance.pk: if self.instance and self.instance.pk:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
else: else:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
@@ -949,17 +885,13 @@ class TransactionEntityForm(forms.ModelForm):
if self.instance and self.instance.pk: if self.instance and self.instance.pk:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
else: else:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
@@ -984,17 +916,13 @@ class TransactionCategoryForm(forms.ModelForm):
if self.instance and self.instance.pk: if self.instance and self.instance.pk:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
else: else:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
@@ -1103,30 +1031,26 @@ class RecurringTransactionForm(forms.ModelForm):
template="transactions/widgets/income_expense_toggle_buttons.html", template="transactions/widgets/income_expense_toggle_buttons.html",
), ),
Row( Row(
Column("account", css_class="form-group col-md-6 mb-0"), Column("account"),
Column("entities", css_class="form-group col-md-6 mb-0"), Column("entities"),
css_class="form-row",
), ),
"description", "description",
Switch("add_description_to_transaction"), Switch("add_description_to_transaction"),
"amount", "amount",
Row( Row(
Column("category", css_class="form-group col-md-6 mb-0"), Column("category"),
Column("tags", css_class="form-group col-md-6 mb-0"), Column("tags"),
css_class="form-row",
), ),
"notes", "notes",
Switch("add_notes_to_transaction"), Switch("add_notes_to_transaction"),
Row( Row(
Column("start_date", css_class="form-group col-md-6 mb-0"), Column("start_date"),
Column("reference_date", css_class="form-group col-md-6 mb-0"), Column("reference_date"),
css_class="form-row",
), ),
Row( Row(
Column("recurrence_interval", css_class="form-group col-md-4 mb-0"), Column("recurrence_interval", css_class="col-span-12 md:col-span-4"),
Column("recurrence_type", css_class="form-group col-md-4 mb-0"), Column("recurrence_type", css_class="col-span-12 md:col-span-4"),
Column("end_date", css_class="form-group col-md-4 mb-0"), Column("end_date", css_class="col-span-12 md:col-span-4"),
css_class="form-row",
), ),
AppendedText("keep_at_most", _("future transactions")), AppendedText("keep_at_most", _("future transactions")),
) )
@@ -1138,17 +1062,13 @@ class RecurringTransactionForm(forms.ModelForm):
if self.instance and self.instance.pk: if self.instance and self.instance.pk:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
else: else:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
+28 -17
View File
@@ -2,29 +2,29 @@ import decimal
import logging import logging
from copy import deepcopy from copy import deepcopy
from apps.common.fields.month_year import MonthYearModelField
from apps.common.functions.decimals import truncate_decimal
from apps.common.middleware.thread_local import get_current_user
from apps.common.models import (
OwnedObject,
OwnedObjectManager,
SharedObject,
SharedObjectManager,
)
from apps.common.templatetags.decimal import drop_trailing_zeros, localize_number
from apps.currencies.utils.convert import convert
from apps.transactions.validators import validate_decimal_places, validate_non_negative
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from django.conf import settings from django.conf import settings
from django.core.validators import MinValueValidator from django.core.validators import MinValueValidator
from django.db import models, transaction from django.db import models, transaction
from django.db.models import Q from django.db.models import Q
from django.dispatch import Signal from django.dispatch import Signal
from django.forms import ValidationError
from django.template.defaultfilters import date from django.template.defaultfilters import date
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from apps.common.fields.month_year import MonthYearModelField
from apps.common.functions.decimals import truncate_decimal
from apps.common.templatetags.decimal import localize_number, drop_trailing_zeros
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,
OwnedObjectManager,
)
logger = logging.getLogger() logger = logging.getLogger()
@@ -381,21 +381,32 @@ class Transaction(OwnedObject):
db_table = "transactions" db_table = "transactions"
default_manager_name = "objects" default_manager_name = "objects"
def clean_fields(self, *args, **kwargs): def clean(self):
super().clean()
# Only process amount and reference_date if account exists
# If account is missing, Django's required field validation will handle it
try:
account = self.account
except Transaction.account.RelatedObjectDoesNotExist:
# Account doesn't exist, skip processing that depends on it
# Django will add the required field error
return
# Validate and normalize amount
if isinstance(self.amount, (str, int, float)): if isinstance(self.amount, (str, int, float)):
self.amount = decimal.Decimal(str(self.amount)) self.amount = decimal.Decimal(str(self.amount))
self.amount = truncate_decimal( self.amount = truncate_decimal(
value=self.amount, decimal_places=self.account.currency.decimal_places value=self.amount, decimal_places=account.currency.decimal_places
) )
# Normalize reference_date
if self.reference_date: if self.reference_date:
self.reference_date = self.reference_date.replace(day=1) self.reference_date = self.reference_date.replace(day=1)
elif not self.reference_date and self.date: elif not self.reference_date and self.date:
self.reference_date = self.date.replace(day=1) self.reference_date = self.date.replace(day=1)
super().clean_fields(*args, **kwargs)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
# This is not recommended as it will run twice on some cases like form and API saves. # This is not recommended as it will run twice on some cases like form and API saves.
# We only do this here because we forgot to independently call it on multiple places. # We only do this here because we forgot to independently call it on multiple places.
@@ -3,7 +3,6 @@ from decimal import Decimal
from django import template from django import template
from django.utils.formats import number_format from django.utils.formats import number_format
register = template.Library() register = template.Library()
@@ -13,13 +12,27 @@ def _format_string(prefix, amount, decimal_places, suffix):
value=abs(amount), decimal_pos=decimal_places, force_grouping=True value=abs(amount), decimal_pos=decimal_places, force_grouping=True
) )
if amount < 0: if amount < 0:
return "-", prefix, formatted_amount, suffix
return f"-{prefix}{formatted_amount}{suffix}" return f"-{prefix}{formatted_amount}{suffix}"
else: else:
return "", prefix, formatted_amount, suffix
return f"{prefix}{formatted_amount}{suffix}" return f"{prefix}{formatted_amount}{suffix}"
else: else:
return "ERR" return "", "", "ERR", ""
@register.simple_tag(name="currency_display") @register.simple_tag(name="currency_display")
def currency_display(amount, prefix, suffix, decimal_places): def currency_display(amount, prefix, suffix, decimal_places, string=False):
return _format_string(prefix, amount, decimal_places, suffix) sign, prefix, amount, suffix = _format_string(
prefix, amount, decimal_places, suffix
)
if string:
return f"{sign}{prefix}{amount}{suffix}"
return {
"sign": sign,
"prefix": prefix,
"amount": amount,
"suffix": suffix,
}
@@ -137,6 +137,7 @@ def quick_transaction_add_as_transaction(request, quick_transaction_id):
"category", "category",
"tags", "tags",
"entities", "entities",
"internal_id",
], ],
) )
@@ -206,6 +207,7 @@ def quick_transaction_add_as_quick_transaction(request, transaction_id):
"recurring_transaction", "recurring_transaction",
"deleted", "deleted",
"deleted_at", "deleted_at",
"internal_id",
], ],
) )
+26 -28
View File
@@ -1,35 +1,43 @@
from apps.common.middleware.thread_local import get_current_user
from apps.common.widgets.crispy.submit import NoClassSubmit
from apps.users.models import UserSettings
from crispy_forms.bootstrap import ( from crispy_forms.bootstrap import (
FormActions, FormActions,
) )
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Submit, Row, Column, Field, Div, HTML from crispy_forms.layout import HTML, Column, Div, Field, Layout, Row, Submit
from django import forms from django import forms
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.forms import ( from django.contrib.auth.forms import (
UsernameField,
AuthenticationForm, AuthenticationForm,
UserCreationForm, UserCreationForm,
UsernameField,
) )
from django.db import transaction from django.db import transaction
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from apps.common.widgets.crispy.submit import NoClassSubmit
from apps.users.models import UserSettings
from apps.common.middleware.thread_local import get_current_user
class LoginForm(AuthenticationForm): class LoginForm(AuthenticationForm):
username = UsernameField( username = UsernameField(
label=_("E-mail"), label=_("E-mail"),
widget=forms.EmailInput( widget=forms.EmailInput(
attrs={"class": "form-control", "placeholder": "E-mail", "name": "email"} attrs={
"class": "input",
"placeholder": _("E-mail"),
"name": "email",
"autocomplete": "email",
}
), ),
) )
password = forms.CharField( password = forms.CharField(
label=_("Password"), label=_("Password"),
strip=False, strip=False,
widget=forms.PasswordInput( widget=forms.PasswordInput(
attrs={"class": "form-control", "placeholder": "Senha"} attrs={
"class": "input",
"placeholder": _("Password"),
"autocomplete": "current-password",
}
), ),
) )
@@ -45,7 +53,7 @@ class LoginForm(AuthenticationForm):
self.helper.layout = Layout( self.helper.layout = Layout(
"username", "username",
"password", "password",
Submit("Submit", "Login", css_class="btn btn-primary w-100"), Submit("Submit", "Login", css_class="w-full mt-3"),
) )
@@ -138,9 +146,7 @@ class UserSettingsForm(forms.ModelForm):
HTML("<hr />"), HTML("<hr />"),
"volume", "volume",
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Save"), css_class="btn btn-primary"),
"submit", _("Save"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
@@ -191,8 +197,8 @@ class UserUpdateForm(forms.ModelForm):
# Define the layout using Crispy Forms, including the new fields # Define the layout using Crispy Forms, including the new fields
self.helper.layout = Layout( self.helper.layout = Layout(
Row( Row(
Column("first_name", css_class="form-group col-md-6"), Column("first_name"),
Column("last_name", css_class="form-group col-md-6"), Column("last_name"),
css_class="row", css_class="row",
), ),
Field("email"), Field("email"),
@@ -213,17 +219,13 @@ class UserUpdateForm(forms.ModelForm):
if self.instance and self.instance.pk: if self.instance and self.instance.pk:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
else: else:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
@@ -354,8 +356,8 @@ class UserAddForm(UserCreationForm):
self.helper.layout = Layout( self.helper.layout = Layout(
Field("email"), Field("email"),
Row( Row(
Column("first_name", css_class="form-group col-md-6"), Column("first_name"),
Column("last_name", css_class="form-group col-md-6"), Column("last_name"),
css_class="row", css_class="row",
), ),
# UserCreationForm provides 'password1' and 'password2' fields # UserCreationForm provides 'password1' and 'password2' fields
@@ -375,17 +377,13 @@ class UserAddForm(UserCreationForm):
if self.instance and self.instance.pk: if self.instance and self.instance.pk:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
else: else:
self.helper.layout.append( self.helper.layout.append(
FormActions( FormActions(
NoClassSubmit( NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
), ),
) )
+6 -1
View File
@@ -18,10 +18,15 @@ urlpatterns = [
name="toggle_sound_playing", name="toggle_sound_playing",
), ),
path( path(
"user/toggle-sidebar/", "user/session/toggle-sidebar/",
views.toggle_sidebar_status, views.toggle_sidebar_status,
name="toggle_sidebar_status", name="toggle_sidebar_status",
), ),
path(
"user/session/toggle-theme/",
views.toggle_theme,
name="toggle_theme",
),
path( path(
"user/settings/", "user/settings/",
views.update_settings, views.update_settings,
+31 -13
View File
@@ -1,27 +1,26 @@
from apps.common.decorators.demo import disabled_on_demo
from apps.common.decorators.htmx import only_htmx
from apps.common.decorators.user import htmx_login_required, is_superuser
from apps.users.forms import (
LoginForm,
UserAddForm,
UserSettingsForm,
UserUpdateForm,
)
from apps.users.models import UserSettings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth import logout, get_user_model from django.contrib.auth import get_user_model, logout
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.views import ( from django.contrib.auth.views import (
LoginView, LoginView,
) )
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import redirect, render, get_object_or_404 from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.decorators.http import require_http_methods from django.views.decorators.http import require_http_methods
from apps.common.decorators.htmx import only_htmx
from apps.common.decorators.user import is_superuser, htmx_login_required
from apps.users.forms import (
LoginForm,
UserSettingsForm,
UserUpdateForm,
UserAddForm,
)
from apps.users.models import UserSettings
from apps.common.decorators.demo import disabled_on_demo
def logout_view(request): def logout_view(request):
logout(request) logout(request)
@@ -118,6 +117,7 @@ def update_settings(request):
@only_htmx @only_htmx
@htmx_login_required @htmx_login_required
@require_http_methods(["GET"])
def toggle_sidebar_status(request): def toggle_sidebar_status(request):
if not request.session.get("sidebar_status"): if not request.session.get("sidebar_status"):
request.session["sidebar_status"] = "floating" request.session["sidebar_status"] = "floating"
@@ -134,6 +134,24 @@ def toggle_sidebar_status(request):
) )
@htmx_login_required
@require_http_methods(["GET"])
def toggle_theme(request):
if not request.session.get("theme"):
request.session["theme"] = "wygiwyh_dark"
if request.session["theme"] == "wygiwyh_dark":
request.session["theme"] = "wygiwyh_light"
elif request.session["theme"] == "wygiwyh_light":
request.session["theme"] = "wygiwyh_dark"
else:
request.session["theme"] = "wygiwyh_light"
return HttpResponse(
status=204,
)
@htmx_login_required @htmx_login_required
@is_superuser @is_superuser
@require_http_methods(["GET"]) @require_http_methods(["GET"])
+6 -8
View File
@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-20 14:08+0000\n" "POT-Creation-Date: 2025-09-20 14:08+0000\n"
"PO-Revision-Date: 2025-07-22 06:17+0000\n" "PO-Revision-Date: 2025-11-01 01:17+0000\n"
"Last-Translator: seraphblade2010 <marc.butenhoff@web.de>\n" "Last-Translator: mlystopad <mlystopadt@gmail.com>\n"
"Language-Team: German <https://translations.herculino.com/projects/wygiwyh/" "Language-Team: German <https://translations.herculino.com/projects/wygiwyh/"
"app/de/>\n" "app/de/>\n"
"Language: de\n" "Language: de\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n" "Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.12.2\n" "X-Generator: Weblate 5.14\n"
#: apps/accounts/forms.py:26 #: apps/accounts/forms.py:26
msgid "Group name" msgid "Group name"
@@ -1197,7 +1197,7 @@ msgstr "Interne ID"
#: apps/transactions/models.py:216 apps/transactions/models.py:307 #: apps/transactions/models.py:216 apps/transactions/models.py:307
#: apps/transactions/models.py:994 #: apps/transactions/models.py:994
msgid "Mute" msgid "Mute"
msgstr "Deaktivieren" msgstr "Stummschalten"
#: apps/rules/forms.py:219 #: apps/rules/forms.py:219
msgid "Search Criteria" msgid "Search Criteria"
@@ -2881,7 +2881,7 @@ msgstr "Nur benutzen, wenn du weißt was du tust"
#: templates/includes/navbar.html:158 templates/includes/sidebar.html:261 #: templates/includes/navbar.html:158 templates/includes/sidebar.html:261
msgid "Django Admin" msgid "Django Admin"
msgstr "Django Admin" msgstr "Django AdministratorIn"
#: templates/includes/navbar.html:169 templates/includes/sidebar.html:276 #: templates/includes/navbar.html:169 templates/includes/sidebar.html:276
msgid "is available" msgid "is available"
@@ -3275,10 +3275,8 @@ msgid "Edit quick transaction"
msgstr "Transaktion bearbeiten" msgstr "Transaktion bearbeiten"
#: templates/quick_transactions/fragments/list.html:40 #: templates/quick_transactions/fragments/list.html:40
#, fuzzy
#| msgid "Yes, delete them!"
msgid "This will delete this item" msgid "This will delete this item"
msgstr "Ja, löschen!" msgstr "Dieser Artikel wird gelöscht"
#: templates/recurring_transactions/fragments/add.html:5 #: templates/recurring_transactions/fragments/add.html:5
msgid "Add recurring transaction" msgid "Add recurring transaction"
File diff suppressed because it is too large Load Diff
+6 -6
View File
@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-20 14:08+0000\n" "POT-Creation-Date: 2025-09-20 14:08+0000\n"
"PO-Revision-Date: 2025-09-23 06:17+0000\n" "PO-Revision-Date: 2025-10-07 20:17+0000\n"
"Last-Translator: sorcierwax <freakywax@gmail.com>\n" "Last-Translator: Erwan Colin <zephone@protonmail.com>\n"
"Language-Team: French <https://translations.herculino.com/projects/wygiwyh/" "Language-Team: French <https://translations.herculino.com/projects/wygiwyh/"
"app/fr/>\n" "app/fr/>\n"
"Language: fr\n" "Language: fr\n"
@@ -1066,7 +1066,7 @@ msgstr "Revenus à date"
#: templates/insights/fragments/category_explorer/charts/account.html:66 #: templates/insights/fragments/category_explorer/charts/account.html:66
#: templates/insights/fragments/category_explorer/charts/currency.html:66 #: templates/insights/fragments/category_explorer/charts/currency.html:66
msgid "Current Expenses" msgid "Current Expenses"
msgstr "Dépenses à date" msgstr "Dépenses actuelles"
#: apps/insights/utils/category_explorer.py:74 #: apps/insights/utils/category_explorer.py:74
#: apps/insights/utils/category_explorer.py:153 #: apps/insights/utils/category_explorer.py:153
@@ -1226,7 +1226,7 @@ msgstr "Déclencheur"
#: apps/rules/models.py:17 #: apps/rules/models.py:17
msgid "Sequenced" msgid "Sequenced"
msgstr "Récurrence" msgstr "Classé"
#: apps/rules/models.py:26 #: apps/rules/models.py:26
msgid "Transaction rule" msgid "Transaction rule"
@@ -2391,7 +2391,7 @@ msgstr "Revenu à date"
#: templates/cotton/ui/account_card.html:120 #: templates/cotton/ui/account_card.html:120
#: templates/cotton/ui/currency_card.html:114 #: templates/cotton/ui/currency_card.html:114
msgid "current expenses" msgid "current expenses"
msgstr "Dépenses à date" msgstr "dépenses actuelles"
#: templates/cotton/ui/account_card.html:146 #: templates/cotton/ui/account_card.html:146
#: templates/cotton/ui/currency_card.html:140 #: templates/cotton/ui/currency_card.html:140
@@ -2974,7 +2974,7 @@ msgstr "Total"
#: templates/insights/fragments/category_overview/index.html:517 #: templates/insights/fragments/category_overview/index.html:517
msgid "Final Total" msgid "Final Total"
msgstr "Total final" msgstr "Total Final"
#: templates/insights/fragments/emergency_fund.html:15 #: templates/insights/fragments/emergency_fund.html:15
msgid "You've spent an average of" msgid "You've spent an average of"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+84 -70
View File
@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-20 14:08+0000\n" "POT-Creation-Date: 2025-09-20 14:08+0000\n"
"PO-Revision-Date: 2025-10-05 09:17+0000\n" "PO-Revision-Date: 2025-11-01 01:17+0000\n"
"Last-Translator: TestXuser <izime07@proton.me>\n" "Last-Translator: mlystopad <mlystopadt@gmail.com>\n"
"Language-Team: Ukrainian <https://translations.herculino.com/projects/" "Language-Team: Ukrainian <https://translations.herculino.com/projects/"
"wygiwyh/app/uk/>\n" "wygiwyh/app/uk/>\n"
"Language: uk\n" "Language: uk\n"
@@ -18,7 +18,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
"X-Generator: Weblate 5.13.3\n" "X-Generator: Weblate 5.14\n"
#: apps/accounts/forms.py:26 #: apps/accounts/forms.py:26
msgid "Group name" msgid "Group name"
@@ -269,11 +269,11 @@ msgstr "Рахунок успішно видалено"
#: apps/accounts/views/accounts.py:165 #: apps/accounts/views/accounts.py:165
msgid "Account is now tracked" msgid "Account is now tracked"
msgstr "" msgstr "Рахунок теперь відстежується"
#: apps/accounts/views/accounts.py:168 #: apps/accounts/views/accounts.py:168
msgid "Account is now untracked" msgid "Account is now untracked"
msgstr "" msgstr "Рахунок більше не відстежується"
#: apps/accounts/views/balance.py:77 #: apps/accounts/views/balance.py:77
msgid "Balance reconciliation" msgid "Balance reconciliation"
@@ -313,7 +313,7 @@ msgstr "Необхідно вказати або 'date', або 'reference_date'
#: apps/common/admin.py:5 #: apps/common/admin.py:5
msgid "Make public" msgid "Make public"
msgstr "" msgstr "Зробити публічним"
#: apps/common/admin.py:10 #: apps/common/admin.py:10
#, fuzzy #, fuzzy
@@ -437,23 +437,25 @@ msgstr[2] "%(weeks)s тижнів тому"
#, python-format #, python-format
msgid "in %(years)s year" msgid "in %(years)s year"
msgid_plural "in %(years)s years" msgid_plural "in %(years)s years"
msgstr[0] "" msgstr[0] "через %(years)s рік"
msgstr[1] "" msgstr[1] "через %(years)s роки"
msgstr[2] "" msgstr[2] "через %(years)s років"
#: apps/common/templatetags/natural.py:51 #: apps/common/templatetags/natural.py:51
#, python-format #, python-format
msgid "in %(months)s month" msgid "in %(months)s month"
msgid_plural "in %(months)s months" msgid_plural "in %(months)s months"
msgstr[0] "" msgstr[0] "через %(months)s місяць"
msgstr[1] "" msgstr[1] "через %(months)s місяці"
msgstr[2] "через %(months)s місяців"
#: apps/common/templatetags/natural.py:56 #: apps/common/templatetags/natural.py:56
#, python-format #, python-format
msgid "in %(weeks)s week" msgid "in %(weeks)s week"
msgid_plural "in %(weeks)s weeks" msgid_plural "in %(weeks)s weeks"
msgstr[0] "" msgstr[0] "через %(weeks)s тиждень"
msgstr[1] "" msgstr[1] "через %(weeks)s тижні"
msgstr[2] "через %(weeks)s тижнів"
#: apps/common/templatetags/toast_bg.py:34 #: apps/common/templatetags/toast_bg.py:34
msgid "Success" msgid "Success"
@@ -567,7 +569,7 @@ msgstr "Дата і час"
#: apps/currencies/models.py:78 apps/users/models.py:12 #: apps/currencies/models.py:78 apps/users/models.py:12
#: apps/users/models.py:497 #: apps/users/models.py:497
msgid "Auto" msgid "Auto"
msgstr "" msgstr "Авто"
#: apps/currencies/models.py:82 apps/export_app/forms.py:68 #: apps/currencies/models.py:82 apps/export_app/forms.py:68
#: apps/export_app/forms.py:145 templates/exchange_rates/fragments/list.html:6 #: apps/export_app/forms.py:145 templates/exchange_rates/fragments/list.html:6
@@ -631,23 +633,27 @@ msgstr "Остання успішна вибірка"
#: apps/currencies/models.py:141 #: apps/currencies/models.py:141
msgid "Target Currencies" msgid "Target Currencies"
msgstr "" msgstr "Цільові валюти"
#: apps/currencies/models.py:143 #: apps/currencies/models.py:143
msgid "" msgid ""
"Select currencies to fetch exchange rates for. Rates will be fetched for " "Select currencies to fetch exchange rates for. Rates will be fetched for "
"each currency against their set exchange currency." "each currency against their set exchange currency."
msgstr "" msgstr ""
"Оберіть валюти для завантаження курсів обміну. Курси будуть завантажені для "
"кожної валюти відносно встановленої валюти обміну."
#: apps/currencies/models.py:151 #: apps/currencies/models.py:151
msgid "Target Accounts" msgid "Target Accounts"
msgstr "" msgstr "Цільові Рахунки"
#: apps/currencies/models.py:153 #: apps/currencies/models.py:153
msgid "" msgid ""
"Select accounts to fetch exchange rates for. Rates will be fetched for each " "Select accounts to fetch exchange rates for. Rates will be fetched for each "
"account's currency against their set exchange currency." "account's currency against their set exchange currency."
msgstr "" msgstr ""
"Оберіть рахунки для завантаження курсів обміну. Курси будуть завантажені для "
"валюти кожного рахунку відносно встановленої валюти обміну."
#: apps/currencies/models.py:160 #: apps/currencies/models.py:160
#, fuzzy #, fuzzy
@@ -658,111 +664,117 @@ msgstr "Обмінний курс"
#: apps/currencies/models.py:163 #: apps/currencies/models.py:163
msgid "Create one exchange rate and keep updating it. Avoids database clutter." msgid "Create one exchange rate and keep updating it. Avoids database clutter."
msgstr "" msgstr ""
"Створіть один курс обміну та оновлюйте його постійно. Це запобігає "
"засміченню бази даних."
#: apps/currencies/models.py:168 #: apps/currencies/models.py:168
msgid "Exchange Rate Service" msgid "Exchange Rate Service"
msgstr "" msgstr "Сервіс Курсів Обміну"
#: apps/currencies/models.py:169 #: apps/currencies/models.py:169
msgid "Exchange Rate Services" msgid "Exchange Rate Services"
msgstr "" msgstr "Сервіси Курсів Обміну"
#: apps/currencies/models.py:221 #: apps/currencies/models.py:221
msgid "'Every X hours' interval type requires a positive integer." msgid "'Every X hours' interval type requires a positive integer."
msgstr "" msgstr "Інтервал типу «Кожні X годин» потребує додатнього цілого числа."
#: apps/currencies/models.py:230 #: apps/currencies/models.py:230
msgid "'Every X hours' interval must be between 1 and 24." msgid "'Every X hours' interval must be between 1 and 24."
msgstr "" msgstr "Інтервал типу «Кожні X годин» повинен бути між 1 та 24."
#: apps/currencies/models.py:244 #: apps/currencies/models.py:244
msgid "" msgid ""
"Invalid hour format. Use comma-separated hours (0-23) and/or ranges (e.g., " "Invalid hour format. Use comma-separated hours (0-23) and/or ranges (e.g., "
"'1-5,8,10-12')." "'1-5,8,10-12')."
msgstr "" msgstr ""
"Неправильний формат годин. Використовуйте години, розділені комами (0–23) та/"
"або діапазони (наприклад, '1-5,8,10-12')."
#: apps/currencies/models.py:255 #: apps/currencies/models.py:255
msgid "" msgid ""
"Invalid format. Please check the requirements for your selected interval " "Invalid format. Please check the requirements for your selected interval "
"type." "type."
msgstr "" msgstr ""
"Неправильний формат. Будь ласка, перевірте вимоги для обраного типу "
"інтервалу."
#: apps/currencies/views/currencies.py:42 #: apps/currencies/views/currencies.py:42
msgid "Currency added successfully" msgid "Currency added successfully"
msgstr "" msgstr "Валюту успішно додано"
#: apps/currencies/views/currencies.py:70 #: apps/currencies/views/currencies.py:70
msgid "Currency updated successfully" msgid "Currency updated successfully"
msgstr "" msgstr "Валюту успішно оновлено"
#: apps/currencies/views/currencies.py:96 #: apps/currencies/views/currencies.py:96
msgid "Currency deleted successfully" msgid "Currency deleted successfully"
msgstr "" msgstr "Валюту успішно видалено"
#: apps/currencies/views/exchange_rates.py:89 #: apps/currencies/views/exchange_rates.py:89
msgid "Exchange rate added successfully" msgid "Exchange rate added successfully"
msgstr "" msgstr "Курс обміну успішно додано"
#: apps/currencies/views/exchange_rates.py:117 #: apps/currencies/views/exchange_rates.py:117
msgid "Exchange rate updated successfully" msgid "Exchange rate updated successfully"
msgstr "" msgstr "Курс обміну успішно оновлено"
#: apps/currencies/views/exchange_rates.py:143 #: apps/currencies/views/exchange_rates.py:143
msgid "Exchange rate deleted successfully" msgid "Exchange rate deleted successfully"
msgstr "" msgstr "Курс обміну успішно видалено"
#: apps/currencies/views/exchange_rates_services.py:50 #: apps/currencies/views/exchange_rates_services.py:50
msgid "Service added successfully" msgid "Service added successfully"
msgstr "" msgstr "Сервіс успішно додано"
#: apps/currencies/views/exchange_rates_services.py:79 #: apps/currencies/views/exchange_rates_services.py:79
msgid "Service updated successfully" msgid "Service updated successfully"
msgstr "" msgstr "Сервіс успішно оновлено"
#: apps/currencies/views/exchange_rates_services.py:106 #: apps/currencies/views/exchange_rates_services.py:106
msgid "Service deleted successfully" msgid "Service deleted successfully"
msgstr "" msgstr "Сервіс успішно видалено"
#: apps/currencies/views/exchange_rates_services.py:122 #: apps/currencies/views/exchange_rates_services.py:122
msgid "Services queued successfully" msgid "Services queued successfully"
msgstr "" msgstr "Сервіси успішно поставлено в чергу"
#: apps/dca/forms.py:65 apps/dca/forms.py:164 #: apps/dca/forms.py:65 apps/dca/forms.py:164
msgid "Create transaction" msgid "Create transaction"
msgstr "" msgstr "Створити транзакцію"
#: apps/dca/forms.py:70 apps/transactions/forms.py:515 #: apps/dca/forms.py:70 apps/transactions/forms.py:515
msgid "From Account" msgid "From Account"
msgstr "" msgstr "З Рахунку"
#: apps/dca/forms.py:76 apps/transactions/forms.py:520 #: apps/dca/forms.py:76 apps/transactions/forms.py:520
msgid "To Account" msgid "To Account"
msgstr "" msgstr "На рахунок"
#: apps/dca/forms.py:116 apps/dca/models.py:171 #: apps/dca/forms.py:116 apps/dca/models.py:171
msgid "Expense Transaction" msgid "Expense Transaction"
msgstr "" msgstr "Витратна транзакція"
#: apps/dca/forms.py:120 apps/dca/forms.py:130 #: apps/dca/forms.py:120 apps/dca/forms.py:130
msgid "Type to search for a transaction to link to this entry" msgid "Type to search for a transaction to link to this entry"
msgstr "" msgstr "Введіть для пошуку транзакції, щоб пов’язати її з цим записом"
#: apps/dca/forms.py:126 apps/dca/models.py:179 #: apps/dca/forms.py:126 apps/dca/models.py:179
msgid "Income Transaction" msgid "Income Transaction"
msgstr "" msgstr "Дохідна транзакція"
#: apps/dca/forms.py:210 #: apps/dca/forms.py:210
msgid "Link transaction" msgid "Link transaction"
msgstr "" msgstr "Пов’язати транзакцію"
#: apps/dca/forms.py:297 apps/dca/forms.py:298 apps/dca/forms.py:303 #: apps/dca/forms.py:297 apps/dca/forms.py:298 apps/dca/forms.py:303
#: apps/dca/forms.py:307 #: apps/dca/forms.py:307
msgid "You must provide an account." msgid "You must provide an account."
msgstr "" msgstr "Необхідно вказати рахунок."
#: apps/dca/forms.py:312 apps/transactions/forms.py:690 #: apps/dca/forms.py:312 apps/transactions/forms.py:690
msgid "From and To accounts must be different." msgid "From and To accounts must be different."
msgstr "" msgstr "Рахунки «З» та «На» повинні бути різними."
#: apps/dca/forms.py:326 #: apps/dca/forms.py:326
#, python-format #, python-format
@@ -771,11 +783,11 @@ msgstr ""
#: apps/dca/models.py:16 #: apps/dca/models.py:16
msgid "Target Currency" msgid "Target Currency"
msgstr "" msgstr "Цільова валюта"
#: apps/dca/models.py:22 #: apps/dca/models.py:22
msgid "Payment Currency" msgid "Payment Currency"
msgstr "" msgstr "Валюта платежу"
#: apps/dca/models.py:26 apps/dca/models.py:181 apps/rules/forms.py:184 #: apps/dca/models.py:26 apps/dca/models.py:181 apps/rules/forms.py:184
#: apps/rules/forms.py:200 apps/rules/models.py:43 apps/rules/models.py:295 #: apps/rules/forms.py:200 apps/rules/models.py:43 apps/rules/models.py:295
@@ -783,7 +795,7 @@ msgstr ""
#: apps/transactions/models.py:319 apps/transactions/models.py:569 #: apps/transactions/models.py:319 apps/transactions/models.py:569
#: apps/transactions/models.py:770 apps/transactions/models.py:1006 #: apps/transactions/models.py:770 apps/transactions/models.py:1006
msgid "Notes" msgid "Notes"
msgstr "" msgstr "Примітки"
#: apps/dca/models.py:34 #: apps/dca/models.py:34
msgid "DCA Strategy" msgid "DCA Strategy"
@@ -795,15 +807,15 @@ msgstr ""
#: apps/dca/models.py:156 #: apps/dca/models.py:156
msgid "Strategy" msgid "Strategy"
msgstr "" msgstr "Стратегія"
#: apps/dca/models.py:160 templates/dca/fragments/strategy/details.html:54 #: apps/dca/models.py:160 templates/dca/fragments/strategy/details.html:54
msgid "Amount Paid" msgid "Amount Paid"
msgstr "" msgstr "Сплачена Сума"
#: apps/dca/models.py:163 templates/dca/fragments/strategy/details.html:53 #: apps/dca/models.py:163 templates/dca/fragments/strategy/details.html:53
msgid "Amount Received" msgid "Amount Received"
msgstr "" msgstr "Отримана Сума"
#: apps/dca/models.py:186 #: apps/dca/models.py:186
msgid "DCA Entry" msgid "DCA Entry"
@@ -827,21 +839,21 @@ msgstr ""
#: apps/dca/views.py:238 #: apps/dca/views.py:238
msgid "Entry added successfully" msgid "Entry added successfully"
msgstr "" msgstr "Запис успішно додано"
#: apps/dca/views.py:265 #: apps/dca/views.py:265
msgid "Entry updated successfully" msgid "Entry updated successfully"
msgstr "" msgstr "Запис успішно оновлено"
#: apps/dca/views.py:291 #: apps/dca/views.py:291
msgid "Entry deleted successfully" msgid "Entry deleted successfully"
msgstr "" msgstr "Запис успішно видалено"
#: apps/export_app/forms.py:14 apps/export_app/forms.py:131 #: apps/export_app/forms.py:14 apps/export_app/forms.py:131
#: templates/includes/navbar.html:150 templates/includes/sidebar.html:255 #: templates/includes/navbar.html:150 templates/includes/sidebar.html:255
#: templates/users/fragments/list.html:6 templates/users/pages/index.html:4 #: templates/users/fragments/list.html:6 templates/users/pages/index.html:4
msgid "Users" msgid "Users"
msgstr "" msgstr "Користувачі"
#: apps/export_app/forms.py:32 apps/export_app/forms.py:137 #: apps/export_app/forms.py:32 apps/export_app/forms.py:137
#: apps/transactions/models.py:380 templates/includes/navbar.html:58 #: apps/transactions/models.py:380 templates/includes/navbar.html:58
@@ -851,14 +863,14 @@ msgstr ""
#: templates/recurring_transactions/fragments/table.html:39 #: templates/recurring_transactions/fragments/table.html:39
#: templates/transactions/pages/transactions.html:5 #: templates/transactions/pages/transactions.html:5
msgid "Transactions" msgid "Transactions"
msgstr "" msgstr "Транзакції"
#: apps/export_app/forms.py:38 apps/export_app/forms.py:134 #: apps/export_app/forms.py:38 apps/export_app/forms.py:134
#: apps/transactions/filters.py:64 templates/categories/fragments/list.html:5 #: apps/transactions/filters.py:64 templates/categories/fragments/list.html:5
#: templates/categories/pages/index.html:4 templates/includes/navbar.html:109 #: templates/categories/pages/index.html:4 templates/includes/navbar.html:109
#: templates/includes/sidebar.html:178 #: templates/includes/sidebar.html:178
msgid "Categories" msgid "Categories"
msgstr "" msgstr "Категорії"
#: apps/export_app/forms.py:50 apps/export_app/forms.py:136 #: apps/export_app/forms.py:50 apps/export_app/forms.py:136
#: apps/rules/forms.py:189 apps/rules/forms.py:199 apps/rules/models.py:46 #: apps/rules/forms.py:189 apps/rules/forms.py:199 apps/rules/models.py:46
@@ -881,7 +893,7 @@ msgstr ""
#: templates/recurring_transactions/fragments/list.html:5 #: templates/recurring_transactions/fragments/list.html:5
#: templates/recurring_transactions/pages/index.html:4 #: templates/recurring_transactions/pages/index.html:4
msgid "Recurring Transactions" msgid "Recurring Transactions"
msgstr "" msgstr "Регулярні транзакції"
#: apps/export_app/forms.py:62 apps/export_app/forms.py:138 #: apps/export_app/forms.py:62 apps/export_app/forms.py:138
#: apps/transactions/models.py:583 templates/includes/navbar.html:75 #: apps/transactions/models.py:583 templates/includes/navbar.html:75
@@ -889,20 +901,20 @@ msgstr ""
#: templates/installment_plans/fragments/list.html:5 #: templates/installment_plans/fragments/list.html:5
#: templates/installment_plans/pages/index.html:4 #: templates/installment_plans/pages/index.html:4
msgid "Installment Plans" msgid "Installment Plans"
msgstr "" msgstr "Плани Розстрочки"
#: apps/export_app/forms.py:74 apps/export_app/forms.py:143 #: apps/export_app/forms.py:74 apps/export_app/forms.py:143
#: templates/exchange_rates_services/fragments/list.html:6 #: templates/exchange_rates_services/fragments/list.html:6
#: templates/exchange_rates_services/pages/index.html:4 #: templates/exchange_rates_services/pages/index.html:4
#: templates/includes/navbar.html:143 templates/includes/sidebar.html:246 #: templates/includes/navbar.html:143 templates/includes/sidebar.html:246
msgid "Automatic Exchange Rates" msgid "Automatic Exchange Rates"
msgstr "" msgstr "Автоматичні Курси Обміну"
#: apps/export_app/forms.py:80 templates/includes/navbar.html:135 #: apps/export_app/forms.py:80 templates/includes/navbar.html:135
#: templates/includes/sidebar.html:226 templates/rules/fragments/list.html:5 #: templates/includes/sidebar.html:226 templates/rules/fragments/list.html:5
#: templates/rules/pages/index.html:4 #: templates/rules/pages/index.html:4
msgid "Rules" msgid "Rules"
msgstr "" msgstr "Правила"
#: apps/export_app/forms.py:86 templates/cotton/transaction/item.html:58 #: apps/export_app/forms.py:86 templates/cotton/transaction/item.html:58
msgid "DCA" msgid "DCA"
@@ -912,24 +924,24 @@ msgstr ""
#: templates/import_app/fragments/profiles/list.html:5 #: templates/import_app/fragments/profiles/list.html:5
#: templates/import_app/pages/profiles_index.html:4 #: templates/import_app/pages/profiles_index.html:4
msgid "Import Profiles" msgid "Import Profiles"
msgstr "" msgstr "Імпортувати Профілі"
#: apps/export_app/forms.py:119 templates/export_app/fragments/export.html:5 #: apps/export_app/forms.py:119 templates/export_app/fragments/export.html:5
#: templates/export_app/pages/index.html:15 #: templates/export_app/pages/index.html:15
msgid "Export" msgid "Export"
msgstr "" msgstr "Експортувати"
#: apps/export_app/forms.py:128 #: apps/export_app/forms.py:128
msgid "Import a ZIP file exported from WYGIWYH" msgid "Import a ZIP file exported from WYGIWYH"
msgstr "" msgstr "Імпортувати ZIP-файл, експортований із WYGIWYH"
#: apps/export_app/forms.py:129 #: apps/export_app/forms.py:129
msgid "ZIP File" msgid "ZIP File"
msgstr "" msgstr "ZIP-Файл"
#: apps/export_app/forms.py:146 apps/rules/models.py:27 #: apps/export_app/forms.py:146 apps/rules/models.py:27
msgid "Transaction rules" msgid "Transaction rules"
msgstr "" msgstr "Правила транзакцій"
#: apps/export_app/forms.py:148 apps/rules/models.py:68 #: apps/export_app/forms.py:148 apps/rules/models.py:68
msgid "Edit transaction action" msgid "Edit transaction action"
@@ -944,52 +956,54 @@ msgstr ""
#: templates/export_app/fragments/restore.html:5 #: templates/export_app/fragments/restore.html:5
#: templates/export_app/pages/index.html:24 #: templates/export_app/pages/index.html:24
msgid "Restore" msgid "Restore"
msgstr "" msgstr "Відновити"
#: apps/export_app/forms.py:196 #: apps/export_app/forms.py:196
msgid "Please upload either a ZIP file or at least one CSV file" msgid "Please upload either a ZIP file or at least one CSV file"
msgstr "" msgstr "Будь ласка, завантажте або ZIP-файл, або принаймні один CSV-файл"
#: apps/export_app/views.py:177 #: apps/export_app/views.py:177
msgid "You have to select at least one export" msgid "You have to select at least one export"
msgstr "" msgstr "Ви повинні обрати принаймні один експорт"
#: apps/export_app/views.py:197 #: apps/export_app/views.py:197
msgid "Data restored successfully" msgid "Data restored successfully"
msgstr "" msgstr "Дані успішно відновлено"
#: apps/export_app/views.py:209 #: apps/export_app/views.py:209
msgid "" msgid ""
"There was an error restoring your data. Check the logs for more details." "There was an error restoring your data. Check the logs for more details."
msgstr "" msgstr ""
"Сталася помилка під час відновлення даних. Перевірте логи для отримання "
"додаткової інформації."
#: apps/import_app/forms.py:49 #: apps/import_app/forms.py:49
msgid "Select a file" msgid "Select a file"
msgstr "" msgstr "Оберіть файл"
#: apps/import_app/forms.py:61 #: apps/import_app/forms.py:61
#: templates/import_app/fragments/profiles/list.html:62 #: templates/import_app/fragments/profiles/list.html:62
#: templates/includes/navbar.html:137 templates/includes/sidebar.html:232 #: templates/includes/navbar.html:137 templates/includes/sidebar.html:232
msgid "Import" msgid "Import"
msgstr "" msgstr "Імпортувати"
#: apps/import_app/models.py:15 #: apps/import_app/models.py:15
msgid "YAML Configuration" msgid "YAML Configuration"
msgstr "" msgstr "YAML Конфігурація"
#: apps/import_app/models.py:19 #: apps/import_app/models.py:19
#: templates/import_app/fragments/profiles/list.html:37 #: templates/import_app/fragments/profiles/list.html:37
msgid "Version" msgid "Version"
msgstr "" msgstr "Версія"
#: apps/import_app/models.py:30 #: apps/import_app/models.py:30
#, python-brace-format #, python-brace-format
msgid "Version {number}" msgid "Version {number}"
msgstr "" msgstr "Версія {number}"
#: apps/import_app/models.py:39 #: apps/import_app/models.py:39
msgid "Invalid YAML Configuration: " msgid "Invalid YAML Configuration: "
msgstr "" msgstr "Неправильна YAML Конфігурація: "
#: apps/import_app/models.py:45 #: apps/import_app/models.py:45
msgid "Queued" msgid "Queued"
+10 -10
View File
@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-20 14:08+0000\n" "POT-Creation-Date: 2025-09-20 14:08+0000\n"
"PO-Revision-Date: 2025-10-06 10:17+0000\n" "PO-Revision-Date: 2025-10-08 16:17+0000\n"
"Last-Translator: doody <doodykimo@gmail.com>\n" "Last-Translator: doody <doodykimo@gmail.com>\n"
"Language-Team: Chinese (Traditional Han script) <https://" "Language-Team: Chinese (Traditional Han script) <https://"
"translations.herculino.com/projects/wygiwyh/app/zh_Hant/>\n" "translations.herculino.com/projects/wygiwyh/app/zh_Hant/>\n"
@@ -326,7 +326,7 @@ msgstr "無法建立新的物件"
#: apps/common/fields/forms/grouped_select.py:24 #: apps/common/fields/forms/grouped_select.py:24
#: apps/common/widgets/tom_select.py:92 apps/common/widgets/tom_select.py:95 #: apps/common/widgets/tom_select.py:92 apps/common/widgets/tom_select.py:95
msgid "Ungrouped" msgid "Ungrouped"
msgstr "取消分組" msgstr "分組"
#: apps/common/fields/month_year.py:30 #: apps/common/fields/month_year.py:30
msgid "Invalid date format. Use YYYY-MM or YYYY-MM-DD." msgid "Invalid date format. Use YYYY-MM or YYYY-MM-DD."
@@ -2165,7 +2165,7 @@ msgstr "目前餘額"
#: templates/net_worth/net_worth.html:105 #: templates/net_worth/net_worth.html:105
#: templates/net_worth/net_worth.html:362 #: templates/net_worth/net_worth.html:362
msgid "Difference" msgid "Difference"
msgstr "差" msgstr "差"
#: templates/accounts/fragments/account_reconciliation.html:70 #: templates/accounts/fragments/account_reconciliation.html:70
msgid "Reconcile balances" msgid "Reconcile balances"
@@ -2915,7 +2915,7 @@ msgstr "您平均的支出為"
#: templates/insights/fragments/emergency_fund.html:23 #: templates/insights/fragments/emergency_fund.html:23
msgid "on the last 12 months, at this rate you could go by" msgid "on the last 12 months, at this rate you could go by"
msgstr "過去十二個月,依照這個速度,你可以維持" msgstr "過去十二個月,依照這個速度,你可以維持"
#: templates/insights/fragments/emergency_fund.html:25 #: templates/insights/fragments/emergency_fund.html:25
msgid "months without any income." msgid "months without any income."
@@ -2993,11 +2993,11 @@ msgstr "分類總覽"
#: templates/insights/pages/index.html:111 #: templates/insights/pages/index.html:111
msgid "Late Transactions" msgid "Late Transactions"
msgstr "逾期交易" msgstr "逾期交易"
#: templates/insights/pages/index.html:117 #: templates/insights/pages/index.html:117
msgid "Latest Transactions" msgid "Latest Transactions"
msgstr "最的交易" msgstr "最的交易"
#: templates/insights/pages/index.html:123 #: templates/insights/pages/index.html:123
msgid "Emergency Fund" msgid "Emergency Fund"
@@ -3122,7 +3122,7 @@ msgstr "最舊的優先"
#: templates/transactions/pages/transactions.html:16 #: templates/transactions/pages/transactions.html:16
#: templates/transactions/pages/transactions.html:78 #: templates/transactions/pages/transactions.html:78
msgid "Newest first" msgid "Newest first"
msgstr "新的優先" msgstr "新的優先"
#: templates/monthly_overview/pages/overview.html:151 #: templates/monthly_overview/pages/overview.html:151
#: templates/transactions/pages/transactions.html:25 #: templates/transactions/pages/transactions.html:25
@@ -3145,7 +3145,7 @@ msgstr "合併"
#: templates/net_worth/net_worth.html:101 #: templates/net_worth/net_worth.html:101
msgid "Evolution" msgid "Evolution"
msgstr "變化" msgstr "走勢圖"
#: templates/net_worth/net_worth.html:128 #: templates/net_worth/net_worth.html:128
#: templates/yearly_overview/pages/overview_by_account.html:7 #: templates/yearly_overview/pages/overview_by_account.html:7
@@ -3154,11 +3154,11 @@ msgstr "按照帳戶"
#: templates/net_worth/net_worth.html:236 #: templates/net_worth/net_worth.html:236
msgid "Evolution by currency" msgid "Evolution by currency"
msgstr "按照貨幣的變化" msgstr "按照貨幣的走勢圖"
#: templates/net_worth/net_worth.html:300 #: templates/net_worth/net_worth.html:300
msgid "Evolution by account" msgid "Evolution by account"
msgstr "按照帳戶的變化" msgstr "按照帳戶的走勢圖"
#: templates/quick_transactions/fragments/add.html:5 #: templates/quick_transactions/fragments/add.html:5
#: templates/quick_transactions/fragments/create_menu.html:5 #: templates/quick_transactions/fragments/create_menu.html:5
+3 -1
View File
@@ -1 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="500" zoomAndPan="magnify" viewBox="0 0 375 374.999991" height="500" preserveAspectRatio="xMidYMid meet" version="1.0"><defs><clipPath id="a2373d79ec"><path d="M 1.980469 1.980469 L 373 1.980469 L 373 373 L 1.980469 373 Z M 1.980469 1.980469 " clip-rule="nonzero"/></clipPath></defs><g clip-path="url(#a2373d79ec)"><path fill="#fbb700" d="M 239.671875 301.757812 L 79.152344 141.238281 L 118.234375 102.152344 L 239.671875 223.589844 L 355.179688 108.078125 C 325.429688 45.34375 261.519531 1.957031 187.472656 1.957031 C 113.375 1.957031 49.433594 45.410156 19.707031 108.210938 L 174.503906 263.003906 L 135.757812 301.757812 L 2.882812 168.878906 C 2.273438 174.996094 1.957031 181.199219 1.957031 187.472656 C 1.957031 289.929688 85.015625 372.988281 187.472656 372.988281 C 289.929688 372.988281 372.988281 289.929688 372.988281 187.472656 C 372.988281 181.347656 372.679688 175.296875 372.101562 169.320312 L 239.671875 301.757812 " fill-opacity="1" fill-rule="nonzero"/></g></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 375 374.999991" version="1.0">
<path fill="#000000" d="M 239.671875 301.757812 L 79.152344 141.238281 L 118.234375 102.152344 L 239.671875 223.589844 L 355.179688 108.078125 C 325.429688 45.34375 261.519531 1.957031 187.472656 1.957031 C 113.375 1.957031 49.433594 45.410156 19.707031 108.210938 L 174.503906 263.003906 L 135.757812 301.757812 L 2.882812 168.878906 C 2.273438 174.996094 1.957031 181.199219 1.957031 187.472656 C 1.957031 289.929688 85.015625 372.988281 187.472656 372.988281 C 289.929688 372.988281 372.988281 289.929688 372.988281 187.472656 C 372.988281 181.347656 372.679688 175.296875 372.101562 169.320312 L 239.671875 301.757812 "/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 718 B

@@ -1,46 +1,57 @@
{% load i18n %} {% load i18n %}
<div class="container px-md-3 py-3 column-gap-5"> <c-ui.fab-single-action
<div class="tw:text-3xl fw-bold font-monospace tw:w-full mb-3"> url="{% url 'account_group_add' %}"
hx_target="#generic-offcanvas">
</c-ui.fab-single-action>
<div class="container">
<div class="text-3xl font-bold font-mono w-full mb-3">
{% spaceless %} {% spaceless %}
<div>{% translate 'Account Groups' %}<span> <div>{% translate 'Account Groups' %}</div>
<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 'account_group_add' %}"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-circle-plus fa-fw"></i></a>
</span></div>
{% endspaceless %} {% endspaceless %}
</div> </div>
<div class="card"> <div class="card bg-base-100 shadow-xl">
<div class="card-body table-responsive"> <div class="card-body">
{% if account_groups %} {% if account_groups %}
<c-config.search></c-config.search> <c-config.search></c-config.search>
<table class="table table-hover"> <div class="overflow-x-auto">
<table class="table table-zebra">
<thead> <thead>
<tr> <tr>
<th scope="col" class="col-auto"></th> <th scope="col" class="table-col-auto"></th>
<th scope="col" class="col">{% translate 'Name' %}</th> <th scope="col">{% translate 'Name' %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for account_group in account_groups %} {% for account_group in account_groups %}
<tr class="account_group"> <tr class="account_group">
<td class="col-auto"> <td class="table-col-auto">
<div class="btn-group" role="group" aria-label="{% translate 'Actions' %}"> <div class="join" role="group" aria-label="{% translate 'Actions' %}">
<a class="btn btn-secondary btn-sm" <a class="btn btn-secondary btn-sm join-item"
role="button" role="button"
data-bs-toggle="tooltip" data-tippy-content="{% translate "Edit" %}"
data-bs-title="{% translate "Edit" %}"
hx-get="{% url 'account_group_edit' pk=account_group.id %}" hx-get="{% url 'account_group_edit' pk=account_group.id %}"
hx-target="#generic-offcanvas"> hx-target="#generic-offcanvas">
<i class="fa-solid fa-pencil fa-fw"></i></a> <i class="fa-solid fa-pencil fa-fw"></i></a>
<a class="btn btn-secondary btn-sm text-danger" {% if not account_group.owner %}
<a class="btn btn-secondary btn-sm join-item"
role="button" role="button"
data-bs-toggle="tooltip" data-tippy-content="{% translate "Take ownership" %}"
data-bs-title="{% translate "Delete" %}" hx-get="{% url 'account_group_take_ownership' pk=account_group.id %}">
<i class="fa-solid fa-crown fa-fw"></i></a>
{% endif %}
{% if user == account_group.owner %}
<a class="btn btn-secondary btn-sm join-item"
role="button"
hx-target="#generic-offcanvas"
hx-swap="innerHTML"
data-tippy-content="{% translate "Share" %}"
hx-get="{% url 'account_group_share_settings' pk=account_group.id %}">
<i class="fa-solid fa-share fa-fw"></i></a>
{% endif %}
<a class="btn btn-error btn-sm join-item"
role="button"
data-tippy-content="{% translate "Delete" %}"
hx-delete="{% url 'account_group_delete' pk=account_group.id %}" hx-delete="{% url 'account_group_delete' pk=account_group.id %}"
hx-trigger='confirmed' hx-trigger='confirmed'
data-bypass-on-ctrl="true" data-bypass-on-ctrl="true"
@@ -48,31 +59,14 @@
data-text="{% translate "You won't be able to revert this!" %}" data-text="{% translate "You won't be able to revert this!" %}"
data-confirm-text="{% translate "Yes, delete it!" %}" data-confirm-text="{% translate "Yes, delete it!" %}"
_="install prompt_swal"><i class="fa-solid fa-trash fa-fw"></i></a> _="install prompt_swal"><i class="fa-solid fa-trash fa-fw"></i></a>
{% if not account_group.owner %}
<a class="btn btn-secondary btn-sm text-warning"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Take ownership" %}"
hx-get="{% url 'account_group_take_ownership' pk=account_group.id %}">
<i class="fa-solid fa-crown fa-fw"></i></a>
{% endif %}
{% if user == account_group.owner %}
<a class="btn btn-secondary btn-sm text-primary"
role="button"
hx-target="#generic-offcanvas"
hx-swap="innerHTML"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Share" %}"
hx-get="{% url 'account_group_share_settings' pk=account_group.id %}">
<i class="fa-solid fa-share fa-fw"></i></a>
{% endif %}
</div> </div>
</td> </td>
<td class="col">{{ account_group.name }}</td> <td>{{ account_group.name }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div>
{% else %} {% else %}
<c-msg.empty title="{% translate "No account groups" %}" remove-padding></c-msg.empty> <c-msg.empty title="{% translate "No account groups" %}" remove-padding></c-msg.empty>
{% endif %} {% endif %}
@@ -9,36 +9,33 @@
<form hx-post="{% url 'account_reconciliation' %}"> <form hx-post="{% url 'account_reconciliation' %}">
{% csrf_token %} {% csrf_token %}
{{ form.management_form }} {{ form.management_form }}
<div class="accordion accordion-flush" id="balanceAccordionFlush"> <div class="join join-vertical w-full" id="balanceAccordionFlush">
{% for form in form.forms %} {% for form in form.forms %}
<div class="accordion-item"> <c-ui.components.collapse>
<h2 class="accordion-header"> <c-slot name="title">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" {% if form.account_group %}<span class="badge badge-primary badge-outline me-2">{{ form.account_group.name }}</span>{% endif %}{{ form.account_name }}
data-bs-target="#flush-collapse-{{ forloop.counter0 }}" aria-expanded="false" </c-slot>
aria-controls="flush-collapseOne"> <c-slot name="content">
{% if form.account_group %}<span class="badge text-bg-primary me-2">{{ form.account_group.name }}</span>{% endif %}{{ form.account_name }} <div class="fieldset">
</button> <span class="fieldset-legend">{% translate 'Current balance' %}</span>
</h2>
<div id="flush-collapse-{{ forloop.counter0 }}" class="accordion-collapse collapse">
<div class="accordion-body">
<div class="mb-3">
<div class="form-label">
{% translate 'Current balance' %}
</div>
<div data-amount="{{ form.current_balance|floatformat:"-40u" }}" <div data-amount="{{ form.current_balance|floatformat:"-40u" }}"
data-decimal-places="{{ form.currency_decimal_places }}" data-decimal-places="{{ form.currency_decimal_places }}"
id="amount-{{ forloop.counter0 }}"> id="amount-{{ forloop.counter0 }}" class="text-base">
{% currency_display amount=form.current_balance prefix=form.currency_prefix suffix=form.currency_suffix decimal_places=form.currency_decimal_places %} <c-amount.display
:amount="form.current_balance"
:prefix="form.currency_prefix"
:suffix="form.currency_suffix"
:decimal_places="form.currency_decimal_places"
color="auto"></c-amount.display>
</div> </div>
</div> </div>
<div> <div>
{% crispy form %} {% crispy form %}
</div> </div>
<div class="mb-3"> <div class="fieldset">
<div class="form-label"> <span class="fieldset-legend">{% translate 'Difference' %}</span>
{% translate 'Difference' %} <div class="text-base"
</div> _="on input from #id_form-{{ forloop.counter0 }}-new_balance
<div _="on input from #id_form-{{ forloop.counter0 }}-new_balance
set original_amount to parseFloat('{{ form.current_balance|floatformat:"-40u" }}') set original_amount to parseFloat('{{ form.current_balance|floatformat:"-40u" }}')
then set prefix to '{{ form.currency_prefix }}' then set prefix to '{{ form.currency_prefix }}'
then set suffix to '{{ form.currency_suffix }}' then set suffix to '{{ form.currency_suffix }}'
@@ -46,7 +43,6 @@
then call parseLocaleNumber(#id_form-{{ forloop.counter0 }}-new_balance.value) then call parseLocaleNumber(#id_form-{{ forloop.counter0 }}-new_balance.value)
then set new_amount to result then set new_amount to result
then set diff to (Math.round((new_amount - original_amount) * Math.pow(10, decimal_places))) / Math.pow(10, decimal_places) then set diff to (Math.round((new_amount - original_amount) * Math.pow(10, decimal_places))) / Math.pow(10, decimal_places)
then log diff
then set format_new_amount to then set format_new_amount to
Intl.NumberFormat( Intl.NumberFormat(
undefined, undefined,
@@ -57,17 +53,15 @@
} }
).format(diff) ).format(diff)
then set formatted_string to `${prefix}${format_new_amount}${suffix}` then set formatted_string to `${prefix}${format_new_amount}${suffix}`
then put formatted_string into me if diff else then put formatted_string into me if diff else put '-' into me">-</div>
put '-' into me">-</div>
</div>
</div>
</div>
</div> </div>
</c-slot>
</c-ui.components.collapse>
{% endfor %} {% endfor %}
</div> </div>
<div class="mt-3"> <div class="mt-3">
<div> <div>
<input type="submit" name="submit" value="{% translate 'Reconcile balances' %}" class="btn btn-outline-primary w-100" id="submit-id-submit"> <input type="submit" name="submit" value="{% translate 'Reconcile balances' %}" class="btn btn-primary w-full" id="submit-id-submit">
</div> </div>
</div> </div>
</form> </form>
+59 -64
View File
@@ -1,51 +1,74 @@
{% load i18n %} {% load i18n %}
<div class="container px-md-3 py-3 column-gap-5"> <c-ui.fab-single-action
<div class="tw:text-3xl fw-bold font-monospace tw:w-full mb-3"> url="{% url 'account_add' %}"
hx_target="#generic-offcanvas">
</c-ui.fab-single-action>
<div class="container">
<div class="text-3xl font-bold font-mono w-full mb-3">
{% spaceless %} {% spaceless %}
<div>{% translate 'Accounts' %}<span> <div>{% translate 'Accounts' %}</div>
<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 'account_add' %}"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-circle-plus fa-fw"></i></a>
</span></div>
{% endspaceless %} {% endspaceless %}
</div> </div>
<div class="card"> <div class="card bg-base-100 shadow-xl">
<div class="card-body table-responsive"> <div class="card-body">
{% if accounts %} {% if accounts %}
<c-config.search></c-config.search> <c-config.search></c-config.search>
<table class="table table-hover text-nowrap"> <div class="overflow-x-auto">
<table class="table table-zebra">
<thead> <thead>
<tr> <tr>
<th scope="col" class="col-auto"></th> <th scope="col" class="table-col-auto"></th>
<th scope="col" class="col">{% translate 'Name' %}</th> <th scope="col">{% translate 'Name' %}</th>
<th scope="col" class="col">{% translate 'Group' %}</th> <th scope="col">{% translate 'Group' %}</th>
<th scope="col" class="col">{% translate 'Currency' %}</th> <th scope="col">{% translate 'Currency' %}</th>
<th scope="col" class="col">{% translate 'Exchange Currency' %}</th> <th scope="col">{% translate 'Exchange Currency' %}</th>
<th scope="col" class="col">{% translate 'Is Asset' %}</th> <th scope="col">{% translate 'Is Asset' %}</th>
<th scope="col" class="col">{% translate 'Archived' %}</th> <th scope="col">{% translate 'Archived' %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for account in accounts %} {% for account in accounts %}
<tr class="account"> <tr class="account">
<td class="col-auto"> <td class="table-col-auto">
<div class="btn-group" role="group" aria-label="{% translate 'Actions' %}"> <div class="join" role="group" aria-label="{% translate 'Actions' %}">
<a class="btn btn-secondary btn-sm" <a class="btn btn-secondary btn-sm join-item"
role="button" role="button"
data-bs-toggle="tooltip" data-tippy-content="{% translate "Edit" %}"
data-bs-title="{% translate "Edit" %}"
hx-get="{% url 'account_edit' pk=account.id %}" hx-get="{% url 'account_edit' pk=account.id %}"
hx-target="#generic-offcanvas"> hx-target="#generic-offcanvas">
<i class="fa-solid fa-pencil fa-fw"></i></a> <i class="fa-solid fa-pencil fa-fw"></i></a>
<a class="btn btn-secondary btn-sm text-danger" {% if not account.owner %}
<a class="btn btn-secondary btn-sm join-item"
role="button" role="button"
data-bs-toggle="tooltip" data-tippy-content="{% translate "Take ownership" %}"
data-bs-title="{% translate "Delete" %}" hx-get="{% url 'account_take_ownership' pk=account.id %}">
<i class="fa-solid fa-crown fa-fw"></i></a>
{% endif %}
{% if user == account.owner %}
<a class="btn btn-secondary btn-sm join-item"
role="button"
hx-target="#generic-offcanvas"
hx-swap="innerHTML"
data-tippy-content="{% translate "Share" %}"
hx-get="{% url 'account_share_settings' pk=account.id %}">
<i class="fa-solid fa-share fa-fw"></i></a>
{% endif %}
<a class="btn btn-secondary btn-sm join-item"
role="button"
hx-get="{% url 'account_toggle_untracked' pk=account.id %}"
data-tippy-content="
{% if account.is_untracked_by %}{% translate "Track" %}{% else %}{% translate "Untrack" %}{% endif %}">
{% if account.is_untracked_by %}
<i class="fa-solid fa-eye fa-fw"></i>
{% else %}
<i class="fa-solid fa-eye-slash fa-fw"></i>
{% endif %}
</a>
<a class="btn btn-error btn-sm join-item"
role="button"
data-tippy-content="{% translate "Delete" %}"
hx-delete="{% url 'account_delete' pk=account.id %}" hx-delete="{% url 'account_delete' pk=account.id %}"
hx-trigger='confirmed' hx-trigger='confirmed'
data-bypass-on-ctrl="true" data-bypass-on-ctrl="true"
@@ -53,47 +76,19 @@
data-text="{% translate "You won't be able to revert this!" %}" data-text="{% translate "You won't be able to revert this!" %}"
data-confirm-text="{% translate "Yes, delete it!" %}" data-confirm-text="{% translate "Yes, delete it!" %}"
_="install prompt_swal"><i class="fa-solid fa-trash fa-fw"></i></a> _="install prompt_swal"><i class="fa-solid fa-trash fa-fw"></i></a>
{% if not account.owner %}
<a class="btn btn-secondary btn-sm text-primary"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Take ownership" %}"
hx-get="{% url 'account_take_ownership' pk=account.id %}">
<i class="fa-solid fa-crown fa-fw"></i></a>
{% endif %}
{% if user == account.owner %}
<a class="btn btn-secondary btn-sm text-primary"
role="button"
hx-target="#generic-offcanvas"
hx-swap="innerHTML"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Share" %}"
hx-get="{% url 'account_share_settings' pk=account.id %}">
<i class="fa-solid fa-share fa-fw"></i></a>
{% endif %}
<a class="btn btn-secondary btn-sm"
role="button"
hx-get="{% url 'account_toggle_untracked' pk=account.id %}"
data-bs-toggle="tooltip"
data-bs-title="{% if account.is_untracked_by %}{% translate "Track" %}{% else %}{% translate "Untrack" %}{% endif %}">
{% if account.is_untracked_by %}
<i class="fa-solid fa-eye fa-fw"></i>
{% else %}
<i class="fa-solid fa-eye-slash fa-fw"></i>
{% endif %}
</a>
</div> </div>
</td> </td>
<td class="col">{{ account.name }}</td> <td>{{ account.name }}</td>
<td class="col">{{ account.group.name }}</td> <td>{{ account.group.name }}</td>
<td class="col">{{ account.currency }}</td> <td>{{ account.currency }}</td>
<td class="col">{% if account.exchange_currency %}{{ account.exchange_currency }}{% else %}-{% endif %}</td> <td>{% if account.exchange_currency %}{{ account.exchange_currency }}{% else %}-{% endif %}</td>
<td class="col">{% if account.is_asset %}<i class="fa-solid fa-solid fa-check text-success"></i>{% endif %}</td> <td>{% if account.is_asset %}<i class="fa-solid fa-solid fa-check text-success"></i>{% endif %}</td>
<td class="col">{% if account.is_archived %}<i class="fa-solid fa-solid fa-check text-success"></i>{% endif %}</td> <td>{% if account.is_archived %}<i class="fa-solid fa-solid fa-check text-success"></i>{% endif %}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div>
{% else %} {% else %}
<c-msg.empty title="{% translate "No accounts" %}" remove-padding></c-msg.empty> <c-msg.empty title="{% translate "No accounts" %}" remove-padding></c-msg.empty>
{% endif %} {% endif %}
+23 -23
View File
@@ -2,67 +2,67 @@
{% load i18n %} {% load i18n %}
<div> <div>
<div class="tw:hidden tw:lg:grid tw:lg:grid-cols-7 tw:gap-4 tw:lg:gap-0"> <div class="hidden lg:grid lg:grid-cols-7 gap-4 lg:gap-0 bg-base-200">
<div class="border-start border-top border-bottom p-2 text-center"> <div class="border-l border-t border-b border-base-300 p-2 text-center">
{% translate 'MON' %} {% translate 'MON' %}
</div> </div>
<div class="border-top border-bottom p-2 text-center"> <div class="border-t border-b border-base-300 p-2 text-center">
{% translate 'TUE' %} {% translate 'TUE' %}
</div> </div>
<div class="border-top border-bottom p-2 text-center"> <div class="border-t border-b border-base-300 p-2 text-center">
{% translate 'WED' %} {% translate 'WED' %}
</div> </div>
<div class="border-top border-bottom p-2 text-center"> <div class="border-t border-b border-base-300 p-2 text-center">
{% translate 'THU' %} {% translate 'THU' %}
</div> </div>
<div class="border-top border-bottom p-2 text-center"> <div class="border-t border-b border-base-300 p-2 text-center">
{% translate 'FRI' %} {% translate 'FRI' %}
</div> </div>
<div class="border-top border-bottom p-2 text-center"> <div class="border-t border-b border-base-300 p-2 text-center">
{% translate 'SAT' %} {% translate 'SAT' %}
</div> </div>
<div class="border-end border-top border-bottom p-2 text-center"> <div class="border-r border-t border-b border-base-300 p-2 text-center">
{% translate 'SUN' %} {% translate 'SUN' %}
</div> </div>
</div> </div>
<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"> <div class="grid grid-cols-1 grid-rows-1 lg:grid-cols-7 lg:grid-rows-6 gap-4 lg:gap-0">
{% for date in dates %} {% for date in dates %}
{% if date %} {% if date %}
<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" <div class="card bg-base-100 h-full hover:bg-base-200! border border-base-content/30 rounded-none {% if not date.transactions %}hidden! lg:flex!{% endif %}{% if today == date.date %} border-2 border-primary{% endif %} cursor-pointer" role="button"
hx-get="{% url 'calendar_transactions_list' day=date.date.day month=date.date.month year=date.date.year %}" hx-get="{% url 'calendar_transactions_list' day=date.date.day month=date.date.month year=date.date.year %}"
hx-target="#persistent-generic-offcanvas-left"> 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="card-header border-0 bg-transparent text-end flex justify-between p-2 w-full">
<div class="tw:lg:hidden text-start w-100">{{ date.date|date:"l"|lower }}</div> <div class="lg:hidden text-start w-full">{{ date.date|date:"l"|lower }}</div>
<div class="text-end w-100">{{ date.day }}</div> <div class="text-end w-full">{{ date.day }}</div>
</div> </div>
<div class="card-body p-2"> <div class="card-body p-2 flex flex-row flex-wrap gap-1">
{% for transaction in date.transactions %} {% for transaction in date.transactions %}
{% if transaction.is_paid %} {% if transaction.is_paid %}
{% if transaction.type == "IN" and not transaction.account.is_asset %} {% 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 text-success" data-tippy-content="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Income' %}{% endif %}"></i>
{% elif transaction.type == "IN" and transaction.account.is_asset %} {% 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 text-success/80" data-tippy-content="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Income' %}{% endif %}"></i>
{% elif transaction.type == "EX" and not transaction.account.is_asset %} {% 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 text-error" data-tippy-content="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Expense' %}{% endif %}"></i>
{% elif transaction.type == "EX" and transaction.account.is_asset %} {% 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 text-error/80" data-tippy-content="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Expense' %}{% endif %}"></i>
{% endif %} {% endif %}
{% else %} {% else %}
{% if transaction.type == "IN" and not transaction.account.is_asset %} {% 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 text-success" data-tippy-content="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Income' %}{% endif %}"></i>
{% elif transaction.type == "IN" and transaction.account.is_asset %} {% 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 text-success/80" data-tippy-content="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Income' %}{% endif %}"></i>
{% elif transaction.type == "EX" and not transaction.account.is_asset %} {% 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 text-error" data-tippy-content="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Expense' %}{% endif %}"></i>
{% elif transaction.type == "EX" and transaction.account.is_asset %} {% 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 text-error/80" data-tippy-content="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Expense' %}{% endif %}"></i>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
{% else %} {% else %}
<div class="tw:hidden! tw:lg:block! card h-100 rounded-0"></div> <div class="hidden! lg:block! card bg-base-300 h-full rounded-none"></div>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</div> </div>
+11 -22
View File
@@ -3,7 +3,6 @@
{% load i18n %} {% load i18n %}
{% load month_name %} {% load month_name %}
{% load static %} {% load static %}
{% load webpack_loader %}
{% block title %}{% translate 'Monthly Overview' %} :: {{ month|month_name }}/{{ year }}{% endblock %} {% block title %}{% translate 'Monthly Overview' %} :: {{ month|month_name }}/{{ year }}{% endblock %}
@@ -13,45 +12,35 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="container px-md-3 py-3 column-gap-5"> <div class="container">
<div class="row mb-3 gx-xl-4 gy-3 mb-4"> <div class="flex flex-wrap mb-4 gap-x-xl-4 gap-y-3">
{# Date picker#} {# Date picker#}
<div class="col-12 col-xl-4 flex-row align-items-center d-flex"> <div class="w-full xl:w-4/12 flex-row items-center flex">
<div class="tw:text-base h-100 align-items-center d-flex">
<a role="button" <a role="button"
class="pe-4 py-2"
hx-boost="true" hx-boost="true"
class="btn btn-ghost"
hx-trigger="click, previous_month from:window" hx-trigger="click, previous_month from:window"
href="{% url 'calendar' month=previous_month year=previous_year %}"><i href="{% url 'calendar' month=previous_month year=previous_year %}">
class="fa-solid fa-chevron-left"></i></a> <i class="fa-solid fa-chevron-left"></i>
</div> </a>
<div class="tw:text-3xl fw-bold font-monospace tw:w-full text-center" <div class="text-2xl font-bold btn btn-ghost flex-1 text-center whitespace-normal flex-wrap h-auto min-w-0 1flex flex-"
hx-get="{% url 'month_year_picker' %}" hx-get="{% url 'month_year_picker' %}"
hx-target="#generic-offcanvas-left" hx-target="#generic-offcanvas-left"
hx-trigger="click, date_picker from:window" hx-trigger="click, date_picker from:window"
hx-vals='{"month": {{ month }}, "year": {{ year }}, "for": "calendar", "field": "date"}' role="button"> hx-vals='{"month": {{ month }}, "year": {{ year }}, "for": "calendar", "field": "date"}' role="button">
{{ month|month_name }} {{ year }} {{ month|month_name }} {{ year }}
</div> </div>
<div class="tw:text-base mx-2 h-100 align-items-center d-flex">
<a role="button" <a role="button"
class="ps-3 py-2"
hx-boost="true" hx-boost="true"
class="btn btn-ghost"
hx-trigger="click, next_month from:window" hx-trigger="click, next_month from:window"
href="{% url 'calendar' month=next_month year=next_year %}"> href="{% url 'calendar' month=next_month year=next_year %}">
<i class="fa-solid fa-chevron-right"></i> <i class="fa-solid fa-chevron-right"></i>
</a> </a>
</div> </div>
</div> </div>
{# Action buttons#} <div class="flex flex-wrap">
<div class="col-12 col-xl-8"> <div class="show-loading w-full" hx-get="{% url 'calendar_list' month=month year=year %}"
{# <c-ui.quick-transactions-buttons#}
{# :year="year"#}
{# :month="month"#}
{# ></c-ui.quick-transactions-buttons>#}
</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, every 10m"></div> hx-trigger="load, updated from:window, selective_update from:window, every 10m"></div>
</div> </div>
</div> </div>
+19 -22
View File
@@ -1,32 +1,29 @@
{% load i18n %} {% load i18n %}
<div class="container px-md-3 py-3 column-gap-5"> <c-ui.fab-single-action
<div class="tw:text-3xl fw-bold font-monospace tw:w-full mb-3"> url="{% url 'category_add' %}"
hx_target="#generic-offcanvas">
</c-ui.fab-single-action>
<div class="container">
<div class="text-3xl font-bold font-mono w-full mb-3">
{% spaceless %} {% spaceless %}
<div>{% translate 'Categories' %}<span> <div>{% translate 'Categories' %}</div>
<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 'category_add' %}"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-circle-plus fa-fw"></i></a>
</span></div>
{% endspaceless %} {% endspaceless %}
</div> </div>
<div class="card"> <div class="card bg-base-100 shadow-xl">
<div class="card-header"> <div class="card-header bg-base-200 p-4 rounded-box">
<ul class="nav nav-pills card-header-pills" id="myTab" role="tablist"> <div role="tablist" class="tabs tabs-border">
<li class="nav-item" role="presentation"> <input type="radio" name="installment_plan_tabs" class="tab" aria-label="{% translate 'Active' %}"
<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> checked="checked"
</li> hx-get="{% url 'categories_table_active' %}" hx-trigger="load, click" hx-target="#categories-table"
<li class="nav-item" role="presentation"> hx-indicator="#categories-table"/>
<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> <input type="radio" name="installment_plan_tabs" class="tab" aria-label="{% translate 'Archived' %}"
</li> hx-get="{% url 'categories_table_archived' %}" hx-trigger="click" hx-target="#categories-table"
</ul> hx-indicator="#categories-table"/>
</div>
</div> </div>
<div class="card-body"> <div class="card-body">
<div id="categories-table"></div> <div id="categories-table" class="show-loading"></div>
</div> </div>
</div> </div>
</div> </div>
+34 -36
View File
@@ -6,34 +6,49 @@
<div class="show-loading" hx-get="{% url 'categories_table_archived' %}" hx-trigger="updated from:window" <div class="show-loading" hx-get="{% url 'categories_table_archived' %}" hx-trigger="updated from:window"
hx-swap="outerHTML"> hx-swap="outerHTML">
{% endif %} {% endif %}
{% if categories %} {% if categories %}
<div class="table-responsive"> <div>
<c-config.search></c-config.search> <c-config.search></c-config.search>
<table class="table table-hover"> <div class="overflow-x-auto">
<table class="table table-zebra">
<thead> <thead>
<tr> <tr>
<th scope="col" class="col-auto"></th> <th scope="col" class="table-col-auto"></th>
<th scope="col" class="col">{% translate 'Name' %}</th> <th scope="col">{% translate 'Name' %}</th>
<th scope="col" class="col">{% translate 'Muted' %}</th> <th scope="col">{% translate 'Muted' %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for category in categories %} {% for category in categories %}
<tr class="category"> <tr class="category">
<td class="col-auto text-center"> <td class="table-col-auto text-center">
<div class="btn-group" role="group" aria-label="{% translate 'Actions' %}"> <div class="join" role="group" aria-label="{% translate 'Actions' %}">
<a class="btn btn-secondary btn-sm" <a class="btn btn-secondary btn-sm join-item"
role="button" role="button"
data-bs-toggle="tooltip"
hx-swap="innerHTML" hx-swap="innerHTML"
data-bs-title="{% translate "Edit" %}" data-tippy-content="{% translate "Edit" %}"
hx-get="{% url 'category_edit' category_id=category.id %}" hx-get="{% url 'category_edit' category_id=category.id %}"
hx-target="#generic-offcanvas"> hx-target="#generic-offcanvas">
<i class="fa-solid fa-pencil fa-fw"></i></a> <i class="fa-solid fa-pencil fa-fw"></i></a>
<a class="btn btn-secondary btn-sm text-danger" {% if not category.owner %}
<a class="btn btn-secondary btn-sm join-item"
role="button" role="button"
data-bs-toggle="tooltip" data-tippy-content="{% translate "Take ownership" %}"
data-bs-title="{% translate "Delete" %}" hx-get="{% url 'category_take_ownership' category_id=category.id %}">
<i class="fa-solid fa-crown fa-fw"></i></a>
{% endif %}
{% if user == category.owner %}
<a class="btn btn-secondary btn-sm join-item"
role="button"
hx-target="#generic-offcanvas"
hx-swap="innerHTML"
data-tippy-content="{% translate "Share" %}"
hx-get="{% url 'category_share_settings' pk=category.id %}">
<i class="fa-solid fa-share fa-fw"></i></a>
{% endif %}
<a class="btn btn-error btn-sm join-item"
role="button"
data-tippy-content="{% translate "Delete" %}"
hx-delete="{% url 'category_delete' category_id=category.id %}" hx-delete="{% url 'category_delete' category_id=category.id %}"
hx-trigger='confirmed' hx-trigger='confirmed'
hx-swap="innerHTML" hx-swap="innerHTML"
@@ -42,28 +57,10 @@
data-text="{% translate "You won't be able to revert this!" %}" data-text="{% translate "You won't be able to revert this!" %}"
data-confirm-text="{% translate "Yes, delete it!" %}" data-confirm-text="{% translate "Yes, delete it!" %}"
_="install prompt_swal"><i class="fa-solid fa-trash fa-fw"></i></a> _="install prompt_swal"><i class="fa-solid fa-trash fa-fw"></i></a>
{% if not category.owner %}
<a class="btn btn-secondary btn-sm text-primary"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Take ownership" %}"
hx-get="{% url 'category_take_ownership' category_id=category.id %}">
<i class="fa-solid fa-crown fa-fw"></i></a>
{% endif %}
{% if user == category.owner %}
<a class="btn btn-secondary btn-sm text-primary"
role="button"
hx-target="#generic-offcanvas"
hx-swap="innerHTML"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Share" %}"
hx-get="{% url 'category_share_settings' pk=category.id %}">
<i class="fa-solid fa-share fa-fw"></i></a>
{% endif %}
</div> </div>
</td> </td>
<td class="col">{{ category.name }}</td> <td>{{ category.name }}</td>
<td class="col"> <td>
{% if category.mute %}<i class="fa-solid fa-check text-success"></i>{% endif %} {% if category.mute %}<i class="fa-solid fa-check text-success"></i>{% endif %}
</td> </td>
</tr> </tr>
@@ -71,7 +68,8 @@
</tbody> </tbody>
</table> </table>
</div> </div>
{% else %} </div>
{% else %}
<c-msg.empty title="{% translate "No categories" %}" remove-padding></c-msg.empty> <c-msg.empty title="{% translate "No categories" %}" remove-padding></c-msg.empty>
{% endif %} {% endif %}
</div> </div>
@@ -7,49 +7,33 @@
{% block body %} {% block body %}
{% regroup month_year_data by year as years_list %} {% regroup month_year_data by year as years_list %}
<ul class="nav nav-pills nav-fill" id="yearTabs" role="tablist"> <div role="tablist" class="tabs tabs-border w-full" id="yearTabs">
{% for x in years_list %} {% for x in years_list %}
<li class="nav-item" role="presentation"> <input type="radio"
<button class="nav-link{% if x.grouper == current_year %} active{% endif %}" name="year_tabs"
id="{{ x.grouper }}"
data-bs-toggle="tab"
data-bs-target="#{{ x.grouper }}-pane"
type="button"
role="tab" role="tab"
aria-controls="{{ x.grouper }}-pane" class="tab"
aria-selected="{% if x.grouper == current_year %}true{% else %}false{% endif %}"> aria-label="{{ x.grouper }}"
{{ x.grouper }} id="tab-{{ x.grouper }}"
</button> {% if x.grouper == current_year %}checked="checked"{% endif %} />
</li> <div role="tabpanel" class="tab-content" id="{{ x.grouper }}-pane">
{% endfor %} <ul class="menu bg-base-100 w-full" id="month-year-list" hx-boost="true">
</ul>
<div class="tab-content" id="yearTabsContent" hx-boost="true">
{% for x in years_list %}
<div class="tab-pane fade{% if x.grouper == current_year %} show active{% endif %} mt-2"
id="{{ x.grouper }}-pane"
role="tabpanel"
aria-labelledby="{{ x.grouper }}"
tabindex="0">
<ul class="list-group list-group-flush" id="month-year-list">
{% for month_data in x.list %} {% for month_data in x.list %}
<li class="list-group-item tw:hover:bg-zinc-900 <li {% if month_data.month == current_month and month_data.year == current_year %}class="disabled"{% endif %}>
{% if month_data.month == current_month and month_data.year == current_year %} disabled bg-primary{% endif %}" <a class="{% if month_data.month == current_month and month_data.year == current_year %}menu-active{% endif %}"
href={{ month_data.url }}
{% if month_data.month == current_month and month_data.year == current_year %}aria-disabled="true"{% endif %}> {% if month_data.month == current_month and month_data.year == current_year %}aria-disabled="true"{% endif %}>
<div class="d-flex justify-content-between"> <span class="flex-1">{{ month_data.month|month_name }}</span>
<a class="text-decoration-none stretched-link {% if month_data.month == current_month and month_data.year == current_year %} text-black{% endif %}" <span class="badge badge-primary">{{ month_data.transaction_count }}</span>
href={{ month_data.url }}> </a>
{{ month_data.month|month_name }}</a>
<span class="badge text-bg-secondary">{{ month_data.transaction_count }}</span>
</div>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
<hr> <hr class="hr my-4">
<div class="w-full text-end"> <div class="w-full text-end">
<a class="btn btn-outline-primary btn-sm" href="{{ today_url }}" role="button" hx-boost="true">{% trans 'Today' %}</a> <a class="btn btn-outline btn-primary btn-sm" href="{{ today_url }}" role="button" hx-boost="true">{% trans 'Today' %}</a>
</div> </div>
{% endblock %} {% endblock %}
+15 -9
View File
@@ -2,19 +2,25 @@
{% load toast_bg %} {% load toast_bg %}
{% if messages %} {% if messages %}
{% for message in messages %} {% for message in messages %}
<div class="toast align-items-center text-bg-{{ message.tags | toast_bg }} border-0" <div class="toasty alert alert-{{ message.tags | toast_bg }}"
role="alert" role="alert"
aria-live="assertive" aria-live="assertive"
aria-atomic="true"> aria-atomic="true">
<div class="toast-header"> <div class="flex items-center justify-between w-full">
<i class="{{ message.tags | toast_icon }} fa-fw me-1"></i> <div class="flex items-center gap-2">
<strong class="me-auto">{{ message.tags | toast_title }}</strong> <i class="{{ message.tags | toast_icon }} fa-fw"></i>
<button type="button" <div>
class="btn-close" <strong>{{ message.tags | toast_title }}</strong>
data-bs-dismiss="toast" <div>{{ message }}</div>
aria-label={% translate 'Close' %}></button> </div>
</div>
<button type="button"
class="btn btn-ghost btn-sm btn-circle"
_="on click remove closest .toasty"
aria-label={% translate 'Close' %}>
<i class="fa-solid fa-xmark"></i>
</button>
</div> </div>
<div class="toast-body">{{ message }}</div>
</div> </div>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
+25 -2
View File
@@ -1,3 +1,26 @@
{#This is here so we can add dynamic Tailwind classes that will be required via JS/hyperscript but Tailwind has no knowledge of#} {#This is here so we can add dynamic Tailwind classes that will be required via JS/hyperscript but Tailwind has no knowledge of#}
<div class="tw:lg:w-[15vw]"></div> <div class="lg:w-[15vw]"></div>
<div class="tw:lg:ml-[16vw]"></div> <div class="lg:ml-[16vw]"></div>
<div class="grid-cols-12"></div>
<div class="md:col-span-1"></div>
<div class="md:col-span-2"></div>
<div class="md:col-span-3"></div>
<div class="md:col-span-4"></div>
<div class="md:col-span-5"></div>
<div class="md:col-span-6"></div>
<div class="md:col-span-7"></div>
<div class="md:col-span-8"></div>
<div class="md:col-span-9"></div>
<div class="md:col-span-10"></div>
<div class="md:col-span-11"></div>
<div class="md:col-span-12"></div>
<div class="col-span-12"></div>
<div class="alert-error"></div>
<div class="alert-info"></div>
<div class="alert-success"></div>
<div class="alert-warning"></div>
<div class="textarea"></div>
<div class="border-base-content/60"></div>
<div class="bg-error/20"></div>
<div class="bg-success/20"></div>
<div class="checkbox checkbox-xs"></div>
+15 -2
View File
@@ -1,10 +1,23 @@
{% load currency_display %} {% load currency_display %}
{% currency_display amount=amount prefix=prefix suffix=suffix decimal_places=decimal_places as formatted_amount %}
{% if not divless %} {% if not divless %}
<div class="{% if text_end %}text-end{% elif text_start %}text-start{% endif %}"> <div class="{% if text_end %}text-end{% elif text_start %}text-start{% endif %}">
{% 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
data-original-value="{% currency_display amount=amount prefix=prefix suffix=suffix decimal_places=decimal_places %}" {% if color == 'grey' or color == "gray" %} text-exchange-rate
{% elif color == 'green' %} text-income {% elif color == 'red' %} text-expense
{% elif color == 'auto' %}
{% if amount > 0 %} text-income
{% elif amount < 0 %} text-expense
{% else %} text-base-content {% endif %}
{% endif %}
font-medium {{ custom_class }}"
data-original-sign="{{ formatted_amount.sign }}"
data-original-prefix="{{ formatted_amount.prefix }}"
data-original-amount="{{ formatted_amount.amount }}"
data-original-suffix="{{ formatted_amount.suffix }}"
data-amount="{{ amount|floatformat:"-40u" }}"> data-amount="{{ amount|floatformat:"-40u" }}">
</span><span>{{ slot }}</span> </span><span>{{ slot }}</span>
{% if not divless %} {% if not divless %}
+10 -29
View File
@@ -1,33 +1,14 @@
<div class="tw:min-h-16"> {% load i18n%}
<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 tw:z-20">
<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 class="fab">
<div tabindex="0" role="button" class="btn btn-lg btn-circle btn-primary">
<i class="fa-solid fa-plus text-2xl fa-fw"></i>
</div> </div>
<button <div class="fab-close">
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" {% trans 'Close' %} <span class="btn btn-circle btn-lg btn-error"><i class="fa-solid fa-xmark text-2xl fa-fw"></i></span>
_=" </div>
on click or focusout
if #menu.classList.contains('tw:invisible') and event.type === 'click' {{ slot }}
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> </div>
@@ -1,11 +1,6 @@
{% load i18n %} <div hx-get="{{ url }}"
<div class="tw:relative fab-item">
<button class="btn btn-sm btn-{{ color }}"
hx-get="{{ url }}"
hx-trigger="{{ hx_trigger }}" hx-trigger="{{ hx_trigger }}"
hx-target="{{ hx_target }}" hx-target="{{ hx_target }}"
hx-vals='{{ hx_vals }}'> hx-vals='{{ hx_vals }}'>
<i class="{{ icon }} me-2"></i> <span class="bg-neutral/60 text-neutral-content rounded-box p-2">{{ title }}</span>
{{ title }} <button class="btn btn-lg btn-circle btn-{{color}}"><i class="{{ icon }} fa-fw"></i></button></div>
</button>
</div>
@@ -1,12 +1,12 @@
<li class="tw:lg:hidden tw:lg:group-hover:block"> <li class="lg:hidden lg:group-hover:block">
<div class="d-flex align-items-center" data-bs-toggle="collapse" href="#{{ title|slugify }}" role="button" <div class="flex items-center" data-bs-toggle="collapse" href="#{{ title|slugify }}" role="button"
aria-expanded="false" aria-controls="{{ title|slugify }}"> aria-expanded="false" aria-controls="{{ title|slugify }}">
<span <span
class="text-muted small fw-bold text-uppercase tw:lg:hidden tw:lg:group-hover:inline me-2">{{ title }}</span> class="text-base-content/60 text-sm font-bold uppercase lg:hidden lg:group-hover:inline me-2">{{ title }}</span>
<hr class="flex-grow-1"/> <hr class="flex-grow"/>
<i class="fas fa-chevron-down text-muted tw:lg:before:hidden tw:lg:group-hover:before:inline tw:ml-2 tw:lg:ml-0 tw:lg:group-hover:ml-2"></i> <i class="fas fa-chevron-down text-base-content/60 lg:before:hidden lg:group-hover:before:inline ml-2 lg:ml-0 lg:group-hover:ml-2"></i>
</div> </div>
</li> </li>
<div class="collapse tw:lg:hidden tw:lg:group-hover:block" id="{{ title|slugify }}"> <div class="collapse lg:hidden lg:group-hover:block" id="{{ title|slugify }}">
{{ slot }} {{ slot }}
</div> </div>
@@ -1,6 +1,8 @@
<li> <li>
<div class="d-flex align-items-center"> <div class="flex items-center min-h-6">
<span class="sidebar-menu-header text-muted small fw-bold text-uppercase me-2">{{ title }}</span> {% if title %}
<hr class="flex-grow-1"/> <span class="sidebar-menu-header text-base-content/60 text-xs font-bold uppercase mr-3">{{ title }}</span>
{% endif %}
<hr class="hr grow"/>
</div> </div>
</li> </li>
@@ -1,14 +1,13 @@
{% load active_link %} {% load active_link %}
<li> <li>
<a href="{% url url %}" <a href="{% url url %}"
class="tw:lg:text-sm d-flex align-items-center text-decoration-none p-2 rounded-3 sidebar-item {% active_link views=active css_class="sidebar-active" %}" class="text-xs flex items-center no-underline ps-3 p-2 rounded-box sidebar-item {% active_link views=active css_class="sidebar-active" %}"
{% if tooltip %} {% if tooltip %}
data-bs-placement="right" data-tippy-placement="right"
data-bs-toggle="tooltip" data-tippy-content="{{ tooltip }}"
data-bs-title="{{ tooltip }}"
{% endif %}> {% endif %}>
<i class="{{ icon }} fa-fw"></i> <i class="{{ icon }} fa-fw"></i>
<span <span
class="ms-3 fw-medium tw:lg:group-hover:truncate tw:lg:group-focus:truncate tw:lg:group-hover:text-ellipsis tw:lg:group-focus:text-ellipsis">{{ title }}</span> class="ms-3 font-medium">{{ title }}</span>
</a> </a>
</li> </li>
@@ -2,15 +2,14 @@
<li> <li>
<a href="{{ url }}" <a href="{{ url }}"
hx-boost="false" hx-boost="false"
class="tw:lg:text-sm d-flex align-items-center text-decoration-none p-2 rounded-3 sidebar-item {% active_link views=active css_class="sidebar-active" %}" class="text-xs flex items-center no-underline ps-3 p-2 rounded-3xl sidebar-item {% active_link views=active css_class="sidebar-active" %}"
{% if tooltip %} {% if tooltip %}
data-bs-placement="right" data-tippy-placement="right"
data-bs-toggle="tooltip" data-tippy-content="{{ tooltip }}"
data-bs-title="{{ tooltip }}"
{% endif %}> {% endif %}>
<i class="{{ icon }} fa-fw"></i> <i class="{{ icon }} fa-fw"></i>
<span <span
class="ms-3 fw-medium tw:lg:group-hover:truncate tw:lg:group-focus:truncate tw:lg:group-hover:text-ellipsis tw:lg:group-focus:text-ellipsis">{{ title }}</span> class="ms-3 font-medium">{{ title }}</span>
</a> </a>
</li> </li>
+2 -2
View File
@@ -1,8 +1,8 @@
{% load i18n %} {% load i18n %}
<div id="search" class="mb-3"> <div id="search" class="mb-3">
<label class="w-100"> <label class="w-full">
<input type="search" <input type="search"
class="form-control" class="input input-bordered w-full"
placeholder="{% translate 'Search' %}" placeholder="{% translate 'Search' %}"
_="on input or search _="on input or search
show < tbody>tr /> in <table/> show < tbody>tr /> in <table/>
+5 -5
View File
@@ -1,9 +1,9 @@
<div class="row {% if not remove_padding %}p-5{% endif %}"> <div class="grid grid-cols-1 {% if not remove_padding %}p-5{% endif %}">
<div class="col {% if not remove_padding %}p-5{% endif %}"> <div class="{% if not remove_padding %}p-5{% endif %}">
<div class="text-center"> <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 %} text-6xl"></i>
<p class="lead mt-4 mb-0">{{ title }}</p> <p class="text-lg mt-4 mb-0">{{ title }}</p>
<p class="tw:text-gray-500">{{ subtitle }}</p> <p class="text-subtle">{{ subtitle }}</p>
</div> </div>
</div> </div>
</div> </div>
+87 -94
View File
@@ -1,23 +1,22 @@
{% load markdown %} {% load markdown %}
{% load i18n %} {% load i18n %}
<div <div
class="transaction {% if transaction.type == "EX" %}expense{% else %}income{% endif %} tw:group/transaction tw:relative tw:hover:z-10"> class="transaction {% if transaction.type == "EX" %}expense{% else %}income{% endif %} group/transaction">
<div class="d-flex my-1"> <div class="flex my-1">
{% if not disable_selection or not dummy %} {% if not disable_selection or not dummy %}
<label class="px-3 d-flex align-items-center justify-content-center"> <label class="px-3 flex! items-center justify-center">
<input class="form-check-input" type="checkbox" name="transactions" value="{{ transaction.id }}" <input class="checkbox" type="checkbox" name="transactions" value="{{ transaction.id }}"
id="check-{{ transaction.id }}" aria-label="{% translate 'Select' %}" hx-preserve> id="check-{{ transaction.id }}" aria-label="{% translate 'Select' %}" hx-preserve>
</label> </label>
{% endif %} {% endif %}
<div class="tw:border-s-4 tw:border-e-0 tw:border-t-0 tw:border-b-0 border-bottom <div class="border-s-4 border-e-0 border-t-0
tw:hover:bg-zinc-900 p-2 {% if transaction.account.is_asset %}tw:border-dashed{% else %}tw:border-solid{% endif %} hover:bg-base-200 p-2 {% if transaction.account.is_asset %}border-dashed{% else %}border-solid{% endif %}
{% if transaction.type == "EX" %}tw:border-red-500{% else %}tw:border-green-500{% endif %} tw:relative {% if transaction.type == "EX" %}border-s-error{% else %}border-s-success{% endif %} relative
w-100 transaction-item"> w-full transaction-item bg-base-100 rounded">
<div class="row font-monospace tw:text-sm align-items-center"> <div class="flex flex-wrap font-mono text-sm items-center">
<div <div class="lg:w-auto w-full flex items-center text-2xl lg:text-xl lg:text-center text-center p-0 cursor-pointer">
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 %} {% if not transaction.deleted %}
<a class="text-decoration-none p-3 tw:text-gray-500!" <a class="no-underline p-3 text-base-content/50"
title="{% if transaction.is_paid %}{% trans 'Paid' %}{% else %}{% trans 'Projected' %}{% endif %}" title="{% if transaction.is_paid %}{% trans 'Paid' %}{% else %}{% trans 'Projected' %}{% endif %}"
role="button" role="button"
{% if not dummy %} {% if not dummy %}
@@ -28,7 +27,7 @@
class="fa-regular fa-circle"></i>{% endif %} class="fa-regular fa-circle"></i>{% endif %}
</a> </a>
{% else %} {% else %}
<div class="text-decoration-none p-3 tw:text-gray-500!" <div class="no-underline p-3 text-base-content/50"
title="{% if transaction.is_paid %}{% trans 'Paid' %}{% else %}{% trans 'Projected' %}{% endif %}"> 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 {% if transaction.is_paid %}<i class="fa-regular fa-circle-check"></i>{% else %}<i
class="fa-regular fa-circle"></i>{% endif %} class="fa-regular fa-circle"></i>{% endif %}
@@ -36,79 +35,79 @@
{% endif %} {% endif %}
</div> </div>
<div <div
class="col-lg col-12 {% if transaction.account.is_untracked_by or transaction.category.mute or transaction.mute %}tw:brightness-80{% endif %}"> class="lg:flex-1 w-full {% if transaction.account.is_untracked_by or transaction.category.mute or transaction.mute %}opacity-70{% endif %}">
{# Date#} {# Date#}
<div class="row mb-2 mb-lg-1 tw:text-gray-400"> <div class="flex flex-wrap mb-2 lg:mb-1 text-base-content/70">
<div class="col-auto pe-1"><i class="fa-solid fa-calendar fa-fw me-1 fa-xs"></i></div> <div class="w-auto pe-1"><i class="fa-solid fa-calendar fa-fw mr-1 fa-xs"></i></div>
<div <div
class="col ps-0">{{ transaction.date|date:"SHORT_DATE_FORMAT" }} • {{ transaction.reference_date|date:"b/Y" }}</div> class="flex-1 ps-0">{{ transaction.date|date:"SHORT_DATE_FORMAT" }} • {{ transaction.reference_date|date:"b/Y" }}</div>
</div> </div>
{# Description#} {# Description#}
<div class="mb-2 mb-lg-1 text-body tw:text-base"> <div class="mb-2 lg:mb-1 text-base-content text-base">
{% spaceless %} {% spaceless %}
<span class="{% if transaction.description %}me-2{% endif %}">{{ transaction.description }}</span> <span class="{% if transaction.description %}mr-2{% endif %}">{{ transaction.description }}</span>
{% if transaction.installment_plan and transaction.installment_id %} {% if transaction.installment_plan and transaction.installment_id %}
<span <span
class="badge text-bg-secondary mx-1">{{ transaction.installment_id }}/{{ transaction.installment_plan.installment_total_number }}</span> class="badge badge-neutral mx-1">{{ transaction.installment_id }}/{{ transaction.installment_plan.installment_total_number }}</span>
{% endif %} {% endif %}
{% if transaction.recurring_transaction %} {% if transaction.recurring_transaction %}
<span class="text-primary tw:text-xs mx-1"><i class="fa-solid fa-arrows-rotate fa-fw"></i></span> <span class="text-primary text-xs mx-1"><i class="fa-solid fa-arrows-rotate fa-fw"></i></span>
{% endif %} {% endif %}
{% if transaction.dca_expense_entries.all or transaction.dca_income_entries.all %} {% if transaction.dca_expense_entries.all or transaction.dca_income_entries.all %}
<span class="badge text-bg-secondary mx-1">{% trans 'DCA' %}</span> <span class="badge badge-neutral mx-1">{% trans 'DCA' %}</span>
{% endif %} {% endif %}
{% endspaceless %} {% endspaceless %}
</div> </div>
<div class="tw:text-gray-400 tw:text-sm"> <div class="text-base-content/70 text-sm">
{# Entities #} {# Entities #}
{% comment %} First, check for the highest priority: a valid 'overriden_entities' list. {% endcomment %} {% comment %} First, check for the highest priority: a valid 'overriden_entities' list. {% endcomment %}
{% if overriden_entities %} {% if overriden_entities %}
<div class="row mb-2 mb-lg-1 tw:text-gray-400"> <div class="flex flex-wrap mb-2 lg:mb-1 text-base-content/70">
<div class="col-auto pe-1"><i class="fa-solid fa-user-group fa-fw me-1 fa-xs"></i></div> <div class="w-auto pe-1"><i class="fa-solid fa-user-group fa-fw mr-1 fa-xs"></i></div>
<div class="col ps-0">{{ overriden_entities|join:", " }}</div> <div class="flex-1 ps-0">{{ overriden_entities|join:", " }}</div>
</div> </div>
{% comment %} If no override, fall back to transaction entities, but ONLY if the transaction has an ID. {% endcomment %} {% comment %} If no override, fall back to transaction entities, but ONLY if the transaction has an ID. {% endcomment %}
{% elif transaction.id and transaction.entities.all %} {% elif transaction.id and transaction.entities.all %}
<div class="row mb-2 mb-lg-1 tw:text-gray-400"> <div class="flex flex-wrap mb-2 lg:mb-1 text-base-content/70">
<div class="col-auto pe-1"><i class="fa-solid fa-user-group fa-fw me-1 fa-xs"></i></div> <div class="w-auto pe-1"><i class="fa-solid fa-user-group fa-fw mr-1 fa-xs"></i></div>
<div class="col ps-0">{{ transaction.entities.all|join:", " }}</div> <div class="flex-1 ps-0">{{ transaction.entities.all|join:", " }}</div>
</div> </div>
{% endif %} {% endif %}
{# Notes#} {# Notes#}
{% if transaction.notes %} {% if transaction.notes %}
<div class="row mb-2 mb-lg-1 tw:text-gray-400"> <div class="flex flex-wrap mb-2 lg:mb-1 text-base-content/70">
<div class="col-auto pe-1"><i class="fa-solid fa-align-left fa-fw me-1 fa-xs"></i></div> <div class="w-auto pe-1"><i class="fa-solid fa-align-left fa-fw mr-1 fa-xs"></i></div>
<div class="col ps-0">{{ transaction.notes | limited_markdown | linebreaksbr }}</div> <div class="flex-1 ps-0">{{ transaction.notes | limited_markdown | linebreaksbr }}</div>
</div> </div>
{% endif %} {% endif %}
{# Category#} {# Category#}
{% if transaction.category %} {% if transaction.category %}
<div class="row mb-2 mb-lg-1 tw:text-gray-400"> <div class="flex flex-wrap mb-2 lg:mb-1 text-base-content/70">
<div class="col-auto pe-1"><i class="fa-solid fa-icons fa-fw me-1 fa-xs"></i></div> <div class="w-auto pe-1"><i class="fa-solid fa-icons fa-fw mr-1 fa-xs"></i></div>
<div class="col ps-0">{{ transaction.category.name }}</div> <div class="flex-1 ps-0">{{ transaction.category.name }}</div>
</div> </div>
{% endif %} {% endif %}
{# Tags#} {# Tags#}
{% comment %} First, check for the highest priority: a valid 'overriden_tags' list. {% endcomment %} {% comment %} First, check for the highest priority: a valid 'overriden_tags' list. {% endcomment %}
{% if overriden_tags %} {% if overriden_tags %}
<div class="row mb-2 mb-lg-1 tw:text-gray-400"> <div class="flex flex-wrap mb-2 lg:mb-1 text-base-content/70">
<div class="col-auto pe-1"><i class="fa-solid fa-hashtag fa-fw me-1 fa-xs"></i></div> <div class="w-auto pe-1"><i class="fa-solid fa-hashtag fa-fw mr-1 fa-xs"></i></div>
<div class="col ps-0">{{ overriden_tags|join:", " }}</div> <div class="flex-1 ps-0">{{ overriden_tags|join:", " }}</div>
</div> </div>
{% comment %} If no override, fall back to transaction tags, but ONLY if the transaction has an ID. {% endcomment %} {% comment %} If no override, fall back to transaction tags, but ONLY if the transaction has an ID. {% endcomment %}
{% elif transaction.id and transaction.tags.all %} {% elif transaction.id and transaction.tags.all %}
<div class="row mb-2 mb-lg-1 tw:text-gray-400"> <div class="flex flex-wrap mb-2 lg:mb-1 text-base-content/70">
<div class="col-auto pe-1"><i class="fa-solid fa-hashtag fa-fw me-1 fa-xs"></i></div> <div class="w-auto pe-1"><i class="fa-solid fa-hashtag fa-fw mr-1 fa-xs"></i></div>
<div class="col ps-0">{{ transaction.tags.all|join:", " }}</div> <div class="flex-1 ps-0">{{ transaction.tags.all|join:", " }}</div>
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div <div
class="col-lg-auto col-12 text-lg-end align-self-end {% if transaction.account.is_untracked_by or transaction.category.mute or transaction.mute %}tw:brightness-80{% endif %}"> class="lg:w-auto w-full lg:text-right self-end {% if transaction.account.is_untracked_by or transaction.category.mute or transaction.mute %}opacity-70{% endif %}">
<div class="main-amount mb-2 mb-lg-0"> <div class="main-amount mb-2 lg:mb-0">
<c-amount.display <c-amount.display
:amount="transaction.amount" :amount="transaction.amount"
:prefix="transaction.account.currency.prefix" :prefix="transaction.account.currency.prefix"
@@ -120,7 +119,7 @@
{% if not dummy %} {% if not dummy %}
{% with exchanged=transaction.exchanged_amount %} {% with exchanged=transaction.exchanged_amount %}
{% if exchanged %} {% if exchanged %}
<div class="exchanged-amount mb-2 mb-lg-0"> <div class="exchanged-amount mb-2 lg:mb-0">
<c-amount.display <c-amount.display
:amount="exchanged.amount" :amount="exchanged.amount"
:prefix="exchanged.prefix" :prefix="exchanged.prefix"
@@ -136,112 +135,106 @@
</div> </div>
</div> </div>
{% if not dummy %} {% if not dummy %}
<div> <div class="z-1000">
{# Item actions#} {# Item actions#}
<div <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 tw:group-hover/transaction:visible d-flex flex-row card"> class="card card-border-base-100 transaction-actions absolute left-1/2 -translate-x-1/2 -translate-y-1/2 top-0 invisible group-hover/transaction:visible flex flex-row bg-base-200">
<div class="card-body p-1 shadow-lg d-flex flex-row gap-1"> <div class="card-body p-1 shadow-lg flex flex-row gap-1">
{% if not transaction.deleted %} {% if not transaction.deleted %}
<a class="btn btn-secondary btn-sm transaction-action" <a class="btn btn-soft btn-sm transaction-action"
role="button" role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Edit" %}"
hx-get="{% url 'transaction_edit' transaction_id=transaction.id %}" hx-get="{% url 'transaction_edit' transaction_id=transaction.id %}"
hx-target="#generic-offcanvas" hx-swap="innerHTML"> hx-target="#generic-offcanvas" hx-swap="innerHTML"
data-tippy-content="{% translate "Edit" %}">
<i class="fa-solid fa-pencil fa-fw"></i></a> <i class="fa-solid fa-pencil fa-fw"></i></a>
<a class="btn btn-secondary btn-sm transaction-action" <a class="btn btn-error btn-soft btn-sm transaction-action"
role="button" role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Delete" %}"
hx-delete="{% url 'transaction_delete' transaction_id=transaction.id %}" hx-delete="{% url 'transaction_delete' transaction_id=transaction.id %}"
hx-trigger='confirmed' hx-trigger='confirmed'
data-tippy-content="{% translate "Delete" %}"
data-bypass-on-ctrl="true" data-bypass-on-ctrl="true"
data-title="{% translate "Are you sure?" %}" data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}" data-text="{% translate "You won't be able to revert this!" %}"
data-confirm-text="{% translate "Yes, delete it!" %}" data-confirm-text="{% translate "Yes, delete it!" %}"
_="install prompt_swal"><i class="fa-solid fa-trash fa-fw text-danger"></i> _="install prompt_swal"><i class="fa-solid fa-trash fa-fw"></i>
</a> </a>
<button class="btn btn-secondary btn-sm transaction-action" type="button" data-bs-toggle="dropdown" <button class="btn btn-soft btn-sm transaction-action" data-bs-toggle="dropdown" aria-expanded="false">
aria-expanded="false">
<i class="fa-solid fa-ellipsis fa-fw"></i> <i class="fa-solid fa-ellipsis fa-fw"></i>
</button> </button>
<ul class="dropdown-menu dropdown-menu-end dropdown-menu-md-start"> <ul class="dropdown-menu dropdown-menu-end dropdown-menu-md-start menu">
<div class="absolute inset-[0rem_-3rem_-3rem_-3rem] pointer-events-auto -z-10"></div>
{% if transaction.account.is_untracked_by %} {% if transaction.account.is_untracked_by %}
<li> <li class="menu-disabled" aria-disabled="true">
<a class="dropdown-item disabled d-flex align-items-center" aria-disabled="true"> <a class="flex items-center">
<i class="fa-solid fa-eye fa-fw me-2"></i> <i class="fa-solid fa-eye fa-fw mr-2"></i>
<div> <div>
{% translate 'Show on summaries' %} {% translate 'Show on summaries' %}
<div <div
class="d-block text-body-secondary tw:text-xs tw:font-medium">{% translate 'Controlled by account' %}</div> class="block text-base-content/60 text-xs font-medium">{% translate 'Controlled by account' %}</div>
</div> </div>
</a> </a>
</li> </li>
{% elif transaction.category.mute %} {% elif transaction.category.mute %}
<li> <li class="menu-disabled" aria-disabled="true">
<a class="dropdown-item disabled d-flex align-items-center" aria-disabled="true"> <a class="flex items-center">
<i class="fa-solid fa-eye fa-fw me-2"></i> <i class="fa-solid fa-eye fa-fw mr-2"></i>
<div> <div>
{% translate 'Show on summaries' %} {% translate 'Show on summaries' %}
<div <div
class="d-block text-body-secondary tw:text-xs tw:font-medium">{% translate 'Controlled by category' %}</div> class="block text-base-content/60 text-xs font-medium">{% translate 'Controlled by category' %}</div>
</div> </div>
</a> </a>
</li> </li>
{% elif transaction.mute %} {% elif transaction.mute %}
<li><a class="dropdown-item" href="#" <li><a href="#"
hx-get="{% url 'transaction_mute' transaction_id=transaction.id %}" hx-get="{% url 'transaction_mute' transaction_id=transaction.id %}"
hx-target="closest .transaction" hx-swap="outerHTML"><i hx-target="closest .transaction" hx-swap="outerHTML"><i
class="fa-solid fa-eye fa-fw me-2"></i>{% translate 'Show on summaries' %}</a></li> class="fa-solid fa-eye fa-fw mr-2"></i>{% translate 'Show on summaries' %}</a></li>
{% else %} {% else %}
<li><a class="dropdown-item" href="#" <li><a href="#"
hx-get="{% url 'transaction_mute' transaction_id=transaction.id %}" hx-get="{% url 'transaction_mute' transaction_id=transaction.id %}"
hx-target="closest .transaction" hx-swap="outerHTML"><i hx-target="closest .transaction" hx-swap="outerHTML"><i
class="fa-solid fa-eye-slash fa-fw me-2"></i>{% translate 'Hide from summaries' %}</a></li> class="fa-solid fa-eye-slash fa-fw mr-2"></i>{% translate 'Hide from summaries' %}</a></li>
{% endif %} {% endif %}
<li><a class="dropdown-item" href="#" <li><a href="#"
hx-get="{% url 'quick_transaction_add_as_quick_transaction' transaction_id=transaction.id %}"><i hx-get="{% url 'quick_transaction_add_as_quick_transaction' transaction_id=transaction.id %}"><i
class="fa-solid fa-person-running fa-fw me-2"></i>{% translate 'Add as quick transaction' %}</a> class="fa-solid fa-person-running fa-fw mr-2"></i>{% translate 'Add as quick transaction' %}</a>
</li> </li>
<li> <hr class="my-1 hr">
<hr class="dropdown-divider"> <li><a href="#"
</li>
<li><a class="dropdown-item" href="#"
hx-get="{% url 'transaction_change_month' transaction_id=transaction.id change_type='previous' %}"><i hx-get="{% url 'transaction_change_month' transaction_id=transaction.id change_type='previous' %}"><i
class="fa-solid fa-calendar-minus fa-fw me-2"></i>{% translate 'Move to previous month' %}</a> class="fa-solid fa-calendar-minus fa-fw mr-2"></i>{% translate 'Move to previous month' %}</a>
</li> </li>
<li><a class="dropdown-item" href="#" <li><a href="#"
hx-get="{% url 'transaction_change_month' transaction_id=transaction.id change_type='next' %}"><i hx-get="{% url 'transaction_change_month' transaction_id=transaction.id change_type='next' %}"><i
class="fa-solid fa-calendar-plus fa-fw me-2"></i>{% translate 'Move to next month' %}</a></li> class="fa-solid fa-calendar-plus fa-fw mr-2"></i>{% translate 'Move to next month' %}</a></li>
<li><a class="dropdown-item" href="#" <li><a href="#"
hx-get="{% url 'transaction_move_to_today' transaction_id=transaction.id %}"><i hx-get="{% url 'transaction_move_to_today' transaction_id=transaction.id %}"><i
class="fa-solid fa-calendar-day fa-fw me-2"></i>{% translate 'Move to today' %}</a></li> class="fa-solid fa-calendar-day fa-fw mr-2"></i>{% translate 'Move to today' %}</a></li>
<li> <hr class="my-1 hr">
<hr class="dropdown-divider"> <li><a href="#"
</li>
<li><a class="dropdown-item" href="#"
hx-get="{% url 'transaction_clone' transaction_id=transaction.id %}"><i hx-get="{% url 'transaction_clone' transaction_id=transaction.id %}"><i
class="fa-solid fa-clone fa-fw me-2"></i>{% translate 'Duplicate' %}</a></li> class="fa-solid fa-clone fa-fw mr-2"></i>{% translate 'Duplicate' %}</a></li>
</ul> </ul>
{% else %} {% else %}
<a class="btn btn-secondary btn-sm transaction-action" <div class="tooltip" data-tippy-content="{% translate "Restore" %}">
<a class="btn btn-success btn-soft btn-sm transaction-action"
role="button" role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Restore" %}"
hx-get="{% url 'transaction_undelete' transaction_id=transaction.id %}"><i hx-get="{% url 'transaction_undelete' transaction_id=transaction.id %}"><i
class="fa-solid fa-trash-arrow-up"></i></a> class="fa-solid fa-trash-arrow-up fa-fw"></i></a>
<a class="btn btn-secondary btn-sm transaction-action" </div>
<div class="tooltip" data-tippy-content="{% translate "Delete" %}">
<a class="btn btn-error btn-soft btn-sm transaction-action"
role="button" role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Delete" %}"
hx-delete="{% url 'transaction_delete' transaction_id=transaction.id %}" hx-delete="{% url 'transaction_delete' transaction_id=transaction.id %}"
hx-trigger='confirmed' hx-trigger='confirmed'
data-bypass-on-ctrl="true" data-bypass-on-ctrl="true"
data-title="{% translate "Are you sure?" %}" data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}" data-text="{% translate "You won't be able to revert this!" %}"
data-confirm-text="{% translate "Yes, delete it!" %}" data-confirm-text="{% translate "Yes, delete it!" %}"
_="install prompt_swal"><i class="fa-solid fa-trash fa-fw text-danger"></i> _="install prompt_swal"><i class="fa-solid fa-trash fa-fw"></i>
</a> </a>
</div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
@@ -1,38 +1,38 @@
<div class="card mb-2 transaction-item"> <div class="card bg-base-100 shadow-xl 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 flex items-center gap-3" data-bs-toggle="collapse" data-bs-target="#{{ transaction.id }}" role="button" aria-expanded="false" aria-controls="{{ transaction.id }}">
<!-- Main visible content --> <!-- Main visible content -->
<div class="tw:flex flex-lg-row flex-column tw:lg:items-center tw:w-full tw:gap-3"> <div class="flex flex-col lg:flex-row lg:items-center w-full gap-3">
<!-- Type indicator --> <!-- Type indicator -->
<div class="tw:w-8"> <div class="w-8">
{% if transaction.type == 'IN' %} {% if transaction.type == 'IN' %}
<span class="badge bg-success"></span> <span class="badge badge-success"></span>
{% else %} {% else %}
<span class="badge bg-danger"></span> <span class="badge badge-error"></span>
{% endif %} {% endif %}
</div> </div>
<!-- Payment status --> <!-- Payment status -->
<div class="tw:w-8"> <div class="w-8">
{% if transaction.is_paid %} {% if transaction.is_paid %}
<span class="badge bg-success"></span> <span class="badge badge-success"></span>
{% else %} {% else %}
<span class="badge bg-warning"></span> <span class="badge badge-warning"></span>
{% endif %} {% endif %}
</div> </div>
<!-- Description --> <!-- Description -->
<div class="tw:flex-grow"> <div class="flex-grow">
<span class="tw:font-medium">{{ transaction.description }}</span> <span class="font-medium">{{ transaction.description }}</span>
</div> </div>
<!-- Amount --> <!-- Amount -->
<div class="tw:text-right tw:whitespace-nowrap"> <div class="text-right whitespace-nowrap">
<span class="{% if transaction.type == 'IN' %}tw:text-green-400{% else %}tw:text-red-400{% endif %}"> <span class="{% if transaction.type == 'IN' %}text-green-400{% else %}text-red-400{% endif %}">
{{ transaction.amount }} {{ transaction.amount }}
</span> </span>
{% if transaction.exchanged_amount %} {% if transaction.exchanged_amount %}
<br> <br>
<small class="text-muted"> <small class="text-base-content/60">
{{ transaction.exchanged_amount.prefix }}{{ transaction.exchanged_amount.amount }}{{ transaction.exchanged_amount.suffix }} {{ transaction.exchanged_amount.prefix }}{{ transaction.exchanged_amount.amount }}{{ transaction.exchanged_amount.suffix }}
</small> </small>
{% endif %} {% endif %}
@@ -43,48 +43,48 @@
<!-- Expandable details --> <!-- Expandable details -->
<div class="collapse" id="{{ transaction.id }}"> <div class="collapse" id="{{ transaction.id }}">
<div class="card-body p-3 transaction-details"> <div class="card-body p-3 transaction-details">
<div class="row"> <div class="grid grid-cols-1 md:grid-cols-2">
<div class="col-md-6"> <div>
<dl class="row"> <dl class="grid grid-cols-3">
<dt class="col-sm-4">Date</dt> <dt class="col-span-1">Date</dt>
<dd class="col-sm-8">{{ transaction.date|date:"Y-m-d" }}</dd> <dd class="col-span-2">{{ transaction.date|date:"Y-m-d" }}</dd>
<dt class="col-sm-4">Reference Date</dt> <dt class="col-span-1">Reference Date</dt>
<dd class="col-sm-8">{{ transaction.reference_date|date:"Y-m" }}</dd> <dd class="col-span-2">{{ transaction.reference_date|date:"Y-m" }}</dd>
<dt class="col-sm-4">Account</dt> <dt class="col-span-1">Account</dt>
<dd class="col-sm-8">{{ transaction.account.name }}</dd> <dd class="col-span-2">{{ transaction.account.name }}</dd>
<dt class="col-sm-4">Category</dt> <dt class="col-span-1">Category</dt>
<dd class="col-sm-8">{{ transaction.category|default:"-" }}</dd> <dd class="col-span-2">{{ transaction.category|default:"-" }}</dd>
</dl> </dl>
</div> </div>
<div class="col-md-6"> <div>
<dl class="row"> <dl class="grid grid-cols-3">
{% if transaction.tags.exists %} {% if transaction.tags.exists %}
<dt class="col-sm-4">Tags</dt> <dt class="col-span-1">Tags</dt>
<dd class="col-sm-8"> <dd class="col-span-2">
{% for tag in transaction.tags.all %} {% for tag in transaction.tags.all %}
<span class="badge bg-secondary">{{ tag.name }}</span> <span class="badge badge-secondary">{{ tag.name }}</span>
{% endfor %} {% endfor %}
</dd> </dd>
{% endif %} {% endif %}
{% if transaction.installment_plan %} {% if transaction.installment_plan %}
<dt class="col-sm-4">Installment</dt> <dt class="col-span-1">Installment</dt>
<dd class="col-sm-8"> <dd class="col-span-2">
{{ transaction.installment_id }} of {{ transaction.installment_plan.total_installments }} {{ transaction.installment_id }} of {{ transaction.installment_plan.total_installments }}
</dd> </dd>
{% endif %} {% endif %}
{% if transaction.recurring_transaction %} {% if transaction.recurring_transaction %}
<dt class="col-sm-4">Recurring</dt> <dt class="col-span-1">Recurring</dt>
<dd class="col-sm-8">Yes</dd> <dd class="col-span-2">Yes</dd>
{% endif %} {% endif %}
{% if transaction.notes %} {% if transaction.notes %}
<dt class="col-sm-4">Notes</dt> <dt class="col-span-1">Notes</dt>
<dd class="col-sm-8">{{ transaction.notes }}</dd> <dd class="col-span-2">{{ transaction.notes }}</dd>
{% endif %} {% endif %}
</dl> </dl>
</div> </div>
+73 -101
View File
@@ -1,195 +1,167 @@
{% load tools %} {% load tools %}
{% load i18n %} {% load i18n %}
<div class="col card shadow"> <div class="card bg-base-100 shadow-md card-border border-base-300">
<div class="card-body"> <div class="card-body">
{% if account.account.group %} <h5 class="card-title mb-4">
<div class="tw:text-sm mb-2"> {% if account.account.group %}<span class="badge badge-primary badge-outline">{{ account.account.group }}</span>{% endif %} {{ account.account.name }}
<span class="badge text-bg-primary ">{{ account.account.group }}</span>
</div>
{% endif %}
<h5 class="card-title">
{{ account.account.name }}
</h5> </h5>
<div class="d-flex justify-content-between align-items-baseline mt-2"> <div class="card-data-section">
<div class="text-end font-monospace"> <div class="card-data-row">
<div class="tw:text-gray-400">{% translate 'projected income' %}</div> <span class="card-data-label">{% translate 'projected income' %}</span>
</div> <div class="card-data-values">
<div class="dotted-line flex-grow-1"></div>
{% if account.income_projected != 0 %} {% if account.income_projected != 0 %}
<div class="text-end font-monospace tw:text-green-400">
<c-amount.display <c-amount.display
:amount="account.income_projected" :amount="account.income_projected"
:prefix="account.currency.prefix" :prefix="account.currency.prefix"
:suffix="account.currency.suffix" :suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"></c-amount.display> :decimal_places="account.currency.decimal_places"
</div> color="green"></c-amount.display>
{% else %}
<div class="text-end font-monospace">-</div>
{% endif %}
</div>
{% if account.exchanged and account.exchanged.income_projected %} {% if account.exchanged and account.exchanged.income_projected %}
<div class="text-end font-monospace tw:text-gray-500">
<c-amount.display <c-amount.display
:amount="account.exchanged.income_projected" :amount="account.exchanged.income_projected"
:prefix="account.exchanged.currency.prefix" :prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix" :suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display> :decimal_places="account.exchanged.currency.decimal_places"
</div> color="gray"></c-amount.display>
{% endif %}
{% else %}
<span class="font-semibold">-</span>
{% endif %} {% 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> </div>
<div class="dotted-line flex-grow-1"></div> </div>
<div> <div class="card-data-row">
<span class="card-data-label">{% translate 'projected expenses' %}</span>
<div class="card-data-values">
{% if account.expense_projected != 0 %} {% if account.expense_projected != 0 %}
<div class="text-end font-monospace tw:text-red-400">
<c-amount.display <c-amount.display
:amount="account.expense_projected" :amount="account.expense_projected"
:prefix="account.currency.prefix" :prefix="account.currency.prefix"
:suffix="account.currency.suffix" :suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"></c-amount.display> :decimal_places="account.currency.decimal_places"
</div> color="red"></c-amount.display>
{% else %}
<div class="text-end font-monospace">-</div>
{% endif %}
</div>
</div>
{% if account.exchanged and account.exchanged.expense_projected %} {% if account.exchanged and account.exchanged.expense_projected %}
<div class="text-end font-monospace tw:text-gray-500">
<c-amount.display <c-amount.display
:amount="account.exchanged.expense_projected" :amount="account.exchanged.expense_projected"
:prefix="account.exchanged.currency.prefix" :prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix" :suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display> :decimal_places="account.exchanged.currency.decimal_places"
</div> color="gray"></c-amount.display>
{% endif %}
{% else %}
<span class="font-semibold">-</span>
{% endif %} {% 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> </div>
<div class="dotted-line flex-grow-1"></div> </div>
<div <div class="card-data-row">
class="text-end font-monospace"> <span class="card-data-label">{% translate 'projected total' %}</span>
<div class="card-data-values">
<c-amount.display <c-amount.display
:amount="account.total_projected" :amount="account.total_projected"
:prefix="account.currency.prefix" :prefix="account.currency.prefix"
:suffix="account.currency.suffix" :suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places" :decimal_places="account.currency.decimal_places"
color="{% if account.total_projected > 0 %}green{% elif account.total_projected < 0 %}red{% endif %}"></c-amount.display> color="{% if account.total_projected > 0 %}green{% elif account.total_projected < 0 %}red{% endif %}"></c-amount.display>
</div>
</div>
{% if account.exchanged.total_projected and account.exchanged.total_projected %} {% if account.exchanged.total_projected and account.exchanged.total_projected %}
<div class="text-end font-monospace tw:text-gray-500">
<c-amount.display <c-amount.display
:amount="account.exchanged.total_projected" :amount="account.exchanged.total_projected"
:prefix="account.exchanged.currency.prefix" :prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix" :suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display> :decimal_places="account.exchanged.currency.decimal_places"
</div> color="gray"></c-amount.display>
{% endif %} {% endif %}
<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> </div>
<div class="dotted-line flex-grow-1"></div> </div>
</div>
<hr class="card-data-divider" />
<div class="card-data-section">
<div class="card-data-row">
<span class="card-data-label">{% translate 'current income' %}</span>
<div class="card-data-values">
{% if account.income_current != 0 %} {% if account.income_current != 0 %}
<div class="text-end font-monospace tw:text-green-400">
<c-amount.display <c-amount.display
:amount="account.income_current" :amount="account.income_current"
:prefix="account.currency.prefix" :prefix="account.currency.prefix"
:suffix="account.currency.suffix" :suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"></c-amount.display> :decimal_places="account.currency.decimal_places"
</div> color="green"></c-amount.display>
{% else %}
<div class="text-end font-monospace">-</div>
{% endif %}
</div>
{% if account.exchanged and account.exchanged.income_current %} {% if account.exchanged and account.exchanged.income_current %}
<div class="text-end font-monospace tw:text-gray-500">
<c-amount.display <c-amount.display
:amount="account.exchanged.income_current" :amount="account.exchanged.income_current"
:prefix="account.exchanged.currency.prefix" :prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix" :suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display> :decimal_places="account.exchanged.currency.decimal_places"
</div> color="gray"></c-amount.display>
{% endif %}
{% else %}
<span class="font-semibold">-</span>
{% endif %} {% 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> </div>
<div class="dotted-line flex-grow-1"></div> </div>
<div class="card-data-row">
<span class="card-data-label">{% translate 'current expenses' %}</span>
<div class="card-data-values">
{% if account.expense_current != 0 %} {% if account.expense_current != 0 %}
<div class="text-end font-monospace tw:text-red-400">
<c-amount.display <c-amount.display
:amount="account.expense_current" :amount="account.expense_current"
:prefix="account.currency.prefix" :prefix="account.currency.prefix"
:suffix="account.currency.suffix" :suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"></c-amount.display> :decimal_places="account.currency.decimal_places"
</div> color="red"></c-amount.display>
{% else %}
<div class="text-end font-monospace">-</div>
{% endif %}
</div>
{% if account.exchanged and account.exchanged.expense_current %} {% if account.exchanged and account.exchanged.expense_current %}
<div class="text-end font-monospace tw:text-gray-500">
<c-amount.display <c-amount.display
:amount="account.exchanged.expense_current" :amount="account.exchanged.expense_current"
:prefix="account.exchanged.currency.prefix" :prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix" :suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display> :decimal_places="account.exchanged.currency.decimal_places"
</div> color="gray"></c-amount.display>
{% endif %}
{% else %}
<span class="font-semibold">-</span>
{% endif %} {% 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> </div>
<div class="dotted-line flex-grow-1"></div> </div>
<div class="text-end font-monospace"> <div class="card-data-row">
<span class="card-data-label">{% translate 'current total' %}</span>
<div class="card-data-values">
<c-amount.display <c-amount.display
:amount="account.total_current" :amount="account.total_current"
:prefix="account.currency.prefix" :prefix="account.currency.prefix"
:suffix="account.currency.suffix" :suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places" :decimal_places="account.currency.decimal_places"
color="{% if account.total_current > 0 %}green{% elif account.total_current < 0 %}red{% endif %}"></c-amount.display> color="{% if account.total_current > 0 %}green{% elif account.total_current < 0 %}red{% endif %}"></c-amount.display>
</div>
</div>
{% if account.exchanged and account.exchanged.total_current %} {% if account.exchanged and account.exchanged.total_current %}
<div class="text-end font-monospace tw:text-gray-500">
<c-amount.display <c-amount.display
:amount="account.exchanged.total_current" :amount="account.exchanged.total_current"
:prefix="account.exchanged.currency.prefix" :prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix" :suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display> :decimal_places="account.exchanged.currency.decimal_places"
</div> color="gray"></c-amount.display>
{% endif %} {% endif %}
<div>
<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> </div>
<div class="dotted-line flex-grow-1"></div> </div>
<div class="text-end font-monospace"> </div>
<hr class="card-data-divider" />
<div class="card-data-section">
<div class="card-data-row">
<span class="card-data-label">{% translate 'final total' %}</span>
<div class="card-data-values">
<c-amount.display <c-amount.display
:amount="account.total_final" :amount="account.total_final"
:prefix="account.currency.prefix" :prefix="account.currency.prefix"
:suffix="account.currency.suffix" :suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places" :decimal_places="account.currency.decimal_places"
color="{% if account.total_final > 0 %}green{% elif account.total_final < 0 %}red{% endif %}"></c-amount.display> color="{% if account.total_final > 0 %}green{% elif account.total_final < 0 %}red{% endif %}"></c-amount.display>
</div>
</div>
{% if account.exchanged and account.exchanged.total_final %} {% if account.exchanged and account.exchanged.total_final %}
<div class="text-end font-monospace tw:text-gray-500">
<c-amount.display <c-amount.display
:amount="account.exchanged.total_final" :amount="account.exchanged.total_final"
:prefix="account.exchanged.currency.prefix" :prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix" :suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display> :decimal_places="account.exchanged.currency.decimal_places"
</div> color="gray"></c-amount.display>
{% endif %} {% endif %}
</div> </div>
</div>
</div>
{% with p=percentages|get_dict_item:account_id %} {% with p=percentages|get_dict_item:account_id %}
<div class="my-3"> <div class="my-3">
<c-ui.percentage-distribution :percentage="p"></c-ui.percentage-distribution> <c-ui.percentage-distribution :percentage="p"></c-ui.percentage-distribution>
+1 -1
View File
@@ -1,4 +1,4 @@
<div class="card tw:relative h-100 shadow"> <div class="card bg-base-100 shadow-xl relative h-full">
<div class="card-body"> <div class="card-body">
{{ slot }} {{ slot }}
</div> </div>
@@ -0,0 +1,21 @@
<div class="bg-base-100 border-base-300 border-2 join-item {{ base_classes }}"
x-data="{ expanded: {% if active %}true{% else %}false{% endif %} }">
<div class="font-medium text-sm cursor-pointer p-4 flex w-full items-center justify-between gap-4 text-left {{ title_classes }}"
@click="expanded = ! expanded">
<div>
{{ title }}
</div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke-width="2" stroke="currentColor" class="size-5 shrink-0 transition" aria-hidden="true" x-bind:class="expanded ? 'rotate-180' : ''">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5"></path>
</svg>
</div>
<div class="bg-base-200 p-4 {{ content_classes }}"
x-show="expanded"
x-collapse>
{{ content }}
</div>
</div>
+72 -94
View File
@@ -1,189 +1,167 @@
{% load tools %} {% load tools %}
{% load i18n %} {% load i18n %}
<div class="col card shadow"> <div class="col card bg-base-100 shadow card-border">
<div class="card-body"> <div class="card-body">
<h5 class="card-title"> <h5 class="card-title mb-4">
{{ currency.currency.name }} {{ currency.currency.name }}
</h5> </h5>
<div class="d-flex justify-content-between align-items-baseline mt-2"> <div class="card-data-section">
<div class="text-end font-monospace"> <div class="card-data-row">
<div class="tw:text-gray-400">{% translate 'projected income' %}</div> <span class="card-data-label">{% translate 'projected income' %}</span>
</div> <div class="card-data-values">
<div class="dotted-line flex-grow-1"></div>
{% if currency.income_projected != 0 %} {% if currency.income_projected != 0 %}
<div class="text-end font-monospace tw:text-green-400">
<c-amount.display <c-amount.display
:amount="currency.income_projected" :amount="currency.income_projected"
:prefix="currency.currency.prefix" :prefix="currency.currency.prefix"
:suffix="currency.currency.suffix" :suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"></c-amount.display> :decimal_places="currency.currency.decimal_places"
</div> color="green"></c-amount.display>
{% else %}
<div class="text-end font-monospace">-</div>
{% endif %}
</div>
{% if currency.exchanged and currency.exchanged.income_projected %} {% if currency.exchanged and currency.exchanged.income_projected %}
<div class="text-end font-monospace tw:text-gray-500">
<c-amount.display <c-amount.display
:amount="currency.exchanged.income_projected" :amount="currency.exchanged.income_projected"
:prefix="currency.exchanged.currency.prefix" :prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix" :suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"></c-amount.display> :decimal_places="currency.exchanged.currency.decimal_places"
</div> color="gray"></c-amount.display>
{% endif %}
{% else %}
<span class="font-semibold">-</span>
{% endif %} {% 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> </div>
<div class="dotted-line flex-grow-1"></div> </div>
<div> <div class="card-data-row">
<span class="card-data-label">{% translate 'projected expenses' %}</span>
<div class="card-data-values">
{% if currency.expense_projected != 0 %} {% if currency.expense_projected != 0 %}
<div class="text-end font-monospace tw:text-red-400">
<c-amount.display <c-amount.display
:amount="currency.expense_projected" :amount="currency.expense_projected"
:prefix="currency.currency.prefix" :prefix="currency.currency.prefix"
:suffix="currency.currency.suffix" :suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"></c-amount.display> :decimal_places="currency.currency.decimal_places"
</div> color="red"></c-amount.display>
{% else %}
<div class="text-end font-monospace">-</div>
{% endif %}
</div>
</div>
{% if currency.exchanged and currency.exchanged.expense_projected %} {% if currency.exchanged and currency.exchanged.expense_projected %}
<div class="text-end font-monospace tw:text-gray-500">
<c-amount.display <c-amount.display
:amount="currency.exchanged.expense_projected" :amount="currency.exchanged.expense_projected"
:prefix="currency.exchanged.currency.prefix" :prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix" :suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"></c-amount.display> :decimal_places="currency.exchanged.currency.decimal_places"
</div> color="gray"></c-amount.display>
{% endif %}
{% else %}
<span class="font-semibold">-</span>
{% endif %} {% 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> </div>
<div class="dotted-line flex-grow-1"></div> </div>
<div class="text-end font-monospace"> <div class="card-data-row">
<span class="card-data-label">{% translate 'projected total' %}</span>
<div class="card-data-values">
<c-amount.display <c-amount.display
:amount="currency.total_projected" :amount="currency.total_projected"
:prefix="currency.currency.prefix" :prefix="currency.currency.prefix"
:suffix="currency.currency.suffix" :suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places" :decimal_places="currency.currency.decimal_places"
color="{% if currency.total_projected > 0 %}green{% elif currency.total_projected < 0 %}red{% endif %}"></c-amount.display> color="{% if currency.total_projected > 0 %}green{% elif currency.total_projected < 0 %}red{% endif %}"></c-amount.display>
</div>
</div>
{% if currency.exchanged.total_projected and currency.exchanged.total_projected %} {% if currency.exchanged.total_projected and currency.exchanged.total_projected %}
<div class="text-end font-monospace tw:text-gray-500">
<c-amount.display <c-amount.display
:amount="currency.exchanged.total_projected" :amount="currency.exchanged.total_projected"
:prefix="currency.exchanged.currency.prefix" :prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix" :suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"></c-amount.display> :decimal_places="currency.exchanged.currency.decimal_places"
</div> color="gray"></c-amount.display>
{% endif %} {% endif %}
<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> </div>
<div class="dotted-line flex-grow-1"></div> </div>
</div>
<hr class="card-data-divider" />
<div class="card-data-section">
<div class="card-data-row">
<span class="card-data-label">{% translate 'current income' %}</span>
<div class="card-data-values">
{% if currency.income_current != 0 %} {% if currency.income_current != 0 %}
<div class="text-end font-monospace tw:text-green-400">
<c-amount.display <c-amount.display
:amount="currency.income_current" :amount="currency.income_current"
:prefix="currency.currency.prefix" :prefix="currency.currency.prefix"
:suffix="currency.currency.suffix" :suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"></c-amount.display> :decimal_places="currency.currency.decimal_places"
</div> color="green"></c-amount.display>
{% else %}
<div class="text-end font-monospace">-</div>
{% endif %}
</div>
{% if currency.exchanged and currency.exchanged.income_current %} {% if currency.exchanged and currency.exchanged.income_current %}
<div class="text-end font-monospace tw:text-gray-500">
<c-amount.display <c-amount.display
:amount="currency.exchanged.income_current" :amount="currency.exchanged.income_current"
:prefix="currency.exchanged.currency.prefix" :prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix" :suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"></c-amount.display> :decimal_places="currency.exchanged.currency.decimal_places"
</div> color="gray"></c-amount.display>
{% endif %}
{% else %}
<span class="font-semibold">-</span>
{% endif %} {% 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> </div>
<div class="dotted-line flex-grow-1"></div> </div>
<div class="card-data-row">
<span class="card-data-label">{% translate 'current expenses' %}</span>
<div class="card-data-values">
{% if currency.expense_current != 0 %} {% if currency.expense_current != 0 %}
<div class="text-end font-monospace tw:text-red-400">
<c-amount.display <c-amount.display
:amount="currency.expense_current" :amount="currency.expense_current"
:prefix="currency.currency.prefix" :prefix="currency.currency.prefix"
:suffix="currency.currency.suffix" :suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"></c-amount.display> :decimal_places="currency.currency.decimal_places"
</div> color="red"></c-amount.display>
{% else %}
<div class="text-end font-monospace">-</div>
{% endif %}
</div>
{% if currency.exchanged and currency.exchanged.expense_current %} {% if currency.exchanged and currency.exchanged.expense_current %}
<div class="text-end font-monospace tw:text-gray-500">
<c-amount.display <c-amount.display
:amount="currency.exchanged.expense_current" :amount="currency.exchanged.expense_current"
:prefix="currency.exchanged.currency.prefix" :prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix" :suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"></c-amount.display> :decimal_places="currency.exchanged.currency.decimal_places"
</div> color="gray"></c-amount.display>
{% endif %}
{% else %}
<span class="font-semibold">-</span>
{% endif %} {% 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> </div>
<div class="dotted-line flex-grow-1"></div> </div>
<div class="text-end font-monospace"> <div class="card-data-row">
<span class="card-data-label">{% translate 'current total' %}</span>
<div class="card-data-values">
<c-amount.display <c-amount.display
:amount="currency.total_current" :amount="currency.total_current"
:prefix="currency.currency.prefix" :prefix="currency.currency.prefix"
:suffix="currency.currency.suffix" :suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places" :decimal_places="currency.currency.decimal_places"
color="{% if currency.total_current > 0 %}green{% elif currency.total_current < 0 %}red{% endif %}"></c-amount.display> color="{% if currency.total_current > 0 %}green{% elif currency.total_current < 0 %}red{% endif %}"></c-amount.display>
</div>
</div>
{% if currency.exchanged and currency.exchanged.total_current %} {% if currency.exchanged and currency.exchanged.total_current %}
<div class="text-end font-monospace tw:text-gray-500">
<c-amount.display <c-amount.display
:amount="currency.exchanged.total_current" :amount="currency.exchanged.total_current"
:prefix="currency.exchanged.currency.prefix" :prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix" :suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"></c-amount.display> :decimal_places="currency.exchanged.currency.decimal_places"
</div> color="gray"></c-amount.display>
{% endif %} {% endif %}
<div>
<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> </div>
<div class="dotted-line flex-grow-1"></div> </div>
<div class="text-end font-monospace"> </div>
<hr class="card-data-divider" />
<div class="card-data-section">
<div class="card-data-row">
<span class="card-data-label">{% translate 'final total' %}</span>
<div class="card-data-values">
<c-amount.display <c-amount.display
:amount="currency.total_final" :amount="currency.total_final"
:prefix="currency.currency.prefix" :prefix="currency.currency.prefix"
:suffix="currency.currency.suffix" :suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places" :decimal_places="currency.currency.decimal_places"
color="{% if currency.total_final > 0 %}green{% elif currency.total_final < 0 %}red{% endif %}"></c-amount.display> color="{% if currency.total_final > 0 %}green{% elif currency.total_final < 0 %}red{% endif %}"></c-amount.display>
</div>
</div>
{% if currency.exchanged and currency.exchanged.total_final %} {% if currency.exchanged and currency.exchanged.total_final %}
<div class="text-end font-monospace tw:text-gray-500">
<c-amount.display <c-amount.display
:amount="currency.exchanged.total_final" :amount="currency.exchanged.total_final"
:prefix="currency.exchanged.currency.prefix" :prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix" :suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"></c-amount.display> :decimal_places="currency.exchanged.currency.decimal_places"
</div> color="gray"></c-amount.display>
{% endif %} {% endif %}
</div> </div>
</div>
</div>
{% with p=percentages|get_dict_item:currency_id %} {% with p=percentages|get_dict_item:currency_id %}
<div class="my-3"> <div class="my-3">
<c-ui.percentage-distribution :percentage="p"></c-ui.percentage-distribution> <c-ui.percentage-distribution :percentage="p"></c-ui.percentage-distribution>
@@ -1,67 +1,69 @@
{% load i18n %} {% 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="sticky bottom-4 left-0 right-0 z-1000 hidden mx-auto w-fit" id="actions-bar"
_="on change from #transactions-list or htmx:afterSettle from window _="on change from #transactions-list or htmx:afterSettle from window
if #actions-bar then if #actions-bar then
if no <input[type='checkbox']:checked/> in #transactions-list if no <input[type='checkbox']:checked/> in #transactions-list
if #actions-bar if #actions-bar
add .slide-in-bottom-reverse then settle add .slide-in-bottom-reverse then settle
then add .tw:hidden to #actions-bar then add .hidden to #actions-bar
then remove .slide-in-bottom-reverse then remove .slide-in-bottom-reverse
end end
else else
if #actions-bar if #actions-bar
remove .tw:hidden from #actions-bar set #selected-count's innerHTML to length of <input[type='checkbox']:checked/> in #transactions-list
then remove .hidden from #actions-bar
then trigger selected_transactions_updated then trigger selected_transactions_updated
end end
end end
end end
end"> end">
<div class="card slide-in-bottom"> <div class="card bg-base-300 shadow slide-in-bottom max-w-[90vw] card-border">
<div class="card-body p-2 d-flex justify-content-between align-items-center gap-3"> <div class="card-body flex-row p-2 flex justify-between items-center gap-3 overflow-x-auto">
{% spaceless %} {% spaceless %}
<div class="dropdown"> <div class="font-bold text-md ms-2" id="selected-count">0</div>
<button class="btn btn-secondary btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown" <div class="divider divider-horizontal m-0"></div>
aria-expanded="false"> <div>
<button role="button" class="btn btn-secondary btn-sm" type="button"
data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa-regular fa-square-check fa-fw"></i> <i class="fa-regular fa-square-check fa-fw"></i>
<i class="fa-solid fa-chevron-down fa-xs"></i>
</button> </button>
<ul class="dropdown-menu"> <ul class="dropdown-menu menu">
<li> <li>
<div class="dropdown-item px-3 tw:cursor-pointer" <a class="cursor-pointer"
_="on click set <#transactions-list input[type='checkbox']/>'s checked to true then call me.blur() then trigger change"> _="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 text-success me-3"></i>{% translate 'Select All' %}
</div> </a>
</li> </li>
<li> <li>
<div class="dropdown-item px-3 tw:cursor-pointer" <a class="cursor-pointer"
_="on click set <#transactions-list input[type='checkbox']/>'s checked to false then call me.blur() then trigger change"> _="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 text-error me-3"></i>{% translate 'Unselect All' %}
</div> </a>
</li> </li>
</ul> </ul>
</div> </div>
<div class="vr tw:align-middle"></div> <div class="divider divider-horizontal m-0"></div>
<button class="btn btn-secondary btn-sm" <button class="btn btn-secondary btn-sm"
hx-get="{% url 'transactions_bulk_undelete' %}" hx-get="{% url 'transactions_bulk_undelete' %}"
hx-include=".transaction" hx-include=".transaction"
data-bs-toggle="tooltip" data-tippy-content="{% translate 'Restore' %}">
data-bs-title="{% translate 'Restore' %}">
<i class="fa-solid fa-trash-arrow-up fa-fw"></i> <i class="fa-solid fa-trash-arrow-up fa-fw"></i>
</button> </button>
<button class="btn btn-secondary btn-sm" <button class="btn btn-secondary btn-sm"
hx-get="{% url 'transactions_bulk_delete' %}" hx-get="{% url 'transactions_bulk_delete' %}"
hx-include=".transaction" hx-include=".transaction"
hx-trigger="confirmed" hx-trigger="confirmed"
data-bs-toggle="tooltip" data-tippy-content="{% translate 'Delete' %}"
data-bs-title="{% translate 'Delete' %}"
data-bypass-on-ctrl="true" data-bypass-on-ctrl="true"
data-title="{% translate "Are you sure?" %}" data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}" data-text="{% translate "You won't be able to revert this!" %}"
data-confirm-text="{% translate "Yes, delete them!" %}" data-confirm-text="{% translate "Yes, delete them!" %}"
_="install prompt_swal"> _="install prompt_swal">
<i class="fa-solid fa-trash text-danger"></i> <i class="fa-solid fa-trash text-error"></i>
</button> </button>
<div class="vr tw:align-middle"></div> <div class="divider divider-horizontal m-0"></div>
<div class="btn-group" <div class="join"
_="on selected_transactions_updated from #actions-bar _="on selected_transactions_updated from #actions-bar
set realTotal to math.bignumber(0) set realTotal to math.bignumber(0)
set flatTotal to math.bignumber(0) set flatTotal to math.bignumber(0)
@@ -99,137 +101,136 @@
put mean.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40}) into #calc-menu-mean's innerText put mean.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40}) into #calc-menu-mean's innerText
put flatAmountValues.length.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40}) into #calc-menu-count's innerText put flatAmountValues.length.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40}) into #calc-menu-count's innerText
end"> end">
<button class="btn btn-secondary btn-sm" _="on click <button class="btn btn-secondary btn-sm join-item"
_="on click
set original_value to #real-total-front's innerText set original_value to #real-total-front's innerText
writeText(original_value) on navigator.clipboard writeText(original_value) on navigator.clipboard
put '{% translate "copied!" %}' into #real-total-front's innerText put '{% translate "copied!" %}' into #real-total-front's innerText
wait 1s wait 1s
put original_value into #real-total-front's innerText put original_value into #real-total-front's innerText
end"> end">
<i class="fa-solid fa-plus fa-fw me-md-2 text-primary"></i> <i class="fa-solid fa-plus fa-fw me-md-2"></i>
<span class="d-none d-md-inline-block" id="real-total-front">0</span> <span class="hidden md:inline-block" id="real-total-front">0</span>
</button> </button>
<button type="button" class="btn btn-sm btn-secondary dropdown-toggle dropdown-toggle-split" <div>
data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside"> <button class="join-item btn btn-sm btn-secondary"
<span class="visually-hidden">{% trans "Toggle Dropdown" %}</span> type="button"
data-bs-toggle="dropdown"
data-bs-auto-close="outside"
aria-expanded="false">
<i class="fa-solid fa-chevron-down fa-xs"></i>
</button> </button>
<ul class="dropdown-menu"> <ul class="dropdown-menu dropdown-menu-end menu">
<li> <li class="cursor-pointer"
<div class="dropdown-item-text p-0"> _="on click
set original_value to #calc-menu-flat-total's innerText
writeText(original_value) on navigator.clipboard
put '{% translate "copied!" %}' into #calc-menu-flat-total
wait 1s
put original_value into #calc-menu-flat-total
end">
<div class="py-1 px-3">
<div> <div>
<div class="text-body-secondary tw:text-xs tw:font-medium px-3"> <div class="text-base-content/60 text-xs font-medium">
{% trans "Flat Total" %} {% trans "Flat Total" %}
</div> </div>
<div class="dropdown-item px-3 tw:cursor-pointer" <div id="calc-menu-flat-total">
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 0
</div> </div>
</div> </div>
</div> </div>
</li> </li>
<li> <li class="cursor-pointer"
<div class="dropdown-item-text p-0"> _="on click
set original_value to #calc-menu-real-total's innerText
writeText(original_value) on navigator.clipboard
put '{% translate "copied!" %}' into #calc-menu-real-total
wait 1s
put original_value into #calc-menu-real-total
end">
<div class="py-1 px-3">
<div> <div>
<div class="text-body-secondary tw:text-xs tw:font-medium px-3"> <div class="text-base-content/60 text-xs font-medium">
{% trans "Real Total" %} {% trans "Real Total" %}
</div> </div>
<div class="dropdown-item px-3 tw:cursor-pointer" <div id="calc-menu-real-total">
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 0
</div> </div>
</div> </div>
</div> </div>
</li> </li>
<li> <li class="cursor-pointer"
<div class="dropdown-item-text p-0"> _="on click
set original_value to #calc-menu-mean's innerText
writeText(original_value) on navigator.clipboard
put '{% translate "copied!" %}' into #calc-menu-mean
wait 1s
put original_value into #calc-menu-mean
end">
<div class="p-1 px-3">
<div> <div>
<div class="text-body-secondary tw:text-xs tw:font-medium px-3"> <div class="text-base-content/60 text-xs font-medium">
{% trans "Mean" %} {% trans "Mean" %}
</div> </div>
<div class="dropdown-item px-3 tw:cursor-pointer" <div id="calc-menu-mean">
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 0
</div> </div>
</div> </div>
</div> </div>
</li> </li>
<li> <li class="cursor-pointer"
<div class="dropdown-item-text p-0"> _="on click
set original_value to #calc-menu-max's innerText
writeText(original_value) on navigator.clipboard
put '{% translate "copied!" %}' into #calc-menu-max
wait 1s
put original_value into #calc-menu-max
end">
<div class="py-1 px-3">
<div> <div>
<div class="text-body-secondary tw:text-xs tw:font-medium px-3"> <div class="text-base-content/60 text-xs font-medium">
{% trans "Max" %} {% trans "Max" %}
</div> </div>
<div class="dropdown-item px-3 tw:cursor-pointer" <div id="calc-menu-max">
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 0
</div> </div>
</div> </div>
</div> </div>
</li> </li>
<li> <li class="cursor-pointer"
<div class="dropdown-item-text p-0"> _="on click
set original_value to #calc-menu-min's innerText
writeText(original_value) on navigator.clipboard
put '{% translate "copied!" %}' into #calc-menu-min
wait 1s
put original_value into #calc-menu-min
end">
<div class="py-1 px-3">
<div> <div>
<div class="text-body-secondary tw:text-xs tw:font-medium px-3"> <div class="text-base-content/60 text-xs font-medium">
{% trans "Min" %} {% trans "Min" %}
</div> </div>
<div class="dropdown-item px-3 tw:cursor-pointer" <div id="calc-menu-min">
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 0
</div> </div>
</div> </div>
</div> </div>
</li> </li>
<li> <li class="cursor-pointer"
<div class="dropdown-item-text p-0"> _="on click
set original_value to #calc-menu-count's innerText
writeText(original_value) on navigator.clipboard
put '{% translate "copied!" %}' into #calc-menu-count
wait 1s
put original_value into #calc-menu-count
end">
<div class="py-1 px-3">
<div> <div>
<div class="text-body-secondary tw:text-xs tw:font-medium px-3"> <div class="text-base-content/60 text-xs font-medium">
{% trans "Count" %} {% trans "Count" %}
</div> </div>
<div class="dropdown-item px-3 tw:cursor-pointer" <div id="calc-menu-count">
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 0
</div> </div>
</div> </div>
@@ -237,6 +238,7 @@
</li> </li>
</ul> </ul>
</div> </div>
</div>
{% endspaceless %} {% endspaceless %}
</div> </div>
</div> </div>
@@ -0,0 +1,7 @@
<div class="fab">
<button class="btn btn-lg btn-circle btn-primary"
hx-get="{{ url }}"
hx-target="{{ hx_target }}">
<i class="fa-solid fa-plus text-2xl fa-fw"></i>
</button>
</div>
+1 -3
View File
@@ -1,8 +1,6 @@
{% spaceless %} {% spaceless %}
{% load i18n %} {% load i18n %}
<span class="tw:text-xs text-white-50 mx-1" <span class="text-xs text-base-content/50 mx-1" data-tippy-content="{{ content }}">
data-bs-toggle="tooltip"
data-bs-title="{{ content }}">
<i class="{% if not icon %}fa-solid fa-circle-question{% else %}{{ icon }}{% endif %} fa-fw"></i> <i class="{% if not icon %}fa-solid fa-circle-question{% else %}{{ icon }}{% endif %} fa-fw"></i>
</span> </span>
{% endspaceless %} {% endspaceless %}
+4 -4
View File
@@ -1,9 +1,9 @@
<div class="card tw:relative h-100 shadow"> <div class="card card-border relative h-full shadow bg-base-100">
<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="absolute h-8 w-8 right-2 top-2 bg-{{ color }}-300 text-{{ color }}-800 text-center flex items-center justify-center rounded-lg">
{% if icon %}<i class="{{ icon }}"></i>{% else %}<span class="fw-bold">{{ title.0 }}</span>{% endif %} {% if icon %}<i class="{{ icon }}"></i>{% else %}<span class="font-bold">{{ title.0 }}</span>{% endif %}
</div> </div>
<div class="card-body"> <div class="card-body">
<h5 class="tw:text-{{ color }}-400 fw-bold tw:mr-[50px] {{ title_css_classes }}" {{ attrs }}>{{ title }}{% if help_text %}<c-ui.help-icon :content="help_text" icon=""></c-ui.help-icon>{% endif %}</h5> <h5 class="font-bold mr-[50px] text-xl {{ title_css_classes }}" {{ attrs }}>{{ title }}{% if help_text %}<c-ui.help-icon :content="help_text" icon=""></c-ui.help-icon>{% endif %}</h5>
{{ slot }} {{ slot }}
</div> </div>
</div> </div>
@@ -1,31 +1,57 @@
{% load i18n %} {% load i18n %}
<div class="progress-stacked"> <div class="flex w-full h-4 rounded-full bg-white overflow-hidden"
<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" }}%"> role="group"
<div class="progress-bar progress-bar-striped tw:bg-green-300!" aria-label="{% trans 'Income and Expense Percentages' %}">
data-bs-toggle="tooltip"
data-bs-placement="top" <!-- Segment 1: Projected Income -->
title="{% trans 'Projected Income' %} ({{ percentage.percentages.income_projected|floatformat:2 }}%)"> <div class="flex items-center justify-center h-4 bg-success/60 tooltip tooltip-bottom tooltip-success
{% if percentage.percentages.income_projected > 0 %}rounded-s-full{% endif %}
{% if percentage.percentages.income_projected > 0 and percentage.percentages.income_current <= 0 and percentage.percentages.expense_projected <= 0 and percentage.percentages.expense_current <= 0 %}rounded-e-full{% endif %}"
style="width: {{ percentage.percentages.income_projected|floatformat:'2u' }}%"
data-tippy-content="{% trans 'Projected Income' %} ({{ percentage.percentages.income_projected|floatformat:2 }}%)"
role="progressbar"
aria-label="{% trans 'Projected Income' %} ({{ percentage.percentages.income_projected|floatformat:2 }}%)"
aria-valuenow="{{ percentage.percentages.income_projected|floatformat:0 }}"
aria-valuemin="0"
aria-valuemax="100">
</div> </div>
<!-- Segment 2: Current Income -->
<div class="flex items-center justify-center h-4 bg-success tooltip tooltip-bottom tooltip-success
{% if percentage.percentages.income_projected <= 0 and percentage.percentages.income_current > 0 %}rounded-s-full{% endif %}
{% if percentage.percentages.income_current > 0 and percentage.percentages.expense_projected <= 0 and percentage.percentages.expense_current <= 0 %}rounded-e-full{% endif %}"
style="width: {{ percentage.percentages.income_current|floatformat:'2u' }}%"
data-tippy-content="{% trans 'Current Income' %} ({{ percentage.percentages.income_current|floatformat:2 }}%)"
role="progressbar"
aria-label="{% trans 'Current Income' %} ({{ percentage.percentages.income_current|floatformat:2 }}%)"
aria-valuenow="{{ percentage.percentages.income_current|floatformat:0 }}"
aria-valuemin="0"
aria-valuemax="100">
</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!" <!-- Segment 3: Projected Expenses -->
data-bs-toggle="tooltip" <div class="flex items-center justify-center h-4 bg-error/60 tooltip tooltip-bottom tooltip-error
data-bs-placement="top" {% if percentage.percentages.income_projected <= 0 and percentage.percentages.income_current <= 0 and percentage.percentages.expense_projected > 0 %}rounded-s-full{% endif %}
title="{% trans 'Current Income' %} ({{ p.percentages.income_current|floatformat:2 }}%)"> {% if percentage.percentages.expense_projected > 0 and percentage.percentages.expense_current <= 0 %}rounded-e-full{% endif %}"
</div> style="width: {{ percentage.percentages.expense_projected|floatformat:'2u' }}%"
</div> data-tippy-content="{% trans 'Projected Expenses' %} ({{ percentage.percentages.expense_projected|floatformat:2 }}%)"
<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" }}%"> role="progressbar"
<div class="progress-bar progress-bar-striped tw:bg-red-300!" aria-label="{% trans 'Projected Expenses' %} ({{ percentage.percentages.expense_projected|floatformat:2 }}%)"
data-bs-toggle="tooltip" aria-valuenow="{{ percentage.percentages.expense_projected|floatformat:0 }}"
data-bs-placement="top" aria-valuemin="0"
title="{% trans 'Projected Expenses' %} ({{ percentage.percentages.expense_projected|floatformat:2 }}%)"> aria-valuemax="100">
</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!"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="{% trans 'Current Expenses' %} ({{ percentage.percentages.expense_current|floatformat:2 }}%)">
</div> </div>
<!-- Segment 4: Current Expenses -->
<div class="flex items-center justify-center h-4 bg-error tooltip tooltip-bottom tooltip-error
{% if percentage.percentages.income_projected <= 0 and percentage.percentages.income_current <= 0 and percentage.percentages.expense_projected <= 0 and percentage.percentages.expense_current > 0 %}rounded-s-full{% endif %}
{% if percentage.percentages.expense_current > 0 %}rounded-e-full{% endif %}"
style="width: {{ percentage.percentages.expense_current|floatformat:'2u' }}%"
data-tippy-content="{% trans 'Current Expenses' %} ({{ percentage.percentages.expense_current|floatformat:2 }}%)"
role="progressbar"
aria-label="{% trans 'Current Expenses' %} ({{ percentage.percentages.expense_current|floatformat:2 }}%)"
aria-valuenow="{{ percentage.percentages.expense_current|floatformat:0 }}"
aria-valuemin="0"
aria-valuemax="100">
</div> </div>
</div> </div>
@@ -1,7 +1,7 @@
{% load i18n %} {% load i18n %}
<div class="d-grid gap-2 d-xl-flex justify-content-xl-end"> <div class="grid gap-2 grid-cols-1 xl:flex xl:justify-end">
<div class="d-grid gap-2 d-xl-flex flex-wrap justify-content-xl-center"> <div class="grid gap-2 xl:flex flex-wrap xl:justify-center">
<button class="btn btn-sm btn-outline-success" <button class="btn btn-sm btn-outline btn-success"
hx-get="{% url 'transaction_add' %}" hx-get="{% url 'transaction_add' %}"
hx-target="#generic-offcanvas" hx-target="#generic-offcanvas"
hx-trigger="click, add_income from:window" hx-trigger="click, add_income from:window"
@@ -9,7 +9,7 @@
<i class="fa-solid fa-arrow-right-to-bracket me-2"></i> <i class="fa-solid fa-arrow-right-to-bracket me-2"></i>
{% translate "Income" %} {% translate "Income" %}
</button> </button>
<button class="btn btn-sm btn-outline-danger" <button class="btn btn-sm btn-outline btn-error"
hx-get="{% url 'transaction_add' %}" hx-get="{% url 'transaction_add' %}"
hx-target="#generic-offcanvas" hx-target="#generic-offcanvas"
hx-trigger="click, add_expense from:window" hx-trigger="click, add_expense from:window"
@@ -17,21 +17,21 @@
<i class="fa-solid fa-arrow-right-from-bracket me-2"></i> <i class="fa-solid fa-arrow-right-from-bracket me-2"></i>
{% translate "Expense" %} {% translate "Expense" %}
</button> </button>
<button class="btn btn-sm btn-outline-warning" <button class="btn btn-sm btn-outline btn-warning"
hx-get="{% url 'installment_plan_add' %}" hx-get="{% url 'installment_plan_add' %}"
hx-trigger="click, installment from:window" hx-trigger="click, installment from:window"
hx-target="#generic-offcanvas"> hx-target="#generic-offcanvas">
<i class="fa-solid fa-divide me-2"></i> <i class="fa-solid fa-divide me-2"></i>
{% translate "Installment" %} {% translate "Installment" %}
</button> </button>
<button class="btn btn-sm btn-outline-warning" <button class="btn btn-sm btn-outline btn-warning"
hx-get="{% url 'recurring_transaction_add' %}" hx-get="{% url 'recurring_transaction_add' %}"
hx-trigger="click, balance from:window" hx-trigger="click, balance from:window"
hx-target="#generic-offcanvas"> hx-target="#generic-offcanvas">
<i class="fa-solid fa-repeat me-2"></i> <i class="fa-solid fa-repeat me-2"></i>
{% translate "Recurring" %} {% translate "Recurring" %}
</button> </button>
<button class="btn btn-sm btn-outline-info" <button class="btn btn-sm btn-outline btn-info"
hx-get="{% url 'transactions_transfer' %}" hx-get="{% url 'transactions_transfer' %}"
hx-target="#generic-offcanvas" hx-target="#generic-offcanvas"
hx-trigger="click, add_transfer from:window" hx-trigger="click, add_transfer from:window"
@@ -39,7 +39,7 @@
<i class="fa-solid fa-money-bill-transfer me-2"></i> <i class="fa-solid fa-money-bill-transfer me-2"></i>
{% translate "Transfer" %} {% translate "Transfer" %}
</button> </button>
<button class="btn btn-sm btn-outline-info" <button class="btn btn-sm btn-outline btn-info"
hx-get="{% url 'account_reconciliation' %}" hx-get="{% url 'account_reconciliation' %}"
hx-trigger="click, balance from:window" hx-trigger="click, balance from:window"
hx-target="#generic-offcanvas"> hx-target="#generic-offcanvas">
@@ -1,102 +1,101 @@
{% load i18n %} {% 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="sticky bottom-4 left-0 right-0 z-1000 hidden mx-auto w-fit" id="actions-bar"
_="on change from #transactions-list or htmx:afterSettle from window _="on change from #transactions-list or htmx:afterSettle from window
if #actions-bar then if #actions-bar then
if no <input[type='checkbox']:checked/> in #transactions-list if no <input[type='checkbox']:checked/> in #transactions-list
if #actions-bar if #actions-bar
add .slide-in-bottom-reverse then settle add .slide-in-bottom-reverse then settle
then add .tw:hidden to #actions-bar then add .hidden to #actions-bar
then remove .slide-in-bottom-reverse then remove .slide-in-bottom-reverse
end end
else else
if #actions-bar if #actions-bar
set #selected-count's innerHTML to length of <input[type='checkbox']:checked/> in #transactions-list set #selected-count's innerHTML to length of <input[type='checkbox']:checked/> in #transactions-list
then remove .tw:hidden from #actions-bar then remove .hidden from #actions-bar
then trigger selected_transactions_updated then trigger selected_transactions_updated
end end
end end
end end
end"> end">
<div class="card slide-in-bottom tw:max-w-[90vw]"> <div class="card bg-base-300 shadow slide-in-bottom max-w-[90vw] card-border">
<div class="card-body p-2 d-flex justify-content-between align-items-center gap-3 tw:overflow-x-auto"> <div class="card-body flex-row p-2 flex justify-between items-center gap-3 overflow-x-auto">
{% spaceless %} {% spaceless %}
<div class="tw:font-bold tw:text-md ms-2" id="selected-count">0</div> <div class="font-bold text-md ms-2" id="selected-count">0</div>
<div class="vr tw:align-middle"></div> <div class="divider divider-horizontal m-0"></div>
<div class="dropdown"> <div>
<button class="btn btn-secondary btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown" <button role="button" class="btn btn-secondary btn-sm" type="button"
aria-expanded="false" data-bs-popper-config='{"strategy":"fixed"}'> data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa-regular fa-square-check fa-fw"></i> <i class="fa-regular fa-square-check fa-fw"></i>
<i class="fa-solid fa-chevron-down fa-xs"></i>
</button> </button>
<ul class="dropdown-menu"> <ul class="dropdown-menu menu">
<li> <li>
<div class="dropdown-item px-3 tw:cursor-pointer" <a class="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"> _="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 text-success me-3"></i>{% translate 'Select All' %}
</div> </a>
</li> </li>
<li> <li>
<div class="dropdown-item px-3 tw:cursor-pointer" <a class="cursor-pointer"
_="on click set <#transactions-list input[type='checkbox']/>'s checked to false then call me.blur() then trigger change"> _="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 text-error me-3"></i>{% translate 'Unselect All' %}
</div> </a>
</li> </li>
</ul> </ul>
</div> </div>
<div class="vr tw:align-middle"></div> <div class="divider divider-horizontal m-0"></div>
<div class="btn-group"> <div class="join">
<button class="btn btn-secondary btn-sm" <button class="btn btn-secondary join-item btn-sm"
hx-get="{% url 'transactions_bulk_edit' %}" hx-get="{% url 'transactions_bulk_edit' %}"
hx-target="#generic-offcanvas" hx-target="#generic-offcanvas"
hx-include=".transaction" hx-include=".transaction"
data-bs-toggle="tooltip" data-tippy-content="{% translate 'Edit' %}">
data-bs-title="{% translate 'Edit' %}">
<i class="fa-solid fa-pencil"></i> <i class="fa-solid fa-pencil"></i>
</button> </button>
<button type="button" class="btn btn-sm btn-secondary dropdown-toggle dropdown-toggle-split" <div>
data-bs-toggle="dropdown" data-bs-popper-config='{"strategy":"fixed"}' aria-expanded="false" <button type="button" role="button" class="join-item btn btn-sm btn-secondary"
data-bs-auto-close="outside"> data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">{% trans "Toggle Dropdown" %}</span> <i class="fa-solid fa-chevron-down fa-xs"></i>
</button> </button>
<ul class="dropdown-menu"> <ul class="dropdown-menu menu">
<li> <li>
<div class="dropdown-item px-3 tw:cursor-pointer" <a class="cursor-pointer"
hx-get="{% url 'transactions_bulk_unpay' %}" hx-get="{% url 'transactions_bulk_unpay' %}"
hx-include=".transaction"> 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 text-red-400 fa-fw me-3"></i>{% translate 'Mark as unpaid' %}
</div> </a>
</li> </li>
<li> <li>
<div class="dropdown-item px-3 tw:cursor-pointer" <a class="cursor-pointer"
hx-get="{% url 'transactions_bulk_pay' %}" hx-get="{% url 'transactions_bulk_pay' %}"
hx-include=".transaction"> 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 text-green-400 fa-fw me-3"></i>{% translate 'Mark as paid' %}
</div> </a>
</li> </li>
</ul> </ul>
</div> </div>
</div>
<button class="btn btn-secondary btn-sm" <button class="btn btn-secondary btn-sm"
hx-get="{% url 'transactions_bulk_clone' %}" hx-get="{% url 'transactions_bulk_clone' %}"
hx-include=".transaction" hx-include=".transaction"
data-bs-toggle="tooltip" data-tippy-content="{% translate 'Duplicate' %}">
data-bs-title="{% translate 'Duplicate' %}">
<i class="fa-solid fa-clone fa-fw"></i> <i class="fa-solid fa-clone fa-fw"></i>
</button> </button>
<button class="btn btn-secondary btn-sm" <button class="btn btn-error btn-sm"
hx-get="{% url 'transactions_bulk_delete' %}" hx-get="{% url 'transactions_bulk_delete' %}"
hx-include=".transaction" hx-include=".transaction"
hx-trigger="confirmed" hx-trigger="confirmed"
data-bs-toggle="tooltip" data-tippy-content="{% translate 'Delete' %}"
data-bs-title="{% translate 'Delete' %}"
data-bypass-on-ctrl="true" data-bypass-on-ctrl="true"
data-title="{% translate "Are you sure?" %}" data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}" data-text="{% translate "You won't be able to revert this!" %}"
data-confirm-text="{% translate "Yes, delete them!" %}" data-confirm-text="{% translate "Yes, delete them!" %}"
_="install prompt_swal"> _="install prompt_swal">
<i class="fa-solid fa-trash text-danger"></i> <i class="fa-solid fa-trash"></i>
</button> </button>
<div class="vr tw:align-middle"></div> <div class="divider divider-horizontal m-0"></div>
<div class="btn-group" <div class="join"
_="on selected_transactions_updated from #actions-bar _="on selected_transactions_updated from #actions-bar
set realTotal to math.bignumber(0) set realTotal to math.bignumber(0)
set flatTotal to math.bignumber(0) set flatTotal to math.bignumber(0)
@@ -134,138 +133,136 @@
put mean.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40}) into #calc-menu-mean's innerText put mean.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40}) into #calc-menu-mean's innerText
put flatAmountValues.length.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40}) into #calc-menu-count's innerText put flatAmountValues.length.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40}) into #calc-menu-count's innerText
end"> end">
<button class="btn btn-secondary btn-sm" _="on click <button class="btn btn-secondary btn-sm join-item"
_="on click
set original_value to #real-total-front's innerText set original_value to #real-total-front's innerText
writeText(original_value) on navigator.clipboard writeText(original_value) on navigator.clipboard
put '{% translate "copied!" %}' into #real-total-front's innerText put '{% translate "copied!" %}' into #real-total-front's innerText
wait 1s wait 1s
put original_value into #real-total-front's innerText put original_value into #real-total-front's innerText
end"> end">
<i class="fa-solid fa-plus fa-fw me-md-2 text-primary"></i> <i class="fa-solid fa-plus fa-fw me-md-2"></i>
<span class="d-none d-md-inline-block" id="real-total-front">0</span> <span class="hidden md:inline-block" id="real-total-front">0</span>
</button> </button>
<button type="button" class="btn btn-sm btn-secondary dropdown-toggle dropdown-toggle-split" <div>
data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside" <button class="join-item btn btn-sm btn-secondary"
data-bs-popper-config='{"strategy":"fixed"}'> type="button"
<span class="visually-hidden">{% trans "Toggle Dropdown" %}</span> data-bs-toggle="dropdown"
data-bs-auto-close="outside"
aria-expanded="false">
<i class="fa-solid fa-chevron-down fa-xs"></i>
</button> </button>
<ul class="dropdown-menu"> <ul class="dropdown-menu dropdown-menu-end menu">
<li> <li class="cursor-pointer"
<div class="dropdown-item-text p-0"> _="on click
set original_value to #calc-menu-flat-total's innerText
writeText(original_value) on navigator.clipboard
put '{% translate "copied!" %}' into #calc-menu-flat-total
wait 1s
put original_value into #calc-menu-flat-total
end">
<div class="py-1 px-3">
<div> <div>
<div class="text-body-secondary tw:text-xs tw:font-medium px-3"> <div class="text-base-content/60 text-xs font-medium">
{% trans "Flat Total" %} {% trans "Flat Total" %}
</div> </div>
<div class="dropdown-item px-3 tw:cursor-pointer" <div id="calc-menu-flat-total">
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 0
</div> </div>
</div> </div>
</div> </div>
</li> </li>
<li> <li class="cursor-pointer"
<div class="dropdown-item-text p-0"> _="on click
set original_value to #calc-menu-real-total's innerText
writeText(original_value) on navigator.clipboard
put '{% translate "copied!" %}' into #calc-menu-real-total
wait 1s
put original_value into #calc-menu-real-total
end">
<div class="py-1 px-3">
<div> <div>
<div class="text-body-secondary tw:text-xs tw:font-medium px-3"> <div class="text-base-content/60 text-xs font-medium">
{% trans "Real Total" %} {% trans "Real Total" %}
</div> </div>
<div class="dropdown-item px-3 tw:cursor-pointer" <div id="calc-menu-real-total">
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 0
</div> </div>
</div> </div>
</div> </div>
</li> </li>
<li> <li class="cursor-pointer"
<div class="dropdown-item-text p-0"> _="on click
set original_value to #calc-menu-mean's innerText
writeText(original_value) on navigator.clipboard
put '{% translate "copied!" %}' into #calc-menu-mean
wait 1s
put original_value into #calc-menu-mean
end">
<div class="p-1 px-3">
<div> <div>
<div class="text-body-secondary tw:text-xs tw:font-medium px-3"> <div class="text-base-content/60 text-xs font-medium">
{% trans "Mean" %} {% trans "Mean" %}
</div> </div>
<div class="dropdown-item px-3 tw:cursor-pointer" <div id="calc-menu-mean">
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 0
</div> </div>
</div> </div>
</div> </div>
</li> </li>
<li> <li class="cursor-pointer"
<div class="dropdown-item-text p-0"> _="on click
set original_value to #calc-menu-max's innerText
writeText(original_value) on navigator.clipboard
put '{% translate "copied!" %}' into #calc-menu-max
wait 1s
put original_value into #calc-menu-max
end">
<div class="py-1 px-3">
<div> <div>
<div class="text-body-secondary tw:text-xs tw:font-medium px-3"> <div class="text-base-content/60 text-xs font-medium">
{% trans "Max" %} {% trans "Max" %}
</div> </div>
<div class="dropdown-item px-3 tw:cursor-pointer" <div id="calc-menu-max">
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 0
</div> </div>
</div> </div>
</div> </div>
</li> </li>
<li> <li class="cursor-pointer"
<div class="dropdown-item-text p-0"> _="on click
set original_value to #calc-menu-min's innerText
writeText(original_value) on navigator.clipboard
put '{% translate "copied!" %}' into #calc-menu-min
wait 1s
put original_value into #calc-menu-min
end">
<div class="py-1 px-3">
<div> <div>
<div class="text-body-secondary tw:text-xs tw:font-medium px-3"> <div class="text-base-content/60 text-xs font-medium">
{% trans "Min" %} {% trans "Min" %}
</div> </div>
<div class="dropdown-item px-3 tw:cursor-pointer" <div id="calc-menu-min">
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 0
</div> </div>
</div> </div>
</div> </div>
</li> </li>
<li> <li class="cursor-pointer"
<div class="dropdown-item-text p-0"> _="on click
set original_value to #calc-menu-count's innerText
writeText(original_value) on navigator.clipboard
put '{% translate "copied!" %}' into #calc-menu-count
wait 1s
put original_value into #calc-menu-count
end">
<div class="py-1 px-3">
<div> <div>
<div class="text-body-secondary tw:text-xs tw:font-medium px-3"> <div class="text-base-content/60 text-xs font-medium">
{% trans "Count" %} {% trans "Count" %}
</div> </div>
<div class="dropdown-item px-3 tw:cursor-pointer" <div id="calc-menu-count">
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 0
</div> </div>
</div> </div>
@@ -273,6 +270,7 @@
</li> </li>
</ul> </ul>
</div> </div>
</div>
{% endspaceless %} {% endspaceless %}
</div> </div>
</div> </div>
@@ -10,7 +10,7 @@
title="{% translate "Income" %}"></c-components.fab_menu_button> title="{% translate "Income" %}"></c-components.fab_menu_button>
<c-components.fab_menu_button <c-components.fab_menu_button
color="danger" color="error"
hx_target="#generic-offcanvas" hx_target="#generic-offcanvas"
hx_trigger="click, add_income from:window" hx_trigger="click, add_income from:window"
hx_vals='{"year": {{ year }}, {% if month %}"month": {{ month }},{% endif %} "type": "EX"}' hx_vals='{"year": {{ year }}, {% if month %}"month": {{ month }},{% endif %} "type": "EX"}'
@@ -0,0 +1,10 @@
<c-ui.components.collapse
title_class="join-item"
:active="div.active">
<c-slot name="title">
{{ div.name }}
</c-slot>
<c-slot name="content">
{{ fields|safe }}
</c-slot>
</c-ui.components.collapse>
@@ -0,0 +1,3 @@
<div class="join join-vertical w-full{% if accordion.css_class %} {{accordion.css_class}}{% endif %}" id="{{ accordion.css_id }}">
{{ content|safe }}
</div>
@@ -0,0 +1,22 @@
{% for fieldset in form.fieldsets %}
<fieldset class="fieldset fieldset-{{ forloop.counter }} {{ fieldset.classes }}">
{% if fieldset.legend %}
<legend class="fieldset-legend">{{ fieldset.legend }}</legend>
{% endif %}
{% if fieldset.description %}
<p class="text-sm text-base-content/60">{{ fieldset.description }}</p>
{% endif %}
{% for field in fieldset %}
{% if field.is_hidden %}
{{ field }}
{% else %}
{% include "crispy-daisyui/field.html" %}
{% endif %}
{% endfor %}
{% if not forloop.last or not fieldset_open %}
</fieldset>
{% endif %}
{% endfor %}
@@ -0,0 +1,9 @@
{% if form.form_html %}
{% if include_media %}{{ form.media }}{% endif %}
{% if form_show_errors %}
{% include "crispy-daisyui/errors.html" %}
{% endif %}
{{ form.form_html }}
{% else %}
{% include "crispy-daisyui/uni_form.html" %}
{% endif %}
+8
View File
@@ -0,0 +1,8 @@
{% if form.non_field_errors %}
<div class="alert alert-error">
{% if form_error_title %}<h4 class="font-bold">{{ form_error_title }}</h4>{% endif %}
<ul class="m-0 list-inside">
{{ form.non_field_errors|unordered_list }}
</ul>
</div>
{% endif %}
@@ -0,0 +1,8 @@
{% if formset.non_form_errors %}
<div class="alert alert-error">
{% if formset_error_title %}<h4 class="font-bold">{{ formset_error_title }}</h4>{% endif %}
<ul class="m-0 list-inside">
{{ formset.non_form_errors|unordered_list }}
</ul>
</div>
{% endif %}
+84
View File
@@ -0,0 +1,84 @@
{% load crispy_forms_field %}
{% load crispy_extra %}
{% if field.is_hidden %}
{{ field }}
{% else %}
{% if field|is_checkbox and tag != "td" %}
<div class="">
{% if label_class %}
<div class="{{ field_class }}">
{% endif %}
{% endif %}
<{% if tag %}{{ tag }}{% else %}fieldset{% endif %} id="div_{{ field.auto_id }}" class="{% if field|is_checkbox and form_show_labels %}form-control{% else %}fieldset{% endif %}{% if wrapper_class %} {{ wrapper_class }}{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}">
{% if field.label and not field|is_checkbox and form_show_labels %}
{% if field.use_fieldset %}<fieldset class="fieldset"{% if field.aria_describedby %} aria-describedby="{{ field.aria_describedby }}"{% endif %}>{% endif %}
<{% if field.use_fieldset %}legend{% else %}label{% endif %}
{% if field.id_for_label %}for="{{ field.id_for_label }}"{% endif %} class="fieldset-legend{% if label_class %} {{ label_class }}{% endif %}{% if field.field.required %} requiredField{% endif %}">
{{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}
</{% if field.use_fieldset %}legend{% else %}label{% endif %}>
{% endif %}
{% if field|is_checkboxselectmultiple or field|is_radioselect %}
{% include 'crispy-daisyui/layout/radio_checkbox_select.html' %}
{% endif %}
{% if not field|is_checkboxselectmultiple and not field|is_radioselect %}
{% if field|is_checkbox and form_show_labels %}
{% if field.errors %}
{% crispy_field field 'class' 'checkbox checkbox-error' %}
{% else %}
{% crispy_field field 'class' 'checkbox' %}
{% endif %}
<label for="{{ field.id_for_label }}" class="label{% if field.field.required %} requiredField{% endif %}">
{{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}
</label>
{% include 'crispy-daisyui/layout/help_text_and_errors.html' %}
{% else %}
{% if field_class %}<div class="{{ field_class }}">{% endif %}
{% if field|is_file %}
{% include 'crispy-daisyui/layout/field_file.html' %}
{% elif field|is_select %}
{% if field.errors %}
{% crispy_field field 'class' 'select-error w-full' %}
{% else %}
{% crispy_field field 'class' '' %}
{% endif %}
{% elif field|is_checkbox %}
{% if field.errors %}
{% crispy_field field 'class' 'checkbox checkbox-error w-full' %}
{% else %}
{% crispy_field field 'class' 'checkbox w-full' %}
{% endif %}
{% elif field|is_input %}
{% if field.errors %}
{% crispy_field field 'class' 'input input-error w-full' %}
{% else %}
{% crispy_field field 'class' 'input w-full' %}
{% endif %}
{% elif field|is_textarea %}
{% if field.errors %}
{% crispy_field field 'class' 'textarea textarea-error w-full' %}
{% else %}
{% crispy_field field 'class' 'textarea w-full' %}
{% endif %}
{% elif field.errors %}
{% crispy_field field 'class' 'input input-error w-full' %}
{% else %}
{% crispy_field field 'class' 'input w-full' %}
{% endif %}
{% if not field|is_file %}
{% include 'crispy-daisyui/layout/help_text_and_errors.html' %}
{% endif %}
{% if field_class %}</div>{% endif %}
{% endif %}
{% endif %}
{% if field.use_fieldset and field.label and form_show_labels %}</fieldset>{% endif %}
</{% if tag %}{{ tag }}{% else %}fieldset{% endif %}>
{% if field|is_checkbox and tag != "td" %}
{% if label_class %}
</div>
{% endif %}
</div>
{% endif %}
{% endif %}
+13
View File
@@ -0,0 +1,13 @@
{% if inputs %}
<div class="">
{% if label_class %}
<div class="aab {{ label_class }}"></div>
{% endif %}
<div class="{{ field_class }}">
{% for input in inputs %}
{% include "crispy-daisyui/layout/baseinput.html" %}
{% endfor %}
</div>
</div>
{% endif %}
@@ -0,0 +1,4 @@
<div class="alert {{ alert.css_class }}" role="alert"{% if alert.css_id %} id="{{ alert.css_id }}"{% endif %}>
{{ content|safe }}
{% if dismiss %}<button type="button" class="btn btn-sm btn-circle btn-ghost" data-bs-dismiss="alert" aria-label="Close"></button>{% endif %}
</div>
@@ -0,0 +1 @@
{% for name, value in widget.attrs.items %}{% if value is not False %} {{ name }}{% if value is not True %}="{{ value|stringformat:'s' }}"{% endif %}{% endif %}{% endfor %}
@@ -0,0 +1,9 @@
<input type="{{ input.input_type }}"
name="{% if input.name|wordcount > 1 %}{{ input.name|slugify }}{% else %}{{ input.name }}{% endif %}"
value="{{ input.value }}"
{% if input.input_type != "hidden" %}
class="btn {{ input.field_classes }}"
id="{% if input.id %}{{ input.id }}{% else %}{{ input.input_type }}-id-{{ input.name|slugify }}{% endif %}"
{% endif %}
{{ input.flat_attrs }}
/>
@@ -0,0 +1 @@
<button class="btn" {{ button.flat_attrs }}>{{ button.content|safe }}</button>
@@ -0,0 +1,4 @@
<div {% if buttonholder.css_id %}id="{{ buttonholder.css_id }}"{% endif %}
class="flex flex-col gap-2{% if buttonholder.css_class %} {{ buttonholder.css_class }}{% endif %}">
{{ fields_output|safe }}
</div>
@@ -0,0 +1,16 @@
{% if field.is_hidden %}
{{ field }}
{% else %}
<div id="div_{{ field.auto_id }}" class="{% if wrapper_class %} {{ wrapper_class }}{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}">
{% if field.label %}
<fieldset class="fieldset"{% if field.aria_describedby %} aria-describedby="{{ field.aria_describedby }}"{% endif %}>
<legend for="{{ field.id_for_label }}" class="fieldset-legend {{ label_class }}{% if field.field.required %} requiredField{% endif %}">
{{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}
</legend>
{% endif %}
{% include 'crispy-daisyui/layout/radio_checkbox_select.html' %}
{% if field.label %}</fieldset>{% endif %}
</div>
{% endif %}
@@ -0,0 +1,6 @@
<div {% if div.css_id %}id="{{ div.css_id }}"{% endif %}
class="{% if 'col' in div.css_class %}{{ div.css_class|default:'' }}{% else %}md:col-span-6 col-span-12{{ div.css_class|default:'' }}{% endif %}" {{ div.flat_attrs }}>
{{ fields|safe }}
</div>
@@ -0,0 +1,4 @@
<div {% if div.css_id %}id="{{ div.css_id }}"{% endif %}
{% if div.css_class %}class="{{ div.css_class }}"{% endif %} {{ div.flat_attrs }}>
{{ fields|safe }}
</div>
@@ -0,0 +1,12 @@
{% if form_show_errors and field.errors %}
{% if field.errors.field_id %}
{# Django 5.2+ #}
<div id="{{field.errors.field_id}}_error" class="text-error text-sm mt-1">
{% else %}
<div id="{{field.auto_id}}_error" class="text-error text-sm mt-1">
{% endif %}
{% for error in field.errors %}
<span id="error_{{ forloop.counter }}_{{ field.auto_id }}"><strong>{{ error }}</strong></span>
{% endfor %}
</div>
{% endif %}
@@ -0,0 +1,12 @@
{% if form_show_errors and field.errors %}
{% if field.errors.field_id %}
{# Django 5.2+ #}
<div id="{{field.errors.field_id}}_error" class="text-error text-sm mt-1">
{% else %}
<div id="{{field.auto_id}}_error" class="text-error text-sm mt-1">
{% endif %}
{% for error in field.errors %}
<p id="error_{{ forloop.counter }}_{{ field.auto_id }}"><strong>{{ error }}</strong></p>
{% endfor %}
</div>
{% endif %}
@@ -0,0 +1,26 @@
{% load crispy_forms_field %}
{% for widget in field.subwidgets %}
{% if widget.data.is_initial %}
<div class="join mb-2">
<span class="btn btn-disabled join-item">{{ widget.data.initial_text }}</span>
<div class="input join-item flex items-center flex-grow">
<span class="break-all flex-grow">
<a href="{{ field.value.url }}" class="link">{{ field.value.name }}</a>
</span>
{% if not widget.data.required %}
<span class="ml-2">
<label class="label cursor-pointer gap-2">
<span class="label-text">{{ widget.data.clear_checkbox_label }}</span>
<input type="checkbox" name="{{ widget.data.checkbox_name }}" id="{{ widget.data.checkbox_id }}" class="checkbox"{% if field.field.disabled %} disabled{% endif %}>
</label>
</span>
{% endif %}
</div>
</div>
{% endif %}
<div class="fieldset">
<input type="{{ widget.data.type }}" name="{{ widget.data.name }}" class="file-input w-full {% if widget.data.attrs.class %} {{ widget.data.attrs.class }}{% endif %}{% if field.errors %} file-input-error{% endif %}"{% if field.field.disabled %} disabled{% endif %}{% for name, value in widget.data.attrs.items %}{% if value is not False and name != 'class' %} {{ name }}{% if value is not True %}="{{ value|stringformat:'s' }}"{% endif %}{% endif %}{% endfor %}>
{% include 'crispy-daisyui/layout/help_text_and_errors.html' %}
</div>
{% endfor %}
@@ -0,0 +1,39 @@
{% load crispy_forms_field %}
<div{% if div.css_id %} id="{{ div.css_id }}"{% endif %} class="{% if wrapper_class %} {{ wrapper_class }}{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}{% if div.css_class %} {{ div.css_class }}{% endif %}" {{ div.flat_attrs }}>
{% if field.label and form_show_labels %}
<label for="{{ field.id_for_label }}" class="label {{ label_class }}{% if field.field.required %} requiredField{% endif %}">
<span class="label-text">{{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}</span>
</label>
{% endif %}
<div{% if field_class %} class="{{ field_class }}"{% endif %}>
<div class="join {% if input_size %} {{ input_size }}{% endif %}">
{% if field|is_select %}
{% if field.errors %}
{% crispy_field field 'class' 'select-error join-item flex-grow' %}
{% else %}
{% crispy_field field 'class' 'join-item flex-grow' %}
{% endif %}
{% else %}
{% if field.errors %}
{% crispy_field field 'class' 'input input-bordered input-error join-item flex-grow' %}
{% else %}
{% crispy_field field 'class' 'input input-bordered join-item flex-grow' %}
{% endif %}
{% endif %}
{{ buttons|safe }}
</div>
{% if field.errors.field_id %}
{# Django 5.2+ #}
<div id="{{field.errors.field_id}}_error" class="text-error text-sm mt-1">
{% else %}
<div id="{{field.auto_id}}_error" class="text-error text-sm mt-1">
{% endif %}
{% for error in field.errors %}
<p id="error_{{ forloop.counter }}_{{ field.auto_id }}"><small><strong>{{ error }}</strong></small></p>
{% endfor %}
</div>
{% include 'crispy-daisyui/layout/help_text.html' %}
</div>
</div>
@@ -0,0 +1,6 @@
<fieldset {% if fieldset.css_id %}id="{{ fieldset.css_id }}"{% endif %}
{% if fieldset.css_class or form_style %}class="{{ fieldset.css_class }} {{ form_style }}"{% endif %}
{{ fieldset.flat_attrs|safe }}>
{% if legend %}<legend class="block text-gray-700 font-bold mb-2">{{ legend|safe }}</legend>{% endif %}
{{ fields|safe }}
</fieldset>
@@ -0,0 +1,20 @@
{% load crispy_forms_field %}
{% if field.is_hidden %}
{{ field }}
{% else %}
<{% if tag %}{{ tag }}{% else %}div{% endif %} id="div_{{ field.auto_id }}" class="fieldset{% if wrapper_class %} {{ wrapper_class }}{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}">
<label class="input input-bordered flex items-center gap-2{% if field.errors %} input-error{% endif %}" {% if field.id_for_label %}for="{{ field.id_for_label }}"{% endif %}>
<span class="label-text">{{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}</span>
{% if field|is_select %}
{% crispy_field field 'class' 'grow' %}
{% else %}
{% crispy_field field 'class' 'grow' %}
{% endif %}
</label>
{% include 'crispy-daisyui/layout/help_text_and_errors.html' %}
</{% if tag %}{{ tag }}{% else %}div{% endif %}>
{% endif %}
@@ -0,0 +1,10 @@
<div
{% if formactions.flat_attrs %}{{ formactions.flat_attrs }}{% endif %}
class="flex flex-col gap-2 mt-3 {{ formactions.css_class|default:'' }} {{ field_class }}"
{% if formactions.id %} id="{{ formactions.id }}"{% endif %}>
{% if label_class %}
<div class="aab {{ label_class }}"></div>
{% endif %}
{{ fields_output|safe }}
</div>
@@ -0,0 +1,7 @@
{% if field.help_text %}
{% if help_text_inline %}
<span id="{{ field.auto_id }}_helptext" class="label text-wrap">{{ field.help_text|safe}}</span>
{% else %}
<p {% if field.auto_id %}id="{{ field.auto_id }}_helptext" {% endif %}class="label text-wrap">{{ field.help_text|safe }}</p>
{% endif %}
{% endif %}
@@ -0,0 +1,13 @@
{% if help_text_inline and not error_text_inline %}
{% include 'crispy-daisyui/layout/help_text.html' %}
{% endif %}
{% if error_text_inline %}
{% include 'crispy-daisyui/layout/field_errors.html' %}
{% else %}
{% include 'crispy-daisyui/layout/field_errors_block.html' %}
{% endif %}
{% if not help_text_inline %}
{% include 'crispy-daisyui/layout/help_text.html' %}
{% endif %}
@@ -0,0 +1,26 @@
{% load crispy_forms_field %}
{% if field.is_hidden %}
{{ field }}
{% else %}
{% if field|is_checkbox %}
<div id="div_{{ field.auto_id }}" class="form-control inline-flex{% if wrapper_class %} {{ wrapper_class }}{% endif %}">
<label class="label cursor-pointer gap-2">
{% crispy_field field 'class' 'checkbox' %}
<span class="label-text">{{ field.label }}</span>
</label>
</div>
{% else %}
<div id="div_{{ field.auto_id }}"{% if wrapper_class %} class="{{ wrapper_class }}"{% endif %}>
<label for="{{ field.id_for_label }}" class="sr-only">
{{ field.label }}
</label>
{% if field.errors %}
{% crispy_field field 'class' 'input input-bordered input-error' 'placeholder' field.label %}
{% else %}
{% crispy_field field 'class' 'input input-bordered' 'placeholder' field.label %}
{% endif %}
</div>
{% endif %}
{% endif %}
@@ -0,0 +1,11 @@
<div id="{{ modal.css_id }}" class="modal {{ modal.css_class }}" {{ modal.flat_attrs }}>
<div class="modal-box" role="document">
<form method="dialog">
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" aria-label="Close"></button>
</form>
<h3 class="font-bold text-lg {{ modal.title_class }}" id="{{ modal.title_id }}-label">{{ modal.title }}</h3>
<div class="py-4">
{{ fields }}
</div>
</div>
</div>
@@ -0,0 +1,27 @@
{% load crispy_forms_field %}
{% if field.is_hidden %}
{{ field }}
{% else %}
{% if field.label %}
<label for="{{ field.id_for_label }}" class="label{% if labelclass %} {{ labelclass }}{% endif %}">
{% endif %}
{% if field|is_checkbox %}
{% crispy_field field %}
{% endif %}
{% if field.label %}
<span class="label-text">{{ field.label }}</span>
{% endif %}
{% if not field|is_checkbox %}
{% crispy_field field %}
{% endif %}
{% if field.label %}
</label>
{% endif %}
{% endif %}
@@ -0,0 +1,52 @@
{% load crispy_forms_field %}
{% if field.is_hidden %}
{{ field }}
{% else %}
<div id="div_{{ field.auto_id }}" class="{% if wrapper_class %} {{ wrapper_class }}{% endif %}{% if form_group_wrapper_class %} {{ form_group_wrapper_class }}{% endif %}{% if form_show_errors and field.errors %} has-error{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}">
<fieldset class="fieldset{% if field_class %} {{ field_class }}{% endif %}">
{% if field.label and form_show_labels %}
<legend class="fieldset-legend{{ label_class }}{% if field.field.required %} requiredField{% endif %}">
{{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}
</legend>
{% endif %}
<label class="{% if input_size %} {{ input_size }}{% endif %}{% if field.errors %} input-error{% endif %}">
{# prepend #}
{% if crispy_prepended_text %}
{{ crispy_prepended_text }}
{% endif %}
{# input #}
{% if field|is_select %}
{% if field.errors %}
{% crispy_field field 'class' 'select-error grow' %}
{% else %}
{% crispy_field field 'class' 'grow' %}
{% endif %}
{% else %}
{% crispy_field field 'class' 'grow' %}
{% endif %}
{# append #}
{% if crispy_appended_text %}
{{ crispy_appended_text }}
{% endif %}
</label>
{# help text as label paragraph #}
{% if not help_text_inline %}
{% include 'crispy-daisyui/layout/help_text.html' %}
{% endif %}
{# errors #}
{% if error_text_inline %}
{% include 'crispy-daisyui/layout/field_errors.html' %}
{% else %}
{% include 'crispy-daisyui/layout/field_errors_block.html' %}
{% endif %}
</fieldset>
</div>
{% endif %}
@@ -0,0 +1,32 @@
{% load crispy_forms_filters %}
{% load l10n %}
<div {% if field_class %}class="{{ field_class }}"{% endif %}{% if flat_attrs %} {{ flat_attrs }}{% endif %}>
{% for group, options, index in field|optgroups %}
{% if group %}<strong class="font-bold mb-2 block">{{ group }}</strong>{% endif %}
{% for option in options %}
<label class="label cursor-pointer{% if inline_class %} inline-flex{% endif %}">
<span class="label-text">{{ option.label|unlocalize }}</span>
<input type="{{option.type}}" class="{% if option.type == 'radio' %}radio{% else %}checkbox{% endif %}{% if field.errors %} {% if option.type == 'radio' %}radio{% else %}checkbox{% endif %}-error{% endif %}" name="{{ field.html_name }}" value="{{ option.value|unlocalize }}" {% include "crispy-daisyui/layout/attrs.html" with widget=option %}>
</label>
{% if field.errors and forloop.last and not inline_class and forloop.parentloop.last %}
{% include 'crispy-daisyui/layout/field_errors_block.html' %}
{% endif %}
{% endfor %}
{% endfor %}
</div>
{% if field.errors and inline_class %}
{% if field.errors.field_id %}
{# Django 5.2+ #}
<label id="{{field.errors.field_id}}_error" for="{{ field.auto_id }}" class="label">
{% else %}
<label id="{{field.auto_id}}_error" for="{{ field.auto_id }}" class="label">
{% endif %}
{% for error in field.errors %}
<span id="error_{{ forloop.counter }}_{{ field.auto_id }}" class="label-text-alt text-error">{{ error }}</span>
{% endfor %}
</label>
{% endif %}
{% include 'crispy-daisyui/layout/help_text.html' %}
@@ -0,0 +1,16 @@
{% if field.is_hidden %}
{{ field }}
{% else %}
<div id="div_{{ field.auto_id }}" class="{% if wrapper_class %} {{ wrapper_class }}{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}">
{% if field.label %}
<fieldset class="fieldset"{% if field.aria_describedby %} aria-describedby="{{ field.aria_describedby }}"{% endif %}>
<legend for="{{ field.id_for_label }}" class="fieldset-legend {{ label_class }}{% if field.field.required %} requiredField{% endif %}">
{{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}
</legend>
{% endif %}
{% include 'crispy-daisyui/layout/radio_checkbox_select.html' %}
{% if field.label %}</fieldset>{% endif %}
</div>
{% endif %}
@@ -0,0 +1,3 @@
<div {% if div.css_id %}id="{{ div.css_id }}"{% endif %} class="grid grid-cols-12 gap-x-4 {{ div.css_class|default:'' }}" {{ div.flat_attrs }}>
{{ fields|safe }}
</div>

Some files were not shown because too many files have changed in this diff Show More