Compare commits

..

42 Commits

Author SHA1 Message Date
github-actions
cefbaafa19 Auto commit for release 'develop' on 2024-04-22 2024-04-22 05:11:03 +02:00
James Cole
a8c88800c4 Fix column 2024-04-21 19:49:28 +02:00
github-actions
9d1a127200 Auto commit for release 'develop' on 2024-04-21 2024-04-21 17:23:16 +02:00
James Cole
3fdde2d1c8 Removed for now, needs dynamic configuration. 2024-04-21 17:18:35 +02:00
James Cole
cdc0b8dd2c Add index options 2024-04-21 17:09:15 +02:00
James Cole
1a1e06e6e8 Fix tests 2024-04-21 07:07:06 +02:00
James Cole
6d39b8468c Add two buttons and options for them 2024-04-21 06:57:57 +02:00
James Cole
bdee3947b2 Add debugbar settings 2024-04-21 06:56:14 +02:00
James Cole
2317037655 Clean up transactions, first attempt at navigation. 2024-04-21 06:26:17 +02:00
James Cole
dcea6b757b Better button response in category overview. 2024-04-20 17:08:30 +02:00
James Cole
bd7fe92818 Expand accounts page. 2024-04-20 16:18:41 +02:00
James Cole
850e47d8db Fix https://github.com/firefly-iii/firefly-iii/issues/8776 2024-04-20 08:15:17 +02:00
James Cole
96fe62400f Fix undefined index 2024-04-20 07:38:05 +02:00
James Cole
5d07fcdcb6 Add user roles. 2024-04-20 07:36:53 +02:00
James Cole
fd5d2d57a8 Fix https://github.com/firefly-iii/firefly-iii/issues/8804 2024-04-20 07:19:15 +02:00
James Cole
8e7d42201f Merge branch 'main' into develop 2024-04-20 07:18:51 +02:00
James Cole
f26bd3cb31 Fix https://github.com/firefly-iii/firefly-iii/issues/8613 2024-04-19 19:59:42 +02:00
James Cole
36915cdace Fix https://github.com/firefly-iii/firefly-iii/issues/8752 2024-04-19 19:58:32 +02:00
James Cole
8a5cecd2a0 Fix https://github.com/firefly-iii/firefly-iii/issues/8781 2024-04-19 19:58:09 +02:00
James Cole
78da1b22bb Update vite.config.js
Signed-off-by: James Cole <james@firefly-iii.org>
2024-04-19 08:28:31 +02:00
github-actions
6d970a9794 Auto commit for release 'develop' on 2024-04-18 2024-04-18 10:25:45 +02:00
Sander D
8bb7739f05 Update release.yml
Signed-off-by: Sander D <git@sanderdorigo.nl>
2024-04-18 10:20:47 +02:00
James Cole
7788bb4b33 Update composer.lock 2024-04-18 09:52:01 +02:00
James Cole
2ecb4bb3b7 Update composer.json 2024-04-18 09:51:45 +02:00
James Cole
4a783d3c3c Drop a specific preference from the return of Firefly III 2024-04-18 05:54:57 +02:00
github-actions
e16645ae87 Auto commit for release 'develop' on 2024-04-18 2024-04-18 05:09:58 +02:00
github-actions
9d3189be7e Auto commit for release 'develop' on 2024-04-15 2024-04-15 07:59:54 +02:00
James Cole
07fca78293 Merge pull request #8791 from firefly-iii/dependabot/npm_and_yarn/develop/i18next-23.11.2
Bump i18next from 23.11.1 to 23.11.2
2024-04-15 07:55:47 +02:00
James Cole
82080501c7 Merge pull request #8790 from firefly-iii/dependabot/npm_and_yarn/develop/sass-1.75.0
Bump sass from 1.74.1 to 1.75.0
2024-04-15 07:55:37 +02:00
James Cole
d93d6bfc66 Update phpcs.sh
Signed-off-by: James Cole <james@firefly-iii.org>
2024-04-15 07:54:52 +02:00
dependabot[bot]
a41326ef94 Bump i18next from 23.11.1 to 23.11.2
Bumps [i18next](https://github.com/i18next/i18next) from 23.11.1 to 23.11.2.
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v23.11.1...v23.11.2)

---
updated-dependencies:
- dependency-name: i18next
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-15 03:55:25 +00:00
dependabot[bot]
90b77845c3 Bump sass from 1.74.1 to 1.75.0
Bumps [sass](https://github.com/sass/dart-sass) from 1.74.1 to 1.75.0.
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sass/dart-sass/compare/1.74.1...1.75.0)

---
updated-dependencies:
- dependency-name: sass
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-15 03:55:12 +00:00
James Cole
57af80d820 Merge pull request #8788 from firefly-iii/dependabot/composer/develop/larastan/larastan-2.9.4 2024-04-15 05:45:30 +02:00
James Cole
fc4d5a1dfd Merge pull request #8787 from firefly-iii/dependabot/composer/develop/phpunit/phpunit-10.5.18 2024-04-15 05:45:21 +02:00
dependabot[bot]
8ab9ab8d21 Bump larastan/larastan from 2.9.2 to 2.9.4
Bumps [larastan/larastan](https://github.com/larastan/larastan) from 2.9.2 to 2.9.4.
- [Release notes](https://github.com/larastan/larastan/releases)
- [Changelog](https://github.com/larastan/larastan/blob/2.x/RELEASE.md)
- [Commits](https://github.com/larastan/larastan/compare/v2.9.2...v2.9.4)

---
updated-dependencies:
- dependency-name: larastan/larastan
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-15 03:01:51 +00:00
dependabot[bot]
75674b5793 Bump phpunit/phpunit from 10.5.17 to 10.5.18
Bumps [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) from 10.5.17 to 10.5.18.
- [Release notes](https://github.com/sebastianbergmann/phpunit/releases)
- [Changelog](https://github.com/sebastianbergmann/phpunit/blob/10.5.18/ChangeLog-10.5.md)
- [Commits](https://github.com/sebastianbergmann/phpunit/compare/10.5.17...10.5.18)

---
updated-dependencies:
- dependency-name: phpunit/phpunit
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-15 03:01:41 +00:00
James Cole
a7d6f26051 Sign release 2024-04-14 13:34:41 +02:00
James Cole
eb540ce148 New PR template 2024-04-14 13:32:56 +02:00
James Cole
a3077fe43b Merge branch 'main' into develop 2024-04-14 08:52:38 +02:00
James Cole
63bb84d375 Workflow runs on "main" 2024-04-14 08:52:27 +02:00
James Cole
e5f5aa628e Small changes in CI script and vite config 2024-04-14 08:51:59 +02:00
James Cole
c54f84dc8e Fix https://github.com/firefly-iii/firefly-iii/issues/8779 2024-04-13 05:50:26 +02:00
98 changed files with 1600 additions and 936 deletions

View File

@@ -226,16 +226,16 @@
},
{
"name": "friendsofphp/php-cs-fixer",
"version": "v3.53.0",
"version": "v3.54.0",
"source": {
"type": "git",
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
"reference": "69a19093a9ded8d1baac62ed6c009b8bc148d008"
"reference": "2aecbc8640d7906c38777b3dcab6f4ca79004d08"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/69a19093a9ded8d1baac62ed6c009b8bc148d008",
"reference": "69a19093a9ded8d1baac62ed6c009b8bc148d008",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/2aecbc8640d7906c38777b3dcab6f4ca79004d08",
"reference": "2aecbc8640d7906c38777b3dcab6f4ca79004d08",
"shasum": ""
},
"require": {
@@ -307,7 +307,7 @@
],
"support": {
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.53.0"
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.54.0"
},
"funding": [
{
@@ -315,7 +315,7 @@
"type": "github"
}
],
"time": "2024-04-08T15:03:00+00:00"
"time": "2024-04-17T08:12:13+00:00"
},
{
"name": "psr/container",

View File

@@ -20,23 +20,8 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# Install composer packages
#composer install --no-scripts --no-ansi
SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
# enable test .env file.
# cp .ci/.env.ci .env
OUTPUT_FORMAT=txt
EXTRA_PARAMS=""
if [[ $GITHUB_ACTIONS = "true" ]]
then
OUTPUT_FORMAT=txt
EXTRA_PARAMS=""
fi
# clean up php code
cd $SCRIPT_DIR/php-cs-fixer
composer update --quiet
@@ -44,8 +29,8 @@ rm -f .php-cs-fixer.cache
PHP_CS_FIXER_IGNORE_ENV=true
./vendor/bin/php-cs-fixer fix \
--config $SCRIPT_DIR/php-cs-fixer/.php-cs-fixer.php \
--format=$OUTPUT_FORMAT \
--allow-risky=yes $EXTRA_PARAMS
--format=txt \
--allow-risky=yes
EXIT_CODE=$?

View File

@@ -1,13 +1,13 @@
<!--
Thank you for submitting new code to Firefly III, or any of the related projects. Please read the following rules carefully.
- Do not submit solutions for problems that are not already reported in an issue
- Firefly III can't be your learning experience. If you're new to all of this, please go be new somewhere else
- Do not open PRs to "discuss" possible solutions or to "get feedback" on your code. I don't have time for that.
- Please do not submit solutions for problems that are not already reported in an issue.
- Unfortunately, Firefly III can't be your learning experience. If you're new to all of this, please open an issue first.
- Please do not open PRs to "discuss" possible solutions or to "get feedback" on your code. I simply don't have time for that.
- Pull requests for the MAIN branch will be closed.
- DO NOT include translated strings in your PR.
Perhaps open an issue first, before you open a PR?
If it feels necessary to open an issue first, please do so, before you open a PR.
See also: https://docs.firefly-iii.org/explanation/support/#contributing-code

View File

@@ -51,7 +51,7 @@ jobs:
CROWDIN_TOKEN: ${{ secrets.CROWDIN_TOKEN }}
- name: Cleanup translations
id: cleanup-transactions
uses: JC5/firefly-iii-dev@v38
uses: JC5/firefly-iii-dev@main
with:
action: 'ff3:crowdin-warning'
output: ''
@@ -60,7 +60,7 @@ jobs:
GH_TOKEN: ''
- name: Cleanup changelog
id: cleanup-changelog
uses: JC5/firefly-iii-dev@v38
uses: JC5/firefly-iii-dev@main
with:
action: 'ff3:changelog'
output: ''
@@ -69,7 +69,7 @@ jobs:
GH_TOKEN: ${{ secrets.CHANGELOG_TOKEN }}
- name: Extract changelog
id: extract-changelog
uses: JC5/firefly-iii-dev@v38
uses: JC5/firefly-iii-dev@main
with:
action: 'ff3:extract-changelog'
output: 'output'
@@ -78,7 +78,7 @@ jobs:
GH_TOKEN: ""
- name: Replace version
id: replace-version
uses: JC5/firefly-iii-dev@v38
uses: JC5/firefly-iii-dev@main
with:
action: 'ff3:version'
output: ''
@@ -88,7 +88,7 @@ jobs:
FF_III_VERSION: ${{ github.event_name == 'schedule' && 'develop' || github.event.inputs.version }}
- name: Generate JSON v1
id: json-v1
uses: JC5/firefly-iii-dev@v38
uses: JC5/firefly-iii-dev@main
with:
action: 'ff3:json-translations v1'
output: ''
@@ -97,7 +97,7 @@ jobs:
GH_TOKEN: ''
- name: Generate JSON v2
id: json-v2
uses: JC5/firefly-iii-dev@v38
uses: JC5/firefly-iii-dev@main
with:
action: 'ff3:json-translations v2'
output: ''
@@ -106,7 +106,7 @@ jobs:
GH_TOKEN: ''
- name: Code cleanup
id: code-cleanup
uses: JC5/firefly-iii-dev@v38
uses: JC5/firefly-iii-dev@main
with:
action: 'ff3:code'
output: ''
@@ -122,10 +122,14 @@ jobs:
- name: Run CI
run: |
rm -rf vendor composer.lock
composer validate --strict
composer update --no-dev --no-scripts --no-plugins -q
sudo chown -R runner:docker resources/lang
.ci/phpcs.sh
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@v6
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.PASSPHRASE }}
- name: Release
run: |
# do some configuration
@@ -141,7 +145,6 @@ jobs:
tarName=FireflyIII-$version.tar.gz
# update composer (again)
composer validate --strict
composer update --no-dev --no-scripts --no-plugins
composer dump-autoload
@@ -191,6 +194,10 @@ jobs:
sha256sum -b $zipName > $zipName.sha256
sha256sum -b $tarName > $tarName.sha256
# add signatures:
gpg --armor --detach-sign $zipName
gpg --armor --detach-sign $tarName
# create a development (nightly) release:
if [[ "develop" == "$version" ]]; then
echo 'Develop release.'
@@ -198,7 +205,7 @@ jobs:
rm output.txt
echo "Bi-weekly development release of Firefly III with the latest fixes, translations and features. Docker users can find this release under the \`develop\` tag." >> output.txt
echo "" >> output.txt
echo "This release was created on **$(date +'%Y-%m-%d')** and may contain unexpected bugs. Data loss is rare but is not impossible." >> output.txt
echo "This release was created on **$(date +'%Y-%m-%d')** and may contain unexpected bugs. Data loss is rare but is not impossible. The releases are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/)." >> output.txt
echo "" >> output.txt
echo "* Please read the installation instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/installation/docker/), [Portainer](https://docs.firefly-iii.org/how-to/firefly-iii/installation/portainer/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/installation/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/installation/self-managed/)" >> output.txt
echo "* Or read the upgrade instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/docker/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/self-managed/)" >> output.txt
@@ -221,6 +228,10 @@ jobs:
gh release upload $releaseName $zipName.sha256
gh release upload $releaseName $tarName.sha256
# add signatures to release
gh release upload $releaseName $zipName.asc
gh release upload $releaseName $tarName.asc
# get current HEAD and add as file to the release
HEAD=$(git rev-parse HEAD)
echo $HEAD > HEAD.txt
@@ -234,6 +245,7 @@ jobs:
echo '' >> output.txt
echo "* Installation instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/installation/docker/), [Portainer](https://docs.firefly-iii.org/how-to/firefly-iii/installation/portainer/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/installation/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/installation/self-managed/)" >> output.txt
echo "* Or read the upgrade instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/docker/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/self-managed/)" >> output.txt
echo "* The releases are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/)."
echo "Create default release."
git tag -a $releaseName -m "Here be changelog"
@@ -248,6 +260,10 @@ jobs:
gh release upload $releaseName $zipName.sha256
gh release upload $releaseName $tarName.sha256
# add signatures to release
gh release upload $releaseName $zipName.asc
gh release upload $releaseName $tarName.asc
# get current HEAD and add as file to the release
HEAD=$(git rev-parse HEAD)
echo $HEAD > HEAD.txt

View File

@@ -73,7 +73,8 @@ class UpdateController extends Controller
$data = $request->getAll();
// Fixes 8750.
foreach ($data['transactions'] as $index => $info) {
$transactions = $data['transactions'] ?? [];
foreach ($transactions as $index => $info) {
unset($data['transactions'][$index]['type']);
}

View File

@@ -84,6 +84,10 @@ class PreferencesController extends Controller
{
$manager = $this->getManager();
if ('currencyPreference' === $preference->name) {
throw new FireflyException('Please use api/v1/currencies/default instead.');
}
/** @var PreferenceTransformer $transformer */
$transformer = app(PreferenceTransformer::class);
$transformer->setParameters($this->parameters);
@@ -103,6 +107,11 @@ class PreferencesController extends Controller
{
$manager = $this->getManager();
$data = $request->getAll();
if ('currencyPreference' === $data['name']) {
throw new FireflyException('Please use api/v1/currencies/default instead.');
}
$pref = app('preferences')->set($data['name'], $data['data']);
/** @var PreferenceTransformer $transformer */
@@ -122,6 +131,10 @@ class PreferencesController extends Controller
*/
public function update(PreferenceUpdateRequest $request, Preference $preference): JsonResponse
{
if ('currencyPreference' === $preference->name) {
throw new FireflyException('Please use api/v1/currencies/default instead.');
}
$manager = $this->getManager();
$data = $request->getAll();
$pref = app('preferences')->set($preference->name, $data['data']);

View File

@@ -71,8 +71,9 @@ class StoreRequest extends FormRequest
if (is_array($triggers)) {
foreach ($triggers as $trigger) {
$return[] = [
'type' => $trigger['type'],
'value' => $trigger['value'],
'type' => $trigger['type'] ?? '',
'value' => $trigger['value'] ?? null,
'prohibited' => $this->convertBoolean((string)($trigger['prohibited'] ?? 'false')),
'active' => $this->convertBoolean((string)($trigger['active'] ?? 'true')),
'stop_processing' => $this->convertBoolean((string)($trigger['stop_processing'] ?? 'false')),
];

View File

@@ -81,10 +81,12 @@ class UpdateRequest extends FormRequest
if (is_array($triggers)) {
foreach ($triggers as $trigger) {
$active = array_key_exists('active', $trigger) ? $trigger['active'] : true;
$prohibited = array_key_exists('prohibited', $trigger) ? $trigger['prohibited'] : false;
$stopProcessing = array_key_exists('stop_processing', $trigger) ? $trigger['stop_processing'] : false;
$return[] = [
'type' => $trigger['type'],
'value' => $trigger['value'],
'prohibited' => $prohibited,
'active' => $active,
'stop_processing' => $stopProcessing,
];

View File

@@ -122,6 +122,9 @@ class Controller extends BaseController
$obj = null;
}
}
if (null !== $date && 'end' === $field) {
$obj->endOfDay();
}
$bag->set($field, $obj);
}

View File

@@ -25,7 +25,6 @@ namespace FireflyIII\Api\V2\Controllers\Model\Account;
use FireflyIII\Api\V2\Controllers\Controller;
use FireflyIII\Api\V2\Request\Model\Account\IndexRequest;
use FireflyIII\Api\V2\Request\Model\Transaction\InfiniteListRequest;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
use FireflyIII\Transformers\V2\AccountTransformer;
@@ -58,7 +57,7 @@ class IndexController extends Controller
}
/**
* TODO see autocomplete/accountcontroller for list.
* TODO see autocomplete/account controller for list.
*/
public function index(IndexRequest $request): JsonResponse
{
@@ -80,25 +79,4 @@ class IndexController extends Controller
->header('Content-Type', self::CONTENT_TYPE)
;
}
public function infiniteList(InfiniteListRequest $request): JsonResponse
{
$this->repository->resetAccountOrder();
// get accounts of the specified type, and return.
$types = $request->getAccountTypes();
// get from repository
$accounts = $this->repository->getAccountsInOrder($types, $request->getSortInstructions('accounts'), $request->getStartRow(), $request->getEndRow());
$total = $this->repository->countAccounts($types);
$count = $request->getEndRow() - $request->getStartRow();
$paginator = new LengthAwarePaginator($accounts, $total, $count, $this->parameters->get('page'));
$transformer = new AccountTransformer();
$transformer->setParameters($this->parameters); // give params to transformer
return response()
->json($this->jsonApiList(self::RESOURCE_KEY, $paginator, $transformer))
->header('Content-Type', self::CONTENT_TYPE)
;
}
}

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Api\V2\Request\Chart;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes;
@@ -39,6 +40,7 @@ class BalanceChartRequest extends FormRequest
use ChecksLogin;
use ConvertsDataTypes;
use ValidatesUserGroupTrait;
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
/**
* Get all data from the request.

View File

@@ -44,14 +44,11 @@ class IndexRequest extends FormRequest
public function getAccountTypes(): array
{
$type = (string)$this->get('type', 'default');
$type = (string) $this->get('type', 'default');
return $this->mapAccountTypes($type);
}
/**
* Get all data from the request.
*/
public function getDate(): Carbon
{
return $this->getCarbonDate('date');
@@ -63,7 +60,9 @@ class IndexRequest extends FormRequest
public function rules(): array
{
return [
'date' => 'date|after:1900-01-01|before:2099-12-31',
'date' => 'date|after:1900-01-01|before:2099-12-31',
'start' => 'date|after:1900-01-01|before:2099-12-31|before:end|required_with:end',
'end' => 'date|after:1900-01-01|before:2099-12-31|after:start|required_with:start',
];
}
}

View File

@@ -31,6 +31,7 @@ use Illuminate\Contracts\View\Factory;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
/**
@@ -106,6 +107,8 @@ class ForgotPasswordController extends Controller
}
$host = request()->host();
if ($configuredHost !== $host) {
Log::error(sprintf('Host header is "%s", APP_URL is "%s".', $host, $configuredHost));
throw new FireflyException('The Host-header does not match the host in the APP_URL environment variable. Please make sure these match. See also: https://bit.ly/FF3-host-header');
}
}

View File

@@ -25,6 +25,7 @@ namespace FireflyIII\Http\Middleware;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Vite;
use Barryvdh\Debugbar\Facades\Debugbar;
/**
* Class SecureHeaders
@@ -43,6 +44,9 @@ class SecureHeaders
// generate and share nonce.
$nonce = base64_encode(random_bytes(16));
Vite::useCspNonce($nonce);
if (class_exists('Barryvdh\Debugbar\Facades\Debugbar')) {
Debugbar::getJavascriptRenderer()->setCspNonce($nonce);
}
app('view')->share('JS_NONCE', $nonce);
$response = $next($request);
@@ -55,14 +59,29 @@ class SecureHeaders
"base-uri 'self'",
"font-src 'self' data:",
sprintf("connect-src 'self' %s", $trackingScriptSrc),
sprintf("img-src 'self' 'nonce-%1s'", $nonce),
sprintf("img-src 'self' data: 'nonce-%1s' ", $nonce),
"manifest-src 'self'",
];
// overrule in development mode
if (true === env('IS_LOCAL_DEV')) {
$csp = [
"default-src 'none'",
"object-src 'none'",
sprintf("script-src 'unsafe-eval' 'strict-dynamic' 'nonce-%1s' https://firefly.sd.internal/_debugbar/assets", $nonce),
"style-src 'unsafe-inline' 'self' https://10.0.0.15:5173/",
"base-uri 'self'",
"font-src 'self' data: https://10.0.0.15:5173/",
sprintf("connect-src 'self' %s https://10.0.0.15:5173/ wss://10.0.0.15:5173/", $trackingScriptSrc),
sprintf("img-src 'self' data: 'nonce-%1s'", $nonce),
"manifest-src 'self'",
];
}
$route = $request->route();
$customUrl = '';
$authGuard = (string)config('firefly.authentication_guard');
$logoutUrl = (string)config('firefly.custom_logout_url');
$authGuard = (string) config('firefly.authentication_guard');
$logoutUrl = (string) config('firefly.custom_logout_url');
if ('remote_user_guard' === $authGuard && '' !== $logoutUrl) {
$customUrl = $logoutUrl;
}
@@ -110,8 +129,8 @@ class SecureHeaders
*/
private function getTrackingScriptSource(): string
{
if ('' !== (string)config('firefly.tracker_site_id') && '' !== (string)config('firefly.tracker_url')) {
return (string)config('firefly.tracker_url');
if ('' !== (string) config('firefly.tracker_site_id') && '' !== (string) config('firefly.tracker_url')) {
return (string) config('firefly.tracker_url');
}
return '';

View File

@@ -295,4 +295,16 @@ class UserGroupRepository implements UserGroupRepositoryInterface
$this->user->user_group_id = $userGroup->id;
$this->user->save();
}
#[\Override]
public function getMembershipsFromGroupId(int $groupId): Collection
{
return $this->user->groupMemberships()->where('user_group_id', $groupId)->get();
}
#[\Override]
public function getById(int $id): ?UserGroup
{
return UserGroup::find($id);
}
}

View File

@@ -36,8 +36,12 @@ interface UserGroupRepositoryInterface
{
public function destroy(UserGroup $userGroup): void;
public function getMembershipsFromGroupId(int $groupId): Collection;
public function get(): Collection;
public function getById(int $id): ?UserGroup;
public function getAll(): Collection;
public function setUser(null|Authenticatable|User $user): void;

View File

@@ -27,11 +27,13 @@ namespace FireflyIII\Repositories\UserGroups\Account;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountMeta;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Services\Internal\Update\AccountUpdateService;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
/**
* Class AccountRepository
@@ -121,7 +123,7 @@ class AccountRepository implements AccountRepositoryInterface
if (!in_array($type, $list, true)) {
return null;
}
$currencyId = (int)$this->getMetaValue($account, 'currency_id');
$currencyId = (int) $this->getMetaValue($account, 'currency_id');
if ($currencyId > 0) {
return TransactionCurrency::find($currencyId);
}
@@ -143,7 +145,7 @@ class AccountRepository implements AccountRepositoryInterface
return null;
}
if (1 === $result->count()) {
return (string)$result->first()->data;
return (string) $result->first()->data;
}
return null;
@@ -228,7 +230,7 @@ class AccountRepository implements AccountRepositoryInterface
continue;
}
if ($index !== (int)$account->order) {
if ($index !== (int) $account->order) {
app('log')->debug(sprintf('Account #%d ("%s"): order should %d be but is %d.', $account->id, $account->name, $index, $account->order));
$account->order = $index;
$account->save();
@@ -305,4 +307,34 @@ class AccountRepository implements AccountRepositoryInterface
return $service->update($account, $data);
}
#[\Override]
public function getMetaValues(Collection $accounts, array $fields): Collection
{
$query = AccountMeta::whereIn('account_id', $accounts->pluck('id')->toArray());
if (count($fields) > 0) {
$query->whereIn('name', $fields);
}
return $query->get(['account_meta.id', 'account_meta.account_id', 'account_meta.name', 'account_meta.data']);
}
#[\Override]
public function getAccountTypes(Collection $accounts): Collection
{
return AccountType::leftJoin('accounts', 'accounts.account_type_id', '=', 'account_types.id')
->whereIn('accounts.id', $accounts->pluck('id')->toArray())
->get(['accounts.id', 'account_types.type'])
;
}
#[\Override]
public function getLastActivity(Collection $accounts): array
{
return Transaction::whereIn('account_id', $accounts->pluck('id')->toArray())
->leftJoin('transaction_journals', 'transaction_journals.id', 'transactions.transaction_journal_id')
->groupBy('transactions.account_id')
->get(['transactions.account_id', DB::raw('MAX(transaction_journals.date) as date_max')])->toArray() // @phpstan-ignore-line
;
}
}

View File

@@ -37,6 +37,12 @@ interface AccountRepositoryInterface
{
public function countAccounts(array $types): int;
public function getAccountTypes(Collection $accounts): Collection;
public function getLastActivity(Collection $accounts): array;
public function getMetaValues(Collection $accounts, array $fields): Collection;
public function find(int $accountId): ?Account;
public function findByAccountNumber(string $number, array $types): ?Account;

View File

@@ -24,12 +24,13 @@ declare(strict_types=1);
namespace FireflyIII\Support\Http\Api;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\GroupMembership;
use FireflyIII\Models\UserGroup;
use FireflyIII\Repositories\UserGroup\UserGroupRepositoryInterface;
use FireflyIII\User;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
/**
* Trait ValidatesUserGroupTrait
@@ -42,63 +43,64 @@ trait ValidatesUserGroupTrait
*/
protected function validateUserGroup(Request $request): UserGroup
{
app('log')->debug(sprintf('validateUserGroup: %s', static::class));
Log::debug(sprintf('validateUserGroup: %s', static::class));
if (!auth()->check()) {
app('log')->debug('validateUserGroup: user is not logged in, return NULL.');
Log::debug('validateUserGroup: user is not logged in, return NULL.');
throw new AuthenticationException();
}
/** @var User $user */
$user = auth()->user();
$groupId = 0;
$user = auth()->user();
$groupId = 0;
if (!$request->has('user_group_id')) {
$groupId = $user->user_group_id;
app('log')->debug(sprintf('validateUserGroup: no user group submitted, use default group #%d.', $groupId));
Log::debug(sprintf('validateUserGroup: no user group submitted, use default group #%d.', $groupId));
}
if ($request->has('user_group_id')) {
$groupId = (int)$request->get('user_group_id');
app('log')->debug(sprintf('validateUserGroup: user group submitted, search for memberships in group #%d.', $groupId));
$groupId = (int) $request->get('user_group_id');
Log::debug(sprintf('validateUserGroup: user group submitted, search for memberships in group #%d.', $groupId));
}
/** @var null|GroupMembership $membership */
$membership = $user->groupMemberships()->where('user_group_id', $groupId)->first();
/** @var UserGroupRepositoryInterface $repository */
$repository = app(UserGroupRepositoryInterface::class);
$repository->setUser($user);
$memberships = $repository->getMembershipsFromGroupId($groupId);
if (null === $membership) {
app('log')->debug(sprintf('validateUserGroup: user has no access to group #%d.', $groupId));
if (0 === $memberships->count()) {
Log::debug(sprintf('validateUserGroup: user has no access to group #%d.', $groupId));
throw new AuthorizationException((string)trans('validation.no_access_group'));
throw new AuthorizationException((string) trans('validation.no_access_group'));
}
// need to get the group from the membership:
/** @var null|UserGroup $group */
$group = $membership->userGroup;
$group = $repository->getById($groupId);
if (null === $group) {
app('log')->debug(sprintf('validateUserGroup: group #%d does not exist.', $groupId));
Log::debug(sprintf('validateUserGroup: group #%d does not exist.', $groupId));
throw new AuthorizationException((string)trans('validation.belongs_user_or_user_group'));
throw new AuthorizationException((string) trans('validation.belongs_user_or_user_group'));
}
app('log')->debug(sprintf('validateUserGroup: validate access of user to group #%d ("%s").', $groupId, $group->title));
$roles = property_exists($this, 'acceptedRoles') ? $this->acceptedRoles : [];
Log::debug(sprintf('validateUserGroup: validate access of user to group #%d ("%s").', $groupId, $group->title));
$roles = property_exists($this, 'acceptedRoles') ? $this->acceptedRoles : [];
if (0 === count($roles)) {
app('log')->debug('validateUserGroup: no roles defined, so no access.');
Log::debug('validateUserGroup: no roles defined, so no access.');
throw new AuthorizationException((string)trans('validation.no_accepted_roles_defined'));
throw new AuthorizationException((string) trans('validation.no_accepted_roles_defined'));
}
app('log')->debug(sprintf('validateUserGroup: have %d roles to check.', count($roles)), $roles);
Log::debug(sprintf('validateUserGroup: have %d roles to check.', count($roles)), $roles);
/** @var UserRoleEnum $role */
foreach ($roles as $role) {
if ($user->hasRoleInGroupOrOwner($group, $role)) {
app('log')->debug(sprintf('validateUserGroup: User has role "%s" in group #%d, return the group.', $role->value, $groupId));
Log::debug(sprintf('validateUserGroup: User has role "%s" in group #%d, return the group.', $role->value, $groupId));
return $group;
}
app('log')->debug(sprintf('validateUserGroup: User does NOT have role "%s" in group #%d, continue searching.', $role->value, $groupId));
Log::debug(sprintf('validateUserGroup: User does NOT have role "%s" in group #%d, continue searching.', $role->value, $groupId));
}
app('log')->debug('validateUserGroup: User does NOT have enough rights to access endpoint.');
Log::debug('validateUserGroup: User does NOT have enough rights to access endpoint.');
throw new AuthorizationException((string)trans('validation.belongs_user_or_user_group'));
throw new AuthorizationException((string) trans('validation.belongs_user_or_user_group'));
}
}

View File

@@ -464,14 +464,15 @@ class Navigation
$increment = 'addDay';
$format = $this->preferredCarbonFormat($start, $end);
$displayFormat = (string)trans('config.month_and_day_js', [], $locale);
$diff = $start->diffInMonths($end, true);
// increment by month (for year)
if ($start->diffInMonths($end, true) > 1) {
if ($diff >= 1.0001) {
$increment = 'addMonth';
$displayFormat = (string)trans('config.month_js');
}
// increment by year (for multi-year)
if ($start->diffInMonths($end, true) > 12) {
if ($diff >= 12.0001) {
$increment = 'addYear';
$displayFormat = (string)trans('config.year_js');
}
@@ -494,11 +495,15 @@ class Navigation
public function preferredCarbonFormat(Carbon $start, Carbon $end): string
{
$format = 'Y-m-d';
if ((int)$start->diffInMonths($end, true) > 1) {
$diff = $start->diffInMonths($end, true);
Log::debug(sprintf('preferredCarbonFormat(%s, %s) = %f', $start->format('Y-m-d'), $end->format('Y-m-d'), $diff));
if ($diff >= 1.001) {
Log::debug(sprintf('Return Y-m because %s', $diff));
$format = 'Y-m';
}
if ((int)$start->diffInMonths($end, true) > 12) {
if ($diff >= 12.001) {
Log::debug(sprintf('Return Y because %s', $diff));
$format = 'Y';
}

View File

@@ -44,6 +44,7 @@ class Preferences
}
return Preference::where('user_id', $user->id)
->where('name', '!=', 'currencyPreference')
->where(function (Builder $q) use ($user): void {
$q->whereNull('user_group_id');
$q->orWhere('user_group_id', $user->user_group_id);

View File

@@ -159,6 +159,10 @@ class OperatorQuerySearch implements SearchInterface
foreach ($query1->getNodes() as $searchNode) {
$this->handleSearchNode($searchNode);
}
// add missing information
$this->collector->withBillInformation();
$this->collector->setSearchWords($this->words);
$this->collector->excludeSearchWords($this->prohibitedWords);
}

View File

@@ -49,6 +49,7 @@ class Translation extends AbstractExtension
{
return [
$this->journalLinkTranslation(),
$this->laravelTranslation(),
];
}
@@ -68,4 +69,19 @@ class Translation extends AbstractExtension
['is_safe' => ['html']]
);
}
public function laravelTranslation(): TwigFunction
{
return new TwigFunction(
'__',
static function (string $key) {
$translation = trans($key);
if ($key === $translation) {
return $key;
}
return $translation;
}
);
}
}

View File

@@ -34,8 +34,7 @@ use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
*/
class RuleTransformer extends AbstractTransformer
{
/** @var RuleRepositoryInterface */
private $ruleRepository;
private RuleRepositoryInterface $ruleRepository;
/**
* CurrencyTransformer constructor.
@@ -109,8 +108,16 @@ class RuleTransformer extends AbstractTransformer
if ('user_action' === $ruleTrigger->trigger_type) {
continue;
}
$triggerType = (string) $ruleTrigger->trigger_type;
$triggerValue = (string)$ruleTrigger->trigger_value;
$needsContext = config(sprintf('search.operators.%s.needs_context', $ruleTrigger->trigger_type), true);
$prohibited = false;
if (str_starts_with($triggerType, '-')) {
$prohibited = true;
$triggerType = substr($triggerType, 1);
}
$needsContext = config(sprintf('search.operators.%s.needs_context', $triggerType), true);
if (false === $needsContext) {
$triggerValue = 'true';
}
@@ -119,8 +126,9 @@ class RuleTransformer extends AbstractTransformer
'id' => (string)$ruleTrigger->id,
'created_at' => $ruleTrigger->created_at->toAtomString(),
'updated_at' => $ruleTrigger->updated_at->toAtomString(),
'type' => $ruleTrigger->trigger_type,
'type' => $triggerType,
'value' => $triggerValue,
'prohibited' => $prohibited,
'order' => $ruleTrigger->order,
'active' => $ruleTrigger->active,
'stop_processing' => $ruleTrigger->stop_processing,

View File

@@ -27,13 +27,12 @@ namespace FireflyIII\Transformers\V2;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountMeta;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Currency\CurrencyRepositoryInterface;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
/**
* Class AccountTransformer
@@ -42,118 +41,45 @@ class AccountTransformer extends AbstractTransformer
{
private array $accountMeta;
private array $accountTypes;
private array $balances;
private array $balanceDifferences;
private array $convertedBalances;
private array $currencies;
private TransactionCurrency $default;
private array $lastActivity;
/**
* @throws FireflyException
* This method collects meta-data for one or all accounts in the transformer's collection.
*/
public function collectMetaData(Collection $objects): Collection
{
// TODO separate methods
$this->currencies = [];
$this->accountMeta = [];
$this->accountTypes = [];
$this->lastActivity = [];
$this->balances = app('steam')->balancesByAccounts($objects, $this->getDate());
$this->convertedBalances = app('steam')->balancesByAccountsConverted($objects, $this->getDate());
$this->currencies = [];
$this->accountMeta = [];
$this->accountTypes = [];
$this->lastActivity = [];
$this->convertedBalances = [];
$this->balanceDifferences = [];
/** @var CurrencyRepositoryInterface $repository */
$repository = app(CurrencyRepositoryInterface::class);
$this->default = app('amount')->getDefaultCurrency();
// get balances of all accounts
$this->getMetaBalances($objects);
// get currencies:
$accountIds = $objects->pluck('id')->toArray();
// TODO move query to repository
$meta = AccountMeta::whereIn('account_id', $accountIds)
->whereIn('name', ['currency_id', 'account_role', 'account_number'])
->get(['account_meta.id', 'account_meta.account_id', 'account_meta.name', 'account_meta.data'])
;
$currencyIds = $meta->where('name', 'currency_id')->pluck('data')->toArray();
// get default currency:
$this->getDefaultCurrency();
// collect currency and other meta-data:
$this->collectAccountMetaData($objects);
$currencies = $repository->getByIds($currencyIds);
foreach ($currencies as $currency) {
$id = $currency->id;
$this->currencies[$id] = $currency;
}
foreach ($meta as $entry) {
$id = $entry->account_id;
$this->accountMeta[$id][$entry->name] = $entry->data;
}
// get account types:
// select accounts.id, account_types.type from account_types left join accounts on accounts.account_type_id = account_types.id;
// TODO move query to repository
$accountTypes = AccountType::leftJoin('accounts', 'accounts.account_type_id', '=', 'account_types.id')
->whereIn('accounts.id', $accountIds)
->get(['accounts.id', 'account_types.type'])
;
$this->collectAccountTypes($objects);
/** @var AccountType $row */
foreach ($accountTypes as $row) {
$this->accountTypes[$row->id] = (string)config(sprintf('firefly.shortNamesByFullName.%s', $row->type));
// get last activity:
$this->getLastActivity($objects);
// TODO add balance difference
if (null !== $this->parameters->get('start') && null !== $this->parameters->get('end')) {
$this->getBalanceDifference($objects, $this->parameters->get('start'), $this->parameters->get('end'));
}
// get last activity
// TODO move query to repository
$array = Transaction::whereIn('account_id', $accountIds)
->leftJoin('transaction_journals', 'transaction_journals.id', 'transactions.transaction_journal_id')
->groupBy('transactions.account_id')
->get(['transactions.account_id', DB::raw('MAX(transaction_journals.date) as date_max')])->toArray() // @phpstan-ignore-line
;
foreach ($array as $row) {
$this->lastActivity[(int)$row['account_id']] = Carbon::parse($row['date_max'], config('app.timezone'));
}
// TODO needs separate method.
/** @var null|array $sort */
$sort = $this->parameters->get('sort');
if (null !== $sort && count($sort) > 0) {
foreach ($sort as $column => $direction) {
// account_number + iban
if ('iban' === $column) {
$meta = $this->accountMeta;
$objects = $objects->sort(function (Account $left, Account $right) use ($meta, $direction) {
$leftIban = trim(sprintf('%s%s', $left->iban, $meta[$left->id]['account_number'] ?? ''));
$rightIban = trim(sprintf('%s%s', $right->iban, $meta[$right->id]['account_number'] ?? ''));
if ('asc' === $direction) {
return strcasecmp($leftIban, $rightIban);
}
return strcasecmp($rightIban, $leftIban);
});
}
if ('balance' === $column) {
$balances = $this->convertedBalances;
$objects = $objects->sort(function (Account $left, Account $right) use ($balances, $direction) {
$leftBalance = (float)($balances[$left->id]['native_balance'] ?? 0);
$rightBalance = (float)($balances[$right->id]['native_balance'] ?? 0);
if ('asc' === $direction) {
return $leftBalance <=> $rightBalance;
}
return $rightBalance <=> $leftBalance;
});
}
if ('last_activity' === $column) {
$dates = $this->lastActivity;
$objects = $objects->sort(function (Account $left, Account $right) use ($dates, $direction) {
$leftDate = $dates[$left->id] ?? Carbon::create(1900, 1, 1, 0, 0, 0);
$rightDate = $dates[$right->id] ?? Carbon::create(1900, 1, 1, 0, 0, 0);
if ('asc' === $direction) {
return $leftDate->gt($rightDate) ? 1 : -1;
}
return $rightDate->gt($leftDate) ? 1 : -1;
});
}
}
}
// $objects = $objects->sortByDesc('name');
return $objects;
return $this->sortAccounts($objects);
}
private function getDate(): Carbon
@@ -171,44 +97,56 @@ class AccountTransformer extends AbstractTransformer
*/
public function transform(Account $account): array
{
$id = $account->id;
$id = $account->id;
// various meta
$accountRole = $this->accountMeta[$id]['account_role'] ?? null;
$accountType = $this->accountTypes[$id];
$order = $account->order;
$accountRole = $this->accountMeta[$id]['account_role'] ?? null;
$accountType = $this->accountTypes[$id];
$order = $account->order;
// no currency? use default
$currency = $this->default;
if (array_key_exists($id, $this->accountMeta) && 0 !== (int)($this->accountMeta[$id]['currency_id'] ?? 0)) {
$currency = $this->currencies[(int)$this->accountMeta[$id]['currency_id']];
$currency = $this->default;
if (array_key_exists($id, $this->accountMeta) && 0 !== (int) ($this->accountMeta[$id]['currency_id'] ?? 0)) {
$currency = $this->currencies[(int) $this->accountMeta[$id]['currency_id']];
}
// amounts and calculation.
$balance = $this->balances[$id] ?? null;
$nativeBalance = $this->convertedBalances[$id]['native_balance'] ?? null;
$balance = $this->balances[$id]['balance'] ?? null;
$nativeBalance = $this->convertedBalances[$id]['native_balance'] ?? null;
// no order for some accounts:
if (!in_array(strtolower($accountType), ['liability', 'liabilities', 'asset'], true)) {
$order = null;
}
// balance difference
$diffStart = null;
$diffEnd = null;
$balanceDiff = null;
$nativeBalanceDiff = null;
if (null !== $this->parameters->get('start') && null !== $this->parameters->get('end')) {
$diffStart = $this->parameters->get('start')->toAtomString();
$diffEnd = $this->parameters->get('end')->toAtomString();
$balanceDiff = $this->balanceDifferences[$id]['balance'] ?? null;
$nativeBalanceDiff = $this->balanceDifferences[$id]['native_balance'] ?? null;
}
return [
'id' => (string)$account->id,
'id' => (string) $account->id,
'created_at' => $account->created_at->toAtomString(),
'updated_at' => $account->updated_at->toAtomString(),
'active' => $account->active,
'order' => $order,
'name' => $account->name,
'iban' => '' === (string)$account->iban ? null : $account->iban,
'iban' => '' === (string) $account->iban ? null : $account->iban,
'account_number' => $this->accountMeta[$id]['account_number'] ?? null,
'type' => strtolower($accountType),
'account_role' => $accountRole,
'currency_id' => (string)$currency->id,
'currency_id' => (string) $currency->id,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'native_currency_id' => (string)$this->default->id,
'native_currency_id' => (string) $this->default->id,
'native_currency_code' => $this->default->code,
'native_currency_symbol' => $this->default->symbol,
'native_currency_decimal_places' => $this->default->decimal_places,
@@ -218,6 +156,12 @@ class AccountTransformer extends AbstractTransformer
'native_current_balance' => $nativeBalance,
'current_balance_date' => $this->getDate()->endOfDay()->toAtomString(),
// balance difference
'balance_difference' => $balanceDiff,
'native_balance_difference' => $nativeBalanceDiff,
'balance_difference_start' => $diffStart,
'balance_difference_end' => $diffEnd,
// more meta
'last_activity' => array_key_exists($id, $this->lastActivity) ? $this->lastActivity[$id]->toAtomString() : null,
@@ -246,4 +190,179 @@ class AccountTransformer extends AbstractTransformer
],
];
}
private function getMetaBalances(Collection $accounts): void
{
try {
$this->convertedBalances = app('steam')->balancesByAccountsConverted($accounts, $this->getDate());
} catch (FireflyException $e) {
Log::error($e->getMessage());
}
}
private function getDefaultCurrency(): void
{
$this->default = app('amount')->getDefaultCurrency();
}
private function collectAccountMetaData(Collection $accounts): void
{
/** @var CurrencyRepositoryInterface $repository */
$repository = app(CurrencyRepositoryInterface::class);
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$metaFields = $accountRepository->getMetaValues($accounts, ['currency_id', 'account_role', 'account_number']);
$currencyIds = $metaFields->where('name', 'currency_id')->pluck('data')->toArray();
$currencies = $repository->getByIds($currencyIds);
foreach ($currencies as $currency) {
$id = $currency->id;
$this->currencies[$id] = $currency;
}
foreach ($metaFields as $entry) {
$id = $entry->account_id;
$this->accountMeta[$id][$entry->name] = $entry->data;
}
}
private function collectAccountTypes(Collection $accounts): void
{
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$accountTypes = $accountRepository->getAccountTypes($accounts);
/** @var AccountType $row */
foreach ($accountTypes as $row) {
$this->accountTypes[$row->id] = (string) config(sprintf('firefly.shortNamesByFullName.%s', $row->type));
}
}
private function getLastActivity(Collection $accounts): void
{
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$lastActivity = $accountRepository->getLastActivity($accounts);
foreach ($lastActivity as $row) {
$this->lastActivity[(int) $row['account_id']] = Carbon::parse($row['date_max'], config('app.timezone'));
}
}
private function sortAccounts(Collection $accounts): Collection
{
/** @var null|array $sort */
$sort = $this->parameters->get('sort');
if (null === $sort || 0 === count($sort)) {
return $accounts;
}
/**
* @var string $column
* @var string $direction
*/
foreach ($sort as $column => $direction) {
// account_number + iban
if ('iban' === $column) {
$accounts = $this->sortByIban($accounts, $direction);
}
if ('balance' === $column) {
$accounts = $this->sortByBalance($accounts, $direction);
}
if ('last_activity' === $column) {
$accounts = $this->sortByLastActivity($accounts, $direction);
}
if ('balance_difference' === $column) {
$accounts = $this->sortByBalanceDifference($accounts, $direction);
}
}
return $accounts;
}
private function sortByIban(Collection $accounts, string $direction): Collection
{
$meta = $this->accountMeta;
return $accounts->sort(function (Account $left, Account $right) use ($meta, $direction) {
$leftIban = trim(sprintf('%s%s', $left->iban, $meta[$left->id]['account_number'] ?? ''));
$rightIban = trim(sprintf('%s%s', $right->iban, $meta[$right->id]['account_number'] ?? ''));
if ('asc' === $direction) {
return strcasecmp($leftIban, $rightIban);
}
return strcasecmp($rightIban, $leftIban);
});
}
private function sortByBalance(Collection $accounts, string $direction): Collection
{
$balances = $this->convertedBalances;
return $accounts->sort(function (Account $left, Account $right) use ($balances, $direction) {
$leftBalance = (float) ($balances[$left->id]['native_balance'] ?? 0);
$rightBalance = (float) ($balances[$right->id]['native_balance'] ?? 0);
if ('asc' === $direction) {
return $leftBalance <=> $rightBalance;
}
return $rightBalance <=> $leftBalance;
});
}
private function sortByLastActivity(Collection $accounts, string $direction): Collection
{
$dates = $this->lastActivity;
return $accounts->sort(function (Account $left, Account $right) use ($dates, $direction) {
$leftDate = $dates[$left->id] ?? Carbon::create(1900, 1, 1, 0, 0, 0);
$rightDate = $dates[$right->id] ?? Carbon::create(1900, 1, 1, 0, 0, 0);
if ('asc' === $direction) {
return $leftDate->gt($rightDate) ? 1 : -1;
}
return $rightDate->gt($leftDate) ? 1 : -1;
});
}
private function getBalanceDifference(Collection $accounts, Carbon $start, Carbon $end): void
{
// collect balances, start and end for both native and converted.
// yes the b is usually used for boolean by idiots but here it's for balance.
$bStart = [];
$bEnd = [];
try {
$bStart = app('steam')->balancesByAccountsConverted($accounts, $start);
$bEnd = app('steam')->balancesByAccountsConverted($accounts, $end);
} catch (FireflyException $e) {
Log::error($e->getMessage());
}
/** @var Account $account */
foreach ($accounts as $account) {
$id = $account->id;
if (array_key_exists($id, $bStart) && array_key_exists($id, $bEnd)) {
$this->balanceDifferences[$id] = [
'balance' => bcsub($bEnd[$id]['balance'], $bStart[$id]['balance']),
'native_balance' => bcsub($bEnd[$id]['native_balance'], $bStart[$id]['native_balance']),
];
}
}
}
private function sortByBalanceDifference(Collection $accounts, string $direction): Collection
{
$balances = $this->balanceDifferences;
return $accounts->sort(function (Account $left, Account $right) use ($balances, $direction) {
$leftBalance = (float) ($balances[$left->id]['native_balance'] ?? 0);
$rightBalance = (float) ($balances[$right->id]['native_balance'] ?? 0);
if ('asc' === $direction) {
return $leftBalance <=> $rightBalance;
}
return $rightBalance <=> $leftBalance;
});
}
}

View File

@@ -381,10 +381,7 @@ class User extends Authenticatable
$dbRolesTitles = $dbRoles->pluck('title')->toArray();
/** @var Collection $groupMemberships */
$groupMemberships = $this->groupMemberships()
->whereIn('user_role_id', $dbRolesIds)
->where('user_group_id', $userGroup->id)->get()
;
$groupMemberships = $this->groupMemberships()->whereIn('user_role_id', $dbRolesIds)->where('user_group_id', $userGroup->id)->get();
if (0 === $groupMemberships->count()) {
app('log')->error(sprintf(
'User #%d "%s" does not have roles %s in user group #%d "%s"',

View File

@@ -101,6 +101,7 @@
"psr/log": "<4",
"ramsey/uuid": "^4.7",
"rcrowe/twigbridge": "^0.14",
"twig/twig": "3.8.0",
"spatie/laravel-html": "^3.2",
"spatie/laravel-ignition": "^2",
"spatie/period": "^2.4",

166
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "90c32aa5ed2c60408abbdd7edf8100db",
"content-hash": "be35d14f371e7f8bc178ae6f7a615e5c",
"packages": [
{
"name": "bacon/bacon-qr-code",
@@ -1670,16 +1670,16 @@
},
{
"name": "laravel/framework",
"version": "v11.3.1",
"version": "v11.4.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "3b87d0767e9cbddec46480d883010ba720e50dea"
"reference": "c1dc67c28811dc5be524a30b18f29ce62126716a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/3b87d0767e9cbddec46480d883010ba720e50dea",
"reference": "3b87d0767e9cbddec46480d883010ba720e50dea",
"url": "https://api.github.com/repos/laravel/framework/zipball/c1dc67c28811dc5be524a30b18f29ce62126716a",
"reference": "c1dc67c28811dc5be524a30b18f29ce62126716a",
"shasum": ""
},
"require": {
@@ -1698,7 +1698,7 @@
"fruitcake/php-cors": "^1.3",
"guzzlehttp/guzzle": "^7.8",
"guzzlehttp/uri-template": "^1.0",
"laravel/prompts": "^0.1.15",
"laravel/prompts": "^0.1.18",
"laravel/serializable-closure": "^1.3",
"league/commonmark": "^2.2.1",
"league/flysystem": "^3.8.0",
@@ -1871,20 +1871,20 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"time": "2024-04-10T15:13:49+00:00"
"time": "2024-04-16T14:38:51+00:00"
},
{
"name": "laravel/passport",
"version": "v12.0.3",
"version": "v12.1.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/passport.git",
"reference": "b33d6fce4268d997299547c6e714bada2dab9888"
"reference": "ff4742c71c58a4941b8738496ba96dabdf5e395b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/passport/zipball/b33d6fce4268d997299547c6e714bada2dab9888",
"reference": "b33d6fce4268d997299547c6e714bada2dab9888",
"url": "https://api.github.com/repos/laravel/passport/zipball/ff4742c71c58a4941b8738496ba96dabdf5e395b",
"reference": "ff4742c71c58a4941b8738496ba96dabdf5e395b",
"shasum": ""
},
"require": {
@@ -1947,20 +1947,20 @@
"issues": "https://github.com/laravel/passport/issues",
"source": "https://github.com/laravel/passport"
},
"time": "2024-04-05T15:09:10+00:00"
"time": "2024-04-15T18:49:04+00:00"
},
{
"name": "laravel/prompts",
"version": "v0.1.18",
"version": "v0.1.19",
"source": {
"type": "git",
"url": "https://github.com/laravel/prompts.git",
"reference": "3b5e6b03f1f1175574b5a32331d99c9819da9848"
"reference": "0ab75ac3434d9f610c5691758a6146a3d1940c18"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/prompts/zipball/3b5e6b03f1f1175574b5a32331d99c9819da9848",
"reference": "3b5e6b03f1f1175574b5a32331d99c9819da9848",
"url": "https://api.github.com/repos/laravel/prompts/zipball/0ab75ac3434d9f610c5691758a6146a3d1940c18",
"reference": "0ab75ac3434d9f610c5691758a6146a3d1940c18",
"shasum": ""
},
"require": {
@@ -2002,22 +2002,22 @@
],
"support": {
"issues": "https://github.com/laravel/prompts/issues",
"source": "https://github.com/laravel/prompts/tree/v0.1.18"
"source": "https://github.com/laravel/prompts/tree/v0.1.19"
},
"time": "2024-04-04T17:41:50+00:00"
"time": "2024-04-16T14:20:35+00:00"
},
{
"name": "laravel/sanctum",
"version": "v4.0.1",
"version": "v4.0.2",
"source": {
"type": "git",
"url": "https://github.com/laravel/sanctum.git",
"reference": "d1de99bf8d31199aaf93881561622489ab91ba58"
"reference": "9cfc0ce80cabad5334efff73ec856339e8ec1ac1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/sanctum/zipball/d1de99bf8d31199aaf93881561622489ab91ba58",
"reference": "d1de99bf8d31199aaf93881561622489ab91ba58",
"url": "https://api.github.com/repos/laravel/sanctum/zipball/9cfc0ce80cabad5334efff73ec856339e8ec1ac1",
"reference": "9cfc0ce80cabad5334efff73ec856339e8ec1ac1",
"shasum": ""
},
"require": {
@@ -2068,7 +2068,7 @@
"issues": "https://github.com/laravel/sanctum/issues",
"source": "https://github.com/laravel/sanctum"
},
"time": "2024-03-19T20:09:38+00:00"
"time": "2024-04-10T19:39:58+00:00"
},
{
"name": "laravel/serializable-closure",
@@ -2324,16 +2324,16 @@
},
{
"name": "lcobucci/jwt",
"version": "5.2.0",
"version": "5.3.0",
"source": {
"type": "git",
"url": "https://github.com/lcobucci/jwt.git",
"reference": "0ba88aed12c04bd2ed9924f500673f32b67a6211"
"reference": "08071d8d2c7f4b00222cc4b1fb6aa46990a80f83"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/lcobucci/jwt/zipball/0ba88aed12c04bd2ed9924f500673f32b67a6211",
"reference": "0ba88aed12c04bd2ed9924f500673f32b67a6211",
"url": "https://api.github.com/repos/lcobucci/jwt/zipball/08071d8d2c7f4b00222cc4b1fb6aa46990a80f83",
"reference": "08071d8d2c7f4b00222cc4b1fb6aa46990a80f83",
"shasum": ""
},
"require": {
@@ -2381,7 +2381,7 @@
],
"support": {
"issues": "https://github.com/lcobucci/jwt/issues",
"source": "https://github.com/lcobucci/jwt/tree/5.2.0"
"source": "https://github.com/lcobucci/jwt/tree/5.3.0"
},
"funding": [
{
@@ -2393,7 +2393,7 @@
"type": "patreon"
}
],
"time": "2023-11-20T21:17:42+00:00"
"time": "2024-04-11T23:07:54+00:00"
},
{
"name": "league/commonmark",
@@ -3265,16 +3265,16 @@
},
{
"name": "monolog/monolog",
"version": "3.5.0",
"version": "3.6.0",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/monolog.git",
"reference": "c915e2634718dbc8a4a15c61b0e62e7a44e14448"
"reference": "4b18b21a5527a3d5ffdac2fd35d3ab25a9597654"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/c915e2634718dbc8a4a15c61b0e62e7a44e14448",
"reference": "c915e2634718dbc8a4a15c61b0e62e7a44e14448",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/4b18b21a5527a3d5ffdac2fd35d3ab25a9597654",
"reference": "4b18b21a5527a3d5ffdac2fd35d3ab25a9597654",
"shasum": ""
},
"require": {
@@ -3297,7 +3297,7 @@
"phpstan/phpstan": "^1.9",
"phpstan/phpstan-deprecation-rules": "^1.0",
"phpstan/phpstan-strict-rules": "^1.4",
"phpunit/phpunit": "^10.1",
"phpunit/phpunit": "^10.5.17",
"predis/predis": "^1.1 || ^2",
"ruflin/elastica": "^7",
"symfony/mailer": "^5.4 || ^6",
@@ -3350,7 +3350,7 @@
],
"support": {
"issues": "https://github.com/Seldaek/monolog/issues",
"source": "https://github.com/Seldaek/monolog/tree/3.5.0"
"source": "https://github.com/Seldaek/monolog/tree/3.6.0"
},
"funding": [
{
@@ -3362,20 +3362,20 @@
"type": "tidelift"
}
],
"time": "2023-10-27T15:32:31+00:00"
"time": "2024-04-12T21:02:21+00:00"
},
{
"name": "nesbot/carbon",
"version": "3.2.4",
"version": "3.3.0",
"source": {
"type": "git",
"url": "https://github.com/briannesbitt/Carbon.git",
"reference": "82c28278c1c8f7b82dcdab25692237f052ffc8d8"
"reference": "7219739c4e01d4680c980545821733b6ed8ee880"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/82c28278c1c8f7b82dcdab25692237f052ffc8d8",
"reference": "82c28278c1c8f7b82dcdab25692237f052ffc8d8",
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/7219739c4e01d4680c980545821733b6ed8ee880",
"reference": "7219739c4e01d4680c980545821733b6ed8ee880",
"shasum": ""
},
"require": {
@@ -3468,7 +3468,7 @@
"type": "tidelift"
}
],
"time": "2024-04-05T09:58:10+00:00"
"time": "2024-04-18T16:35:06+00:00"
},
{
"name": "nette/schema",
@@ -5320,16 +5320,16 @@
},
{
"name": "spatie/ignition",
"version": "1.13.1",
"version": "1.13.2",
"source": {
"type": "git",
"url": "https://github.com/spatie/ignition.git",
"reference": "889bf1dfa59e161590f677728b47bf4a6893983b"
"reference": "952798e239d9969e4e694b124c2cc222798dbb28"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/ignition/zipball/889bf1dfa59e161590f677728b47bf4a6893983b",
"reference": "889bf1dfa59e161590f677728b47bf4a6893983b",
"url": "https://api.github.com/repos/spatie/ignition/zipball/952798e239d9969e4e694b124c2cc222798dbb28",
"reference": "952798e239d9969e4e694b124c2cc222798dbb28",
"shasum": ""
},
"require": {
@@ -5399,7 +5399,7 @@
"type": "github"
}
],
"time": "2024-03-29T14:03:47+00:00"
"time": "2024-04-16T08:49:17+00:00"
},
{
"name": "spatie/laravel-html",
@@ -5481,16 +5481,16 @@
},
{
"name": "spatie/laravel-ignition",
"version": "2.5.1",
"version": "2.5.2",
"source": {
"type": "git",
"url": "https://github.com/spatie/laravel-ignition.git",
"reference": "0c864b3cbd66ce67a2096c5f743e07ce8f1d6ab9"
"reference": "c93fcadcc4629775c839ac9a90916f07a660266f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/0c864b3cbd66ce67a2096c5f743e07ce8f1d6ab9",
"reference": "0c864b3cbd66ce67a2096c5f743e07ce8f1d6ab9",
"url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/c93fcadcc4629775c839ac9a90916f07a660266f",
"reference": "c93fcadcc4629775c839ac9a90916f07a660266f",
"shasum": ""
},
"require": {
@@ -5500,7 +5500,7 @@
"illuminate/support": "^10.0|^11.0",
"php": "^8.1",
"spatie/flare-client-php": "^1.3.5",
"spatie/ignition": "^1.13",
"spatie/ignition": "^1.13.2",
"symfony/console": "^6.2.3|^7.0",
"symfony/var-dumper": "^6.2.3|^7.0"
},
@@ -5569,7 +5569,7 @@
"type": "github"
}
],
"time": "2024-04-02T06:30:22+00:00"
"time": "2024-04-16T08:57:16+00:00"
},
{
"name": "spatie/period",
@@ -9507,16 +9507,16 @@
},
{
"name": "larastan/larastan",
"version": "v2.9.2",
"version": "v2.9.5",
"source": {
"type": "git",
"url": "https://github.com/larastan/larastan.git",
"reference": "a79b46b96060504b400890674b83f66aa7f5db6d"
"reference": "101f1a4470f87326f4d3995411d28679d8800abe"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/larastan/larastan/zipball/a79b46b96060504b400890674b83f66aa7f5db6d",
"reference": "a79b46b96060504b400890674b83f66aa7f5db6d",
"url": "https://api.github.com/repos/larastan/larastan/zipball/101f1a4470f87326f4d3995411d28679d8800abe",
"reference": "101f1a4470f87326f4d3995411d28679d8800abe",
"shasum": ""
},
"require": {
@@ -9529,15 +9529,15 @@
"illuminate/pipeline": "^9.52.16 || ^10.28.0 || ^11.0",
"illuminate/support": "^9.52.16 || ^10.28.0 || ^11.0",
"php": "^8.0.2",
"phpmyadmin/sql-parser": "^5.8.2",
"phpstan/phpstan": "^1.10.50"
"phpmyadmin/sql-parser": "^5.9.0",
"phpstan/phpstan": "^1.10.66"
},
"require-dev": {
"doctrine/coding-standard": "^12.0",
"nikic/php-parser": "^4.17.1",
"orchestra/canvas": "^7.11.1 || ^8.11.0 || ^9.0.0",
"orchestra/testbench": "^7.33.0 || ^8.13.0 || ^9.0.0",
"phpunit/phpunit": "^9.6.13 || ^10.5"
"nikic/php-parser": "^4.19.1",
"orchestra/canvas": "^7.11.1 || ^8.11.0 || ^9.0.2",
"orchestra/testbench": "^7.33.0 || ^8.13.0 || ^9.0.3",
"phpunit/phpunit": "^9.6.13 || ^10.5.16"
},
"suggest": {
"orchestra/testbench": "Using Larastan for analysing a package needs Testbench"
@@ -9585,7 +9585,7 @@
],
"support": {
"issues": "https://github.com/larastan/larastan/issues",
"source": "https://github.com/larastan/larastan/tree/v2.9.2"
"source": "https://github.com/larastan/larastan/tree/v2.9.5"
},
"funding": [
{
@@ -9605,7 +9605,7 @@
"type": "patreon"
}
],
"time": "2024-02-27T03:16:03+00:00"
"time": "2024-04-16T19:13:34+00:00"
},
{
"name": "maximebf/debugbar",
@@ -10285,16 +10285,16 @@
},
{
"name": "phpstan/phpstan",
"version": "1.10.66",
"version": "1.10.67",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "94779c987e4ebd620025d9e5fdd23323903950bd"
"reference": "16ddbe776f10da6a95ebd25de7c1dbed397dc493"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/94779c987e4ebd620025d9e5fdd23323903950bd",
"reference": "94779c987e4ebd620025d9e5fdd23323903950bd",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/16ddbe776f10da6a95ebd25de7c1dbed397dc493",
"reference": "16ddbe776f10da6a95ebd25de7c1dbed397dc493",
"shasum": ""
},
"require": {
@@ -10337,13 +10337,9 @@
{
"url": "https://github.com/phpstan",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan",
"type": "tidelift"
}
],
"time": "2024-03-28T16:17:31+00:00"
"time": "2024-04-16T07:22:02+00:00"
},
{
"name": "phpstan/phpstan-deprecation-rules",
@@ -10395,16 +10391,16 @@
},
{
"name": "phpstan/phpstan-strict-rules",
"version": "1.5.3",
"version": "1.5.5",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-strict-rules.git",
"reference": "568210bd301f94a0d4b1e5a0808c374c1b9cf11b"
"reference": "2e193a07651a6f4be3baa44ddb21d822681f5918"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/568210bd301f94a0d4b1e5a0808c374c1b9cf11b",
"reference": "568210bd301f94a0d4b1e5a0808c374c1b9cf11b",
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/2e193a07651a6f4be3baa44ddb21d822681f5918",
"reference": "2e193a07651a6f4be3baa44ddb21d822681f5918",
"shasum": ""
},
"require": {
@@ -10438,9 +10434,9 @@
"description": "Extra strict and opinionated rules for PHPStan",
"support": {
"issues": "https://github.com/phpstan/phpstan-strict-rules/issues",
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.5.3"
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.5.5"
},
"time": "2024-04-06T07:43:25+00:00"
"time": "2024-04-19T15:12:26+00:00"
},
{
"name": "phpunit/php-code-coverage",
@@ -10765,16 +10761,16 @@
},
{
"name": "phpunit/phpunit",
"version": "10.5.17",
"version": "10.5.19",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "c1f736a473d21957ead7e94fcc029f571895abf5"
"reference": "c726f0de022368f6ed103e452a765d3304a996a4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c1f736a473d21957ead7e94fcc029f571895abf5",
"reference": "c1f736a473d21957ead7e94fcc029f571895abf5",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c726f0de022368f6ed103e452a765d3304a996a4",
"reference": "c726f0de022368f6ed103e452a765d3304a996a4",
"shasum": ""
},
"require": {
@@ -10846,7 +10842,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.17"
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.19"
},
"funding": [
{
@@ -10862,7 +10858,7 @@
"type": "tidelift"
}
],
"time": "2024-04-05T04:39:01+00:00"
"time": "2024-04-17T14:06:18+00:00"
},
{
"name": "sebastian/cli-parser",

326
config/debugbar.php Normal file
View File

@@ -0,0 +1,326 @@
<?php
declare(strict_types=1);
return [
/*
|--------------------------------------------------------------------------
| Debugbar Settings
|--------------------------------------------------------------------------
|
| Debugbar is enabled by default, when debug is set to true in app.php.
| You can override the value by setting enable to true or false instead of null.
|
| You can provide an array of URI's that must be ignored (eg. 'api/*')
|
*/
'enabled' => env('DEBUGBAR_ENABLED', null),
'except' => [
'telescope*',
'horizon*',
],
/*
|--------------------------------------------------------------------------
| Storage settings
|--------------------------------------------------------------------------
|
| DebugBar stores data for session/ajax requests.
| You can disable this, so the debugbar stores data in headers/session,
| but this can cause problems with large data collectors.
| By default, file storage (in the storage folder) is used. Redis and PDO
| can also be used. For PDO, run the package migrations first.
|
| Warning: Enabling storage.open will allow everyone to access previous
| request, do not enable open storage in publicly available environments!
| Specify a callback if you want to limit based on IP or authentication.
| Leaving it to null will allow localhost only.
*/
'storage' => [
'enabled' => true,
'open' => env('DEBUGBAR_OPEN_STORAGE'), // bool/callback.
'driver' => 'file', // redis, file, pdo, socket, custom
'path' => storage_path('debugbar'), // For file driver
'connection' => null, // Leave null for default connection (Redis/PDO)
'provider' => '', // Instance of StorageInterface for custom driver
'hostname' => '127.0.0.1', // Hostname to use with the "socket" driver
'port' => 2304, // Port to use with the "socket" driver
],
/*
|--------------------------------------------------------------------------
| Editor
|--------------------------------------------------------------------------
|
| Choose your preferred editor to use when clicking file name.
|
| Supported: "phpstorm", "vscode", "vscode-insiders", "vscode-remote",
| "vscode-insiders-remote", "vscodium", "textmate", "emacs",
| "sublime", "atom", "nova", "macvim", "idea", "netbeans",
| "xdebug", "espresso"
|
*/
'editor' => env('DEBUGBAR_EDITOR') ?: env('IGNITION_EDITOR', 'phpstorm'),
/*
|--------------------------------------------------------------------------
| Remote Path Mapping
|--------------------------------------------------------------------------
|
| If you are using a remote dev server, like Laravel Homestead, Docker, or
| even a remote VPS, it will be necessary to specify your path mapping.
|
| Leaving one, or both of these, empty or null will not trigger the remote
| URL changes and Debugbar will treat your editor links as local files.
|
| "remote_sites_path" is an absolute base path for your sites or projects
| in Homestead, Vagrant, Docker, or another remote development server.
|
| Example value: "/home/vagrant/Code"
|
| "local_sites_path" is an absolute base path for your sites or projects
| on your local computer where your IDE or code editor is running on.
|
| Example values: "/Users/<name>/Code", "C:\Users\<name>\Documents\Code"
|
*/
'remote_sites_path' => env('DEBUGBAR_REMOTE_SITES_PATH'),
'local_sites_path' => env('DEBUGBAR_LOCAL_SITES_PATH', env('IGNITION_LOCAL_SITES_PATH')),
/*
|--------------------------------------------------------------------------
| Vendors
|--------------------------------------------------------------------------
|
| Vendor files are included by default, but can be set to false.
| This can also be set to 'js' or 'css', to only include javascript or css vendor files.
| Vendor files are for css: font-awesome (including fonts) and highlight.js (css files)
| and for js: jquery and highlight.js
| So if you want syntax highlighting, set it to true.
| jQuery is set to not conflict with existing jQuery scripts.
|
*/
'include_vendors' => true,
/*
|--------------------------------------------------------------------------
| Capture Ajax Requests
|--------------------------------------------------------------------------
|
| The Debugbar can capture Ajax requests and display them. If you don't want this (ie. because of errors),
| you can use this option to disable sending the data through the headers.
|
| Optionally, you can also send ServerTiming headers on ajax requests for the Chrome DevTools.
|
| Note for your request to be identified as ajax requests they must either send the header
| X-Requested-With with the value XMLHttpRequest (most JS libraries send this), or have application/json as a Accept header.
|
| By default `ajax_handler_auto_show` is set to true allowing ajax requests to be shown automatically in the Debugbar.
| Changing `ajax_handler_auto_show` to false will prevent the Debugbar from reloading.
*/
'capture_ajax' => true,
'add_ajax_timing' => false,
'ajax_handler_auto_show' => true,
'ajax_handler_enable_tab' => true,
/*
|--------------------------------------------------------------------------
| Custom Error Handler for Deprecated warnings
|--------------------------------------------------------------------------
|
| When enabled, the Debugbar shows deprecated warnings for Symfony components
| in the Messages tab.
|
*/
'error_handler' => false,
/*
|--------------------------------------------------------------------------
| Clockwork integration
|--------------------------------------------------------------------------
|
| The Debugbar can emulate the Clockwork headers, so you can use the Chrome
| Extension, without the server-side code. It uses Debugbar collectors instead.
|
*/
'clockwork' => false,
/*
|--------------------------------------------------------------------------
| DataCollectors
|--------------------------------------------------------------------------
|
| Enable/disable DataCollectors
|
*/
'collectors' => [
'phpinfo' => true, // Php version
'messages' => true, // Messages
'time' => true, // Time Datalogger
'memory' => true, // Memory usage
'exceptions' => true, // Exception displayer
'log' => true, // Logs from Monolog (merged in messages if enabled)
'db' => true, // Show database (PDO) queries and bindings
'views' => true, // Views with their data
'route' => true, // Current route information
'auth' => false, // Display Laravel authentication status
'gate' => true, // Display Laravel Gate checks
'session' => true, // Display session data
'symfony_request' => true, // Only one can be enabled..
'mail' => true, // Catch mail messages
'laravel' => false, // Laravel version and environment
'events' => false, // All events fired
'default_request' => false, // Regular or special Symfony request logger
'logs' => false, // Add the latest log messages
'files' => false, // Show the included files
'config' => false, // Display config settings
'cache' => false, // Display cache events
'models' => true, // Display models
'livewire' => true, // Display Livewire (when available)
'jobs' => false, // Display dispatched jobs
],
/*
|--------------------------------------------------------------------------
| Extra options
|--------------------------------------------------------------------------
|
| Configure some DataCollectors
|
*/
'options' => [
'time' => [
'memory_usage' => false, // Calculated by subtracting memory start and end, it may be inaccurate
],
'messages' => [
'trace' => true, // Trace the origin of the debug message
],
'memory' => [
'reset_peak' => false, // run memory_reset_peak_usage before collecting
'with_baseline' => false, // Set boot memory usage as memory peak baseline
'precision' => 0, // Memory rounding precision
],
'auth' => [
'show_name' => true, // Also show the users name/email in the debugbar
'show_guards' => true, // Show the guards that are used
],
'db' => [
'with_params' => true, // Render SQL with the parameters substituted
'backtrace' => true, // Use a backtrace to find the origin of the query in your files.
'backtrace_exclude_paths' => [], // Paths to exclude from backtrace. (in addition to defaults)
'timeline' => false, // Add the queries to the timeline
'duration_background' => true, // Show shaded background on each query relative to how long it took to execute.
'explain' => [ // Show EXPLAIN output on queries
'enabled' => false,
'types' => ['SELECT'], // Deprecated setting, is always only SELECT
],
'hints' => false, // Show hints for common mistakes
'show_copy' => false, // Show copy button next to the query,
'slow_threshold' => false, // Only track queries that last longer than this time in ms
'memory_usage' => false, // Show queries memory usage
'soft_limit' => 100, // After the soft limit, no parameters/backtrace are captured
'hard_limit' => 500, // After the hard limit, queries are ignored
],
'mail' => [
'timeline' => false, // Add mails to the timeline
'show_body' => true,
],
'views' => [
'timeline' => false, // Add the views to the timeline (Experimental)
'data' => false, // true for all data, 'keys' for only names, false for no parameters.
'group' => 50, // Group duplicate views. Pass value to auto-group, or true/false to force
'exclude_paths' => [ // Add the paths which you don't want to appear in the views
'vendor/filament', // Exclude Filament components by default
],
],
'route' => [
'label' => true, // show complete route on bar
],
'session' => [
'hiddens' => [], // hides sensitive values using array paths
],
'symfony_request' => [
'hiddens' => [], // hides sensitive values using array paths, example: request_request.password
],
'events' => [
'data' => false, // collect events data, listeners
],
'logs' => [
'file' => null,
],
'cache' => [
'values' => true, // collect cache values
],
],
/*
|--------------------------------------------------------------------------
| Inject Debugbar in Response
|--------------------------------------------------------------------------
|
| Usually, the debugbar is added just before </body>, by listening to the
| Response after the App is done. If you disable this, you have to add them
| in your template yourself. See http://phpdebugbar.com/docs/rendering.html
|
*/
'inject' => true,
/*
|--------------------------------------------------------------------------
| DebugBar route prefix
|--------------------------------------------------------------------------
|
| Sometimes you want to set route prefix to be used by DebugBar to load
| its resources from. Usually the need comes from misconfigured web server or
| from trying to overcome bugs like this: http://trac.nginx.org/nginx/ticket/97
|
*/
'route_prefix' => '_debugbar',
/*
|--------------------------------------------------------------------------
| DebugBar route middleware
|--------------------------------------------------------------------------
|
| Additional middleware to run on the Debugbar routes
*/
'route_middleware' => [],
/*
|--------------------------------------------------------------------------
| DebugBar route domain
|--------------------------------------------------------------------------
|
| By default DebugBar route served from the same domain that request served.
| To override default domain, specify it as a non-empty value.
*/
'route_domain' => null,
/*
|--------------------------------------------------------------------------
| DebugBar theme
|--------------------------------------------------------------------------
|
| Switches between light and dark theme. If set to auto it will respect system preferences
| Possible values: auto, light, dark
*/
'theme' => env('DEBUGBAR_THEME', 'auto'),
/*
|--------------------------------------------------------------------------
| Backtrace stack limit
|--------------------------------------------------------------------------
|
| By default, the DebugBar limits the number of frames returned by the 'debug_backtrace()' function.
| If you need larger stacktraces, you can increase this number. Setting it to 0 will result in no limit.
*/
'debug_backtrace_limit' => 50,
];

View File

@@ -117,8 +117,8 @@ return [
'expression_engine' => false,
// see cer.php for exchange rates feature flag.
],
'version' => 'develop/2024-04-11',
'api_version' => '2.0.13',
'version' => 'develop/2024-04-22',
'api_version' => '2.0.14',
'db_version' => 24,
// generic settings
@@ -924,7 +924,7 @@ return [
'sorting' => [
'allowed' => [
'transactions' => ['description', 'amount'],
'accounts' => ['name', 'active', 'iban', 'balance', 'last_activity'],
'accounts' => ['name', 'active', 'iban', 'balance', 'last_activity', 'balance_difference'],
],
],
];

293
package-lock.json generated
View File

@@ -2402,9 +2402,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.14.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.1.tgz",
"integrity": "sha512-fH8/o8nSUek8ceQnT7K4EQbSiV7jgkHq81m9lWZFIXjJ7lJzpWXbQFpT/Zh6OZYnpFykvzC3fbEvEAFZu03dPA==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.16.1.tgz",
"integrity": "sha512-92/y0TqNLRYOTXpm6Z7mnpvKAG9P7qmK7yJeRJSdzElNCUnsgbpAsGqerUboYRIQKzgfq4pWu9xVkgpWLfmNsw==",
"cpu": [
"arm"
],
@@ -2415,9 +2415,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.14.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.1.tgz",
"integrity": "sha512-Y/9OHLjzkunF+KGEoJr3heiD5X9OLa8sbT1lm0NYeKyaM3oMhhQFvPB0bNZYJwlq93j8Z6wSxh9+cyKQaxS7PQ==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.16.1.tgz",
"integrity": "sha512-ttWB6ZCfRLuDIUiE0yiu5gcqOsYjA5F7kEV1ggHMj20FwLZ8A1FMeahZJFl/pnOmcnD2QL0z4AcDuo27utGU8A==",
"cpu": [
"arm64"
],
@@ -2428,9 +2428,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.14.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.1.tgz",
"integrity": "sha512-+kecg3FY84WadgcuSVm6llrABOdQAEbNdnpi5X3UwWiFVhZIZvKgGrF7kmLguvxHNQy+UuRV66cLVl3S+Rkt+Q==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.16.1.tgz",
"integrity": "sha512-QLDvPLetbqjHojTGFw9+nuSP3YY/iz2k1cep6crYlr97sS+ZJ0W43b8Z0zC00+lnFZj6JSNxiA4DjboNQMuh1A==",
"cpu": [
"arm64"
],
@@ -2441,9 +2441,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.14.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.1.tgz",
"integrity": "sha512-2pYRzEjVqq2TB/UNv47BV/8vQiXkFGVmPFwJb+1E0IFFZbIX8/jo1olxqqMbo6xCXf8kabANhp5bzCij2tFLUA==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.16.1.tgz",
"integrity": "sha512-TAUK/D8khRrRIa1KwRzo8JNKk3tcqaeXWdtsiLgA8zmACWwlWLjPCJ4DULGHQrMkeBjp1Cd3Yuwx04lZgFx5Vg==",
"cpu": [
"x64"
],
@@ -2454,9 +2454,22 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.14.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.1.tgz",
"integrity": "sha512-mS6wQ6Do6/wmrF9aTFVpIJ3/IDXhg1EZcQFYHZLHqw6AzMBjTHWnCG35HxSqUNphh0EHqSM6wRTT8HsL1C0x5g==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.16.1.tgz",
"integrity": "sha512-KO+WGZjrh6zyFTD1alIFkfdtxf8B4BC+hqd3kBZHscPLvE5FR/6QKsyuCT0JlERxxYBSUKNUQ/UHyX5uwO1x2A==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.16.1.tgz",
"integrity": "sha512-NqxbllzIB1WoAo4ThUXVtd21iiM5IHMTTXmXySKBLVcZvkU0HIZmatlP7hLzb5yQubcmdIeWmncd2NdsjocEiw==",
"cpu": [
"arm"
],
@@ -2467,9 +2480,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.14.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.1.tgz",
"integrity": "sha512-p9rGKYkHdFMzhckOTFubfxgyIO1vw//7IIjBBRVzyZebWlzRLeNhqxuSaZ7kCEKVkm/kuC9fVRW9HkC/zNRG2w==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.16.1.tgz",
"integrity": "sha512-snma5NvV8y7IECQ5rq0sr0f3UUu+92NVmG/913JXJMcXo84h9ak9TA5UI9Cl2XRM9j3m37QwDBtEYnJzRkSmxA==",
"cpu": [
"arm64"
],
@@ -2480,9 +2493,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.14.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.1.tgz",
"integrity": "sha512-nDY6Yz5xS/Y4M2i9JLQd3Rofh5OR8Bn8qe3Mv/qCVpHFlwtZSBYSPaU4mrGazWkXrdQ98GB//H0BirGR/SKFSw==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.16.1.tgz",
"integrity": "sha512-KOvqGprlD84ueivhCi2flvcUwDRD20mAsE3vxQNVEI2Di9tnPGAfEu6UcrSPZbM+jG2w1oSr43hrPo0RNg6GGg==",
"cpu": [
"arm64"
],
@@ -2493,11 +2506,11 @@
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.14.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.1.tgz",
"integrity": "sha512-im7HE4VBL+aDswvcmfx88Mp1soqL9OBsdDBU8NqDEYtkri0qV0THhQsvZtZeNNlLeCUQ16PZyv7cqutjDF35qw==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.16.1.tgz",
"integrity": "sha512-/gsNwtiGLqYwN4vP+EIdUC6Q6LTlpupWqokqIndvZcjn9ig/5P01WyaYCU2wvfL/2Z82jp5kX8c1mDBOvCP3zg==",
"cpu": [
"ppc64le"
"ppc64"
],
"dev": true,
"optional": true,
@@ -2506,9 +2519,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.14.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.1.tgz",
"integrity": "sha512-RWdiHuAxWmzPJgaHJdpvUUlDz8sdQz4P2uv367T2JocdDa98iRw2UjIJ4QxSyt077mXZT2X6pKfT2iYtVEvOFw==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.16.1.tgz",
"integrity": "sha512-uU8zuGkQfGqfD9w6VRJZI4IuG4JIfNxxJgEmLMAmPVHREKGsxFVfgHy5c6CexQF2vOfgjB33OsET3Vdn2lln9A==",
"cpu": [
"riscv64"
],
@@ -2519,9 +2532,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.14.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.1.tgz",
"integrity": "sha512-VMgaGQ5zRX6ZqV/fas65/sUGc9cPmsntq2FiGmayW9KMNfWVG/j0BAqImvU4KTeOOgYSf1F+k6at1UfNONuNjA==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.16.1.tgz",
"integrity": "sha512-lsjLtDgtcGFEuBP6yrXwkRN5/wKlvUZtfbKZZu0yaoNpiBL4epgnO21osAALIspVRnl4qZgyLFd8xjCYYWgwfw==",
"cpu": [
"s390x"
],
@@ -2532,9 +2545,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.14.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.1.tgz",
"integrity": "sha512-9Q7DGjZN+hTdJomaQ3Iub4m6VPu1r94bmK2z3UeWP3dGUecRC54tmVu9vKHTm1bOt3ASoYtEz6JSRLFzrysKlA==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.16.1.tgz",
"integrity": "sha512-N2ZizKhUryqqrMfdCnjhJhZRgv61C6gK+hwVtCIKC8ts8J+go+vqENnGexwg21nHIOvLN5mBM8a7DI2vlyIOPg==",
"cpu": [
"x64"
],
@@ -2545,9 +2558,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.14.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.1.tgz",
"integrity": "sha512-JNEG/Ti55413SsreTguSx0LOVKX902OfXIKVg+TCXO6Gjans/k9O6ww9q3oLGjNDaTLxM+IHFMeXy/0RXL5R/g==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.16.1.tgz",
"integrity": "sha512-5ICeMxqg66FrOA2AbnBQ2TJVxfvZsKLxmof0ibvPLaYtbsJqnTUtJOofgWb46Gjd4uZcA4rdsp4JCxegzQPqCg==",
"cpu": [
"x64"
],
@@ -2558,9 +2571,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.14.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.1.tgz",
"integrity": "sha512-ryS22I9y0mumlLNwDFYZRDFLwWh3aKaC72CWjFcFvxK0U6v/mOkM5Up1bTbCRAhv3kEIwW2ajROegCIQViUCeA==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.16.1.tgz",
"integrity": "sha512-1vIP6Ce02L+qWD7uZYRiFiuAJo3m9kARatWmFSnss0gZnVj2Id7OPUU9gm49JPGasgcR3xMqiH3fqBJ8t00yVg==",
"cpu": [
"arm64"
],
@@ -2571,9 +2584,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.14.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.1.tgz",
"integrity": "sha512-TdloItiGk+T0mTxKx7Hp279xy30LspMso+GzQvV2maYePMAWdmrzqSNZhUpPj3CGw12aGj57I026PgLCTu8CGg==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.16.1.tgz",
"integrity": "sha512-Y3M92DcVsT6LoP+wrKpoUWPaazaP1fzbNkp0a0ZSj5Y//+pQVfVe/tQdsYQQy7dwXR30ZfALUIc9PCh9Izir6w==",
"cpu": [
"ia32"
],
@@ -2584,9 +2597,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.14.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.1.tgz",
"integrity": "sha512-wQGI+LY/Py20zdUPq+XCem7JcPOyzIJBm3dli+56DJsQOHbnXZFEwgmnC6el1TPAfC8lBT3m+z69RmLykNUbew==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.16.1.tgz",
"integrity": "sha512-x0fvpHMuF7fK5r8oZxSi8VYXkrVmRgubXpO/wcf15Lk3xZ4Jvvh5oG+u7Su1776A7XzVKZhD2eRc4t7H50gL3w==",
"cpu": [
"x64"
],
@@ -2695,9 +2708,9 @@
}
},
"node_modules/@types/eslint": {
"version": "8.56.7",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.7.tgz",
"integrity": "sha512-SjDvI/x3zsZnOkYZ3lCt9lOZWZLB2jIlNKz+LBgCtDurK0JZcwucxYHn1w2BJkD34dgX9Tjnak0txtq4WTggEA==",
"version": "8.56.10",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz",
"integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==",
"dev": true,
"dependencies": {
"@types/estree": "*",
@@ -2858,9 +2871,9 @@
"dev": true
},
"node_modules/@types/qs": {
"version": "6.9.14",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.14.tgz",
"integrity": "sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==",
"version": "6.9.15",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz",
"integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==",
"dev": true
},
"node_modules/@types/range-parser": {
@@ -2930,53 +2943,53 @@
}
},
"node_modules/@vue/compiler-core": {
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.21.tgz",
"integrity": "sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==",
"version": "3.4.23",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.23.tgz",
"integrity": "sha512-HAFmuVEwNqNdmk+w4VCQ2pkLk1Vw4XYiiyxEp3z/xvl14aLTUBw2OfVH3vBcx+FtGsynQLkkhK410Nah1N2yyQ==",
"dev": true,
"dependencies": {
"@babel/parser": "^7.23.9",
"@vue/shared": "3.4.21",
"@babel/parser": "^7.24.1",
"@vue/shared": "3.4.23",
"entities": "^4.5.0",
"estree-walker": "^2.0.2",
"source-map-js": "^1.0.2"
"source-map-js": "^1.2.0"
}
},
"node_modules/@vue/compiler-dom": {
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.21.tgz",
"integrity": "sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==",
"version": "3.4.23",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.23.tgz",
"integrity": "sha512-t0b9WSTnCRrzsBGrDd1LNR5HGzYTr7LX3z6nNBG+KGvZLqrT0mY6NsMzOqlVMBKKXKVuusbbB5aOOFgTY+senw==",
"dev": true,
"dependencies": {
"@vue/compiler-core": "3.4.21",
"@vue/shared": "3.4.21"
"@vue/compiler-core": "3.4.23",
"@vue/shared": "3.4.23"
}
},
"node_modules/@vue/compiler-sfc": {
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.21.tgz",
"integrity": "sha512-me7epoTxYlY+2CUM7hy9PCDdpMPfIwrOvAXud2Upk10g4YLv9UBW7kL798TvMeDhPthkZ0CONNrK2GoeI1ODiQ==",
"version": "3.4.23",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.23.tgz",
"integrity": "sha512-fSDTKTfzaRX1kNAUiaj8JB4AokikzStWgHooMhaxyjZerw624L+IAP/fvI4ZwMpwIh8f08PVzEnu4rg8/Npssw==",
"dev": true,
"dependencies": {
"@babel/parser": "^7.23.9",
"@vue/compiler-core": "3.4.21",
"@vue/compiler-dom": "3.4.21",
"@vue/compiler-ssr": "3.4.21",
"@vue/shared": "3.4.21",
"@babel/parser": "^7.24.1",
"@vue/compiler-core": "3.4.23",
"@vue/compiler-dom": "3.4.23",
"@vue/compiler-ssr": "3.4.23",
"@vue/shared": "3.4.23",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.7",
"postcss": "^8.4.35",
"source-map-js": "^1.0.2"
"magic-string": "^0.30.8",
"postcss": "^8.4.38",
"source-map-js": "^1.2.0"
}
},
"node_modules/@vue/compiler-ssr": {
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.21.tgz",
"integrity": "sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q==",
"version": "3.4.23",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.23.tgz",
"integrity": "sha512-hb6Uj2cYs+tfqz71Wj6h3E5t6OKvb4MVcM2Nl5i/z1nv1gjEhw+zYaNOV+Xwn+SSN/VZM0DgANw5TuJfxfezPg==",
"dev": true,
"dependencies": {
"@vue/compiler-dom": "3.4.21",
"@vue/shared": "3.4.21"
"@vue/compiler-dom": "3.4.23",
"@vue/shared": "3.4.23"
}
},
"node_modules/@vue/component-compiler-utils": {
@@ -3051,9 +3064,9 @@
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA=="
},
"node_modules/@vue/shared": {
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz",
"integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==",
"version": "3.4.23",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.23.tgz",
"integrity": "sha512-wBQ0gvf+SMwsCQOyusNw/GoXPV47WGd1xB5A1Pgzy0sQ3Bi5r5xm3n+92y3gCnB3MWqnRDdvfkRGxhKtbBRNgg==",
"dev": true
},
"node_modules/@webassemblyjs/ast": {
@@ -3360,9 +3373,9 @@
}
},
"node_modules/alpinejs": {
"version": "3.13.8",
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.8.tgz",
"integrity": "sha512-XolbBJryCndomtaHd/KHQjQeD/L72FJxy/YhLLFD4Lr7zzGcpcbg+UgXteMR2pYg1KhRUr6V4O3GfN1zJAmRWw==",
"version": "3.13.9",
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.9.tgz",
"integrity": "sha512-BTEJ3fcUpqKlB+aFYSWGVEp8IP3vA96x7wUaNSdb5SJxV5i7s+/Bduxy9D6TokcqpW5MygsnG1eBEFfr4AnQSw==",
"dependencies": {
"@vue/reactivity": "~3.1.1"
}
@@ -3772,9 +3785,9 @@
"dev": true
},
"node_modules/bootstrap5-autocomplete": {
"version": "1.1.25",
"resolved": "https://registry.npmjs.org/bootstrap5-autocomplete/-/bootstrap5-autocomplete-1.1.25.tgz",
"integrity": "sha512-6Z7vzlVBJduPUi7U1MPRBzoXmJf8ob9tGcUNxxos6qU1bbjPX7Li30r1Dhtk55hSBfPmVuN7p6zahF7G38xtWA=="
"version": "1.1.26",
"resolved": "https://registry.npmjs.org/bootstrap5-autocomplete/-/bootstrap5-autocomplete-1.1.26.tgz",
"integrity": "sha512-9BkwEVcrEXdVl1M7QmwTyG29lXs2S/xtZcrsxivhRLcMe0UJP2q9qxpRs8tQDvR9u6Yb8k3DY3h0XwlHjGoFtg=="
},
"node_modules/bootstrap5-tags": {
"version": "1.7.1",
@@ -4007,9 +4020,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001608",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001608.tgz",
"integrity": "sha512-cjUJTQkk9fQlJR2s4HMuPMvTiRggl0rAVMtthQuyOlDWuqHXqN8azLq+pi8B2TjwKJ32diHjUqRIKeFX4z1FoA==",
"version": "1.0.30001612",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001612.tgz",
"integrity": "sha512-lFgnZ07UhaCcsSZgWW0K5j4e69dK1u/ltrL9lTUiFOwNHs12S3UMIEYgBV0Z6C6hRDev7iRnMzzYmKabYdXF9g==",
"dev": true,
"funding": [
{
@@ -4416,9 +4429,9 @@
"dev": true
},
"node_modules/core-js-compat": {
"version": "3.36.1",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.1.tgz",
"integrity": "sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA==",
"version": "3.37.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.0.tgz",
"integrity": "sha512-vYq4L+T8aS5UuFg4UwDhc7YNRWVeVZwltad9C/jV3R2LgVOpS9BDr7l/WL6BN0dbV3k1XejPTHqqEzJgsa0frA==",
"dev": true,
"dependencies": {
"browserslist": "^4.23.0"
@@ -5082,9 +5095,9 @@
"dev": true
},
"node_modules/electron-to-chromium": {
"version": "1.4.733",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.733.tgz",
"integrity": "sha512-gUI9nhI2iBGF0OaYYLKOaOtliFMl+Bt1rY7VmEjwxOxqoYLub/D9xmduPEhbw2imE6gYkJKhIE5it+KE2ulVxQ==",
"version": "1.4.745",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.745.tgz",
"integrity": "sha512-tRbzkaRI5gbUn5DEvF0dV4TQbMZ5CLkWeTAXmpC9IrYT+GE+x76i9p+o3RJ5l9XmdQlI1pPhVtE9uNcJJ0G0EA==",
"dev": true
},
"node_modules/elliptic": {
@@ -6262,9 +6275,9 @@
}
},
"node_modules/i18next": {
"version": "23.11.1",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.11.1.tgz",
"integrity": "sha512-mXw4A24BiPZKRsbb9ewgSvjYd6fxFCNwJyfK6nYfSTIAX2GkCWcb598m3DFkDZmqADatvuASrKo6qwORz3VwTQ==",
"version": "23.11.2",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.11.2.tgz",
"integrity": "sha512-qMBm7+qT8jdpmmDw/kQD16VpmkL9BdL+XNAK5MNbNFaf1iQQq35ZbPrSlqmnNPOSUY4m342+c0t0evinF5l7sA==",
"funding": [
{
"type": "individual",
@@ -6292,9 +6305,9 @@
}
},
"node_modules/i18next-http-backend": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.5.0.tgz",
"integrity": "sha512-Z/aQsGZk1gSxt2/DztXk92DuDD20J+rNudT7ZCdTrNOiK8uQppfvdjq9+DFQfpAnFPn3VZS+KQIr1S/W1KxhpQ==",
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.5.1.tgz",
"integrity": "sha512-+rNX1tghdVxdfjfPt0bI1sNg5ahGW9kA7OboG7b4t03Fp69NdDlRIze6yXhIbN8rbHxJ8IP4dzRm/okZ15lkQg==",
"dependencies": {
"cross-fetch": "4.0.0"
}
@@ -6485,9 +6498,9 @@
}
},
"node_modules/ipaddr.js": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz",
"integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz",
"integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==",
"dev": true,
"engines": {
"node": ">= 10"
@@ -7058,15 +7071,12 @@
}
},
"node_modules/magic-string": {
"version": "0.30.9",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.9.tgz",
"integrity": "sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw==",
"version": "0.30.10",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz",
"integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==",
"dev": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
},
"engines": {
"node": ">=12"
}
},
"node_modules/make-dir": {
@@ -8631,9 +8641,9 @@
"dev": true
},
"node_modules/qs": {
"version": "6.12.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.12.0.tgz",
"integrity": "sha512-trVZiI6RMOkO476zLGaBIzszOdFPnCCXHPG9kn0yuS1uz6xdVxPfZdB3vUig9pxPFDM9BRAgz/YUIVQ1/vuiUg==",
"version": "6.12.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz",
"integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==",
"dev": true,
"dependencies": {
"side-channel": "^1.0.6"
@@ -8981,9 +8991,9 @@
}
},
"node_modules/rollup": {
"version": "4.14.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.1.tgz",
"integrity": "sha512-4LnHSdd3QK2pa1J6dFbfm1HN0D7vSK/ZuZTsdyUAlA6Rr1yTouUTL13HaDOGJVgby461AhrNGBS7sCGXXtT+SA==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.16.1.tgz",
"integrity": "sha512-5CaD3MPDlPKfhqzRvWXK96G6ELJfPZNb3LHiZxTHgDdC6jvwfGz2E8nY+9g1ONk4ttHsK1WaFP19Js4PSr1E3g==",
"dev": true,
"dependencies": {
"@types/estree": "1.0.5"
@@ -8996,21 +9006,22 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.14.1",
"@rollup/rollup-android-arm64": "4.14.1",
"@rollup/rollup-darwin-arm64": "4.14.1",
"@rollup/rollup-darwin-x64": "4.14.1",
"@rollup/rollup-linux-arm-gnueabihf": "4.14.1",
"@rollup/rollup-linux-arm64-gnu": "4.14.1",
"@rollup/rollup-linux-arm64-musl": "4.14.1",
"@rollup/rollup-linux-powerpc64le-gnu": "4.14.1",
"@rollup/rollup-linux-riscv64-gnu": "4.14.1",
"@rollup/rollup-linux-s390x-gnu": "4.14.1",
"@rollup/rollup-linux-x64-gnu": "4.14.1",
"@rollup/rollup-linux-x64-musl": "4.14.1",
"@rollup/rollup-win32-arm64-msvc": "4.14.1",
"@rollup/rollup-win32-ia32-msvc": "4.14.1",
"@rollup/rollup-win32-x64-msvc": "4.14.1",
"@rollup/rollup-android-arm-eabi": "4.16.1",
"@rollup/rollup-android-arm64": "4.16.1",
"@rollup/rollup-darwin-arm64": "4.16.1",
"@rollup/rollup-darwin-x64": "4.16.1",
"@rollup/rollup-linux-arm-gnueabihf": "4.16.1",
"@rollup/rollup-linux-arm-musleabihf": "4.16.1",
"@rollup/rollup-linux-arm64-gnu": "4.16.1",
"@rollup/rollup-linux-arm64-musl": "4.16.1",
"@rollup/rollup-linux-powerpc64le-gnu": "4.16.1",
"@rollup/rollup-linux-riscv64-gnu": "4.16.1",
"@rollup/rollup-linux-s390x-gnu": "4.16.1",
"@rollup/rollup-linux-x64-gnu": "4.16.1",
"@rollup/rollup-linux-x64-musl": "4.16.1",
"@rollup/rollup-win32-arm64-msvc": "4.16.1",
"@rollup/rollup-win32-ia32-msvc": "4.16.1",
"@rollup/rollup-win32-x64-msvc": "4.16.1",
"fsevents": "~2.3.2"
}
},
@@ -9063,9 +9074,9 @@
"dev": true
},
"node_modules/sass": {
"version": "1.74.1",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.74.1.tgz",
"integrity": "sha512-w0Z9p/rWZWelb88ISOLyvqTWGmtmu2QJICqDBGyNnfG4OUnPX9BBjjYIXUpXCMOOg5MQWNpqzt876la1fsTvUA==",
"version": "1.75.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.75.0.tgz",
"integrity": "sha512-ShMYi3WkrDWxExyxSZPst4/okE9ts46xZmJDSawJQrnte7M1V9fScVB+uNXOVKRBt0PggHOwoZcn8mYX4trnBw==",
"dev": true,
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
@@ -10087,9 +10098,9 @@
}
},
"node_modules/vite": {
"version": "5.2.8",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.2.8.tgz",
"integrity": "sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==",
"version": "5.2.10",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.2.10.tgz",
"integrity": "sha512-PAzgUZbP7msvQvqdSD+ErD5qGnSFiGOoWmV5yAKUEI0kdhjbH6nMWVyZQC/hSc4aXwc0oJ9aEdIiF9Oje0JFCw==",
"dev": true,
"dependencies": {
"esbuild": "^0.20.1",
@@ -10937,7 +10948,7 @@
"chartjs-adapter-date-fns": "^3.0.0",
"chartjs-chart-sankey": "^0.12.0",
"date-fns": "^3.6.0",
"i18next": "^23.10.1",
"i18next": "^23.11.2",
"i18next-chained-backend": "^4.6.2",
"i18next-http-backend": "^2.4.2",
"i18next-localstorage-backend": "^4.2.0",
@@ -10948,7 +10959,7 @@
"axios": "^1.6.8",
"laravel-vite-plugin": "^1.0.2",
"patch-package": "^8.0.0",
"sass": "^1.72.0",
"sass": "^1.75.0",
"vite": "^5",
"vite-plugin-manifest-sri": "^0.2.0"
}

View File

@@ -22,95 +22,10 @@ var count = 0;
$(document).ready(function () {
updateListButtons();
addSort();
$('.clone-transaction').click(cloneTransaction);
$('.clone-transaction-and-edit').click(cloneTransactionAndEdit);
});
var fixHelper = function (e, tr) {
"use strict";
var $originals = tr.children();
var $helper = tr.clone();
$helper.children().each(function (index) {
// Set helper cell sizes to match the original sizes
$(this).width($originals.eq(index).width());
});
return $helper;
};
/**
*
*/
function addSort() {
if (typeof $(".table-sortable>tbody").sortable !== "undefined") {
$('.table-sortable>tbody').sortable(
{
items: "tr:not(.unsortable)",
handle: '.object-handle',
stop: sortStop,
start: function (event, ui) {
// Build a placeholder cell that spans all the cells in the row
var cellCount = 0;
$('td, th', ui.helper).each(function () {
// For each TD or TH try and get it's colspan attribute, and add that or 1 to the total
var colspan = 1;
var colspanAttr = $(this).attr('colspan');
if (colspanAttr > 1) {
colspan = colspanAttr;
}
cellCount += colspan;
});
// Add the placeholder UI - note that this is the item's content, so TD rather than TR
ui.placeholder.html('<td colspan="' + cellCount + '">&nbsp;</td>');
}
}
);
}
}
/**
*
* @param event
* @param ui
* @returns {boolean|undefined}
*/
function sortStop(event, ui) {
"use strict";
var current = $(ui.item);
var thisDate = current.data('date');
var originalBG = current.css('backgroundColor');
if (current.prev().data('date') !== thisDate && current.next().data('date') !== thisDate) {
// animate something with color:
current.animate({backgroundColor: "#d9534f"}, 200, function () {
$(this).animate({backgroundColor: originalBG}, 200);
return undefined;
});
return false;
}
//return false;
// do update
var list = $('tr[data-date="' + thisDate + '"]');
var submit = [];
$.each(list, function (i, v) {
var row = $(v);
var id = row.data('id');
submit.push(id);
});
// do extra animation when done?
$.post('transactions/reorder', {items: submit, date: thisDate, _token: token});
current.animate({backgroundColor: "#5cb85c"}, 200, function () {
$(this).animate({backgroundColor: originalBG}, 200);
return undefined;
});
return undefined;
}
/**
*

View File

@@ -103,6 +103,12 @@
.skin-firefly-iii .btn-info {
color: #fff;
}
.skin-firefly-iii .btn-danger {
color: #fff;
}
.skin-firefly-iii .btn-warning {
color: #fff;
}
.skin-firefly-iii .btn-default {
background-color: #55606a;
color: #bec5cb;
@@ -251,6 +257,11 @@
}
.skin-firefly-iii .table > thead > tr > th,
.skin-firefly-iii .table > tbody > tr > th,
.skin-firefly-iii .table > tfoot > tr > th {
border-bottom: 2px #c9d1d9 solid;
}
.skin-firefly-iii .table > thead > tr > th,
.skin-firefly-iii .table > tbody > tr > th,
.skin-firefly-iii .table > tfoot > tr > th,
.skin-firefly-iii .table > thead > tr > td,
.skin-firefly-iii .table > tbody > tr > td,
@@ -480,6 +491,7 @@
.skin-firefly-iii .nav-tabs-custom > .nav-tabs > li.active > a {
border-left-color: #353c42;
border-right-color: #353c42;
color: #bec5cb;
}
.skin-firefly-iii .nav-tabs-custom > .nav-tabs.pull-right > li:first-of-type.active > a {
border-left-color: #353c42;
@@ -511,7 +523,7 @@
background-color: #272c30;
}
.skin-firefly-iii .input-group .input-group-addon {
border-right: 1px solid #272c30;
border: 1px solid #272c30;
}
.skin-firefly-iii .form-control {
border-color: #272c30;

File diff suppressed because one or more lines are too long

View File

@@ -4,7 +4,7 @@
"date_time_fns": "do MMMM yyyy @ HH:mm:ss",
"month_and_day_fns": "d MMMM y",
"does_not_exist": "(config.does_not_exist)",
"date_time_fns_short": "MMMM do, yyyy @ HH:mm"
"date_time_fns_short": "do MMMM yyyy @ HH:mm"
},
"form": {
"title": "Titolo"
@@ -17,10 +17,10 @@
"spent": "Speso",
"administration_owner": "Administration owner: {{email}}",
"administration_you": "Your role: {{role}}",
"administration_role_owner": "Owner",
"administration_role_ro": "Read-only",
"administration_role_mng_trx": "Manage transactions",
"administration_role_mng_meta": "Manage classification and meta-data",
"administration_role_owner": "Proprietario",
"administration_role_ro": "Sola lettura",
"administration_role_mng_trx": "Gestisci le transazioni",
"administration_role_mng_meta": "Gestisci classificazione e meta-dati",
"administration_role_mng_budgets": "Manage budgets",
"administration_role_mng_piggies": "Manage piggy banks",
"administration_role_mng_subscriptions": "Manage subscriptions",
@@ -48,7 +48,7 @@
"unknown_dest_plain": "Conto di destinazione sconosciuto",
"unknown_any_plain": "Conto sconosciuto",
"unknown_budget_plain": "Nessun budget",
"stored_journal_js": "Successfully created new transaction \"{{description}}\"",
"stored_journal_js": "Nuova transazione \"{{description}} \" creata con successo",
"wait_loading_transaction": "Attendi il caricamento del modello",
"nothing_found": "(nessun risultato)",
"wait_loading_data": "Ti preghiamo di attendere il caricamento delle tue informazioni...",

View File

@@ -4,7 +4,7 @@
"date_time_fns": "do MMMM yyyy @ HH:mm:ss",
"month_and_day_fns": "d MMMM y",
"does_not_exist": "(config.does_not_exist)",
"date_time_fns_short": "MMMM do, yyyy @ HH:mm"
"date_time_fns_short": "do MMMM yyyy @ HH:mm"
},
"form": {
"title": "Titolo"
@@ -17,10 +17,10 @@
"spent": "Speso",
"administration_owner": "Administration owner: {{email}}",
"administration_you": "Your role: {{role}}",
"administration_role_owner": "Owner",
"administration_role_ro": "Read-only",
"administration_role_mng_trx": "Manage transactions",
"administration_role_mng_meta": "Manage classification and meta-data",
"administration_role_owner": "Proprietario",
"administration_role_ro": "Sola lettura",
"administration_role_mng_trx": "Gestisci le transazioni",
"administration_role_mng_meta": "Gestisci classificazione e meta-dati",
"administration_role_mng_budgets": "Manage budgets",
"administration_role_mng_piggies": "Manage piggy banks",
"administration_role_mng_subscriptions": "Manage subscriptions",
@@ -48,7 +48,7 @@
"unknown_dest_plain": "Conto di destinazione sconosciuto",
"unknown_any_plain": "Conto sconosciuto",
"unknown_budget_plain": "Nessun budget",
"stored_journal_js": "Successfully created new transaction \"{{description}}\"",
"stored_journal_js": "Nuova transazione \"{{description}} \" creata con successo",
"wait_loading_transaction": "Attendi il caricamento del modello",
"nothing_found": "(nessun risultato)",
"wait_loading_data": "Ti preghiamo di attendere il caricamento delle tue informazioni...",

View File

@@ -11,7 +11,7 @@
"axios": "^1.6.8",
"laravel-vite-plugin": "^1.0.2",
"patch-package": "^8.0.0",
"sass": "^1.72.0",
"sass": "^1.75.0",
"vite": "^5",
"vite-plugin-manifest-sri": "^0.2.0"
},
@@ -31,7 +31,7 @@
"chartjs-adapter-date-fns": "^3.0.0",
"chartjs-chart-sankey": "^0.12.0",
"date-fns": "^3.6.0",
"i18next": "^23.10.1",
"i18next": "^23.11.2",
"i18next-chained-backend": "^4.6.2",
"i18next-http-backend": "^2.4.2",
"i18next-localstorage-backend": "^4.2.0",

View File

@@ -30,6 +30,11 @@ import '../../css/grid-ff3-theme.css';
import Get from "../../api/v2/model/account/get.js";
import Put from "../../api/v2/model/account/put.js";
import AccountRenderer from "../../support/renderers/AccountRenderer.js";
import {showInternalsButton} from "../../support/page-settings/show-internals-button.js";
import {showWizardButton} from "../../support/page-settings/show-wizard-button.js";
import {getVariable} from "../../store/get-variable.js";
import {setVariable} from "../../store/set-variable.js";
// set type from URL
const beforeQuery = window.location.href.split('?');
@@ -47,6 +52,9 @@ sortingColumn = params.column ?? '';
sortDirection = params.direction ?? '';
showInternalsButton();
showWizardButton();
let index = function () {
return {
// notifications
@@ -59,10 +67,64 @@ let index = function () {
show: false, text: '',
}
}, totalPages: 1, page: 1, // available columns:
},
totalPages: 1,
page: 1,
// available columns:
// visible is hard coded, enabled is user-configurable.
tableColumns: {
drag_and_drop: {
visible: true,
enabled: true,
},
active: {
visible: true,
enabled: true,
},
name: {
enabled: true
visible: true,
enabled: true,
},
type: {
visible: true,
enabled: true,
},
liability_type: {
visible: type === 'liabilities',
enabled: true,
},
liability_direction: {
visible: type === 'liabilities',
enabled: true,
},
liability_interest: {
visible: type === 'liabilities',
enabled: true,
},
number: {
visible: true,
enabled: true,
},
current_balance: {
visible: type !== 'liabilities',
enabled: true,
},
amount_due: {
visible: type === 'liabilities',
enabled: true,
},
last_activity: {
visible: true,
enabled: true,
},
balance_difference: {
visible: true,
enabled: true,
},
menu: {
visible: true,
enabled: true,
},
},
editors: {},
@@ -77,7 +139,7 @@ let index = function () {
sort(column) {
this.sortingColumn = column;
this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
const url = './accounts/'+type+'?column='+column+'&direction='+this.sortDirection;
const url = './accounts/' + type + '?column=' + column + '&direction=' + this.sortDirection;
window.history.pushState({}, "", url);
@@ -92,11 +154,34 @@ let index = function () {
format(date) {
return format(date, i18next.t('config.date_time_fns'));
},
saveColumnSettings() {
let newSettings = {};
for (let key in this.tableColumns) {
if (this.tableColumns.hasOwnProperty(key)) {
newSettings[key] = this.tableColumns[key].enabled;
}
}
console.log('New settings', newSettings);
setVariable('accts_columns_' + type, newSettings);
},
init() {
this.notifications.wait.show = true;
this.notifications.wait.text = i18next.t('firefly.wait_loading_data')
this.loadAccounts();
const key = 'accts_columns_' + type;
const defaultValue = {"drag_and_drop": false};
getVariable(key, defaultValue).then((response) => {
for (let k in response) {
if (response.hasOwnProperty(k) && this.tableColumns.hasOwnProperty(k)) {
this.tableColumns[k].enabled = response[k] ?? true;
}
}
}).then(() => {
this.loadAccounts();
});
},
renderObjectValue(field, account) {
let renderer = new AccountRenderer();
@@ -140,14 +225,33 @@ let index = function () {
this.accounts[index].nameEditorVisible = true;
},
loadAccounts() {
// sort instructions
const sorting = [{column: this.sortingColumn, direction: this.sortDirection}];
// get start and end from the store:
const start = new Date(window.store.get('start'));
const end = new Date(window.store.get('end'));
let params = {
sorting: sorting,
type: type,
page: this.page,
start: start,
end: end
};
if(!this.tableColumns.balance_difference.enabled){
delete params.start;
delete params.end;
}
this.notifications.wait.show = true;
this.notifications.wait.text = i18next.t('firefly.wait_loading_data')
this.accounts = [];
// sort instructions
// &sorting[0][column]=description&sorting[0][direction]=asc
const sorting = [{column: this.sortingColumn, direction: this.sortDirection}];
// one page only.o
(new Get()).index({sorting: sorting, type: type, page: this.page}).then(response => {
(new Get()).index(params).then(response => {
for (let i = 0; i < response.data.data.length; i++) {
if (response.data.data.hasOwnProperty(i)) {
let current = response.data.data[i];
@@ -165,7 +269,10 @@ let index = function () {
native_current_balance: current.attributes.native_current_balance,
native_currency_code: current.attributes.native_currency_code,
last_activity: null === current.attributes.last_activity ? '' : format(new Date(current.attributes.last_activity), i18next.t('config.month_and_day_fns')),
balance_difference: current.attributes.balance_difference,
native_balance_difference: current.attributes.native_balance_difference
};
console.log(current.attributes.balance_difference);
this.accounts.push(account);
}
}

View File

@@ -23,6 +23,7 @@ import {format} from "date-fns";
import {getVariable} from "../../store/get-variable.js";
import formatMoney from "../../util/format-money.js";
import {getCacheKey} from "../../support/get-cache-key.js";
import {cleanupCache} from "../../support/cleanup-cache.js";
let afterPromises = false;
export default () => ({
@@ -38,6 +39,7 @@ export default () => ({
const start = new Date(window.store.get('start'));
const end = new Date(window.store.get('end'));
const boxesCacheKey = getCacheKey('dashboard-boxes-data', start, end);
cleanupCache();
const cacheValid = window.store.get('cacheValid');
let cachedData = window.store.get(boxesCacheKey);

View File

@@ -69,6 +69,7 @@ let transactions = function () {
resetButton: false,
rulesButton: true,
webhooksButton: true,
categorySelectVisible: false
},
// form behaviour during transaction
@@ -343,7 +344,13 @@ let transactions = function () {
// destination can never be revenue account
this.filters.destination = ['Expense account', 'Loan', 'Debt', 'Mortgage', 'Asset account'];
},
keyUpFromCategory(e) {
if (e.key === 'Enter' && false === this.formStates.categorySelectVisible) {
this.submitTransaction();
return;
}
this.formStates.categorySelectVisible = document.querySelector('input.ac-category').nextSibling.classList.contains('show');
},
submitTransaction() {
// reset all messages:
this.notifications.error.show = false;
@@ -422,7 +429,10 @@ let transactions = function () {
if (this.formStates.returnHereButton) {
this.notifications.success.show = true;
this.notifications.success.url = 'transactions/show/' + this.groupProperties.id;
this.notifications.success.text = i18next.t('firefly.stored_journal_js', {description: this.groupProperties.title, interpolation: { escapeValue: false }});
this.notifications.success.text = i18next.t('firefly.stored_journal_js', {
description: this.groupProperties.title,
interpolation: {escapeValue: false}
});
this.formStates.isSubmitting = false;
// reset group title again
this.groupProperties.title = null;

View File

@@ -72,12 +72,15 @@ let transactions = function () {
resetButton: true,
rulesButton: true,
webhooksButton: true,
},
// form behaviour during transaction
formBehaviour: {
formType: 'edit',
foreignCurrencyEnabled: true,
categorySelectVisible: false,
},
// form data (except transactions) is stored in formData
@@ -111,6 +114,14 @@ let transactions = function () {
}
},
keyUpFromCategory(e) {
if (e.key === 'Enter' && false === this.formBehaviour.categorySelectVisible) {
this.submitTransaction();
return;
}
this.formBehaviour.categorySelectVisible = document.querySelector('input.ac-category').nextSibling.classList.contains('show');
},
// submit the transaction form.
// TODO pretty much duplicate of create.js
submitTransaction() {

View File

@@ -23,217 +23,12 @@ import dates from "../shared/dates.js";
import i18next from "i18next";
import {format} from "date-fns";
import formatMoney from "../../util/format-money.js";
import Put from "../../api/v2/model/transaction/put.js";
import {createGrid, ModuleRegistry} from "@ag-grid-community/core";
import '@ag-grid-community/styles/ag-grid.css';
import '@ag-grid-community/styles/ag-theme-alpine.css';
import '../../css/grid-ff3-theme.css';
import Get from "../../api/v2/model/transaction/get.js";
import AmountEditor from "../../support/ag-grid/AmountEditor.js";
import TransactionDataSource from "../../support/ag-grid/TransactionDataSource.js";
import {InfiniteRowModelModule} from '@ag-grid-community/infinite-row-model';
import DateTimeEditor from "../../support/ag-grid/DateTimeEditor.js";
const ds = new TransactionDataSource();
// set type from URL
const urlParts = window.location.href.split('/');
const type = urlParts[urlParts.length - 1];
ds.setType(type);
let dataTable;
const editableFields = ['description', 'amount', 'date'];
const onCellEditRequestMethod = (event) => {
console.log('onCellEditRequestMethod');
const data = event.data;
const field = event.colDef.field;
let newValue = event.newValue;
if (!editableFields.includes(field)) {
console.log('Field ' + field + ' is not editable.');
return;
}
// this needs to be better
if ('amount' === field) {
newValue = event.newValue.amount;
console.log('New value is now' + newValue);
}
console.log('New value for field "' + field + '" in transaction journal #' + data.transaction_journal_id + ' of group #' + data.id + ' is "' + newValue + '"');
data[field] = newValue;
let rowNode = dataTable.getRowNode(String(event.rowIndex));
rowNode.updateData(data);
// then push update to Firefly III over API:
let submission = {
transactions: [
{
transaction_journal_id: data.transaction_journal_id,
}
]
};
submission.transactions[0][field] = newValue;
let putter = new Put();
putter.put(submission, {id: data.id});
};
const gridOptions = {
rowModelType: 'infinite',
datasource: ds,
cacheOverflowSize: 1,
cacheBlockSize: 20,
onCellEditRequest: onCellEditRequestMethod,
readOnlyEdit: true,
getRowId: function (params) {
console.log('getRowId', params.data.id);
return params.data.id;
},
// Row Data: The data to be displayed.
// rowData: [
// { description: "Tesla", model: "Model Y", price: 64950, electric: true },
// { description: "Ford", model: "F-Series", price: 33850, electric: false },
// { description: "Toyota", model: "Corolla", price: 29600, electric: false },
// ],
// Column Definitions: Defines & controls grid columns.
columnDefs: [
{
field: "icon",
editable: false,
headerName: '',
sortable: false,
width: 40,
cellRenderer: function (params) {
if (params.getValue()) {
return '<a href="./transactions/show/' + parseInt(params.value.id) + '"><em class="' + params.value.classes + '"></em></a>';
}
return '';
}
},
{
field: "description",
cellDataType: 'text',
editable: true,
// cellRenderer: function (params) {
// if (params.getValue()) {
// return '<a href="#">' + params.getValue() + '</a>';
// }
// return '';
// }
},
{
field: "amount",
editable: function (params) {
// only when NO foreign amount.
return null === params.data.amount.foreign_amount && null === params.data.amount.foreign_currency_code;
},
cellEditor: AmountEditor,
cellRenderer(params) {
if (params.getValue()) {
let returnString = '';
let amount = parseFloat(params.getValue().amount);
let obj = params.getValue();
let stringClass = 'text-danger';
if (obj.type === 'withdrawal') {
amount = amount * -1;
}
if (obj.type === 'deposit') {
stringClass = 'text-success';
}
if (obj.type === 'transfer') {
stringClass = 'text-info';
}
returnString += '<span class="' + stringClass + '">' + formatMoney(amount, params.getValue().currency_code) + '</span>';
// foreign amount:
if (obj.foreign_amount) {
let foreignAmount = parseFloat(params.getValue().foreign_amount);
if (obj.type === 'withdrawal') {
foreignAmount = foreignAmount * -1;
}
returnString += ' (<span class="' + stringClass + '">' + formatMoney(foreignAmount, obj.foreign_currency_code) + '</span>)';
}
return returnString;
}
return '';
}
},
{
field: "date",
editable: true,
cellDataType: 'date',
cellEditor: DateTimeEditor,
cellEditorPopup: true,
cellEditorPopupPosition: 'under',
cellRenderer(params) {
if (params.getValue()) {
return format(params.getValue(), i18next.t('config.date_time_fns_short'));
}
return '';
}
},
{
field: "from",
cellDataType: 'text',
cellRenderer: function (params) {
if (params.getValue()) {
let obj = params.getValue();
return '<a href="./accounts/show/' + obj.id + '">' + obj.name + '</a>';
}
return '';
}
},
{
field: "to",
cellDataType: 'text',
cellRenderer: function (params) {
if (params.getValue()) {
let obj = params.getValue();
return '<a href="./accounts/show/' + obj.id + '">' + obj.name + '</a>';
}
return '';
}
},
{
field: "category",
cellDataType: 'text',
cellRenderer: function (params) {
if (params.getValue()) {
let obj = params.getValue();
if (null !== obj.id) {
return '<a href="./categories/show/' + obj.id + '">' + obj.name + '</a>';
}
}
return '';
}
},
{
field: "budget",
cellDataType: 'text',
cellRenderer: function (params) {
if (params.getValue()) {
let obj = params.getValue();
if (null !== obj.id) {
return '<a href="./budgets/show/' + obj.id + '">' + obj.name + '</a>';
}
}
return '';
}
},
]
};
ModuleRegistry.registerModules([InfiniteRowModelModule]);
let index = function () {
return {
// notifications
@@ -276,36 +71,56 @@ let index = function () {
return format(date, i18next.t('config.date_time_fns'));
},
init() {
this.notifications.wait.show = true;
this.notifications.wait.text = i18next.t('firefly.wait_loading_data')
// TODO need date range.
// TODO handle page number
//this.getTransactions(this.page);
this.getTransactions(this.page);``
// Your Javascript code to create the grid
dataTable = createGrid(document.querySelector('#grid'), gridOptions);
// dataTable = createGrid(document.querySelector('#grid'), gridOptions);
},
// getTransactions(page) {
// const urlParts = window.location.href.split('/');
// const type = urlParts[urlParts.length - 1];
// let getter = new Get();
//
// getter.list({page: page, type: type}).then(response => {
// this.parseTransactions(response.data.data)
//
// // set meta data
// this.totalPages = response.data.meta.pagination.total_pages;
// this.perPage = response.data.meta.pagination.per_page;
// this.page = response.data.meta.pagination.current_page;
// }).catch(error => {
// // to do this is auto generated
// this.notifications.wait.show = false;
// this.notifications.error.show = true;
// this.notifications.error.text = error.response.data.message;
// });
// },
getTransactions(page) {
this.notifications.wait.show = true;
this.notifications.wait.text = i18next.t('firefly.wait_loading_data')
this.transactions = [];
const urlParts = window.location.href.split('/');
const type = urlParts[urlParts.length - 1];
let getter = new Get();
getter.list({page: page, type: type}).then(response => {
this.parseTransactions(response.data.data)
// set meta data
this.totalPages = response.data.meta.pagination.total_pages;
this.perPage = response.data.meta.pagination.per_page;
this.page = response.data.meta.pagination.current_page;
}).catch(error => {
// to do this is auto generated
this.notifications.wait.show = false;
this.notifications.error.show = true;
this.notifications.error.text = error.response.data.message;
});
},
previousPage() {
if(this.page > 1) {
this.page--;
}
this.getTransactions(this.page);
},
nextPage() {
if(this.page < this.totalPages) {
this.page++;
}
this.getTransactions(this.page);
},
gotoPage(i) {
if(i > 0 && i <= this.totalPages) {
this.page = i;
}
this.getTransactions(this.page);
},
parseTransactions(data) {
// no parse, just save
for (let i in data) {
@@ -321,7 +136,7 @@ let index = function () {
transaction.split = isSplit;
tranaction.icon = 'fa fa-solid fa-arrow-left';
transaction.icon = 'fa fa-solid fa-arrow-left';
transaction.firstSplit = firstSplit;
transaction.group_title = current.attributes.group_title;
transaction.id = current.id;

View File

@@ -22,31 +22,27 @@ import Get from "../api/v1/preferences/index.js";
import Post from "../api/v1/preferences/post.js";
export function getVariable(name, defaultValue = null) {
const validCache = window.store.get('cacheValid');
// currently unused, window.X can be used by the blade template
// to make things available quicker than if the store has to grab it through the API.
// then again, it's not that slow.
if (validCache && window.hasOwnProperty(name)) {
// console.log('Get from window');
return Promise.resolve(window[name]);
}
// load from store2, if it's present.
const fromStore = window.store.get(name);
if (validCache && typeof fromStore !== 'undefined') {
// console.log('Get "' + name + '" from store');
return Promise.resolve(fromStore);
}
let getter = (new Get);
return getter.getByName(name).then((response) => {
// console.log('Get "' + name + '" from API');
return Promise.resolve(parseResponse(name, response));
}).catch(() => {
}).catch((error) => {
// preference does not exist (yet).
// POST it and then return it anyway.
let poster = (new Post);
poster.post(name, defaultValue).then((response) => {
return poster.post(name, defaultValue).then((response) => {
return Promise.resolve(parseResponse(name, response));
});
});
@@ -55,7 +51,6 @@ export function getVariable(name, defaultValue = null) {
export function parseResponse(name, response) {
let value = response.data.data.attributes.data;
window.store.set(name, value);
// console.log('Store "' + name + '" in localStorage');
return value;
}

View File

@@ -0,0 +1,34 @@
/*
* load-translations.js
* Copyright (c) 2023 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {format} from "date-fns";
import store from "store";
function cleanupCache() {
const localValue = store.get('lastActivity');
store.each(function(value, key) {
if(key.startsWith('dcx') && !key.includes(localValue)) {
store.remove(key);
}
});
}
export {cleanupCache};

View File

@@ -23,7 +23,7 @@ import store from "store";
function getCacheKey(string, start, end) {
const localValue = store.get('lastActivity');
const cacheKey = format(start, 'y-MM-dd') + '_' + format(end, 'y-MM-dd') + '_' + string + localValue;
const cacheKey = 'dcx' + format(start, 'yMMdd')+ format(end, 'yMMdd') + string + localValue;
console.log('getCacheKey: ' + cacheKey);
return String(cacheKey);
}

View File

@@ -0,0 +1,25 @@
/*
* show-internals-button.js
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
export function showInternalsButton() {
console.log('showInternalsButton');
document.querySelector('.toggle-page-internals').classList.remove('d-none')
}

View File

@@ -0,0 +1,24 @@
/*
* show-settings-button.js
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
export function showWizardButton() {
document.querySelector('.toggle-page-wizard').classList.remove('d-none')
}

View File

@@ -20,7 +20,8 @@
import {defineConfig} from 'vite';
import laravel from 'laravel-vite-plugin';
// import manifestSRI from 'vite-plugin-manifest-sri';
import manifestSRI from 'vite-plugin-manifest-sri';
import * as fs from "fs";
const host = '127.0.0.1';
@@ -65,20 +66,23 @@ export default defineConfig({
publicDirectory: '../../../public',
refresh: true,
}),
//manifestSRI(),
manifestSRI(),
],
server: {
usePolling: true,
allowedHosts: '*.sd.internal',
host: '0.0.0.0',
hmr: {host},
cors: true
watch: {
usePolling: true,
},
host: '10.0.0.15',
// hmr: {
// protocol: 'wss',
// },
// https: {
// key: fs.readFileSync(`/Users/sander/Sites/vm/tls-certificates/wildcard.sd.local.key`),
// cert: fs.readFileSync(`/Users/sander/Sites/vm/tls-certificates/wildcard.sd.local.crt`),
// key: fs.readFileSync(`/sites/vm/tls-certificates/wildcard.sd.internal.key`),
// cert: fs.readFileSync(`/sites/vm/tls-certificates/wildcard.sd.internal.crt`),
// },
},
});

View File

@@ -2144,6 +2144,7 @@ return [
'logout' => 'Изход',
'logout_other_sessions' => 'Изход от всички други сесии',
'toggleNavigation' => 'Превключване на навигацията',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => 'Търсене...',
'version' => 'Версия',
'dashboard' => 'Основно табло',

View File

@@ -2144,6 +2144,7 @@ return [
'logout' => 'Tanca la sessió',
'logout_other_sessions' => 'Tanca les altres sessions',
'toggleNavigation' => 'Alternar navegació',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => 'Cerca...',
'version' => 'Versió',
'dashboard' => 'Panell de control',

View File

@@ -2144,6 +2144,7 @@ return [
'logout' => 'Odhlásit se',
'logout_other_sessions' => 'Logout all other sessions',
'toggleNavigation' => 'Vyp/zap. navigaci',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => 'Hledat…',
'version' => 'Verze',
'dashboard' => 'Přehled',

View File

@@ -2144,6 +2144,7 @@ return [
'logout' => 'Logout',
'logout_other_sessions' => 'Logout all other sessions',
'toggleNavigation' => 'Toggle navigation',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => 'Search...',
'version' => 'Version',
'dashboard' => 'Dashboard',

View File

@@ -2144,6 +2144,7 @@ return [
'logout' => 'Abmelden',
'logout_other_sessions' => 'Alle anderen Sitzungen abmelden',
'toggleNavigation' => 'Navigation umschalten',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => 'Suchen...',
'version' => 'Version',
'dashboard' => 'Übersicht',

View File

@@ -2144,6 +2144,7 @@ return [
'logout' => 'Αποσύνδεση',
'logout_other_sessions' => 'Αποσυνδέσετε όλες τις άλλες συνεδρίες',
'toggleNavigation' => 'Εναλλαγή περιήγησης',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => 'Αναζήτηση...',
'version' => 'Έκδοση',
'dashboard' => 'Επισκόπηση',

View File

@@ -2144,6 +2144,7 @@ return [
'logout' => 'Logout',
'logout_other_sessions' => 'Logout all other sessions',
'toggleNavigation' => 'Toggle navigation',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => 'Search...',
'version' => 'Version',
'dashboard' => 'Dashboard',

View File

@@ -2090,6 +2090,7 @@ return [
'logout' => 'Logout',
'logout_other_sessions' => 'Logout all other sessions',
'toggleNavigation' => 'Toggle navigation',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => 'Search...',
'version' => 'Version',
'dashboard' => 'Dashboard',

View File

@@ -2144,6 +2144,7 @@ return [
'logout' => 'Cerrar sesión',
'logout_other_sessions' => 'Desconectar todas las demás sesiones',
'toggleNavigation' => 'Activar navegación',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => 'Buscar...',
'version' => 'Versión',
'dashboard' => 'Panel de control',

View File

@@ -2144,6 +2144,7 @@ return [
'logout' => 'Kirjaudu ulos',
'logout_other_sessions' => 'Kirjaudu ulos kaikista muista istunnoista',
'toggleNavigation' => 'Vaihda navigointia',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => 'Hae ...',
'version' => 'Versio',
'dashboard' => 'Etusivu',

View File

@@ -2144,6 +2144,7 @@ return [
'logout' => 'Se déconnecter',
'logout_other_sessions' => 'Déconnecter toutes les autres sessions',
'toggleNavigation' => 'Activer navigation',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => 'Rechercher...',
'version' => 'Version',
'dashboard' => 'Tableau de bord',

View File

@@ -2144,6 +2144,7 @@ return [
'logout' => 'Kijelentkezés',
'logout_other_sessions' => 'Minden más munkamenet kijelentkeztetése',
'toggleNavigation' => 'Navigációs mód átkapcsolása',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => 'Keresés...',
'version' => 'Verzió',
'dashboard' => 'Műszerfal',

View File

@@ -2144,6 +2144,7 @@ return [
'logout' => 'Keluar',
'logout_other_sessions' => 'Logout all other sessions',
'toggleNavigation' => 'Toggle navigation',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => 'Pencarian...',
'version' => 'Versi',
'dashboard' => 'Dasbor',

View File

@@ -64,7 +64,7 @@ return [
// 'date_time' => '%B %e, %Y, @ %T',
'date_time_js' => 'D MMMM YYYY, HH:mm:ss',
'date_time_fns' => 'do MMMM yyyy @ HH:mm:ss',
'date_time_fns_short' => 'MMMM do, yyyy @ HH:mm',
'date_time_fns_short' => 'do MMMM yyyy @ HH:mm',
// 'specific_day' => '%e %B %Y',
'specific_day_js' => 'D MMMM YYYY',

View File

@@ -42,7 +42,7 @@ return [
'fatal_error' => 'Si è verificato un errore fatale. Controlla i file di login "storage/Los" o usa "Docker Los f [container]" per vedere cosa sta succedendo.',
'maintenance_mode' => 'Firefox III è in modalità di manutenzione.',
'be_right_back' => 'Torno subito!',
'check_back' => 'Firefly III is down for some necessary maintenance. Please check back in a second. If you happen to see this message on the demo site, just wait a few minutes. The database is reset every few hours.',
'check_back' => 'Firefly III non è disponibile per via di una manutenzione necessaria. Per favore riprova tra qualche istante. Se ti capita di vedere questo messaggio sul sito demo, attendi qualche minuto. Il database viene reimpostato ogni poche ore.',
'error_occurred' => 'Oops! Si è verificato un errore.',
'db_error_occurred' => 'Oops! Si è verificato un errore del database.',
'error_not_recoverable' => 'Sfortunatamente questo errore non è riparabile :(. Firefly III è rotto. L\'errore è:',

View File

@@ -896,9 +896,9 @@ return [
'rule_trigger_budget_is' => 'Il budget è ":trigger_value"',
'rule_trigger_tag_is_choice' => 'Qualsiasi tag è..',
'rule_trigger_tag_is' => 'Qualsiasi tag è ":trigger_value"',
'rule_trigger_tag_contains_choice' => 'Any tag contains..',
'rule_trigger_tag_contains' => 'Any tag contains ":trigger_value"',
'rule_trigger_tag_ends_choice' => 'Any tag ends with..',
'rule_trigger_tag_contains_choice' => 'Qualsiasi tag contiene..',
'rule_trigger_tag_contains' => 'Qualsiasi tag contiene ":trigger_value"',
'rule_trigger_tag_ends_choice' => 'Qualsiasi tag finisce con..',
'rule_trigger_tag_ends' => 'Any tag ends with ":trigger_value"',
'rule_trigger_tag_starts_choice' => 'Any tag starts with..',
'rule_trigger_tag_starts' => 'Any tag starts with ":trigger_value"',
@@ -1434,10 +1434,10 @@ return [
'administrations_page_edit_sub_title' => 'Edit financial administration ":title"',
// roles
'administration_role_owner' => 'Owner',
'administration_role_ro' => 'Read-only',
'administration_role_mng_trx' => 'Manage transactions',
'administration_role_mng_meta' => 'Manage classification and meta-data',
'administration_role_owner' => 'Proprietario',
'administration_role_ro' => 'Sola lettura',
'administration_role_mng_trx' => 'Gestisci le transazioni',
'administration_role_mng_meta' => 'Gestisci classificazione e meta-dati',
'administration_role_mng_budgets' => 'Manage budgets',
'administration_role_mng_piggies' => 'Manage piggy banks',
'administration_role_mng_subscriptions' => 'Manage subscriptions',
@@ -2017,7 +2017,7 @@ return [
'deleted_transfer' => 'Trasferimento ":description" eliminato correttamente',
'deleted_reconciliation' => 'Transazione di riconciliazione ":description" elimina con successo',
'stored_journal' => 'Nuova transazione ":description" creata correttamente',
'stored_journal_js' => 'Successfully created new transaction "{{description}}"',
'stored_journal_js' => 'Nuova transazione "{{description}} " creata con successo',
'stored_journal_no_descr' => 'Hai creato con successo la nuova transazione',
'updated_journal_no_descr' => 'Transazione aggiornata con successo',
'select_transactions' => 'Seleziona transazioni',
@@ -2144,6 +2144,7 @@ return [
'logout' => 'Esci',
'logout_other_sessions' => 'Esci da tutte le altre sessioni',
'toggleNavigation' => 'Attiva / disattiva la navigazione',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => 'Cerca...',
'version' => 'Versione',
'dashboard' => 'Cruscotto',
@@ -2325,7 +2326,7 @@ return [
'description' => 'Descrizione',
'sum_of_period' => 'Somma del periodo',
'average_in_period' => 'Media nel periodo',
'no_account_role' => '(no role)',
'no_account_role' => '(nessun ruolo)',
'account_role_defaultAsset' => 'Conto attività predefinito',
'account_role_sharedAsset' => 'Conto attività condiviso',
'account_role_savingAsset' => 'Conto risparmio',
@@ -2490,7 +2491,7 @@ return [
'block_code_bounced' => 'Messaggi email respinti',
'block_code_expired' => 'Conto demo scaduto',
'no_block_code' => 'Nessun motivo per bloccare o non bloccare un utente',
'demo_user_export' => 'The demo user cannot export data',
'demo_user_export' => 'L\'utente demo non può esportare dati',
'block_code_email_changed' => 'L\'utente non ha ancora confermato il nuovo indirizzo emails',
'admin_update_email' => 'Contrariamente alla pagina del profilo, l\'utente NON riceverà alcuna notifica al proprio indirizzo email!',
'update_user' => 'Aggiorna utente',

View File

@@ -70,5 +70,5 @@ return [
'cannot_find_budget' => 'Firefly III non riesce a trovare il budget ":name"',
'cannot_find_category' => 'Firefly III non riesce a trovare la categoria ":name"',
'cannot_set_budget' => 'Firefly III non può impostare il budget ":name" a una transazione di tipo ":type"',
'journal_invalid_amount' => 'Firefly III can\'t set amount ":amount" because it is not a valid number.',
'journal_invalid_amount' => 'Firefly III non può impostare l\'importo ":amount" perché non è un numero valido.',
];

View File

@@ -55,11 +55,11 @@ return [
'reconciled_forbidden_field' => 'Questa transazione è già riconciliata, non è possibile modificare il campo ":field"',
'deleted_user' => 'A causa dei vincoli di sicurezza, non è possibile registrarsi utilizzando questo indirizzo email.',
'rule_trigger_value' => 'Questo valore non è valido per il trigger selezionato.',
'rule_action_expression' => 'Invalid expression. :error',
'rule_action_expression' => 'Espressione non valida. :error',
'rule_action_value' => 'Questo valore non è valido per l\'azione selezionata.',
'file_already_attached' => 'Il file caricato ":name" è già associato a questo oggetto.',
'file_attached' => 'File caricato con successo ":name".',
'file_zero' => 'The file is zero bytes in size.',
'file_zero' => 'Il file ha dimensione zero.',
'must_exist' => 'L\'ID nel campo :attribute non esiste nel database.',
'all_accounts_equal' => 'Tutti i conti in questo campo devono essere uguali.',
'group_title_mandatory' => 'Il titolo del gruppo è obbligatorio quando ci sono più di una transazione.',
@@ -68,8 +68,8 @@ return [
'invalid_selection' => 'La tua selezione non è valida.',
'belongs_user' => 'Questo valore è collegato a un oggetto che non sembra esistere.',
'belongs_user_or_user_group' => 'Questo valore è collegato a un oggetto che non sembra esistere nella tua attuale amministrazione finanziaria.',
'no_access_group' => 'The user has no access to this user group.',
'no_accepted_roles_defined' => 'No access roles have been defined for this endpoint, access denied.',
'no_access_group' => 'L\'utente non ha accesso a questo gruppo.',
'no_accepted_roles_defined' => 'Nessun ruolo di accesso è stato definito per questo endpoint, accesso negato.',
'at_least_one_transaction' => 'Hai bisogno di almeno una transazione.',
'recurring_transaction_id' => 'Hai bisogno di almeno una transazione.',
'need_id_to_match' => 'È necessario inviare questa voce con un ID affinché l\'API sia in grado di abbinarla.',
@@ -199,7 +199,7 @@ return [
*
*/
'secure_password' => 'This is not a secure password. Please try again. For more information, visit https://bit.ly/FF3-password',
'secure_password' => 'Questa non è una password sicura. Per favore riprova. Per ulteriori informazioni, visita https://bit.ly/FF3-password',
'valid_recurrence_rep_type' => 'Il tipo di ripetizione della transazione ricorrente non è valido.',
'valid_recurrence_rep_moment' => 'Il momento di ripetizione per questo tipo di ripetizione non è valido.',
'invalid_account_info' => 'Informazione sul conto non valida.',

View File

@@ -2144,6 +2144,7 @@ return [
'logout' => 'ログアウト',
'logout_other_sessions' => 'すべてのセッションからログアウト',
'toggleNavigation' => 'ナビゲーションを切り替え',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => '検索...',
'version' => 'バージョン',
'dashboard' => 'ダッシュボード',

View File

@@ -2144,6 +2144,7 @@ return [
'logout' => '로그아웃',
'logout_other_sessions' => '다른 모든 세션 로그아웃',
'toggleNavigation' => '토글 내비게이션',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => '검색...',
'version' => '버전',
'dashboard' => '대시보드',

View File

@@ -2144,6 +2144,7 @@ return [
'logout' => 'Logg ut',
'logout_other_sessions' => 'Logg ut alle andre økter',
'toggleNavigation' => 'Vis/Skjul navigation',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => 'Søk...',
'version' => 'Versjon',
'dashboard' => 'Startskjerm',

View File

@@ -2144,6 +2144,7 @@ return [
'logout' => 'Uitloggen',
'logout_other_sessions' => 'Alle andere sessies afmelden',
'toggleNavigation' => 'Navigatie aan of uit',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => 'Zoeken...',
'version' => 'Versie',
'dashboard' => 'Dashboard',

View File

@@ -2144,6 +2144,7 @@ return [
'logout' => 'Logg ut',
'logout_other_sessions' => 'Logg ut alle andre økter',
'toggleNavigation' => 'Vis/Skjul navigation',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => 'Søk...',
'version' => 'Versjon',
'dashboard' => 'Startskjerm',

View File

@@ -2144,6 +2144,7 @@ return [
'logout' => 'Wyloguj',
'logout_other_sessions' => 'Wyloguj z pozostałych sesji',
'toggleNavigation' => 'Przełącz nawigację',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => 'Szukaj...',
'version' => 'Wersja',
'dashboard' => 'Kokpit',

View File

@@ -2144,6 +2144,7 @@ return [
'logout' => 'Desconectar',
'logout_other_sessions' => 'Sair de todas as outras sessões',
'toggleNavigation' => 'Alternar navegação',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => 'Pesquisar...',
'version' => 'Versão',
'dashboard' => 'Painel de Controle',

View File

@@ -2144,6 +2144,7 @@ return [
'logout' => 'Terminar Sessão',
'logout_other_sessions' => 'Terminar todas as outras sessões',
'toggleNavigation' => 'Mostrar/esconder navegação',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => 'Pesquisar...',
'version' => 'Versão',
'dashboard' => 'Painel de controlo',

View File

@@ -2144,6 +2144,7 @@ return [
'logout' => 'Ieșire',
'logout_other_sessions' => 'Deconectează toate celelalte sesiuni',
'toggleNavigation' => 'Navigare',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => 'Cautare...',
'version' => 'Versiunea',
'dashboard' => 'Panou de control',

View File

@@ -2144,6 +2144,7 @@ return [
'logout' => 'Выход',
'logout_other_sessions' => 'Завершить все другие сессии',
'toggleNavigation' => 'Переключить навигацию',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => 'Поиск...',
'version' => 'Версия',
'dashboard' => 'Сводка',

View File

@@ -68,8 +68,8 @@ return [
'invalid_selection' => 'Вы сделали неправильный выбор.',
'belongs_user' => 'Это значение связано с объектом, который не существует.',
'belongs_user_or_user_group' => 'Это значение связано с объектом, который не существует в Вашем текущем финансовом администрировании.',
'no_access_group' => 'The user has no access to this user group.',
'no_accepted_roles_defined' => 'No access roles have been defined for this endpoint, access denied.',
'no_access_group' => 'Пользователь не имеет доступа к данной группе пользователей.',
'no_accepted_roles_defined' => 'Нет ролей доступа для этой конечной точки, доступ запрещен.',
'at_least_one_transaction' => 'Необходима как минимум одна транзакция.',
'recurring_transaction_id' => 'Необходима минимум одна транзакция.',
'need_id_to_match' => 'Вы должны отправить эту запись с ID для того, чтобы API мог сопоставить её.',

View File

@@ -2144,6 +2144,7 @@ return [
'logout' => 'Odhlásiť sa',
'logout_other_sessions' => 'Odhlásiť všetky ostatné sedenia',
'toggleNavigation' => 'Vyp/zap. navigáciu',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => 'Vyhľadať...',
'version' => 'Verzia',
'dashboard' => 'Prehľad',

View File

@@ -2144,6 +2144,7 @@ return [
'logout' => 'Odjava',
'logout_other_sessions' => 'Odjavi vse druge seje',
'toggleNavigation' => 'Preklopi navigacijo',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => 'Iskanje...',
'version' => 'Različica',
'dashboard' => 'Nadzorna plošča',

View File

@@ -2145,6 +2145,7 @@ return [
'logout' => 'Logga ut',
'logout_other_sessions' => 'Logga ut alla andra sessioner',
'toggleNavigation' => 'Växla navigering',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => 'Sök...',
'version' => 'Version',
'dashboard' => 'Kontrollpanel',

View File

@@ -2144,6 +2144,7 @@ return [
'logout' => 'Logout',
'logout_other_sessions' => 'Logout all other sessions',
'toggleNavigation' => 'Toggle navigation',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => 'Search...',
'version' => 'Version',
'dashboard' => 'Dashboard',

View File

@@ -2145,6 +2145,7 @@ return [
'logout' => ıkış Yap',
'logout_other_sessions' => 'Logout all other sessions',
'toggleNavigation' => 'Navigasyonu aç / kapat',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => 'Aranıyor...',
'version' => 'Versiyon',
'dashboard' => 'Gösterge paneli',

View File

@@ -2144,6 +2144,7 @@ return [
'logout' => 'Вихід із системи',
'logout_other_sessions' => 'Вийти з усіх інших сеансів',
'toggleNavigation' => 'Переключити навігацію',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => 'Пошук...',
'version' => 'Версія',
'dashboard' => 'Головна панель',

View File

@@ -2144,6 +2144,7 @@ return [
'logout' => 'Đăng xuất',
'logout_other_sessions' => 'Logout all other sessions',
'toggleNavigation' => 'Chuyển đổi đổigle navigation',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => 'Tìm kiếm...',
'version' => 'Phiên bản',
'dashboard' => 'Bảng điều khiển',

View File

@@ -2145,6 +2145,7 @@ return [
'logout' => '退出登录',
'logout_other_sessions' => '退出所有其他已登录设备',
'toggleNavigation' => '切换导览',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => '搜索…',
'version' => '版本',
'dashboard' => '仪表盘',

View File

@@ -2144,6 +2144,7 @@ return [
'logout' => '登出',
'logout_other_sessions' => 'Logout all other sessions',
'toggleNavigation' => '切換導覽',
'toggle_dropdown' => 'Toggle dropdown',
'searchPlaceholder' => '搜尋…',
'version' => '版本',
'dashboard' => '監控面板',

View File

@@ -1,4 +1,4 @@
<table class="table table-condensed table-hover table-responsive table-sortable">
<table class="table table-condensed table-hover table-responsive">
<thead>
<tr>
{% if showCategory or showBudget %}
@@ -49,7 +49,7 @@
<tbody>
{% for group in groups %}
{% if group.count > 1 %}
<tr style="border-top:1px #aaa solid;" class="unsortable">
<tr style="border-top:1px #aaa solid;">
<td colspan="2" style="border-top:1px #aaa solid;">
<small><strong>
<a href="{{ route('transactions.show', [group.id]) }}"
@@ -104,29 +104,29 @@
data-id="{{ group.id }}">
<td style=" {{ style|raw }}" class="hidden-xs">
{% if transaction.transaction_type_type == 'Withdrawal' %}
<span class="object-handle fa fa-long-arrow-left fa-fw"
<span class="fa fa-long-arrow-left fa-fw"
title="{{ trans('firefly.Withdrawal') }}"></span>
{% endif %}
{% if transaction.transaction_type_type == 'Deposit' %}
<span class="object-handle fa fa-long-arrow-right fa-fw"
<span class="fa fa-long-arrow-right fa-fw"
title="{{ trans('firefly.Deposit') }}"></span>
{% endif %}
{% if transaction.transaction_type_type == 'Transfer' %}
<span class="object-handle fa fa-exchange fa-fw" title="{{ trans('firefly.Transfer') }}"></span>
<span class="fa fa-exchange fa-fw" title="{{ trans('firefly.Transfer') }}"></span>
{% endif %}
{% if transaction.transaction_type_type == 'Reconciliation' %}
<span class="object-handle fa-fw fa fa-calculator"
<span class="fa-fw fa fa-calculator"
title="{{ trans('firefly.reconciliation_transaction') }}"></span>
{% endif %}
{% if transaction.transaction_type_type == 'Opening balance' %}
<span class="object-handle fa-fw fa fa-star-o"
<span class="fa-fw fa fa-star-o"
title="{{ trans('firefly.Opening balance') }}"></span>
{% endif %}
{% if transaction.transaction_type_type == 'Liability credit' %}
<span class="object-handle fa-fw fa fa-star-o"
<span class="fa-fw fa fa-star-o"
title="{{ trans('firefly.Liability credit') }}"></span>
{% endif %}

View File

@@ -0,0 +1,35 @@
<div class="modal fade" id="passwordModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
{{ __('firefly.secure_pw_title') }}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{{ __('firefly.close') }}"></button>
</div>
<div class="modal-body">
<p>
{{ __('firefly.secure_pw_history') }}
</p>
<p>
{{ __('firefly.secure_pw_ff') }}
</p>
<p>
{{ __('firefly.secure_pw_check_box') }}
</p>
<h4>{{ __('firefly.secure_pw_working_title') }}</h4>
<p>
{{ __('firefly.secure_pw_working')|raw }}
</p>
<h4>{{ __('firefly.secure_pw_should') }}</h4>
<p>
{{ __('firefly.secure_pw_long_password') }}
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ __('firefly.close') }}</button>
</div>
</div>
</div>
</div>

View File

@@ -65,5 +65,5 @@
</div>
</div>
</form>
{% include 'partials.password-modal' %}
{% include 'partials.password-modal-twig' %}
{% endblock %}

View File

@@ -7,7 +7,7 @@
<div class="col-xl-4 col-lg-6 col-md-12 col-sm-12 col-xs-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">Info</h3>
<h3 class="card-title">Net worth</h3>
</div>
<div class="card-body">
some chart
@@ -17,7 +17,7 @@
<div class="col-xl-4 col-lg-6 col-md-12 col-sm-12 col-xs-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">Info</h3>
<h3 class="card-title">In + out this period</h3>
</div>
<div class="card-body">
Same
@@ -27,7 +27,7 @@
<div class="col-xl-4 col-lg-6 col-md-12 col-sm-12 col-xs-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">Info</h3>
<h3 class="card-title">Something else</h3>
</div>
<div class="card-body">
Same
@@ -56,42 +56,56 @@
<table class="table">
<thead>
<tr>
<td>&nbsp;</td>
<td>
<td x-show="tableColumns.drag_and_drop.visible && tableColumns.drag_and_drop.enabled">&nbsp;</td>
<td x-show="tableColumns.active.visible && tableColumns.active.enabled">
<a href="#" x-on:click.prevent="sort('active')">Active?</a>
<em x-show="sortingColumn === 'active' && sortDirection === 'asc'" class="fa-solid fa-arrow-down-wide-short"></em>
<em x-show="sortingColumn === 'active' && sortDirection === 'desc'" class="fa-solid fa-arrow-up-wide-short"></em>
</td>
<td>
<td x-show="tableColumns.name.visible && tableColumns.name.enabled">
<a href="#" x-on:click.prevent="sort('name')">Name</a>
<em x-show="sortingColumn === 'name' && sortDirection === 'asc'" class="fa-solid fa-arrow-down-z-a"></em>
<em x-show="sortingColumn === 'name' && sortDirection === 'desc'" class="fa-solid fa-arrow-up-z-a"></em>
</td>
<td>Type</td>
<td>
<td x-show="tableColumns.type.visible && tableColumns.type.enabled">Type</td>
<td x-show="tableColumns.liability_type.visible && tableColumns.liability_type.enabled">Liability type</td>
<td x-show="tableColumns.liability_direction.visible && tableColumns.liability_direction.enabled">Liability direction</td>
<td x-show="tableColumns.liability_interest.visible && tableColumns.liability_interest.enabled">Liability interest</td>
<td x-show="tableColumns.number.visible && tableColumns.number.enabled">
<a href="#" x-on:click.prevent="sort('iban')">Account number</a>
<em x-show="sortingColumn === 'iban' && sortDirection === 'asc'" class="fa-solid fa-arrow-down-z-a"></em>
<em x-show="sortingColumn === 'iban' && sortDirection === 'desc'" class="fa-solid fa-arrow-up-z-a"></em>
</td>
<td>
<td x-show="tableColumns.current_balance.visible && tableColumns.current_balance.enabled">
<a href="#" x-on:click.prevent="sort('balance')">Current balance</a>
<em x-show="sortingColumn === 'balance' && sortDirection === 'asc'" class="fa-solid fa-arrow-down-wide-short"></em>
<em x-show="sortingColumn === 'balance' && sortDirection === 'desc'" class="fa-solid fa-arrow-up-wide-short"></em>
</td>
<td>
<td x-show="tableColumns.amount_due.visible && tableColumns.amount_due.enabled">
<a href="#" x-on:click.prevent="sort('amount_due')">Current balance</a>
<em x-show="sortingColumn === 'amount_due' && sortDirection === 'asc'" class="fa-solid fa-arrow-down-wide-short"></em>
<em x-show="sortingColumn === 'amount_due' && sortDirection === 'desc'" class="fa-solid fa-arrow-up-wide-short"></em>
</td>
<td x-show="tableColumns.last_activity.visible && tableColumns.last_activity.enabled">
<a href="#" x-on:click.prevent="sort('last_activity')">Last activity</a>
<em x-show="sortingColumn === 'last_activity' && sortDirection === 'asc'" class="fa-solid fa-arrow-down-wide-short"></em>
<em x-show="sortingColumn === 'last_activity' && sortDirection === 'desc'" class="fa-solid fa-arrow-up-wide-short"></em>
</td>
<td>Balance difference</td>
<td>&nbsp;</td>
<td x-show="tableColumns.balance_difference.visible && tableColumns.balance_difference.enabled">
<a href="#" x-on:click.prevent="sort('balance_difference')">Balance difference</a>
<em x-show="sortingColumn === 'balance_difference' && sortDirection === 'asc'" class="fa-solid fa-arrow-down-wide-short"></em>
<em x-show="sortingColumn === 'balance_difference' && sortDirection === 'desc'" class="fa-solid fa-arrow-up-wide-short"></em>
</td>
<td x-show="tableColumns.menu.visible && tableColumns.menu.enabled">&nbsp;</td>
</tr>
</thead>
<tbody>
<template x-for="(account, index) in accounts" :key="index">
<tr>
<td>TODO</td>
<td>
<td x-show="tableColumns.drag_and_drop.visible && tableColumns.drag_and_drop.enabled">
<em class="fa-solid fa-bars"></em>
</td>
<td x-show="tableColumns.active.visible && tableColumns.active.enabled">
<template x-if="account.active">
<em class="text-success fa-solid fa-check"></em>
&nbsp;</template>
@@ -99,7 +113,7 @@
<em class="text-danger fa-solid fa-xmark"></em>
&nbsp;</template>
</td>
<td>
<td x-show="tableColumns.name.visible && tableColumns.name.enabled">
<!-- render content using a function -->
<span x-html="renderObjectValue('name', account)" x-show="!account.nameEditorVisible"></span>
@@ -119,7 +133,7 @@
</div>
</div>
</td>
<td>
<td x-show="tableColumns.type.visible && tableColumns.type.enabled">
<template x-if="null === account.role || '' === account.role">
<span><em>{{ __('firefly.no_account_role') }}</em></span>
</template>
@@ -127,7 +141,10 @@
<span x-text="accountRole(account.role)"></span>"
</template>
</td>
<td>
<td x-show="tableColumns.liability_type.visible && tableColumns.liability_type.enabled"></td>
<td x-show="tableColumns.liability_direction.visible && tableColumns.liability_direction.enabled"></td>
<td x-show="tableColumns.liability_interest.visible && tableColumns.liability_interest.enabled"></td>
<td x-show="tableColumns.number.visible && tableColumns.number.enabled">
<!-- IBAN and no account nr -->
<template x-if="'' === account.account_number && '' !== account.iban">
<span x-text="account.iban"></span>
@@ -144,14 +161,33 @@
</span>
</template>
</td>
<td>
<td x-show="tableColumns.current_balance.visible && tableColumns.current_balance.enabled">
<span x-text="formatMoney(account.current_balance, account.currency_code)"></span>
</td>
<td>
<td x-show="tableColumns.amount_due.visible && tableColumns.amount_due.enabled">
TODO
</td>
<td x-show="tableColumns.last_activity.visible && tableColumns.last_activity.enabled">
<span x-text="account.last_activity"></span>
</td>
<td>TODO 2 </td>
<td>&nbsp;</td>
<td x-show="tableColumns.balance_difference.visible && tableColumns.balance_difference.enabled">
<template x-if="null !== account.balance_difference">
<span x-text="formatMoney(account.balance_difference, account.currency_code)"></span>
</template>
</td>
<td x-show="tableColumns.menu.visible && tableColumns.menu.enabled">
<div class="btn-group btn-group-sm">
<a :href="'./accounts/edit/' + account.id" class="btn btn-sm btn-light"><em class="fa-solid fa-pencil"></em></a>
<button type="button" class="btn btn-light dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">{{ __('firefly.toggle_dropdown') }}</span>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" :href="'./accounts/show/' + account.id"><em class="fa-solid fa-eye"></em> {{ __('firefly.show') }}</a></li>
<li><a class="dropdown-item" :href="'./accounts/reconcile/' + account.id"><em class="fa-solid fa-calculator"></em> {{ __('firefly.reconcile_selected') }}</a></li>
<li><a class="dropdown-item" :href="'./accounts/delete/' + account.id"><em class="fa-solid fa-trash"></em> {{ __('firefly.delete') }}</a></li>
</ul>
</div>
</td>
</tr>
</template>
</tbody>
@@ -167,6 +203,55 @@
Nav
</div>
</div>
<!-- Internal settings modal -->
<div class="modal fade" id="internalsModal" tabindex="-1" aria-labelledby="internalsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="internalsModalLabel">TODO Page settings</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<h2>Visible columns</h2>
<template x-for="(column, key) in tableColumns" :key="key">
<div class="form-check" x-show="column.visible">
<input class="form-check-input" type="checkbox" x-model="column.enabled" @change="saveColumnSettings"> <span x-text="key"></span>
</div>
</template>
- Group accounts <br>
- Columns to show<br>
- Show / hide inactive accounts (dropdown: both, active inactive only)<br>
- default sort field<br>
- default sort direction<br>
- show info boxes (once they contain info)<br>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="wizardModal" tabindex="-1" aria-labelledby="wizardModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="wizardModalLabel">TODO Would you like to know more?</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Need to learn what's on this page?<br>
Take me to the help pages (opens in a new window or tab)
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-bs-dismiss="modal"><em class="fa-solid fa-hat-wizard"></em> Show me around</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"> <em class="fa-solid fa-right-from-bracket"></em> Take me to the documentation</button>
</div>
</div>
</div>
</div>

View File

@@ -111,12 +111,12 @@
<div class="row">
<div class="col text-end">
<div class="btn-group">
<button @click="addSplit()" class="btn btn-secondary"
:disabled="formStates.isSubmitting">{{ __('firefly.add_another_split') }}</button>
<template x-if="1 !== entries.length">
<button :disabled="formStates.isSubmitting" class="btn btn-danger text-white"
@click="removeSplit(index)">{{ __('firefly.transaction_remove_split') }}</button>
</template>
<button @click="addSplit()" class="btn btn-info"
:disabled="formStates.isSubmitting">{{ __('firefly.add_another_split') }}</button>
<button class="btn btn-success text-white" :disabled="formStates.isSubmitting"
@click="submitTransaction()">{{ __('firefly.submit') }}</button>
</div>

View File

@@ -35,7 +35,10 @@
<!-- begin date range drop down -->
<li class="nav-item dropdown">
<a class="nav-link daterange-holder" data-bs-toggle="dropdown" href="#"></a>
<a class="nav-link daterange-holder d-none d-sm-block" data-bs-toggle="dropdown" href="#"></a>
<a class="nav-link daterange-icon d-block d-sm-none" data-bs-toggle="dropdown" href="#">
<em class="fa-regular fa-calendar-days"></em>
</a>
<div class="dropdown-menu dropdown-menu-lg dropdown-menu-end">
<a href="#" class="dropdown-item daterange-current" @click="changeDateRange">

View File

@@ -14,17 +14,17 @@
</template>
</h3>
<template x-if="loading">
<p>
<p class="d-none d-xs-block">
<em class="fa-solid fa-spinner fa-spin"></em>
</p>
</template>
<template x-if="!loading && 0 !== balanceBox.amounts.length">
<p>
<p class="d-none d-sm-block">
<a href="{{ route('reports.report.default', ['allAssetAccounts',$start->format('Ymd'),$end->format('Ymd')]) }}">{{ __('firefly.in_out_period') }}</a>
</p>
</template>
<template x-if="!loading && 0 === balanceBox.amounts.length">
<p>
<p class="d-none d-sm-block">
TODO (no money in or out)
</p>
</template>
@@ -33,7 +33,7 @@
<i class="fa-solid fa-scale-balanced"></i>
</span>
<div class="small-box-footer hover-footer">
<div class="small-box-footer hover-footer d-none d-xl-block">
<template x-if="0 === balanceBox.subtitles.length">
<span>&nbsp;</span>
</template>
@@ -66,21 +66,21 @@
</h3>
</template>
<template x-if="loading">
<p>
<p class="d-none d-sm-block">
<em class="fa-solid fa-spinner fa-spin"></em>
</p>
</template>
<template x-if="!loading && billBox.unpaid.length > 0">
<p><a href="{{ route('bills.index') }}">{{ __('firefly.bills_to_pay') }}</a></p>
<p class="d-none d-sm-block"><a href="{{ route('bills.index') }}">{{ __('firefly.bills_to_pay') }}</a></p>
</template>
<template x-if="0 === billBox.unpaid.length && !loading">
<p>TODO No subscriptions are waiting to be paid</p>
<p class="d-none d-sm-block">TODO No subscriptions are waiting to be paid</p>
</template>
</div>
<span class="small-box-icon">
<em class="fa-regular fa-calendar"></em>
</span>
<span class="small-box-footer">
<span class="small-box-footer d-none d-xl-block">
<template x-if="0 === billBox.paid.length">
<span>&nbsp;</span>
</template>
@@ -117,21 +117,21 @@
</h3>
<template x-if="loading">
<p>
<p class="d-none d-sm-block">
<em class="fa-solid fa-spinner fa-spin"></em>
</p>
</template>
<template x-if="!loading && 0 !== leftBox.left.length">
<p><a href="{{ route('budgets.index') }}">{{ __('firefly.left_to_spend') }}</a></p>
<p class="d-none d-sm-block"><a href="{{ route('budgets.index') }}">{{ __('firefly.left_to_spend') }}</a></p>
</template>
<template x-if="!loading && 0 === leftBox.left.length">
<p>TODO no money is budgeted in this period</p>
<p class="d-none d-sm-block">TODO no money is budgeted in this period</p>
</template>
</div>
<span class="small-box-icon">
<em class="fa-solid fa-money-check-dollar"></em>
</span>
<span class="small-box-footer">
<span class="small-box-footer d-none d-xl-block">
<template x-if="0 !== leftBox.perDay.length">
<span>{{ __('firefly.per_day') }}:</span>
</template>
@@ -163,12 +163,12 @@
</h3>
<template x-if="loading">
<p>
<p class="d-none d-sm-block">
<em class="fa-solid fa-spinner fa-spin"></em>
</p>
</template>
<template x-if="!loading">
<p>
<p class="d-none d-sm-block">
<a href="{{ route('reports.report.default', ['allAssetAccounts','currentYearStart','currentYearEnd']) }}">{{ __('firefly.net_worth') }}</a>
</p>
</template>
@@ -176,7 +176,7 @@
<span class="small-box-icon">
<i class="fa-solid fa-chart-line"></i>
</span>
<span class="small-box-footer">
<span class="small-box-footer d-none d-xl-block">
&nbsp;
</span>
</div>

View File

@@ -6,7 +6,7 @@
<input type="search"
class="form-control ac-category"
:id="'category_name_' + index"
@keyup.enter="submitTransaction()"
@keyup="keyUpFromCategory"
x-model="transaction.category_name"
:data-index="index"
placeholder="{{ __('firefly.category') }}">

View File

@@ -1,7 +1,13 @@
<footer class="app-footer">
<!--begin::To the end-->
<div class="float-end d-none d-sm-inline">
<a href="{{ route('debug') }}">v{{ $FF_VERSION }}</a>
<a href="{{ route('debug') }}">
@if(str_starts_with($FF_VERSION, 'develop'))
<span class="text-danger">{{ $FF_VERSION }}</span>
@else
v{{ $FF_VERSION }}
@endif
</a>
</div>
<!--end::To the end-->
<!--begin::Copyright-->

View File

@@ -2,7 +2,16 @@
<a class="nav-link" href="{{ route(Route::current()->getName(), Route::current()->parameters()) }}?force_default_layout=true">
<i class="fa-solid fa-landmark"></i>
</a>
</li>
<li class="nav-item toggle-page-internals d-none">
<a class="nav-link" href="#" data-bs-toggle="modal" data-bs-target="#internalsModal">
<em class="fa-solid fa-sliders"></em>
</a>
</li>
<li class="nav-item toggle-page-wizard d-none">
<a class="nav-link" href="#" data-bs-toggle="modal" data-bs-target="#wizardModal">
<em class="fa-solid fa-hat-wizard"></em>
</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link" data-bs-toggle="dropdown" href="#">

View File

@@ -18,11 +18,6 @@
:optionalDateFields="$optionalDateFields"></x-transaction-split>
</template>
</div>
<div class="row">
<div class="col text-end">
<button class="btn btn-success" :disabled="formStates.isSubmitting" @click="submitTransaction()">Update</button>
</div>
</div>
</div>
</div>

View File

@@ -40,60 +40,38 @@
</div>
<div class="row mb-3">
<div class="col">
Nav
</div>
</div>
<div class="row mb-3">
<div class="col-xl-10 col-lg-12 col-md-12 col-sm-12 col-xs-12">
<div class="card mb-3">
<div class="card-header">
<div class="row">
<div class="col">
<h3 class="card-title">Transactions</h3>
</div>
<div class="col text-end">
</div>
</div>
</div>
<div class="card-body p-0">
<div id="grid" class="ag-theme-alpine-auto-dark ag-theme-firefly-iii" style="height: 500px">
</div>
</div>
</div>
<template x-if="!notifications.wait.show">
<nav aria-label="TODO Page navigation">
<ul class="pagination">
<template x-if="page > 1">
<li class="page-item"><a class="page-link" @click.prevent="previousPage">Previous</a></li>
</template>
<template x-for="i in totalPages">
<li :class="{'page-item': true, 'active': i === page}">
<a class="page-link" href="#" x-text="i" @click.prevent="gotoPage(i)"></a>
</li>
</template>
<template x-if="page < totalPages">
<li class="page-item"><a class="page-link" @click.prevent="nextPage" href="#">Next</a></li>
</template>
</ul>
</nav>
</template>
<div class="card">
<div class="card-header">
<div class="row">
<div class="col">
<h3 class="card-title">Transactions</h3>
</div>
<div class="col text-end">
<div class="btn-group">
<button class="btn btn-secondary btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
Options
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#">
<em class="text-danger fa-regular fa-circle-xmark"></em>
Category</a></li>
<li><a class="dropdown-item" href="#">
<em class="text-success fa-regular fa-circle-check"></em>
Budget</a></li>
<li><a class="dropdown-item" href="#">Another action</a></li>
<li><a class="dropdown-item" href="#">Something else here</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#">Separated link</a></li>
</ul>
</div>
</div>
<h3 class="card-title">Transactions</h3>
</div>
</div>
<div class="card-body p-0">
<table class="table table-hover table-striped">
<thead>
<tr>
@@ -145,7 +123,8 @@
<template x-if="transaction.split && transaction.firstSplit">
<span>I AM FIRST SPLIT</span>
</template>
<a :href="'./transactions/show/' + transaction.id" x-text="transaction.description"></a>
<a :href="'./transactions/show/' + transaction.id"
x-text="transaction.description"></a>
</td>
</template>
<template x-if="tableColumns.source.enabled">
@@ -164,16 +143,19 @@
<td>
<template x-if="'withdrawal' === transaction.type">
<span class="text-danger" x-text="formatMoney(transaction.amount*-1, transaction.currency_code)"></span>
<span class="text-danger"
x-text="formatMoney(transaction.amount*-1, transaction.currency_code)"></span>
</template>
<template x-if="'deposit' === transaction.type">
<span class="text-success" x-text="formatMoney(transaction.amount, transaction.currency_code)"></span>
<span class="text-success"
x-text="formatMoney(transaction.amount, transaction.currency_code)"></span>
</template>
<template x-if="'transfer' === transaction.type">
<span class="text-info" x-text="formatMoney(transaction.amount, transaction.currency_code)"></span>
<span class="text-info"
x-text="formatMoney(transaction.amount, transaction.currency_code)"></span>
</template>
<template
x-if="'transfer' !== transaction.type && 'deposit' !== transaction.type && 'withdrawal' !== transaction.type">