Compare commits

..

110 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
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
Herculino Trotta
8bb40be41c Merge pull request #388 from eitchtee/weblate
Translations update from Weblate
2025-10-06 09:25:37 -03:00
doody
66c1cf2371 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-06 10:17:42 +00:00
doody
4b23836544 locale(Chinese (Traditional Han script)): update translation
Currently translated at 99.5% (692 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/zh_Hant/
2025-10-06 09:17:42 +00:00
doody
585af1270f locale(Chinese (Traditional Han script)): update translation
Currently translated at 83.7% (582 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/zh_Hant/
2025-10-06 08:17:42 +00:00
doody
a0cc51b2ec locale(Chinese (Traditional Han script)): update translation
Currently translated at 52.2% (363 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/zh_Hant/
2025-10-06 07:17:42 +00:00
Weblate
6a5de7d94d Merge remote-tracking branch 'origin/main' 2025-10-05 20:50:54 +00:00
Herculino Trotta
6d9687de0b Merge pull request #390 from eitchtee/add-mcp-server-mention
Add MCP Server section to README
2025-10-05 17:50:50 -03:00
Herculino Trotta
e9acf1dd8f Add MCP Server section to README
Added information about the MCP Server for self-hosting.

Closes #389
2025-10-05 17:50:03 -03:00
TestXuser
698e05bd06 locale(Ukrainian): update translation
Currently translated at 14.2% (99 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/uk/
2025-10-05 09:17:43 +00:00
doody
90b3778e36 locale(Chinese (Traditional Han script)): update translation
Currently translated at 37.5% (261 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/zh_Hant/
2025-10-05 09:17:42 +00:00
doody
85a773bc01 locale(Chinese (Traditional Han script)): update translation
Currently translated at 18.7% (130 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/zh_Hant/
2025-10-05 08:17:42 +00:00
doody
355016a7a5 locale(Chinese (Traditional Han script)): update translation
Currently translated at 12.9% (90 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/zh_Hant/
2025-10-05 07:17:42 +00:00
doody
f04fcf99b7 locale(Chinese (Traditional Han script)): update translation
Currently translated at 2.1% (15 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/zh_Hant/
2025-10-04 17:17:42 +00:00
doody
0fb389e7e8 locale((Chinese (Traditional Han script))): added translation using Weblate 2025-10-04 16:46:45 +00:00
Herculino Trotta
63898aeef0 Merge pull request #386 from eitchtee/weblate
Translations update from Weblate
2025-09-23 09:18:59 -03:00
sorcierwax
4fdf00d098 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-09-23 06:17:42 +00:00
Herculino Trotta
025cc585d5 Merge pull request #385 from eitchtee/weblate
Translations update from Weblate
2025-09-21 18:30:26 -03:00
Dimitri Decrock
17018d87cd locale(Dutch): update translation
Currently translated at 100.0% (695 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/nl/
2025-09-21 13:17:42 +00:00
Herculino Trotta
1e5f4f6583 Merge pull request #384 from eitchtee/weblate
Translations update from Weblate
2025-09-20 11:55:33 -03:00
Herculino Trotta
a99851cf9b locale((Portuguese)): deleted translation using Weblate 2025-09-20 14:55:08 +00:00
Weblate
9fb1ad4861 Merge remote-tracking branch 'origin/main' 2025-09-20 14:47:54 +00:00
Herculino Trotta
66c3abfe37 Remove PT-BR to PT translation merge step
Removed the step that merges PT-BR translations into PT in the workflow.
2025-09-20 11:47:51 -03:00
Weblate
8ca64f5820 Merge remote-tracking branch 'origin/main' 2025-09-20 14:44:20 +00:00
Herculino Trotta
e743821570 locale(Portuguese (Brazil)): update translation
Currently translated at 100.0% (695 of 695 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/pt_BR/
2025-09-20 14:44:20 +00:00
eitchtee
5c698d8735 chore(locale): update translation files
[skip ci] Automatically generated by Django makemessages workflow
2025-09-20 14:44:17 +00:00
Herculino Trotta
3e5aa90df0 Merge PT-BR translations into PT 2025-09-20 11:42:01 -03:00
Herculino Trotta
b2add14238 Merge pull request #378 from eitchtee/weblate
Translations update from Weblate
2025-09-20 11:36:30 -03:00
Herculino Trotta
a052c00aa8 Merge branch 'main' into weblate 2025-09-20 11:36:20 -03:00
eitchtee
7f343708e0 chore(locale): update translation files
[skip ci] Automatically generated by Django makemessages workflow
2025-09-20 14:08:24 +00:00
Herculino Trotta
22e95c7f4a Merge pull request #383
feat(dca): remove ticks from price chart
2025-09-20 11:06:17 -03:00
Herculino Trotta
7645153f77 feat(dca): remove ticks from price chart 2025-09-20 11:05:56 -03:00
eitchtee
1abfed9abf chore(locale): update translation files
[skip ci] Automatically generated by Django makemessages workflow
2025-09-20 14:05:29 +00:00
Weblate
eea0ab009d Merge remote-tracking branch 'origin/main' 2025-09-20 14:02:44 +00:00
Herculino Trotta
29446def22 Merge pull request #382
feat(networth): add a chart with the currency difference between each month
2025-09-20 11:02:41 -03:00
Herculino Trotta
9dce5e9efe feat(networth): add a chart with the currency difference between each month 2025-09-20 11:02:23 -03:00
Weblate
695e2cb322 Merge remote-tracking branch 'origin/main' 2025-09-20 04:40:47 +00:00
Herculino Trotta
b135ec3b15 Merge pull request #381
fix(login): use full dynamic height
2025-09-20 01:40:44 -03:00
Herculino Trotta
bb3cc5da6c fix(login): use full dynamic height 2025-09-20 01:40:22 -03:00
Phillip Maizza
ca7fe24a8a locale(Italian): update translation
Currently translated at 100.0% (694 of 694 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/it/
2025-09-15 18:17:42 +00:00
Phillip Maizza
483ba74010 locale(Italian): update translation
Currently translated at 100.0% (694 of 694 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/it/
2025-09-15 17:17:42 +00:00
254 changed files with 17291 additions and 18028 deletions

View File

@@ -12,7 +12,7 @@ on:
required: true
type: string
ref:
description: 'Git ref to checkout (branch, tag, or SHA)'
description: 'Git ref to checkout'
required: true
default: 'main'
type: string
@@ -29,73 +29,57 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write # Needed if you switch to GHCR, good practice
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.ref }}
if: github.event_name == 'workflow_dispatch'
- name: Checkout code (non-manual)
uses: actions/checkout@v4
if: github.event_name != 'workflow_dispatch'
ref: ${{ inputs.ref || github.ref }}
- name: Log in to Docker Hub
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
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
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push nightly image
if: github.event_name == 'push'
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: ./docker/prod/django/Dockerfile
push: true
provenance: false
# Pass the calculated tags from the meta step
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
VERSION=nightly
tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:nightly
VERSION=${{ steps.meta.outputs.version }}
platforms: linux/amd64,linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Build and push release image
if: github.event_name == 'release'
uses: docker/build-push-action@v6
with:
context: .
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
# --- CACHE CONFIGURATION ---
# We set a specific 'scope' key.
# This allows the Release tag to see the cache created by the Main branch.
cache-from: type=gha,scope=build-cache
cache-to: type=gha,mode=max,scope=build-cache

4
.gitignore vendored
View File

@@ -123,6 +123,7 @@ celerybeat.pid
# Environments
.env
.prod.env
.venv
env/
venv/
@@ -161,5 +162,6 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/
node_modules/
postgres_data/
.prod.env
.prod.env

8
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,8 @@
{
"djlint.showInstallError": false,
"files.associations": {
"*.css": "tailwindcss"
},
"tailwindCSS.experimental.configFile": "frontend/src/styles/tailwind.css",
"djlint.profile": "django",
}

View File

@@ -13,6 +13,7 @@
<a href="#key-features">Features</a> •
<a href="#how-to-use">Usage</a> •
<a href="#how-it-works">How</a> •
<a href="#mcp-server">MCP Server</a> •
<a href="#help-us-translate-wygiwyh">Translate</a> •
<a href="#caveats-and-warnings">Caveats and Warnings</a> •
<a href="#built-with">Built with</a>
@@ -183,6 +184,10 @@ Check out our [Wiki](https://github.com/eitchtee/WYGIWYH/wiki) for more informat
> [!NOTE]
> Login with your github account
# MCP Server
[IZIme07](https://github.com/IZIme07) has kindly created an MCP Server for WYGIWYH that you can self-host. [Check it out at MCP-WYGIWYH](https://github.com/ReNewator/MCP-WYGIWYH)!
# Caveats and Warnings
- I'm not an accountant, some terms and even calculations might be wrong. Make sure to open an issue if you see anything that could be improved.

View File

@@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/5.1/ref/settings/
"""
import os
import re
import sys
from pathlib import Path
@@ -46,7 +47,7 @@ INSTALLED_APPS = [
"django.contrib.sites",
"whitenoise.runserver_nostatic",
"django.contrib.staticfiles",
"webpack_boilerplate",
"django_vite",
"django.contrib.humanize",
"django.contrib.postgres",
"django_browser_reload",
@@ -128,6 +129,14 @@ STORAGES = {
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"
@@ -289,7 +298,7 @@ STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "static_files"
STATICFILES_DIRS = [
ROOT_DIR / "frontend/build",
ROOT_DIR / "frontend" / "build",
BASE_DIR / "static",
]
@@ -305,9 +314,11 @@ CACHES = {
}
}
WEBPACK_LOADER = {
"MANIFEST_FILE": ROOT_DIR / "frontend/build/manifest.json",
}
DJANGO_VITE_ASSETS_PATH = STATIC_ROOT
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
# 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"
# CRISPY FORMS
CRISPY_ALLOWED_TEMPLATE_PACKS = ["bootstrap5", "crispy_forms/pure_text"]
CRISPY_TEMPLATE_PACK = "bootstrap5"
CRISPY_ALLOWED_TEMPLATE_PACKS = [
"crispy_forms/pure_text",
"crispy-daisyui",
]
CRISPY_TEMPLATE_PACK = "crispy-daisyui"
SESSION_EXPIRE_AT_BROWSER_CLOSE = False
SESSION_COOKIE_AGE = int(os.getenv("SESSION_EXPIRY_TIME", 2678400)) # 31 days

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.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.db.models import Q
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 Meta:
@@ -38,17 +36,13 @@ class AccountGroupForm(forms.ModelForm):
if self.instance and self.instance.pk:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
),
)
else:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
),
)
@@ -108,17 +102,13 @@ class AccountForm(forms.ModelForm):
if self.instance and self.instance.pk:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
),
)
else:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
),
)
@@ -156,9 +146,8 @@ class AccountBalanceForm(forms.Form):
self.helper.layout = Layout(
"new_balance",
Row(
Column("category", css_class="form-group col-md-6 mb-0"),
Column("tags", css_class="form-group col-md-6 mb-0"),
css_class="form-row",
Column("category"),
Column("tags"),
),
Field("account_id"),
)

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.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()
@@ -39,6 +38,7 @@ class SharedObjectForm(forms.Form):
choices=SharedObject.Visibility.choices,
required=True,
label=_("Visibility"),
widget=TomSelect(clear_button=False),
help_text=_(
"Private: Only shown for the owner and shared users. Only editable by the owner."
"<br/>"
@@ -48,9 +48,6 @@ class SharedObjectForm(forms.Form):
class Meta:
fields = ["visibility", "shared_with_users"]
widgets = {
"visibility": TomSelect(clear_button=False),
}
def __init__(self, *args, **kwargs):
# Get the current user to filter available sharing options
@@ -73,12 +70,10 @@ class SharedObjectForm(forms.Form):
self.helper.layout = Layout(
Field("owner"),
Field("visibility"),
HTML("<hr>"),
HTML('<hr class="hr my-3">'),
Field("shared_with_users"),
FormActions(
NoClassSubmit(
"submit", _("Save"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Save"), css_class="btn btn-primary"),
),
)

View File

@@ -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)

View File

@@ -11,7 +11,7 @@ def toast_bg(tags):
elif "warning" in tags:
return "warning"
elif "error" in tags:
return "danger"
return "error"
elif "info" in tags:
return "info"

View File

@@ -0,0 +1,5 @@
from crispy_forms.layout import Field
class Switch(Field):
template = "crispy-daisyui/layout/switch.html"

View File

@@ -1,15 +1,14 @@
import datetime
from django.forms import widgets
from django.utils import formats, translation, dates
from django.utils.translation import gettext_lazy as _
from apps.common.functions.format import get_format
from apps.common.utils.django import (
django_to_python_datetime,
django_to_airdatepicker_datetime,
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):
@@ -52,6 +51,8 @@ class AirDatePickerInput(widgets.DateInput):
def build_attrs(self, base_attrs, extra_attrs=None):
attrs = super().build_attrs(base_attrs, extra_attrs)
attrs["class"] = attrs.get("class", "") + " input"
attrs["data-now-button-txt"] = _("Today")
attrs["data-auto-close"] = str(self.auto_close).lower()
attrs["data-clear-button"] = str(self.clear_button).lower()

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.utils.translation import gettext_lazy as _
@@ -17,7 +17,7 @@ class TomSelect(widgets.Select):
checkboxes=False,
group_by=None,
*args,
**kwargs
**kwargs,
):
super().__init__(attrs, *args, **kwargs)
self.remove_button = remove_button

View File

@@ -1,16 +1,15 @@
from crispy_bootstrap5.bootstrap5 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.daisyui import Switch
from apps.common.widgets.crispy.submit import NoClassSubmit
from apps.common.widgets.datepicker import AirDateTimePickerInput
from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput
from apps.common.widgets.tom_select import TomSelect
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):
@@ -51,17 +50,13 @@ class CurrencyForm(forms.ModelForm):
if self.instance and self.instance.pk:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
),
)
else:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
),
)
@@ -89,17 +84,13 @@ class ExchangeRateForm(forms.ModelForm):
if self.instance and self.instance.pk:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
),
)
else:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
),
)
@@ -132,8 +123,8 @@ class ExchangeRateServiceForm(forms.ModelForm):
Switch("singleton"),
"api_key",
Row(
Column("interval_type", css_class="form-group col-md-6"),
Column("fetch_interval", css_class="form-group col-md-6"),
Column("interval_type"),
Column("fetch_interval"),
),
"target_currencies",
"target_accounts",
@@ -142,16 +133,12 @@ class ExchangeRateServiceForm(forms.ModelForm):
if self.instance and self.instance.pk:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
),
)
else:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
),
)

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.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 (
DynamicModelChoiceField,
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):
@@ -36,8 +34,8 @@ class DCAStrategyForm(forms.ModelForm):
self.helper.layout = Layout(
"name",
Row(
Column("payment_currency", css_class="form-group col-md-6"),
Column("target_currency", css_class="form-group col-md-6"),
Column("payment_currency"),
Column("target_currency"),
),
"notes",
)
@@ -45,17 +43,13 @@ class DCAStrategyForm(forms.ModelForm):
if self.instance and self.instance.pk:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
),
)
else:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
),
)
@@ -155,11 +149,11 @@ class DCAEntryForm(forms.ModelForm):
self.helper.layout = Layout(
"date",
Row(
Column("amount_paid", css_class="form-group col-md-6"),
Column("amount_received", css_class="form-group col-md-6"),
Column("amount_paid"),
Column("amount_received"),
),
"notes",
BS5Accordion(
Accordion(
AccordionGroup(
_("Create transaction"),
Switch("create_transaction"),
@@ -168,19 +162,11 @@ class DCAEntryForm(forms.ModelForm):
Row(
Column(
"from_account",
css_class="form-group",
),
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",
Column("from_category"),
Column("from_tags"),
),
),
css_class="p-1 mx-1 my-3 border rounded-3",
@@ -192,14 +178,10 @@ class DCAEntryForm(forms.ModelForm):
"to_account",
css_class="form-group",
),
css_class="form-row",
),
Row(
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",
Column("to_category"),
Column("to_tags"),
),
),
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:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
),
)
else:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
),
)

View File

@@ -1,11 +1,10 @@
from apps.common.widgets.crispy.submit import NoClassSubmit
from crispy_forms.bootstrap import FormActions
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.utils.translation import gettext_lazy as _
from apps.common.widgets.crispy.submit import NoClassSubmit
class ExportForm(forms.Form):
users = forms.BooleanField(
@@ -115,9 +114,7 @@ class ExportForm(forms.Form):
"dca",
"import_profiles",
FormActions(
NoClassSubmit(
"submit", _("Export"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Export"), css_class="btn btn-primary"),
),
)
@@ -162,7 +159,7 @@ class RestoreForm(forms.Form):
self.helper.form_method = "post"
self.helper.layout = Layout(
"zip_file",
HTML("<hr />"),
HTML('<hr class="hr my-3"/>'),
"users",
"accounts",
"currencies",
@@ -181,9 +178,7 @@ class RestoreForm(forms.Form):
"dca_entries",
"import_profiles",
FormActions(
NoClassSubmit(
"submit", _("Restore"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Restore"), css_class="btn btn-primary"),
),
)

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.helper import FormHelper
from crispy_forms.layout import (
@@ -6,9 +8,6 @@ from crispy_forms.layout import (
from django import forms
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 Meta:
@@ -30,17 +29,13 @@ class ImportProfileForm(forms.ModelForm):
if self.instance and self.instance.pk:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
),
)
else:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
),
)
@@ -57,8 +52,6 @@ class ImportRunFileUploadForm(forms.Form):
self.helper.layout = Layout(
"file",
FormActions(
NoClassSubmit(
"submit", _("Import"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Import"), css_class="btn btn-primary"),
),
)

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 (
AirDatePickerInput,
AirMonthYearPickerInput,
AirYearPickerInput,
AirDatePickerInput,
)
from apps.transactions.models import TransactionCategory
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):
@@ -59,8 +58,8 @@ class MonthRangeForm(forms.Form):
self.helper.layout = Layout(
Row(
Column("month_from", css_class="form-group col-md-6"),
Column("month_to", css_class="form-group col-md-6"),
Column("month_from"),
Column("month_to"),
),
)
@@ -82,8 +81,8 @@ class YearRangeForm(forms.Form):
self.helper.layout = Layout(
Row(
Column("year_from", css_class="form-group col-md-6"),
Column("year_to", css_class="form-group col-md-6"),
Column("year_from"),
Column("year_to"),
),
)
@@ -105,8 +104,8 @@ class DateRangeForm(forms.Form):
self.helper.layout = Layout(
Row(
Column("date_from", css_class="form-group col-md-6"),
Column("date_to", css_class="form-group col-md-6"),
Column("date_from"),
Column("date_to"),
css_class="mb-0",
),
)

View File

@@ -182,3 +182,29 @@ def calculate_historical_account_balance(queryset):
historical_account_balance[date_filter(end_date, "b Y")] = month_data
return historical_account_balance
def calculate_monthly_net_worth_difference(historical_net_worth):
diff_dict = OrderedDict()
if not historical_net_worth:
return diff_dict
# Get all currencies
currencies = set()
for data in historical_net_worth.values():
currencies.update(data.keys())
# Initialize prev_values for all currencies
prev_values = {currency: Decimal("0.00") for currency in currencies}
for month, values in historical_net_worth.items():
diff_values = {}
for currency in sorted(list(currencies)):
current_val = values.get(currency, Decimal("0.00"))
prev_val = prev_values.get(currency, Decimal("0.00"))
diff_values[currency] = current_val - prev_val
diff_dict[month] = diff_values
prev_values = values.copy()
return diff_dict

View File

@@ -8,6 +8,7 @@ from django.views.decorators.http import require_http_methods
from apps.net_worth.utils.calculate_net_worth import (
calculate_historical_currency_net_worth,
calculate_historical_account_balance,
calculate_monthly_net_worth_difference,
)
from apps.transactions.models import Transaction
from apps.transactions.utils.calculations import (
@@ -96,6 +97,38 @@ def net_worth(request):
chart_data_currency_json = json.dumps(chart_data_currency, cls=DjangoJSONEncoder)
monthly_difference_data = calculate_monthly_net_worth_difference(
historical_net_worth=historical_currency_net_worth
)
diff_labels = (
list(monthly_difference_data.keys()) if monthly_difference_data else []
)
diff_currencies = (
list(monthly_difference_data[diff_labels[0]].keys())
if monthly_difference_data and diff_labels
else []
)
diff_datasets = []
for i, currency in enumerate(diff_currencies):
data = [
float(month_data.get(currency, 0))
for month_data in monthly_difference_data.values()
]
diff_datasets.append(
{
"label": currency,
"data": data,
"borderWidth": 3,
}
)
chart_data_monthly_difference = {"labels": diff_labels, "datasets": diff_datasets}
chart_data_monthly_difference_json = json.dumps(
chart_data_monthly_difference, cls=DjangoJSONEncoder
)
historical_account_balance = calculate_historical_account_balance(
queryset=transactions_account_queryset
)
@@ -140,6 +173,7 @@ def net_worth(request):
"chart_data_accounts_json": chart_data_accounts_json,
"accounts": accounts,
"type": view_type,
"chart_data_monthly_difference_json": chart_data_monthly_difference_json,
},
)

View File

@@ -1,20 +1,21 @@
from crispy_bootstrap5.bootstrap5 import Switch, BS5Accordion
from crispy_forms.bootstrap import FormActions, AccordionGroup
from apps.common.fields.forms.dynamic_select import DynamicModelChoiceField
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.layout import Layout, Field, Row, Column, HTML
from crispy_forms.layout import HTML, Column, Field, Layout, Row
from django import forms
from django.core.exceptions import ValidationError
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 Meta:
@@ -53,17 +54,13 @@ class TransactionRuleForm(forms.ModelForm):
if self.instance and self.instance.pk:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
),
)
else:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
),
)
@@ -97,17 +94,13 @@ class TransactionRuleActionForm(forms.ModelForm):
if self.instance and self.instance.pk:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
),
)
else:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
),
)
@@ -214,148 +207,148 @@ class UpdateOrCreateTransactionRuleActionForm(forms.ModelForm):
self.helper.layout = Layout(
"order",
BS5Accordion(
Accordion(
AccordionGroup(
_("Search Criteria"),
Field("filter", rows=1),
Row(
Column(
Field("search_type_operator"),
css_class="form-group col-md-4",
css_class="col-span-12 md:col-span-4",
),
Column(
Field("search_type", rows=1),
css_class="form-group col-md-8",
css_class="col-span-12 md:col-span-8",
),
),
Row(
Column(
Field("search_is_paid_operator"),
css_class="form-group col-md-4",
css_class="col-span-12 md:col-span-4",
),
Column(
Field("search_is_paid", rows=1),
css_class="form-group col-md-8",
css_class="col-span-12 md:col-span-8",
),
),
Row(
Column(
Field("search_mute_operator"),
css_class="form-group col-md-4",
css_class="col-span-12 md:col-span-4",
),
Column(
Field("search_mute", rows=1),
css_class="form-group col-md-8",
css_class="col-span-12 md:col-span-8",
),
),
Row(
Column(
Field("search_account_operator"),
css_class="form-group col-md-4",
css_class="col-span-12 md:col-span-4",
),
Column(
Field("search_account", rows=1),
css_class="form-group col-md-8",
css_class="col-span-12 md:col-span-8",
),
),
Row(
Column(
Field("search_entities_operator"),
css_class="form-group col-md-4",
css_class="col-span-12 md:col-span-4",
),
Column(
Field("search_entities", rows=1),
css_class="form-group col-md-8",
css_class="col-span-12 md:col-span-8",
),
),
Row(
Column(
Field("search_date_operator"),
css_class="form-group col-md-4",
css_class="col-span-12 md:col-span-4",
),
Column(
Field("search_date", rows=1),
css_class="form-group col-md-8",
css_class="col-span-12 md:col-span-8",
),
),
Row(
Column(
Field("search_reference_date_operator"),
css_class="form-group col-md-4",
css_class="col-span-12 md:col-span-4",
),
Column(
Field("search_reference_date", rows=1),
css_class="form-group col-md-8",
css_class="col-span-12 md:col-span-8",
),
),
Row(
Column(
Field("search_description_operator"),
css_class="form-group col-md-4",
css_class="col-span-12 md:col-span-4",
),
Column(
Field("search_description", rows=1),
css_class="form-group col-md-8",
css_class="col-span-12 md:col-span-8",
),
),
Row(
Column(
Field("search_amount_operator"),
css_class="form-group col-md-4",
css_class="col-span-12 md:col-span-4",
),
Column(
Field("search_amount", rows=1),
css_class="form-group col-md-8",
css_class="col-span-12 md:col-span-8",
),
),
Row(
Column(
Field("search_category_operator"),
css_class="form-group col-md-4",
css_class="col-span-12 md:col-span-4",
),
Column(
Field("search_category", rows=1),
css_class="form-group col-md-8",
css_class="col-span-12 md:col-span-8",
),
),
Row(
Column(
Field("search_tags_operator"),
css_class="form-group col-md-4",
css_class="col-span-12 md:col-span-4",
),
Column(
Field("search_tags", rows=1),
css_class="form-group col-md-8",
css_class="col-span-12 md:col-span-8",
),
),
Row(
Column(
Field("search_notes_operator"),
css_class="form-group col-md-4",
css_class="col-span-12 md:col-span-4",
),
Column(
Field("search_notes", rows=1),
css_class="form-group col-md-8",
css_class="col-span-12 md:col-span-8",
),
),
Row(
Column(
Field("search_internal_note_operator"),
css_class="form-group col-md-4",
css_class="col-span-12 md:col-span-4",
),
Column(
Field("search_internal_note", rows=1),
css_class="form-group col-md-8",
css_class="col-span-12 md:col-span-8",
),
),
Row(
Column(
Field("search_internal_id_operator"),
css_class="form-group col-md-4",
css_class="col-span-12 md:col-span-4",
),
Column(
Field("search_internal_id", rows=1),
css_class="form-group col-md-8",
css_class="col-span-12 md:col-span-8",
),
),
active=True,
@@ -386,17 +379,13 @@ class UpdateOrCreateTransactionRuleActionForm(forms.ModelForm):
if self.instance and self.instance.pk:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
),
)
else:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
),
)
@@ -427,9 +416,7 @@ class DryRunCreatedTransacion(forms.Form):
self.helper.layout = Layout(
"transaction",
FormActions(
NoClassSubmit(
"submit", _("Test"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Test"), css_class="btn btn-primary"),
),
)
@@ -464,9 +451,7 @@ class DryRunDeletedTransacion(forms.Form):
self.helper.layout = Layout(
"transaction",
FormActions(
NoClassSubmit(
"submit", _("Test"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Test"), css_class="btn btn-primary"),
),
)
@@ -496,13 +481,11 @@ class DryRunUpdatedTransactionForm(BulkEditTransactionForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
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
self.helper.layout[-1] = FormActions(
NoClassSubmit(
"submit", _("Test"), css_class="btn btn-outline-primary w-100"
)
NoClassSubmit("submit", _("Test"), css_class="btn btn-primary")
)
if self.data.get("transaction"):

View File

@@ -564,7 +564,7 @@ def dry_run_rule_updated(request, pk):
response = render(
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},
)

View File

@@ -1,11 +1,4 @@
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.common.fields.month_year import MonthYearFormField
from apps.common.widgets.datepicker import AirDatePickerInput
@@ -15,9 +8,15 @@ from apps.currencies.models import Currency
from apps.transactions.models import (
Transaction,
TransactionCategory,
TransactionTag,
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 = (
("1", _("Paid")),
@@ -159,14 +158,12 @@ class TransactionsFilter(django_filters.FilterSet):
Field("description"),
Row(Column("date_start"), Column("date_end")),
Row(
Column("reference_date_start", css_class="form-group col-md-6 mb-0"),
Column("reference_date_end", css_class="form-group col-md-6 mb-0"),
css_class="form-row",
Column("reference_date_start"),
Column("reference_date_end"),
),
Row(
Column("from_amount", css_class="form-group col-md-6 mb-0"),
Column("to_amount", css_class="form-group col-md-6 mb-0"),
css_class="form-row",
Column("from_amount"),
Column("to_amount"),
),
Field("account", size=1),
Field("currency", size=1),

View File

@@ -1,39 +1,38 @@
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.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.datepicker import AirDatePickerInput, AirMonthYearPickerInput
from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput
from apps.common.widgets.tom_select import TomSelect
from apps.rules.signals import transaction_created, transaction_updated
from apps.transactions.models import (
InstallmentPlan,
QuickTransaction,
RecurringTransaction,
Transaction,
TransactionCategory,
TransactionTag,
InstallmentPlan,
RecurringTransaction,
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):
@@ -134,21 +133,18 @@ class TransactionForm(forms.ModelForm):
),
Field("is_paid", template="transactions/widgets/paid_toggle_button.html"),
Row(
Column("account", css_class="form-group col-md-6 mb-0"),
Column("entities", css_class="form-group col-md-6 mb-0"),
css_class="form-row",
Column("account"),
Column("entities"),
),
Row(
Column(Field("date"), css_class="form-group col-md-6 mb-0"),
Column(Field("reference_date"), css_class="form-group col-md-6 mb-0"),
css_class="form-row",
Column(Field("date")),
Column(Field("reference_date")),
),
"description",
Field("amount", inputmode="decimal"),
Row(
Column("category", css_class="form-group col-md-6 mb-0"),
Column("tags", css_class="form-group col-md-6 mb-0"),
css_class="form-row",
Column("category"),
Column("tags"),
),
"notes",
)
@@ -164,20 +160,18 @@ class TransactionForm(forms.ModelForm):
Field("is_paid", template="transactions/widgets/paid_toggle_button.html"),
"account",
Row(
Column(Field("date"), css_class="form-group col-md-6 mb-0"),
Column(Field("reference_date"), css_class="form-group col-md-6 mb-0"),
css_class="form-row",
Column(Field("date")),
Column(Field("reference_date")),
),
"description",
Field("amount", inputmode="decimal"),
BS5Accordion(
Accordion(
AccordionGroup(
_("More"),
"entities",
Row(
Column("category", css_class="form-group col-md-6 mb-0"),
Column("tags", css_class="form-group col-md-6 mb-0"),
css_class="form-row",
Column("category"),
Column("tags"),
),
"notes",
active=False,
@@ -187,9 +181,7 @@ class TransactionForm(forms.ModelForm):
css_class="mb-3",
),
FormActions(
NoClassSubmit(
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
),
)
@@ -202,29 +194,25 @@ class TransactionForm(forms.ModelForm):
)
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
),
)
else:
self.fields["amount"].widget = ArbitraryDecimalDisplayNumberInput()
self.helper.layout.append(
Div(
NoClassSubmit(
"submit", _("Add"), css_class="btn btn-outline-primary"
),
NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
NoClassSubmit(
"submit_and_similar",
_("Save and add similar"),
css_class="btn btn-outline-primary",
css_class="btn btn-primary btn-soft",
),
NoClassSubmit(
"submit_and_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"),
"name",
HTML("<hr />"),
HTML('<hr class="hr my-3" />'),
Row(
Column("account", css_class="form-group col-md-6 mb-0"),
Column("entities", css_class="form-group col-md-6 mb-0"),
css_class="form-row",
Column("account"),
Column("entities"),
),
"description",
Field("amount", inputmode="decimal"),
Row(
Column("category", css_class="form-group col-md-6 mb-0"),
Column("tags", css_class="form-group col-md-6 mb-0"),
css_class="form-row",
Column("category"),
Column("tags"),
),
"notes",
Switch("mute"),
@@ -372,19 +358,14 @@ class QuickTransactionForm(forms.ModelForm):
)
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
),
)
else:
self.fields["amount"].widget = ArbitraryDecimalDisplayNumberInput()
self.helper.layout.append(
Div(
NoClassSubmit(
"submit", _("Add"), css_class="btn btn-outline-primary"
),
css_class="d-grid gap-2",
FormActions(
NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
),
)
@@ -481,27 +462,22 @@ class BulkEditTransactionForm(forms.Form):
template="transactions/widgets/unselectable_paid_toggle_button.html",
),
Row(
Column("account", css_class="form-group col-md-6 mb-0"),
Column("entities", css_class="form-group col-md-6 mb-0"),
css_class="form-row",
Column("account"),
Column("entities"),
),
Row(
Column(Field("date"), css_class="form-group col-md-6 mb-0"),
Column(Field("reference_date"), css_class="form-group col-md-6 mb-0"),
css_class="form-row",
Column(Field("date")),
Column(Field("reference_date")),
),
"description",
Field("amount", inputmode="decimal"),
Row(
Column("category", css_class="form-group col-md-6 mb-0"),
Column("tags", css_class="form-group col-md-6 mb-0"),
css_class="form-row",
Column("category"),
Column("tags"),
),
"notes",
FormActions(
NoClassSubmit(
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
),
)
@@ -600,62 +576,34 @@ class TransferForm(forms.Form):
self.helper.layout = Layout(
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",
),
css_class="form-row",
),
Field("description"),
Field("notes"),
Switch("mute"),
Row(
Column(
Row(
Column(
"from_account",
css_class="form-group col-md-6 mb-0",
),
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",
Column("from_account"),
Column(Field("from_amount")),
Column("from_category"),
Column("from_tags"),
css_class="bg-base-100 rounded-box p-4 border-base-content/60 border my-3",
),
Row(
Column(
Row(
Column(
"to_account",
css_class="form-group col-md-6 mb-0",
),
Column(
Field("to_amount"),
css_class="form-group col-md-6 mb-0",
),
css_class="form-row",
),
Row(
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",
),
"to_account",
),
css_class="p-1 mx-1 my-3 border rounded-3",
Column(
Field("to_amount"),
),
Column("to_category"),
Column("to_tags"),
css_class="bg-base-100 rounded-box p-4 border-base-content/60 border",
),
FormActions(
NoClassSubmit(
"submit", _("Transfer"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Transfer"), css_class="btn btn-primary"),
),
)
@@ -841,30 +789,26 @@ class InstallmentPlanForm(forms.ModelForm):
template="transactions/widgets/income_expense_toggle_buttons.html",
),
Row(
Column("account", css_class="form-group col-md-6 mb-0"),
Column("entities", css_class="form-group col-md-6 mb-0"),
css_class="form-row",
Column("account"),
Column("entities"),
),
"description",
Switch("add_description_to_transaction"),
"notes",
Switch("add_notes_to_transaction"),
Row(
Column("number_of_installments", css_class="form-group col-md-6 mb-0"),
Column("installment_start", css_class="form-group col-md-6 mb-0"),
css_class="form-row",
Column("number_of_installments"),
Column("installment_start"),
),
Row(
Column("start_date", css_class="form-group col-md-4 mb-0"),
Column("reference_date", css_class="form-group col-md-4 mb-0"),
Column("recurrence", css_class="form-group col-md-4 mb-0"),
css_class="form-row",
Column("start_date", css_class="col-span-12 md:col-span-4"),
Column("reference_date", css_class="col-span-12 md:col-span-4"),
Column("recurrence", css_class="col-span-12 md:col-span-4"),
),
"installment_amount",
Row(
Column("category", css_class="form-group col-md-6 mb-0"),
Column("tags", css_class="form-group col-md-6 mb-0"),
css_class="form-row",
Column("category"),
Column("tags"),
),
)
@@ -874,17 +818,13 @@ class InstallmentPlanForm(forms.ModelForm):
if self.instance and self.instance.pk:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
),
)
else:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
),
)
@@ -917,17 +857,13 @@ class TransactionTagForm(forms.ModelForm):
if self.instance and self.instance.pk:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
),
)
else:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
),
)
@@ -949,17 +885,13 @@ class TransactionEntityForm(forms.ModelForm):
if self.instance and self.instance.pk:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
),
)
else:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
),
)
@@ -984,17 +916,13 @@ class TransactionCategoryForm(forms.ModelForm):
if self.instance and self.instance.pk:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
),
)
else:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
),
)
@@ -1103,30 +1031,26 @@ class RecurringTransactionForm(forms.ModelForm):
template="transactions/widgets/income_expense_toggle_buttons.html",
),
Row(
Column("account", css_class="form-group col-md-6 mb-0"),
Column("entities", css_class="form-group col-md-6 mb-0"),
css_class="form-row",
Column("account"),
Column("entities"),
),
"description",
Switch("add_description_to_transaction"),
"amount",
Row(
Column("category", css_class="form-group col-md-6 mb-0"),
Column("tags", css_class="form-group col-md-6 mb-0"),
css_class="form-row",
Column("category"),
Column("tags"),
),
"notes",
Switch("add_notes_to_transaction"),
Row(
Column("start_date", css_class="form-group col-md-6 mb-0"),
Column("reference_date", css_class="form-group col-md-6 mb-0"),
css_class="form-row",
Column("start_date"),
Column("reference_date"),
),
Row(
Column("recurrence_interval", css_class="form-group col-md-4 mb-0"),
Column("recurrence_type", css_class="form-group col-md-4 mb-0"),
Column("end_date", css_class="form-group col-md-4 mb-0"),
css_class="form-row",
Column("recurrence_interval", css_class="col-span-12 md:col-span-4"),
Column("recurrence_type", css_class="col-span-12 md:col-span-4"),
Column("end_date", css_class="col-span-12 md:col-span-4"),
),
AppendedText("keep_at_most", _("future transactions")),
)
@@ -1138,17 +1062,13 @@ class RecurringTransactionForm(forms.ModelForm):
if self.instance and self.instance.pk:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
),
)
else:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
),
)

View File

@@ -2,29 +2,29 @@ import decimal
import logging
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 django.conf import settings
from django.core.validators import MinValueValidator
from django.db import models, transaction
from django.db.models import Q
from django.dispatch import Signal
from django.forms import ValidationError
from django.template.defaultfilters import date
from django.utils import timezone
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()
@@ -381,21 +381,32 @@ class Transaction(OwnedObject):
db_table = "transactions"
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)):
self.amount = decimal.Decimal(str(self.amount))
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:
self.reference_date = self.reference_date.replace(day=1)
elif not self.reference_date and self.date:
self.reference_date = self.date.replace(day=1)
super().clean_fields(*args, **kwargs)
def save(self, *args, **kwargs):
# 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.

View File

@@ -3,7 +3,6 @@ from decimal import Decimal
from django import template
from django.utils.formats import number_format
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
)
if amount < 0:
return "-", prefix, formatted_amount, suffix
return f"-{prefix}{formatted_amount}{suffix}"
else:
return "", prefix, formatted_amount, suffix
return f"{prefix}{formatted_amount}{suffix}"
else:
return "ERR"
return "", "", "ERR", ""
@register.simple_tag(name="currency_display")
def currency_display(amount, prefix, suffix, decimal_places):
return _format_string(prefix, amount, decimal_places, suffix)
def currency_display(amount, prefix, suffix, decimal_places, string=False):
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,
}

View File

@@ -137,6 +137,7 @@ def quick_transaction_add_as_transaction(request, quick_transaction_id):
"category",
"tags",
"entities",
"internal_id",
],
)
@@ -206,6 +207,7 @@ def quick_transaction_add_as_quick_transaction(request, transaction_id):
"recurring_transaction",
"deleted",
"deleted_at",
"internal_id",
],
)

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 (
FormActions,
)
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.contrib.auth import get_user_model
from django.contrib.auth.forms import (
UsernameField,
AuthenticationForm,
UserCreationForm,
UsernameField,
)
from django.db import transaction
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):
username = UsernameField(
label=_("E-mail"),
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(
label=_("Password"),
strip=False,
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(
"username",
"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 />"),
"volume",
FormActions(
NoClassSubmit(
"submit", _("Save"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Save"), css_class="btn btn-primary"),
),
)
@@ -191,8 +197,8 @@ class UserUpdateForm(forms.ModelForm):
# Define the layout using Crispy Forms, including the new fields
self.helper.layout = Layout(
Row(
Column("first_name", css_class="form-group col-md-6"),
Column("last_name", css_class="form-group col-md-6"),
Column("first_name"),
Column("last_name"),
css_class="row",
),
Field("email"),
@@ -213,17 +219,13 @@ class UserUpdateForm(forms.ModelForm):
if self.instance and self.instance.pk:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
),
)
else:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
),
)
@@ -354,8 +356,8 @@ class UserAddForm(UserCreationForm):
self.helper.layout = Layout(
Field("email"),
Row(
Column("first_name", css_class="form-group col-md-6"),
Column("last_name", css_class="form-group col-md-6"),
Column("first_name"),
Column("last_name"),
css_class="row",
),
# UserCreationForm provides 'password1' and 'password2' fields
@@ -375,17 +377,13 @@ class UserAddForm(UserCreationForm):
if self.instance and self.instance.pk:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
),
)
else:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
),
)

View File

@@ -18,10 +18,15 @@ urlpatterns = [
name="toggle_sound_playing",
),
path(
"user/toggle-sidebar/",
"user/session/toggle-sidebar/",
views.toggle_sidebar_status,
name="toggle_sidebar_status",
),
path(
"user/session/toggle-theme/",
views.toggle_theme,
name="toggle_theme",
),
path(
"user/settings/",
views.update_settings,

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.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.views import (
LoginView,
)
from django.core.exceptions import PermissionDenied
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.utils.translation import gettext_lazy as _
from django.views.decorators.http import require_http_methods
from apps.common.decorators.htmx import only_htmx
from apps.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):
logout(request)
@@ -118,6 +117,7 @@ def update_settings(request):
@only_htmx
@htmx_login_required
@require_http_methods(["GET"])
def toggle_sidebar_status(request):
if not request.session.get("sidebar_status"):
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
@is_superuser
@require_http_methods(["GET"])

View File

@@ -7,9 +7,9 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-14 04:57+0000\n"
"PO-Revision-Date: 2025-07-22 06:17+0000\n"
"Last-Translator: seraphblade2010 <marc.butenhoff@web.de>\n"
"POT-Creation-Date: 2025-09-20 14:08+0000\n"
"PO-Revision-Date: 2025-11-01 01:17+0000\n"
"Last-Translator: mlystopad <mlystopadt@gmail.com>\n"
"Language-Team: German <https://translations.herculino.com/projects/wygiwyh/"
"app/de/>\n"
"Language: de\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\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
msgid "Group name"
@@ -1197,7 +1197,7 @@ msgstr "Interne ID"
#: apps/transactions/models.py:216 apps/transactions/models.py:307
#: apps/transactions/models.py:994
msgid "Mute"
msgstr "Deaktivieren"
msgstr "Stummschalten"
#: apps/rules/forms.py:219
msgid "Search Criteria"
@@ -1358,7 +1358,7 @@ msgstr ""
#: apps/transactions/filters.py:24 templates/cotton/transaction/item.html:21
#: templates/cotton/transaction/item.html:32 templates/includes/navbar.html:47
#: templates/insights/fragments/category_overview/index.html:61
#: templates/net_worth/net_worth.html:32
#: templates/net_worth/net_worth.html:33
#: templates/transactions/widgets/paid_toggle_button.html:8
#: templates/transactions/widgets/unselectable_paid_toggle_button.html:12
msgid "Projected"
@@ -2235,6 +2235,8 @@ msgid "Current balance"
msgstr "Aktueller Saldo"
#: templates/accounts/fragments/account_reconciliation.html:39
#: templates/net_worth/net_worth.html:105
#: templates/net_worth/net_worth.html:362
msgid "Difference"
msgstr "Differenz"
@@ -2612,19 +2614,19 @@ msgstr "Aktueller Preis"
msgid "Amount Bought"
msgstr "Anzahl gekauft"
#: templates/dca/fragments/strategy/details.html:390
#: templates/dca/fragments/strategy/details.html:392
msgid "Entry Price vs Current Price"
msgstr "Einstiegspreis zu Aktuellem Preis"
#: templates/dca/fragments/strategy/details.html:406
#: templates/dca/fragments/strategy/details.html:408
msgid "Days Between Investments"
msgstr "Tage zwischen Investitionen"
#: templates/dca/fragments/strategy/details.html:453
#: templates/dca/fragments/strategy/details.html:455
msgid "Investment Frequency"
msgstr "Investitions-Häufigkeit"
#: templates/dca/fragments/strategy/details.html:455
#: templates/dca/fragments/strategy/details.html:457
msgid "The straighter the blue line, the more consistent your DCA strategy is."
msgstr ""
"Je gerader die blaue Linie, desto gleichmäßiger ist deine DCA-Strategie."
@@ -2827,7 +2829,7 @@ msgstr "Nettovermögen"
#: templates/includes/navbar.html:45
#: templates/insights/fragments/category_overview/index.html:65
#: templates/net_worth/net_worth.html:22
#: templates/net_worth/net_worth.html:23
msgid "Current"
msgstr "Aktuell"
@@ -2879,7 +2881,7 @@ msgstr "Nur benutzen, wenn du weißt was du tust"
#: templates/includes/navbar.html:158 templates/includes/sidebar.html:261
msgid "Django Admin"
msgstr "Django Admin"
msgstr "Django AdministratorIn"
#: templates/includes/navbar.html:169 templates/includes/sidebar.html:276
msgid "is available"
@@ -3228,25 +3230,31 @@ msgstr "Transaktionen filtern"
msgid "Order by"
msgstr "Sortieren nach"
#: templates/net_worth/net_worth.html:40
#: templates/net_worth/net_worth.html:42
#: templates/yearly_overview/pages/overview_by_currency.html:9
msgid "By currency"
msgstr "Nach Währung"
#: templates/net_worth/net_worth.html:75
#: templates/net_worth/net_worth.html:78
msgid "Consolidated"
msgstr "Zusammengefasst"
#: templates/net_worth/net_worth.html:104
#: templates/net_worth/net_worth.html:101
#, fuzzy
#| msgid "Evolution by account"
msgid "Evolution"
msgstr "Verlauf nach Konto"
#: templates/net_worth/net_worth.html:128
#: templates/yearly_overview/pages/overview_by_account.html:7
msgid "By account"
msgstr "Nach Konto"
#: templates/net_worth/net_worth.html:211
#: templates/net_worth/net_worth.html:236
msgid "Evolution by currency"
msgstr "Verlauf nach Währung"
#: templates/net_worth/net_worth.html:275
#: templates/net_worth/net_worth.html:300
msgid "Evolution by account"
msgstr "Verlauf nach Konto"
@@ -3267,10 +3275,8 @@ msgid "Edit quick transaction"
msgstr "Transaktion bearbeiten"
#: templates/quick_transactions/fragments/list.html:40
#, fuzzy
#| msgid "Yes, delete them!"
msgid "This will delete this item"
msgstr "Ja, löschen!"
msgstr "Dieser Artikel wird gelöscht"
#: templates/recurring_transactions/fragments/add.html:5
msgid "Add recurring transaction"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-14 04:57+0000\n"
"POT-Creation-Date: 2025-09-20 14:08+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -1317,7 +1317,7 @@ msgstr ""
#: apps/transactions/filters.py:24 templates/cotton/transaction/item.html:21
#: templates/cotton/transaction/item.html:32 templates/includes/navbar.html:47
#: templates/insights/fragments/category_overview/index.html:61
#: templates/net_worth/net_worth.html:32
#: templates/net_worth/net_worth.html:33
#: templates/transactions/widgets/paid_toggle_button.html:8
#: templates/transactions/widgets/unselectable_paid_toggle_button.html:12
msgid "Projected"
@@ -2168,6 +2168,8 @@ msgid "Current balance"
msgstr ""
#: templates/accounts/fragments/account_reconciliation.html:39
#: templates/net_worth/net_worth.html:105
#: templates/net_worth/net_worth.html:362
msgid "Difference"
msgstr ""
@@ -2543,19 +2545,19 @@ msgstr ""
msgid "Amount Bought"
msgstr ""
#: templates/dca/fragments/strategy/details.html:390
#: templates/dca/fragments/strategy/details.html:392
msgid "Entry Price vs Current Price"
msgstr ""
#: templates/dca/fragments/strategy/details.html:406
#: templates/dca/fragments/strategy/details.html:408
msgid "Days Between Investments"
msgstr ""
#: templates/dca/fragments/strategy/details.html:453
#: templates/dca/fragments/strategy/details.html:455
msgid "Investment Frequency"
msgstr ""
#: templates/dca/fragments/strategy/details.html:455
#: templates/dca/fragments/strategy/details.html:457
msgid "The straighter the blue line, the more consistent your DCA strategy is."
msgstr ""
@@ -2755,7 +2757,7 @@ msgstr ""
#: templates/includes/navbar.html:45
#: templates/insights/fragments/category_overview/index.html:65
#: templates/net_worth/net_worth.html:22
#: templates/net_worth/net_worth.html:23
msgid "Current"
msgstr ""
@@ -3138,25 +3140,29 @@ msgstr ""
msgid "Order by"
msgstr ""
#: templates/net_worth/net_worth.html:40
#: templates/net_worth/net_worth.html:42
#: templates/yearly_overview/pages/overview_by_currency.html:9
msgid "By currency"
msgstr ""
#: templates/net_worth/net_worth.html:75
#: templates/net_worth/net_worth.html:78
msgid "Consolidated"
msgstr ""
#: templates/net_worth/net_worth.html:104
#: templates/net_worth/net_worth.html:101
msgid "Evolution"
msgstr ""
#: templates/net_worth/net_worth.html:128
#: templates/yearly_overview/pages/overview_by_account.html:7
msgid "By account"
msgstr ""
#: templates/net_worth/net_worth.html:211
#: templates/net_worth/net_worth.html:236
msgid "Evolution by currency"
msgstr ""
#: templates/net_worth/net_worth.html:275
#: templates/net_worth/net_worth.html:300
msgid "Evolution by account"
msgstr ""

File diff suppressed because it is too large Load Diff

View File

@@ -7,9 +7,9 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-14 04:57+0000\n"
"PO-Revision-Date: 2025-09-08 06:17+0000\n"
"Last-Translator: sorcierwax <freakywax@gmail.com>\n"
"POT-Creation-Date: 2025-09-20 14:08+0000\n"
"PO-Revision-Date: 2025-10-07 20:17+0000\n"
"Last-Translator: Erwan Colin <zephone@protonmail.com>\n"
"Language-Team: French <https://translations.herculino.com/projects/wygiwyh/"
"app/fr/>\n"
"Language: fr\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 5.13\n"
"X-Generator: Weblate 5.13.3\n"
#: apps/accounts/forms.py:26
msgid "Group name"
@@ -1066,7 +1066,7 @@ msgstr "Revenus à date"
#: templates/insights/fragments/category_explorer/charts/account.html:66
#: templates/insights/fragments/category_explorer/charts/currency.html:66
msgid "Current Expenses"
msgstr "Dépenses à date"
msgstr "Dépenses actuelles"
#: apps/insights/utils/category_explorer.py:74
#: apps/insights/utils/category_explorer.py:153
@@ -1226,7 +1226,7 @@ msgstr "Déclencheur"
#: apps/rules/models.py:17
msgid "Sequenced"
msgstr "Récurrence"
msgstr "Classé"
#: apps/rules/models.py:26
msgid "Transaction rule"
@@ -1345,7 +1345,7 @@ msgstr ""
#: apps/transactions/filters.py:24 templates/cotton/transaction/item.html:21
#: templates/cotton/transaction/item.html:32 templates/includes/navbar.html:47
#: templates/insights/fragments/category_overview/index.html:61
#: templates/net_worth/net_worth.html:32
#: templates/net_worth/net_worth.html:33
#: templates/transactions/widgets/paid_toggle_button.html:8
#: templates/transactions/widgets/unselectable_paid_toggle_button.html:12
msgid "Projected"
@@ -2217,6 +2217,8 @@ msgid "Current balance"
msgstr "Balance actuelle"
#: templates/accounts/fragments/account_reconciliation.html:39
#: templates/net_worth/net_worth.html:105
#: templates/net_worth/net_worth.html:362
msgid "Difference"
msgstr "Différence"
@@ -2389,7 +2391,7 @@ msgstr "Revenu à date"
#: templates/cotton/ui/account_card.html:120
#: templates/cotton/ui/currency_card.html:114
msgid "current expenses"
msgstr "Dépenses à date"
msgstr "dépenses actuelles"
#: templates/cotton/ui/account_card.html:146
#: templates/cotton/ui/currency_card.html:140
@@ -2592,19 +2594,19 @@ msgstr "Prix actuel"
msgid "Amount Bought"
msgstr "Montant acheté"
#: templates/dca/fragments/strategy/details.html:390
#: templates/dca/fragments/strategy/details.html:392
msgid "Entry Price vs Current Price"
msgstr "Prix d'entrée Vs Prix actuel"
#: templates/dca/fragments/strategy/details.html:406
#: templates/dca/fragments/strategy/details.html:408
msgid "Days Between Investments"
msgstr "Jours entre les investissements"
#: templates/dca/fragments/strategy/details.html:453
#: templates/dca/fragments/strategy/details.html:455
msgid "Investment Frequency"
msgstr "Fréquence d'investissement"
#: templates/dca/fragments/strategy/details.html:455
#: templates/dca/fragments/strategy/details.html:457
msgid "The straighter the blue line, the more consistent your DCA strategy is."
msgstr ""
"Plus la ligne bleue est droite, plus votre stratégie DCA est cohérente."
@@ -2807,7 +2809,7 @@ msgstr "Valeur nette"
#: templates/includes/navbar.html:45
#: templates/insights/fragments/category_overview/index.html:65
#: templates/net_worth/net_worth.html:22
#: templates/net_worth/net_worth.html:23
msgid "Current"
msgstr "A date"
@@ -2972,7 +2974,7 @@ msgstr "Total"
#: templates/insights/fragments/category_overview/index.html:517
msgid "Final Total"
msgstr "Total final"
msgstr "Total Final"
#: templates/insights/fragments/emergency_fund.html:15
msgid "You've spent an average of"
@@ -3203,25 +3205,29 @@ msgstr "Filtrer les transactions"
msgid "Order by"
msgstr "Trier par"
#: templates/net_worth/net_worth.html:40
#: templates/net_worth/net_worth.html:42
#: templates/yearly_overview/pages/overview_by_currency.html:9
msgid "By currency"
msgstr "Par devises"
#: templates/net_worth/net_worth.html:75
#: templates/net_worth/net_worth.html:78
msgid "Consolidated"
msgstr "Consolidé"
#: templates/net_worth/net_worth.html:104
#: templates/net_worth/net_worth.html:101
msgid "Evolution"
msgstr "Evolution"
#: templates/net_worth/net_worth.html:128
#: templates/yearly_overview/pages/overview_by_account.html:7
msgid "By account"
msgstr "Par comptes"
#: templates/net_worth/net_worth.html:211
#: templates/net_worth/net_worth.html:236
msgid "Evolution by currency"
msgstr "Evolution par devises"
#: templates/net_worth/net_worth.html:275
#: templates/net_worth/net_worth.html:300
msgid "Evolution by account"
msgstr "Evolution par comptes"

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-14 04:57+0000\n"
"POT-Creation-Date: 2025-09-20 14:08+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
@@ -1316,7 +1316,7 @@ msgstr ""
#: apps/transactions/filters.py:24 templates/cotton/transaction/item.html:21
#: templates/cotton/transaction/item.html:32 templates/includes/navbar.html:47
#: templates/insights/fragments/category_overview/index.html:61
#: templates/net_worth/net_worth.html:32
#: templates/net_worth/net_worth.html:33
#: templates/transactions/widgets/paid_toggle_button.html:8
#: templates/transactions/widgets/unselectable_paid_toggle_button.html:12
msgid "Projected"
@@ -2167,6 +2167,8 @@ msgid "Current balance"
msgstr ""
#: templates/accounts/fragments/account_reconciliation.html:39
#: templates/net_worth/net_worth.html:105
#: templates/net_worth/net_worth.html:362
msgid "Difference"
msgstr ""
@@ -2542,19 +2544,19 @@ msgstr ""
msgid "Amount Bought"
msgstr ""
#: templates/dca/fragments/strategy/details.html:390
#: templates/dca/fragments/strategy/details.html:392
msgid "Entry Price vs Current Price"
msgstr ""
#: templates/dca/fragments/strategy/details.html:406
#: templates/dca/fragments/strategy/details.html:408
msgid "Days Between Investments"
msgstr ""
#: templates/dca/fragments/strategy/details.html:453
#: templates/dca/fragments/strategy/details.html:455
msgid "Investment Frequency"
msgstr ""
#: templates/dca/fragments/strategy/details.html:455
#: templates/dca/fragments/strategy/details.html:457
msgid "The straighter the blue line, the more consistent your DCA strategy is."
msgstr ""
@@ -2754,7 +2756,7 @@ msgstr ""
#: templates/includes/navbar.html:45
#: templates/insights/fragments/category_overview/index.html:65
#: templates/net_worth/net_worth.html:22
#: templates/net_worth/net_worth.html:23
msgid "Current"
msgstr ""
@@ -3137,25 +3139,29 @@ msgstr ""
msgid "Order by"
msgstr ""
#: templates/net_worth/net_worth.html:40
#: templates/net_worth/net_worth.html:42
#: templates/yearly_overview/pages/overview_by_currency.html:9
msgid "By currency"
msgstr ""
#: templates/net_worth/net_worth.html:75
#: templates/net_worth/net_worth.html:78
msgid "Consolidated"
msgstr ""
#: templates/net_worth/net_worth.html:104
#: templates/net_worth/net_worth.html:101
msgid "Evolution"
msgstr ""
#: templates/net_worth/net_worth.html:128
#: templates/yearly_overview/pages/overview_by_account.html:7
msgid "By account"
msgstr ""
#: templates/net_worth/net_worth.html:211
#: templates/net_worth/net_worth.html:236
msgid "Evolution by currency"
msgstr ""
#: templates/net_worth/net_worth.html:275
#: templates/net_worth/net_worth.html:300
msgid "Evolution by account"
msgstr ""

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-14 04:57+0000\n"
"PO-Revision-Date: 2025-09-11 22:17+0000\n"
"PO-Revision-Date: 2025-09-15 18:17+0000\n"
"Last-Translator: Phillip Maizza <phillipmaizza@gmail.com>\n"
"Language-Team: Italian <https://translations.herculino.com/projects/wygiwyh/"
"app/it/>\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.13.2\n"
"X-Generator: Weblate 5.13.3\n"
#: apps/accounts/forms.py:26
msgid "Group name"
@@ -134,7 +134,7 @@ msgstr "Gruppo conto"
#: templates/account_groups/pages/index.html:4
#: templates/includes/navbar.html:121 templates/includes/sidebar.html:204
msgid "Account Groups"
msgstr "Gruppi di conti"
msgstr "Gruppi conti"
#: apps/accounts/models.py:39 apps/currencies/models.py:44
#: templates/accounts/fragments/list.html:27
@@ -212,7 +212,7 @@ msgstr ""
#: apps/accounts/views/account_groups.py:44
msgid "Account Group added successfully"
msgstr "Gruppo di conti aggiunto con successo"
msgstr "Gruppo conti aggiunto con successo"
#: apps/accounts/views/account_groups.py:69
#: apps/accounts/views/account_groups.py:152 apps/accounts/views/accounts.py:68
@@ -228,7 +228,7 @@ msgstr "Solo il proprietario può modificare questo elemento"
#: apps/accounts/views/account_groups.py:82
msgid "Account Group updated successfully"
msgstr "Gruppo di conti aggiornato con successo"
msgstr "Gruppo conti aggiornato con successo"
#: apps/accounts/views/account_groups.py:111
#: apps/accounts/views/accounts.py:145 apps/dca/views.py:105
@@ -239,7 +239,7 @@ msgstr "Lelemento non è più condiviso con te"
#: apps/accounts/views/account_groups.py:114
msgid "Account Group deleted successfully"
msgstr "Gruppo di conti eliminato con successo"
msgstr "Gruppo conti eliminato con successo"
#: apps/accounts/views/account_groups.py:135
#: apps/accounts/views/accounts.py:189 apps/dca/views.py:129
@@ -301,11 +301,11 @@ msgstr "Dati tag non validi. Fornisci un ID o un nome."
#: apps/api/fields/transactions.py:105
msgid "Entity with this ID does not exist."
msgstr "Lentità con questo ID non esiste."
msgstr "Il beneficiario con questo ID non esiste."
#: apps/api/fields/transactions.py:115
msgid "Invalid entity data. Provide an ID or name."
msgstr "Dati entità non validi. Fornisci un ID o un nome."
msgstr "Dati beneficiario non validi. Fornisci un ID o un nome."
#: apps/api/serializers/transactions.py:192
msgid "Either 'date' or 'reference_date' must be provided."
@@ -490,7 +490,7 @@ msgstr "Rimuovi"
#: templates/transactions/pages/transactions.html:89
#: templates/transactions/pages/transactions.html:101
msgid "Clear"
msgstr "Pulisci"
msgstr "Reset"
#: apps/common/widgets/tom_select.py:16
msgid "No results..."
@@ -552,7 +552,7 @@ msgstr "A valuta"
#: apps/currencies/models.py:74 apps/currencies/models.py:81
msgid "Exchange Rate"
msgstr "Tasso di cambio"
msgstr "Cambio valuta"
#: apps/currencies/models.py:76
msgid "Date and Time"
@@ -568,7 +568,7 @@ msgstr "Automatico"
#: templates/exchange_rates/pages/index.html:4
#: templates/includes/navbar.html:129 templates/includes/sidebar.html:218
msgid "Exchange Rates"
msgstr "Tassi di cambio"
msgstr "Cambio valute"
#: apps/currencies/models.py:94
msgid "From and To currencies cannot be the same."
@@ -650,17 +650,17 @@ msgstr ""
#: apps/currencies/models.py:160
msgid "Single exchange rate"
msgstr "Tasso di cambio singolo"
msgstr "Cambio unico"
#: apps/currencies/models.py:163
msgid "Create one exchange rate and keep updating it. Avoids database clutter."
msgstr ""
"Crea un solo tasso di cambio e lo mantiene aggiornato. Evita ingombri nel "
"Crea un solo cambio valuta e aggiornalo nel tempo, evitando duplicati nel "
"database."
#: apps/currencies/models.py:168
msgid "Exchange Rate Service"
msgstr "Servizio tasso di cambio"
msgstr "Servizio cambio valuta"
#: apps/currencies/models.py:169
msgid "Exchange Rate Services"
@@ -704,15 +704,15 @@ msgstr "Valuta eliminata con successo"
#: apps/currencies/views/exchange_rates.py:89
msgid "Exchange rate added successfully"
msgstr "Tasso di cambio aggiunto con successo"
msgstr "Cambio valuta aggiunto con successo"
#: apps/currencies/views/exchange_rates.py:117
msgid "Exchange rate updated successfully"
msgstr "Tasso di cambio aggiornato con successo"
msgstr "Cambio valuta aggiornato con successo"
#: apps/currencies/views/exchange_rates.py:143
msgid "Exchange rate deleted successfully"
msgstr "Tasso di cambio eliminato con successo"
msgstr "Cambio valuta eliminato con successo"
#: apps/currencies/views/exchange_rates_services.py:50
msgid "Service added successfully"
@@ -876,7 +876,7 @@ msgstr "Categorie"
#: templates/includes/sidebar.html:190
#: templates/insights/fragments/category_overview/index.html:49
msgid "Entities"
msgstr "Entità"
msgstr "Beneficiari"
#: apps/export_app/forms.py:56 apps/export_app/forms.py:140
#: apps/transactions/models.py:807 templates/includes/navbar.html:77
@@ -884,7 +884,7 @@ msgstr "Entità"
#: templates/recurring_transactions/fragments/list.html:5
#: templates/recurring_transactions/pages/index.html:4
msgid "Recurring Transactions"
msgstr "Transazioni ricorrenti"
msgstr "Pagamenti ricorrenti"
#: apps/export_app/forms.py:62 apps/export_app/forms.py:138
#: apps/transactions/models.py:583 templates/includes/navbar.html:75
@@ -892,14 +892,14 @@ msgstr "Transazioni ricorrenti"
#: templates/installment_plans/fragments/list.html:5
#: templates/installment_plans/pages/index.html:4
msgid "Installment Plans"
msgstr "Rate / Piani di rateizzazione"
msgstr "Pagamenti a rate"
#: apps/export_app/forms.py:74 apps/export_app/forms.py:143
#: templates/exchange_rates_services/fragments/list.html:6
#: templates/exchange_rates_services/pages/index.html:4
#: templates/includes/navbar.html:143 templates/includes/sidebar.html:246
msgid "Automatic Exchange Rates"
msgstr "Tassi di cambio automatici"
msgstr "Cambio valuta automatico"
#: apps/export_app/forms.py:80 templates/includes/navbar.html:135
#: templates/includes/sidebar.html:226 templates/rules/fragments/list.html:5
@@ -1343,7 +1343,7 @@ msgstr "Azione Aggiornamento o Creazione transazione eliminata correttamente"
#: apps/transactions/filters.py:24 templates/cotton/transaction/item.html:21
#: templates/cotton/transaction/item.html:32 templates/includes/navbar.html:47
#: templates/insights/fragments/category_overview/index.html:61
#: templates/net_worth/net_worth.html:32
#: templates/net_worth/net_worth.html:33
#: templates/transactions/widgets/paid_toggle_button.html:8
#: templates/transactions/widgets/unselectable_paid_toggle_button.html:12
msgid "Projected"
@@ -1392,12 +1392,12 @@ msgstr "Senza tag"
#: apps/transactions/filters.py:201
msgid "Any entity"
msgstr "Qualsiasi entità"
msgstr "Qualsiasi beneficiario"
#: apps/transactions/filters.py:202
#: templates/insights/fragments/category_overview/index.html:274
msgid "No entity"
msgstr "Nessuna entità"
msgstr "Nessun beneficiario"
#: apps/transactions/forms.py:175
msgid "More"
@@ -1436,7 +1436,7 @@ msgstr "Nome tag"
#: apps/transactions/forms.py:939
msgid "Entity name"
msgstr "Nome entità"
msgstr "Nome beneficiario"
#: apps/transactions/forms.py:971
msgid "Category name"
@@ -1487,12 +1487,12 @@ msgid ""
"Deactivated entities won't be able to be selected when creating new "
"transactions"
msgstr ""
"Le entità disattivate non potranno essere selezionate durante la creazione "
"di nuove transazioni"
"I beneficiari disattivati non potranno essere selezionati durante la "
"creazione di nuove transazioni"
#: apps/transactions/models.py:277
msgid "Entity"
msgstr "Entità"
msgstr "Beneficiari"
#: apps/transactions/models.py:289 apps/transactions/models.py:971
#: templates/calendar_view/fragments/list.html:42
@@ -1504,7 +1504,7 @@ msgstr "Entità"
#: templates/insights/fragments/category_overview/index.html:79
#: templates/monthly_overview/fragments/monthly_summary.html:39
msgid "Income"
msgstr "Entrata"
msgstr "Entrate"
#: apps/transactions/models.py:290 apps/transactions/models.py:972
#: templates/calendar_view/fragments/list.html:46
@@ -1625,7 +1625,7 @@ msgstr "Tipo di ricorrenza"
#: apps/transactions/models.py:782
msgid "Recurrence Interval"
msgstr "Intervallo di ricorrenza"
msgstr "Frequenza"
#: apps/transactions/models.py:785
msgid "Keep at most"
@@ -1714,15 +1714,15 @@ msgstr "Categoria eliminata con successo"
#: apps/transactions/views/entities.py:66
msgid "Entity added successfully"
msgstr "Entità aggiunta con successo"
msgstr "Beneficiario aggiunto con successo"
#: apps/transactions/views/entities.py:104
msgid "Entity updated successfully"
msgstr "Entità aggiornata con successo"
msgstr "Beneficiario aggiornato con successo"
#: apps/transactions/views/entities.py:133
msgid "Entity deleted successfully"
msgstr "Entità eliminata con successo"
msgstr "Beneficiario eliminato con successo"
#: apps/transactions/views/installment_plans.py:87
msgid "Installment Plan added successfully"
@@ -1886,8 +1886,8 @@ msgid ""
"displayed\n"
"Consider helping translate WYGIWYH to your language at %(translation_link)s"
msgstr ""
"Questo cambia la lingua (se disponibile) e la modalità di visualizzazione di "
"numeri e date.\n"
"Cambia la lingua (se disponibile) e la modalità di visualizzazione di numeri "
"e date.\n"
"Considera la possibilità di contribuire alla traduzione di WYGIWYH nella tua "
"lingua su %(translation_link)s"
@@ -1897,7 +1897,7 @@ msgstr "Nuova Password"
#: apps/users/forms.py:160
msgid "Leave blank to keep the current password."
msgstr "Lasciare vuoto per mantenere la password corrente."
msgstr "Lascia vuoto per mantenere la password attuale."
#: apps/users/forms.py:163
msgid "Confirm New Password"
@@ -2016,11 +2016,11 @@ msgstr "Le tue impostazioni sono state aggiornate"
#: templates/account_groups/fragments/add.html:5
msgid "Add account group"
msgstr "Aggiungi gruppo di account"
msgstr "Aggiungi gruppo conti"
#: templates/account_groups/fragments/edit.html:5
msgid "Edit account group"
msgstr "Modifica gruppo di account"
msgstr "Modifica gruppo conti"
#: templates/account_groups/fragments/list.html:32
#: templates/accounts/fragments/list.html:37
@@ -2192,7 +2192,7 @@ msgstr "Condividi"
#: templates/account_groups/fragments/list.html:77
msgid "No account groups"
msgstr "Nessun gruppo di account"
msgstr "Nessun gruppo conti"
#: templates/account_groups/fragments/share.html:5
#: templates/accounts/fragments/share.html:5
@@ -2212,6 +2212,8 @@ msgid "Current balance"
msgstr "Saldo corrente"
#: templates/accounts/fragments/account_reconciliation.html:39
#: templates/net_worth/net_worth.html:105
#: templates/net_worth/net_worth.html:362
msgid "Difference"
msgstr "Differenza"
@@ -2517,7 +2519,7 @@ msgstr "Aggiungi strategia DCA"
#: templates/dca/fragments/strategy/details.html:22
msgid "No exchange rate available"
msgstr "Nessun tasso di cambio disponibile"
msgstr "Nessun cambio valuta disponibile"
#: templates/dca/fragments/strategy/details.html:33
msgid "Entries"
@@ -2587,19 +2589,19 @@ msgstr "Prezzo attuale"
msgid "Amount Bought"
msgstr "Importo acquistato"
#: templates/dca/fragments/strategy/details.html:390
#: templates/dca/fragments/strategy/details.html:392
msgid "Entry Price vs Current Price"
msgstr "Prezzo di ingresso vs prezzo corrente"
#: templates/dca/fragments/strategy/details.html:406
#: templates/dca/fragments/strategy/details.html:408
msgid "Days Between Investments"
msgstr "Giorni tra gli investimenti"
#: templates/dca/fragments/strategy/details.html:453
#: templates/dca/fragments/strategy/details.html:455
msgid "Investment Frequency"
msgstr "Frequenza di investimento"
#: templates/dca/fragments/strategy/details.html:455
#: templates/dca/fragments/strategy/details.html:457
msgid "The straighter the blue line, the more consistent your DCA strategy is."
msgstr "Più dritta è la linea blu, più coerente è la tua strategia DCA."
@@ -2610,33 +2612,33 @@ msgstr "Modifica la strategia DCA"
#: templates/dca/fragments/strategy/list.html:5
#: templates/dca/pages/strategy_index.html:4
msgid "Dollar Cost Average Strategies"
msgstr "Strategie di costo medio del dollaro"
msgstr "Strategie DCA"
#: templates/dca/pages/strategy_detail_index.html:4
msgid "Dollar Cost Average Strategy"
msgstr "Strategia del costo medio del dollaro"
msgstr "Strategie DCA"
#: templates/entities/fragments/add.html:5
msgid "Add entity"
msgstr "Aggiungi entità"
msgstr "Aggiungi beneficiario"
#: templates/entities/fragments/edit.html:5
msgid "Edit entity"
msgstr "Modifica entità"
msgstr "Modifica beneficiario"
#: templates/entities/fragments/table.html:71
msgid "No entities"
msgstr "Nessuna entità"
msgstr "Nessun beneficiario"
#: templates/exchange_rates/fragments/add.html:5
#: templates/exchange_rates_services/fragments/add.html:5
msgid "Add exchange rate"
msgstr "Aggiungi tasso di cambio"
msgstr "Aggiungi cambio valuta"
#: templates/exchange_rates/fragments/edit.html:5
#: templates/exchange_rates_services/fragments/edit.html:5
msgid "Edit exchange rate"
msgstr "Modifica tasso di cambio"
msgstr "Modifica cambio valuta"
#: templates/exchange_rates/fragments/list.html:25
#: templates/includes/navbar.html:62 templates/includes/sidebar.html:85
@@ -2659,7 +2661,7 @@ msgstr "Tasso"
#: templates/exchange_rates/fragments/table.html:51
#: templates/exchange_rates_services/fragments/table.html:51
msgid "No exchange rates"
msgstr "Nessun tasso di cambio"
msgstr "Nessun cambio valuta"
#: templates/exchange_rates/fragments/table.html:58
#: templates/exchange_rates_services/fragments/table.html:58
@@ -2801,14 +2803,14 @@ msgstr "Patrimonio netto"
#: templates/includes/navbar.html:45
#: templates/insights/fragments/category_overview/index.html:65
#: templates/net_worth/net_worth.html:22
#: templates/net_worth/net_worth.html:23
msgid "Current"
msgstr "Attuale"
#: templates/includes/navbar.html:51 templates/includes/sidebar.html:71
#: templates/insights/pages/index.html:5
msgid "Insights"
msgstr "Approfondimenti"
msgstr "Analisi"
#: templates/includes/navbar.html:67 templates/includes/sidebar.html:93
msgid "Trash Can"
@@ -2820,19 +2822,19 @@ msgstr "Strumenti"
#: templates/includes/navbar.html:89 templates/includes/sidebar.html:120
msgid "Dollar Cost Average Tracker"
msgstr "Tracker del costo medio del dollaro"
msgstr "Costo medio del dollaro"
#: templates/includes/navbar.html:92 templates/includes/sidebar.html:126
#: templates/mini_tools/unit_price_calculator.html:5
#: templates/mini_tools/unit_price_calculator.html:10
msgid "Unit Price Calculator"
msgstr "Calcolatore del prezzo unitario"
msgstr "Prezzo per unità"
#: templates/includes/navbar.html:95 templates/includes/sidebar.html:132
#: templates/mini_tools/currency_converter/currency_converter.html:8
#: templates/mini_tools/currency_converter/currency_converter.html:15
msgid "Currency Converter"
msgstr "Convertitore di valuta"
msgstr "Converti valuta"
#: templates/includes/navbar.html:104 templates/includes/sidebar.html:150
#: templates/includes/sidebar.html:163
@@ -2936,7 +2938,7 @@ msgstr "Tabella"
#: templates/insights/fragments/category_overview/index.html:24
msgid "Bars"
msgstr "Barre"
msgstr "Grafico a barre"
#: templates/insights/fragments/category_overview/index.html:39
msgid ""
@@ -2951,7 +2953,7 @@ msgid ""
"Transaction amounts associated with multiple tags and entities will be "
"counted once for each tag"
msgstr ""
"Gli importi delle transazioni associati a più tag ed entità verranno "
"Gli importi delle transazioni associati a più tag e beneficiari verranno "
"conteggiati una volta per ogni tag"
#: templates/insights/fragments/category_overview/index.html:69
@@ -2997,7 +2999,7 @@ msgstr "Tutto bene!"
#: templates/insights/fragments/late_transactions.html:16
msgid "No late transactions"
msgstr "Nessuna transazione in ritardo"
msgstr "Nessuna transazione in sospeso"
#: templates/insights/fragments/latest_transactions.html:14
msgid "No recent transactions"
@@ -3023,35 +3025,35 @@ msgstr "Anno"
#: templates/insights/pages/index.html:45
msgid "Month Range"
msgstr "Intervallo di mesi"
msgstr "Intervallo mesi"
#: templates/insights/pages/index.html:50
msgid "Year Range"
msgstr "Intervallo di anni"
msgstr "Intervallo anni"
#: templates/insights/pages/index.html:55
msgid "Date Range"
msgstr "Intervallo di date"
msgstr "Intervallo date"
#: templates/insights/pages/index.html:83
msgid "Account Flow"
msgstr "Flusso del conto"
msgstr "Flusso conti"
#: templates/insights/pages/index.html:90
msgid "Currency Flow"
msgstr "Flusso di valuta"
msgstr "Flusso valute"
#: templates/insights/pages/index.html:97
msgid "Category Explorer"
msgstr "Esploratore di categorie"
msgstr "Categorie"
#: templates/insights/pages/index.html:104
msgid "Categories Overview"
msgstr "Panoramica delle categorie"
msgstr "Panoramica categorie"
#: templates/insights/pages/index.html:111
msgid "Late Transactions"
msgstr "Transazioni in ritardo"
msgstr "Transazioni in sospeso"
#: templates/insights/pages/index.html:117
msgid "Latest Transactions"
@@ -3114,7 +3116,7 @@ msgstr "Investi"
#: templates/mini_tools/unit_price_calculator.html:100
#: templates/mini_tools/unit_price_calculator.html:125
msgid "Item price"
msgstr "Prezzo dell'elemento"
msgstr "Prezzo"
#: templates/mini_tools/unit_price_calculator.html:33
#: templates/mini_tools/unit_price_calculator.html:106
@@ -3144,7 +3146,7 @@ msgstr "Quota di spesa giornaliera"
#: templates/monthly_overview/fragments/monthly_summary.html:6
msgid "This is the final total divided by the remaining days in the month"
msgstr "Questo è il totale finale diviso per i giorni rimanenti del mese"
msgstr "Il totale finale diviso per i giorni del mese rimanenti"
#: templates/monthly_overview/fragments/monthly_summary.html:42
#: templates/monthly_overview/fragments/monthly_summary.html:106
@@ -3194,25 +3196,31 @@ msgstr "Filtra transazioni"
msgid "Order by"
msgstr "Ordina per"
#: templates/net_worth/net_worth.html:40
#: templates/net_worth/net_worth.html:42
#: templates/yearly_overview/pages/overview_by_currency.html:9
msgid "By currency"
msgstr "Per valuta"
#: templates/net_worth/net_worth.html:75
#: templates/net_worth/net_worth.html:78
msgid "Consolidated"
msgstr "Consolidate"
#: templates/net_worth/net_worth.html:104
#: templates/net_worth/net_worth.html:101
#, fuzzy
#| msgid "Evolution by account"
msgid "Evolution"
msgstr "Evoluzione per conto"
#: templates/net_worth/net_worth.html:128
#: templates/yearly_overview/pages/overview_by_account.html:7
msgid "By account"
msgstr "Per conto"
#: templates/net_worth/net_worth.html:211
#: templates/net_worth/net_worth.html:236
msgid "Evolution by currency"
msgstr "Evoluzione per valuta"
#: templates/net_worth/net_worth.html:275
#: templates/net_worth/net_worth.html:300
msgid "Evolution by account"
msgstr "Evoluzione per conto"
@@ -3224,7 +3232,7 @@ msgstr "Aggiungi transazione rapida"
#: templates/quick_transactions/fragments/create_menu.html:13
#: templates/quick_transactions/fragments/list.html:68
msgid "Nothing to see here..."
msgstr "Qui non c'è niente da vedere..."
msgstr "Non cè nulla da mostrare..."
#: templates/quick_transactions/fragments/edit.html:5
msgid "Edit quick transaction"

View File

@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-14 04:57+0000\n"
"PO-Revision-Date: 2025-09-09 18:17+0000\n"
"POT-Creation-Date: 2025-09-20 14:08+0000\n"
"PO-Revision-Date: 2025-09-21 13:17+0000\n"
"Last-Translator: Dimitri Decrock <dj.flashpower@gmail.com>\n"
"Language-Team: Dutch <https://translations.herculino.com/projects/wygiwyh/"
"app/nl/>\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.13.2\n"
"X-Generator: Weblate 5.13.3\n"
#: apps/accounts/forms.py:26
msgid "Group name"
@@ -1344,7 +1344,7 @@ msgstr "Verrichting Bijwerken Of Maken succesvol verwijderd"
#: apps/transactions/filters.py:24 templates/cotton/transaction/item.html:21
#: templates/cotton/transaction/item.html:32 templates/includes/navbar.html:47
#: templates/insights/fragments/category_overview/index.html:61
#: templates/net_worth/net_worth.html:32
#: templates/net_worth/net_worth.html:33
#: templates/transactions/widgets/paid_toggle_button.html:8
#: templates/transactions/widgets/unselectable_paid_toggle_button.html:12
msgid "Projected"
@@ -2208,6 +2208,8 @@ msgid "Current balance"
msgstr "Huidige saldo"
#: templates/accounts/fragments/account_reconciliation.html:39
#: templates/net_worth/net_worth.html:105
#: templates/net_worth/net_worth.html:362
msgid "Difference"
msgstr "Verschil"
@@ -2583,19 +2585,19 @@ msgstr "Actuele Prijs"
msgid "Amount Bought"
msgstr "Gekocht Bedrag"
#: templates/dca/fragments/strategy/details.html:390
#: templates/dca/fragments/strategy/details.html:392
msgid "Entry Price vs Current Price"
msgstr "Instapprijs vs Huidige Prijs"
#: templates/dca/fragments/strategy/details.html:406
#: templates/dca/fragments/strategy/details.html:408
msgid "Days Between Investments"
msgstr "Dagen Tussen Investeringen"
#: templates/dca/fragments/strategy/details.html:453
#: templates/dca/fragments/strategy/details.html:455
msgid "Investment Frequency"
msgstr "Investeringsfrequentie"
#: templates/dca/fragments/strategy/details.html:455
#: templates/dca/fragments/strategy/details.html:457
msgid "The straighter the blue line, the more consistent your DCA strategy is."
msgstr "Hoe rechter de blauwe lijn, hoe consistenter je DCA-strategie is."
@@ -2796,7 +2798,7 @@ msgstr "Netto Waarde"
#: templates/includes/navbar.html:45
#: templates/insights/fragments/category_overview/index.html:65
#: templates/net_worth/net_worth.html:22
#: templates/net_worth/net_worth.html:23
msgid "Current"
msgstr "Huidige"
@@ -3190,25 +3192,29 @@ msgstr "Filter verrichtingen"
msgid "Order by"
msgstr "Sorteer op"
#: templates/net_worth/net_worth.html:40
#: templates/net_worth/net_worth.html:42
#: templates/yearly_overview/pages/overview_by_currency.html:9
msgid "By currency"
msgstr "Op munteenheid"
#: templates/net_worth/net_worth.html:75
#: templates/net_worth/net_worth.html:78
msgid "Consolidated"
msgstr "Samengevoegd"
#: templates/net_worth/net_worth.html:104
#: templates/net_worth/net_worth.html:101
msgid "Evolution"
msgstr "Evolutie"
#: templates/net_worth/net_worth.html:128
#: templates/yearly_overview/pages/overview_by_account.html:7
msgid "By account"
msgstr "Op rekening"
#: templates/net_worth/net_worth.html:211
#: templates/net_worth/net_worth.html:236
msgid "Evolution by currency"
msgstr "Evolutie per munteenheid"
#: templates/net_worth/net_worth.html:275
#: templates/net_worth/net_worth.html:300
msgid "Evolution by account"
msgstr "Evolutie per rekening"

File diff suppressed because it is too large Load Diff

View File

@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-14 04:57+0000\n"
"PO-Revision-Date: 2025-09-07 14:17+0000\n"
"POT-Creation-Date: 2025-09-20 14:08+0000\n"
"PO-Revision-Date: 2025-09-20 14:44+0000\n"
"Last-Translator: Herculino Trotta <netotrotta@gmail.com>\n"
"Language-Team: Portuguese (Brazil) <https://translations.herculino.com/"
"projects/wygiwyh/app/pt_BR/>\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 5.13\n"
"X-Generator: Weblate 5.13.3\n"
#: apps/accounts/forms.py:26
msgid "Group name"
@@ -1342,7 +1342,7 @@ msgstr "Ação Atualizar ou Criar Transação apagada com sucesso"
#: apps/transactions/filters.py:24 templates/cotton/transaction/item.html:21
#: templates/cotton/transaction/item.html:32 templates/includes/navbar.html:47
#: templates/insights/fragments/category_overview/index.html:61
#: templates/net_worth/net_worth.html:32
#: templates/net_worth/net_worth.html:33
#: templates/transactions/widgets/paid_toggle_button.html:8
#: templates/transactions/widgets/unselectable_paid_toggle_button.html:12
msgid "Projected"
@@ -2207,6 +2207,8 @@ msgid "Current balance"
msgstr "Saldo atual"
#: templates/accounts/fragments/account_reconciliation.html:39
#: templates/net_worth/net_worth.html:105
#: templates/net_worth/net_worth.html:362
msgid "Difference"
msgstr "Diferença"
@@ -2582,19 +2584,19 @@ msgstr "Preço atual"
msgid "Amount Bought"
msgstr "Quantia comprada"
#: templates/dca/fragments/strategy/details.html:390
#: templates/dca/fragments/strategy/details.html:392
msgid "Entry Price vs Current Price"
msgstr "Preço de Entrada vs Preço Atual"
#: templates/dca/fragments/strategy/details.html:406
#: templates/dca/fragments/strategy/details.html:408
msgid "Days Between Investments"
msgstr "Dias entre investimentos"
#: templates/dca/fragments/strategy/details.html:453
#: templates/dca/fragments/strategy/details.html:455
msgid "Investment Frequency"
msgstr "Frequência de Investimento"
#: templates/dca/fragments/strategy/details.html:455
#: templates/dca/fragments/strategy/details.html:457
msgid "The straighter the blue line, the more consistent your DCA strategy is."
msgstr ""
"Quanto mais reta for a linha azul, mais consistente é sua estratégia de CMP."
@@ -2797,7 +2799,7 @@ msgstr "Patrimônio"
#: templates/includes/navbar.html:45
#: templates/insights/fragments/category_overview/index.html:65
#: templates/net_worth/net_worth.html:22
#: templates/net_worth/net_worth.html:23
msgid "Current"
msgstr "Atual"
@@ -3189,25 +3191,29 @@ msgstr "Filtrar transações"
msgid "Order by"
msgstr "Ordernar por"
#: templates/net_worth/net_worth.html:40
#: templates/net_worth/net_worth.html:42
#: templates/yearly_overview/pages/overview_by_currency.html:9
msgid "By currency"
msgstr "Por moeda"
#: templates/net_worth/net_worth.html:75
#: templates/net_worth/net_worth.html:78
msgid "Consolidated"
msgstr "Consolidado"
#: templates/net_worth/net_worth.html:104
#: templates/net_worth/net_worth.html:101
msgid "Evolution"
msgstr "Evolução"
#: templates/net_worth/net_worth.html:128
#: templates/yearly_overview/pages/overview_by_account.html:7
msgid "By account"
msgstr "Por conta"
#: templates/net_worth/net_worth.html:211
#: templates/net_worth/net_worth.html:236
msgid "Evolution by currency"
msgstr "Evolução por moeda"
#: templates/net_worth/net_worth.html:275
#: templates/net_worth/net_worth.html:300
msgid "Evolution by account"
msgstr "Evolução por conta"

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-14 04:57+0000\n"
"POT-Creation-Date: 2025-09-20 14:08+0000\n"
"PO-Revision-Date: 2025-04-14 06:16+0000\n"
"Last-Translator: Emil <emil.bjorkroth@gmail.com>\n"
"Language-Team: Swedish <https://translations.herculino.com/projects/wygiwyh/"
@@ -1318,7 +1318,7 @@ msgstr ""
#: apps/transactions/filters.py:24 templates/cotton/transaction/item.html:21
#: templates/cotton/transaction/item.html:32 templates/includes/navbar.html:47
#: templates/insights/fragments/category_overview/index.html:61
#: templates/net_worth/net_worth.html:32
#: templates/net_worth/net_worth.html:33
#: templates/transactions/widgets/paid_toggle_button.html:8
#: templates/transactions/widgets/unselectable_paid_toggle_button.html:12
msgid "Projected"
@@ -2169,6 +2169,8 @@ msgid "Current balance"
msgstr ""
#: templates/accounts/fragments/account_reconciliation.html:39
#: templates/net_worth/net_worth.html:105
#: templates/net_worth/net_worth.html:362
msgid "Difference"
msgstr ""
@@ -2544,19 +2546,19 @@ msgstr ""
msgid "Amount Bought"
msgstr ""
#: templates/dca/fragments/strategy/details.html:390
#: templates/dca/fragments/strategy/details.html:392
msgid "Entry Price vs Current Price"
msgstr ""
#: templates/dca/fragments/strategy/details.html:406
#: templates/dca/fragments/strategy/details.html:408
msgid "Days Between Investments"
msgstr ""
#: templates/dca/fragments/strategy/details.html:453
#: templates/dca/fragments/strategy/details.html:455
msgid "Investment Frequency"
msgstr ""
#: templates/dca/fragments/strategy/details.html:455
#: templates/dca/fragments/strategy/details.html:457
msgid "The straighter the blue line, the more consistent your DCA strategy is."
msgstr ""
@@ -2756,7 +2758,7 @@ msgstr ""
#: templates/includes/navbar.html:45
#: templates/insights/fragments/category_overview/index.html:65
#: templates/net_worth/net_worth.html:22
#: templates/net_worth/net_worth.html:23
msgid "Current"
msgstr ""
@@ -3139,25 +3141,29 @@ msgstr ""
msgid "Order by"
msgstr ""
#: templates/net_worth/net_worth.html:40
#: templates/net_worth/net_worth.html:42
#: templates/yearly_overview/pages/overview_by_currency.html:9
msgid "By currency"
msgstr ""
#: templates/net_worth/net_worth.html:75
#: templates/net_worth/net_worth.html:78
msgid "Consolidated"
msgstr ""
#: templates/net_worth/net_worth.html:104
#: templates/net_worth/net_worth.html:101
msgid "Evolution"
msgstr ""
#: templates/net_worth/net_worth.html:128
#: templates/yearly_overview/pages/overview_by_account.html:7
msgid "By account"
msgstr ""
#: templates/net_worth/net_worth.html:211
#: templates/net_worth/net_worth.html:236
msgid "Evolution by currency"
msgstr ""
#: templates/net_worth/net_worth.html:275
#: templates/net_worth/net_worth.html:300
msgid "Evolution by account"
msgstr ""

View File

@@ -7,9 +7,9 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-14 04:57+0000\n"
"PO-Revision-Date: 2025-05-12 14:16+0000\n"
"Last-Translator: Felix <xnovaua@gmail.com>\n"
"POT-Creation-Date: 2025-09-20 14:08+0000\n"
"PO-Revision-Date: 2025-11-01 01:17+0000\n"
"Last-Translator: mlystopad <mlystopadt@gmail.com>\n"
"Language-Team: Ukrainian <https://translations.herculino.com/projects/"
"wygiwyh/app/uk/>\n"
"Language: uk\n"
@@ -18,7 +18,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"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"
"X-Generator: Weblate 5.11.3\n"
"X-Generator: Weblate 5.14\n"
#: apps/accounts/forms.py:26
msgid "Group name"
@@ -154,8 +154,9 @@ msgid "Default currency for exchange calculations"
msgstr "Валюта за замовчуванням для обмінних розрахунків"
#: apps/accounts/models.py:55
#, fuzzy
msgid "Asset account"
msgstr "Рахунок активів"
msgstr "Рахунок активу"
#: apps/accounts/models.py:57
msgid ""
@@ -268,11 +269,11 @@ msgstr "Рахунок успішно видалено"
#: apps/accounts/views/accounts.py:165
msgid "Account is now tracked"
msgstr ""
msgstr "Рахунок теперь відстежується"
#: apps/accounts/views/accounts.py:168
msgid "Account is now untracked"
msgstr ""
msgstr "Рахунок більше не відстежується"
#: apps/accounts/views/balance.py:77
msgid "Balance reconciliation"
@@ -312,7 +313,7 @@ msgstr "Необхідно вказати або 'date', або 'reference_date'
#: apps/common/admin.py:5
msgid "Make public"
msgstr ""
msgstr "Зробити публічним"
#: apps/common/admin.py:10
#, fuzzy
@@ -436,23 +437,25 @@ msgstr[2] "%(weeks)s тижнів тому"
#, python-format
msgid "in %(years)s year"
msgid_plural "in %(years)s years"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
msgstr[0] "через %(years)s рік"
msgstr[1] "через %(years)s роки"
msgstr[2] "через %(years)s років"
#: apps/common/templatetags/natural.py:51
#, python-format
msgid "in %(months)s month"
msgid_plural "in %(months)s months"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "через %(months)s місяць"
msgstr[1] "через %(months)s місяці"
msgstr[2] "через %(months)s місяців"
#: apps/common/templatetags/natural.py:56
#, python-format
msgid "in %(weeks)s week"
msgid_plural "in %(weeks)s weeks"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "через %(weeks)s тиждень"
msgstr[1] "через %(weeks)s тижні"
msgstr[2] "через %(weeks)s тижнів"
#: apps/common/templatetags/toast_bg.py:34
msgid "Success"
@@ -566,7 +569,7 @@ msgstr "Дата і час"
#: apps/currencies/models.py:78 apps/users/models.py:12
#: apps/users/models.py:497
msgid "Auto"
msgstr ""
msgstr "Авто"
#: apps/currencies/models.py:82 apps/export_app/forms.py:68
#: apps/export_app/forms.py:145 templates/exchange_rates/fragments/list.html:6
@@ -630,23 +633,27 @@ msgstr "Остання успішна вибірка"
#: apps/currencies/models.py:141
msgid "Target Currencies"
msgstr ""
msgstr "Цільові валюти"
#: apps/currencies/models.py:143
msgid ""
"Select currencies to fetch exchange rates for. Rates will be fetched for "
"each currency against their set exchange currency."
msgstr ""
"Оберіть валюти для завантаження курсів обміну. Курси будуть завантажені для "
"кожної валюти відносно встановленої валюти обміну."
#: apps/currencies/models.py:151
msgid "Target Accounts"
msgstr ""
msgstr "Цільові Рахунки"
#: apps/currencies/models.py:153
msgid ""
"Select accounts to fetch exchange rates for. Rates will be fetched for each "
"account's currency against their set exchange currency."
msgstr ""
"Оберіть рахунки для завантаження курсів обміну. Курси будуть завантажені для "
"валюти кожного рахунку відносно встановленої валюти обміну."
#: apps/currencies/models.py:160
#, fuzzy
@@ -657,111 +664,117 @@ msgstr "Обмінний курс"
#: apps/currencies/models.py:163
msgid "Create one exchange rate and keep updating it. Avoids database clutter."
msgstr ""
"Створіть один курс обміну та оновлюйте його постійно. Це запобігає "
"засміченню бази даних."
#: apps/currencies/models.py:168
msgid "Exchange Rate Service"
msgstr ""
msgstr "Сервіс Курсів Обміну"
#: apps/currencies/models.py:169
msgid "Exchange Rate Services"
msgstr ""
msgstr "Сервіси Курсів Обміну"
#: apps/currencies/models.py:221
msgid "'Every X hours' interval type requires a positive integer."
msgstr ""
msgstr "Інтервал типу «Кожні X годин» потребує додатнього цілого числа."
#: apps/currencies/models.py:230
msgid "'Every X hours' interval must be between 1 and 24."
msgstr ""
msgstr "Інтервал типу «Кожні X годин» повинен бути між 1 та 24."
#: apps/currencies/models.py:244
msgid ""
"Invalid hour format. Use comma-separated hours (0-23) and/or ranges (e.g., "
"'1-5,8,10-12')."
msgstr ""
"Неправильний формат годин. Використовуйте години, розділені комами (023) та/"
"або діапазони (наприклад, '1-5,8,10-12')."
#: apps/currencies/models.py:255
msgid ""
"Invalid format. Please check the requirements for your selected interval "
"type."
msgstr ""
"Неправильний формат. Будь ласка, перевірте вимоги для обраного типу "
"інтервалу."
#: apps/currencies/views/currencies.py:42
msgid "Currency added successfully"
msgstr ""
msgstr "Валюту успішно додано"
#: apps/currencies/views/currencies.py:70
msgid "Currency updated successfully"
msgstr ""
msgstr "Валюту успішно оновлено"
#: apps/currencies/views/currencies.py:96
msgid "Currency deleted successfully"
msgstr ""
msgstr "Валюту успішно видалено"
#: apps/currencies/views/exchange_rates.py:89
msgid "Exchange rate added successfully"
msgstr ""
msgstr "Курс обміну успішно додано"
#: apps/currencies/views/exchange_rates.py:117
msgid "Exchange rate updated successfully"
msgstr ""
msgstr "Курс обміну успішно оновлено"
#: apps/currencies/views/exchange_rates.py:143
msgid "Exchange rate deleted successfully"
msgstr ""
msgstr "Курс обміну успішно видалено"
#: apps/currencies/views/exchange_rates_services.py:50
msgid "Service added successfully"
msgstr ""
msgstr "Сервіс успішно додано"
#: apps/currencies/views/exchange_rates_services.py:79
msgid "Service updated successfully"
msgstr ""
msgstr "Сервіс успішно оновлено"
#: apps/currencies/views/exchange_rates_services.py:106
msgid "Service deleted successfully"
msgstr ""
msgstr "Сервіс успішно видалено"
#: apps/currencies/views/exchange_rates_services.py:122
msgid "Services queued successfully"
msgstr ""
msgstr "Сервіси успішно поставлено в чергу"
#: apps/dca/forms.py:65 apps/dca/forms.py:164
msgid "Create transaction"
msgstr ""
msgstr "Створити транзакцію"
#: apps/dca/forms.py:70 apps/transactions/forms.py:515
msgid "From Account"
msgstr ""
msgstr "З Рахунку"
#: apps/dca/forms.py:76 apps/transactions/forms.py:520
msgid "To Account"
msgstr ""
msgstr "На рахунок"
#: apps/dca/forms.py:116 apps/dca/models.py:171
msgid "Expense Transaction"
msgstr ""
msgstr "Витратна транзакція"
#: apps/dca/forms.py:120 apps/dca/forms.py:130
msgid "Type to search for a transaction to link to this entry"
msgstr ""
msgstr "Введіть для пошуку транзакції, щоб пов’язати її з цим записом"
#: apps/dca/forms.py:126 apps/dca/models.py:179
msgid "Income Transaction"
msgstr ""
msgstr "Дохідна транзакція"
#: apps/dca/forms.py:210
msgid "Link transaction"
msgstr ""
msgstr "Пов’язати транзакцію"
#: apps/dca/forms.py:297 apps/dca/forms.py:298 apps/dca/forms.py:303
#: apps/dca/forms.py:307
msgid "You must provide an account."
msgstr ""
msgstr "Необхідно вказати рахунок."
#: apps/dca/forms.py:312 apps/transactions/forms.py:690
msgid "From and To accounts must be different."
msgstr ""
msgstr "Рахунки «З» та «На» повинні бути різними."
#: apps/dca/forms.py:326
#, python-format
@@ -770,11 +783,11 @@ msgstr ""
#: apps/dca/models.py:16
msgid "Target Currency"
msgstr ""
msgstr "Цільова валюта"
#: apps/dca/models.py:22
msgid "Payment Currency"
msgstr ""
msgstr "Валюта платежу"
#: 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
@@ -782,7 +795,7 @@ msgstr ""
#: apps/transactions/models.py:319 apps/transactions/models.py:569
#: apps/transactions/models.py:770 apps/transactions/models.py:1006
msgid "Notes"
msgstr ""
msgstr "Примітки"
#: apps/dca/models.py:34
msgid "DCA Strategy"
@@ -794,15 +807,15 @@ msgstr ""
#: apps/dca/models.py:156
msgid "Strategy"
msgstr ""
msgstr "Стратегія"
#: apps/dca/models.py:160 templates/dca/fragments/strategy/details.html:54
msgid "Amount Paid"
msgstr ""
msgstr "Сплачена Сума"
#: apps/dca/models.py:163 templates/dca/fragments/strategy/details.html:53
msgid "Amount Received"
msgstr ""
msgstr "Отримана Сума"
#: apps/dca/models.py:186
msgid "DCA Entry"
@@ -826,21 +839,21 @@ msgstr ""
#: apps/dca/views.py:238
msgid "Entry added successfully"
msgstr ""
msgstr "Запис успішно додано"
#: apps/dca/views.py:265
msgid "Entry updated successfully"
msgstr ""
msgstr "Запис успішно оновлено"
#: apps/dca/views.py:291
msgid "Entry deleted successfully"
msgstr ""
msgstr "Запис успішно видалено"
#: apps/export_app/forms.py:14 apps/export_app/forms.py:131
#: templates/includes/navbar.html:150 templates/includes/sidebar.html:255
#: templates/users/fragments/list.html:6 templates/users/pages/index.html:4
msgid "Users"
msgstr ""
msgstr "Користувачі"
#: apps/export_app/forms.py:32 apps/export_app/forms.py:137
#: apps/transactions/models.py:380 templates/includes/navbar.html:58
@@ -850,14 +863,14 @@ msgstr ""
#: templates/recurring_transactions/fragments/table.html:39
#: templates/transactions/pages/transactions.html:5
msgid "Transactions"
msgstr ""
msgstr "Транзакції"
#: apps/export_app/forms.py:38 apps/export_app/forms.py:134
#: apps/transactions/filters.py:64 templates/categories/fragments/list.html:5
#: templates/categories/pages/index.html:4 templates/includes/navbar.html:109
#: templates/includes/sidebar.html:178
msgid "Categories"
msgstr ""
msgstr "Категорії"
#: 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
@@ -880,7 +893,7 @@ msgstr ""
#: templates/recurring_transactions/fragments/list.html:5
#: templates/recurring_transactions/pages/index.html:4
msgid "Recurring Transactions"
msgstr ""
msgstr "Регулярні транзакції"
#: apps/export_app/forms.py:62 apps/export_app/forms.py:138
#: apps/transactions/models.py:583 templates/includes/navbar.html:75
@@ -888,20 +901,20 @@ msgstr ""
#: templates/installment_plans/fragments/list.html:5
#: templates/installment_plans/pages/index.html:4
msgid "Installment Plans"
msgstr ""
msgstr "Плани Розстрочки"
#: apps/export_app/forms.py:74 apps/export_app/forms.py:143
#: templates/exchange_rates_services/fragments/list.html:6
#: templates/exchange_rates_services/pages/index.html:4
#: templates/includes/navbar.html:143 templates/includes/sidebar.html:246
msgid "Automatic Exchange Rates"
msgstr ""
msgstr "Автоматичні Курси Обміну"
#: apps/export_app/forms.py:80 templates/includes/navbar.html:135
#: templates/includes/sidebar.html:226 templates/rules/fragments/list.html:5
#: templates/rules/pages/index.html:4
msgid "Rules"
msgstr ""
msgstr "Правила"
#: apps/export_app/forms.py:86 templates/cotton/transaction/item.html:58
msgid "DCA"
@@ -911,24 +924,24 @@ msgstr ""
#: templates/import_app/fragments/profiles/list.html:5
#: templates/import_app/pages/profiles_index.html:4
msgid "Import Profiles"
msgstr ""
msgstr "Імпортувати Профілі"
#: apps/export_app/forms.py:119 templates/export_app/fragments/export.html:5
#: templates/export_app/pages/index.html:15
msgid "Export"
msgstr ""
msgstr "Експортувати"
#: apps/export_app/forms.py:128
msgid "Import a ZIP file exported from WYGIWYH"
msgstr ""
msgstr "Імпортувати ZIP-файл, експортований із WYGIWYH"
#: apps/export_app/forms.py:129
msgid "ZIP File"
msgstr ""
msgstr "ZIP-Файл"
#: apps/export_app/forms.py:146 apps/rules/models.py:27
msgid "Transaction rules"
msgstr ""
msgstr "Правила транзакцій"
#: apps/export_app/forms.py:148 apps/rules/models.py:68
msgid "Edit transaction action"
@@ -943,52 +956,54 @@ msgstr ""
#: templates/export_app/fragments/restore.html:5
#: templates/export_app/pages/index.html:24
msgid "Restore"
msgstr ""
msgstr "Відновити"
#: apps/export_app/forms.py:196
msgid "Please upload either a ZIP file or at least one CSV file"
msgstr ""
msgstr "Будь ласка, завантажте або ZIP-файл, або принаймні один CSV-файл"
#: apps/export_app/views.py:177
msgid "You have to select at least one export"
msgstr ""
msgstr "Ви повинні обрати принаймні один експорт"
#: apps/export_app/views.py:197
msgid "Data restored successfully"
msgstr ""
msgstr "Дані успішно відновлено"
#: apps/export_app/views.py:209
msgid ""
"There was an error restoring your data. Check the logs for more details."
msgstr ""
"Сталася помилка під час відновлення даних. Перевірте логи для отримання "
"додаткової інформації."
#: apps/import_app/forms.py:49
msgid "Select a file"
msgstr ""
msgstr "Оберіть файл"
#: apps/import_app/forms.py:61
#: templates/import_app/fragments/profiles/list.html:62
#: templates/includes/navbar.html:137 templates/includes/sidebar.html:232
msgid "Import"
msgstr ""
msgstr "Імпортувати"
#: apps/import_app/models.py:15
msgid "YAML Configuration"
msgstr ""
msgstr "YAML Конфігурація"
#: apps/import_app/models.py:19
#: templates/import_app/fragments/profiles/list.html:37
msgid "Version"
msgstr ""
msgstr "Версія"
#: apps/import_app/models.py:30
#, python-brace-format
msgid "Version {number}"
msgstr ""
msgstr "Версія {number}"
#: apps/import_app/models.py:39
msgid "Invalid YAML Configuration: "
msgstr ""
msgstr "Неправильна YAML Конфігурація: "
#: apps/import_app/models.py:45
msgid "Queued"
@@ -1335,7 +1350,7 @@ msgstr ""
#: apps/transactions/filters.py:24 templates/cotton/transaction/item.html:21
#: templates/cotton/transaction/item.html:32 templates/includes/navbar.html:47
#: templates/insights/fragments/category_overview/index.html:61
#: templates/net_worth/net_worth.html:32
#: templates/net_worth/net_worth.html:33
#: templates/transactions/widgets/paid_toggle_button.html:8
#: templates/transactions/widgets/unselectable_paid_toggle_button.html:12
msgid "Projected"
@@ -2190,6 +2205,8 @@ msgid "Current balance"
msgstr ""
#: templates/accounts/fragments/account_reconciliation.html:39
#: templates/net_worth/net_worth.html:105
#: templates/net_worth/net_worth.html:362
msgid "Difference"
msgstr ""
@@ -2565,19 +2582,19 @@ msgstr ""
msgid "Amount Bought"
msgstr ""
#: templates/dca/fragments/strategy/details.html:390
#: templates/dca/fragments/strategy/details.html:392
msgid "Entry Price vs Current Price"
msgstr ""
#: templates/dca/fragments/strategy/details.html:406
#: templates/dca/fragments/strategy/details.html:408
msgid "Days Between Investments"
msgstr ""
#: templates/dca/fragments/strategy/details.html:453
#: templates/dca/fragments/strategy/details.html:455
msgid "Investment Frequency"
msgstr ""
#: templates/dca/fragments/strategy/details.html:455
#: templates/dca/fragments/strategy/details.html:457
msgid "The straighter the blue line, the more consistent your DCA strategy is."
msgstr ""
@@ -2777,7 +2794,7 @@ msgstr ""
#: templates/includes/navbar.html:45
#: templates/insights/fragments/category_overview/index.html:65
#: templates/net_worth/net_worth.html:22
#: templates/net_worth/net_worth.html:23
msgid "Current"
msgstr ""
@@ -3160,25 +3177,29 @@ msgstr ""
msgid "Order by"
msgstr ""
#: templates/net_worth/net_worth.html:40
#: templates/net_worth/net_worth.html:42
#: templates/yearly_overview/pages/overview_by_currency.html:9
msgid "By currency"
msgstr ""
#: templates/net_worth/net_worth.html:75
#: templates/net_worth/net_worth.html:78
msgid "Consolidated"
msgstr ""
#: templates/net_worth/net_worth.html:104
#: templates/net_worth/net_worth.html:101
msgid "Evolution"
msgstr ""
#: templates/net_worth/net_worth.html:128
#: templates/yearly_overview/pages/overview_by_account.html:7
msgid "By account"
msgstr ""
#: templates/net_worth/net_worth.html:211
#: templates/net_worth/net_worth.html:236
msgid "Evolution by currency"
msgstr ""
#: templates/net_worth/net_worth.html:275
#: templates/net_worth/net_worth.html:300
msgid "Evolution by account"
msgstr ""
@@ -3417,15 +3438,15 @@ msgstr ""
#: templates/users/fragments/add.html:5
msgid "Add user"
msgstr ""
msgstr "Додати користувача"
#: templates/users/fragments/edit.html:5
msgid "Edit user"
msgstr ""
msgstr "Редагувати користувача"
#: templates/users/fragments/list.html:30
msgid "Email"
msgstr ""
msgstr "Email"
#: templates/users/fragments/list.html:31
msgid "Superuser"
@@ -3465,7 +3486,7 @@ msgstr ""
#: templates/users/login.html:40
msgid "Login with"
msgstr ""
msgstr "Увiйти за допомогою"
#: templates/yearly_overview/pages/overview_by_account.html:7
#: templates/yearly_overview/pages/overview_by_currency.html:9

File diff suppressed because it is too large Load Diff

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

View File

@@ -1,78 +1,72 @@
{% load i18n %}
<div class="container px-md-3 py-3 column-gap-5">
<div class="tw:text-3xl fw-bold font-monospace tw:w-full mb-3">
<c-ui.fab-single-action
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 %}
<div>{% translate 'Account Groups' %}<span>
<a class="text-decoration-none tw:text-2xl p-1 category-action"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Add" %}"
hx-get="{% url 'account_group_add' %}"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-circle-plus fa-fw"></i></a>
</span></div>
<div>{% translate 'Account Groups' %}</div>
{% endspaceless %}
</div>
<div class="card">
<div class="card-body table-responsive">
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
{% if account_groups %}
<c-config.search></c-config.search>
<table class="table table-hover">
<thead>
<tr>
<th scope="col" class="col-auto"></th>
<th scope="col" class="col">{% translate 'Name' %}</th>
</tr>
</thead>
<tbody>
{% for account_group in account_groups %}
<tr class="account_group">
<td class="col-auto">
<div class="btn-group" role="group" aria-label="{% translate 'Actions' %}">
<a class="btn btn-secondary btn-sm"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Edit" %}"
hx-get="{% url 'account_group_edit' pk=account_group.id %}"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-pencil fa-fw"></i></a>
<a class="btn btn-secondary btn-sm text-danger"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Delete" %}"
hx-delete="{% url 'account_group_delete' pk=account_group.id %}"
hx-trigger='confirmed'
data-bypass-on-ctrl="true"
data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}"
data-confirm-text="{% translate "Yes, delete it!" %}"
_="install prompt_swal"><i class="fa-solid fa-trash fa-fw"></i></a>
{% 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>
</td>
<td class="col">{{ account_group.name }}</td>
<div class="overflow-x-auto">
<table class="table table-zebra">
<thead>
<tr>
<th scope="col" class="table-col-auto"></th>
<th scope="col">{% translate 'Name' %}</th>
</tr>
{% endfor %}
</tbody>
</table>
</thead>
<tbody>
{% for account_group in account_groups %}
<tr class="account_group">
<td class="table-col-auto">
<div class="join" role="group" aria-label="{% translate 'Actions' %}">
<a class="btn btn-secondary btn-sm join-item"
role="button"
data-tippy-content="{% translate "Edit" %}"
hx-get="{% url 'account_group_edit' pk=account_group.id %}"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-pencil fa-fw"></i></a>
{% if not account_group.owner %}
<a class="btn btn-secondary btn-sm join-item"
role="button"
data-tippy-content="{% 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 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-trigger='confirmed'
data-bypass-on-ctrl="true"
data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}"
data-confirm-text="{% translate "Yes, delete it!" %}"
_="install prompt_swal"><i class="fa-solid fa-trash fa-fw"></i></a>
</div>
</td>
<td>{{ account_group.name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<c-msg.empty title="{% translate "No account groups" %}" remove-padding></c-msg.empty>
{% endif %}

View File

@@ -9,65 +9,59 @@
<form hx-post="{% url 'account_reconciliation' %}">
{% csrf_token %}
{{ form.management_form }}
<div class="accordion accordion-flush" id="balanceAccordionFlush">
<div class="join join-vertical w-full" id="balanceAccordionFlush">
{% for form in form.forms %}
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
data-bs-target="#flush-collapse-{{ forloop.counter0 }}" aria-expanded="false"
aria-controls="flush-collapseOne">
{% if form.account_group %}<span class="badge text-bg-primary me-2">{{ form.account_group.name }}</span>{% endif %}{{ form.account_name }}
</button>
</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" }}"
data-decimal-places="{{ form.currency_decimal_places }}"
id="amount-{{ forloop.counter0 }}">
{% currency_display amount=form.current_balance prefix=form.currency_prefix suffix=form.currency_suffix decimal_places=form.currency_decimal_places %}
</div>
</div>
<div>
{% crispy form %}
</div>
<div class="mb-3">
<div class="form-label">
{% translate 'Difference' %}
</div>
<div _="on input from #id_form-{{ forloop.counter0 }}-new_balance
set original_amount to parseFloat('{{ form.current_balance|floatformat:"-40u" }}')
then set prefix to '{{ form.currency_prefix }}'
then set suffix to '{{ form.currency_suffix }}'
then set decimal_places to {{ form.currency_decimal_places }}
then call parseLocaleNumber(#id_form-{{ forloop.counter0 }}-new_balance.value)
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 log diff
then set format_new_amount to
Intl.NumberFormat(
undefined,
{
minimumFractionDigits: decimal_places,
maximumFractionDigits: decimal_places,
roundingMode: 'trunc'
}
).format(diff)
then set formatted_string to `${prefix}${format_new_amount}${suffix}`
then put formatted_string into me if diff else
put '-' into me">-</div>
<c-ui.components.collapse>
<c-slot name="title">
{% if form.account_group %}<span class="badge badge-primary badge-outline me-2">{{ form.account_group.name }}</span>{% endif %}{{ form.account_name }}
</c-slot>
<c-slot name="content">
<div class="fieldset">
<span class="fieldset-legend">{% translate 'Current balance' %}</span>
<div data-amount="{{ form.current_balance|floatformat:"-40u" }}"
data-decimal-places="{{ form.currency_decimal_places }}"
id="amount-{{ forloop.counter0 }}" class="text-base">
<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>
{% crispy form %}
</div>
<div class="fieldset">
<span class="fieldset-legend">{% translate 'Difference' %}</span>
<div class="text-base"
_="on input from #id_form-{{ forloop.counter0 }}-new_balance
set original_amount to parseFloat('{{ form.current_balance|floatformat:"-40u" }}')
then set prefix to '{{ form.currency_prefix }}'
then set suffix to '{{ form.currency_suffix }}'
then set decimal_places to {{ form.currency_decimal_places }}
then call parseLocaleNumber(#id_form-{{ forloop.counter0 }}-new_balance.value)
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 format_new_amount to
Intl.NumberFormat(
undefined,
{
minimumFractionDigits: decimal_places,
maximumFractionDigits: decimal_places,
roundingMode: 'trunc'
}
).format(diff)
then set formatted_string to `${prefix}${format_new_amount}${suffix}`
then put formatted_string into me if diff else put '-' into me">-</div>
</div>
</c-slot>
</c-ui.components.collapse>
{% endfor %}
</div>
<div class="mt-3">
<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>
</form>

View File

@@ -1,101 +1,96 @@
{% load i18n %}
<div class="container px-md-3 py-3 column-gap-5">
<div class="tw:text-3xl fw-bold font-monospace tw:w-full mb-3">
<c-ui.fab-single-action
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 %}
<div>{% translate 'Accounts' %}<span>
<a class="text-decoration-none tw:text-2xl p-1 category-action"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Add" %}"
hx-get="{% url 'account_add' %}"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-circle-plus fa-fw"></i></a>
</span></div>
<div>{% translate 'Accounts' %}</div>
{% endspaceless %}
</div>
<div class="card">
<div class="card-body table-responsive">
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
{% if accounts %}
<c-config.search></c-config.search>
<table class="table table-hover text-nowrap">
<thead>
<tr>
<th scope="col" class="col-auto"></th>
<th scope="col" class="col">{% translate 'Name' %}</th>
<th scope="col" class="col">{% translate 'Group' %}</th>
<th scope="col" class="col">{% translate 'Currency' %}</th>
<th scope="col" class="col">{% translate 'Exchange Currency' %}</th>
<th scope="col" class="col">{% translate 'Is Asset' %}</th>
<th scope="col" class="col">{% translate 'Archived' %}</th>
</tr>
</thead>
<tbody>
{% for account in accounts %}
<tr class="account">
<td class="col-auto">
<div class="btn-group" role="group" aria-label="{% translate 'Actions' %}">
<a class="btn btn-secondary btn-sm"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Edit" %}"
hx-get="{% url 'account_edit' pk=account.id %}"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-pencil fa-fw"></i></a>
<a class="btn btn-secondary btn-sm text-danger"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Delete" %}"
hx-delete="{% url 'account_delete' pk=account.id %}"
hx-trigger='confirmed'
data-bypass-on-ctrl="true"
data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}"
data-confirm-text="{% translate "Yes, delete it!" %}"
_="install prompt_swal"><i class="fa-solid fa-trash fa-fw"></i></a>
{% 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>
</td>
<td class="col">{{ account.name }}</td>
<td class="col">{{ account.group.name }}</td>
<td class="col">{{ account.currency }}</td>
<td class="col">{% 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 class="col">{% if account.is_archived %}<i class="fa-solid fa-solid fa-check text-success"></i>{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
<c-config.search></c-config.search>
<div class="overflow-x-auto">
<table class="table table-zebra">
<thead>
<tr>
<th scope="col" class="table-col-auto"></th>
<th scope="col">{% translate 'Name' %}</th>
<th scope="col">{% translate 'Group' %}</th>
<th scope="col">{% translate 'Currency' %}</th>
<th scope="col">{% translate 'Exchange Currency' %}</th>
<th scope="col">{% translate 'Is Asset' %}</th>
<th scope="col">{% translate 'Archived' %}</th>
</tr>
</thead>
<tbody>
{% for account in accounts %}
<tr class="account">
<td class="table-col-auto">
<div class="join" role="group" aria-label="{% translate 'Actions' %}">
<a class="btn btn-secondary btn-sm join-item"
role="button"
data-tippy-content="{% translate "Edit" %}"
hx-get="{% url 'account_edit' pk=account.id %}"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-pencil fa-fw"></i></a>
{% if not account.owner %}
<a class="btn btn-secondary btn-sm join-item"
role="button"
data-tippy-content="{% 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 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-trigger='confirmed'
data-bypass-on-ctrl="true"
data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}"
data-confirm-text="{% translate "Yes, delete it!" %}"
_="install prompt_swal"><i class="fa-solid fa-trash fa-fw"></i></a>
</div>
</td>
<td>{{ account.name }}</td>
<td>{{ account.group.name }}</td>
<td>{{ account.currency }}</td>
<td>{% if account.exchange_currency %}{{ account.exchange_currency }}{% else %}-{% endif %}</td>
<td>{% if account.is_asset %}<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>
{% endfor %}
</tbody>
</table>
</div>
{% 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 %}
</div>
</div>

View File

@@ -2,67 +2,67 @@
{% load i18n %}
<div>
<div class="tw:hidden tw:lg:grid tw:lg:grid-cols-7 tw:gap-4 tw:lg:gap-0">
<div class="border-start border-top border-bottom p-2 text-center">
<div class="hidden lg:grid lg:grid-cols-7 gap-4 lg:gap-0 bg-base-200">
<div class="border-l border-t border-b border-base-300 p-2 text-center">
{% translate 'MON' %}
</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' %}
</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' %}
</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' %}
</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' %}
</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' %}
</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' %}
</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 %}
{% 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-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="tw:lg:hidden text-start w-100">{{ date.date|date:"l"|lower }}</div>
<div class="text-end w-100">{{ date.day }}</div>
<div class="card-header border-0 bg-transparent text-end flex justify-between p-2 w-full">
<div class="lg:hidden text-start w-full">{{ date.date|date:"l"|lower }}</div>
<div class="text-end w-full">{{ date.day }}</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 %}
{% if transaction.is_paid %}
{% if transaction.type == "IN" and not transaction.account.is_asset %}
<i class="fa-solid fa-circle-check tw:text-green-400" data-bs-toggle="tooltip" data-bs-title="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Income' %}{% endif %}"></i>
<i class="fa-solid fa-circle-check text-success" data-tippy-content="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Income' %}{% endif %}"></i>
{% elif transaction.type == "IN" and transaction.account.is_asset %}
<i class="fa-solid fa-circle-check tw:text-green-300" data-bs-toggle="tooltip" data-bs-title="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Income' %}{% endif %}"></i>
<i class="fa-solid fa-circle-check 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 %}
<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 %}
<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 %}
{% else %}
{% if transaction.type == "IN" and not transaction.account.is_asset %}
<i class="fa-regular fa-circle tw:text-green-400" data-bs-toggle="tooltip" data-bs-title="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Income' %}{% endif %}"></i>
<i class="fa-regular fa-circle text-success" data-tippy-content="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Income' %}{% endif %}"></i>
{% elif transaction.type == "IN" and transaction.account.is_asset %}
<i class="fa-regular fa-circle tw:text-green-300" data-bs-toggle="tooltip" data-bs-title="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Income' %}{% endif %}"></i>
<i class="fa-regular fa-circle 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 %}
<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 %}
<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 %}
{% endfor %}
</div>
</div>
{% 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 %}
{% endfor %}
</div>

View File

@@ -3,7 +3,6 @@
{% load i18n %}
{% load month_name %}
{% load static %}
{% load webpack_loader %}
{% block title %}{% translate 'Monthly Overview' %} :: {{ month|month_name }}/{{ year }}{% endblock %}
@@ -13,45 +12,35 @@
{% endblock %}
{% block content %}
<div class="container px-md-3 py-3 column-gap-5">
<div class="row mb-3 gx-xl-4 gy-3 mb-4">
<div class="container">
<div class="flex flex-wrap mb-4 gap-x-xl-4 gap-y-3">
{# Date picker#}
<div class="col-12 col-xl-4 flex-row align-items-center d-flex">
<div class="tw:text-base h-100 align-items-center d-flex">
<a role="button"
class="pe-4 py-2"
hx-boost="true"
hx-trigger="click, previous_month from:window"
href="{% url 'calendar' month=previous_month year=previous_year %}"><i
class="fa-solid fa-chevron-left"></i></a>
</div>
<div class="tw:text-3xl fw-bold font-monospace tw:w-full text-center"
<div class="w-full xl:w-4/12 flex-row items-center flex">
<a role="button"
hx-boost="true"
class="btn btn-ghost"
hx-trigger="click, previous_month from:window"
href="{% url 'calendar' month=previous_month year=previous_year %}">
<i class="fa-solid fa-chevron-left"></i>
</a>
<div class="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-target="#generic-offcanvas-left"
hx-trigger="click, date_picker from:window"
hx-vals='{"month": {{ month }}, "year": {{ year }}, "for": "calendar", "field": "date"}' role="button">
{{ month|month_name }} {{ year }}
</div>
<div class="tw:text-base mx-2 h-100 align-items-center d-flex">
<a role="button"
class="ps-3 py-2"
hx-boost="true"
hx-trigger="click, next_month from:window"
href="{% url 'calendar' month=next_month year=next_year %}">
<i class="fa-solid fa-chevron-right"></i>
</a>
</div>
</div>
{# Action buttons#}
<div class="col-12 col-xl-8">
{# <c-ui.quick-transactions-buttons#}
{# :year="year"#}
{# :month="month"#}
{# ></c-ui.quick-transactions-buttons>#}
<a role="button"
hx-boost="true"
class="btn btn-ghost"
hx-trigger="click, next_month from:window"
href="{% url 'calendar' month=next_month year=next_year %}">
<i class="fa-solid fa-chevron-right"></i>
</a>
</div>
</div>
<div class="row">
<div class="show-loading" hx-get="{% url 'calendar_list' month=month year=year %}"
<div class="flex flex-wrap">
<div class="show-loading w-full" hx-get="{% url 'calendar_list' month=month year=year %}"
hx-trigger="load, updated from:window, selective_update from:window, every 10m"></div>
</div>
</div>

View File

@@ -1,32 +1,29 @@
{% load i18n %}
<div class="container px-md-3 py-3 column-gap-5">
<div class="tw:text-3xl fw-bold font-monospace tw:w-full mb-3">
<c-ui.fab-single-action
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 %}
<div>{% translate 'Categories' %}<span>
<a class="text-decoration-none tw:text-2xl p-1 category-action"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Add" %}"
hx-get="{% url 'category_add' %}"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-circle-plus fa-fw"></i></a>
</span></div>
<div>{% translate 'Categories' %}</div>
{% endspaceless %}
</div>
<div class="card">
<div class="card-header">
<ul class="nav nav-pills card-header-pills" id="myTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" data-bs-toggle="tab" type="button" role="tab" aria-selected="true" hx-get="{% url 'categories_table_active' %}" hx-trigger="load, click" hx-target="#categories-table">{% translate 'Active' %}</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" hx-get="{% url 'categories_table_archived' %}" hx-target="#categories-table" data-bs-toggle="tab" type="button" role="tab" aria-selected="false">{% translate 'Archived' %}</button>
</li>
</ul>
<div class="card bg-base-100 shadow-xl">
<div class="card-header bg-base-200 p-4 rounded-box">
<div role="tablist" class="tabs tabs-border">
<input type="radio" name="installment_plan_tabs" class="tab" aria-label="{% translate 'Active' %}"
checked="checked"
hx-get="{% url 'categories_table_active' %}" hx-trigger="load, click" hx-target="#categories-table"
hx-indicator="#categories-table"/>
<input type="radio" name="installment_plan_tabs" class="tab" aria-label="{% translate 'Archived' %}"
hx-get="{% url 'categories_table_archived' %}" hx-trigger="click" hx-target="#categories-table"
hx-indicator="#categories-table"/>
</div>
</div>
<div class="card-body">
<div id="categories-table"></div>
<div id="categories-table" class="show-loading"></div>
</div>
</div>
</div>

View File

@@ -6,72 +6,70 @@
<div class="show-loading" hx-get="{% url 'categories_table_archived' %}" hx-trigger="updated from:window"
hx-swap="outerHTML">
{% endif %}
{% if categories %}
<div class="table-responsive">
{% if categories %}
<div>
<c-config.search></c-config.search>
<table class="table table-hover">
<thead>
<tr>
<th scope="col" class="col-auto"></th>
<th scope="col" class="col">{% translate 'Name' %}</th>
<th scope="col" class="col">{% translate 'Muted' %}</th>
</tr>
</thead>
<tbody>
{% for category in categories %}
<tr class="category">
<td class="col-auto text-center">
<div class="btn-group" role="group" aria-label="{% translate 'Actions' %}">
<a class="btn btn-secondary btn-sm"
role="button"
data-bs-toggle="tooltip"
hx-swap="innerHTML"
data-bs-title="{% translate "Edit" %}"
hx-get="{% url 'category_edit' category_id=category.id %}"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-pencil fa-fw"></i></a>
<a class="btn btn-secondary btn-sm text-danger"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Delete" %}"
hx-delete="{% url 'category_delete' category_id=category.id %}"
hx-trigger='confirmed'
hx-swap="innerHTML"
data-bypass-on-ctrl="true"
data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}"
data-confirm-text="{% translate "Yes, delete it!" %}"
_="install prompt_swal"><i class="fa-solid fa-trash fa-fw"></i></a>
{% 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>
</td>
<td class="col">{{ category.name }}</td>
<td class="col">
{% if category.mute %}<i class="fa-solid fa-check text-success"></i>{% endif %}
</td>
<div class="overflow-x-auto">
<table class="table table-zebra">
<thead>
<tr>
<th scope="col" class="table-col-auto"></th>
<th scope="col">{% translate 'Name' %}</th>
<th scope="col">{% translate 'Muted' %}</th>
</tr>
{% endfor %}
</tbody>
</table>
</thead>
<tbody>
{% for category in categories %}
<tr class="category">
<td class="table-col-auto text-center">
<div class="join" role="group" aria-label="{% translate 'Actions' %}">
<a class="btn btn-secondary btn-sm join-item"
role="button"
hx-swap="innerHTML"
data-tippy-content="{% translate "Edit" %}"
hx-get="{% url 'category_edit' category_id=category.id %}"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-pencil fa-fw"></i></a>
{% if not category.owner %}
<a class="btn btn-secondary btn-sm join-item"
role="button"
data-tippy-content="{% 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 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-trigger='confirmed'
hx-swap="innerHTML"
data-bypass-on-ctrl="true"
data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}"
data-confirm-text="{% translate "Yes, delete it!" %}"
_="install prompt_swal"><i class="fa-solid fa-trash fa-fw"></i></a>
</div>
</td>
<td>{{ category.name }}</td>
<td>
{% if category.mute %}<i class="fa-solid fa-check text-success"></i>{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% else %}
<c-msg.empty title="{% translate "No categories" %}" remove-padding></c-msg.empty>
{% endif %}
{% else %}
<c-msg.empty title="{% translate "No categories" %}" remove-padding></c-msg.empty>
{% endif %}
</div>

View File

@@ -7,49 +7,33 @@
{% block body %}
{% 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 %}
<li class="nav-item" role="presentation">
<button class="nav-link{% if x.grouper == current_year %} active{% endif %}"
id="{{ x.grouper }}"
data-bs-toggle="tab"
data-bs-target="#{{ x.grouper }}-pane"
type="button"
role="tab"
aria-controls="{{ x.grouper }}-pane"
aria-selected="{% if x.grouper == current_year %}true{% else %}false{% endif %}">
{{ x.grouper }}
</button>
</li>
{% endfor %}
</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">
<input type="radio"
name="year_tabs"
role="tab"
class="tab"
aria-label="{{ x.grouper }}"
id="tab-{{ x.grouper }}"
{% if x.grouper == current_year %}checked="checked"{% endif %} />
<div role="tabpanel" class="tab-content" id="{{ x.grouper }}-pane">
<ul class="menu bg-base-100 w-full" id="month-year-list" hx-boost="true">
{% for month_data in x.list %}
<li class="list-group-item tw:hover:bg-zinc-900
{% if month_data.month == current_month and month_data.year == current_year %} disabled bg-primary{% endif %}"
{% if month_data.month == current_month and month_data.year == current_year %}aria-disabled="true"{% endif %}>
<div class="d-flex justify-content-between">
<a class="text-decoration-none stretched-link {% if month_data.month == current_month and month_data.year == current_year %} text-black{% endif %}"
href={{ month_data.url }}>
{{ month_data.month|month_name }}</a>
<span class="badge text-bg-secondary">{{ month_data.transaction_count }}</span>
</div>
<li {% if month_data.month == current_month and month_data.year == current_year %}class="disabled"{% 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 %}>
<span class="flex-1">{{ month_data.month|month_name }}</span>
<span class="badge badge-primary">{{ month_data.transaction_count }}</span>
</a>
</li>
{% endfor %}
</ul>
</div>
{% endfor %}
</div>
<hr>
<hr class="hr my-4">
<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>
{% endblock %}

View File

@@ -2,19 +2,25 @@
{% load toast_bg %}
{% if 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"
aria-live="assertive"
aria-atomic="true">
<div class="toast-header">
<i class="{{ message.tags | toast_icon }} fa-fw me-1"></i>
<strong class="me-auto">{{ message.tags | toast_title }}</strong>
<div class="flex items-center justify-between w-full">
<div class="flex items-center gap-2">
<i class="{{ message.tags | toast_icon }} fa-fw"></i>
<div>
<strong>{{ message.tags | toast_title }}</strong>
<div>{{ message }}</div>
</div>
</div>
<button type="button"
class="btn-close"
data-bs-dismiss="toast"
aria-label={% translate 'Close' %}></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 class="toast-body">{{ message }}</div>
</div>
{% endfor %}
{% endif %}

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#}
<div class="tw:lg:w-[15vw]"></div>
<div class="tw:lg:ml-[16vw]"></div>
<div class="lg:w-[15vw]"></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>

View File

@@ -1,10 +1,23 @@
{% load currency_display %}
{% currency_display amount=amount prefix=prefix suffix=suffix decimal_places=decimal_places as formatted_amount %}
{% if not divless %}
<div class="{% if text_end %}text-end{% elif text_start %}text-start{% endif %}">
{% endif %}
<span class="amount{% if color == 'grey' or color == "gray" %} tw:text-gray-500{% elif color == 'green' %} tw:text-green-400{% elif color == 'red' %} tw:text-red-400{% endif %} {{ custom_class }}"
data-original-value="{% currency_display amount=amount prefix=prefix suffix=suffix decimal_places=decimal_places %}"
<span class="amount
{% 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" }}">
</span><span>{{ slot }}</span>
{% if not divless %}

View File

@@ -1,33 +1,14 @@
<div class="tw:min-h-16">
<div
id="fab-wrapper"
class="tw:fixed tw:bottom-5 tw:right-5 tw:ml-auto tw:w-max tw:flex tw:flex-col tw:items-end mt-5 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">
{% load i18n%}
{{ 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>
<button
class="btn btn-primary rounded-circle p-0 tw:w-12 tw:h-12 tw:flex tw:items-center tw:justify-center tw:shadow-lg tw:hover:shadow-xl tw:focus:shadow-xl tw:transition-all tw:duration-300 tw:ease-in-out"
_="
on click or focusout
if #menu.classList.contains('tw:invisible') and event.type === 'click'
add .{'tw:rotate-45'} to #fab-icon
remove .{'tw:invisible'} from #menu
remove .{'tw:hidden'} from #menu
remove .{'tw:opacity-0'} from #menu
else
wait 0.2s
remove .{'tw:rotate-45'} from #fab-icon
add .{'tw:invisible'} to #menu
add .{'tw:hidden'} to #menu
add .{'tw:opacity-0'} to #menu
end
"
>
<i id="fab-icon" class="fa-solid fa-plus tw:text-3xl tw:transition-transform tw:duration-300 tw:ease-in-out"></i>
</button>
</div>
</div>
<div class="fab-close">
{% trans 'Close' %} <span class="btn btn-circle btn-lg btn-error"><i class="fa-solid fa-xmark text-2xl fa-fw"></i></span>
</div>
{{ slot }}
</div>

View File

@@ -1,11 +1,6 @@
{% load i18n %}
<div class="tw:relative fab-item">
<button class="btn btn-sm btn-{{ color }}"
hx-get="{{ url }}"
hx-trigger="{{ hx_trigger }}"
hx-target="{{ hx_target }}"
hx-vals='{{ hx_vals }}'>
<i class="{{ icon }} me-2"></i>
{{ title }}
</button>
</div>
<div hx-get="{{ url }}"
hx-trigger="{{ hx_trigger }}"
hx-target="{{ hx_target }}"
hx-vals='{{ hx_vals }}'>
<span class="bg-neutral/60 text-neutral-content rounded-box p-2">{{ title }}</span>
<button class="btn btn-lg btn-circle btn-{{color}}"><i class="{{ icon }} fa-fw"></i></button></div>

View File

@@ -1,12 +1,12 @@
<li class="tw:lg:hidden tw:lg:group-hover:block">
<div class="d-flex align-items-center" data-bs-toggle="collapse" href="#{{ title|slugify }}" role="button"
<li class="lg:hidden lg:group-hover:block">
<div class="flex items-center" data-bs-toggle="collapse" href="#{{ title|slugify }}" role="button"
aria-expanded="false" aria-controls="{{ title|slugify }}">
<span
class="text-muted small fw-bold text-uppercase tw:lg:hidden tw:lg:group-hover:inline me-2">{{ title }}</span>
<hr class="flex-grow-1"/>
<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>
class="text-base-content/60 text-sm font-bold uppercase lg:hidden lg:group-hover:inline me-2">{{ title }}</span>
<hr class="flex-grow"/>
<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>
</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 }}
</div>

View File

@@ -1,6 +1,8 @@
<li>
<div class="d-flex align-items-center">
<span class="sidebar-menu-header text-muted small fw-bold text-uppercase me-2">{{ title }}</span>
<hr class="flex-grow-1"/>
<div class="flex items-center min-h-6">
{% if title %}
<span class="sidebar-menu-header text-base-content/60 text-xs font-bold uppercase mr-3">{{ title }}</span>
{% endif %}
<hr class="hr grow"/>
</div>
</li>

View File

@@ -1,14 +1,13 @@
{% load active_link %}
<li>
<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 %}
data-bs-placement="right"
data-bs-toggle="tooltip"
data-bs-title="{{ tooltip }}"
data-tippy-placement="right"
data-tippy-content="{{ tooltip }}"
{% endif %}>
<i class="{{ icon }} fa-fw"></i>
<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>
</li>

View File

@@ -2,15 +2,14 @@
<li>
<a href="{{ url }}"
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 %}
data-bs-placement="right"
data-bs-toggle="tooltip"
data-bs-title="{{ tooltip }}"
data-tippy-placement="right"
data-tippy-content="{{ tooltip }}"
{% endif %}>
<i class="{{ icon }} fa-fw"></i>
<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>
</li>

View File

@@ -1,8 +1,8 @@
{% load i18n %}
<div id="search" class="mb-3">
<label class="w-100">
<label class="w-full">
<input type="search"
class="form-control"
class="input input-bordered w-full"
placeholder="{% translate 'Search' %}"
_="on input or search
show < tbody>tr /> in <table/>

View File

@@ -1,9 +1,9 @@
<div class="row {% if not remove_padding %}p-5{% endif %}">
<div class="col {% if not remove_padding %}p-5{% endif %}">
<div class="grid grid-cols-1 {% if not remove_padding %}p-5{% endif %}">
<div class="{% if not remove_padding %}p-5{% endif %}">
<div class="text-center">
<i class="{% if icon %}{{ icon }}{% else %}fa-solid fa-circle-xmark{% endif %} tw:text-6xl"></i>
<p class="lead mt-4 mb-0">{{ title }}</p>
<p class="tw:text-gray-500">{{ subtitle }}</p>
<i class="{% if icon %}{{ icon }}{% else %}fa-solid fa-circle-xmark{% endif %} text-6xl"></i>
<p class="text-lg mt-4 mb-0">{{ title }}</p>
<p class="text-subtle">{{ subtitle }}</p>
</div>
</div>
</div>

View File

@@ -1,23 +1,22 @@
{% load markdown %}
{% load i18n %}
<div
class="transaction {% if transaction.type == "EX" %}expense{% else %}income{% endif %} tw:group/transaction tw:relative tw:hover:z-10">
<div class="d-flex my-1">
class="transaction {% if transaction.type == "EX" %}expense{% else %}income{% endif %} group/transaction">
<div class="flex my-1">
{% if not disable_selection or not dummy %}
<label class="px-3 d-flex align-items-center justify-content-center">
<input class="form-check-input" type="checkbox" name="transactions" value="{{ transaction.id }}"
<label class="px-3 flex! items-center justify-center">
<input class="checkbox" type="checkbox" name="transactions" value="{{ transaction.id }}"
id="check-{{ transaction.id }}" aria-label="{% translate 'Select' %}" hx-preserve>
</label>
{% endif %}
<div class="tw:border-s-4 tw:border-e-0 tw:border-t-0 tw:border-b-0 border-bottom
tw:hover:bg-zinc-900 p-2 {% if transaction.account.is_asset %}tw:border-dashed{% else %}tw:border-solid{% endif %}
{% if transaction.type == "EX" %}tw:border-red-500{% else %}tw:border-green-500{% endif %} tw:relative
w-100 transaction-item">
<div class="row font-monospace tw:text-sm align-items-center">
<div
class="col-lg-auto col-12 d-flex align-items-center tw:text-2xl tw:lg:text-xl text-lg-center text-center p-0 ps-1">
<div class="border-s-4 border-e-0 border-t-0
hover:bg-base-200 p-2 {% if transaction.account.is_asset %}border-dashed{% else %}border-solid{% endif %}
{% if transaction.type == "EX" %}border-s-error{% else %}border-s-success{% endif %} relative
w-full transaction-item bg-base-100 rounded">
<div class="flex flex-wrap font-mono text-sm items-center">
<div class="lg:w-auto w-full flex items-center text-2xl lg:text-xl lg:text-center text-center p-0 cursor-pointer">
{% 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 %}"
role="button"
{% if not dummy %}
@@ -28,7 +27,7 @@
class="fa-regular fa-circle"></i>{% endif %}
</a>
{% 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 %}">
{% if transaction.is_paid %}<i class="fa-regular fa-circle-check"></i>{% else %}<i
class="fa-regular fa-circle"></i>{% endif %}
@@ -36,79 +35,79 @@
{% endif %}
</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#}
<div class="row mb-2 mb-lg-1 tw:text-gray-400">
<div class="col-auto pe-1"><i class="fa-solid fa-calendar fa-fw me-1 fa-xs"></i></div>
<div class="flex flex-wrap mb-2 lg:mb-1 text-base-content/70">
<div class="w-auto pe-1"><i class="fa-solid fa-calendar fa-fw mr-1 fa-xs"></i></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>
{# 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 %}
<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 %}
<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 %}
{% 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 %}
{% 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 %}
{% endspaceless %}
</div>
<div class="tw:text-gray-400 tw:text-sm">
<div class="text-base-content/70 text-sm">
{# Entities #}
{% comment %} First, check for the highest priority: a valid 'overriden_entities' list. {% endcomment %}
{% if overriden_entities %}
<div class="row mb-2 mb-lg-1 tw:text-gray-400">
<div class="col-auto pe-1"><i class="fa-solid fa-user-group fa-fw me-1 fa-xs"></i></div>
<div class="col ps-0">{{ overriden_entities|join:", " }}</div>
<div class="flex flex-wrap mb-2 lg:mb-1 text-base-content/70">
<div class="w-auto pe-1"><i class="fa-solid fa-user-group fa-fw mr-1 fa-xs"></i></div>
<div class="flex-1 ps-0">{{ overriden_entities|join:", " }}</div>
</div>
{% 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 %}
<div class="row mb-2 mb-lg-1 tw:text-gray-400">
<div class="col-auto pe-1"><i class="fa-solid fa-user-group fa-fw me-1 fa-xs"></i></div>
<div class="col ps-0">{{ transaction.entities.all|join:", " }}</div>
<div class="flex flex-wrap mb-2 lg:mb-1 text-base-content/70">
<div class="w-auto pe-1"><i class="fa-solid fa-user-group fa-fw mr-1 fa-xs"></i></div>
<div class="flex-1 ps-0">{{ transaction.entities.all|join:", " }}</div>
</div>
{% endif %}
{# Notes#}
{% if transaction.notes %}
<div class="row mb-2 mb-lg-1 tw:text-gray-400">
<div class="col-auto pe-1"><i class="fa-solid fa-align-left fa-fw me-1 fa-xs"></i></div>
<div class="col ps-0">{{ transaction.notes | limited_markdown | linebreaksbr }}</div>
<div class="flex flex-wrap mb-2 lg:mb-1 text-base-content/70">
<div class="w-auto pe-1"><i class="fa-solid fa-align-left fa-fw mr-1 fa-xs"></i></div>
<div class="flex-1 ps-0">{{ transaction.notes | limited_markdown | linebreaksbr }}</div>
</div>
{% endif %}
{# Category#}
{% if transaction.category %}
<div class="row mb-2 mb-lg-1 tw:text-gray-400">
<div class="col-auto pe-1"><i class="fa-solid fa-icons fa-fw me-1 fa-xs"></i></div>
<div class="col ps-0">{{ transaction.category.name }}</div>
<div class="flex flex-wrap mb-2 lg:mb-1 text-base-content/70">
<div class="w-auto pe-1"><i class="fa-solid fa-icons fa-fw mr-1 fa-xs"></i></div>
<div class="flex-1 ps-0">{{ transaction.category.name }}</div>
</div>
{% endif %}
{# Tags#}
{% comment %} First, check for the highest priority: a valid 'overriden_tags' list. {% endcomment %}
{% if overriden_tags %}
<div class="row mb-2 mb-lg-1 tw:text-gray-400">
<div class="col-auto pe-1"><i class="fa-solid fa-hashtag fa-fw me-1 fa-xs"></i></div>
<div class="col ps-0">{{ overriden_tags|join:", " }}</div>
<div class="flex flex-wrap mb-2 lg:mb-1 text-base-content/70">
<div class="w-auto pe-1"><i class="fa-solid fa-hashtag fa-fw mr-1 fa-xs"></i></div>
<div class="flex-1 ps-0">{{ overriden_tags|join:", " }}</div>
</div>
{% 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 %}
<div class="row mb-2 mb-lg-1 tw:text-gray-400">
<div class="col-auto pe-1"><i class="fa-solid fa-hashtag fa-fw me-1 fa-xs"></i></div>
<div class="col ps-0">{{ transaction.tags.all|join:", " }}</div>
<div class="flex flex-wrap mb-2 lg:mb-1 text-base-content/70">
<div class="w-auto pe-1"><i class="fa-solid fa-hashtag fa-fw mr-1 fa-xs"></i></div>
<div class="flex-1 ps-0">{{ transaction.tags.all|join:", " }}</div>
</div>
{% endif %}
</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 %}">
<div class="main-amount mb-2 mb-lg-0">
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 lg:mb-0">
<c-amount.display
:amount="transaction.amount"
:prefix="transaction.account.currency.prefix"
@@ -120,7 +119,7 @@
{% if not dummy %}
{% with exchanged=transaction.exchanged_amount %}
{% if exchanged %}
<div class="exchanged-amount mb-2 mb-lg-0">
<div class="exchanged-amount mb-2 lg:mb-0">
<c-amount.display
:amount="exchanged.amount"
:prefix="exchanged.prefix"
@@ -136,112 +135,106 @@
</div>
</div>
{% if not dummy %}
<div>
<div class="z-1000">
{# Item actions#}
<div
class="transaction-actions tw:absolute! tw:left-1/2 tw:top-0 tw:-translate-x-1/2 tw:-translate-y-1/2 tw:invisible tw:group-hover/transaction:visible d-flex flex-row card">
<div class="card-body p-1 shadow-lg d-flex flex-row gap-1">
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 flex flex-row gap-1">
{% if not transaction.deleted %}
<a class="btn btn-secondary btn-sm transaction-action"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Edit" %}"
hx-get="{% url 'transaction_edit' transaction_id=transaction.id %}"
hx-target="#generic-offcanvas" hx-swap="innerHTML">
<a class="btn btn-soft btn-sm transaction-action"
role="button"
hx-get="{% url 'transaction_edit' transaction_id=transaction.id %}"
hx-target="#generic-offcanvas" hx-swap="innerHTML"
data-tippy-content="{% translate "Edit" %}">
<i class="fa-solid fa-pencil fa-fw"></i></a>
<a class="btn btn-secondary btn-sm transaction-action"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Delete" %}"
hx-delete="{% url 'transaction_delete' transaction_id=transaction.id %}"
hx-trigger='confirmed'
data-bypass-on-ctrl="true"
data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}"
data-confirm-text="{% translate "Yes, delete it!" %}"
_="install prompt_swal"><i class="fa-solid fa-trash fa-fw text-danger"></i>
<a class="btn btn-error btn-soft btn-sm transaction-action"
role="button"
hx-delete="{% url 'transaction_delete' transaction_id=transaction.id %}"
hx-trigger='confirmed'
data-tippy-content="{% translate "Delete" %}"
data-bypass-on-ctrl="true"
data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}"
data-confirm-text="{% translate "Yes, delete it!" %}"
_="install prompt_swal"><i class="fa-solid fa-trash fa-fw"></i>
</a>
<button class="btn btn-secondary btn-sm transaction-action" type="button" data-bs-toggle="dropdown"
aria-expanded="false">
<i class="fa-solid fa-ellipsis fa-fw"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end dropdown-menu-md-start">
{% if transaction.account.is_untracked_by %}
<li>
<a class="dropdown-item disabled d-flex align-items-center" aria-disabled="true">
<i class="fa-solid fa-eye fa-fw me-2"></i>
<div>
{% translate 'Show on summaries' %}
<div
class="d-block text-body-secondary tw:text-xs tw:font-medium">{% translate 'Controlled by account' %}</div>
</div>
</a>
<button class="btn btn-soft btn-sm transaction-action" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa-solid fa-ellipsis fa-fw"></i>
</button>
<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 %}
<li class="menu-disabled" aria-disabled="true">
<a class="flex items-center">
<i class="fa-solid fa-eye fa-fw mr-2"></i>
<div>
{% translate 'Show on summaries' %}
<div
class="block text-base-content/60 text-xs font-medium">{% translate 'Controlled by account' %}</div>
</div>
</a>
</li>
{% elif transaction.category.mute %}
<li class="menu-disabled" aria-disabled="true">
<a class="flex items-center">
<i class="fa-solid fa-eye fa-fw mr-2"></i>
<div>
{% translate 'Show on summaries' %}
<div
class="block text-base-content/60 text-xs font-medium">{% translate 'Controlled by category' %}</div>
</div>
</a>
</li>
{% elif transaction.mute %}
<li><a href="#"
hx-get="{% url 'transaction_mute' transaction_id=transaction.id %}"
hx-target="closest .transaction" hx-swap="outerHTML"><i
class="fa-solid fa-eye fa-fw mr-2"></i>{% translate 'Show on summaries' %}</a></li>
{% else %}
<li><a href="#"
hx-get="{% url 'transaction_mute' transaction_id=transaction.id %}"
hx-target="closest .transaction" hx-swap="outerHTML"><i
class="fa-solid fa-eye-slash fa-fw mr-2"></i>{% translate 'Hide from summaries' %}</a></li>
{% endif %}
<li><a href="#"
hx-get="{% url 'quick_transaction_add_as_quick_transaction' transaction_id=transaction.id %}"><i
class="fa-solid fa-person-running fa-fw mr-2"></i>{% translate 'Add as quick transaction' %}</a>
</li>
{% elif transaction.category.mute %}
<li>
<a class="dropdown-item disabled d-flex align-items-center" aria-disabled="true">
<i class="fa-solid fa-eye fa-fw me-2"></i>
<div>
{% translate 'Show on summaries' %}
<div
class="d-block text-body-secondary tw:text-xs tw:font-medium">{% translate 'Controlled by category' %}</div>
</div>
</a>
<hr class="my-1 hr">
<li><a href="#"
hx-get="{% url 'transaction_change_month' transaction_id=transaction.id change_type='previous' %}"><i
class="fa-solid fa-calendar-minus fa-fw mr-2"></i>{% translate 'Move to previous month' %}</a>
</li>
{% elif transaction.mute %}
<li><a class="dropdown-item" href="#"
hx-get="{% url 'transaction_mute' transaction_id=transaction.id %}"
hx-target="closest .transaction" hx-swap="outerHTML"><i
class="fa-solid fa-eye fa-fw me-2"></i>{% translate 'Show on summaries' %}</a></li>
{% else %}
<li><a class="dropdown-item" href="#"
hx-get="{% url 'transaction_mute' transaction_id=transaction.id %}"
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>
{% endif %}
<li><a class="dropdown-item" href="#"
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>
</li>
<li>
<hr class="dropdown-divider">
</li>
<li><a class="dropdown-item" href="#"
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>
</li>
<li><a class="dropdown-item" href="#"
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>
<li><a class="dropdown-item" href="#"
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>
<li>
<hr class="dropdown-divider">
</li>
<li><a class="dropdown-item" href="#"
hx-get="{% url 'transaction_clone' transaction_id=transaction.id %}"><i
class="fa-solid fa-clone fa-fw me-2"></i>{% translate 'Duplicate' %}</a></li>
</ul>
<li><a href="#"
hx-get="{% url 'transaction_change_month' transaction_id=transaction.id change_type='next' %}"><i
class="fa-solid fa-calendar-plus fa-fw mr-2"></i>{% translate 'Move to next month' %}</a></li>
<li><a href="#"
hx-get="{% url 'transaction_move_to_today' transaction_id=transaction.id %}"><i
class="fa-solid fa-calendar-day fa-fw mr-2"></i>{% translate 'Move to today' %}</a></li>
<hr class="my-1 hr">
<li><a href="#"
hx-get="{% url 'transaction_clone' transaction_id=transaction.id %}"><i
class="fa-solid fa-clone fa-fw mr-2"></i>{% translate 'Duplicate' %}</a></li>
</ul>
{% else %}
<a class="btn btn-secondary btn-sm transaction-action"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Restore" %}"
hx-get="{% url 'transaction_undelete' transaction_id=transaction.id %}"><i
class="fa-solid fa-trash-arrow-up"></i></a>
<a class="btn btn-secondary btn-sm transaction-action"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Delete" %}"
hx-delete="{% url 'transaction_delete' transaction_id=transaction.id %}"
hx-trigger='confirmed'
data-bypass-on-ctrl="true"
data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}"
data-confirm-text="{% translate "Yes, delete it!" %}"
_="install prompt_swal"><i class="fa-solid fa-trash fa-fw text-danger"></i>
</a>
<div class="tooltip" data-tippy-content="{% translate "Restore" %}">
<a class="btn btn-success btn-soft btn-sm transaction-action"
role="button"
hx-get="{% url 'transaction_undelete' transaction_id=transaction.id %}"><i
class="fa-solid fa-trash-arrow-up fa-fw"></i></a>
</div>
<div class="tooltip" data-tippy-content="{% translate "Delete" %}">
<a class="btn btn-error btn-soft btn-sm transaction-action"
role="button"
hx-delete="{% url 'transaction_delete' transaction_id=transaction.id %}"
hx-trigger='confirmed'
data-bypass-on-ctrl="true"
data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}"
data-confirm-text="{% translate "Yes, delete it!" %}"
_="install prompt_swal"><i class="fa-solid fa-trash fa-fw"></i>
</a>
</div>
{% endif %}
</div>
</div>

View File

@@ -1,38 +1,38 @@
<div class="card mb-2 transaction-item">
<div class="card-body p-2 tw:flex tw:items-center tw:gap-3" data-bs-toggle="collapse" data-bs-target="#{{ transaction.id }}" role="button" aria-expanded="false" aria-controls="{{ transaction.id }}">
<div class="card bg-base-100 shadow-xl mb-2 transaction-item">
<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 -->
<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 -->
<div class="tw:w-8">
<div class="w-8">
{% if transaction.type == 'IN' %}
<span class="badge bg-success"></span>
<span class="badge badge-success"></span>
{% else %}
<span class="badge bg-danger"></span>
<span class="badge badge-error"></span>
{% endif %}
</div>
<!-- Payment status -->
<div class="tw:w-8">
<div class="w-8">
{% if transaction.is_paid %}
<span class="badge bg-success"></span>
<span class="badge badge-success"></span>
{% else %}
<span class="badge bg-warning"></span>
<span class="badge badge-warning"></span>
{% endif %}
</div>
<!-- Description -->
<div class="tw:flex-grow">
<span class="tw:font-medium">{{ transaction.description }}</span>
<div class="flex-grow">
<span class="font-medium">{{ transaction.description }}</span>
</div>
<!-- Amount -->
<div class="tw:text-right tw:whitespace-nowrap">
<span class="{% if transaction.type == 'IN' %}tw:text-green-400{% else %}tw:text-red-400{% endif %}">
<div class="text-right whitespace-nowrap">
<span class="{% if transaction.type == 'IN' %}text-green-400{% else %}text-red-400{% endif %}">
{{ transaction.amount }}
</span>
{% if transaction.exchanged_amount %}
<br>
<small class="text-muted">
<small class="text-base-content/60">
{{ transaction.exchanged_amount.prefix }}{{ transaction.exchanged_amount.amount }}{{ transaction.exchanged_amount.suffix }}
</small>
{% endif %}
@@ -43,48 +43,48 @@
<!-- Expandable details -->
<div class="collapse" id="{{ transaction.id }}">
<div class="card-body p-3 transaction-details">
<div class="row">
<div class="col-md-6">
<dl class="row">
<dt class="col-sm-4">Date</dt>
<dd class="col-sm-8">{{ transaction.date|date:"Y-m-d" }}</dd>
<div class="grid grid-cols-1 md:grid-cols-2">
<div>
<dl class="grid grid-cols-3">
<dt class="col-span-1">Date</dt>
<dd class="col-span-2">{{ transaction.date|date:"Y-m-d" }}</dd>
<dt class="col-sm-4">Reference Date</dt>
<dd class="col-sm-8">{{ transaction.reference_date|date:"Y-m" }}</dd>
<dt class="col-span-1">Reference Date</dt>
<dd class="col-span-2">{{ transaction.reference_date|date:"Y-m" }}</dd>
<dt class="col-sm-4">Account</dt>
<dd class="col-sm-8">{{ transaction.account.name }}</dd>
<dt class="col-span-1">Account</dt>
<dd class="col-span-2">{{ transaction.account.name }}</dd>
<dt class="col-sm-4">Category</dt>
<dd class="col-sm-8">{{ transaction.category|default:"-" }}</dd>
<dt class="col-span-1">Category</dt>
<dd class="col-span-2">{{ transaction.category|default:"-" }}</dd>
</dl>
</div>
<div class="col-md-6">
<dl class="row">
<div>
<dl class="grid grid-cols-3">
{% if transaction.tags.exists %}
<dt class="col-sm-4">Tags</dt>
<dd class="col-sm-8">
<dt class="col-span-1">Tags</dt>
<dd class="col-span-2">
{% for tag in transaction.tags.all %}
<span class="badge bg-secondary">{{ tag.name }}</span>
<span class="badge badge-secondary">{{ tag.name }}</span>
{% endfor %}
</dd>
{% endif %}
{% if transaction.installment_plan %}
<dt class="col-sm-4">Installment</dt>
<dd class="col-sm-8">
<dt class="col-span-1">Installment</dt>
<dd class="col-span-2">
{{ transaction.installment_id }} of {{ transaction.installment_plan.total_installments }}
</dd>
{% endif %}
{% if transaction.recurring_transaction %}
<dt class="col-sm-4">Recurring</dt>
<dd class="col-sm-8">Yes</dd>
<dt class="col-span-1">Recurring</dt>
<dd class="col-span-2">Yes</dd>
{% endif %}
{% if transaction.notes %}
<dt class="col-sm-4">Notes</dt>
<dd class="col-sm-8">{{ transaction.notes }}</dd>
<dt class="col-span-1">Notes</dt>
<dd class="col-span-2">{{ transaction.notes }}</dd>
{% endif %}
</dl>
</div>

View File

@@ -1,194 +1,166 @@
{% load tools %}
{% load i18n %}
<div class="col card shadow">
<div class="card bg-base-100 shadow-md card-border border-base-300">
<div class="card-body">
{% if account.account.group %}
<div class="tw:text-sm mb-2">
<span class="badge text-bg-primary ">{{ account.account.group }}</span>
</div>
{% endif %}
<h5 class="card-title">
{{ account.account.name }}
<h5 class="card-title mb-4">
{% if account.account.group %}<span class="badge badge-primary badge-outline">{{ account.account.group }}</span>{% endif %} {{ account.account.name }}
</h5>
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw:text-gray-400">{% translate 'projected income' %}</div>
<div class="card-data-section">
<div class="card-data-row">
<span class="card-data-label">{% translate 'projected income' %}</span>
<div class="card-data-values">
{% if account.income_projected != 0 %}
<c-amount.display
:amount="account.income_projected"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="green"></c-amount.display>
{% if account.exchanged and account.exchanged.income_projected %}
<c-amount.display
:amount="account.exchanged.income_projected"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
{% endif %}
{% else %}
<span class="font-semibold">-</span>
{% endif %}
</div>
</div>
<div class="dotted-line flex-grow-1"></div>
{% if account.income_projected != 0 %}
<div class="text-end font-monospace tw:text-green-400">
<c-amount.display
:amount="account.income_projected"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"></c-amount.display>
<div class="card-data-row">
<span class="card-data-label">{% translate 'projected expenses' %}</span>
<div class="card-data-values">
{% if account.expense_projected != 0 %}
<c-amount.display
:amount="account.expense_projected"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="red"></c-amount.display>
{% if account.exchanged and account.exchanged.expense_projected %}
<c-amount.display
:amount="account.exchanged.expense_projected"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
{% endif %}
{% else %}
<span class="font-semibold">-</span>
{% endif %}
</div>
</div>
{% else %}
<div class="text-end font-monospace">-</div>
{% endif %}
</div>
{% if account.exchanged and account.exchanged.income_projected %}
<div class="text-end font-monospace tw:text-gray-500">
<c-amount.display
:amount="account.exchanged.income_projected"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% 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 class="dotted-line flex-grow-1"></div>
<div>
{% if account.expense_projected != 0 %}
<div class="text-end font-monospace tw:text-red-400">
<div class="card-data-row">
<span class="card-data-label">{% translate 'projected total' %}</span>
<div class="card-data-values">
<c-amount.display
:amount="account.expense_projected"
:amount="account.total_projected"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"></c-amount.display>
:decimal_places="account.currency.decimal_places"
color="{% if account.total_projected > 0 %}green{% elif account.total_projected < 0 %}red{% endif %}"></c-amount.display>
{% if account.exchanged.total_projected and account.exchanged.total_projected %}
<c-amount.display
:amount="account.exchanged.total_projected"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
{% endif %}
</div>
{% else %}
<div class="text-end font-monospace">-</div>
{% endif %}
</div>
</div>
{% if account.exchanged and account.exchanged.expense_projected %}
<div class="text-end font-monospace tw:text-gray-500">
<c-amount.display
:amount="account.exchanged.expense_projected"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% 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 class="dotted-line flex-grow-1"></div>
<div
class="text-end font-monospace">
<c-amount.display
:amount="account.total_projected"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
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 %}
<div class="text-end font-monospace tw:text-gray-500">
<c-amount.display
:amount="account.exchanged.total_projected"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% 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 class="dotted-line flex-grow-1"></div>
{% if account.income_current != 0 %}
<div class="text-end font-monospace tw:text-green-400">
<c-amount.display
:amount="account.income_current"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"></c-amount.display>
</div>
{% else %}
<div class="text-end font-monospace">-</div>
{% endif %}
</div>
{% if account.exchanged and account.exchanged.income_current %}
<div class="text-end font-monospace tw:text-gray-500">
<c-amount.display
:amount="account.exchanged.income_current"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% 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 class="dotted-line flex-grow-1"></div>
{% if account.expense_current != 0 %}
<div class="text-end font-monospace tw:text-red-400">
<c-amount.display
:amount="account.expense_current"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"></c-amount.display>
</div>
{% else %}
<div class="text-end font-monospace">-</div>
{% endif %}
</div>
{% if account.exchanged and account.exchanged.expense_current %}
<div class="text-end font-monospace tw:text-gray-500">
<c-amount.display
:amount="account.exchanged.expense_current"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% 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 class="dotted-line flex-grow-1"></div>
<div class="text-end font-monospace">
<c-amount.display
:amount="account.total_current"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
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 %}
<div class="text-end font-monospace tw:text-gray-500">
<c-amount.display
:amount="account.exchanged.total_current"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% 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>
<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 %}
<c-amount.display
:amount="account.income_current"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="green"></c-amount.display>
{% if account.exchanged and account.exchanged.income_current %}
<c-amount.display
:amount="account.exchanged.income_current"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
{% endif %}
{% else %}
<span class="font-semibold">-</span>
{% endif %}
</div>
<div class="dotted-line flex-grow-1"></div>
<div class="text-end font-monospace">
</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 %}
<c-amount.display
:amount="account.expense_current"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="red"></c-amount.display>
{% if account.exchanged and account.exchanged.expense_current %}
<c-amount.display
:amount="account.exchanged.expense_current"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
{% endif %}
{% else %}
<span class="font-semibold">-</span>
{% endif %}
</div>
</div>
<div class="card-data-row">
<span class="card-data-label">{% translate 'current total' %}</span>
<div class="card-data-values">
<c-amount.display
:amount="account.total_current"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="{% if account.total_current > 0 %}green{% elif account.total_current < 0 %}red{% endif %}"></c-amount.display>
{% if account.exchanged and account.exchanged.total_current %}
<c-amount.display
:amount="account.exchanged.total_current"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
{% endif %}
</div>
</div>
</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
:amount="account.total_final"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="{% if account.total_final > 0 %}green{% elif account.total_final < 0 %}red{% endif %}"></c-amount.display>
{% if account.exchanged and account.exchanged.total_final %}
<c-amount.display
:amount="account.exchanged.total_final"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
{% endif %}
</div>
</div>
{% if account.exchanged and account.exchanged.total_final %}
<div class="text-end font-monospace tw:text-gray-500">
<c-amount.display
:amount="account.exchanged.total_final"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
</div>
{% with p=percentages|get_dict_item:account_id %}
<div class="my-3">

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">
{{ slot }}
</div>

View File

@@ -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>

View File

@@ -1,188 +1,166 @@
{% load tools %}
{% load i18n %}
<div class="col card shadow">
<div class="col card bg-base-100 shadow card-border">
<div class="card-body">
<h5 class="card-title">
<h5 class="card-title mb-4">
{{ currency.currency.name }}
</h5>
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw:text-gray-400">{% translate 'projected income' %}</div>
<div class="card-data-section">
<div class="card-data-row">
<span class="card-data-label">{% translate 'projected income' %}</span>
<div class="card-data-values">
{% if currency.income_projected != 0 %}
<c-amount.display
:amount="currency.income_projected"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="green"></c-amount.display>
{% if currency.exchanged and currency.exchanged.income_projected %}
<c-amount.display
:amount="currency.exchanged.income_projected"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
{% endif %}
{% else %}
<span class="font-semibold">-</span>
{% endif %}
</div>
</div>
<div class="dotted-line flex-grow-1"></div>
{% if currency.income_projected != 0 %}
<div class="text-end font-monospace tw:text-green-400">
<c-amount.display
:amount="currency.income_projected"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"></c-amount.display>
<div class="card-data-row">
<span class="card-data-label">{% translate 'projected expenses' %}</span>
<div class="card-data-values">
{% if currency.expense_projected != 0 %}
<c-amount.display
:amount="currency.expense_projected"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="red"></c-amount.display>
{% if currency.exchanged and currency.exchanged.expense_projected %}
<c-amount.display
:amount="currency.exchanged.expense_projected"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
{% endif %}
{% else %}
<span class="font-semibold">-</span>
{% endif %}
</div>
</div>
{% else %}
<div class="text-end font-monospace">-</div>
{% endif %}
</div>
{% if currency.exchanged and currency.exchanged.income_projected %}
<div class="text-end font-monospace tw:text-gray-500">
<c-amount.display
:amount="currency.exchanged.income_projected"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% 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 class="dotted-line flex-grow-1"></div>
<div>
{% if currency.expense_projected != 0 %}
<div class="text-end font-monospace tw:text-red-400">
<div class="card-data-row">
<span class="card-data-label">{% translate 'projected total' %}</span>
<div class="card-data-values">
<c-amount.display
:amount="currency.expense_projected"
:amount="currency.total_projected"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"></c-amount.display>
:decimal_places="currency.currency.decimal_places"
color="{% if currency.total_projected > 0 %}green{% elif currency.total_projected < 0 %}red{% endif %}"></c-amount.display>
{% if currency.exchanged.total_projected and currency.exchanged.total_projected %}
<c-amount.display
:amount="currency.exchanged.total_projected"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
{% endif %}
</div>
{% else %}
<div class="text-end font-monospace">-</div>
{% endif %}
</div>
</div>
{% if currency.exchanged and currency.exchanged.expense_projected %}
<div class="text-end font-monospace tw:text-gray-500">
<c-amount.display
:amount="currency.exchanged.expense_projected"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% 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 class="dotted-line flex-grow-1"></div>
<div class="text-end font-monospace">
<c-amount.display
:amount="currency.total_projected"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
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 %}
<div class="text-end font-monospace tw:text-gray-500">
<c-amount.display
:amount="currency.exchanged.total_projected"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% 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 class="dotted-line flex-grow-1"></div>
{% if currency.income_current != 0 %}
<div class="text-end font-monospace tw:text-green-400">
<c-amount.display
:amount="currency.income_current"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"></c-amount.display>
</div>
{% else %}
<div class="text-end font-monospace">-</div>
{% endif %}
</div>
{% if currency.exchanged and currency.exchanged.income_current %}
<div class="text-end font-monospace tw:text-gray-500">
<c-amount.display
:amount="currency.exchanged.income_current"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% 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 class="dotted-line flex-grow-1"></div>
{% if currency.expense_current != 0 %}
<div class="text-end font-monospace tw:text-red-400">
<c-amount.display
:amount="currency.expense_current"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"></c-amount.display>
</div>
{% else %}
<div class="text-end font-monospace">-</div>
{% endif %}
</div>
{% if currency.exchanged and currency.exchanged.expense_current %}
<div class="text-end font-monospace tw:text-gray-500">
<c-amount.display
:amount="currency.exchanged.expense_current"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% 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 class="dotted-line flex-grow-1"></div>
<div class="text-end font-monospace">
<c-amount.display
:amount="currency.total_current"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
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 %}
<div class="text-end font-monospace tw:text-gray-500">
<c-amount.display
:amount="currency.exchanged.total_current"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% 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>
<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 %}
<c-amount.display
:amount="currency.income_current"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="green"></c-amount.display>
{% if currency.exchanged and currency.exchanged.income_current %}
<c-amount.display
:amount="currency.exchanged.income_current"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
{% endif %}
{% else %}
<span class="font-semibold">-</span>
{% endif %}
</div>
<div class="dotted-line flex-grow-1"></div>
<div class="text-end font-monospace">
</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 %}
<c-amount.display
:amount="currency.expense_current"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="red"></c-amount.display>
{% if currency.exchanged and currency.exchanged.expense_current %}
<c-amount.display
:amount="currency.exchanged.expense_current"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
{% endif %}
{% else %}
<span class="font-semibold">-</span>
{% endif %}
</div>
</div>
<div class="card-data-row">
<span class="card-data-label">{% translate 'current total' %}</span>
<div class="card-data-values">
<c-amount.display
:amount="currency.total_current"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="{% if currency.total_current > 0 %}green{% elif currency.total_current < 0 %}red{% endif %}"></c-amount.display>
{% if currency.exchanged and currency.exchanged.total_current %}
<c-amount.display
:amount="currency.exchanged.total_current"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
{% endif %}
</div>
</div>
</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
:amount="currency.total_final"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="{% if currency.total_final > 0 %}green{% elif currency.total_final < 0 %}red{% endif %}"></c-amount.display>
{% if currency.exchanged and currency.exchanged.total_final %}
<c-amount.display
:amount="currency.exchanged.total_final"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
{% endif %}
</div>
</div>
{% if currency.exchanged and currency.exchanged.total_final %}
<div class="text-end font-monospace tw:text-gray-500">
<c-amount.display
:amount="currency.exchanged.total_final"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
</div>
{% with p=percentages|get_dict_item:currency_id %}
<div class="my-3">

View File

@@ -1,67 +1,69 @@
{% 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
if #actions-bar then
if no <input[type='checkbox']:checked/> in #transactions-list
if #actions-bar
add .slide-in-bottom-reverse then settle
then add .tw:hidden to #actions-bar
then add .hidden to #actions-bar
then remove .slide-in-bottom-reverse
end
else
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
end
end
end
end">
<div class="card slide-in-bottom">
<div class="card-body p-2 d-flex justify-content-between align-items-center gap-3">
<div class="card bg-base-300 shadow slide-in-bottom max-w-[90vw] card-border">
<div class="card-body flex-row p-2 flex justify-between items-center gap-3 overflow-x-auto">
{% spaceless %}
<div class="dropdown">
<button class="btn btn-secondary btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown"
aria-expanded="false">
<div class="font-bold text-md ms-2" id="selected-count">0</div>
<div class="divider divider-horizontal m-0"></div>
<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-solid fa-chevron-down fa-xs"></i>
</button>
<ul class="dropdown-menu">
<ul class="dropdown-menu menu">
<li>
<div class="dropdown-item px-3 tw:cursor-pointer"
_="on click set <#transactions-list input[type='checkbox']/>'s checked to true then call me.blur() then trigger change">
<i class="fa-regular fa-square-check tw:text-green-400 me-3"></i>{% translate 'Select All' %}
</div>
<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">
<i class="fa-regular fa-square-check text-success me-3"></i>{% translate 'Select All' %}
</a>
</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">
<i class="fa-regular fa-square tw:text-red-400 me-3"></i>{% translate 'Unselect All' %}
</div>
<i class="fa-regular fa-square text-error me-3"></i>{% translate 'Unselect All' %}
</a>
</li>
</ul>
</div>
<div class="vr tw:align-middle"></div>
<div class="divider divider-horizontal m-0"></div>
<button class="btn btn-secondary btn-sm"
hx-get="{% url 'transactions_bulk_undelete' %}"
hx-include=".transaction"
data-bs-toggle="tooltip"
data-bs-title="{% translate 'Restore' %}">
data-tippy-content="{% translate 'Restore' %}">
<i class="fa-solid fa-trash-arrow-up fa-fw"></i>
</button>
<button class="btn btn-secondary btn-sm"
hx-get="{% url 'transactions_bulk_delete' %}"
hx-include=".transaction"
hx-trigger="confirmed"
data-bs-toggle="tooltip"
data-bs-title="{% translate 'Delete' %}"
data-tippy-content="{% translate 'Delete' %}"
data-bypass-on-ctrl="true"
data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}"
data-confirm-text="{% translate "Yes, delete them!" %}"
_="install prompt_swal">
<i class="fa-solid fa-trash text-danger"></i>
<i class="fa-solid fa-trash text-error"></i>
</button>
<div class="vr tw:align-middle"></div>
<div class="btn-group"
<div class="divider divider-horizontal m-0"></div>
<div class="join"
_="on selected_transactions_updated from #actions-bar
set realTotal to math.bignumber(0)
set flatTotal to math.bignumber(0)
@@ -99,143 +101,143 @@
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
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
writeText(original_value) on navigator.clipboard
put '{% translate "copied!" %}' into #real-total-front's innerText
wait 1s
put original_value into #real-total-front's innerText
end">
<i class="fa-solid fa-plus fa-fw me-md-2 text-primary"></i>
<span class="d-none d-md-inline-block" id="real-total-front">0</span>
</button>
<button type="button" class="btn btn-sm btn-secondary dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside">
<span class="visually-hidden">{% trans "Toggle Dropdown" %}</span>
<i class="fa-solid fa-plus fa-fw me-md-2"></i>
<span class="hidden md:inline-block" id="real-total-front">0</span>
</button>
<div>
<button class="join-item btn btn-sm btn-secondary"
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>
<ul class="dropdown-menu">
<li>
<div class="dropdown-item-text p-0">
<div>
<div class="text-body-secondary tw:text-xs tw:font-medium px-3">
{% trans "Flat Total" %}
</div>
<div class="dropdown-item px-3 tw:cursor-pointer"
id="calc-menu-flat-total"
_="on click
set original_value to my innerText
writeText(my innerText) on navigator.clipboard
put '{% translate "copied!" %}' into me
wait 1s
put original_value into me
end">
0
<ul class="dropdown-menu dropdown-menu-end menu">
<li class="cursor-pointer"
_="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 class="text-base-content/60 text-xs font-medium">
{% trans "Flat Total" %}
</div>
<div id="calc-menu-flat-total">
0
</div>
</div>
</div>
</div>
</li>
<li>
<div class="dropdown-item-text p-0">
<div>
<div class="text-body-secondary tw:text-xs tw:font-medium px-3">
{% trans "Real Total" %}
</div>
<div class="dropdown-item px-3 tw:cursor-pointer"
id="calc-menu-real-total"
_="on click
set original_value to my innerText
writeText(my innerText) on navigator.clipboard
put '{% translate "copied!" %}' into me
wait 1s
put original_value into me
end">
0
</li>
<li class="cursor-pointer"
_="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 class="text-base-content/60 text-xs font-medium">
{% trans "Real Total" %}
</div>
<div id="calc-menu-real-total">
0
</div>
</div>
</div>
</div>
</li>
<li>
<div class="dropdown-item-text p-0">
<div>
<div class="text-body-secondary tw:text-xs tw:font-medium px-3">
{% trans "Mean" %}
</div>
<div class="dropdown-item px-3 tw:cursor-pointer"
id="calc-menu-mean"
_="on click
set original_value to my innerText
writeText(my innerText) on navigator.clipboard
put '{% translate "copied!" %}' into me
wait 1s
put original_value into me
end">
0
</li>
<li class="cursor-pointer"
_="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 class="text-base-content/60 text-xs font-medium">
{% trans "Mean" %}
</div>
<div id="calc-menu-mean">
0
</div>
</div>
</div>
</div>
</li>
<li>
<div class="dropdown-item-text p-0">
<div>
<div class="text-body-secondary tw:text-xs tw:font-medium px-3">
{% trans "Max" %}
</div>
<div class="dropdown-item px-3 tw:cursor-pointer"
id="calc-menu-max"
_="on click
set original_value to my innerText
writeText(my innerText) on navigator.clipboard
put '{% translate "copied!" %}' into me
wait 1s
put original_value into me
end">
0
</li>
<li class="cursor-pointer"
_="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 class="text-base-content/60 text-xs font-medium">
{% trans "Max" %}
</div>
<div id="calc-menu-max">
0
</div>
</div>
</div>
</div>
</li>
<li>
<div class="dropdown-item-text p-0">
<div>
<div class="text-body-secondary tw:text-xs tw:font-medium px-3">
{% trans "Min" %}
</div>
<div class="dropdown-item px-3 tw:cursor-pointer"
id="calc-menu-min"
_="on click
set original_value to my innerText
writeText(my innerText) on navigator.clipboard
put '{% translate "copied!" %}' into me
wait 1s
put original_value into me
end">
0
</li>
<li class="cursor-pointer"
_="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 class="text-base-content/60 text-xs font-medium">
{% trans "Min" %}
</div>
<div id="calc-menu-min">
0
</div>
</div>
</div>
</div>
</li>
<li>
<div class="dropdown-item-text p-0">
<div>
<div class="text-body-secondary tw:text-xs tw:font-medium px-3">
{% trans "Count" %}
</div>
<div class="dropdown-item px-3 tw:cursor-pointer"
id="calc-menu-count"
_="on click
set original_value to my innerText
writeText(my innerText) on navigator.clipboard
put '{% translate "copied!" %}' into me
wait 1s
put original_value into me
end">
0
</li>
<li class="cursor-pointer"
_="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 class="text-base-content/60 text-xs font-medium">
{% trans "Count" %}
</div>
<div id="calc-menu-count">
0
</div>
</div>
</div>
</div>
</li>
</ul>
</li>
</ul>
</div>
</div>
{% endspaceless %}
</div>

View File

@@ -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>

View File

@@ -1,8 +1,6 @@
{% spaceless %}
{% load i18n %}
<span class="tw:text-xs text-white-50 mx-1"
data-bs-toggle="tooltip"
data-bs-title="{{ content }}">
<span class="text-xs text-base-content/50 mx-1" data-tippy-content="{{ content }}">
<i class="{% if not icon %}fa-solid fa-circle-question{% else %}{{ icon }}{% endif %} fa-fw"></i>
</span>
{% endspaceless %}

View File

@@ -1,9 +1,9 @@
<div class="card tw:relative h-100 shadow">
<div class="tw:absolute tw:h-8 tw:w-8 tw:right-2 tw:top-2 tw:bg-{{ color }}-300 tw:text-{{ color }}-800 text-center align-items-center d-flex justify-content-center rounded-2">
{% if icon %}<i class="{{ icon }}"></i>{% else %}<span class="fw-bold">{{ title.0 }}</span>{% endif %}
<div class="card card-border relative h-full shadow bg-base-100">
<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="font-bold">{{ title.0 }}</span>{% endif %}
</div>
<div class="card-body">
<h5 class="tw:text-{{ color }}-400 fw-bold tw:mr-[50px]" {{ attrs }}>{{ title }}{% if help_text %}<c-ui.help-icon :content="help_text" icon=""></c-ui.help-icon>{% endif %}</h5>
<h5 class="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 }}
</div>
</div>

View File

@@ -1,31 +1,57 @@
{% load i18n %}
<div class="progress-stacked">
<div class="progress position-relative" role="progressbar" aria-label="{% trans 'Projected Income' %} ({{ percentage.percentages.income_projected|floatformat:2 }}%)" aria-valuenow="{{ percentage.percentages.expense_projected|floatformat:0 }}" aria-valuemin="0" aria-valuemax="100" style="width: {{ percentage.percentages.income_projected|floatformat:"2u" }}%">
<div class="progress-bar progress-bar-striped tw:bg-green-300!"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="{% trans 'Projected Income' %} ({{ percentage.percentages.income_projected|floatformat:2 }}%)">
<div class="flex w-full h-4 rounded-full bg-white overflow-hidden"
role="group"
aria-label="{% trans 'Income and Expense Percentages' %}">
<!-- Segment 1: Projected Income -->
<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>
<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!"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="{% trans 'Current Income' %} ({{ p.percentages.income_current|floatformat:2 }}%)">
<!-- 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 'Projected Expenses' %} ({{ percentage.percentages.expense_projected|floatformat:2 }}%)" aria-valuenow="{{ percentage.percentages.expense_projected|floatformat:0 }}" aria-valuemin="0" aria-valuemax="100" style="width: {{ percentage.percentages.expense_projected|floatformat:"2u" }}%">
<div class="progress-bar progress-bar-striped tw:bg-red-300!"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="{% trans 'Projected Expenses' %} ({{ percentage.percentages.expense_projected|floatformat:2 }}%)">
<!-- Segment 3: Projected Expenses -->
<div class="flex items-center justify-center h-4 bg-error/60 tooltip tooltip-bottom tooltip-error
{% if percentage.percentages.income_projected <= 0 and percentage.percentages.income_current <= 0 and percentage.percentages.expense_projected > 0 %}rounded-s-full{% endif %}
{% if percentage.percentages.expense_projected > 0 and percentage.percentages.expense_current <= 0 %}rounded-e-full{% endif %}"
style="width: {{ percentage.percentages.expense_projected|floatformat:'2u' }}%"
data-tippy-content="{% trans 'Projected Expenses' %} ({{ percentage.percentages.expense_projected|floatformat:2 }}%)"
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">
</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 }}%)">
<!-- 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>

View File

@@ -1,7 +1,7 @@
{% load i18n %}
<div class="d-grid gap-2 d-xl-flex justify-content-xl-end">
<div class="d-grid gap-2 d-xl-flex flex-wrap justify-content-xl-center">
<button class="btn btn-sm btn-outline-success"
<div class="grid gap-2 grid-cols-1 xl:flex xl:justify-end">
<div class="grid gap-2 xl:flex flex-wrap xl:justify-center">
<button class="btn btn-sm btn-outline btn-success"
hx-get="{% url 'transaction_add' %}"
hx-target="#generic-offcanvas"
hx-trigger="click, add_income from:window"
@@ -9,7 +9,7 @@
<i class="fa-solid fa-arrow-right-to-bracket me-2"></i>
{% translate "Income" %}
</button>
<button class="btn btn-sm btn-outline-danger"
<button class="btn btn-sm btn-outline btn-error"
hx-get="{% url 'transaction_add' %}"
hx-target="#generic-offcanvas"
hx-trigger="click, add_expense from:window"
@@ -17,21 +17,21 @@
<i class="fa-solid fa-arrow-right-from-bracket me-2"></i>
{% translate "Expense" %}
</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-trigger="click, installment from:window"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-divide me-2"></i>
{% translate "Installment" %}
</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-trigger="click, balance from:window"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-repeat me-2"></i>
{% translate "Recurring" %}
</button>
<button class="btn btn-sm btn-outline-info"
<button class="btn btn-sm btn-outline btn-info"
hx-get="{% url 'transactions_transfer' %}"
hx-target="#generic-offcanvas"
hx-trigger="click, add_transfer from:window"
@@ -39,7 +39,7 @@
<i class="fa-solid fa-money-bill-transfer me-2"></i>
{% translate "Transfer" %}
</button>
<button class="btn btn-sm btn-outline-info"
<button class="btn btn-sm btn-outline btn-info"
hx-get="{% url 'account_reconciliation' %}"
hx-trigger="click, balance from:window"
hx-target="#generic-offcanvas">

View File

@@ -1,102 +1,101 @@
{% 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
if #actions-bar then
if no <input[type='checkbox']:checked/> in #transactions-list
if #actions-bar
add .slide-in-bottom-reverse then settle
then add .tw:hidden to #actions-bar
then add .hidden to #actions-bar
then remove .slide-in-bottom-reverse
end
else
if #actions-bar
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
end
end
end
end">
<div class="card slide-in-bottom tw:max-w-[90vw]">
<div class="card-body p-2 d-flex justify-content-between align-items-center gap-3 tw:overflow-x-auto">
<div class="card bg-base-300 shadow slide-in-bottom max-w-[90vw] card-border">
<div class="card-body flex-row p-2 flex justify-between items-center gap-3 overflow-x-auto">
{% spaceless %}
<div class="tw:font-bold tw:text-md ms-2" id="selected-count">0</div>
<div class="vr tw:align-middle"></div>
<div class="dropdown">
<button class="btn btn-secondary btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown"
aria-expanded="false" data-bs-popper-config='{"strategy":"fixed"}'>
<div class="font-bold text-md ms-2" id="selected-count">0</div>
<div class="divider divider-horizontal m-0"></div>
<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-solid fa-chevron-down fa-xs"></i>
</button>
<ul class="dropdown-menu">
<ul class="dropdown-menu menu">
<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">
<i class="fa-regular fa-square-check tw:text-green-400 me-3"></i>{% translate 'Select All' %}
</div>
<i class="fa-regular fa-square-check text-success me-3"></i>{% translate 'Select All' %}
</a>
</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">
<i class="fa-regular fa-square tw:text-red-400 me-3"></i>{% translate 'Unselect All' %}
</div>
<i class="fa-regular fa-square text-error me-3"></i>{% translate 'Unselect All' %}
</a>
</li>
</ul>
</div>
<div class="vr tw:align-middle"></div>
<div class="btn-group">
<button class="btn btn-secondary btn-sm"
<div class="divider divider-horizontal m-0"></div>
<div class="join">
<button class="btn btn-secondary join-item btn-sm"
hx-get="{% url 'transactions_bulk_edit' %}"
hx-target="#generic-offcanvas"
hx-include=".transaction"
data-bs-toggle="tooltip"
data-bs-title="{% translate 'Edit' %}">
data-tippy-content="{% translate 'Edit' %}">
<i class="fa-solid fa-pencil"></i>
</button>
<button type="button" class="btn btn-sm btn-secondary dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown" data-bs-popper-config='{"strategy":"fixed"}' aria-expanded="false"
data-bs-auto-close="outside">
<span class="visually-hidden">{% trans "Toggle Dropdown" %}</span>
</button>
<div>
<button type="button" role="button" class="join-item btn btn-sm btn-secondary"
data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa-solid fa-chevron-down fa-xs"></i>
</button>
<ul class="dropdown-menu">
<li>
<div class="dropdown-item px-3 tw:cursor-pointer"
hx-get="{% url 'transactions_bulk_unpay' %}"
hx-include=".transaction">
<i class="fa-regular fa-circle tw:text-red-400 fa-fw me-3"></i>{% translate 'Mark as unpaid' %}
</div>
</li>
<li>
<div class="dropdown-item px-3 tw:cursor-pointer"
hx-get="{% url 'transactions_bulk_pay' %}"
hx-include=".transaction">
<i class="fa-regular fa-circle-check tw:text-green-400 fa-fw me-3"></i>{% translate 'Mark as paid' %}
</div>
</li>
</ul>
<ul class="dropdown-menu menu">
<li>
<a class="cursor-pointer"
hx-get="{% url 'transactions_bulk_unpay' %}"
hx-include=".transaction">
<i class="fa-regular fa-circle text-red-400 fa-fw me-3"></i>{% translate 'Mark as unpaid' %}
</a>
</li>
<li>
<a class="cursor-pointer"
hx-get="{% url 'transactions_bulk_pay' %}"
hx-include=".transaction">
<i class="fa-regular fa-circle-check text-green-400 fa-fw me-3"></i>{% translate 'Mark as paid' %}
</a>
</li>
</ul>
</div>
</div>
<button class="btn btn-secondary btn-sm"
hx-get="{% url 'transactions_bulk_clone' %}"
hx-include=".transaction"
data-bs-toggle="tooltip"
data-bs-title="{% translate 'Duplicate' %}">
data-tippy-content="{% translate 'Duplicate' %}">
<i class="fa-solid fa-clone fa-fw"></i>
</button>
<button class="btn btn-secondary btn-sm"
<button class="btn btn-error btn-sm"
hx-get="{% url 'transactions_bulk_delete' %}"
hx-include=".transaction"
hx-trigger="confirmed"
data-bs-toggle="tooltip"
data-bs-title="{% translate 'Delete' %}"
data-tippy-content="{% translate 'Delete' %}"
data-bypass-on-ctrl="true"
data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}"
data-confirm-text="{% translate "Yes, delete them!" %}"
_="install prompt_swal">
<i class="fa-solid fa-trash text-danger"></i>
<i class="fa-solid fa-trash"></i>
</button>
<div class="vr tw:align-middle"></div>
<div class="btn-group"
<div class="divider divider-horizontal m-0"></div>
<div class="join"
_="on selected_transactions_updated from #actions-bar
set realTotal to math.bignumber(0)
set flatTotal to math.bignumber(0)
@@ -134,144 +133,143 @@
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
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
writeText(original_value) on navigator.clipboard
put '{% translate "copied!" %}' into #real-total-front's innerText
wait 1s
put original_value into #real-total-front's innerText
end">
<i class="fa-solid fa-plus fa-fw me-md-2 text-primary"></i>
<span class="d-none d-md-inline-block" id="real-total-front">0</span>
</button>
<button type="button" class="btn btn-sm btn-secondary dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside"
data-bs-popper-config='{"strategy":"fixed"}'>
<span class="visually-hidden">{% trans "Toggle Dropdown" %}</span>
<i class="fa-solid fa-plus fa-fw me-md-2"></i>
<span class="hidden md:inline-block" id="real-total-front">0</span>
</button>
<div>
<button class="join-item btn btn-sm btn-secondary"
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>
<ul class="dropdown-menu">
<li>
<div class="dropdown-item-text p-0">
<div>
<div class="text-body-secondary tw:text-xs tw:font-medium px-3">
{% trans "Flat Total" %}
</div>
<div class="dropdown-item px-3 tw:cursor-pointer"
id="calc-menu-flat-total"
_="on click
set original_value to my innerText
writeText(my innerText) on navigator.clipboard
put '{% translate "copied!" %}' into me
wait 1s
put original_value into me
end">
0
<ul class="dropdown-menu dropdown-menu-end menu">
<li class="cursor-pointer"
_="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 class="text-base-content/60 text-xs font-medium">
{% trans "Flat Total" %}
</div>
<div id="calc-menu-flat-total">
0
</div>
</div>
</div>
</div>
</li>
<li>
<div class="dropdown-item-text p-0">
<div>
<div class="text-body-secondary tw:text-xs tw:font-medium px-3">
{% trans "Real Total" %}
</div>
<div class="dropdown-item px-3 tw:cursor-pointer"
id="calc-menu-real-total"
_="on click
set original_value to my innerText
writeText(my innerText) on navigator.clipboard
put '{% translate "copied!" %}' into me
wait 1s
put original_value into me
end">
0
</li>
<li class="cursor-pointer"
_="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 class="text-base-content/60 text-xs font-medium">
{% trans "Real Total" %}
</div>
<div id="calc-menu-real-total">
0
</div>
</div>
</div>
</div>
</li>
<li>
<div class="dropdown-item-text p-0">
<div>
<div class="text-body-secondary tw:text-xs tw:font-medium px-3">
{% trans "Mean" %}
</div>
<div class="dropdown-item px-3 tw:cursor-pointer"
id="calc-menu-mean"
_="on click
set original_value to my innerText
writeText(my innerText) on navigator.clipboard
put '{% translate "copied!" %}' into me
wait 1s
put original_value into me
end">
0
</li>
<li class="cursor-pointer"
_="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 class="text-base-content/60 text-xs font-medium">
{% trans "Mean" %}
</div>
<div id="calc-menu-mean">
0
</div>
</div>
</div>
</div>
</li>
<li>
<div class="dropdown-item-text p-0">
<div>
<div class="text-body-secondary tw:text-xs tw:font-medium px-3">
{% trans "Max" %}
</div>
<div class="dropdown-item px-3 tw:cursor-pointer"
id="calc-menu-max"
_="on click
set original_value to my innerText
writeText(my innerText) on navigator.clipboard
put '{% translate "copied!" %}' into me
wait 1s
put original_value into me
end">
0
</li>
<li class="cursor-pointer"
_="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 class="text-base-content/60 text-xs font-medium">
{% trans "Max" %}
</div>
<div id="calc-menu-max">
0
</div>
</div>
</div>
</div>
</li>
<li>
<div class="dropdown-item-text p-0">
<div>
<div class="text-body-secondary tw:text-xs tw:font-medium px-3">
{% trans "Min" %}
</div>
<div class="dropdown-item px-3 tw:cursor-pointer"
id="calc-menu-min"
_="on click
set original_value to my innerText
writeText(my innerText) on navigator.clipboard
put '{% translate "copied!" %}' into me
wait 1s
put original_value into me
end">
0
</li>
<li class="cursor-pointer"
_="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 class="text-base-content/60 text-xs font-medium">
{% trans "Min" %}
</div>
<div id="calc-menu-min">
0
</div>
</div>
</div>
</div>
</li>
<li>
<div class="dropdown-item-text p-0">
<div>
<div class="text-body-secondary tw:text-xs tw:font-medium px-3">
{% trans "Count" %}
</div>
<div class="dropdown-item px-3 tw:cursor-pointer"
id="calc-menu-count"
_="on click
set original_value to my innerText
writeText(my innerText) on navigator.clipboard
put '{% translate "copied!" %}' into me
wait 1s
put original_value into me
end">
0
</li>
<li class="cursor-pointer"
_="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 class="text-base-content/60 text-xs font-medium">
{% trans "Count" %}
</div>
<div id="calc-menu-count">
0
</div>
</div>
</div>
</div>
</li>
</ul>
</li>
</ul>
</div>
</div>
{% endspaceless %}
</div>

View File

@@ -10,7 +10,7 @@
title="{% translate "Income" %}"></c-components.fab_menu_button>
<c-components.fab_menu_button
color="danger"
color="error"
hx_target="#generic-offcanvas"
hx_trigger="click, add_income from:window"
hx_vals='{"year": {{ year }}, {% if month %}"month": {{ month }},{% endif %} "type": "EX"}'

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 %}

View File

@@ -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 %}

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 %}

View File

@@ -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 %}

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 %}

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 %}

View File

@@ -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>

View File

@@ -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 %}

View File

@@ -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 }}
/>

View File

@@ -0,0 +1 @@
<button class="btn" {{ button.flat_attrs }}>{{ button.content|safe }}</button>

View File

@@ -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>

View File

@@ -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 %}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 %}

View File

@@ -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>

View File

@@ -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 %}

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