Compare commits

..

16 Commits

Author SHA1 Message Date
James Cole
58b3338f6c Expand layout 2026-05-25 19:45:50 +02:00
James Cole
eee46562be Expand index. 2026-05-25 19:36:08 +02:00
James Cole
0579049b8b Merge branch 'develop' into adminlte 2026-05-25 13:40:19 +02:00
James Cole
db73ae39d1 Merge pull request #12288 from firefly-iii/dependabot/npm_and_yarn/develop/vite-8.0.14
Bump vite from 8.0.13 to 8.0.14
2026-05-25 13:39:38 +02:00
James Cole
972d75dc41 Merge pull request #12290 from firefly-iii/dependabot/npm_and_yarn/develop/date-fns-4.3.0
Bump date-fns from 4.1.0 to 4.3.0
2026-05-25 13:39:23 +02:00
James Cole
7891c24f5c Merge pull request #12291 from firefly-iii/dependabot/npm_and_yarn/develop/webpack-5.107.1
Bump webpack from 5.105.4 to 5.107.1
2026-05-25 13:39:03 +02:00
dependabot[bot]
7fa4d67a3f Bump webpack from 5.105.4 to 5.107.1
Bumps [webpack](https://github.com/webpack/webpack) from 5.105.4 to 5.107.1.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Changelog](https://github.com/webpack/webpack/blob/main/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack/compare/v5.105.4...v5.107.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-25 06:37:17 +00:00
James Cole
5e4d6bbdb5 Merge branch 'develop' into adminlte 2026-05-25 08:22:43 +02:00
James Cole
3cd030cd69 Expand code for boxes. 2026-05-25 08:22:13 +02:00
dependabot[bot]
12baa27de9 Bump date-fns from 4.1.0 to 4.3.0
Bumps [date-fns](https://github.com/date-fns/date-fns) from 4.1.0 to 4.3.0.
- [Release notes](https://github.com/date-fns/date-fns/releases)
- [Commits](https://github.com/date-fns/date-fns/compare/v4.1.0...v4.3.0)

---
updated-dependencies:
- dependency-name: date-fns
  dependency-version: 4.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-25 04:22:37 +00:00
dependabot[bot]
9e60d0ca0d Bump vite from 8.0.13 to 8.0.14
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 8.0.13 to 8.0.14.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.0.14/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 8.0.14
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-25 04:22:28 +00:00
James Cole
b3bf43e6b2 Update pages. 2026-05-24 18:44:29 +02:00
James Cole
8115294582 Expand views and todos. 2026-05-24 16:22:14 +02:00
James Cole
b99c97740d Fix some icons 2026-05-24 14:15:47 +02:00
James Cole
e511b55c76 Expand v2 and v3 code. 2026-05-24 14:10:38 +02:00
James Cole
9ccb8e8527 Add new layout stuff. 2026-05-21 19:04:18 +02:00
60 changed files with 2951 additions and 167 deletions

3
.gitignore vendored
View File

@@ -28,3 +28,6 @@ public/v1/js/webhooks
resources/assets/v2/node_modules
resources/assets/v2/build
public/v2/i18n
# ignore v3 build files
resources/assets/v3/node_modules

View File

@@ -22,8 +22,13 @@
declare(strict_types=1);
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Steam;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use function Safe\mb_ord;
use function Safe\preg_match;
use function Safe\preg_replace_callback;
@@ -32,7 +37,7 @@ if (!function_exists('env_default_when_empty')) {
/**
* @return null|mixed
*/
function env_default_when_empty(mixed $value, bool|int|string|null $default = null): mixed
function env_default_when_empty(mixed $value, bool | int | string | null $default = null): mixed
{
if (null === $value) {
return $default;
@@ -45,6 +50,59 @@ if (!function_exists('env_default_when_empty')) {
}
}
if (!function_exists('bladeAccountBalance')) {
function bladeAccountBalance(\FireflyIII\Models\Account $account): string
{
/** @var Carbon $date */
$date = now();
// get the date from the current session. If it's in the future, keep `now()`.
/** @var Carbon $session */
$session = clone session('end', today(config('app.timezone'))->endOfMonth());
if ($session->lt($date)) {
$date = $session->copy();
$date->endOfDay();
}
Log::debug(sprintf('twig balance: Call finalAccountBalance with date/time "%s"', $date->toIso8601String()));
// 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized.
$info = Steam::accountsBalancesOptimized(new Collection()->push($account), $date)[$account->id];
// $info = Steam::finalAccountBalance($account, $date);
$currency = Steam::getAccountCurrency($account);
$primary = Amount::getPrimaryCurrency();
$convertToPrimary = Amount::convertToPrimary();
$usePrimary = $convertToPrimary && $primary->id !== $currency->id;
$currency ??= $primary;
$strings = [];
foreach ($info as $key => $balance) {
if ('balance' === $key) {
// balance in account currency.
if (!$usePrimary) {
$strings[] = Amount::formatAnything($currency, $balance, false);
}
continue;
}
if ('pc_balance' === $key) {
// balance in primary currency.
if ($usePrimary) {
$strings[] = Amount::formatAnything($primary, $balance, false);
}
continue;
}
// for multi currency accounts.
if ($usePrimary && $key !== $primary->code) {
$strings[] = Amount::formatAnything(Amount::getTransactionCurrencyByCode($key), $balance, false);
}
}
return implode(', ', $strings);
}
}
if (!function_exists('string_is_equal')) {
function string_is_equal(string $left, string $right): bool
{
@@ -64,14 +122,14 @@ if (!function_exists('blade_escape_js')) {
return preg_replace_callback(
'#[^a-zA-Z0-9,\._]#Su',
static function ($matches) {
$char = $matches[0];
$char = $matches[0];
/*
* A few characters have short escape sequences in JSON and JavaScript.
* Escape sequences supported only by JavaScript, not JSON, are omitted.
* \" is also supported but omitted, because the resulting string is not HTML safe.
*/
$short = match ($char) {
$short = match ($char) {
'\\' => '\\\\',
'/' => '\/',
"\x08" => '\b',
@@ -93,9 +151,9 @@ if (!function_exists('blade_escape_js')) {
// Split characters outside the BMP into surrogate pairs
// https://tools.ietf.org/html/rfc2781.html#section-2.1
$u = $codepoint - 0x10_000;
$high = 0xD800 | ($u >> 10);
$low = 0xDC00 | ($u & 0x3FF);
$u = $codepoint - 0x10_000;
$high = 0xD800 | ($u >> 10);
$low = 0xDC00 | ($u & 0x3FF);
return \sprintf('\u%04X\u%04X', $high, $low);
},

View File

@@ -118,11 +118,19 @@ class ProcessesUpdatedTransactionGroup
$all = $group->transactionJournals()->get()->pluck('id')->toArray();
/** @var Account $sourceAccount */
$sourceAccount = $first->transactions()->where('amount', '<', '0')->first()->account;
/** @var Account|null $sourceAccount */
$sourceAccount = $first->transactions()->where('amount', '<', '0')->first()?->account;
/** @var Account $destAccount */
$destAccount = $first->transactions()->where('amount', '>', '0')->first()->account;
/** @var Account|null $destAccount */
$destAccount = $first->transactions()->where('amount', '>', '0')->first()?->account;
if(null === $destAccount) {
Log::warning(sprintf('Group #%d (journal #%d) has no destination account. Break.', $group->id, $first->id));
return 0;
}
if(null === $sourceAccount) {
Log::warning(sprintf('Group #%d (journal #%d) has no source account. Break.', $group->id, $first->id));
return 0;
}
$type = $first->transactionType->type;
$effect = 0;

View File

@@ -23,7 +23,9 @@ declare(strict_types=1);
namespace FireflyIII\Providers;
use FireflyIII\Models\Account;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Schema;
@@ -57,6 +59,24 @@ class AppServiceProvider extends ServiceProvider
return response()->json($value)->withHeaders($headers);
});
// blade extension for active menu link
Blade::directive('menuItemActive', function (string $route): string {
$name = Route::getCurrentRoute()->getName() ?? '';
Log::debug(sprintf('menuItemActive("%s", "%s")', $route, $name));
if (str_contains($route, $name)) {
return 'active';
}
return '';
});
// blade extension for account balance.
Blade::directive('balance', function (string $account): string {
var_dump($account);exit;
return $account;
return 'blablabla';
});
// blade extension
Blade::directive('activeXRoutePartial', function (string $route): string {
$name = Route::getCurrentRoute()->getName() ?? '';

View File

@@ -203,6 +203,8 @@ class JournalUpdateService
/**
* Get destination transaction.
*
* @throws FireflyException
*/
private function getDestinationTransaction(): Transaction
{
@@ -211,6 +213,9 @@ class JournalUpdateService
$result = $this->transactionJournal->transactions()->where('amount', '>', 0)->first();
$this->destinationTransaction = $result;
}
if(null === $this->destinationTransaction) {
throw new FireflyException(sprintf('Destination transaction for transaction group #%d could not be found.', $this->transactionGroup->transaction_group_id ?? 0));
}
return $this->destinationTransaction;
}

View File

@@ -875,4 +875,31 @@ class Steam
return $return;
}
/**
* Will format the amount by the currency related to the given account.
*/
public function formatAmountBySymbol(string $amount, ?string $symbol = null, ?int $decimalPlaces = null, ?bool $coloured = null): string
{
if (null === $symbol) {
$message = sprintf(
'formatAmountBySymbol("%s", %s, %d, %s) was called without a symbol. Please browse to /flush to clear your cache.',
$amount,
var_export($symbol, true),
$decimalPlaces,
var_export($coloured, true)
);
Log::error($message);
$currency = Amount::getPrimaryCurrency();
}
if (null !== $symbol) {
$decimalPlaces ??= 2;
$coloured ??= true;
$currency = new TransactionCurrency();
$currency->symbol = $symbol;
$currency->decimal_places = $decimalPlaces;
}
return Amount::formatAnything($currency, $amount, $coloured);
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace FireflyIII\View\Components\Dashboard;
use Carbon\Carbon;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class Boxes extends Component
{
/**
* Create a new component instance.
*/
public function __construct(public Carbon $start, public Carbon $end)
{
//
}
/**
* Get the view / contents that represent the component.
*/
public function render(): View|Closure|string
{
return view('components.dashboard.boxes');
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace FireflyIII\View\Components\Generic;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class Amount extends Component
{
/**
* Create a new component instance.
*/
public function __construct(public array $transaction)
{
//
}
/**
* Get the view / contents that represent the component.
*/
public function render(): View|Closure|string
{
return view('components.generic.amount');
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace FireflyIII\View\Components\Layout;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class CreateMenu extends Component
{
/**
* Create a new component instance.
*/
public function __construct()
{
//
}
/**
* Get the view / contents that represent the component.
*/
public function render(): View|Closure|string
{
return view('components.layout.create-menu');
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace FireflyIII\View\Components\Layout;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class FavIcons extends Component
{
/**
* Create a new component instance.
*/
public function __construct()
{
//
}
/**
* Get the view / contents that represent the component.
*/
public function render(): View|Closure|string
{
return view('components.layout.fav-icons');
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace FireflyIII\View\Components\Layout;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class Sidebar extends Component
{
/**
* Create a new component instance.
*/
public function __construct()
{
//
}
/**
* Get the view / contents that represent the component.
*/
public function render(): View|Closure|string
{
return view('components.layout.sidebar');
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace FireflyIII\View\Components\Layout;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class TopBoxes extends Component
{
/**
* Create a new component instance.
*/
public function __construct()
{
//
}
/**
* Get the view / contents that represent the component.
*/
public function render(): View|Closure|string
{
return view('components.layout.top-boxes');
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace FireflyIII\View\Components\Layout;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class UserMenu extends Component
{
/**
* Create a new component instance.
*/
public function __construct()
{
//
}
/**
* Get the view / contents that represent the component.
*/
public function render(): View|Closure|string
{
return view('components.layout.user-menu');
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace FireflyIII\View\Components\Lists;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class GroupsTiny extends Component
{
/**
* Create a new component instance.
*/
public function __construct(public array $transactions)
{
//
}
/**
* Get the view / contents that represent the component.
*/
public function render(): View|Closure|string
{
return view('components.lists.groups-tiny');
}
}

View File

@@ -49,7 +49,7 @@ return [
|
*/
'view' => 'partials/layout/breadcrumbs',
'view' => 'partials/layout/breadcrumbs-v3',
/*
|--------------------------------------------------------------------------

322
package-lock.json generated
View File

@@ -7,7 +7,8 @@
"hasInstallScript": true,
"workspaces": [
"resources/assets/v1",
"resources/assets/v2"
"resources/assets/v2",
"resources/assets/v3"
],
"dependencies": {
"patch-package": "^8.0.1"
@@ -1915,9 +1916,9 @@
}
},
"node_modules/@oxc-project/types": {
"version": "0.130.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.130.0.tgz",
"integrity": "sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==",
"version": "0.132.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.132.0.tgz",
"integrity": "sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==",
"dev": true,
"license": "MIT",
"funding": {
@@ -2259,9 +2260,9 @@
}
},
"node_modules/@rolldown/binding-android-arm64": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.1.tgz",
"integrity": "sha512-fJI3I0r3C3Oj/zdBCpaCmBRZYf07xpaq4yCfDDoSFm+beWNzbIl26puW8RraUdugoJw/95zerNOn6jasAhzSmg==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.2.tgz",
"integrity": "sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==",
"cpu": [
"arm64"
],
@@ -2276,9 +2277,9 @@
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.1.tgz",
"integrity": "sha512-cKnAhWEsV7TPcA/5EAteDp6KcJZBQ2G+BqE7zayMMi7kMvwRsbv7WT9aOnn0WNl4SKEIf43vjS31iUPu80nzXg==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.2.tgz",
"integrity": "sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==",
"cpu": [
"arm64"
],
@@ -2293,9 +2294,9 @@
}
},
"node_modules/@rolldown/binding-darwin-x64": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.1.tgz",
"integrity": "sha512-YKrVwQjIRBPo+5G/u03wGjbdy4q7pyzCe93DK9VJ7zkVmeg8LJ7GbgsiHWdR4xSoe4CAXRD7Bcjgbtr64bkXNg==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.2.tgz",
"integrity": "sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==",
"cpu": [
"x64"
],
@@ -2310,9 +2311,9 @@
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.1.tgz",
"integrity": "sha512-z/oBsREo46SsFqBwYtFe0kpJeBijAT48O/WXLI4suiCLBkr03RTtTJMCzSdDd2znlh8VJizL09XVkQgk8IZonw==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.2.tgz",
"integrity": "sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==",
"cpu": [
"x64"
],
@@ -2327,9 +2328,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.1.tgz",
"integrity": "sha512-ik8q7GM11zxvYxFc2PeDcT6TBvhCQMaUxfph/M5l9sKuTs/Sjg3L+Byw0F7w0ZVLBZmx30P+gG0ECzzN+MFcmQ==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.2.tgz",
"integrity": "sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==",
"cpu": [
"arm"
],
@@ -2344,9 +2345,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.1.tgz",
"integrity": "sha512-QoSx2EkyrrdZ6kcyE8stqZ62t0Yra8Fs5ia9lOxJrh6TMQJK7gQKmscdTHf7pOXKREKrVwOtJcQG3qVSfc866A==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.2.tgz",
"integrity": "sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==",
"cpu": [
"arm64"
],
@@ -2361,9 +2362,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.1.tgz",
"integrity": "sha512-uwNwFpwKeNiZawfAWBgg0VIztPTV3ihhh1vV334h9ivnNLorxnQMU6Fz8wG1Zb4Qh9LC1/MkcyT3YlDXG3Rsgg==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.2.tgz",
"integrity": "sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==",
"cpu": [
"arm64"
],
@@ -2378,9 +2379,9 @@
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.1.tgz",
"integrity": "sha512-zY1bul7OWr7DFBiJ++wofXvnr8B45ce3QsQUhKrIhXsygAh7bTkwyeM1bi1a2g5C/yC/N8TZyGDEoMfm/l9mpg==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.2.tgz",
"integrity": "sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==",
"cpu": [
"ppc64"
],
@@ -2395,9 +2396,9 @@
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.1.tgz",
"integrity": "sha512-0frlsT/f4Ft6I7SMESTKnF3cZsdicQn1dCMkF/jT9wDLE+gGoiQfv1nmT9e+s7s/fekvvy6tZM2jHvI2tkbJDQ==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.2.tgz",
"integrity": "sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==",
"cpu": [
"s390x"
],
@@ -2412,9 +2413,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.1.tgz",
"integrity": "sha512-XABVmGp9Tg0WspTVvwduTc4fpqy6JnAUrSQe6OuyqD/03nI7r0O9OWUkMIwFrjKAIqolvqoA4ZrJppgwE0Gxmw==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.2.tgz",
"integrity": "sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==",
"cpu": [
"x64"
],
@@ -2429,9 +2430,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.1.tgz",
"integrity": "sha512-bV4fzswuzVcKD90o/VM6QqKxnxlDq0g2BISDLNVmxrnhpv1DDbyPhCIjYfvzYLV+MvkKKnQt2Q6AO86SEBULUQ==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.2.tgz",
"integrity": "sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==",
"cpu": [
"x64"
],
@@ -2446,9 +2447,9 @@
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.1.tgz",
"integrity": "sha512-/Mh0Zhq3OP7fVs0kcQHZP6lZEthMGTaSf8UBQYSFEZDWGXXlEC+nJ6EqenaK2t4LBXMe3A+K/G2BVXXdtOr4PQ==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.2.tgz",
"integrity": "sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==",
"cpu": [
"arm64"
],
@@ -2463,9 +2464,9 @@
}
},
"node_modules/@rolldown/binding-wasm32-wasi": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.1.tgz",
"integrity": "sha512-+1xc9X45l8ufsBAm6Gjvx2qDRIY9lTVt0cgWNcJ+1gdhXvkbxePA60yRTwSTuXL09CMhyJmjpV7E3NoyxbqFQQ==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.2.tgz",
"integrity": "sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==",
"cpu": [
"wasm32"
],
@@ -2482,9 +2483,9 @@
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.1.tgz",
"integrity": "sha512-1D+UqZdfnuR+Jy1GgMJwi85bD40H21uNmOPRWQhw4oRSuolZ/B5rixZ45DK2KXOTCvmVCecauWgEhbw8bI7tOw==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.2.tgz",
"integrity": "sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==",
"cpu": [
"arm64"
],
@@ -2499,9 +2500,9 @@
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.1.tgz",
"integrity": "sha512-INAycaWuhlOK3wk4mRHGsdgwYWmd9cChdPdE9bwWmy6rn9VqVNYNFGhOdXrofXUxwHIncSiPNb8tNm8knDVIeQ==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.2.tgz",
"integrity": "sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==",
"cpu": [
"x64"
],
@@ -2631,28 +2632,6 @@
"@types/node": "*"
}
},
"node_modules/@types/eslint": {
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
"integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "*",
"@types/json-schema": "*"
}
},
"node_modules/@types/eslint-scope": {
"version": "3.7.7",
"resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz",
"integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/eslint": "*",
"@types/estree": "*"
}
},
"node_modules/@types/estree": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
@@ -3848,6 +3827,22 @@
"@popperjs/core": "^2.11.8"
}
},
"node_modules/bootstrap-icons": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.13.1.tgz",
"integrity": "sha512-ijombt4v6bv5CLeXvRWKy7CuM3TRTuPEuGaGKvTV5cz65rQSY8RQ2JcHt6b90cBBAC7s8fsf2EkQDldzCoXUjw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
],
"license": "MIT"
},
"node_modules/bootstrap-sass": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/bootstrap-sass/-/bootstrap-sass-3.4.3.tgz",
@@ -4498,6 +4493,47 @@
"dev": true,
"license": "MIT"
},
"node_modules/concurrently": {
"version": "9.2.1",
"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz",
"integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==",
"dev": true,
"license": "MIT",
"dependencies": {
"chalk": "4.1.2",
"rxjs": "7.8.2",
"shell-quote": "1.8.3",
"supports-color": "8.1.1",
"tree-kill": "1.2.2",
"yargs": "17.7.2"
},
"bin": {
"conc": "dist/bin/concurrently.js",
"concurrently": "dist/bin/concurrently.js"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
}
},
"node_modules/concurrently/node_modules/supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
"node_modules/connect-history-api-fallback": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz",
@@ -4995,9 +5031,9 @@
"license": "MIT"
},
"node_modules/date-fns": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.3.0.tgz",
"integrity": "sha512-OYcL+3N/jyWbYdFGqoMAhytDgxP9pbYPUUiRCOgn4Fewaadk9l/Wam4Avciiyp2BgkpfQyBV9B+ehnVJych+eQ==",
"license": "MIT",
"funding": {
"type": "github",
@@ -8466,6 +8502,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/overlayscrollbars": {
"version": "2.16.0",
"resolved": "https://registry.npmjs.org/overlayscrollbars/-/overlayscrollbars-2.16.0.tgz",
"integrity": "sha512-N03oje/q7j93D0aLZtoCdsDSYLmhheSsv8H7oSLE7HhdV9P/bmCURtLV/KbPye7P/bpfyt/obSfDpGUYoJ0OWg==",
"license": "MIT"
},
"node_modules/p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
@@ -9917,13 +9959,13 @@
}
},
"node_modules/rolldown": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.1.tgz",
"integrity": "sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.2.tgz",
"integrity": "sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@oxc-project/types": "=0.130.0",
"@oxc-project/types": "=0.132.0",
"@rolldown/pluginutils": "^1.0.0"
},
"bin": {
@@ -9933,21 +9975,21 @@
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
"@rolldown/binding-android-arm64": "1.0.1",
"@rolldown/binding-darwin-arm64": "1.0.1",
"@rolldown/binding-darwin-x64": "1.0.1",
"@rolldown/binding-freebsd-x64": "1.0.1",
"@rolldown/binding-linux-arm-gnueabihf": "1.0.1",
"@rolldown/binding-linux-arm64-gnu": "1.0.1",
"@rolldown/binding-linux-arm64-musl": "1.0.1",
"@rolldown/binding-linux-ppc64-gnu": "1.0.1",
"@rolldown/binding-linux-s390x-gnu": "1.0.1",
"@rolldown/binding-linux-x64-gnu": "1.0.1",
"@rolldown/binding-linux-x64-musl": "1.0.1",
"@rolldown/binding-openharmony-arm64": "1.0.1",
"@rolldown/binding-wasm32-wasi": "1.0.1",
"@rolldown/binding-win32-arm64-msvc": "1.0.1",
"@rolldown/binding-win32-x64-msvc": "1.0.1"
"@rolldown/binding-android-arm64": "1.0.2",
"@rolldown/binding-darwin-arm64": "1.0.2",
"@rolldown/binding-darwin-x64": "1.0.2",
"@rolldown/binding-freebsd-x64": "1.0.2",
"@rolldown/binding-linux-arm-gnueabihf": "1.0.2",
"@rolldown/binding-linux-arm64-gnu": "1.0.2",
"@rolldown/binding-linux-arm64-musl": "1.0.2",
"@rolldown/binding-linux-ppc64-gnu": "1.0.2",
"@rolldown/binding-linux-s390x-gnu": "1.0.2",
"@rolldown/binding-linux-x64-gnu": "1.0.2",
"@rolldown/binding-linux-x64-musl": "1.0.2",
"@rolldown/binding-openharmony-arm64": "1.0.2",
"@rolldown/binding-wasm32-wasi": "1.0.2",
"@rolldown/binding-win32-arm64-msvc": "1.0.2",
"@rolldown/binding-win32-x64-msvc": "1.0.2"
}
},
"node_modules/run-parallel": {
@@ -9974,6 +10016,16 @@
"queue-microtask": "^1.2.2"
}
},
"node_modules/rxjs": {
"version": "7.8.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -10767,6 +10819,13 @@
"node": ">=10.13.0"
}
},
"node_modules/tailwindcss": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz",
"integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==",
"dev": true,
"license": "MIT"
},
"node_modules/tapable": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
@@ -11052,6 +11111,16 @@
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
"node_modules/tree-kill": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
"integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
"dev": true,
"license": "MIT",
"bin": {
"tree-kill": "cli.js"
}
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@@ -11297,6 +11366,10 @@
"resolved": "resources/assets/v2",
"link": true
},
"node_modules/v3": {
"resolved": "resources/assets/v3",
"link": true
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -11308,16 +11381,16 @@
}
},
"node_modules/vite": {
"version": "8.0.13",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.13.tgz",
"integrity": "sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==",
"version": "8.0.14",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.14.tgz",
"integrity": "sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==",
"dev": true,
"license": "MIT",
"dependencies": {
"lightningcss": "^1.32.0",
"picomatch": "^4.0.4",
"postcss": "^8.5.14",
"rolldown": "1.0.1",
"postcss": "^8.5.15",
"rolldown": "1.0.2",
"tinyglobby": "^0.2.16"
},
"bin": {
@@ -11616,13 +11689,12 @@
"license": "BSD-2-Clause"
},
"node_modules/webpack": {
"version": "5.105.4",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.4.tgz",
"integrity": "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==",
"version": "5.107.1",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.107.1.tgz",
"integrity": "sha512-mvdIWxj/H6QsfgDdH9djne3a5dYcmEmtsXGESkypaGN5jXjF/b+9KDlmTDQ2TKlFUeA2fI9Y65kihD30JOdB+Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.8",
"@types/json-schema": "^7.0.15",
"@webassemblyjs/ast": "^1.14.1",
@@ -11632,21 +11704,20 @@
"acorn-import-phases": "^1.0.3",
"browserslist": "^4.28.1",
"chrome-trace-event": "^1.0.2",
"enhanced-resolve": "^5.20.0",
"es-module-lexer": "^2.0.0",
"enhanced-resolve": "^5.21.4",
"es-module-lexer": "^2.1.0",
"eslint-scope": "5.1.1",
"events": "^3.2.0",
"glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.2.11",
"json-parse-even-better-errors": "^2.3.1",
"loader-runner": "^4.3.1",
"mime-types": "^2.1.27",
"loader-runner": "^4.3.2",
"mime-db": "^1.54.0",
"neo-async": "^2.6.2",
"schema-utils": "^4.3.3",
"tapable": "^2.3.0",
"terser-webpack-plugin": "^5.3.17",
"terser-webpack-plugin": "^5.5.0",
"watchpack": "^2.5.1",
"webpack-sources": "^3.3.4"
"webpack-sources": "^3.4.1"
},
"bin": {
"webpack": "bin/webpack.js"
@@ -12010,6 +12081,16 @@
"dev": true,
"license": "MIT"
},
"node_modules/webpack/node_modules/mime-db": {
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/webpack/node_modules/schema-utils": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
@@ -12258,7 +12339,7 @@
},
"resources/assets/v1": {
"dependencies": {
"date-fns": "4.1.0",
"date-fns": "^4.3.0",
"stream-browserify": "^3.0.0"
},
"devDependencies": {
@@ -12276,7 +12357,7 @@
"vue-i18n": "^8",
"vue-loader": "^15",
"vue-template-compiler": "^2.7",
"webpack": "~5.105.4"
"webpack": "^5.107.1"
}
},
"resources/assets/v2": {
@@ -12292,7 +12373,7 @@
"chart.js": "^4",
"chartjs-adapter-date-fns": "^3.0.0",
"chartjs-chart-sankey": "^0.14.0",
"date-fns": "4.1.0",
"date-fns": "^4.3.0",
"i18next": "^26.0.3",
"i18next-chained-backend": "^5.0.0",
"i18next-http-backend": "^3.0.1",
@@ -12305,7 +12386,22 @@
"laravel-vite-plugin": "^3",
"patch-package": "^8",
"sass": "^1",
"vite": "=8.0.13",
"vite": "^8.0.14",
"vite-plugin-manifest-sri": "^0.2.0"
}
},
"resources/assets/v3": {
"dependencies": {
"admin-lte": "^4.0.0",
"alpinejs": "^3.15.12",
"bootstrap-icons": "^1.13.1",
"overlayscrollbars": "^2.16.0"
},
"devDependencies": {
"concurrently": "^9.0.1",
"laravel-vite-plugin": "^3.1",
"tailwindcss": "^4.0.0",
"vite": "^8.0.0",
"vite-plugin-manifest-sri": "^0.2.0"
}
}

View File

@@ -4,7 +4,8 @@
},
"workspaces": [
"resources/assets/v1",
"resources/assets/v2"
"resources/assets/v2",
"resources/assets/v3"
],
"devDependencies": {
"postcss": "^8.5.14"

0
public/v1/js/.gitkeep Normal file → Executable file
View File

1
public/v1/js/css/app.css Executable file
View File

@@ -0,0 +1 @@

1
public/v1/js/css/layout.css Executable file
View File

@@ -0,0 +1 @@

View File

@@ -22,6 +22,8 @@
"/public/v1/js/app_vue.js.LICENSE.txt": "/public/v1/js/app_vue.js.LICENSE.txt",
"/public/v1/js/create_transaction.js": "/public/v1/js/create_transaction.js",
"/public/v1/js/create_transaction.js.LICENSE.txt": "/public/v1/js/create_transaction.js.LICENSE.txt",
"/public/v1/js/css/app.css": "/public/v1/js/css/app.css",
"/public/v1/js/css/layout.css": "/public/v1/js/css/layout.css",
"/public/v1/js/edit_transaction.js": "/public/v1/js/edit_transaction.js",
"/public/v1/js/edit_transaction.js.LICENSE.txt": "/public/v1/js/edit_transaction.js.LICENSE.txt",
"/public/v1/js/exchange-rates/index.js": "/public/v1/js/exchange-rates/index.js",
@@ -97,6 +99,10 @@
"/public/v1/js/ff/transactions/mass/edit-bulk.js": "/public/v1/js/ff/transactions/mass/edit-bulk.js",
"/public/v1/js/ff/transactions/mass/edit.js": "/public/v1/js/ff/transactions/mass/edit.js",
"/public/v1/js/ff/transactions/show.js": "/public/v1/js/ff/transactions/show.js",
"/public/v1/js/js/layout.js": "/public/v1/js/js/layout.js",
"/public/v1/js/js/layout.js.LICENSE.txt": "/public/v1/js/js/layout.js.LICENSE.txt",
"/public/v1/js/layout.js": "/public/v1/js/layout.js",
"/public/v1/js/layout.js.LICENSE.txt": "/public/v1/js/layout.js.LICENSE.txt",
"/public/v1/js/lib/Chart.bundle.min.js": "/public/v1/js/lib/Chart.bundle.min.js",
"/public/v1/js/lib/accounting.min.js": "/public/v1/js/lib/accounting.min.js",
"/public/v1/js/lib/bootstrap-multiselect.js": "/public/v1/js/lib/bootstrap-multiselect.js",

View File

@@ -10,7 +10,7 @@
"prod": "mix --production"
},
"dependencies": {
"date-fns": "4.1.0",
"date-fns": "4.3.0",
"stream-browserify": "^3.0.0"
},
"devDependencies": {
@@ -28,6 +28,6 @@
"vue-i18n": "^8",
"vue-loader": "^15",
"vue-template-compiler": "^2.7",
"webpack": "~5.105.4"
"webpack": "~5.107.1"
}
}

View File

@@ -12,7 +12,7 @@
"laravel-vite-plugin": "^3",
"patch-package": "^8",
"sass": "^1",
"vite": "=8.0.13",
"vite": "=8.0.14",
"vite-plugin-manifest-sri": "^0.2.0"
},
"dependencies": {
@@ -26,7 +26,7 @@
"chart.js": "^4",
"chartjs-adapter-date-fns": "^3.0.0",
"chartjs-chart-sankey": "^0.14.0",
"date-fns": "4.1.0",
"date-fns": "4.3.0",
"i18next": "^26.0.3",
"i18next-chained-backend": "^5.0.0",
"i18next-http-backend": "^3.0.1",

View File

@@ -0,0 +1,21 @@
/*
* app.css
* Copyright (c) 2026 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/>.
*/
/** here **/

View File

@@ -0,0 +1,34 @@
/*
* basic.js
* Copyright (c) 2021 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 {api} from "../../boot/axios";
export default class Preferences {
getByName(name) {
return api.get('/api/v1/preferences/' + name);
}
getByNameNow(name) {
return api.get('/api/v1/preferences/' + name);
}
postByName(name, value) {
return api.post('/api/v1/preferences', {name: name, data: value});
}
}

View File

@@ -0,0 +1,28 @@
/*
* post.js
* Copyright (c) 2022 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 {api} from "../../boot/axios";
export default class Post {
post(name, value) {
let url = '/api/v1/preferences';
return api.post(url, {name: name, data: value});
}
}

View File

@@ -0,0 +1,28 @@
/*
* index.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 {api} from "../../boot/axios.js";
export default class Summary {
get(start, end, code) {
return api.get('/api/v1/summary/basic', {params: {start: start, end: end, code: code}});
}
}

View File

@@ -0,0 +1,21 @@
/*
* app.js
* Copyright (c) 2026 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/>.
*/
// unused, see the specific pages.

View File

@@ -0,0 +1,40 @@
/*
* axios.js
* Copyright (c) 2022 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 axios from 'axios'
// Be careful when using SSR for cross-request state pollution
// due to creating a Singleton instance here;
// If any client changes this (global) instance, it might be a
// good idea to move this instance creation inside of the
// "export default () => {}" function below (which runs individually
// for each client)
// for use inside Vue files (Options API) through this.$axios and this.$api
const url = '/';
const api = axios.create({baseURL: url, withCredentials: true});
axios.defaults.withCredentials = true;
axios.defaults.baseURL = url;
export {api}

View File

@@ -0,0 +1,82 @@
/*
* bootstrap.js
* Copyright (c) 2026 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/>.
*/
// JS
import "bootstrap"
import "admin-lte"
import Alpine from 'alpinejs'
import store from "store";
import axios from 'axios';
import observePlugin from 'store/plugins/observe';
import {getFreshVariable} from "v2/src/store/get-fresh-variable.js";
import {getVariable} from "v2/src/store/get-variable.js";
import {getViewRange} from "v2/src/support/get-viewrange.js";
import {loadTranslations} from "v2/src/support/load-translations.js";
store.addPlugin(observePlugin);
window.bootstrapped = false;
window.store = store;
window.Alpine = Alpine
window.axios = axios;
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
// always grab the preference "marker" from Firefly III.
getFreshVariable('lastActivity').then((serverValue) => {
if(null === serverValue) {
console.log('Server value is null in getFreshVariable.');
throw new Error('401 in getFreshVariable.');
}
const localValue = store.get('lastActivity');
store.set('cacheValid', localValue === serverValue);
store.set('lastActivity', serverValue);
console.log('Server value: ' + serverValue);
console.log('Local value: ' + localValue);
console.log('Cache valid: ' + (localValue === serverValue));
}).then(() => {
Promise.all([
getVariable('viewRange'),
getVariable('darkMode'),
getVariable('locale'),
getVariable('language')
]).then((values) => {
if (!store.get('start') || !store.get('end')) {
// calculate new start and end, and store them.
const range = getViewRange(values[0], new Date);
store.set('start', range.start);
store.set('end', range.end);
}
// save local in window.__ something
window.__localeId__ = values[2];
store.set('language', values[3]);
store.set('locale', values[3]);
loadTranslations(values[3]).then(() => {
const event = new Event('firefly-iii-bootstrapped');
document.dispatchEvent(event);
window.bootstrapped = true;
console.log('Bootstrapped!');
});
});
}).catch((error) => {
console.error('Error while bootstrapping: ' + error);
});

View File

@@ -0,0 +1,190 @@
/*
* boxes.js
* Copyright (c) 2026 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 Summary from "../../api/summary/index.js";
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 () => ({
balanceBox: {amounts: [], subtitles: []},
billBox: {paid: [], unpaid: []},
leftBox: {left: [], perDay: []},
netBox: {net: []},
convertToPrimary: false,
loading: false,
boxData: null,
boxOptions: null,
eventListeners: {
['@convert-to-primary.window'](event){
this.convertToPrimary = event.detail;
this.accountList = [];
console.log('I heard that! (dashboard/boxes)');
this.boxData = null;
this.loadBoxes();
}
},
getFreshData() {
const start = new Date(window.store.get('start'));
const end = new Date(window.store.get('end'));
// TODO cache key is hard coded, problem?
const boxesCacheKey = getCacheKey('ds_boxes_data', {convertToPrimary: this.convertToPrimary, start: start, end: end});
cleanupCache();
//const cacheValid = window.store.get('cacheValid');
let cachedData = window.store.get(boxesCacheKey);
const cacheValid = false; // force refresh
if (cacheValid && typeof cachedData !== 'undefined') {
this.boxData = cachedData;
this.generateOptions(this.boxData);
return;
}
// get stuff
let getter = new Summary();
getter.get(format(start, 'yyyy-MM-dd'), format(end, 'yyyy-MM-dd'), null).then((response) => {
this.boxData = response.data;
window.store.set(boxesCacheKey, response.data);
this.generateOptions(this.boxData);
});
},
generateOptions(data) {
this.balanceBox = {amounts: [], subtitles: []};
this.billBox = {paid: [], unpaid: []};
this.leftBox = {left: [], perDay: []};
this.netBox = {net: []};
let subtitles = {};
// process new content:
for (const i in data) {
if (data.hasOwnProperty(i)) {
const current = data[i];
if (!current.hasOwnProperty('key')) {
continue;
}
let key = current.key;
// console.log('NOT PRIMARY CURRENCY');
if (key.startsWith('balance-in-')) {
this.balanceBox.amounts.push(formatMoney(current.monetary_value, current.currency_code));
continue;
}
// spent info is used in subtitle:
if (key.startsWith('spent-in-')) {
// prep subtitles (for later)
if (!subtitles.hasOwnProperty(current.currency_code)) {
subtitles[current.currency_code] = '';
}
// append the amount spent.
subtitles[current.currency_code] =
subtitles[current.currency_code] +
formatMoney(current.monetary_value, current.currency_code);
continue;
}
// earned info is used in subtitle:
if (key.startsWith('earned-in-')) {
// prep subtitles (for later)
if (!subtitles.hasOwnProperty(current.currency_code)) {
subtitles[current.currency_code] = '';
}
// prepend the amount earned.
subtitles[current.currency_code] =
formatMoney(current.monetary_value, current.currency_code) + ' + ' +
subtitles[current.currency_code];
continue;
}
if (key.startsWith('bills-unpaid-in-')) {
this.billBox.unpaid.push(formatMoney(current.monetary_value, current.currency_code));
continue;
}
if (key.startsWith('bills-paid-in-')) {
this.billBox.paid.push(formatMoney(current.monetary_value, current.currency_code));
continue;
}
if (key.startsWith('left-to-spend-in-')) {
this.leftBox.left.push(formatMoney(current.monetary_value, current.currency_code));
continue;
}
if (key.startsWith('left-per-day-to-spend-in-')) {
this.leftBox.perDay.push(formatMoney(current.monetary_value, current.currency_code));
continue;
}
if (key.startsWith('net-worth-in-')) {
this.netBox.net.push(formatMoney(current.monetary_value, current.currency_code));
}
}
}
for (let i in subtitles) {
if (subtitles.hasOwnProperty(i)) {
this.balanceBox.subtitles.push(subtitles[i]);
}
}
this.loading = false;
},
loadBoxes() {
if (true === this.loading) {
return;
}
this.loading = true;
if (null === this.boxData) {
this.getFreshData();
return;
}
this.generateOptions(this.boxData);
this.loading = false;
},
// Getter
init() {
// console.log('boxes init');
// TODO can be replaced by "getVariables"
Promise.all([getVariable('viewRange'), getVariable('convert_to_primary', false)]).then((values) => {
// console.log('boxes after promises');
afterPromises = true;
this.convertToPrimary = values[1];
this.loadBoxes();
});
window.store.observe('end', () => {
if (!afterPromises) {
return;
}
// console.log('boxes observe end');
this.boxData = null;
this.loadBoxes();
});
window.store.observe('convert_to_primary', (newValue) => {
if (!afterPromises) {
return;
}
// console.log('boxes observe convertToPrimary');
this.convertToPrimary = newValue;
this.loadBoxes();
});
},
});

View File

@@ -0,0 +1,65 @@
/*
* dashboard.js
* Copyright (c) 2026 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/>.
*/
// CSS
// import "admin-lte/dist/css/adminlte.min.css"
// Plus Bootstrap and dependency CSS
import "bootstrap-icons/font/bootstrap-icons.css"
// import "overlayscrollbars/styles/overlayscrollbars.css"
import '../../boot/bootstrap.js';
import sidebar from '../../pages/shared/sidebar.js';
import boxes from './boxes.js';
let index = function () {
return {
foo2: 'bar2',
init() {
console.log('init op index')
}
}
};
const comps = {
index,
sidebar,
boxes
};
function loadPage(comps) {
Object.keys(comps).forEach(comp => {
let data = comps[comp]();
Alpine.data(comp, () => data);
});
Alpine.start();
}
// wait for load until bootstrapped event is received.
document.addEventListener('firefly-iii-bootstrapped', () => {
console.log('Loaded through event listener.');
loadPage(comps);
});
// or is bootstrapped before event is triggered.
if (window.bootstrapped) {
console.log('Loaded through window variable.');
loadPage(comps);
}

View File

@@ -0,0 +1,31 @@
/*
* sidebar.js
* Copyright (c) 2026 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 default () => ({
foo: 'bar',
init() {
console.log('init op sidebar')
},
logoutUser(e) {
e.preventDefault();
document.getElementById('logout-form').submit();
console.log('Logout user');
}
});

View File

@@ -0,0 +1,64 @@
/*
* get-variable.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 Get from "../api/preferences/index.js";
import Post from "../api/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('Returning "'+name+'" from window: ' + window[name]);
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('Returning "'+name+'" from store: ' + fromStore);
return Promise.resolve(fromStore);
}
let getter = (new Get);
return getter.getByName(name).then((response) => {
console.log('Returning "'+name+'" from server: ' + parseResponse(name, response));
return Promise.resolve(parseResponse(name, response));
}).catch((error) => {
if('' === defaultValue) {
// do not try to store empty strings.
return Promise.resolve(defaultValue);
}
// preference does not exist (yet).
// POST it and then return it anyway.
let poster = (new Post);
return poster.post(name, defaultValue).then((response) => {
console.log('Returning "'+name+'" from POST: ' + parseResponse(name, response));
return Promise.resolve(parseResponse(name, response));
});
});
}
export function parseResponse(name, response) {
let value = response.data.data.attributes.data;
window.store.set(name, value);
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

@@ -0,0 +1,96 @@
/*
* 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";
//const { createHash } = require('crypto');
function getCacheKey(string, params) {
const lastActivity = store.get('lastActivity')
let newParams = {lastActivity: lastActivity, key: string};
for (const key in params) {
if (params.hasOwnProperty(key)) {
if(params[key] === null || params[key] === undefined) {
newParams[key] = '';
continue;
}
if(params[key] instanceof Date) {
newParams[key] = format(params[key], 'yMMdd');
continue;
}
newParams[key] = params[key];
}
}
return 'dcx_' + md5(JSON.stringify(newParams)).substring(0,12) + lastActivity;
}
// Formatted version of a popular md5 implementation
// Original copyright (c) Paul Johnston & Greg Holt.
// The function itself is now 42 lines long.
function md5(inputString) {
var hc="0123456789abcdef";
function rh(n) {var j,s="";for(j=0;j<=3;j++) s+=hc.charAt((n>>(j*8+4))&0x0F)+hc.charAt((n>>(j*8))&0x0F);return s;}
function ad(x,y) {var l=(x&0xFFFF)+(y&0xFFFF);var m=(x>>16)+(y>>16)+(l>>16);return (m<<16)|(l&0xFFFF);}
function rl(n,c) {return (n<<c)|(n>>>(32-c));}
function cm(q,a,b,x,s,t) {return ad(rl(ad(ad(a,q),ad(x,t)),s),b);}
function ff(a,b,c,d,x,s,t) {return cm((b&c)|((~b)&d),a,b,x,s,t);}
function gg(a,b,c,d,x,s,t) {return cm((b&d)|(c&(~d)),a,b,x,s,t);}
function hh(a,b,c,d,x,s,t) {return cm(b^c^d,a,b,x,s,t);}
function ii(a,b,c,d,x,s,t) {return cm(c^(b|(~d)),a,b,x,s,t);}
function sb(x) {
var i;var nblk=((x.length+8)>>6)+1;var blks=new Array(nblk*16);for(i=0;i<nblk*16;i++) blks[i]=0;
for(i=0;i<x.length;i++) blks[i>>2]|=x.charCodeAt(i)<<((i%4)*8);
blks[i>>2]|=0x80<<((i%4)*8);blks[nblk*16-2]=x.length*8;return blks;
}
var i,x=sb(""+inputString),a=1732584193,b=-271733879,c=-1732584194,d=271733878,olda,oldb,oldc,oldd;
for(i=0;i<x.length;i+=16) {olda=a;oldb=b;oldc=c;oldd=d;
a=ff(a,b,c,d,x[i+ 0], 7, -680876936);d=ff(d,a,b,c,x[i+ 1],12, -389564586);c=ff(c,d,a,b,x[i+ 2],17, 606105819);
b=ff(b,c,d,a,x[i+ 3],22,-1044525330);a=ff(a,b,c,d,x[i+ 4], 7, -176418897);d=ff(d,a,b,c,x[i+ 5],12, 1200080426);
c=ff(c,d,a,b,x[i+ 6],17,-1473231341);b=ff(b,c,d,a,x[i+ 7],22, -45705983);a=ff(a,b,c,d,x[i+ 8], 7, 1770035416);
d=ff(d,a,b,c,x[i+ 9],12,-1958414417);c=ff(c,d,a,b,x[i+10],17, -42063);b=ff(b,c,d,a,x[i+11],22,-1990404162);
a=ff(a,b,c,d,x[i+12], 7, 1804603682);d=ff(d,a,b,c,x[i+13],12, -40341101);c=ff(c,d,a,b,x[i+14],17,-1502002290);
b=ff(b,c,d,a,x[i+15],22, 1236535329);a=gg(a,b,c,d,x[i+ 1], 5, -165796510);d=gg(d,a,b,c,x[i+ 6], 9,-1069501632);
c=gg(c,d,a,b,x[i+11],14, 643717713);b=gg(b,c,d,a,x[i+ 0],20, -373897302);a=gg(a,b,c,d,x[i+ 5], 5, -701558691);
d=gg(d,a,b,c,x[i+10], 9, 38016083);c=gg(c,d,a,b,x[i+15],14, -660478335);b=gg(b,c,d,a,x[i+ 4],20, -405537848);
a=gg(a,b,c,d,x[i+ 9], 5, 568446438);d=gg(d,a,b,c,x[i+14], 9,-1019803690);c=gg(c,d,a,b,x[i+ 3],14, -187363961);
b=gg(b,c,d,a,x[i+ 8],20, 1163531501);a=gg(a,b,c,d,x[i+13], 5,-1444681467);d=gg(d,a,b,c,x[i+ 2], 9, -51403784);
c=gg(c,d,a,b,x[i+ 7],14, 1735328473);b=gg(b,c,d,a,x[i+12],20,-1926607734);a=hh(a,b,c,d,x[i+ 5], 4, -378558);
d=hh(d,a,b,c,x[i+ 8],11,-2022574463);c=hh(c,d,a,b,x[i+11],16, 1839030562);b=hh(b,c,d,a,x[i+14],23, -35309556);
a=hh(a,b,c,d,x[i+ 1], 4,-1530992060);d=hh(d,a,b,c,x[i+ 4],11, 1272893353);c=hh(c,d,a,b,x[i+ 7],16, -155497632);
b=hh(b,c,d,a,x[i+10],23,-1094730640);a=hh(a,b,c,d,x[i+13], 4, 681279174);d=hh(d,a,b,c,x[i+ 0],11, -358537222);
c=hh(c,d,a,b,x[i+ 3],16, -722521979);b=hh(b,c,d,a,x[i+ 6],23, 76029189);a=hh(a,b,c,d,x[i+ 9], 4, -640364487);
d=hh(d,a,b,c,x[i+12],11, -421815835);c=hh(c,d,a,b,x[i+15],16, 530742520);b=hh(b,c,d,a,x[i+ 2],23, -995338651);
a=ii(a,b,c,d,x[i+ 0], 6, -198630844);d=ii(d,a,b,c,x[i+ 7],10, 1126891415);c=ii(c,d,a,b,x[i+14],15,-1416354905);
b=ii(b,c,d,a,x[i+ 5],21, -57434055);a=ii(a,b,c,d,x[i+12], 6, 1700485571);d=ii(d,a,b,c,x[i+ 3],10,-1894986606);
c=ii(c,d,a,b,x[i+10],15, -1051523);b=ii(b,c,d,a,x[i+ 1],21,-2054922799);a=ii(a,b,c,d,x[i+ 8], 6, 1873313359);
d=ii(d,a,b,c,x[i+15],10, -30611744);c=ii(c,d,a,b,x[i+ 6],15,-1560198380);b=ii(b,c,d,a,x[i+13],21, 1309151649);
a=ii(a,b,c,d,x[i+ 4], 6, -145523070);d=ii(d,a,b,c,x[i+11],10,-1120210379);c=ii(c,d,a,b,x[i+ 2],15, 718787259);
b=ii(b,c,d,a,x[i+ 9],21, -343485551);a=ad(a,olda);b=ad(b,oldb);c=ad(c,oldc);d=ad(d,oldd);
}
return rh(a)+rh(b)+rh(c)+rh(d);
}
export {getCacheKey};

View File

@@ -0,0 +1,38 @@
/*
* format-money.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";
export default function (amount, currencyCode) {
if( (typeof amount !== 'number' && typeof amount !== 'string') || isNaN(amount)) {
console.warn('format-money: amount is not a number:', amount);
return '';
}
if(typeof currencyCode !== 'string' || currencyCode.length !== 3) {
console.warn('format-money: currencyCode is not a valid ISO 4217 code:', currencyCode);
return '';
}
let locale = window.__localeId__.replace('_', '-');
return Intl.NumberFormat(locale, {
style: 'currency',
currency: currencyCode
}).format(amount);
}

View File

@@ -0,0 +1,21 @@
{
"$schema": "https://www.schemastore.org/package.json",
"name": "v3",
"private": true,
"dependencies": {
"admin-lte": "^4.0.0",
"alpinejs": "^3.15.12"
},
"type": "module",
"scripts": {
"build": "vite build",
"dev": "vite"
},
"devDependencies": {
"concurrently": "^9.0.1",
"laravel-vite-plugin": "^3.1",
"tailwindcss": "^4.0.0",
"vite": "^8.0.0",
"vite-plugin-manifest-sri": "^0.2.0"
}
}

View File

@@ -0,0 +1,99 @@
/*!
* app.scss
* Copyright (c) 2026 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/>.
*/
$color-mode-type: media-query;
$link-decoration: none !default;
$font-family-sans-serif: "Roboto", sans-serif;
$danger: #CD5029 !default;
$primary: #1E6581 !default;
$success: #64B624 !default;
// admin LTE
@use "../../../../node_modules/admin-lte/src/scss/adminlte" with (
$color-mode-type: $color-mode-type,
$link-decoration: $link-decoration,
$font-family-sans-serif: $font-family-sans-serif,
$danger: $danger,
$primary: $primary,
$success: $success
);
//@use '../../../../node_modules/@fortawesome/fontawesome-free/scss/variables' with (
// $font-path: "@fortawesome/fontawesome-free/webfonts"
//);
//@use '../../../../node_modules/@fortawesome/fontawesome-free/scss/fontawesome';
//@use '../../../../node_modules/@fortawesome/fontawesome-free/scss/fa' as fa;
//@use '../../../../node_modules/@fortawesome/fontawesome-free/scss/solid' as fa-solid;
//@use '../../../../node_modules/@fortawesome/fontawesome-free/scss/brands' as fa-brands;
//@use '../../../../node_modules/@fortawesome/fontawesome-free/scss/regular' as fa-regular;
// some local CSS
.skip-links {display: none;}
/*
Remove bottom margin from unstyled lists
*/
.list-no-margin {margin-bottom: 0;}
.list-no-margin li.list-indent {margin-left:1.6em;}
.hover-footer {
overflow-x:hidden;
width:100%;
text-align:right;
white-space: nowrap;
text-overflow: ellipsis
}
.hover-footer:hover {
overflow: auto;
text-overflow: unset;
white-space: normal;
}
h3.hover-expand {
overflow-x: hidden;
text-overflow: ellipsis
}
h3.hover-expand:hover {
overflow-x: auto;
}
.form-control {
appearance: auto;
}
// edit buttons
.hidden-edit-button {
cursor: pointer;
}
td:not(:hover) .hidden-edit-button {
visibility: hidden;
}
// Bootstrap
// @import "bootstrap/scss/bootstrap";
// @import "~bootstrap-sass/assets/stylesheets/bootstrap";
// hover buttons

View File

@@ -0,0 +1,70 @@
/*
* vite.config.js
* Copyright (c) 2026 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 {defineConfig} from 'vite';
import laravel from 'laravel-vite-plugin';
import manifestSRI from 'vite-plugin-manifest-sri';
import fs from "fs";
export default defineConfig(({command, mode, isSsrBuild, isPreview}) => {
let https = null;
if (command === 'serve') {
https = {
key: fs.readFileSync(`/vagrant/tls-certificates/wildcard.sd.internal.key`),
cert: fs.readFileSync(`/vagrant/tls-certificates/wildcard.sd.internal.crt`),
};
}
return {
base: './',
plugins: [
laravel({
input: [
// CSS for entire app
'sass/app.scss',
// dashboard
'js/pages/dashboard/boxes.js',
'js/pages/dashboard/dashboard.js'
],
buildDirectory: '../../../../public/build',
// publicDirectory: '../../../public',
refresh: true,
fonts: [],
}),
manifestSRI(),
],
server: {
watch: {
ignored: ['**/storage/framework/views/**'],
usePolling: true,
},
cors: true,
// make sure this IP matches the IP of the dev machine.
origin: 'https://192.168.96.169:5173',
port: 5173,
host: true,
// hmr: {
// protocol: 'wss',
// },
https: https,
},
}
});

View File

@@ -46,8 +46,10 @@ return [
'YTD' => 'YTD',
'welcome_back' => 'What\'s playing?',
'main_dashboard_page_title' => 'Home',
'view_documentation' => 'View documentation',
'everything' => 'Everything',
'today' => 'today',
'organization' => 'Organization',
'customRange' => 'Custom range',
'date_range' => 'Date range',
'apply' => 'Apply',

View File

@@ -0,0 +1,187 @@
<div class="row mb-2" x-data="boxes" x-bind="eventListeners">
<div class="col-xl-3 col-lg-6 col-md-12 col-sm-12">
<div class="small-box text-bg-primary">
<div class="inner balance-box">
<h4 class="hover-expand">
<template x-if="0 === balanceBox.amounts.length">
<span>&nbsp;</span>
</template>
<template x-for="(amount, index) in balanceBox.amounts" :key="index">
<span>
<span x-text="amount"></span><span
:class="{ 'invisible': (balanceBox.amounts.length == index+1) }">, </span>
</span>
</template>
</h4>
<template x-if="loading">
<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 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 class="d-none d-sm-block">
TODO (no money in or out)
</p>
</template>
</div>
<span class="small-box-icon">
<i class="fa-solid fa-scale-balanced"></i>
</span>
<div class="small-box-footer hover-footer d-none d-xl-block">
<template x-if="0 === balanceBox.subtitles.length">
<span>&nbsp;</span>
</template>
<template x-for="(subtitle, index) in balanceBox.subtitles" :key="index">
<span>
<span x-text="subtitle"></span><span
:class="{ 'invisible': (balanceBox.amounts.length == index+1) }"> &amp; </span>
</span>
</template>
</div>
</div>
<!--end::Small Box Widget 1-->
</div>
<!--end::Col-->
<div class="col-xl-3 col-lg-6 col-md-12 col-sm-12" style="flex-grow: 1;">
<!--begin::Small Box Widget 2-->
<div class="small-box text-bg-success">
<div class="inner">
<template x-if="0 === billBox.unpaid.length">
<h4>&nbsp;</h4>
</template>
<template x-if="billBox.unpaid.length > 0">
<h4 class="hover-expand">
<template x-for="(amount, index) in billBox.unpaid" :key="index">
<span>
<span x-text="amount"></span><span
:class="{ 'invisible': (billBox.unpaid.length == index+1) }">, </span>
</span>
</template>
</h4>
</template>
<template x-if="loading">
<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 class="d-none d-sm-block"><a href="{{ route('subscriptions.index') }}">{{ __('firefly.bills_to_pay') }}</a></p>
</template>
<template x-if="0 === billBox.unpaid.length && !loading">
<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 d-none d-xl-block">
<template x-if="0 === billBox.paid.length">
<span>&nbsp;</span>
</template>
<template x-if="billBox.paid.length > 0">
<span>
{{ __('firefly.paid') }}:
<template x-for="(amount, index) in billBox.paid" :key="index">
<span>
<span x-text="amount"></span><span
:class="{ 'invisible': (billBox.paid.length == index+1) }">, </span>
</span>
</template>
</span>
</template>
</span>
</div>
<!--end::Small Box Widget 2-->
</div>
<!--end::Col-->
<div class="col-xl-3 col-lg-6 col-md-12 col-sm-12" style="flex-grow: 1;">
<!--begin::Small Box Widget 3-->
<div class="small-box text-bg-warning">
<div class="inner">
<h4 class="hover-expand">
<template x-if="0 === leftBox.left.length">
<span>&nbsp;</span>
</template>
<template x-for="(amount, index) in leftBox.left" :key="index">
<span>
<span x-text="amount"></span><span
:class="{ 'invisible': (leftBox.left.length == index+1) }">, </span>
</span>
</template>
</h4>
<template x-if="loading">
<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 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 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 d-none d-xl-block">
<template x-if="0 !== leftBox.perDay.length">
<span>{{ __('firefly.per_day') }}:</span>
</template>
<template x-if="0 === leftBox.perDay.length">
<span>&nbsp;</span>
</template>
<template x-for="(amount, index) in leftBox.perDay" :key="index">
<span>
<span x-text="amount"></span><span
:class="{ 'invisible': (leftBox.perDay.length == index+1) }">, </span>
</span>
</template>
</span>
</div>
<!--end::Small Box Widget 3-->
</div>
<!--end::Col-->
<div class="col-xl-3 col-lg-6 col-md-12 col-sm-12" style="flex-grow: 1;">
<!--begin::Small Box Widget 4-->
<div class="small-box text-bg-danger">
<div class="inner">
<h4 class="hover-expand">
<template x-for="(amount, index) in netBox.net" :key="index">
<span>
<span x-text="amount"></span><span
:class="{ 'invisible': (netBox.net.length == index+1) }">, </span>
</span>
</template>
</h4>
<template x-if="loading">
<p class="d-none d-sm-block">
<em class="fa-solid fa-spinner fa-spin"></em>
</p>
</template>
<template x-if="!loading">
<p class="d-none d-sm-block">
<a href="{{ route('reports.report.default', ['allAssetAccounts','currentYearStart','currentYearEnd']) }}">{{ __('firefly.net_worth') }}</a>
</p>
</template>
</div>
<span class="small-box-icon">
<i class="fa-solid fa-chart-line"></i>
</span>
<span class="small-box-footer d-none d-xl-block">
&nbsp;
</span>
</div>
<!--end::Small Box Widget 4-->
</div>
<!--end::Col-->
</div>
<!--end::Row-->

View File

@@ -0,0 +1,116 @@
@if(\FireflyIII\Enums\TransactionTypeEnum::DEPOSIT->value === $transaction['transaction_type_type'])
{!! \FireflyIII\Support\Facades\Steam::formatAmountBySymbol(bcmul($transaction['amount'],'-1'), $transaction['currency_symbol'], $transaction['currency_decimal_places']) !!}
@if(null !== $transaction['foreign_amount'])
({!! \FireflyIII\Support\Facades\Steam::formatAmountBySymbol(bcmul($transaction['foreign_amount'], '-1'), $transaction['foreign_currency_symbol'], $transaction['foreign_currency_decimal_places']) !!})
@endif
@if($convertToPrimary && null !== $transaction['pc_amount'])
({!! \FireflyIII\Support\Facades\Steam::formatAmountBySymbol(bcmul($transaction['pc_amount'], '-1'), $primaryCurrency->symbol, $primaryCurrency->decimal_places) !!})
@endif
@endif
@if(\FireflyIII\Enums\TransactionTypeEnum::WITHDRAWAL->value === $transaction['transaction_type_type'])
{!! \FireflyIII\Support\Facades\Steam::formatAmountBySymbol($transaction['amount'], $transaction['currency_symbol'], $transaction['currency_decimal_places']) !!}
@if(null !== $transaction['foreign_amount'])
({!! \FireflyIII\Support\Facades\Steam::formatAmountBySymbol($transaction['foreign_amount'], $transaction['foreign_currency_symbol'], $transaction['foreign_currency_decimal_places']) !!})
@endif
@if($convertToPrimary && null !== $transaction['pc_amount'])
({!! \FireflyIII\Support\Facades\Steam::formatAmountBySymbol($transaction['pc_amount'], $primaryCurrency->symbol, $primaryCurrency->decimal_places) !!})
@endif
@endif
@if(\FireflyIII\Enums\TransactionTypeEnum::TRANSFER->value === $transaction['transaction_type_type'])
<span class="text-info money-transfer">
{{-- transfer away --}}
@if(isset($account) && $transaction['source_account_id'] === $account->id)
{!! \FireflyIII\Support\Facades\Steam::formatAmountBySymbol(bcmul($transaction['amount'],'-1'), $transaction['currency_symbol'], $transaction['currency_decimal_places'], false) !!}
@if(null !== $transaction['foreign_amount'])
({!! \FireflyIII\Support\Facades\Steam::formatAmountBySymbol(bcmul($transaction['foreign_amount'],'-1'), $transaction['foreign_currency_symbol'], $transaction['foreign_currency_decimal_places'], false) !!})
@endif
@if($convertToPrimary && null !== $transaction['pc_amount'])
({!! \FireflyIII\Support\Facades\Steam::formatAmountBySymbol(bcmul($transaction['pc_amount'], '-1'), $primaryCurrency->symbol, $primaryCurrency->decimal_places, false) !!})
@endif
@endif
{{-- transfer in (default) --}}
@if(!isset($account) || $transaction['source_account_id'] !== $account->id)
{!! \FireflyIII\Support\Facades\Steam::formatAmountBySymbol($transaction['amount'], $transaction['currency_symbol'], $transaction['currency_decimal_places'], false) !!}
@if(null !== $transaction['foreign_amount'])
({!! \FireflyIII\Support\Facades\Steam::formatAmountBySymbol($transaction['foreign_amount'], $transaction['foreign_currency_symbol'], $transaction['foreign_currency_decimal_places'], false) !!})
@endif
@if($convertToPrimary && null !== $transaction['pc_amount'])
({!! \FireflyIII\Support\Facades\Steam::formatAmountBySymbol($transaction['pc_amount'], $primaryCurrency->symbol, $primaryCurrency->decimal_places, false) !!})
@endif
@endif
</span>
@endif
@if(\FireflyIII\Enums\TransactionTypeEnum::OPENING_BALANCE->value === $transaction['transaction_type_type'])
{{-- Opening balance is deposited on this account (render as positive amount) --}}
@if(\FireflyIII\Enums\AccountTypeEnum::INITIAL_BALANCE->value === $transaction['source_account_type'])
{!! \FireflyIII\Support\Facades\Steam::formatAmountBySymbol(bcmul($transaction['amount'],'-1'), $transaction['currency_symbol'], $transaction['currency_decimal_places'], false) !!}
@if(null !== $transaction['foreign_amount'])
({!! \FireflyIII\Support\Facades\Steam::formatAmountBySymbol(bcmul($transaction['foreign_amount'],'-1'), $transaction['foreign_currency_symbol'], $transaction['foreign_currency_decimal_places'], false) !!})
@endif
@if($convertToPrimary && null !== $transaction['pc_amount'])
({!! \FireflyIII\Support\Facades\Steam::formatAmountBySymbol(bcmul($transaction['pc_amount'], '-1'), $primaryCurrency->symbol, $primaryCurrency->decimal_places, false) !!})
@endif
@endif
{{-- Opening balance is removed from this account (render as negative amount) --}}
@if(\FireflyIII\Enums\AccountTypeEnum::INITIAL_BALANCE->value === $transaction['destination_account_type'])
{!! \FireflyIII\Support\Facades\Steam::formatAmountBySymbol($transaction['amount'], $transaction['currency_symbol'], $transaction['currency_decimal_places'], false) !!}
@if(null !== $transaction['foreign_amount'])
({!! \FireflyIII\Support\Facades\Steam::formatAmountBySymbol($transaction['foreign_amount'], $transaction['foreign_currency_symbol'], $transaction['foreign_currency_decimal_places'], false) !!})
@endif
@if($convertToPrimary && null !== $transaction['pc_amount'])
({!! \FireflyIII\Support\Facades\Steam::formatAmountBySymbol($transaction['pc_amount'], $primaryCurrency->symbol, $primaryCurrency->decimal_places, false) !!})
@endif
@endif
@endif
@if(\FireflyIII\Enums\TransactionTypeEnum::RECONCILIATION->value === $transaction['transaction_type_type'])
{{-- Reconciliation correction is deposited on this account (render as positive amount) --}}
@if(\FireflyIII\Enums\AccountTypeEnum::RECONCILIATION->value === $transaction['source_account_type'])
{!! \FireflyIII\Support\Facades\Steam::formatAmountBySymbol(bcmul($transaction['amount'],'-1'), $transaction['currency_symbol'], $transaction['currency_decimal_places'], false) !!}
@if(null !== $transaction['foreign_amount'])
({!! \FireflyIII\Support\Facades\Steam::formatAmountBySymbol(bcmul($transaction['foreign_amount'],'-1'), $transaction['foreign_currency_symbol'], $transaction['foreign_currency_decimal_places'], false) !!})
@endif
@if($convertToPrimary && null !== $transaction['pc_amount'])
({!! \FireflyIII\Support\Facades\Steam::formatAmountBySymbol(bcmul($transaction['pc_amount'], '-1'), $primaryCurrency->symbol, $primaryCurrency->decimal_places, false) !!})
@endif
@endif
{{-- Reconciliation correction is removed from this account (render as negative amount) --}}
@if(\FireflyIII\Enums\AccountTypeEnum::RECONCILIATION->value === $transaction['destination_account_type'])
{!! \FireflyIII\Support\Facades\Steam::formatAmountBySymbol($transaction['amount'], $transaction['currency_symbol'], $transaction['currency_decimal_places'], false) !!}
@if(null !== $transaction['foreign_amount'])
({!! \FireflyIII\Support\Facades\Steam::formatAmountBySymbol($transaction['foreign_amount'], $transaction['foreign_currency_symbol'], $transaction['foreign_currency_decimal_places'], false) !!})
@endif
@if($convertToPrimary && null !== $transaction['pc_amount'])
({!! \FireflyIII\Support\Facades\Steam::formatAmountBySymbol($transaction['pc_amount'], $primaryCurrency->symbol, $primaryCurrency->decimal_places, false) !!})
@endif
@endif
@endif
{{--
<span class="pull-right small">
{% elseif transaction.transaction_type_type == 'Reconciliation' %}
{% else %}
{{ formatAmountBySymbol(transaction.amount, transaction.currency_symbol, transaction.currency_decimal_places) }}
{% if null != transaction.foreign_amount %}
({{ formatAmountBySymbol(transaction.foreign_amount, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places) }})
{% endif %}
{% if convertToPrimary and null != transaction.pc_amount %}
({{ formatAmountBySymbol(transaction.pc_amount, primaryCurrency.symbol, foreign_currency_.decimal_places) }})
{% endif %}
{% endif %}
</span>
--}}

View File

@@ -0,0 +1,154 @@
<!-- begin create menu -->
<li class="nav-item dropdown">
<a class="nav-link" data-bs-toggle="dropdown" href="#" aria-expanded="false">
<em class="bi bi-plus-circle"></em>
</a>
<div class="dropdown-menu dropdown-menu-lg dropdown-menu-end">
<a href="{{ route('transactions.create', ['withdrawal']) }}" class="dropdown-item">
<div class="d-flex">
<div class="grow">
<h3 class="dropdown-item-title">
<!-- withdrawal, deposit, transfer -->
<em class="bi bi-arrow-left me-2"></em>
{{ __('firefly.create_new_withdrawal') }}
</h3>
</div>
</div>
</a>
<div class="dropdown-divider"></div>
<a href="{{ route('transactions.create', ['deposit']) }}" class="dropdown-item">
<div class="d-flex">
<div class="grow">
<h3 class="dropdown-item-title">
<!-- withdrawal, deposit, transfer -->
<em class="bi bi-arrow-right me-2"></em>
{{ __('firefly.create_new_deposit') }}
</h3>
</div>
</div>
</a>
<div class="dropdown-divider"></div>
<a href="{{ route('transactions.create', ['transfer']) }}" class="dropdown-item">
<div class="d-flex">
<div class="grow">
<h3 class="dropdown-item-title">
<!-- withdrawal, deposit, transfer -->
<em class="bi bi-arrow-left-right me-2"></em>
{{ __('firefly.create_new_transfer') }}
</h3>
</div>
</div>
</a>
<div class="dropdown-divider"></div>
<a href="{{ route('accounts.create', ['asset']) }}" class="dropdown-item">
<div class="d-flex">
<div class="grow">
<h3 class="dropdown-item-title">
<!-- withdrawal, deposit, transfer -->
<em class="bi bi-cash me-2"></em>
{{ __('firefly.create_new_asset') }}
</h3>
</div>
</div>
</a>
<div class="dropdown-divider"></div>
<a href="{{ route('accounts.create', ['liabilities']) }}" class="dropdown-item">
<div class="d-flex">
<div class="grow">
<h3 class="dropdown-item-title">
<!-- withdrawal, deposit, transfer -->
<em class="bi bi-ticket-detailed me-2"></em>
{{ __('firefly.create_new_liabilities') }}
</h3>
</div>
</div>
</a>
<div class="dropdown-divider"></div>
<a href="{{ route('budgets.create') }}" class="dropdown-item">
<div class="d-flex">
<div class="grow">
<h3 class="dropdown-item-title">
<!-- withdrawal, deposit, transfer -->
<em class="bi bi-pie-chart me-2"></em>
{{ __('firefly.create_new_budget') }}
</h3>
</div>
</div>
</a>
<div class="dropdown-divider"></div>
<a href="{{ route('categories.create') }}" class="dropdown-item">
<div class="d-flex">
<div class="grow">
<h3 class="dropdown-item-title">
<!-- withdrawal, deposit, transfer -->
<em class="bi bi-bookmark me-2"></em>
{{ __('firefly.create_new_category') }}
</h3>
</div>
</div>
</a>
<div class="dropdown-divider"></div>
<a href="{{ route('piggy-banks.create') }}" class="dropdown-item">
<div class="d-flex">
<div class="grow">
<h3 class="dropdown-item-title">
<!-- withdrawal, deposit, transfer -->
<em class="bi bi-bullseye me-2"></em>
{{ __('firefly.create_new_piggy_bank') }}
</h3>
</div>
</div>
</a>
<div class="dropdown-divider"></div>
<a href="{{ route('subscriptions.create') }}" class="dropdown-item">
<div class="d-flex">
<div class="grow">
<h3 class="dropdown-item-title">
<!-- withdrawal, deposit, transfer -->
<em class="bi bi-calendar me-2"></em>
{{ __('firefly.create_new_subscription') }}
</h3>
</div>
</div>
</a>
<div class="dropdown-divider"></div>
<a href="{{ route('rules.create') }}" class="dropdown-item">
<div class="d-flex">
<div class="grow">
<h3 class="dropdown-item-title">
<!-- withdrawal, deposit, transfer -->
<em class="bi bi-shuffle me-2"></em>
{{ __('firefly.create_new_rule') }}
</h3>
</div>
</div>
</a>
<div class="dropdown-divider"></div>
<a href="{{ route('recurring.create') }}" class="dropdown-item">
<div class="d-flex">
<div class="grow">
<h3 class="dropdown-item-title">
<!-- withdrawal, deposit, transfer -->
<em class="bi bi-paint-bucket me-2"></em>
{{ __('firefly.create_new_recurrence') }}
</h3>
</div>
</div>
</a>
<div class="dropdown-divider"></div>
<a href="{{ route('webhooks.create') }}" class="dropdown-item">
<div class="d-flex">
<div class="grow">
<h3 class="dropdown-item-title">
<!-- withdrawal, deposit, transfer -->
<em class="bi bi-lightning me-2"></em>
{{ __('firefly.create_new_webhook') }}
</h3>
</div>
</div>
</a>
</div>
</li>
<!-- end create menu -->

View File

@@ -0,0 +1,11 @@
<link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png?v=3e8AboOwbd">
<link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png?v=3e8AboOwbd">
<link rel="manifest" href="manifest.webmanifest?v=3e8AboOwbd" crossorigin="use-credentials">
<link rel="mask-icon" href="safari-pinned-tab.svg" color="#3c8dbc">
<link rel="shortcut icon" href="favicon.ico?v=3e8AboOwbd">
<meta name="apple-mobile-web-app-title" content="Firefly III">
<meta name="application-name" content="Firefly III">
<meta name="msapplication-TileColor" content="#3c8dbc">
<meta name="msapplication-TileImage" content="mstile-144x144.png?v=3e8AboOwbd">
<meta name="theme-color" content="#3c8dbc">

View File

@@ -0,0 +1,229 @@
<ul
class="nav sidebar-menu flex-column"
data-lte-toggle="treeview"
role="navigation"
aria-label="Main navigation"
data-accordion="false"
id="navigation"
x-data="sidebar"
>
<!-- <li class="nav-item menu-open"> -->
<!-- <a href="route('index')" class="nav-link active"> -->
<li class="nav-item">
<a href="{{ route('index') }}" class="nav-link @menuItemActive('index')">
<em class="nav-icon bi bi-speedometer"></em>
<p>{{ __('firefly.dashboard') }}</p>
</a>
</li>
<li class="nav-header text-uppercase">{{ __('firefly.financial_control') }}</li>
<li class="nav-item">
<a href="{{ route('budgets.index') }}" class="nav-link @menuItemActive('budgets')">
<em class="nav-icon bi bi-pie-chart"></em>
<p>
{{ __('firefly.budgets') }}
</p>
</a>
</li>
<li class="nav-item">
<a href="{{ route('subscriptions.index') }}" class="nav-link @menuItemActive('subscriptions')">
<em class="nav-icon bi bi-calendar"></em>
<p>
{{ __('firefly.subscriptions') }}
</p>
</a>
</li>
<li class="nav-item">
<a href="{{ route('piggy-banks.index') }}" class="nav-link @menuItemActive('piggy-banks')">
<em class="nav-icon bi bi-bullseye"></em>
<p>
{{ __('firefly.piggyBanks') }}
</p>
</a>
</li>
<li class="nav-header text-uppercase">{{ __('firefly.accounting') }}</li>
<li class="nav-item">
<a href="#" class="nav-link">
<em class="nav-icon bi bi-wallet"></em>
<p>
{{ __('firefly.transactions') }}
<em class="nav-arrow bi bi-chevron-right"></em>
</p>
</a>
<ul class="nav nav-treeview">
<li class="nav-item">
<a href="{{ route('transactions.index', ['withdrawal']) }}" class="nav-link">
<em class="nav-icon bi bi-arrow-left"></em>
<p>{{ __('firefly.expenses') }}</p>
</a>
</li>
<li class="nav-item">
<a href="{{ route('transactions.index', ['withdrawal']) }}" class="nav-link">
<em class="nav-icon bi bi-arrow-right"></em>
<p>{{ __('firefly.income') }}</p>
</a>
</li>
<li class="nav-item">
<a href="{{ route('transactions.index', ['transfers']) }}" class="nav-link">
<em class="nav-icon bi bi-arrow-left-right"></em>
<p>{{ __('firefly.transfers') }}</p>
</a>
</li>
<li class="nav-item">
<a href="{{ route('transactions.index', ['all']) }}" class="nav-link">
<em class="nav-icon bi bi-arrow-repeat"></em>
<p>{{ __('firefly.all_transactions') }}</p>
</a>
</li>
</ul>
</li>
<li class="nav-item">
<a href="#" class="nav-link">
<em class="nav-icon bi bi-cpu"></em>
<p>
{{ __('firefly.automation') }}
<em class="nav-arrow bi bi-chevron-right"></em>
</p>
</a>
<ul class="nav nav-treeview">
<li class="nav-item">
<a href="{{ route('rules.index') }}" class="nav-link">
<em class="nav-icon bi bi-shuffle"></em>
<p>{{__('firefly.rules')}}</p>
</a>
</li>
<li class="nav-item">
<a href="{{ route('recurring.index') }}" class="nav-link">
<em class="nav-icon bi bi-paint-bucket"></em>
<p>{{__('firefly.recurrences') }}</p>
</a>
</li>
@if(true === $featuringWebhooks)
<li class="nav-item">
<a href="{{ route('webhooks.index') }}" class="nav-link">
<em class="nav-icon bi bi-lightning"></em>
<p>{{ __('firefly.webhooks') }}</p>
</a>
</li>
@endif
@if(false === $featuringWebhooks)
<li class="nav-item">
<a href="#" class="nav-link">
<em class="nav-icon bi bi-lightning"></em>
<p>{{ __('firefly.webhooks') }} ({{ trans('firefly.webhooks_menu_disabled') }})</p>
</a>
</li>
@endif
</ul>
</li>
<li class="nav-header text-uppercase">{{ __('firefly.organization') }}</li>
<li class="nav-item">
<a href="#" class="nav-link">
<em class="nav-icon bi bi-credit-card"></em>
<p>
{{__('firefly.accounts')}}
<em class="nav-arrow bi bi-chevron-right"></em>
</p>
</a>
<ul class="nav nav-treeview">
<li class="nav-item">
<a href="{{ route('accounts.index', ['asset']) }}" class="nav-link">
<em class="nav-icon bi bi-cash"></em>
<p>{{ __('firefly.asset_accounts') }}</p>
</a>
</li>
<li class="nav-item">
<a href="{{ route('accounts.index', ['expense']) }}" class="nav-link">
<em class="nav-icon bi bi-cart"></em>
<p>{{trans('firefly.expense_accounts')}}</p>
</a>
</li>
<li class="nav-item">
<a href="{{ route('accounts.index', ['revenue']) }}" class="nav-link">
<em class="nav-icon bi bi-box-arrow-down"></em>
<p>{{ __('firefly.revenue_accounts') }}</p>
</a>
</li>
<li class="nav-item">
<a href="{{ route('accounts.index', ['liabilities']) }}" class="nav-link">
<em class="nav-icon bi bi-ticket-detailed"></em>
<p>{{ __('firefly.liabilities_accounts') }}</p>
</a>
</li>
</ul>
</li>
<li class="nav-item">
<a href="#" class="nav-link">
<em class="nav-icon bi bi-tags"></em>
<p>
{{trans('firefly.classification')}}
<em class="nav-arrow bi bi-chevron-right"></em>
</p>
</a>
<ul class="nav nav-treeview">
<li class="nav-item">
<a href="{{ route('categories.index') }}" class="nav-link">
<em class="nav-icon bi bi-bookmark"></em>
<p>{{trans('firefly.categories')}}</p>
</a>
</li>
<li class="nav-item">
<a href="{{ route('tags.index') }}" class="nav-link">
<em class="nav-icon bi bi-tag"></em>
<p>{{trans('firefly.tags')}}</p>
</a>
</li>
<li class="nav-item">
<a href="{{ route('object-groups.index') }}" class="nav-link">
<em class="nav-icon bi bi-envelope"></em>
<p>{{trans('firefly.object_groups_menu_bar')}}</p>
</a>
</li>
</ul>
</li>
<li class="nav-header text-uppercase">{{ __('firefly.others') }}</li>
<li class="nav-item">
<a href="{{ route('currencies.index') }}" class="nav-link">
<em class="nav-icon bi bi-currency-euro"></em>
<p>{{ __('firefly.currencies') }}</p>
</a>
</li>
<li class="nav-item">
<a href="{{ route('exchange-rates.index') }}" class="nav-link">
<em class="nav-icon bi bi-currency-exchange"></em>
<p>{{ __('firefly.menu_exchange_rates_index') }}</p>
</a>
</li>
<li class="nav-item">
<a href="{{ route('reports.index') }}" class="nav-link">
<em class="nav-icon bi bi-bar-chart"></em>
<p>{{ __('firefly.reports') }}</p>
</a>
</li>
<li class="nav-item">
<a href="{{ route('export.index') }}" class="nav-link">
<em class="nav-icon bi bi-upload"></em>
<p>{{ __('firefly.export_data_menu') }}</p>
</a>
</li>
@if('web' === $authGuard)
<li class="nav-item">
<a href="{{ route('logout') }}" @click="logoutUser" class="nav-link logout-link">
<em class="nav-icon bi bi-person text-danger"></em>
<p>{{ __('firefly.logout') }}</p>
</a>
</li>
@endif
@if('remote_user_guard' === $authGuard && '' !== $logoutUrl)
<li class="nav-item">
<a href="{{ $logoutUrl }}" class="nav-link logout-link">
<em class="nav-icon bi bi-person text-danger"></em>
<p>{{ __('firefly.logout') }}</p>
</a>
</li>
@endif
</ul>

View File

@@ -0,0 +1,3 @@
<div>
<!-- He who is contented is rich. - Laozi -->
</div>

View File

@@ -0,0 +1,28 @@
<li class="nav-item dropdown">
<a class="nav-link" data-bs-toggle="dropdown" href="#">
<em class="bi bi-person"></em>
</a>
<div class="dropdown-menu dropdown-menu-lg dropdown-menu-end">
<span class="dropdown-item dropdown-header">{{ Auth::user()->email }}</span>
<div class="dropdown-divider"></div>
<a href="#" class="dropdown-item">
<i class="bi bi-envelope me-2"></i> Profile
</a>
<div class="dropdown-divider"></div>
<a href="#" class="dropdown-item">
<i class="bi bi-people-fill me-2"></i> Preferences
</a>
<div class="dropdown-divider"></div>
<a href="#" class="dropdown-item">
<i class="bi bi-file-earmark-fill me-2"></i> Tokens
</a>
<div class="dropdown-divider"></div>
<a href="#" class="dropdown-item">
<i class="bi bi-file-earmark-fill me-2"></i> Financial administrations
</a>
<div class="dropdown-divider"></div>
<a href="#" class="dropdown-item">
<i class="bi bi-file-earmark-fill me-2"></i> System settings
</a>
</div>
</li>

View File

@@ -0,0 +1,14 @@
<div class="list-group">
@foreach($transactions as $transaction)
<a class="list-group-item list-group-item-action d-flex justify-content-between align-items-center" href="{{ route('transactions.show', [$transaction['transaction_group_id']]) }}">
@if('' !== (string) $transaction['transaction_group_title'])
{{ $transaction['transaction_group_title'] }}:
@endif
{{ $transaction['description'] }}
<span class="small">
<x-generic.amount :transaction="$transaction" />
</span>
</a>
@endforeach
</div>

View File

@@ -0,0 +1,250 @@
@extends('layout.v3.session')
@section('content')
TODO internals modal voor pagina settings
TODO wizard modal voor weet ik veel
TODO dark mode ook onthouden en dat script in het template
TODO date picker
TODO intro js
<x-dashboard.boxes :start="$start" :end="$end" />
<div class="row" x-data="index">
<div class="col-lg-8 col-md-12 col-sm-12">
<!--ACCOUNTS -->
<div class="card card-primary card-outline mb-4">
<div class="card-header with-border">
<div class="card-title"><a href="{{ route('accounts.index',['asset']) }}" title="{{ __('firefly.yourAccounts') }}">{{ __('firefly.yourAccounts') }}</a></div>
</div>
<div class="card-body">
<canvas id="accounts-chart" class="wide-chart" height="400" width="100%"></canvas>
</div>
<div class="card-footer">
<a href="{{ route('accounts.index',['asset']) }}" class="btn btn-primary btn-sm"><span class="bi bi-cash"></span> {{ __('firefly.go_to_asset_accounts') }}</a>
</div>
</div>
<!--BUDGETS -->
<div class="card card-outline mb-4">
<div class="card-header with-border">
<div class="card-title"><a href="{{ route('budgets.index') }}"
title="{{ __('firefly.budgetsAndSpending') }}">{{ __('firefly.budgetsAndSpending') }}</a></div>
</div>
<div class="card-body">
<canvas id="budgets-chart" class="wide-chart" height="400" width="100%"></canvas>
</div>
<div class="card-footer">
<a href="{{ route('budgets.index') }}" class="btn btn-primary btn-sm">
<span class="bi bi-pie-chart"></span>
<span>{{ __('firefly.go_to_budgets') }}</span>
</a>
</div>
</div>
<!--CATEGORIES -->
<div class="card card-outline mb-4">
<div class="card-header with-border">
<div class="card-title"><a href="{{ route('categories.index') }}"
title="{{ __('firefly.categories') }}">{{ __('firefly.categories') }}</a></div>
</div>
<div class="card-body">
<canvas id="categories-chart" class="wide-chart" height="400" width="100%"></canvas>
</div>
<div class="card-footer">
<a href="{{ route('categories.index') }}" class="btn btn-primary btn-sm">
<span class="bi bi-bookmark"></span>
<span>{{ __('firefly.go_to_categories') }}</span>
</a>
</div>
</div>
</div>
<div class="col-lg-4 col-md-6 col-sm-12">
<!--TRANSACTIONS -->
<div id="all_transactions">
@foreach($transactions as $data)
<div class="card mb-4">
<div class="card-header with-border">
<div class="card-title"><a href="{{ route('accounts.show', [$data['account']['id']]) }}">{{ $data['account']['name'] }}</a>
</div>
</div>
@if(count($data['transactions']) > 0)
<div class="card-body p-0">
<x-lists.groups-tiny :transactions="$data['transactions']" />
</div>
@endif
@if(0 === count($data['transactions']))
<div class="card-body">
<p>
<em>
{{ __('firefly.no_transactions_account', ['name' => $data['account']['name']]) }}
</em>
</p>
</div>
@endif
<div class="card-footer">
<!-- Single button -->
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary btn-sm dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">{{ __('firefly.sidebar_frontpage_create') }}</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item" href="{{ route('transactions.create', ['withdrawal']) }}?source={{ $data['account']['id'] }}">{{ __('firefly.create_new_withdrawal') }}</a>
</li>
<li>
<a class="dropdown-item" href="{{ route('transactions.create', ['deposit']) }}?destination={{ $data['account']['id'] }}">{{ __('firefly.create_new_deposit') }}</a>
</li>
<li>
<a class="dropdown-item" href="{{ route('transactions.create', ['transfer']) }}?source={{ $data['account']['id'] }}">{{ __('firefly.create_new_transfer') }}</a>
</li>
</ul>
</div>
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary btn-sm dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
{{ bladeAccountBalance($data['account']) }}
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item" href="{{ route('accounts.show', [$data['account']['id']]) }}">{{ __('firefly.show') }}</a>
</li>
<li>
<a class="dropdown-item" href="{{ route('accounts.reconcile', [$data['account']['id']]) }}">{{ __('firefly.reconcile') }}</a>
</li>
<li>
<a class="dropdown-item" href="{{ route('accounts.edit', [$data['account']['id']]) }}">{{ __('firefly.edit') }}</a>
</li>
<li>
<a class="dropdown-item" href="{{ route('accounts.delete', [$data['account']['id']]) }}">{{ __('firefly.delete') }}</a>
</li>
</ul>
</div>
</div>
</div>
@endforeach
</div>
@if($billCount > 0)
<!--BILLS -->
<div class="card mb-4">
<div class="card-header with-border">
<div class="card-title"><a href="{{ route('subscriptions.index') }}" title="{{ __('firefly.bills') }}">{{ __('firefly.bills') }}</a></div>
</div>
<div class="card-body">
<div class="center-chart">
<canvas id="bills-chart" class="low-chart" height="200"></canvas>
</div>
</div>
<div class="card-footer">
<a href="{{ route('bills.index') }}" class="btn btn-primary btn-sm"><span
class="bi bi-calendar"></span> {{ __('firefly.go_to_bills') }}</a>
</div>
</div>
@endif
<!--box for piggy bank data (JSON) -->
<div id="piggy_bank_overview">
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<!--EXPENSE ACCOUNTS -->
<div class="card mb-4">
<div class="card-header with-border">
<div class="card-title"><a href="{{ route('accounts.index',['expense']) }}" title="{{ __('firefly.expense_accounts') }}">{{ __('firefly.expense_accounts') }}</a>
</div>
</div>
<div class="card-body">
<canvas id="expense-accounts-chart" class="wide-chart" height="400"
width="100%"></canvas>
</div>
<div class="card-footer">
<a href="{{ route('accounts.index', ['expense']) }}" class="btn btn-primary btn-sm"><span
class="bi bi-cart"></span> {{ __('firefly.go_to_expense_accounts') }}</a>
</div>
</div>
<!--OPTIONAL REVENUE ACCOUNTS -->
<div class="card mb-4">
<div class="card-header with-border">
<div class="card-title"><a href="{{ route('accounts.index',['revenue']) }}"
title="{{ __('firefly.revenue_accounts') }}">{{ __('firefly.revenue_accounts') }}</a></div>
</div>
<div class="card-body">
<canvas id="revenue-accounts-chart" class="wide-chart" height="400"
width="100%"></canvas>
</div>
<div class="card-footer">
<a href="{{ route('accounts.index', ['revenue']) }}" class="btn btn-primary btn-sm"><span
class="bi bi-box-arrow-down"></span> {{ __('firefly.go_to_revenue_accounts') }}</a>
</div>
</div>
</div>
</div>
@endsection
@section('scripts')
@vite(['js/pages/dashboard/dashboard.js'])
<script type="text/javascript" nonce="{{ $JS_NONCE }}">
var lineColor = 'red';
var lineTextColor = '#000';
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
lineColor = '#a00';
lineTextColor = '#bec5cb';
}
var billCount = {{ $billCount }};
var accountFrontpageUrl = '{{ route('chart.account.frontpage') }}';
var accountRevenueUrl = '{{ route('chart.account.revenue') }}';
var accountExpenseUrl = '{{ route('chart.account.expense') }}';
var piggyInfoUrl = '{{ route('json.fp.piggy-banks') }}';
var drawVerticalLine = '';
{{-- render vertical line with text "today" --}}
@if($start->lte($today) && $end->gte($today))
drawVerticalLine = '{{ $today->isoFormat($monthAndDayFormat) }}';
@endif
</script>
<script type="text/javascript" src="v1/js/lib/Chart.bundle.min.js?v={{ $FF_BUILD_TIME }}"
nonce="{{ $JS_NONCE }}"></script>
<script type="text/javascript" src="v1/js/lib/chartjs-plugin-annotation.min.js?v={{ $FF_BUILD_TIME }}"
nonce="{{ $JS_NONCE }}"></script>
<script type="text/javascript" src="v1/js/ff/charts.defaults.js?v={{ $FF_BUILD_TIME }}"
nonce="{{ $JS_NONCE }}"></script>
<script type="text/javascript" src="v1/js/ff/charts.js?v={{ $FF_BUILD_TIME }}" nonce="{{ $JS_NONCE }}"></script>
<script type="text/javascript" src="v1/js/ff/index.js?v={{ $FF_BUILD_TIME }}" nonce="{{ $JS_NONCE }}"></script>
@endsection
{{--
<script type="text/javascript" nonce="{{ $JS_NONCE }}">
var lineColor = 'red';
var lineTextColor = '#000';
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
lineColor = '#a00';
lineTextColor = '#bec5cb';
}
var billCount = {{ $billCount }};
var accountFrontpageUrl = '{{ route('chart.account.frontpage') }}';
var accountRevenueUrl = '{{ route('chart.account.revenue') }}';
var accountExpenseUrl = '{{ route('chart.account.expense') }}';
var piggyInfoUrl = '{{ route('json.fp.piggy-banks') }}';
var drawVerticalLine = '';
<!--render vertical line with text "today" -->
@if($start->lte($today) and $end->gte($today))
drawVerticalLine = '{{ $today->isoFormat($monthAndDayFormat) }}';
@endif
</script>
<script type="text/javascript" src="v1/js/lib/Chart.bundle.min.js?v={{ $FF_BUILD_TIME }}"
nonce="{{ $JS_NONCE }}"></script>
<script type="text/javascript" src="v1/js/lib/chartjs-plugin-annotation.min.js?v={{ $FF_BUILD_TIME }}"
nonce="{{ $JS_NONCE }}"></script>
<script type="text/javascript" src="v1/js/ff/charts.defaults.js?v={{ $FF_BUILD_TIME }}"
nonce="{{ $JS_NONCE }}"></script>
<script type="text/javascript" src="v1/js/ff/charts.js?v={{ $FF_BUILD_TIME }}" nonce="{{ $JS_NONCE }}"></script>
<script type="text/javascript" src="v1/js/ff/index.js?v={{ $FF_BUILD_TIME }}" nonce="{{ $JS_NONCE }}"></script>
--}}

View File

@@ -0,0 +1,37 @@
<div class="card mb-4">
<div class="card-header">
<div class="card-title"><a href="{{ route('piggy-banks.index') }}" title="{{ __('firefly.piggyBanks') }}">{{ __('firefly.piggyBanks') }}</a></div>
</div>
<div class="card-body">
@foreach($info as $entry)
<strong>{{ $entry['name'] }}</strong><br/>
<div class="progress">
<div class="progress-bar progress-bar-striped w-{{ round($entry['percentage']) }}" role="progressbar" aria-valuenow="{{ $entry['percentage'] }}" aria-valuemin="0" aria-valuemax="100">
@if($entry['percentage'] > 20)
@if($convertToPrimary && 0 !== $avg['pc_amount'])
{{ \FireflyIII\Support\Facades\Steam::formatAmountBySymbol($entry['pc_amount'], $entry['primary_currency_symbol'], $entry['primary_currency_decimal_places'], false) }}
({{ \FireflyIII\Support\Facades\Steam::formatAmountBySymbol($entry['amount'], $entry['currency_symbol'], $entry['currency_decimal_places'], false) }})
@endif
@if(!$convertToPrimary)
{{ \FireflyIII\Support\Facades\Steam::formatAmountBySymbol($entry['amount'], $entry['currency_symbol'], $entry['currency_decimal_places'], false) }}
@endif
@endif
</div>
@if($entry['percentage'] <= 20)
&nbsp;
@if($convertToPrimary && 0 !== $avg['pc_amount'])
{{ \FireflyIII\Support\Facades\Steam::formatAmountBySymbol($entry['pc_amount'], $entry['primary_currency_symbol'], $entry['primary_currency_decimal_places'], false) }}
({{ \FireflyIII\Support\Facades\Steam::formatAmountBySymbol($entry['amount'], $entry['currency_symbol'], $entry['currency_decimal_places'], false) }})
@endif
@if(!$convertToPrimary)
{{ \FireflyIII\Support\Facades\Steam::formatAmountBySymbol($entry['amount'], $entry['currency_symbol'], $entry['currency_decimal_places'], false) }}
@endif
@endif
</div>
@endforeach
</div>
<div class="card-footer">
<a href="{{ route('piggy-banks.index') }}" class="btn btn-primary btn-sm"><span class="bi bi-bullseye"></span> {{ __('firefly.go_to_piggies') }}</a>
</div>
</div>

View File

@@ -62,7 +62,7 @@
})()
</script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@vite(['src/sass/app.scss'])
{{-- @vite(['css/app.css', 'js/app.js']) --}}
<style nonce="{{ $JS_NONCE ?? '' }}">
.monospace {font-family: monospace;font-size:11px;}
</style>

View File

@@ -62,7 +62,7 @@
})()
</script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@vite(['src/sass/app.scss'])
@vite(['sass/app.scss'])
</head>
<body class="login-page bg-body-secondary">

View File

@@ -0,0 +1,376 @@
<!doctype html>
<html lang="{{ __('config.html_language') }}">
<!--begin::Head-->
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="csrf-token" content="{{ csrf_token() }}">
<meta name="robots" content="noindex, nofollow, noarchive, noodp, NoImageIndex, noydir">
<meta name="apple-mobile-web-app-capable" content="yes">
<!--
If the base href URL begins with "http://" but you are sure it should start with "https://",
please visit the following page: https://bit.ly/FF3-broken-base-href
-->
<base href="{{ route('index', null, true) }}/">
<title>
@if('' !== (string)$pageTitle)
{{ $pageTitle }} »
@endif
@if('' !== (string)$subTitle && '' === (string) $pageTitle)
{{ $subTitle }} »
@endif
@if('Firefly III' !== $title)
{{ $title }} »
@endif
Firefly III
</title>
<!--begin::Accessibility Meta Tags-->
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<meta name="color-scheme" content="light dark" />
<meta name="theme-color" content="#007bff" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#1a1a1a" media="(prefers-color-scheme: dark)" />
<!--end::Accessibility Meta Tags-->
<!--begin::Accessibility Features-->
<!-- Skip links will be dynamically added by accessibility.js -->
@if('browser' === $darkMode)
<meta name="color-scheme" content="light dark">
@endif
@if('dark' === $darkMode)
<meta name="color-scheme" content="dark">
@endif
@if('light' === $darkMode)
<meta name="color-scheme" content="light">
@endif
@vite(['sass/app.scss'])
<x-layout.fav-icons />
<!--end::Accessibility Features-->
</head>
<!--end::Head-->
<!--begin::Body-->
<body class="layout-fixed sidebar-mini sidebar-expand-lg bg-body-tertiary">
<!--begin::App Wrapper-->
<div class="app-wrapper">
<!--begin::Header-->
<nav class="app-header navbar navbar-expand bg-body">
<!--begin::Container-->
<div class="container-fluid">
<!--begin::Start Navbar Links-->
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" data-lte-toggle="sidebar" href="#" role="button">
<i class="bi bi-list"></i>
</a>
</li>
</ul>
<!--begin::End Navbar Links-->
<ul class="navbar-nav ms-auto">
<!--begin::Navbar Search-->
<li class="nav-item">
<a class="nav-link" data-widget="navbar-search" href="{{route('search.index')}}" role="button">
<em class="bi bi-search"></em>
</a>
</li>
<!--end::Navbar Search-->
<!--begin::Fullscreen Toggle-->
<li class="nav-item">
<a class="nav-link" href="#" data-lte-toggle="fullscreen">
<i data-lte-icon="maximize" class="bi bi-arrows-fullscreen"></i>
<i data-lte-icon="minimize" class="bi bi-fullscreen-exit d-none"></i>
</a>
</li>
<!--end::Fullscreen Toggle-->
<!--begin::Color Mode Toggle (#6010)-->
<li class="nav-item dropdown">
<a
class="nav-link"
href="#"
id="bd-theme"
aria-label="Toggle color scheme"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<i class="bi bi-sun-fill" data-lte-theme-icon="light"></i>
<i class="bi bi-moon-fill d-none" data-lte-theme-icon="dark"></i>
<i class="bi bi-circle-half d-none" data-lte-theme-icon="auto"></i>
</a>
<ul
class="dropdown-menu dropdown-menu-end"
aria-labelledby="bd-theme"
style="--bs-dropdown-min-width: 8rem"
>
<li>
<button
type="button"
class="dropdown-item d-flex align-items-center"
data-bs-theme-value="light"
aria-pressed="false"
>
<i class="bi bi-sun-fill me-2"></i>
Light
<i class="bi bi-check-lg ms-auto d-none"></i>
</button>
</li>
<li>
<button
type="button"
class="dropdown-item d-flex align-items-center"
data-bs-theme-value="dark"
aria-pressed="false"
>
<i class="bi bi-moon-fill me-2"></i>
Dark
<i class="bi bi-check-lg ms-auto d-none"></i>
</button>
</li>
<li>
<button
type="button"
class="dropdown-item d-flex align-items-center active"
data-bs-theme-value="auto"
aria-pressed="true"
>
<i class="bi bi-circle-half me-2"></i>
Auto
<i class="bi bi-check-lg ms-auto d-none"></i>
</button>
</li>
</ul>
</li>
<!--end::Color Mode Toggle-->
<x-layout.create-menu />
<x-layout.user-menu />
<!--end::User Menu Dropdown-->
</ul>
<!--end::End Navbar Links-->
</div>
<!--end::Container-->
</nav>
<!--end::Header-->
<!--begin::Sidebar-->
<aside class="app-sidebar bg-body-secondary shadow" data-bs-theme="dark">
<!--begin::Sidebar Brand-->
<div class="sidebar-brand">
<!--begin::Brand Link-->
<a href="{{ route('index') }}" class="brand-link">
<!--begin::Brand Image-->
<img
src="./images/logo-session.png"
alt="Firefly III"
class="brand-image opacity-75 shadow"
/>
<!--end::Brand Image-->
<!--begin::Brand Text-->
<span class="brand-text fw-light">Firefly III</span>
<!--end::Brand Text-->
</a>
<!--end::Brand Link-->
</div>
<!--end::Sidebar Brand-->
<!--begin::Sidebar Wrapper-->
<div class="sidebar-wrapper">
<nav class="mt-2">
<!--begin::Sidebar Menu-->
<x-layout.sidebar />
<!--end::Sidebar Menu-->
<!-- Docs CTA (bottom of sidebar) -->
{{--
<div class="p-3 mt-3 border-top border-secondary border-opacity-25">
<a
href="https://docs.firefly-iii.org/"
class="btn btn-sm btn-outline-light w-100 d-flex align-items-center justify-content-center gap-2"
>
<i class="bi bi-book" aria-hidden="true"></i>
{{ trans('firefly.view_documentation') }}
</a>
</div>
--}}
</nav>
</div>
<!--end::Sidebar Wrapper-->
</aside>
<!--end::Sidebar-->
<!--begin::App Main-->
<main class="app-main">
<!--begin::App Content Header-->
<div class="app-content-header">
<!--begin::Container-->
<div class="container-fluid">
<!--begin::Row-->
<div class="row">
<div class="col-sm-6">
<h3 class="mb-0">
<em class="bi {{ $mainTitleIcon }}"></em>
{{ $pageTitle }}
<small class="text-xs text-muted">@if(isset($subTitleIcon))<em class="bi {{ $subTitleIcon }}"></em>X @endif{{$subTitle}}</small>
</h3>
</div>
<div class="col-sm-6">
{{ Breadcrumbs::render() }}
</div>
</div>
<!--end::Row-->
</div>
<!--end::Container-->
</div>
<!--end::App Content Header-->
<!--begin::App Content-->
<div class="app-content">
<!--begin::Container-->
<div class="container-fluid">
@yield('content')
<!--begin::Row-->
<!--end::Row-->
<!--begin::Row-->
<!-- /.row (main row) -->
</div>
<!--end::Container-->
</div>
<!--end::App Content-->
</main>
<!--end::App Main-->
<!--begin::Footer-->
<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>
</div>
<!--end::To the end-->
<!--begin::Copyright-->
<span>
<a href="https://www.firefly-iii.org/" target="_blank" title="Firefly III">Firefly III</a> &copy; James Cole, <a href="https://www.gnu.org/licenses/agpl-3.0.html" title="AGPL-3.0-or-later.">AGPL-3.0-or-later</a>.
</span>
<!--end::Copyright-->
</footer>
<!--end::Footer-->
</div>
<!--end::App Wrapper-->
<!--begin::Script-->
<!--begin::Third Party Plugin(OverlayScrollbars)-->
<!--end::Third Party Plugin(OverlayScrollbars)--><!--begin::Required Plugin(popperjs for Bootstrap 5)-->
<!--end::Required Plugin(popperjs for Bootstrap 5)--><!--begin::Required Plugin(Bootstrap 5)-->
<!--end::Required Plugin(Bootstrap 5)--><!--begin::Required Plugin(AdminLTE)-->
<!--end::Required Plugin(AdminLTE)-->
<!--begin::OverlayScrollbars Configure-->
<!--end::OverlayScrollbars Configure-->
{{-- Moment JS --}}
<script src="v1/js/lib/moment.min.js?v={{ $FF_BUILD_TIME }}" type="text/javascript" nonce="{{ $JS_NONCE }}"></script>
<script src="v1/js/lib/moment/{{ str_replace($language,'-','_') }}.js?v={{ $FF_BUILD_TIME }}" type="text/javascript" nonce="{{ $JS_NONCE }}"></script>
{{-- All kinds of variables. --}}
<script src="{{ route('javascript.variables') }}?ext=.js&amp;v={{ $FF_VERSION }}@if(isset($account))&amp;account={{ $account->id }}@endif" type="text/javascript" nonce="{{ $JS_NONCE }}"></script>
{{-- Base script: jquery and bootstrap --}}
<script src="v1/js/app.js?v={{ $FF_BUILD_TIME }}" type="text/javascript" nonce="{{ $JS_NONCE }}"></script>
{{-- date range picker, current template, etc. --}}
<script src="v1/js/lib/daterangepicker.js?v={{ $FF_BUILD_TIME }}" type="text/javascript" nonce="{{ $JS_NONCE }}"></script>
<script src="v1/lib/adminlte/js/adminlte.min.js?v={{ $FF_BUILD_TIME }}" type="text/javascript" nonce="{{ $JS_NONCE }}"></script>
<script type="text/javascript" src="v1/js/lib/accounting.min.js?v={{ $FF_BUILD_TIME }}" nonce="{{ $JS_NONCE }}"></script>
{{-- Firefly III code --}}
<script type="text/javascript" src="v1/js/ff/firefly.js?v={{ $FF_BUILD_TIME }}" nonce="{{ $JS_NONCE }}"></script>
<script type="text/javascript" src="v1/js/ff/help.js?v={{ $FF_BUILD_TIME }}" nonce="{{ $JS_NONCE }}"></script>
@yield('scripts')
<!--begin::Color Mode Toggle (#6010)-->
<script>
(() => {
'use strict';
const STORAGE_KEY = 'lte-theme';
const getStoredTheme = () => localStorage.getItem(STORAGE_KEY);
const setStoredTheme = (theme) => localStorage.setItem(STORAGE_KEY, theme);
const prefersDark = () => globalThis.matchMedia('(prefers-color-scheme: dark)').matches;
const getPreferredTheme = () => {
const stored = getStoredTheme();
if (stored) return stored;
return prefersDark() ? 'dark' : 'light';
};
const setTheme = (theme) => {
const resolved = theme === 'auto' ? (prefersDark() ? 'dark' : 'light') : theme;
document.documentElement.setAttribute('data-bs-theme', resolved);
};
setTheme(getPreferredTheme());
const showActiveTheme = (theme) => {
// Highlight the active dropdown option
document.querySelectorAll('[data-bs-theme-value]').forEach((el) => {
el.classList.remove('active');
el.setAttribute('aria-pressed', 'false');
const check = el.querySelector('.bi-check-lg');
if (check) check.classList.add('d-none');
});
const active = document.querySelector(`[data-bs-theme-value="${theme}"]`);
if (active) {
active.classList.add('active');
active.setAttribute('aria-pressed', 'true');
const check = active.querySelector('.bi-check-lg');
if (check) check.classList.remove('d-none');
}
// Sync the topbar trigger icon
document.querySelectorAll('[data-lte-theme-icon]').forEach((icon) => {
icon.classList.toggle('d-none', icon.dataset.lteThemeIcon !== theme);
});
};
globalThis.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
const stored = getStoredTheme();
if (!stored || stored === 'auto') setTheme(getPreferredTheme());
});
document.addEventListener('DOMContentLoaded', () => {
showActiveTheme(getPreferredTheme());
document.querySelectorAll('[data-bs-theme-value]').forEach((toggle) => {
toggle.addEventListener('click', () => {
const theme = toggle.getAttribute('data-bs-theme-value');
setStoredTheme(theme);
setTheme(theme);
showActiveTheme(theme);
});
});
});
})();
</script>
<!--end::Color Mode Toggle-->
<!-- OPTIONAL SCRIPTS -->
<!-- sortablejs -->
<!-- sortablejs -->
<!-- apexcharts -->
<!-- ChartJS -->
<!-- jsvectormap -->
<!-- jsvectormap -->
<!--end::Script-->
<form id="logout-form" action="{{ route('logout') }}" method="POST" class="hidden">
<input type="hidden" name="_token" value="{{ csrf_token() }}"/>
</form>
</body>
<!--end::Body-->
</html>

View File

@@ -1,30 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<base href="{{ route('index') }}/" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ 'login_page_title'|_ }}</title>
<!-- fonts and styles -->
<link rel="stylesheet" href="v3-local/css/fonts.css?v={{ FF_BUILD_TIME }}">
<link rel="stylesheet" href="v3-local/lib/fontawesome-free/css/all.min.css?v={{ FF_BUILD_TIME }}">
<link rel="stylesheet" href="v3-local/lib/icheck-bootstrap/icheck-bootstrap.min.css?v={{ FF_BUILD_TIME }}">
<link rel="stylesheet" href="v3-local/dist/css/adminlte.min.css?v={{ FF_BUILD_TIME }}">
{# favicons #}
{% include 'partials.favicons' %}
</head>
<body class="hold-transition login-page dark-mode">
{% block content %}{% endblock %}
<!-- jQuery -->
<script src="v3-local/lib/jquery/jquery.min.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script>
<!-- Bootstrap 4 -->
<script src="v3-local/lib/bootstrap/js/bootstrap.bundle.min.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script>
<!-- AdminLTE App -->
<script src="v3-local/dist/js/adminlte.min.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script>
{% block scripts %}
{% endblock %}
</body>
</html>

View File

@@ -0,0 +1,13 @@
@if(count($breadcrumbs) > 0)
<ol class="breadcrumb float-sm-end">
@foreach($breadcrumbs as $breadcrumb)
@if($breadcrumb->url && !$loop->last)
<li class="breadcrumb-item"><a href="{{ $breadcrumb->url }}">{{ $breadcrumb->title }}</a></li>
@endif
@if(!$breadcrumb->url || $loop->last)
<li class="breadcrumb-item active" aria-current="page">{{ $breadcrumb->title }}</li>
@endif
@endforeach
</ol>
@endif

View File

@@ -57,11 +57,7 @@
<i class="fa-solid fa-plus-circle"></i>
</a>
<div class="dropdown-menu dropdown-menu-lg dropdown-menu-end">
<!-- withdrawal, deposit, transfer -->
<a href="{{ route('transactions.create', ['withdrawal']) }}" class="dropdown-item">
<em class="fa-solid fa-arrow-left fa-fw me-2"></em>
{{ __('firefly.create_new_withdrawal') }}
</a>
<a href="{{ route('transactions.create', ['deposit']) }}" class="dropdown-item">
<em class="fa-solid fa-arrow-right fa-fw me-2"></em>
{{ __('firefly.create_new_deposit') }}