mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-02-25 17:04:51 +01:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3190f3ae09 | ||
|
|
757f6647da | ||
|
|
6721d9dfee | ||
|
|
9705441e2d | ||
|
|
7123aefad0 | ||
|
|
712f5f428e | ||
|
|
a2e97b4ba2 | ||
|
|
60a694635b | ||
|
|
877816b649 | ||
|
|
0a3e47819a | ||
|
|
f9d299cb78 | ||
|
|
52934124c1 | ||
|
|
39c1f634b6 | ||
|
|
fee5b93cea | ||
|
|
a7d8f94412 | ||
|
|
44b87da423 | ||
|
|
85794f5c01 | ||
|
|
f246d115e2 | ||
|
|
aae85ecf94 | ||
|
|
ec911c0085 | ||
|
|
7b77f6f363 | ||
|
|
239e9c4b2a | ||
|
|
5abd0b8d3c | ||
|
|
320217f64a | ||
|
|
2735906d5e | ||
|
|
1f03edcc2e | ||
|
|
1405976292 | ||
|
|
6a06d0ee88 | ||
|
|
49c17f75b4 | ||
|
|
2ff6d69fac |
@@ -1,6 +1,5 @@
|
||||
SERVER_NAME=wygiwyh_server
|
||||
DB_NAME=wygiwyh_pg
|
||||
PROCRASTINATE_NAME=wygiwyh_procrastinate
|
||||
|
||||
DEBUG=false
|
||||
URL = https://...
|
||||
|
||||
39
.github/workflows/release.yml
vendored
39
.github/workflows/release.yml
vendored
@@ -2,9 +2,20 @@ name: Release Pipeline
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
types: [ created ]
|
||||
push:
|
||||
branches: [ main ]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Custom tag name for the image'
|
||||
required: true
|
||||
type: string
|
||||
ref:
|
||||
description: 'Git ref to checkout (branch, tag, or SHA)'
|
||||
required: true
|
||||
default: 'main'
|
||||
type: string
|
||||
|
||||
env:
|
||||
IMAGE_NAME: wygiwyh
|
||||
@@ -18,6 +29,13 @@ jobs:
|
||||
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'
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
|
||||
@@ -39,6 +57,8 @@ jobs:
|
||||
file: ./docker/prod/django/Dockerfile
|
||||
push: true
|
||||
provenance: false
|
||||
build-args: |
|
||||
VERSION=nightly
|
||||
tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:nightly
|
||||
platforms: linux/amd64,linux/arm64
|
||||
cache-from: type=gha
|
||||
@@ -52,9 +72,26 @@ jobs:
|
||||
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
|
||||
|
||||
462
README.md
462
README.md
@@ -90,13 +90,12 @@ If you want to run WYGIWYH locally, on your env file:
|
||||
You can now access localhost:OUTBOUND_PORT
|
||||
|
||||
> [!NOTE]
|
||||
> If you're planning on running this behind Tailscale or other similar service also add your machine given IP to `DJANGO_ALLOWED_HOSTS`
|
||||
> - If you're planning on running this behind Tailscale or other similar service also add your machine given IP to `DJANGO_ALLOWED_HOSTS`
|
||||
> - If you're going to use another IP that isn't localhost, add it to `DJANGO_ALLOWED_HOSTS`, without `http://`
|
||||
|
||||
> [!NOTE]
|
||||
> If you're going to use another IP that isn't localhost, add it to `DJANGO_ALLOWED_HOSTS`, without `http://`
|
||||
|
||||
## Building from source
|
||||
Features are only added to main when ready, if you want to run the latest version, you must build from source.
|
||||
## Latest changes
|
||||
Features are only added to `main` when ready, if you want to run the latest version, you must build from source or use the `:nightly` tag on docker. Keep in mind that there can be undocumented breaking changes.
|
||||
|
||||
All the required Dockerfiles are [here](https://github.com/eitchtee/WYGIWYH/tree/main/docker/prod).
|
||||
|
||||
@@ -104,442 +103,31 @@ All the required Dockerfiles are [here](https://github.com/eitchtee/WYGIWYH/tree
|
||||
|
||||
[nwithan8](https://github.com/nwithan8) has kindly provided a Unraid template for WYGIWYH, have a look at the [unraid_templates](https://github.com/nwithan8/unraid_templates) repo.
|
||||
|
||||
WYGIWYH and WYGIWYH--Procrastinate should be available on the Unraid Store. You need both for all features.
|
||||
WYGIWYH is available on the Unraid Store. You'll need to provision your own postgres (version 15 or up) database.
|
||||
|
||||
## Enviroment Variables
|
||||
|
||||
| variable | type | default | explanation |
|
||||
|-------------------------------|-------------|-----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| DJANGO_ALLOWED_HOSTS | string | localhost 127.0.0.1 | A list of space separated domains and IPs representing the host/domain names that WYGIWYH site can serve. [Click here](https://docs.djangoproject.com/en/5.1/ref/settings/#allowed-hosts) for more details |
|
||||
| HTTPS_ENABLED | true\|false | false | Whether to use secure cookies. If this is set to true, the cookie will be marked as “secure”, which means browsers may ensure that the cookie is only sent under an HTTPS connection |
|
||||
| URL | string | http://localhost http://127.0.0.1 | A list of space separated domains and IPs (with the protocol) representing the trusted origins for unsafe requests (e.g. POST). [Click here](https://docs.djangoproject.com/en/5.1/ref/settings/#csrf-trusted-origins ) for more details |
|
||||
| SECRET_KEY | string | "" | This is used to provide cryptographic signing, and should be set to a unique, unpredictable value. |
|
||||
| DEBUG | true\|false | false | Turns DEBUG mode on or off, this is useful to gather more data about possible errors you're having. Don't use in production. |
|
||||
| SQL_DATABASE | string | None *required | The name of your postgres database |
|
||||
| SQL_USER | string | user | The username used to connect to your postgres database |
|
||||
| SQL_PASSWORD | string | password | The password used to connect to your postgres database |
|
||||
| SQL_HOST | string | localhost | The adress used to connect to your postgres database |
|
||||
| SQL_PORT | string | 5432 | The port used to connect to your postgres database |
|
||||
| SESSION_EXPIRY_TIME | int | 2678400 (31 days) | The age of session cookies, in seconds. E.g. how long you will stay logged in |
|
||||
| ENABLE_SOFT_DELETE | true\|false | false | Whether to enable transactions soft delete, if enabled, deleted transactions will remain in the database. Useful for imports and avoiding duplicate entries. |
|
||||
| KEEP_DELETED_TRANSACTIONS_FOR | int | 365 | Time in days to keep soft deleted transactions for. If 0, will keep all transactions indefinitely. Only works if ENABLE_SOFT_DELETE is true. |
|
||||
| TASK_WORKERS | int | 1 | How many workers to have for async tasks. One should be enough for most use cases |
|
||||
|
||||
# How it works
|
||||
|
||||
## Models
|
||||
Check out our [Wiki](https://github.com/eitchtee/WYGIWYH/wiki) for more information.
|
||||
|
||||
### Transactions
|
||||
|
||||
Transactions are the core feature of WYGIWYH, representing expenses or income in your accounts. Each transaction consists of the following fields:
|
||||
|
||||
#### Type
|
||||
|
||||
- **Income**: A positive amount entering your account
|
||||
- **Expense**: A negative amount exiting your account
|
||||
|
||||
#### Paid Status
|
||||
|
||||
A transaction can be either:
|
||||
|
||||
- **Current**: When marked as paid
|
||||
- **Projected**: When marked as unpaid
|
||||
|
||||
#### Account
|
||||
|
||||
The account associated with the transaction. Required, limited to one account per transaction.
|
||||
|
||||
#### Entity
|
||||
|
||||
The party involved in the transaction:
|
||||
|
||||
- For **Income**: The paying entity
|
||||
- For **Expense**: The receiving entity
|
||||
|
||||
Optional field.
|
||||
|
||||
#### Date
|
||||
|
||||
The date when the transaction occurred. Required.
|
||||
|
||||
#### Reference Date
|
||||
|
||||
One of **WYGIWYH**'s key features. The reference date determines which month a transaction should count towards. For example, you can have a transaction that occurred on January 26th count towards February's finances.
|
||||
|
||||
Optional - defaults to the transaction date's month if not specified.
|
||||
|
||||
> [!CAUTION]
|
||||
> While designed primarily for credit card closing dates, this feature allows for debt rolling across months. Use responsibly to maintain accurate financial tracking.
|
||||
|
||||
#### Type
|
||||
|
||||
- Income, meaning a positive amount (usually) entering your account
|
||||
- Expense, meaning a negative amount exiting your account
|
||||
|
||||
#### Description
|
||||
|
||||
The name or purpose of the transaction. Required.
|
||||
|
||||
#### Amount
|
||||
|
||||
The monetary value of the transaction. Required.
|
||||
|
||||
#### Category
|
||||
|
||||
The primary classification of the transaction. Optional.
|
||||
|
||||
#### Tags
|
||||
|
||||
Additional labels for transaction categorization. Optional.
|
||||
|
||||
#### Notes
|
||||
|
||||
Additional information about the transaction. Optional.
|
||||
|
||||

|
||||
|
||||
### Installment Plan
|
||||
|
||||
An Installment Plan is a helper model that generates a series of recurring transactions over a fixed period.
|
||||
|
||||
#### Core Fields
|
||||
|
||||
- **Account**: The account for all transactions in the plan. Required.
|
||||
- **Entity**: The paying or receiving party for all transactions. Optional.
|
||||
- **Description**: The name of the installment plan, used for all transactions. Required.
|
||||
- **Notes**: Additional information applied to all transactions. Optional.
|
||||
|
||||
#### Installment Configuration
|
||||
|
||||
- **Number of Installments**: Total number of transactions to create (e.g., 1/10, 2/10)
|
||||
- **Installment Start**: Initial counting point
|
||||
- **Start Date**: Date of the first transaction
|
||||
- **Reference Date**: Reference date for the first transaction
|
||||
- **Recurrence**: Frequency of transactions (e.g., Monthly)
|
||||
|
||||

|
||||
|
||||
### Transaction Details
|
||||
|
||||
- **Amount**: Value for each transaction. Required.
|
||||
- **Category**: Primary classification for all transactions. Optional.
|
||||
- **Tags**: Labels applied to all transactions. Optional.
|
||||
|
||||
### Recurring Transaction
|
||||
A Recurring Transaction is a helper model that generates recurring transactions indefinitely or until a certain date.
|
||||
|
||||
#### Core Fields
|
||||
|
||||
- **Account**: The account for all transactions in the plan. Required.
|
||||
- **Entity**: The paying or receiving party for all transactions. Optional.
|
||||
- **Description**: The name of the recurring transaction, used for all transactions. Required.
|
||||
- **Notes**: Additional information applied to all transactions. Optional.
|
||||
|
||||
#### Recurring Transaction Configuration
|
||||
|
||||
- **Start Date**: Date of the first transaction. Required.
|
||||
- **Reference Date**: Reference date for the first transaction. Optional.
|
||||
- **Recurrence Type**: Frequency of transactions (e.g., Monthly). Required.
|
||||
- **Recurrence Interval**: The interval between transactions (e.g. every 1 month, every 2 weeks, etc.). Required.
|
||||
- **End date**: When new transactions should stop being created. Optional.
|
||||
|
||||
#### Transaction Details
|
||||
|
||||
- **Amount**: Value for each transaction. Required.
|
||||
- **Category**: Primary classification for all transactions. Optional.
|
||||
- **Tags**: Labels applied to all transactions. Optional.
|
||||
|
||||
#### Other information
|
||||
|
||||
- Recurring transactions are checked and created every midnight using Procrastinate.
|
||||
- **WYGIWYH** tries to keep at most **6** future transactions created at any time.
|
||||
- If you delete a recurring transaction it will not be recreated.
|
||||
- You can stop or pause a recurring transaction at any time on the config page (/recurring-trasanctions/)
|
||||
|
||||

|
||||
|
||||
### Account
|
||||
|
||||
Accounts represent different financial entities where transactions occur. They have the following attributes:
|
||||
|
||||
- **Name**: A unique identifier for the account.
|
||||
- **Group**: An optional [account group](#account-groups) the account belongs to for organizational purposes.
|
||||
- **Currency**: The primary [currency](#currency) of the account.
|
||||
- **Exchange Currency**: An optional currency used for exchange rate calculations.
|
||||
- **Is Asset**: A boolean indicating if the account is considered an asset (counts towards net worth).
|
||||
- **Is Archived**: A boolean indicating if the account is archived (doesn't show up in active lists or count towards net worth).
|
||||
|
||||
### Account Groups
|
||||
|
||||
Account Groups are used to organize accounts into logical categories. They consist of:
|
||||
|
||||
- **Name**: A unique identifier for the group.
|
||||
|
||||
### Currency
|
||||
|
||||
Currencies represent different monetary units. They include:
|
||||
|
||||
* **Code**: A unique identifier for the currency (e.g., USD, EUR).
|
||||
* **Name**: The full name of the currency.
|
||||
* **Decimal Place**: The number of decimal places used for the currency.
|
||||
* **Prefix**: An optional symbol or text that comes before the amount.
|
||||
* **Suffix**: An optional symbol or text that comes after the amount.
|
||||
|
||||
### Exchange Rate
|
||||
|
||||
Exchange Rates store conversion rates between currencies:
|
||||
|
||||
* **From Currency**: The source currency.
|
||||
* **To Currency**: The target currency.
|
||||
* **Rate**: The conversion rate.
|
||||
* **Date**: The date the rate was recorded or is valid for.
|
||||
|
||||
### Category
|
||||
|
||||
Categories are used to classify transactions:
|
||||
|
||||
* **Name**: A unique identifier for the category.
|
||||
* **Muted**: Muted categories won't count towards your monthly total.
|
||||
* **Active**: A boolean indicating if the category is currently in use. This will disable its use on new transactions.
|
||||
|
||||
### Tag
|
||||
|
||||
Tags provide additional labeling for transactions:
|
||||
|
||||
* **Name**: A unique identifier for the tag.
|
||||
* **Active**: A boolean indicating if the category is currently in use. This will disable its use on new transactions.
|
||||
|
||||
### Entity
|
||||
|
||||
Entities represent parties involved in transactions:
|
||||
|
||||
* **Name**: A unique identifier for the entity.
|
||||
* **Active**: A boolean indicating if the entity is currently in use. This will disable its use on new transactions.
|
||||
|
||||
---
|
||||
|
||||
## Helper actions
|
||||
|
||||
### Transfer
|
||||
|
||||
A transfer happens when you move a monetary value from one account to another. This will create two transactions, one expense and one income with the values set by the user.
|
||||
|
||||
Contrary to other finance trackers, due to our multi-currency support, **WYGIWYH**'s transfer system allows for non-zero transfers.
|
||||
|
||||

|
||||
|
||||
### Balance (Account Reconciliation)
|
||||
|
||||
A balance is a easy way of updating your accounts balance. It creates a transaction with the difference between the balance currently in **WYGIWYH** and the new balance informed by you.
|
||||
|
||||
This can be useful for savings accounts or other interest accruing investments.
|
||||
|
||||
---
|
||||
|
||||
## Views
|
||||
|
||||
### Monthly
|
||||
|
||||
The Monthly view provides an overview of your financial activity for a specific month. It includes:
|
||||
|
||||
* Total income and expenses for the month
|
||||
* Daily spending allowance calculation
|
||||
* List of transactions for the month
|
||||
|
||||
> [!NOTE]
|
||||
> Reference dates are taken into account here.
|
||||
|
||||
### Yearly by currency
|
||||
|
||||
This view gives you a yearly summary of your finances grouped by currency. It shows:
|
||||
|
||||
* Total income and expenses for each currency
|
||||
* Monthly breakdown of income and expenses
|
||||
|
||||
### Yearly by account
|
||||
|
||||
Similar to the [yearly by currency](#yearly-by-currency) view, but groups the data by account instead.
|
||||
|
||||
### Calendar
|
||||
|
||||
The Calendar view presents your transactions in a monthly calendar format, allowing you to see your financial activity day by day. It includes:
|
||||
|
||||
* Visual representation of daily transaction totals
|
||||
* Ability to view details of transactions for each day
|
||||
|
||||
> [!NOTE]
|
||||
> Reference dates are **not** taken into account here.
|
||||
|
||||
### Networh
|
||||
|
||||
#### Current
|
||||
|
||||
The Current Net Worth view shows your present financial standing, including:
|
||||
|
||||
* Total value of all asset accounts
|
||||
* Breakdown of assets by account and currency
|
||||
* Historical net worth trend
|
||||
|
||||
#### Projected
|
||||
|
||||
The Projected Net Worth view estimates your future financial position based on current data and recurring transactions. It includes:
|
||||
|
||||
* Your total net worth with projected and current transactions
|
||||
* Breakdown of assets by account and currency
|
||||
* Historical and future net worth trend
|
||||
|
||||
### All Transactions
|
||||
|
||||
This view provides a comprehensive list of all transactions across all accounts. Features include:
|
||||
|
||||
* Advanced filtering and sorting options
|
||||
* Detailed information
|
||||
|
||||
You can use this to see how much you spent on a given category, or a given day, etc..
|
||||
|
||||
### Configuration and Management
|
||||
|
||||
#### Management
|
||||
The Management section in the navbar allows you to add and edit most elements of WYGIWYH, including:
|
||||
|
||||
* Accounts and Groups
|
||||
* Currencies and Exchange Rates
|
||||
* Categories, Tags and Entities
|
||||
* Rules
|
||||
|
||||
#### User Settings
|
||||
|
||||
WYGIWYH allows users to personalize their experience through customizable settings. Each user can configure:
|
||||
|
||||
* **Language**: Choose your preferred interface language.
|
||||
* **Timezone**: Set your local timezone for accurate date and time display.
|
||||
* **Start Page**: Select which page you want to see first when you log in.
|
||||
* **Sound Preferences**: Toggle sound effects on or off.
|
||||
* **Amount Display**: Choose to show or hide monetary amounts by default.
|
||||
|
||||
To access and modify these settings:
|
||||
|
||||
1. Click on your username in the top-right corner of the page.
|
||||
2. Select "Settings" from the dropdown menu.
|
||||
3. Adjust your preferences as desired.
|
||||
4. Click "Save" to apply your changes.
|
||||
|
||||
These settings ensure that WYGIWYH adapts to your personal preferences and working style.
|
||||
|
||||
#### Django Admin
|
||||
From here you can also access Django's own admin site.
|
||||
|
||||
> [!WARNING]
|
||||
> Most side effects aren't triggered from the admin.
|
||||
> Only use it if you know what you're doing or were told by a developer to do so.
|
||||
|
||||
---
|
||||
|
||||
## Tools
|
||||
|
||||
### Calculator
|
||||
|
||||
The calculator is a floating widget that can be toggled by clicking the calculator icon on the navbar or by pressing <kbd>Alt</kbd> + <kbd>C</kbd> on any page.
|
||||
|
||||
It allows for any math expression supported by [math.js](https://mathjs.org).
|
||||
|
||||

|
||||
|
||||
### Dollar Cost Average Tracker
|
||||
|
||||
The DCA Tracker can be accessed from the navbar's **Tools** menu.
|
||||
|
||||
It allows for tracking DCA strategies and getting helpful information and insights.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Currently DCA exists separately from your main transactions. You will need to add your entries manually.
|
||||
|
||||
<img src=".github/img/readme_dca_1.png" width="45%"></img> <img src=".github/img/readme_dca_2.png" width="45%"></img>
|
||||
|
||||
### Unit Price Calculator
|
||||
|
||||
The Unit Price Calculator can be accessed from the navbar's **Tools** menu.
|
||||
|
||||
This is a self-contained tool for comparing and finding the most cost-efficient item quickly and easily.
|
||||
|
||||
Input the price and the amount of each item, the cheapeast will be highlighted in green, and the most expensive in red.
|
||||
|
||||
You can add additional items by clicking the _Add_ button at the end of the page.
|
||||
|
||||
> [!NOTE]
|
||||
> This doesn't do unit convertion. The amount of all items needs to be on the same the unit for proper functioning.
|
||||
|
||||

|
||||
|
||||
### Currency Converter
|
||||
|
||||
The Currency Converter is a tool that allows you to quickly convert amounts between different currencies.
|
||||
|
||||
> [!NOTE]
|
||||
> There's no external Exchange Rate fetching. This uses the Exchange Rates configured in the [Management](#configuration-and-management) page for [Exchange Rates](#exchange-rate)
|
||||
|
||||
## Automation
|
||||
|
||||
### API
|
||||
|
||||
WYGIWYH has a comprehensive API, it's documentation can be accessed on `<your-wygiwyh-url>/api/docs/`
|
||||
|
||||
> [!NOTE]
|
||||
> While the API works, there's still much to be added to it to equipare functionality with the main web app.
|
||||
|
||||
### Transaction Rules
|
||||
|
||||
Transaction Rules are a powerful feature in WYGIWYH that allow for automatic modification of transactions based on specified criteria. This can save time and ensure consistency in your financial tracking.
|
||||
|
||||
Key Aspects of Transaction Rules:
|
||||
|
||||
* **Conditions**: Set specific criteria that a transaction must meet for the rule to apply. This can include attributes like description, amount, account, etc.
|
||||
* **Actions**: Define what changes should be made to a transaction when the conditions are met. This can include setting categories, tags, or modifying other fields.
|
||||
* **Activation Options**: Rules can be set to apply when transactions are created, updated, or both.
|
||||
|
||||
#### Actions and Conditions
|
||||
|
||||
When creating a new rule, you will need to add a Condition and, later, Actions.
|
||||
|
||||
Both use a limited subset of Python, via [SimpleEval](https://github.com/danthedeckie/simpleeval).
|
||||
|
||||
The Condition must evaluate to True or False, and the Action must evaluate to a value that will be set on the selected field.
|
||||
|
||||
You may use any of the available [variables](#available-variables) and [functions](#available-functions).
|
||||
|
||||
#### Available variables
|
||||
|
||||
* `account_name`
|
||||
* `account_id`
|
||||
* `account_group_name`
|
||||
* `account_group_id`
|
||||
* `is_asset_account`
|
||||
* `is_archived_account`
|
||||
* `category_name`
|
||||
* `category_id`
|
||||
* `tag_names`
|
||||
* `tag_ids`
|
||||
* `entities_names`
|
||||
* `entities_ids`
|
||||
* `is_expense`
|
||||
* `is_income`
|
||||
* `is_paid`
|
||||
* `description`
|
||||
* `amount`
|
||||
* `notes`
|
||||
* `date`
|
||||
* `reference_date`
|
||||
|
||||
#### Available functions
|
||||
|
||||
* `relativedelta`
|
||||
|
||||
#### Examples
|
||||
|
||||
Add a tag to an income transaction if it happens in a specific account
|
||||
|
||||
```
|
||||
If...
|
||||
account_name == "My Investing Account" and is_income
|
||||
|
||||
Then...
|
||||
Set Tags to
|
||||
tag_names + ["Yield"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Move credit card transactions to next month when they happen at a cutoff date
|
||||
|
||||
```
|
||||
If...
|
||||
account_name == "My credit card" and date.day >= 26 and reference_date.month == date.month
|
||||
|
||||
Then...
|
||||
Set Reference Date to
|
||||
reference_date + relativedelta(months=1)).replace(day=1)
|
||||
```
|
||||
# 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.
|
||||
|
||||
@@ -31,10 +31,8 @@ SECRET_KEY = os.getenv("SECRET_KEY", "")
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = os.getenv("DEBUG", "false").lower() == "true"
|
||||
|
||||
ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "localhost 127.0.0.1").split(" ")
|
||||
CSRF_TRUSTED_ORIGINS = os.environ.get("URL", "http://localhost http://127.0.0.1").split(
|
||||
" "
|
||||
)
|
||||
ALLOWED_HOSTS = os.getenv("DJANGO_ALLOWED_HOSTS", "localhost 127.0.0.1").split(" ")
|
||||
CSRF_TRUSTED_ORIGINS = os.getenv("URL", "http://localhost http://127.0.0.1").split(" ")
|
||||
|
||||
# Application definition
|
||||
|
||||
@@ -128,11 +126,11 @@ WSGI_APPLICATION = "WYGIWYH.wsgi.application"
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.postgresql",
|
||||
"NAME": os.environ.get("SQL_DATABASE"),
|
||||
"USER": os.environ.get("SQL_USER", "user"),
|
||||
"PASSWORD": os.environ.get("SQL_PASSWORD", "password"),
|
||||
"HOST": os.environ.get("SQL_HOST", "localhost"),
|
||||
"PORT": os.environ.get("SQL_PORT", "5432"),
|
||||
"NAME": os.getenv("SQL_DATABASE"),
|
||||
"USER": os.getenv("SQL_USER", "user"),
|
||||
"PASSWORD": os.getenv("SQL_PASSWORD", "password"),
|
||||
"HOST": os.getenv("SQL_HOST", "localhost"),
|
||||
"PORT": os.getenv("SQL_PORT", "5432"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,3 +386,4 @@ PWA_SERVICE_WORKER_PATH = BASE_DIR / "templates" / "pwa" / "serviceworker.js"
|
||||
|
||||
ENABLE_SOFT_DELETE = os.getenv("ENABLE_SOFT_DELETE", "false").lower() == "true"
|
||||
KEEP_DELETED_TRANSACTIONS_FOR = int(os.getenv("KEEP_DELETED_ENTRIES_FOR", "365"))
|
||||
APP_VERSION = os.getenv("APP_VERSION", "unknown")
|
||||
|
||||
9
app/apps/common/templatetags/settings.py
Normal file
9
app/apps/common/templatetags/settings.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from django import template
|
||||
from django.conf import settings
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.simple_tag(name="settings")
|
||||
def settings_value(name):
|
||||
return getattr(settings, name, "")
|
||||
@@ -489,18 +489,18 @@ class ImportService:
|
||||
value = None
|
||||
|
||||
if isinstance(mapping.source, str):
|
||||
value = row.get(mapping.source)
|
||||
value = row.get(mapping.source, None)
|
||||
elif isinstance(mapping.source, list):
|
||||
for source in mapping.source:
|
||||
value = row.get(source)
|
||||
if value is not None:
|
||||
value = row.get(source, None)
|
||||
if value:
|
||||
break
|
||||
else:
|
||||
# If source is None, use None as the initial value
|
||||
value = None
|
||||
|
||||
# Use default_value if value is None
|
||||
if value is None:
|
||||
if not value:
|
||||
value = mapping.default
|
||||
|
||||
# Apply transformations
|
||||
|
||||
@@ -19,4 +19,19 @@ urlpatterns = [
|
||||
views.monthly_summary,
|
||||
name="monthly_summary",
|
||||
),
|
||||
path(
|
||||
"monthly/<int:month>/<int:year>/summary/accounts/",
|
||||
views.monthly_account_summary,
|
||||
name="monthly_account_summary",
|
||||
),
|
||||
path(
|
||||
"monthly/<int:month>/<int:year>/summary/currencies/",
|
||||
views.monthly_currency_summary,
|
||||
name="monthly_currency_summary",
|
||||
),
|
||||
path(
|
||||
"monthly/summary/select/<str:selected>/",
|
||||
views.monthly_summary_select,
|
||||
name="monthly_summary_select",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -2,6 +2,7 @@ from django.contrib.auth.decorators import login_required
|
||||
from django.db.models import (
|
||||
Q,
|
||||
)
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import render, redirect
|
||||
from django.utils import timezone
|
||||
from django.views.decorators.http import require_http_methods
|
||||
@@ -16,6 +17,7 @@ from apps.transactions.models import Transaction
|
||||
from apps.transactions.utils.calculations import (
|
||||
calculate_currency_totals,
|
||||
calculate_percentage_distribution,
|
||||
calculate_account_totals,
|
||||
)
|
||||
from apps.transactions.utils.default_ordering import default_order
|
||||
|
||||
@@ -31,6 +33,7 @@ def index(request):
|
||||
@require_http_methods(["GET"])
|
||||
def monthly_overview(request, month: int, year: int):
|
||||
order = request.session.get("monthly_transactions_order", "default")
|
||||
summary_tab = request.session.get("monthly_summary_tab", "summary")
|
||||
|
||||
if month < 1 or month > 12:
|
||||
from django.http import Http404
|
||||
@@ -57,6 +60,7 @@ def monthly_overview(request, month: int, year: int):
|
||||
"previous_year": previous_year,
|
||||
"filter": f,
|
||||
"order": order,
|
||||
"summary_tab": summary_tab,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -131,3 +135,61 @@ def monthly_summary(request, month: int, year: int):
|
||||
"monthly_overview/fragments/monthly_summary.html",
|
||||
context=context,
|
||||
)
|
||||
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
@require_http_methods(["GET"])
|
||||
def monthly_account_summary(request, month: int, year: int):
|
||||
# Base queryset with all required filters
|
||||
base_queryset = Transaction.objects.filter(
|
||||
reference_date__year=year,
|
||||
reference_date__month=month,
|
||||
).exclude(Q(category__mute=True) & ~Q(category=None))
|
||||
|
||||
account_data = calculate_account_totals(transactions_queryset=base_queryset.all())
|
||||
account_percentages = calculate_percentage_distribution(account_data)
|
||||
|
||||
context = {
|
||||
"account_data": account_data,
|
||||
"account_percentages": account_percentages,
|
||||
}
|
||||
|
||||
return render(
|
||||
request,
|
||||
"monthly_overview/fragments/monthly_account_summary.html",
|
||||
context=context,
|
||||
)
|
||||
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
@require_http_methods(["GET"])
|
||||
def monthly_currency_summary(request, month: int, year: int):
|
||||
# Base queryset with all required filters
|
||||
base_queryset = Transaction.objects.filter(
|
||||
reference_date__year=year,
|
||||
reference_date__month=month,
|
||||
).exclude(Q(category__mute=True) & ~Q(category=None))
|
||||
|
||||
currency_data = calculate_currency_totals(base_queryset.all(), ignore_empty=True)
|
||||
currency_percentages = calculate_percentage_distribution(currency_data)
|
||||
|
||||
context = {
|
||||
"currency_data": currency_data,
|
||||
"currency_percentages": currency_percentages,
|
||||
}
|
||||
|
||||
return render(
|
||||
request, "monthly_overview/fragments/monthly_currency_summary.html", context
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_http_methods(["GET"])
|
||||
def monthly_summary_select(request, selected):
|
||||
request.session["monthly_summary_tab"] = selected
|
||||
|
||||
return HttpResponse(
|
||||
status=204,
|
||||
)
|
||||
|
||||
@@ -228,9 +228,12 @@ class Transaction(models.Model):
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
if settings.ENABLE_SOFT_DELETE:
|
||||
self.deleted = True
|
||||
self.deleted_at = timezone.now()
|
||||
self.save()
|
||||
if not self.deleted:
|
||||
self.deleted = True
|
||||
self.deleted_at = timezone.now()
|
||||
self.save()
|
||||
else:
|
||||
super().delete(*args, **kwargs)
|
||||
else:
|
||||
super().delete(*args, **kwargs)
|
||||
|
||||
|
||||
@@ -6,11 +6,36 @@ urlpatterns = [
|
||||
path(
|
||||
"transactions/list/", views.transaction_all_list, name="transactions_all_list"
|
||||
),
|
||||
path(
|
||||
"transactions/trash/",
|
||||
views.transactions_trash_can_index,
|
||||
name="transactions_trash_index",
|
||||
),
|
||||
path(
|
||||
"transactions/trash/list/",
|
||||
views.transactions_trash_can_list,
|
||||
name="transactions_trash_list",
|
||||
),
|
||||
path(
|
||||
"transactions/summary/",
|
||||
views.transaction_all_summary,
|
||||
name="transactions_all_summary",
|
||||
),
|
||||
path(
|
||||
"transactions/summary/account/",
|
||||
views.transaction_all_account_summary,
|
||||
name="transaction_all_account_summary",
|
||||
),
|
||||
path(
|
||||
"transactions/summary/currency/",
|
||||
views.transaction_all_currency_summary,
|
||||
name="transaction_all_currency_summary",
|
||||
),
|
||||
path(
|
||||
"transactions/summary/select/<str:selected>/",
|
||||
views.transaction_all_summary_select,
|
||||
name="transaction_all_summary_select",
|
||||
),
|
||||
path(
|
||||
"transactions/actions/pay/",
|
||||
views.bulk_pay_transactions,
|
||||
@@ -26,6 +51,11 @@ urlpatterns = [
|
||||
views.bulk_delete_transactions,
|
||||
name="transactions_bulk_delete",
|
||||
),
|
||||
path(
|
||||
"transactions/actions/undelete/",
|
||||
views.bulk_undelete_transactions,
|
||||
name="transactions_bulk_undelete",
|
||||
),
|
||||
path(
|
||||
"transactions/actions/duplicate/",
|
||||
views.bulk_clone_transactions,
|
||||
@@ -41,6 +71,11 @@ urlpatterns = [
|
||||
views.transaction_delete,
|
||||
name="transaction_delete",
|
||||
),
|
||||
path(
|
||||
"transaction/<int:transaction_id>/undelete/",
|
||||
views.transaction_undelete,
|
||||
name="transaction_undelete",
|
||||
),
|
||||
path(
|
||||
"transaction/<int:transaction_id>/edit/",
|
||||
views.transaction_edit,
|
||||
|
||||
@@ -72,8 +72,12 @@ def calculate_currency_totals(transactions_queryset, ignore_empty=False):
|
||||
.order_by()
|
||||
)
|
||||
|
||||
# Process the results and calculate additional totals
|
||||
# First pass: Process basic totals and store all currency data
|
||||
result = {}
|
||||
currencies_using_exchange = (
|
||||
{}
|
||||
) # Track which currencies use which exchange currencies
|
||||
|
||||
for total in currency_totals:
|
||||
# Skip empty currencies if ignore_empty is True
|
||||
if ignore_empty and all(
|
||||
@@ -91,7 +95,6 @@ def calculate_currency_totals(transactions_queryset, ignore_empty=False):
|
||||
total_current = total["income_current"] - total["expense_current"]
|
||||
total_projected = total["income_projected"] - total["expense_projected"]
|
||||
total_final = total_current + total_projected
|
||||
|
||||
currency_id = total["account__currency"]
|
||||
from_currency = Currency.objects.get(id=currency_id)
|
||||
exchange_currency = (
|
||||
@@ -120,8 +123,6 @@ def calculate_currency_totals(transactions_queryset, ignore_empty=False):
|
||||
# Add exchanged values if exchange_currency exists
|
||||
if exchange_currency:
|
||||
exchanged = {}
|
||||
|
||||
# Convert each value
|
||||
for field in [
|
||||
"expense_current",
|
||||
"expense_projected",
|
||||
@@ -136,7 +137,6 @@ def calculate_currency_totals(transactions_queryset, ignore_empty=False):
|
||||
from_currency=from_currency,
|
||||
to_currency=exchange_currency,
|
||||
)
|
||||
|
||||
if amount is not None:
|
||||
exchanged[field] = amount
|
||||
if "currency" not in exchanged:
|
||||
@@ -148,12 +148,48 @@ def calculate_currency_totals(transactions_queryset, ignore_empty=False):
|
||||
"name": exchange_currency.name,
|
||||
}
|
||||
|
||||
# Only add exchanged data if at least one conversion was successful
|
||||
if exchanged:
|
||||
currency_data["exchanged"] = exchanged
|
||||
# Track which currencies are using which exchange currencies
|
||||
if exchange_currency.id not in currencies_using_exchange:
|
||||
currencies_using_exchange[exchange_currency.id] = []
|
||||
currencies_using_exchange[exchange_currency.id].append(
|
||||
{"currency_id": currency_id, "exchanged": exchanged}
|
||||
)
|
||||
|
||||
result[currency_id] = currency_data
|
||||
|
||||
# Second pass: Add consolidated totals for currencies that are used as exchange currencies
|
||||
for currency_id, currency_data in result.items():
|
||||
if currency_id in currencies_using_exchange:
|
||||
consolidated = {
|
||||
"currency": currency_data["currency"].copy(),
|
||||
"expense_current": currency_data["expense_current"],
|
||||
"expense_projected": currency_data["expense_projected"],
|
||||
"income_current": currency_data["income_current"],
|
||||
"income_projected": currency_data["income_projected"],
|
||||
"total_current": currency_data["total_current"],
|
||||
"total_projected": currency_data["total_projected"],
|
||||
"total_final": currency_data["total_final"],
|
||||
}
|
||||
|
||||
# Add exchanged values from all currencies using this as exchange currency
|
||||
for using_currency in currencies_using_exchange[currency_id]:
|
||||
exchanged = using_currency["exchanged"]
|
||||
for field in [
|
||||
"expense_current",
|
||||
"expense_projected",
|
||||
"income_current",
|
||||
"income_projected",
|
||||
"total_current",
|
||||
"total_projected",
|
||||
"total_final",
|
||||
]:
|
||||
if field in exchanged:
|
||||
consolidated[field] += exchanged[field]
|
||||
|
||||
result[currency_id]["consolidated"] = consolidated
|
||||
|
||||
return result
|
||||
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ def bulk_unpay_transactions(request):
|
||||
@login_required
|
||||
def bulk_delete_transactions(request):
|
||||
selected_transactions = request.GET.getlist("transactions", [])
|
||||
transactions = Transaction.objects.filter(id__in=selected_transactions)
|
||||
transactions = Transaction.all_objects.filter(id__in=selected_transactions)
|
||||
count = transactions.count()
|
||||
transactions.delete()
|
||||
|
||||
@@ -81,6 +81,30 @@ def bulk_delete_transactions(request):
|
||||
)
|
||||
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
def bulk_undelete_transactions(request):
|
||||
selected_transactions = request.GET.getlist("transactions", [])
|
||||
transactions = Transaction.deleted_objects.filter(id__in=selected_transactions)
|
||||
count = transactions.count()
|
||||
transactions.update(deleted=False, deleted_at=None)
|
||||
|
||||
messages.success(
|
||||
request,
|
||||
ngettext_lazy(
|
||||
"%(count)s transaction restored successfully",
|
||||
"%(count)s transactions restored successfully",
|
||||
count,
|
||||
)
|
||||
% {"count": count},
|
||||
)
|
||||
|
||||
return HttpResponse(
|
||||
status=204,
|
||||
headers={"HX-Trigger": "updated"},
|
||||
)
|
||||
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
def bulk_clone_transactions(request):
|
||||
|
||||
@@ -244,7 +244,7 @@ def transaction_clone(request, transaction_id, **kwargs):
|
||||
@login_required
|
||||
@require_http_methods(["DELETE"])
|
||||
def transaction_delete(request, transaction_id, **kwargs):
|
||||
transaction = get_object_or_404(Transaction, id=transaction_id)
|
||||
transaction = get_object_or_404(Transaction.all_objects, id=transaction_id)
|
||||
|
||||
transaction.delete()
|
||||
|
||||
@@ -256,6 +256,24 @@ def transaction_delete(request, transaction_id, **kwargs):
|
||||
)
|
||||
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
@require_http_methods(["GET"])
|
||||
def transaction_undelete(request, transaction_id, **kwargs):
|
||||
transaction = get_object_or_404(Transaction.deleted_objects, id=transaction_id)
|
||||
|
||||
transaction.deleted = False
|
||||
transaction.deleted_at = None
|
||||
transaction.save()
|
||||
|
||||
messages.success(request, _("Transaction restored successfully"))
|
||||
|
||||
return HttpResponse(
|
||||
status=204,
|
||||
headers={"HX-Trigger": "updated"},
|
||||
)
|
||||
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
@require_http_methods(["GET", "POST"])
|
||||
@@ -314,9 +332,13 @@ def transaction_pay(request, transaction_id):
|
||||
@require_http_methods(["GET"])
|
||||
def transaction_all_index(request):
|
||||
order = request.session.get("all_transactions_order", "default")
|
||||
summary_tab = request.session.get("transaction_all_summary_tab", "currency")
|
||||
|
||||
f = TransactionsFilter(request.GET)
|
||||
return render(
|
||||
request, "transactions/pages/transactions.html", {"filter": f, "order": order}
|
||||
request,
|
||||
"transactions/pages/transactions.html",
|
||||
{"filter": f, "order": order, "summary_tab": summary_tab},
|
||||
)
|
||||
|
||||
|
||||
@@ -382,16 +404,98 @@ def transaction_all_summary(request):
|
||||
account_percentages = calculate_percentage_distribution(account_data)
|
||||
|
||||
context = {
|
||||
"income_current": remove_falsey_entries(currency_data, "income_current"),
|
||||
"income_projected": remove_falsey_entries(currency_data, "income_projected"),
|
||||
"expense_current": remove_falsey_entries(currency_data, "expense_current"),
|
||||
"expense_projected": remove_falsey_entries(currency_data, "expense_projected"),
|
||||
"total_current": remove_falsey_entries(currency_data, "total_current"),
|
||||
"total_final": remove_falsey_entries(currency_data, "total_final"),
|
||||
"total_projected": remove_falsey_entries(currency_data, "total_projected"),
|
||||
"currency_data": currency_data,
|
||||
"currency_percentages": currency_percentages,
|
||||
"account_data": account_data,
|
||||
"account_percentages": account_percentages,
|
||||
}
|
||||
|
||||
return render(request, "transactions/fragments/summary.html", context)
|
||||
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
@require_http_methods(["GET"])
|
||||
def transaction_all_account_summary(request):
|
||||
transactions = Transaction.objects.prefetch_related(
|
||||
"account",
|
||||
"account__group",
|
||||
"category",
|
||||
"tags",
|
||||
"account__exchange_currency",
|
||||
"account__currency",
|
||||
"installment_plan",
|
||||
).all()
|
||||
|
||||
f = TransactionsFilter(request.GET, queryset=transactions)
|
||||
|
||||
account_data = calculate_account_totals(transactions_queryset=f.qs.all())
|
||||
account_percentages = calculate_percentage_distribution(account_data)
|
||||
|
||||
context = {
|
||||
"account_data": account_data,
|
||||
"account_percentages": account_percentages,
|
||||
}
|
||||
|
||||
return render(request, "transactions/fragments/all_account_summary.html", context)
|
||||
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
@require_http_methods(["GET"])
|
||||
def transaction_all_currency_summary(request):
|
||||
transactions = Transaction.objects.prefetch_related(
|
||||
"account",
|
||||
"account__group",
|
||||
"category",
|
||||
"tags",
|
||||
"account__exchange_currency",
|
||||
"account__currency",
|
||||
"installment_plan",
|
||||
).all()
|
||||
|
||||
f = TransactionsFilter(request.GET, queryset=transactions)
|
||||
|
||||
currency_data = calculate_currency_totals(f.qs.all(), ignore_empty=True)
|
||||
currency_percentages = calculate_percentage_distribution(currency_data)
|
||||
|
||||
context = {
|
||||
"currency_data": currency_data,
|
||||
"currency_percentages": currency_percentages,
|
||||
}
|
||||
|
||||
return render(request, "transactions/fragments/all_currency_summary.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_http_methods(["GET"])
|
||||
def transaction_all_summary_select(request, selected):
|
||||
request.session["transaction_all_summary_tab"] = selected
|
||||
|
||||
return HttpResponse(
|
||||
status=204,
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_http_methods(["GET"])
|
||||
def transactions_trash_can_index(request):
|
||||
return render(request, "transactions/pages/trash.html")
|
||||
|
||||
|
||||
def transactions_trash_can_list(request):
|
||||
transactions = Transaction.deleted_objects.prefetch_related(
|
||||
"account",
|
||||
"account__group",
|
||||
"category",
|
||||
"tags",
|
||||
"account__exchange_currency",
|
||||
"account__currency",
|
||||
"installment_plan",
|
||||
).all()
|
||||
|
||||
return render(
|
||||
request,
|
||||
"transactions/fragments/trash_list.html",
|
||||
{"transactions": transactions},
|
||||
)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.5 on 2025-02-02 02:56
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0017_usersettings_number_format'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='usersettings',
|
||||
name='start_page',
|
||||
field=models.CharField(choices=[('MONTHLY_OVERVIEW', 'Monthly'), ('YEARLY_OVERVIEW_CURRENCY', 'Yearly by currency'), ('YEARLY_OVERVIEW_ACCOUNT', 'Yearly by account'), ('NETWORTH_CURRENT', 'Current Net Worth'), ('NETWORTH_PROJECTED', 'Projected Net Worth'), ('ALL_TRANSACTIONS', 'All Transactions'), ('CALENDAR', 'Calendar')], default='MONTHLY_OVERVIEW', max_length=255, verbose_name='Start page'),
|
||||
),
|
||||
]
|
||||
@@ -26,7 +26,8 @@ class UserSettings(models.Model):
|
||||
MONTHLY = "MONTHLY_OVERVIEW", _("Monthly")
|
||||
YEARLY_CURRENCY = "YEARLY_OVERVIEW_CURRENCY", _("Yearly by currency")
|
||||
YEARLY_ACCOUNT = "YEARLY_OVERVIEW_ACCOUNT", _("Yearly by account")
|
||||
NETWORTH = "NETWORTH", _("Net Worth")
|
||||
NETWORTH_CURRENT = "NETWORTH_CURRENT", _("Current Net Worth")
|
||||
NETWORTH_PROJECTED = "NETWORTH_PROJECTED", _("Projected Net Worth")
|
||||
ALL_TRANSACTIONS = "ALL_TRANSACTIONS", _("All Transactions")
|
||||
CALENDAR = "CALENDAR", _("Calendar")
|
||||
|
||||
|
||||
@@ -26,10 +26,14 @@ def logout_view(request):
|
||||
def index(request):
|
||||
if request.user.settings.start_page == UserSettings.StartPage.MONTHLY:
|
||||
return redirect(reverse("monthly_index"))
|
||||
elif request.user.settings.start_page == UserSettings.StartPage.YEARLY:
|
||||
return redirect(reverse("yearly_index"))
|
||||
elif request.user.settings.start_page == UserSettings.StartPage.NETWORTH:
|
||||
return redirect(reverse("net_worth"))
|
||||
elif request.user.settings.start_page == UserSettings.StartPage.YEARLY_ACCOUNT:
|
||||
return redirect(reverse("yearly_index_account"))
|
||||
elif request.user.settings.start_page == UserSettings.StartPage.YEARLY_CURRENCY:
|
||||
return redirect(reverse("yearly_index_currency"))
|
||||
elif request.user.settings.start_page == UserSettings.StartPage.NETWORTH_CURRENT:
|
||||
return redirect(reverse("net_worth_current"))
|
||||
elif request.user.settings.start_page == UserSettings.StartPage.NETWORTH_PROJECTED:
|
||||
return redirect(reverse("net_worth_projected"))
|
||||
elif request.user.settings.start_page == UserSettings.StartPage.ALL_TRANSACTIONS:
|
||||
return redirect(reverse("transactions_all_index"))
|
||||
elif request.user.settings.start_page == UserSettings.StartPage.CALENDAR:
|
||||
|
||||
@@ -89,7 +89,6 @@ def yearly_overview_by_currency(request, year: int):
|
||||
"year": year,
|
||||
"totals": data,
|
||||
"percentages": percentages,
|
||||
"single": True if currency else False,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -159,6 +158,5 @@ def yearly_overview_by_account(request, year: int):
|
||||
"year": year,
|
||||
"totals": data,
|
||||
"percentages": percentages,
|
||||
"single": True if account else False,
|
||||
},
|
||||
)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -44,53 +44,10 @@
|
||||
</div>
|
||||
{# Action buttons#}
|
||||
<div class="col-12 col-xl-8">
|
||||
<div class="d-grid gap-2 d-xl-flex justify-content-xl-end">
|
||||
<button class="btn btn-sm btn-outline-success"
|
||||
hx-get="{% url 'transaction_add' %}"
|
||||
hx-target="#generic-offcanvas"
|
||||
hx-trigger="click, add_income from:window"
|
||||
hx-vals='{"year": {{ year }}, "month": {{ month }}, "type": "IN"}'>
|
||||
<i class="fa-solid fa-arrow-right-to-bracket me-2"></i>
|
||||
{% translate "Income" %}
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-danger"
|
||||
hx-get="{% url 'transaction_add' %}"
|
||||
hx-target="#generic-offcanvas"
|
||||
hx-trigger="click, add_expense from:window"
|
||||
hx-vals='{"year": {{ year }}, "month": {{ month }}, "type": "EX"}'>
|
||||
<i class="fa-solid fa-arrow-right-from-bracket me-2"></i>
|
||||
{% translate "Expense" %}
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-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"
|
||||
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"
|
||||
hx-get="{% url 'transactions_transfer' %}"
|
||||
hx-target="#generic-offcanvas"
|
||||
hx-trigger="click, add_transfer from:window"
|
||||
hx-vals='{"year": {{ year }}, "month": {{ month }}}'>
|
||||
<i class="fa-solid fa-money-bill-transfer me-2"></i>
|
||||
{% translate "Transfer" %}
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-info"
|
||||
hx-get="{% url 'account_reconciliation' %}"
|
||||
hx-trigger="click, balance from:window"
|
||||
hx-target="#generic-offcanvas">
|
||||
<i class="fa-solid fa-scale-balanced me-2"></i>
|
||||
{% translate "Balance" %}
|
||||
</button>
|
||||
</div>
|
||||
<c-ui.quick-transactions-buttons
|
||||
:year="year"
|
||||
:month="month"
|
||||
></c-ui.quick-transactions-buttons>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
||||
@@ -2,83 +2,95 @@
|
||||
{% load i18n %}
|
||||
<div class="transaction d-flex my-1 {% if transaction.type == "EX" %}expense{% else %}income{% endif %}">
|
||||
{% if not disable_selection %}
|
||||
<label class="px-3 d-flex align-items-center justify-content-center">
|
||||
<input class="form-check-input" type="checkbox" name="transactions" value="{{ transaction.id }}" id="check-{{ transaction.id }}" aria-label="{% translate 'Select' %}" hx-preserve>
|
||||
</label>
|
||||
<label class="px-3 d-flex align-items-center justify-content-center">
|
||||
<input class="form-check-input" type="checkbox" name="transactions" value="{{ transaction.id }}"
|
||||
id="check-{{ transaction.id }}" aria-label="{% translate 'Select' %}" hx-preserve>
|
||||
</label>
|
||||
{% endif %}
|
||||
<div class="tw-border-s-6 tw-border-e-0 tw-border-t-0 tw-border-b-0 border-bottom
|
||||
hover:tw-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"
|
||||
_="on mouseover remove .tw-invisible from the first .transaction-actions in me end
|
||||
_="on mouseover remove .tw-invisible from the first .transaction-actions in me end
|
||||
on mouseout add .tw-invisible to the first .transaction-actions in me end">
|
||||
<div class="row font-monospace tw-text-sm align-items-center">
|
||||
<div class="col-lg-1 col-12 d-flex align-items-center tw-text-2xl lg:tw-text-xl text-lg-center text-center">
|
||||
<a class="text-decoration-none my-lg-3 mx-lg-3 mx-2 my-2 tw-text-gray-500"
|
||||
role="button"
|
||||
hx-get="{% url 'transaction_pay' transaction_id=transaction.id %}"
|
||||
hx-target="closest .transaction"
|
||||
hx-swap="outerHTML">
|
||||
<div class="col-lg-1 col-12 d-flex align-items-center tw-text-2xl lg:tw-text-xl text-lg-center text-center">
|
||||
{% if not transaction.deleted %}
|
||||
<a class="text-decoration-none my-lg-3 mx-lg-3 mx-2 my-2 tw-text-gray-500"
|
||||
title="{% if transaction.is_paid %}{% trans 'Paid' %}{% else %}{% trans 'Projected' %}{% endif %}"
|
||||
role="button"
|
||||
hx-get="{% url 'transaction_pay' transaction_id=transaction.id %}"
|
||||
hx-target="closest .transaction"
|
||||
hx-swap="outerHTML">
|
||||
{% if transaction.is_paid %}<i class="fa-regular fa-circle-check"></i>{% else %}<i
|
||||
class="fa-regular fa-circle"></i>{% endif %}
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-lg-8 col-12">
|
||||
{# Date#}
|
||||
<div class="row mb-2 mb-lg-1 tw-text-gray-400">
|
||||
</a>
|
||||
{% else %}
|
||||
<div class="text-decoration-none my-lg-3 mx-lg-3 mx-2 my-2 tw-text-gray-500"
|
||||
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 %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-lg-8 col-12">
|
||||
{# 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="col ps-0">{{ transaction.date|date:"SHORT_DATE_FORMAT" }} • {{ transaction.reference_date|date:"b/Y" }}</div>
|
||||
<div
|
||||
class="col 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-white tw-text-base">
|
||||
{% spaceless %}
|
||||
<span>{{ transaction.description }}</span>
|
||||
{% if transaction.installment_plan and transaction.installment_id %}
|
||||
<span
|
||||
class="badge text-bg-secondary ms-2">{{ transaction.installment_id }}/{{ transaction.installment_plan.installment_total_number }}</span>
|
||||
{% endif %}
|
||||
{% if transaction.recurring_transaction %}
|
||||
<span class="text-primary tw-text-xs ms-2"><i class="fa-solid fa-arrows-rotate fa-fw"></i></span>
|
||||
{% endif %}
|
||||
{% endspaceless %}
|
||||
</div>
|
||||
<div class="tw-text-gray-400 tw-text-sm">
|
||||
{# Entities #}
|
||||
{% with transaction.entities.all as entities %}
|
||||
{% if 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">{{ entities|join:", " }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{# 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>
|
||||
{% 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>
|
||||
{% endif %}
|
||||
{# Tags#}
|
||||
{% with transaction.tags.all as tags %}
|
||||
{% if 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">{{ tags|join:", " }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
{# Description#}
|
||||
<div class="mb-2 mb-lg-1 text-white tw-text-base">
|
||||
{% spaceless %}
|
||||
<span>{{ transaction.description }}</span>
|
||||
{% if transaction.installment_plan and transaction.installment_id %}
|
||||
<span class="badge text-bg-secondary ms-2">{{ transaction.installment_id }}/{{ transaction.installment_plan.installment_total_number }}</span>
|
||||
{% endif %}
|
||||
{% if transaction.recurring_transaction %}
|
||||
<span class="text-primary tw-text-xs ms-2"><i class="fa-solid fa-arrows-rotate fa-fw"></i></span>
|
||||
{% endif %}
|
||||
{% endspaceless %}
|
||||
</div>
|
||||
<div class="tw-text-gray-400 tw-text-sm">
|
||||
{# Entities #}
|
||||
{% with transaction.entities.all as entities %}
|
||||
{% if 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">{{ entities|join:", " }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{# 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>
|
||||
{% 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>
|
||||
{% endif %}
|
||||
{# Tags#}
|
||||
{% with transaction.tags.all as tags %}
|
||||
{% if 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">{{ tags|join:", " }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-12 text-lg-end align-self-end">
|
||||
<div class="col-lg-3 col-12 text-lg-end align-self-end">
|
||||
<div class="main-amount mb-2 mb-lg-0">
|
||||
<c-amount.display
|
||||
<c-amount.display
|
||||
:amount="transaction.amount"
|
||||
:prefix="transaction.account.currency.prefix"
|
||||
:suffix="transaction.account.currency.suffix"
|
||||
@@ -87,53 +99,77 @@
|
||||
</div>
|
||||
{# Exchange Rate#}
|
||||
{% with exchanged=transaction.exchanged_amount %}
|
||||
{% if exchanged %}
|
||||
<div class="exchanged-amount mb-2 mb-lg-0">
|
||||
<c-amount.display
|
||||
:amount="exchanged.amount"
|
||||
:prefix="exchanged.prefix"
|
||||
:suffix="exchanged.suffix"
|
||||
:decimal_places="exchanged.decimal_places"
|
||||
color="grey"></c-amount.display>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if exchanged %}
|
||||
<div class="exchanged-amount mb-2 mb-lg-0">
|
||||
<c-amount.display
|
||||
:amount="exchanged.amount"
|
||||
:prefix="exchanged.prefix"
|
||||
:suffix="exchanged.suffix"
|
||||
:decimal_places="exchanged.decimal_places"
|
||||
color="grey"></c-amount.display>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
<div>{% if transaction.account.group %}{{ transaction.account.group.name }} • {% endif %}{{ transaction.account.name }}</div>
|
||||
</div>
|
||||
<div>
|
||||
{# 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 d-flex flex-row card">
|
||||
<div class="card-body p-1 shadow-lg">
|
||||
<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">
|
||||
<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 "Duplicate" %}"
|
||||
hx-get="{% url 'transaction_clone' transaction_id=transaction.id %}"
|
||||
_="on click if event.ctrlKey set @hx-get to `{% url 'transaction_clone' transaction_id=transaction.id %}?edit=true` then call htmx.process(me) end then trigger ready"
|
||||
hx-trigger="ready" >
|
||||
<i class="fa-solid fa-clone 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>
|
||||
<div>
|
||||
{% if transaction.account.group %}{{ transaction.account.group.name }} • {% endif %}{{ transaction.account.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{# 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 d-flex flex-row card">
|
||||
<div class="card-body p-1 shadow-lg">
|
||||
{% 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">
|
||||
<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 "Duplicate" %}"
|
||||
hx-get="{% url 'transaction_clone' transaction_id=transaction.id %}"
|
||||
_="on click if event.ctrlKey set @hx-get to `{% url 'transaction_clone' transaction_id=transaction.id %}?edit=true` then call htmx.process(me) end then trigger ready"
|
||||
hx-trigger="ready">
|
||||
<i class="fa-solid fa-clone 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>
|
||||
{% 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>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
184
app/templates/cotton/ui/account_card.html
Normal file
184
app/templates/cotton/ui/account_card.html
Normal file
@@ -0,0 +1,184 @@
|
||||
{% load tools %}
|
||||
{% load i18n %}
|
||||
<div class="col card shadow">
|
||||
<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>
|
||||
<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>
|
||||
<div class="dotted-line flex-grow-1"></div>
|
||||
<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>
|
||||
</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.currency.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>
|
||||
<div class="text-end font-monospace tw-text-red-400">
|
||||
<c-amount.display
|
||||
:amount="account.expense_projected"
|
||||
:prefix="account.currency.prefix"
|
||||
:suffix="account.currency.suffix"
|
||||
:decimal_places="account.currency.decimal_places"></c-amount.display>
|
||||
</div>
|
||||
</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>
|
||||
<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>
|
||||
</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>
|
||||
<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>
|
||||
</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>
|
||||
</div>
|
||||
<div class="dotted-line flex-grow-1"></div>
|
||||
<div class="text-end font-monospace">
|
||||
<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>
|
||||
</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">
|
||||
<c-ui.percentage-distribution :percentage="p"></c-ui.percentage-distribution>
|
||||
</div>
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
182
app/templates/cotton/ui/currency_card.html
Normal file
182
app/templates/cotton/ui/currency_card.html
Normal file
@@ -0,0 +1,182 @@
|
||||
{% load tools %}
|
||||
{% load i18n %}
|
||||
<div class="col card shadow">
|
||||
<div class="card-body">
|
||||
<div class="tw-text-sm mb-2">
|
||||
<span class="badge text-bg-primary">{{ currency.currency.code }}</span>
|
||||
</div>
|
||||
<h5 class="card-title">
|
||||
{{ 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>
|
||||
<div class="dotted-line flex-grow-1"></div>
|
||||
<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>
|
||||
</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.currency.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>
|
||||
<div class="text-end font-monospace tw-text-red-400">
|
||||
<c-amount.display
|
||||
:amount="currency.expense_projected"
|
||||
:prefix="currency.currency.prefix"
|
||||
:suffix="currency.currency.suffix"
|
||||
:decimal_places="currency.currency.decimal_places"></c-amount.display>
|
||||
</div>
|
||||
</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>
|
||||
<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>
|
||||
</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>
|
||||
<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>
|
||||
</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>
|
||||
</div>
|
||||
<div class="dotted-line flex-grow-1"></div>
|
||||
<div class="text-end font-monospace">
|
||||
<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>
|
||||
</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">
|
||||
<c-ui.percentage-distribution :percentage="p"></c-ui.percentage-distribution>
|
||||
</div>
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
243
app/templates/cotton/ui/deleted_transactions_action_bar.html
Normal file
243
app/templates/cotton/ui/deleted_transactions_action_bar.html
Normal file
@@ -0,0 +1,243 @@
|
||||
{% 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"
|
||||
_="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 remove .slide-in-bottom-reverse
|
||||
end
|
||||
else
|
||||
if #actions-bar
|
||||
remove .tw-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">
|
||||
{% spaceless %}
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-secondary btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown"
|
||||
aria-expanded="false">
|
||||
<i class="fa-regular fa-square-check fa-fw"></i>
|
||||
</button>
|
||||
<ul class="dropdown-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>
|
||||
</li>
|
||||
<li>
|
||||
<div class="dropdown-item px-3 tw-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>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="vr tw-align-middle"></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' %}">
|
||||
<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-bypass-on-ctrl="true"
|
||||
data-title="{% translate "Are you sure?" %}"
|
||||
data-text="{% translate "You won't be able to revert this!" %}"
|
||||
data-confirm-text="{% translate "Yes, delete them!" %}"
|
||||
_="install prompt_swal">
|
||||
<i class="fa-solid fa-trash text-danger"></i>
|
||||
</button>
|
||||
<div class="vr tw-align-middle"></div>
|
||||
<div class="btn-group"
|
||||
_="on selected_transactions_updated from #actions-bar
|
||||
set realTotal to math.bignumber(0)
|
||||
set flatTotal to math.bignumber(0)
|
||||
set transactions to <.transaction:has(input[name='transactions']:checked)/>
|
||||
set flatAmountValues to []
|
||||
set realAmountValues to []
|
||||
|
||||
for transaction in transactions
|
||||
set amt to first <.main-amount .amount/> in transaction
|
||||
set amountValue to parseFloat(amt.getAttribute('data-amount'))
|
||||
append amountValue to flatAmountValues
|
||||
|
||||
if not isNaN(amountValue)
|
||||
set flatTotal to math.chain(flatTotal).add(amountValue)
|
||||
|
||||
if transaction match .income
|
||||
append amountValue to realAmountValues
|
||||
set realTotal to math.chain(realTotal).add(amountValue)
|
||||
else
|
||||
append -amountValue to realAmountValues
|
||||
set realTotal to math.chain(realTotal).subtract(amountValue)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
set mean to flatTotal.divide(flatAmountValues.length).done().toNumber()
|
||||
set realTotal to realTotal.done().toNumber()
|
||||
set flatTotal to flatTotal.done().toNumber()
|
||||
|
||||
put realTotal.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40}) into #real-total-front's innerText
|
||||
put realTotal.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40}) into #calc-menu-real-total's innerText
|
||||
put flatTotal.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40}) into #calc-menu-flat-total's innerText
|
||||
put Math.max.apply(Math, realAmountValues).toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40}) into #calc-menu-max's innerText
|
||||
put Math.min.apply(Math, realAmountValues).toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40}) into #calc-menu-min's innerText
|
||||
put mean.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40}) into #calc-menu-mean's innerText
|
||||
put flatAmountValues.length.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40}) into #calc-menu-count's innerText
|
||||
end">
|
||||
<button class="btn btn-secondary btn-sm" _="on click
|
||||
set original_value to #real-total-front's innerText
|
||||
writeText(original_value) on navigator.clipboard
|
||||
put '{% translate "copied!" %}' into #real-total-front's innerText
|
||||
wait 1s
|
||||
put original_value into #real-total-front's innerText
|
||||
end">
|
||||
<i class="fa-solid fa-plus fa-fw me-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>
|
||||
</button>
|
||||
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<div class="dropdown-item-text p-0">
|
||||
<div>
|
||||
<div class="text-body-secondary tw-text-xs tw-font-medium px-3">
|
||||
{% trans "Flat Total" %}
|
||||
</div>
|
||||
<div class="dropdown-item px-3 tw-cursor-pointer"
|
||||
id="calc-menu-flat-total"
|
||||
_="on click
|
||||
set original_value to my innerText
|
||||
writeText(my innerText) on navigator.clipboard
|
||||
put '{% translate "copied!" %}' into me
|
||||
wait 1s
|
||||
put original_value into me
|
||||
end">
|
||||
0
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="dropdown-item-text p-0">
|
||||
<div>
|
||||
<div class="text-body-secondary tw-text-xs tw-font-medium px-3">
|
||||
{% trans "Real Total" %}
|
||||
</div>
|
||||
<div class="dropdown-item px-3 tw-cursor-pointer"
|
||||
id="calc-menu-real-total"
|
||||
_="on click
|
||||
set original_value to my innerText
|
||||
writeText(my innerText) on navigator.clipboard
|
||||
put '{% translate "copied!" %}' into me
|
||||
wait 1s
|
||||
put original_value into me
|
||||
end">
|
||||
0
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="dropdown-item-text p-0">
|
||||
<div>
|
||||
<div class="text-body-secondary tw-text-xs tw-font-medium px-3">
|
||||
{% trans "Mean" %}
|
||||
</div>
|
||||
<div class="dropdown-item px-3 tw-cursor-pointer"
|
||||
id="calc-menu-mean"
|
||||
_="on click
|
||||
set original_value to my innerText
|
||||
writeText(my innerText) on navigator.clipboard
|
||||
put '{% translate "copied!" %}' into me
|
||||
wait 1s
|
||||
put original_value into me
|
||||
end">
|
||||
0
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="dropdown-item-text p-0">
|
||||
<div>
|
||||
<div class="text-body-secondary tw-text-xs tw-font-medium px-3">
|
||||
{% trans "Max" %}
|
||||
</div>
|
||||
<div class="dropdown-item px-3 tw-cursor-pointer"
|
||||
id="calc-menu-max"
|
||||
_="on click
|
||||
set original_value to my innerText
|
||||
writeText(my innerText) on navigator.clipboard
|
||||
put '{% translate "copied!" %}' into me
|
||||
wait 1s
|
||||
put original_value into me
|
||||
end">
|
||||
0
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="dropdown-item-text p-0">
|
||||
<div>
|
||||
<div class="text-body-secondary tw-text-xs tw-font-medium px-3">
|
||||
{% trans "Min" %}
|
||||
</div>
|
||||
<div class="dropdown-item px-3 tw-cursor-pointer"
|
||||
id="calc-menu-min"
|
||||
_="on click
|
||||
set original_value to my innerText
|
||||
writeText(my innerText) on navigator.clipboard
|
||||
put '{% translate "copied!" %}' into me
|
||||
wait 1s
|
||||
put original_value into me
|
||||
end">
|
||||
0
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="dropdown-item-text p-0">
|
||||
<div>
|
||||
<div class="text-body-secondary tw-text-xs tw-font-medium px-3">
|
||||
{% trans "Count" %}
|
||||
</div>
|
||||
<div class="dropdown-item px-3 tw-cursor-pointer"
|
||||
id="calc-menu-count"
|
||||
_="on click
|
||||
set original_value to my innerText
|
||||
writeText(my innerText) on navigator.clipboard
|
||||
put '{% translate "copied!" %}' into me
|
||||
wait 1s
|
||||
put original_value into me
|
||||
end">
|
||||
0
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endspaceless %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -3,7 +3,7 @@
|
||||
<i class="{{ icon }}"></i>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h5 class="tw-text-{{ color }}-400 fw-bold" {{ attrs }}>{{ title }}{% if help_text %}{% include 'includes/help_icon.html' with content=help_text %}{% endif %}</h5>
|
||||
<h5 class="tw-text-{{ color }}-400 fw-bold tw-mr-[50px]" {{ attrs }}>{{ title }}{% if help_text %}{% include 'includes/help_icon.html' with content=help_text %}{% endif %}</h5>
|
||||
{{ slot }}
|
||||
</div>
|
||||
</div>
|
||||
50
app/templates/cotton/ui/quick_transactions_buttons.html
Normal file
50
app/templates/cotton/ui/quick_transactions_buttons.html
Normal file
@@ -0,0 +1,50 @@
|
||||
{% 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"
|
||||
hx-get="{% url 'transaction_add' %}"
|
||||
hx-target="#generic-offcanvas"
|
||||
hx-trigger="click, add_income from:window"
|
||||
hx-vals='{"year": {{ year }}, {% if month %}"month": {{ month }},{% endif %} "type": "IN"}'>
|
||||
<i class="fa-solid fa-arrow-right-to-bracket me-2"></i>
|
||||
{% translate "Income" %}
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-danger"
|
||||
hx-get="{% url 'transaction_add' %}"
|
||||
hx-target="#generic-offcanvas"
|
||||
hx-trigger="click, add_expense from:window"
|
||||
hx-vals='{"year": {{ year }}, {% if month %}"month": {{ month }},{% endif %} "type": "EX"}'>
|
||||
<i class="fa-solid fa-arrow-right-from-bracket me-2"></i>
|
||||
{% translate "Expense" %}
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-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"
|
||||
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"
|
||||
hx-get="{% url 'transactions_transfer' %}"
|
||||
hx-target="#generic-offcanvas"
|
||||
hx-trigger="click, add_transfer from:window"
|
||||
hx-vals='{"year": {{ year }} {% if month %}, "month": {{ month }}{% endif %}}'>
|
||||
<i class="fa-solid fa-money-bill-transfer me-2"></i>
|
||||
{% translate "Transfer" %}
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-info"
|
||||
hx-get="{% url 'account_reconciliation' %}"
|
||||
hx-trigger="click, balance from:window"
|
||||
hx-target="#generic-offcanvas">
|
||||
<i class="fa-solid fa-scale-balanced me-2"></i>
|
||||
{% translate "Balance" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,16 +1,18 @@
|
||||
{% 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"
|
||||
_="on change from #transactions-list or htmx:afterSettle from window
|
||||
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 remove .slide-in-bottom-reverse
|
||||
end
|
||||
else
|
||||
if #actions-bar
|
||||
remove .tw-hidden from #actions-bar
|
||||
then trigger selected_transactions_updated
|
||||
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 remove .slide-in-bottom-reverse
|
||||
end
|
||||
else
|
||||
if #actions-bar
|
||||
remove .tw-hidden from #actions-bar
|
||||
then trigger selected_transactions_updated
|
||||
end
|
||||
end
|
||||
end
|
||||
end">
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
{% load settings %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load active_link %}
|
||||
@@ -56,7 +57,13 @@
|
||||
<li><a class="dropdown-item {% active_link views='transactions_all_index' %}"
|
||||
href="{% url 'transactions_all_index' %}">{% translate 'All' %}</a></li>
|
||||
<li>
|
||||
<hr class="dropdown-divider">
|
||||
{% settings "ENABLE_SOFT_DELETE" as enable_soft_delete %}
|
||||
{% if enable_soft_delete %}
|
||||
<li><a class="dropdown-item {% active_link views='transactions_trash_index' %}"
|
||||
href="{% url 'transactions_trash_index' %}">{% translate 'Trash Can' %}</a></li>
|
||||
<li>
|
||||
{% endif %}
|
||||
<hr class="dropdown-divider">
|
||||
</li>
|
||||
<li><a class="dropdown-item {% active_link views='installment_plans_index' %}"
|
||||
href="{% url 'installment_plans_index' %}">{% translate 'Installment Plans' %}</a></li>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
{% load settings %}
|
||||
{% load i18n %}
|
||||
<div class="dropdown">
|
||||
<a class="tw-text-2xl" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
@@ -40,5 +41,7 @@
|
||||
</li>
|
||||
<li><a class="dropdown-item" href="{% url 'logout' %}"><i class="fa-solid fa-door-open me-2 fa-fw"></i
|
||||
>{% translate 'Logout' %}</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="https://github.com/eitchtee/WYGIWYH/releases" target="_blank" rel="nofollow">v. {% settings "APP_VERSION" %}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
{% load tools %}
|
||||
{% load i18n %}
|
||||
{% load currency_display %}
|
||||
|
||||
<div class="row row-cols-1 g-4 mt-1 mb-3">
|
||||
{% for account_id, account in account_data.items %}
|
||||
<div class="col">
|
||||
<c-ui.account_card :account="account" :account_id="account_id"
|
||||
:percentages="account_percentages"></c-ui.account_card>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="col">
|
||||
<c-msg.empty
|
||||
title="{% translate "No information to display" %}"></c-msg.empty>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,16 @@
|
||||
{% load tools %}
|
||||
{% load i18n %}
|
||||
{% load currency_display %}
|
||||
<div class="row row-cols-1 g-4 mt-1 mb-3">
|
||||
{% for currency_id, currency in currency_data.items %}
|
||||
<div class="col">
|
||||
<c-ui.currency_card :currency="currency" :currency_id="currency_id"
|
||||
:percentages="currency_percentages"></c-ui.currency_card>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="col">
|
||||
<c-msg.empty
|
||||
title="{% translate "No information to display" %}"></c-msg.empty>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -1,6 +1,6 @@
|
||||
{% load i18n %}
|
||||
{% load currency_display %}
|
||||
<div class="row row-cols-1 g-4 mb-3">
|
||||
<div class="row row-cols-1 g-4 mt-1 mb-3">
|
||||
{# Daily Spending#}
|
||||
<div class="col">
|
||||
<c-ui.info-card color="yellow" icon="fa-solid fa-calendar-day" title="{% trans 'Daily Spending Allowance' %}" help_text={% trans "This is the final total divided by the remaining days in the month" %}>
|
||||
@@ -252,6 +252,7 @@
|
||||
</div>
|
||||
</c-ui.info-card>
|
||||
</div>
|
||||
{% if percentages %}
|
||||
<div class="col">
|
||||
<c-ui.info-card color="yellow" icon="fa-solid fa-percent" title="{% trans 'Distribution' %}">
|
||||
{% for p in percentages.values %}
|
||||
@@ -260,4 +261,5 @@
|
||||
{% endfor %}
|
||||
</c-ui.info-card>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -13,132 +13,172 @@
|
||||
{% 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">
|
||||
{# 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 'monthly_overview' 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"
|
||||
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": "monthly_overview", "field": "reference_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 'monthly_overview' 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">
|
||||
<div class="d-grid gap-2 d-xl-flex justify-content-xl-end">
|
||||
<button class="btn btn-sm btn-outline-success"
|
||||
hx-get="{% url 'transaction_add' %}"
|
||||
hx-target="#generic-offcanvas"
|
||||
hx-trigger="click, add_income from:window"
|
||||
hx-vals='{"year": {{ year }}, "month": {{ month }}, "type": "IN"}'>
|
||||
<i class="fa-solid fa-arrow-right-to-bracket me-2"></i>
|
||||
{% translate "Income" %}
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-danger"
|
||||
hx-get="{% url 'transaction_add' %}"
|
||||
hx-target="#generic-offcanvas"
|
||||
hx-trigger="click, add_expense from:window"
|
||||
hx-vals='{"year": {{ year }}, "month": {{ month }}, "type": "EX"}'>
|
||||
<i class="fa-solid fa-arrow-right-from-bracket me-2"></i>
|
||||
{% translate "Expense" %}
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-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"
|
||||
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"
|
||||
hx-get="{% url 'transactions_transfer' %}"
|
||||
hx-target="#generic-offcanvas"
|
||||
hx-trigger="click, add_transfer from:window"
|
||||
hx-vals='{"year": {{ year }}, "month": {{ month }}}'>
|
||||
<i class="fa-solid fa-money-bill-transfer me-2"></i>
|
||||
{% translate "Transfer" %}
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-info"
|
||||
hx-get="{% url 'account_reconciliation' %}"
|
||||
hx-trigger="click, balance from:window"
|
||||
hx-target="#generic-offcanvas">
|
||||
<i class="fa-solid fa-scale-balanced me-2"></i>
|
||||
{% translate "Balance" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{# Monthly summary#}
|
||||
<div class="row gx-xl-4 gy-3">
|
||||
<div class="col-12 col-xl-4 order-0 order-xl-2">
|
||||
<div id="summary" hx-get="{% url 'monthly_summary' month=month year=year %}" class="show-loading"
|
||||
hx-trigger="load, updated from:window, selective_update from:window">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-xl-8 order-2 order-xl-1">
|
||||
<div class="row mb-1">
|
||||
<div class="col-sm-6 col-12">
|
||||
{# Filter transactions button #}
|
||||
<button type="button" class="btn btn-sm btn-outline-primary dropdown-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-filter" aria-expanded="false" aria-controls="collapse-filter">
|
||||
<i class="fa-solid fa-filter fa-fw me-2"></i>{% translate 'Filter transactions' %}
|
||||
</button>
|
||||
<div class="container px-md-3 py-3 column-gap-5">
|
||||
<div class="row mb-3 gx-xl-4 gy-3 mb-4">
|
||||
{# 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 'monthly_overview' month=previous_month year=previous_year %}"><i
|
||||
class="fa-solid fa-chevron-left"></i></a>
|
||||
</div>
|
||||
{# Ordering button#}
|
||||
<div class="col-sm-6 col-12 tw-content-center my-3 my-sm-0">
|
||||
<div class="text-sm-end" _="on change trigger updated on window">
|
||||
<label for="order">{% translate "Order by" %}</label>
|
||||
<select class="tw-border-0 focus-visible:tw-outline-0 w-full pe-2 tw-leading-normal text-bg-tertiary tw-font-medium rounded" name="order" id="order">
|
||||
<option value="default" {% if order == 'default' %}selected{% endif %}>{% translate 'Default' %}</option>
|
||||
<option value="older" {% if order == 'older' %}selected{% endif %}>{% translate 'Oldest first' %}</option>
|
||||
<option value="newer" {% if order == 'newer' %}selected{% endif %}>{% translate 'Newest first' %}</option>
|
||||
</select>
|
||||
<div class="tw-text-3xl fw-bold font-monospace tw-w-full text-center"
|
||||
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": "monthly_overview", "field": "reference_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 'monthly_overview' 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>
|
||||
</div>
|
||||
</div>
|
||||
{# Monthly summary#}
|
||||
<div class="row gx-xl-4 gy-3">
|
||||
<div class="col-12 col-xl-4 order-0 order-xl-2">
|
||||
<ul class="nav nav-tabs" id="monthly-summary" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link {% if summary_tab == 'summary' %}active{% endif %}"
|
||||
id="summary-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#summary-tab-pane"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="summary-tab-pane"
|
||||
_="on click fetch {% url 'monthly_summary_select' selected='summary' %}"
|
||||
aria-selected="{% if summary_tab == 'summary' or not summary_tab %}true{% else %}false{% endif %}">
|
||||
{% trans 'Summary' %}
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link {% if summary_tab == 'currency' %}active{% endif %}"
|
||||
id="currency-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#currency-tab-pane"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="currency-tab-pane"
|
||||
_="on click fetch {% url 'monthly_summary_select' selected='currency' %}"
|
||||
aria-selected="{% if summary_tab == 'currency' %}true{% else %}false{% endif %}">
|
||||
{% trans 'Currencies' %}
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link {% if summary_tab == 'account' %}active{% endif %}"
|
||||
id="account-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#account-tab-pane"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="account-tab-pane"
|
||||
_="on click fetch {% url 'monthly_summary_select' selected='account' %}"
|
||||
aria-selected="{% if summary_tab == 'account' %}true{% else %}false{% endif %}">
|
||||
{% trans 'Accounts' %}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content" id="monthly-summary-content">
|
||||
<div class="tab-pane fade {% if summary_tab == 'summary' %}show active{% endif %}"
|
||||
id="summary-tab-pane"
|
||||
role="tabpanel"
|
||||
aria-labelledby="summary-tab"
|
||||
tabindex="0">
|
||||
<div id="summary"
|
||||
hx-get="{% url 'monthly_summary' month=month year=year %}"
|
||||
class="show-loading"
|
||||
hx-trigger="load, updated from:window, selective_update from:window">
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade {% if summary_tab == 'currency' %}show active{% endif %}"
|
||||
id="currency-tab-pane"
|
||||
role="tabpanel"
|
||||
aria-labelledby="currency-tab"
|
||||
tabindex="0">
|
||||
<div id="currency-summary"
|
||||
hx-get="{% url 'monthly_currency_summary' month=month year=year %}"
|
||||
class="show-loading"
|
||||
hx-trigger="load, updated from:window, selective_update from:window">
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade {% if summary_tab == 'account' %}show active{% endif %}"
|
||||
id="account-tab-pane"
|
||||
role="tabpanel"
|
||||
aria-labelledby="account-tab"
|
||||
tabindex="0">
|
||||
<div id="account-summary"
|
||||
hx-get="{% url 'monthly_account_summary' month=month year=year %}"
|
||||
class="show-loading"
|
||||
hx-trigger="load, updated from:window, selective_update from:window">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{# Filter transactions form#}
|
||||
<div class="collapse" id="collapse-filter">
|
||||
<div class="card card-body">
|
||||
<form _="on change or submit or search trigger updated on window end
|
||||
<div class="col-12 col-xl-8 order-2 order-xl-1">
|
||||
<div class="row mb-1">
|
||||
<div class="col-sm-6 col-12">
|
||||
{# Filter transactions button #}
|
||||
<button type="button" class="btn btn-sm btn-outline-primary dropdown-toggle" type="button"
|
||||
data-bs-toggle="collapse" data-bs-target="#collapse-filter" aria-expanded="false"
|
||||
aria-controls="collapse-filter">
|
||||
<i class="fa-solid fa-filter fa-fw me-2"></i>{% translate 'Filter transactions' %}
|
||||
</button>
|
||||
</div>
|
||||
{# Ordering button#}
|
||||
<div class="col-sm-6 col-12 tw-content-center my-3 my-sm-0">
|
||||
<div class="text-sm-end" _="on change trigger updated on window">
|
||||
<label for="order">{% translate "Order by" %}</label>
|
||||
<select
|
||||
class="tw-border-0 focus-visible:tw-outline-0 w-full pe-2 tw-leading-normal text-bg-tertiary tw-font-medium rounded"
|
||||
name="order" id="order">
|
||||
<option value="default"
|
||||
{% if order == 'default' %}selected{% endif %}>{% translate 'Default' %}</option>
|
||||
<option value="older"
|
||||
{% if order == 'older' %}selected{% endif %}>{% translate 'Oldest first' %}</option>
|
||||
<option value="newer"
|
||||
{% if order == 'newer' %}selected{% endif %}>{% translate 'Newest first' %}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{# Filter transactions form#}
|
||||
<div class="collapse" id="collapse-filter">
|
||||
<div class="card card-body">
|
||||
<form _="on change or submit or search trigger updated on window end
|
||||
install init_tom_select
|
||||
install init_datepicker"
|
||||
id="filter">
|
||||
{% crispy filter.form %}
|
||||
</form>
|
||||
<button class="btn btn-outline-danger btn-sm" _="on click call #filter.reset() then trigger change on #filter">{% translate 'Clear' %}</button>
|
||||
id="filter">
|
||||
{% crispy filter.form %}
|
||||
</form>
|
||||
<button class="btn btn-outline-danger btn-sm"
|
||||
_="on click call #filter.reset() then trigger change on #filter">{% translate 'Clear' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
{# Transactions list#}
|
||||
<div id="transactions"
|
||||
class="show-loading"
|
||||
hx-get="{% url 'monthly_transactions_list' month=month year=year %}"
|
||||
hx-trigger="load, updated from:window" hx-include="#filter, #order">
|
||||
</div>
|
||||
</div>
|
||||
{# Transactions list#}
|
||||
<div id="transactions"
|
||||
class="show-loading"
|
||||
hx-get="{% url 'monthly_transactions_list' month=month year=year %}"
|
||||
hx-trigger="load, updated from:window" hx-include="#filter, #order">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -46,6 +46,22 @@
|
||||
color="grey"></c-amount.display>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if currency.consolidated %}
|
||||
<div class="d-flex align-items-baseline w-100">
|
||||
<div class="account-name text-start font-monospace tw-text-gray-300">
|
||||
<span class="hierarchy-line-icon"></span>{% trans 'Consolidated' %}</div>
|
||||
<div class="dotted-line flex-grow-1"></div>
|
||||
<div class="">
|
||||
<c-amount.display
|
||||
:amount="currency.consolidated.total_final"
|
||||
:prefix="currency.consolidated.currency.prefix"
|
||||
:suffix="currency.consolidated.currency.suffix"
|
||||
:decimal_places="currency.consolidated.currency.decimal_places"
|
||||
color="{% if currency.total_final > 0 %}green{% elif currency.total_final < 0 %}red{% endif %}"
|
||||
text-end></c-amount.display>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</c-ui.info-card>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
{% load tools %}
|
||||
{% load i18n %}
|
||||
{% load currency_display %}
|
||||
|
||||
<div class="row row-cols-1 g-4 mt-1 mb-3">
|
||||
{% for account_id, account in account_data.items %}
|
||||
<div class="col">
|
||||
<c-ui.account_card :account="account" :account_id="account_id"
|
||||
:percentages="account_percentages"></c-ui.account_card>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="col">
|
||||
<c-msg.empty
|
||||
title="{% translate "No information to display" %}"></c-msg.empty>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,16 @@
|
||||
{% load tools %}
|
||||
{% load i18n %}
|
||||
{% load currency_display %}
|
||||
<div class="row row-cols-1 g-4 mt-1 mb-3">
|
||||
{% for currency_id, currency in currency_data.items %}
|
||||
<div class="col">
|
||||
<c-ui.currency_card :currency="currency" :currency_id="currency_id"
|
||||
:percentages="currency_percentages"></c-ui.currency_card>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="col">
|
||||
<c-msg.empty
|
||||
title="{% translate "No information to display" %}"></c-msg.empty>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -1,431 +1,47 @@
|
||||
{% load tools %}
|
||||
{% load i18n %}
|
||||
{% load currency_display %}
|
||||
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
||||
<ul class="nav nav-tabs" id="all-trasactions-tab" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="currency-tab" data-bs-toggle="tab" data-bs-target="#currency-tab-pane" type="button" role="tab" aria-controls="currency-tab-pane" aria-selected="true">{% trans 'Currencies' %}</button>
|
||||
<button class="nav-link active" id="currency-tab" data-bs-toggle="tab" data-bs-target="#currency-tab-pane"
|
||||
type="button" role="tab" aria-controls="currency-tab-pane"
|
||||
aria-selected="true">{% trans 'Currencies' %}</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="account-tab" data-bs-toggle="tab" data-bs-target="#account-tab-pane" type="button" role="tab" aria-controls="account-tab-pane" aria-selected="false">{% trans 'Accounts' %}</button>
|
||||
<button class="nav-link" id="account-tab" data-bs-toggle="tab" data-bs-target="#account-tab-pane" type="button"
|
||||
role="tab" aria-controls="account-tab-pane" aria-selected="false">{% trans 'Accounts' %}</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content" id="myTabContent">
|
||||
<div class="tab-pane fade show active" id="currency-tab-pane" role="tabpanel" aria-labelledby="currency-tab" tabindex="0">
|
||||
<div class="tab-content" id="all-transactions-content">
|
||||
<div class="tab-pane fade show active" id="currency-tab-pane" role="tabpanel" aria-labelledby="currency-tab"
|
||||
tabindex="0">
|
||||
<div class="row row-cols-1 g-4 mt-2">
|
||||
{# Income#}
|
||||
<div class="col">
|
||||
<c-ui.info-card color="green" icon="fa-solid fa-arrow-right-to-bracket" title="{% trans 'Income' %}">
|
||||
<div class="d-flex justify-content-between mt-3">
|
||||
<div class="text-end font-monospace">
|
||||
<div class="tw-text-gray-400">{% translate 'current' %}</div>
|
||||
</div>
|
||||
<div class="text-end font-monospace">
|
||||
{% for currency in income_current.values %}
|
||||
<div>
|
||||
<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>
|
||||
</div>
|
||||
{% if currency.exchanged %}
|
||||
<div>
|
||||
<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>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% empty %}
|
||||
<div>-</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<hr class="my-1">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div class="text-end font-monospace">
|
||||
<div class="tw-text-gray-400">{% translate 'projected' %}</div>
|
||||
</div>
|
||||
<div class="text-end font-monospace">
|
||||
{% for currency in income_projected.values %}
|
||||
<div>
|
||||
<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>
|
||||
</div>
|
||||
{% if currency.exchanged %}
|
||||
<div>
|
||||
<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>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% empty %}
|
||||
<div>-</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</c-ui.info-card>
|
||||
</div>
|
||||
{# Expenses#}
|
||||
<div class="col">
|
||||
<c-ui.info-card color="red" icon="fa-solid fa-arrow-right-from-bracket" title="{% trans 'Expenses' %}">
|
||||
<div class="d-flex justify-content-between mt-3">
|
||||
<div class="text-end font-monospace">
|
||||
<div class="tw-text-gray-400">{% translate 'current' %}</div>
|
||||
</div>
|
||||
<div class="text-end font-monospace">
|
||||
{% for currency in expense_current.values %}
|
||||
<div>
|
||||
<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>
|
||||
</div>
|
||||
{% if currency.exchanged %}
|
||||
<div>
|
||||
<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>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% empty %}
|
||||
<div>-</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<hr class="my-1">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div class="text-end font-monospace">
|
||||
<div class="tw-text-gray-400">{% translate 'projected' %}</div>
|
||||
</div>
|
||||
<div class="text-end font-monospace">
|
||||
{% for currency in expense_projected.values %}
|
||||
<div>
|
||||
<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>
|
||||
</div>
|
||||
{% if currency.exchanged %}
|
||||
<div>
|
||||
<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>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% empty %}
|
||||
<div>-</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</c-ui.info-card>
|
||||
</div>
|
||||
{# Total#}
|
||||
<div class="col">
|
||||
<c-ui.info-card color="blue" icon="fa-solid fa-scale-balanced" title="{% trans 'Total' %}">
|
||||
<div class="d-flex justify-content-between mt-3">
|
||||
<div class="text-end font-monospace">
|
||||
<div class="tw-text-gray-400">{% translate 'current' %}</div>
|
||||
</div>
|
||||
<div class="text-end font-monospace">
|
||||
{% for currency in total_current.values %}
|
||||
<div>
|
||||
<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>
|
||||
{% if currency.exchanged %}
|
||||
<div>
|
||||
<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>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% empty %}
|
||||
<div>-</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mt-3">
|
||||
<div class="text-end font-monospace">
|
||||
<div class="tw-text-gray-400">{% translate 'projected' %}</div>
|
||||
</div>
|
||||
<div class="text-end font-monospace">
|
||||
{% for currency in total_projected.values %}
|
||||
<div>
|
||||
<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>
|
||||
{% if currency.exchanged %}
|
||||
<div>
|
||||
<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>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% empty %}
|
||||
<div>-</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<hr class="my-1">
|
||||
<div class="d-flex justify-content-end">
|
||||
<div class="text-end font-monospace">
|
||||
{% for currency in total_final.values %}
|
||||
<div>
|
||||
<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>
|
||||
</div>
|
||||
{% if currency.exchanged %}
|
||||
<div>
|
||||
<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>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% empty %}
|
||||
<div>-</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</c-ui.info-card>
|
||||
</div>
|
||||
<div class="col">
|
||||
<c-ui.info-card color="yellow" icon="fa-solid fa-percent" title="{% trans 'Distribution' %}">
|
||||
{% for p in currency_percentages.values %}
|
||||
<p class="tw-text-gray-400 mb-2 {% if not forloop.first %}mt-3{% endif %}">{{ p.currency.name }} ({{ p.currency.code }})</p>
|
||||
<c-ui.percentage-distribution :percentage="p"></c-ui.percentage-distribution>
|
||||
{% endfor %}
|
||||
</c-ui.info-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="account-tab-pane" role="tabpanel" aria-labelledby="account-tab" tabindex="0">
|
||||
<div class="row row-cols-1 g-4 mt-2">
|
||||
<div class="col">
|
||||
{% for account_id, account in account_data.items %}
|
||||
{% if not single %}
|
||||
<div class="tw-text-xl {% if not forloop.first %}mt-4 mb-3{% endif %}">
|
||||
{% if account.account.group %}
|
||||
<span class="badge text-bg-primary me-2">{{ account.account.group }}</span>{% endif %}{{ account.account.name }}
|
||||
</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 income' %}</div>
|
||||
</div>
|
||||
<div class="dotted-line flex-grow-1"></div>
|
||||
<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>
|
||||
{% for currency_id, currency in currency_data.items %}
|
||||
<div class="col">
|
||||
<c-ui.currency_card :currency="currency" :currency_id="currency_id"
|
||||
:percentages="currency_percentages"></c-ui.currency_card>
|
||||
</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.currency.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>
|
||||
<div class="text-end font-monospace tw-text-red-400">
|
||||
<c-amount.display
|
||||
:amount="account.expense_projected"
|
||||
:prefix="account.currency.prefix"
|
||||
:suffix="account.currency.suffix"
|
||||
:decimal_places="account.currency.decimal_places"></c-amount.display>
|
||||
</div>
|
||||
</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>
|
||||
<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>
|
||||
</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>
|
||||
<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>
|
||||
</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>
|
||||
</div>
|
||||
<div class="dotted-line flex-grow-1"></div>
|
||||
<div class="text-end font-monospace">
|
||||
<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>
|
||||
</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=account_percentages|get_dict_item:account_id %}
|
||||
<div class="my-3">
|
||||
<c-ui.percentage-distribution :percentage="p"></c-ui.percentage-distribution>
|
||||
</div>
|
||||
{% endwith %}
|
||||
<hr>
|
||||
{% empty %}
|
||||
<div class="col">
|
||||
<c-msg.empty
|
||||
title="{% translate "No information to display" %}"></c-msg.empty>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="account-tab-pane" role="tabpanel" aria-labelledby="account-tab" tabindex="0">
|
||||
<div class="row row-cols-1 g-4 mt-2">
|
||||
{% for account_id, account in account_data.items %}
|
||||
<div class="col">
|
||||
<c-ui.account_card :account="account" :account_id="account_id"
|
||||
:percentages="account_percentages"></c-ui.account_card>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="col">
|
||||
<c-msg.empty
|
||||
title="{% translate "No information to display" %}"></c-msg.empty>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
11
app/templates/transactions/fragments/trash_list.html
Normal file
11
app/templates/transactions/fragments/trash_list.html
Normal file
@@ -0,0 +1,11 @@
|
||||
{% load i18n %}
|
||||
<div class="trash-list-container" id="transactions-list">
|
||||
{% for transaction in transactions %}
|
||||
<c-transaction.item :transaction="transaction"></c-transaction.item>
|
||||
{% empty %}
|
||||
<c-msg.empty
|
||||
title="{% translate "No deleted transactions to show" %}"></c-msg.empty>
|
||||
{% endfor %}
|
||||
{# Floating bar #}
|
||||
<c-ui.deleted-transactions-action-bar></c-ui.deleted-transactions-action-bar>
|
||||
</div>
|
||||
@@ -45,10 +45,59 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-xl-3 order-1 order-xl-2">
|
||||
<div id="transactions"
|
||||
class="show-loading"
|
||||
hx-get="{% url 'transactions_all_summary' %}"
|
||||
hx-trigger="load, updated from:window, change from:#filter, submit from:#filter, search from:#filter" hx-include="#filter">
|
||||
<ul class="nav nav-tabs" id="all-transactions-summary" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link {% if summary_tab == 'currency' %}active{% endif %}"
|
||||
id="currency-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#currency-tab-pane"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="currency-tab-pane"
|
||||
_="on click fetch {% url 'transaction_all_summary_select' selected='currency' %}"
|
||||
aria-selected="{% if summary_tab == 'currency' %}true{% else %}false{% endif %}">
|
||||
{% trans 'Currencies' %}
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link {% if summary_tab == 'account' %}active{% endif %}"
|
||||
id="account-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#account-tab-pane"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="account-tab-pane"
|
||||
_="on click fetch {% url 'transaction_all_summary_select' selected='account' %}"
|
||||
aria-selected="{% if summary_tab == 'account' %}true{% else %}false{% endif %}">
|
||||
{% trans 'Accounts' %}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content" id="all-transactions-content">
|
||||
<div class="tab-pane fade {% if summary_tab == 'currency' %}show active{% endif %}"
|
||||
id="currency-tab-pane"
|
||||
role="tabpanel"
|
||||
aria-labelledby="currency-tab"
|
||||
tabindex="0">
|
||||
<div id="currency-summary"
|
||||
hx-get="{% url 'transaction_all_currency_summary' %}"
|
||||
class="show-loading"
|
||||
hx-trigger="load, selective_update from:window, updated from:window, change from:#filter, submit from:#filter, search from:#filter"
|
||||
hx-include="#filter">
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade {% if summary_tab == 'account' %}show active{% endif %}"
|
||||
id="account-tab-pane"
|
||||
role="tabpanel"
|
||||
aria-labelledby="account-tab"
|
||||
tabindex="0">
|
||||
<div id="account-summary"
|
||||
hx-get="{% url 'transaction_all_account_summary' %}"
|
||||
class="show-loading"
|
||||
hx-trigger="load, selective_update from:window, updated from:window, change from:#filter, submit from:#filter, search from:#filter"
|
||||
hx-include="#filter">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
14
app/templates/transactions/pages/trash.html
Normal file
14
app/templates/transactions/pages/trash.html
Normal file
@@ -0,0 +1,14 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% translate 'Deleted transactions' %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container px-md-3 py-3 column-gap-5">
|
||||
<div class="tw-text-3xl fw-bold font-monospace tw-w-full mb-3">
|
||||
<div>{% translate 'Deleted transactions' %}</div>
|
||||
</div>
|
||||
|
||||
<div hx-get="{% url 'transactions_trash_list' %}" hx-trigger="load, updated from:window" class="show-loading"></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,188 +1,15 @@
|
||||
{% load tools %}
|
||||
{% load i18n %}
|
||||
<div class="row row-cols-1 g-4 mb-3">
|
||||
{% for account_id, account in totals.items %}
|
||||
<div class="col">
|
||||
{% for account_id, account in totals.items %}
|
||||
{% if not single %}
|
||||
<div class="tw-text-xl {% if not forloop.first %}mt-4 mb-3{% endif %}">
|
||||
{% if account.account.group %}
|
||||
<span class="badge text-bg-primary me-2">{{ account.account.group }}</span>{% endif %}{{ account.account.name }}
|
||||
</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 income' %}</div>
|
||||
</div>
|
||||
<div class="dotted-line flex-grow-1"></div>
|
||||
<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>
|
||||
</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.currency.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>
|
||||
<div class="text-end font-monospace tw-text-red-400">
|
||||
<c-amount.display
|
||||
:amount="account.expense_projected"
|
||||
:prefix="account.currency.prefix"
|
||||
:suffix="account.currency.suffix"
|
||||
:decimal_places="account.currency.decimal_places"></c-amount.display>
|
||||
</div>
|
||||
</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>
|
||||
<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>
|
||||
</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>
|
||||
<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>
|
||||
</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>
|
||||
</div>
|
||||
<div class="dotted-line flex-grow-1"></div>
|
||||
<div class="text-end font-monospace">
|
||||
<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>
|
||||
</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">
|
||||
<c-ui.percentage-distribution :percentage="p"></c-ui.percentage-distribution>
|
||||
</div>
|
||||
{% endwith %}
|
||||
<hr>
|
||||
{% empty %}
|
||||
<c-msg.empty
|
||||
title="{% translate "No information to display" %}"></c-msg.empty>
|
||||
{% endfor %}
|
||||
<c-ui.account_card :account="account" :account_id="account_id"
|
||||
:percentages="percentages"></c-ui.account_card>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="col">
|
||||
<c-msg.empty
|
||||
title="{% translate "No information to display" %}"></c-msg.empty>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
@@ -1,187 +1,15 @@
|
||||
{% load tools %}
|
||||
{% load month_name %}
|
||||
{% load i18n %}
|
||||
<div class="row row-cols-1 g-4 mb-3">
|
||||
{% for currency_id, currency in totals.items %}
|
||||
{% for currency_id, currency in totals.items %}
|
||||
<div class="col">
|
||||
{% if not single %}
|
||||
<div class="tw-text-xl {% if not forloop.first %}mt-4 mb-3{% endif %}">
|
||||
{{ currency.currency.name }} ({{ currency.currency.code }})
|
||||
</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 income' %}</div>
|
||||
</div>
|
||||
<div class="dotted-line flex-grow-1"></div>
|
||||
<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>
|
||||
</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.currency.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>
|
||||
<div class="text-end font-monospace tw-text-red-400">
|
||||
<c-amount.display
|
||||
:amount="currency.expense_projected"
|
||||
:prefix="currency.currency.prefix"
|
||||
:suffix="currency.currency.suffix"
|
||||
:decimal_places="currency.currency.decimal_places"></c-amount.display>
|
||||
</div>
|
||||
</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>
|
||||
<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>
|
||||
</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>
|
||||
<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>
|
||||
</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>
|
||||
</div>
|
||||
<div class="dotted-line flex-grow-1"></div>
|
||||
<div class="text-end font-monospace">
|
||||
<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>
|
||||
</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">
|
||||
<c-ui.percentage-distribution :percentage="p"></c-ui.percentage-distribution>
|
||||
</div>
|
||||
{% endwith %}
|
||||
{% empty %}
|
||||
<c-msg.empty
|
||||
title="{% translate "No information to display" %}"></c-msg.empty>
|
||||
<c-ui.currency_card :currency="currency" :currency_id="currency_id"
|
||||
:percentages="percentages"></c-ui.currency_card>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="col">
|
||||
<c-msg.empty
|
||||
title="{% translate "No information to display" %}"></c-msg.empty>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
@@ -39,53 +39,10 @@
|
||||
</div>
|
||||
{# Action buttons#}
|
||||
<div class="col-12 col-xl-10">
|
||||
<div class="d-grid gap-2 d-xl-flex justify-content-xl-end">
|
||||
<button class="btn btn-sm btn-outline-success"
|
||||
hx-get="{% url 'transaction_add' %}"
|
||||
hx-target="#generic-offcanvas"
|
||||
hx-trigger="click, add_income from:window"
|
||||
hx-vals='{"year": {{ year }}, "type": "IN"}'>
|
||||
<i class="fa-solid fa-arrow-right-to-bracket me-2"></i>
|
||||
{% translate "Income" %}
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-danger"
|
||||
hx-get="{% url 'transaction_add' %}"
|
||||
hx-target="#generic-offcanvas"
|
||||
hx-trigger="click, add_expense from:window"
|
||||
hx-vals='{"year": {{ year }}, "type": "EX"}'>
|
||||
<i class="fa-solid fa-arrow-right-from-bracket me-2"></i>
|
||||
{% translate "Expense" %}
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-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"
|
||||
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"
|
||||
hx-get="{% url 'transactions_transfer' %}"
|
||||
hx-target="#generic-offcanvas"
|
||||
hx-trigger="click, add_transfer from:window"
|
||||
hx-vals='{"year": {{ year }}}'>
|
||||
<i class="fa-solid fa-money-bill-transfer me-2"></i>
|
||||
{% translate "Transfer" %}
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-info"
|
||||
hx-get="{% url 'account_reconciliation' %}"
|
||||
hx-trigger="click, balance from:window"
|
||||
hx-target="#generic-offcanvas">
|
||||
<i class="fa-solid fa-scale-balanced me-2"></i>
|
||||
{% translate "Balance" %}
|
||||
</button>
|
||||
</div>
|
||||
<c-ui.quick-transactions-buttons
|
||||
:year="year"
|
||||
:month="month"
|
||||
></c-ui.quick-transactions-buttons>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
||||
@@ -41,53 +41,10 @@
|
||||
</div>
|
||||
{# Action buttons#}
|
||||
<div class="col-12 col-xl-10">
|
||||
<div class="d-grid gap-2 d-xl-flex justify-content-xl-end">
|
||||
<button class="btn btn-sm btn-outline-success"
|
||||
hx-get="{% url 'transaction_add' %}"
|
||||
hx-target="#generic-offcanvas"
|
||||
hx-trigger="click, add_income from:window"
|
||||
hx-vals='{"year": {{ year }}, "type": "IN"}'>
|
||||
<i class="fa-solid fa-arrow-right-to-bracket me-2"></i>
|
||||
{% translate "Income" %}
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-danger"
|
||||
hx-get="{% url 'transaction_add' %}"
|
||||
hx-target="#generic-offcanvas"
|
||||
hx-trigger="click, add_expense from:window"
|
||||
hx-vals='{"year": {{ year }}, "type": "EX"}'>
|
||||
<i class="fa-solid fa-arrow-right-from-bracket me-2"></i>
|
||||
{% translate "Expense" %}
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-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"
|
||||
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"
|
||||
hx-get="{% url 'transactions_transfer' %}"
|
||||
hx-target="#generic-offcanvas"
|
||||
hx-trigger="click, add_transfer from:window"
|
||||
hx-vals='{"year": {{ year }}}'>
|
||||
<i class="fa-solid fa-money-bill-transfer me-2"></i>
|
||||
{% translate "Transfer" %}
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-info"
|
||||
hx-get="{% url 'account_reconciliation' %}"
|
||||
hx-trigger="click, balance from:window"
|
||||
hx-target="#generic-offcanvas">
|
||||
<i class="fa-solid fa-scale-balanced me-2"></i>
|
||||
{% translate "Balance" %}
|
||||
</button>
|
||||
</div>
|
||||
<c-ui.quick-transactions-buttons
|
||||
:year="year"
|
||||
:month="month"
|
||||
></c-ui.quick-transactions-buttons>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
||||
@@ -10,6 +10,9 @@ RUN pip wheel --wheel-dir /usr/src/app/wheels -r requirements.txt
|
||||
|
||||
FROM python:3.11-slim-bookworm AS python-run-stage
|
||||
|
||||
ARG VERSION=dev
|
||||
ENV APP_VERSION=$VERSION
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
|
||||
@@ -18,6 +18,10 @@ RUN --mount=type=cache,target=/root/.npm \
|
||||
npm run build
|
||||
|
||||
FROM python:3.11-slim-bookworm AS python-run-stage
|
||||
|
||||
ARG VERSION=dev
|
||||
ENV APP_VERSION=$VERSION
|
||||
|
||||
COPY --from=webpack_build /usr/src/frontend/build /usr/src/frontend/build
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
Reference in New Issue
Block a user