Compare commits

...

26 Commits

Author SHA1 Message Date
github-actions[bot]
f3b387fc22 Merge pull request #10673 from firefly-iii/release-1753936796
🤖 Automatically merge the PR into the develop branch.
2025-07-31 06:40:05 +02:00
JC5
03904ffcde 🤖 Auto commit for release 'develop' on 2025-07-31 2025-07-31 06:39:56 +02:00
James Cole
a7973190c2 Start improving bill overview. 2025-07-30 20:35:28 +02:00
James Cole
671ff95f22 Improved subscription overview 2025-07-30 18:57:32 +02:00
github-actions[bot]
01181ceea9 Merge pull request #10672 from firefly-iii/release-1753879077
🤖 Automatically merge the PR into the develop branch.
2025-07-30 14:38:08 +02:00
JC5
97643639d1 🤖 Auto commit for release 'develop' on 2025-07-30 2025-07-30 14:37:57 +02:00
James Cole
424783c47b Add convertToNative to cache key. 2025-07-30 14:33:26 +02:00
James Cole
ea0ced70b2 Clean up sankey. 2025-07-30 11:24:00 +02:00
James Cole
1a633e64ef Category chart will also convert. 2025-07-30 10:20:35 +02:00
James Cole
30da3f4399 Also include budget in currency conversion. 2025-07-30 09:59:52 +02:00
github-actions[bot]
6bdff95d87 Merge pull request #10671 from firefly-iii/release-1753858574
🤖 Automatically merge the PR into the develop branch.
2025-07-30 08:56:22 +02:00
JC5
895ae279d5 🤖 Auto commit for release 'develop' on 2025-07-30 2025-07-30 08:56:14 +02:00
James Cole
8b57f45963 Return the promise. 2025-07-30 08:37:19 +02:00
James Cole
a2d2b7edd3 Add warning to translations. 2025-07-30 07:27:00 +02:00
James Cole
3d65f00c6e Correct tag date and add warning. 2025-07-30 07:26:50 +02:00
James Cole
333004c4d9 Upgrade to font awesome 7, make sure chart includes currencies outside of limits. 2025-07-30 06:59:58 +02:00
James Cole
7451659824 Merge pull request #10661 from firefly-iii/dependabot/npm_and_yarn/develop/cross-env-10.0.0
Bump cross-env from 7.0.3 to 10.0.0
2025-07-28 20:44:31 +02:00
mergify[bot]
ec89c23ace Merge branch 'develop' into dependabot/npm_and_yarn/develop/cross-env-10.0.0 2025-07-28 05:08:38 +00:00
James Cole
92d07d346f Merge pull request #10660 from firefly-iii/dependabot/npm_and_yarn/develop/fortawesome/fontawesome-free-7.0.0
Bump @fortawesome/fontawesome-free from 6.7.2 to 7.0.0
2025-07-28 07:08:02 +02:00
mergify[bot]
a7ac894af2 Merge branch 'develop' into dependabot/npm_and_yarn/develop/cross-env-10.0.0 2025-07-28 04:39:49 +00:00
dependabot[bot]
20f89e3a7c Bump cross-env from 7.0.3 to 10.0.0
Bumps [cross-env](https://github.com/kentcdodds/cross-env) from 7.0.3 to 10.0.0.
- [Release notes](https://github.com/kentcdodds/cross-env/releases)
- [Changelog](https://github.com/kentcdodds/cross-env/blob/main/CHANGELOG.md)
- [Commits](https://github.com/kentcdodds/cross-env/compare/v7.0.3...v10.0.0)

---
updated-dependencies:
- dependency-name: cross-env
  dependency-version: 10.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-28 04:39:15 +00:00
mergify[bot]
5d2f11c3c7 Merge branch 'develop' into dependabot/npm_and_yarn/develop/fortawesome/fontawesome-free-7.0.0 2025-07-28 04:38:58 +00:00
dependabot[bot]
1eea79e431 Bump @fortawesome/fontawesome-free from 6.7.2 to 7.0.0
Bumps [@fortawesome/fontawesome-free](https://github.com/FortAwesome/Font-Awesome) from 6.7.2 to 7.0.0.
- [Release notes](https://github.com/FortAwesome/Font-Awesome/releases)
- [Changelog](https://github.com/FortAwesome/Font-Awesome/blob/7.x/CHANGELOG.md)
- [Commits](https://github.com/FortAwesome/Font-Awesome/compare/6.7.2...7.0.0)

---
updated-dependencies:
- dependency-name: "@fortawesome/fontawesome-free"
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-28 04:38:22 +00:00
James Cole
1aaaac67ca Merge pull request #10659 from firefly-iii/dependabot/composer/develop/phpstan/phpstan-2.1.20
Bump phpstan/phpstan from 2.1.19 to 2.1.20
2025-07-28 06:38:11 +02:00
mergify[bot]
1eb86639c9 Merge branch 'develop' into dependabot/composer/develop/phpstan/phpstan-2.1.20 2025-07-28 04:31:47 +00:00
dependabot[bot]
28b911c4ee Bump phpstan/phpstan from 2.1.19 to 2.1.20
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 2.1.19 to 2.1.20.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Changelog](https://github.com/phpstan/phpstan/blob/2.1.x/CHANGELOG.md)
- [Commits](https://github.com/phpstan/phpstan/compare/2.1.19...2.1.20)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-28 04:31:06 +00:00
28 changed files with 866 additions and 503 deletions

View File

@@ -406,20 +406,20 @@
},
{
"name": "friendsofphp/php-cs-fixer",
"version": "v3.84.0",
"version": "v3.85.1",
"source": {
"type": "git",
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
"reference": "38dad0767bf2a9b516b976852200ae722fe984ca"
"reference": "2fb6d7f6c3398dca5786a1635b27405d73a417ba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/38dad0767bf2a9b516b976852200ae722fe984ca",
"reference": "38dad0767bf2a9b516b976852200ae722fe984ca",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/2fb6d7f6c3398dca5786a1635b27405d73a417ba",
"reference": "2fb6d7f6c3398dca5786a1635b27405d73a417ba",
"shasum": ""
},
"require": {
"clue/ndjson-react": "^1.0",
"clue/ndjson-react": "^1.3",
"composer/semver": "^3.4",
"composer/xdebug-handler": "^3.0.5",
"ext-filter": "*",
@@ -429,12 +429,12 @@
"fidry/cpu-core-counter": "^1.2",
"php": "^7.4 || ^8.0",
"react/child-process": "^0.6.6",
"react/event-loop": "^1.0",
"react/promise": "^2.11 || ^3.0",
"react/socket": "^1.0",
"react/stream": "^1.0",
"react/event-loop": "^1.5",
"react/promise": "^3.2",
"react/socket": "^1.16",
"react/stream": "^1.4",
"sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0",
"symfony/console": "^5.4.45 || ^6.4.13 || ^7.0",
"symfony/console": "^5.4.47 || ^6.4.13 || ^7.0",
"symfony/event-dispatcher": "^5.4.45 || ^6.4.13 || ^7.0",
"symfony/filesystem": "^5.4.45 || ^6.4.13 || ^7.0",
"symfony/finder": "^5.4.45 || ^6.4.17 || ^7.0",
@@ -499,7 +499,7 @@
],
"support": {
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.84.0"
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.85.1"
},
"funding": [
{
@@ -507,7 +507,7 @@
"type": "github"
}
],
"time": "2025-07-15T18:21:57+00:00"
"time": "2025-07-29T22:22:50+00:00"
},
{
"name": "psr/container",

View File

@@ -36,6 +36,7 @@ use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
use FireflyIII\Support\Http\Api\CleansChartData;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection;
@@ -77,6 +78,8 @@ class BudgetController extends Controller
/**
* TODO see autocomplete/accountcontroller
*
* @throws FireflyException
*/
public function dashboard(DateRequest $request): JsonResponse
{
@@ -107,19 +110,37 @@ class BudgetController extends Controller
private function processBudget(Budget $budget, Carbon $start, Carbon $end): array
{
// get all limits:
$limits = $this->blRepository->getBudgetLimits($budget, $start, $end);
$rows = [];
$limits = $this->blRepository->getBudgetLimits($budget, $start, $end);
$rows = [];
$spent = $this->opsRepository->listExpenses($start, $end, null, new Collection([$budget]));
$expenses = $this->processExpenses($budget->id, $spent, $start, $end);
/**
* @var int $currencyId
* @var array $row
*/
foreach ($expenses as $currencyId => $row) {
// budgeted, left and overspent are now 0.
$limit = $this->filterLimit($currencyId, $limits);
if (null !== $limit) {
$row['budgeted'] = $limit->amount;
$row['left'] = bcsub($row['budgeted'], bcmul($row['spent'], '-1'));
$row['overspent'] = bcmul($row['left'], '-1');
$row['left'] = 1 === bccomp($row['left'], '0') ? $row['left'] : '0';
$row['overspent'] = 1 === bccomp($row['overspent'], '0') ? $row['overspent'] : '0';
}
$rows[] = $row;
}
// if no limits
if (0 === $limits->count()) {
// return as a single item in an array
$rows = $this->noBudgetLimits($budget, $start, $end);
}
if ($limits->count() > 0) {
$rows = $this->budgetLimits($budget, $limits);
}
// if (0 === $limits->count()) {
// return as a single item in an array
// $rows = $this->noBudgetLimits($budget, $start, $end);
// }
// is always an array
$return = [];
$return = [];
foreach ($rows as $row) {
$current = [
'label' => $budget->name,
@@ -253,4 +274,39 @@ class BudgetController extends Controller
return $result;
}
private function filterLimit(int $currencyId, Collection $limits): ?BudgetLimit
{
$amount = '0';
$limit = null;
$converter = new ExchangeRateConverter();
/** @var BudgetLimit $current */
foreach ($limits as $current) {
if (true === $this->convertToNative) {
if ($current->transaction_currency_id === $this->nativeCurrency->id) {
// simply add it.
$amount = bcadd($amount, (string)$current->amount);
Log::debug(sprintf('Set amount in limit to %s', $amount));
}
if ($current->transaction_currency_id !== $this->nativeCurrency->id) {
// convert and then add it.
$converted = $converter->convert($current->transactionCurrency, $this->nativeCurrency, $limit->start_date, $limit->amount);
$amount = bcadd($amount, $converted);
Log::debug(sprintf('Budgeted in limit #%d: %s %s, converted to %s %s', $current->id, $current->transactionCurrency->code, $current->amount, $this->nativeCurrency->code, $converted));
Log::debug(sprintf('Set amount in limit to %s', $amount));
}
}
if ($current->transaction_currency_id === $currencyId) {
$limit = $current;
}
}
if (null !== $limit && true === $this->convertToNative) {
// convert and add all amounts.
$limit->amount = app('steam')->positive($amount);
Log::debug(sprintf('Final amount in limit with converted amount %s', $limit->amount));
}
return $limit;
}
}

View File

@@ -25,17 +25,20 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Chart;
use Carbon\Carbon;
use FireflyIII\Api\V2\Controllers\Controller;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V2\Request\Generic\DateRequest;
use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\Http\Api\CleansChartData;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
/**
* Class BudgetController
@@ -45,6 +48,8 @@ class CategoryController extends Controller
use CleansChartData;
use ValidatesUserGroupTrait;
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
private AccountRepositoryInterface $accountRepos;
private CurrencyRepositoryInterface $currencyRepos;
@@ -79,9 +84,10 @@ class CategoryController extends Controller
/** @var Carbon $end */
$end = $this->parameters->get('end');
$accounts = $this->accountRepos->getAccountsByType([AccountTypeEnum::DEBT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::ASSET->value, AccountTypeEnum::DEFAULT->value]);
$accounts = $this->accountRepos->getAccountsByType([AccountTypeEnum::DEBT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::ASSET->value]);
$currencies = [];
$return = [];
$converter = new ExchangeRateConverter();
// get journals for entire period:
/** @var GroupCollectorInterface $collector */
@@ -93,20 +99,40 @@ class CategoryController extends Controller
/** @var array $journal */
foreach ($journals as $journal) {
$currencyId = (int) $journal['currency_id'];
$currency = $currencies[$currencyId] ?? $this->currencyRepos->find($currencyId);
$currencies[$currencyId] = $currency;
$categoryName = $journal['category_name'] ?? (string) trans('firefly.no_category');
$amount = app('steam')->positive($journal['amount']);
$key = sprintf('%s-%s', $categoryName, $currency->code);
// find journal:
$journalCurrencyId = (int)$journal['currency_id'];
$currency = $currencies[$journalCurrencyId] ?? $this->currencyRepos->find($journalCurrencyId);
$currencies[$journalCurrencyId] = $currency;
$currencyId = (int)$currency->id;
$currencyName = (string)$currency->name;
$currencyCode = (string)$currency->code;
$currencySymbol = (string)$currency->symbol;
$currencyDecimalPlaces = (int)$currency->decimal_places;
$amount = app('steam')->positive($journal['amount']);
// overrule if necessary:
if ($this->convertToNative && $journalCurrencyId !== $this->nativeCurrency->id) {
$currencyId = (int)$this->nativeCurrency->id;
$currencyName = (string)$this->nativeCurrency->name;
$currencyCode = (string)$this->nativeCurrency->code;
$currencySymbol = (string)$this->nativeCurrency->symbol;
$currencyDecimalPlaces = (int)$this->nativeCurrency->decimal_places;
$convertedAmount = $converter->convert($currency, $this->nativeCurrency, $journal['date'], $amount);
Log::debug(sprintf('Converted %s %s to %s %s', $journal['currency_code'], $amount, $this->nativeCurrency->code, $convertedAmount));
$amount = $convertedAmount;
}
$categoryName = $journal['category_name'] ?? (string)trans('firefly.no_category');
$key = sprintf('%s-%s', $categoryName, $currencyCode);
// create arrays
$return[$key] ??= [
'label' => $categoryName,
'currency_id' => (string) $currency->id,
'currency_code' => $currency->code,
'currency_name' => $currency->name,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'currency_id' => (string)$currencyId,
'currency_code' => $currencyCode,
'currency_name' => $currencyName,
'currency_symbol' => $currencySymbol,
'currency_decimal_places' => $currencyDecimalPlaces,
'period' => null,
'start' => $start->toAtomString(),
'end' => $end->toAtomString(),
@@ -114,12 +140,12 @@ class CategoryController extends Controller
];
// add monies
$return[$key]['amount'] = bcadd($return[$key]['amount'], (string) $amount);
$return[$key]['amount'] = bcadd($return[$key]['amount'], (string)$amount);
}
$return = array_values($return);
// order by amount
usort($return, static fn (array $a, array $b) => (float) $a['amount'] < (float) $b['amount'] ? 1 : -1);
usort($return, static fn (array $a, array $b) => (float)$a['amount'] < (float)$b['amount'] ? 1 : -1);
return response()->json($this->clean($return));
}

View File

@@ -28,7 +28,9 @@ use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Bill;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Support\JsonApi\Enrichments\SubscriptionEnrichment;
use FireflyIII\Transformers\BillTransformer;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Pagination\LengthAwarePaginator;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
@@ -76,6 +78,15 @@ class ShowController extends Controller
$bills = $bills->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
$paginator = new LengthAwarePaginator($bills, $count, $pageSize, $this->parameters->get('page'));
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new SubscriptionEnrichment();
$enrichment->setUser($admin);
$enrichment->setConvertToNative($this->convertToNative);
$enrichment->setNative($this->nativeCurrency);
$bills = $enrichment->enrich($bills);
/** @var BillTransformer $transformer */
$transformer = app(BillTransformer::class);
$transformer->setParameters($this->parameters);

View File

@@ -154,12 +154,18 @@ class TagController extends Controller
*/
public function index(TagRepositoryInterface $repository)
{
// start with oldest tag
// start with the oldest tag
$first = session('first', today()) ?? today();
$oldestTagDate = $repository->oldestTag() instanceof Tag ? $repository->oldestTag()->date : clone $first;
$newestTagDate = $repository->newestTag() instanceof Tag ? $repository->newestTag()->date : today();
$oldestTagDate->startOfYear();
$newestTagDate->endOfYear();
if ($oldestTagDate->year < 1970) {
$oldestTagDate = Carbon::create(1970, 1, 1, 0, 0, 0, config('app.timezone'));
request()->session()->flash('error', trans('firefly.bad_date_transaction'));
}
$tags = [];
$tags['no-date'] = $repository->getTagsInYear(null);

View File

@@ -24,14 +24,16 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\Budget;
use Deprecated;
use Carbon\Carbon;
use Deprecated;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\Account;
use FireflyIII\Models\Budget;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use FireflyIII\Support\Report\Summarizer\TransactionSummarizer;
use FireflyIII\Support\Repositories\UserGroup\UserGroupInterface;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
@@ -55,17 +57,17 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
$total = '0';
$count = 0;
foreach ($budget->budgetlimits as $limit) {
$diff = (int) $limit->start_date->diffInDays($limit->end_date, true);
$diff = (int)$limit->start_date->diffInDays($limit->end_date, true);
$diff = 0 === $diff ? 1 : $diff;
$amount = $limit->amount;
$perDay = bcdiv((string) $amount, (string) $diff);
$perDay = bcdiv((string)$amount, (string)$diff);
$total = bcadd($total, $perDay);
++$count;
app('log')->debug(sprintf('Found %d budget limits. Per day is %s, total is %s', $count, $perDay, $total));
}
$avg = $total;
if ($count > 0) {
$avg = bcdiv($total, (string) $count);
$avg = bcdiv($total, (string)$count);
}
app('log')->debug(sprintf('%s / %d = %s = average.', $total, $count, $avg));
@@ -93,9 +95,9 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
/** @var array $journal */
foreach ($journals as $journal) {
// prep data array for currency:
$budgetId = (int) $journal['budget_id'];
$budgetId = (int)$journal['budget_id'];
$budgetName = $journal['budget_name'];
$currencyId = (int) $journal['currency_id'];
$currencyId = (int)$journal['currency_id'];
$key = sprintf('%d-%d', $budgetId, $currencyId);
$data[$key] ??= [
@@ -110,7 +112,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
'entries' => [],
];
$date = $journal['date']->format($carbonFormat);
$data[$key]['entries'][$date] = bcadd($data[$key]['entries'][$date] ?? '0', (string) $journal['amount']);
$data[$key]['entries'][$date] = bcadd($data[$key]['entries'][$date] ?? '0', (string)$journal['amount']);
}
return $data;
@@ -124,7 +126,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
public function listExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $budgets = null): array
{
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
if ($accounts instanceof Collection && $accounts->count() > 0) {
$collector->setAccounts($accounts);
@@ -136,15 +138,41 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
$collector->setBudgets($this->getBudgets());
}
$collector->withBudgetInformation()->withAccountInformation()->withCategoryInformation();
$journals = $collector->getExtractedJournals();
$array = [];
$journals = $collector->getExtractedJournals();
$array = [];
// if needs conversion to native.
$convertToNative = Amount::convertToNative($this->user);
$nativeCurrency = Amount::getNativeCurrencyByUserGroup($this->userGroup);
$currencyId = (int) $nativeCurrency->id;
$currencyCode = $nativeCurrency->code;
$currencyName = $nativeCurrency->name;
$currencySymbol = $nativeCurrency->symbol;
$currencyDecimalPlaces = $nativeCurrency->decimal_places;
$converter = new ExchangeRateConverter();
$currencies = [
$currencyId => $nativeCurrency,
];
foreach ($journals as $journal) {
$currencyId = (int) $journal['currency_id'];
$budgetId = (int) $journal['budget_id'];
$budgetName = (string) $journal['budget_name'];
$amount = app('steam')->negative($journal['amount']);
$journalCurrencyId = (int)$journal['currency_id'];
if (false === $convertToNative) {
$currencyId = $journalCurrencyId;
$currencyName = $journal['currency_name'];
$currencySymbol = $journal['currency_symbol'];
$currencyCode = $journal['currency_code'];
$currencyDecimalPlaces = $journal['currency_decimal_places'];
}
if (true === $convertToNative && $journalCurrencyId !== $currencyId) {
$currencies[$journalCurrencyId] ??= TransactionCurrency::find($journalCurrencyId);
$amount = $converter->convert($currencies[$journalCurrencyId], $nativeCurrency, $journal['date'], $amount);
}
// catch "no category" entries.
$budgetId = (int)$journal['budget_id'];
$budgetName = (string)$journal['budget_name'];
// catch "no budget" entries.
if (0 === $budgetId) {
continue;
}
@@ -153,10 +181,10 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
$array[$currencyId] ??= [
'budgets' => [],
'currency_id' => $currencyId,
'currency_name' => $journal['currency_name'],
'currency_symbol' => $journal['currency_symbol'],
'currency_code' => $journal['currency_code'],
'currency_decimal_places' => $journal['currency_decimal_places'],
'currency_name' => $currencyName,
'currency_symbol' => $currencySymbol,
'currency_code' => $currencyCode,
'currency_decimal_places' => $currencyDecimalPlaces,
];
// info about the categories:
@@ -168,9 +196,9 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
// add journal to array:
// only a subset of the fields.
$journalId = (int) $journal['transaction_journal_id'];
$journalId = (int)$journal['transaction_journal_id'];
$array[$currencyId]['budgets'][$budgetId]['transaction_journals'][$journalId] = [
'amount' => app('steam')->negative($journal['amount']),
'amount' => $amount,
'destination_account_id' => $journal['destination_account_id'],
'destination_account_name' => $journal['destination_account_name'],
'source_account_id' => $journal['source_account_id'],

View File

@@ -0,0 +1,160 @@
<?php
declare(strict_types=1);
namespace FireflyIII\Support\JsonApi\Enrichments;
use FireflyIII\Models\Bill;
use FireflyIII\Models\Note;
use FireflyIII\Models\ObjectGroup;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\UserGroup;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class SubscriptionEnrichment implements EnrichmentInterface
{
private User $user;
private UserGroup $userGroup;
private Collection $collection;
private bool $convertToNative = false;
private array $subscriptionIds = [];
private array $objectGroups = [];
private array $mappedObjects = [];
private array $notes = [];
private TransactionCurrency $nativeCurrency;
public function enrich(Collection $collection): Collection
{
$this->collection = $collection;
$this->collectSubscriptionIds();
$this->collectNotes();
$this->collectObjectGroups();
$notes = $this->notes;
$objectGroups = $this->objectGroups;
$this->collection = $this->collection->map(function (Bill $item) use ($notes, $objectGroups) {
$id = (int) $item->id;
$currency = $item->transactionCurrency;
$meta = [
'notes' => null,
'object_group_id' => null,
'object_group_title' => null,
'object_group_order' => null,
];
$amounts = [
'amount_min' => Steam::bcround($item->amount_min, $currency->decimal_places),
'amount_max' => Steam::bcround($item->amount_max, $currency->decimal_places),
'average' => Steam::bcround(bcdiv(bcadd($item->amount_min, $item->amount_max), '2'), $currency->decimal_places),
];
// add object group if available
if (array_key_exists($id, $this->mappedObjects)) {
$key = $this->mappedObjects[$id];
$meta['object_group_id'] = $objectGroups[$key]['id'];
$meta['object_group_title'] = $objectGroups[$key]['title'];
$meta['object_group_order'] = $objectGroups[$key]['order'];
}
// Add notes if available.
if (array_key_exists($item->id, $notes)) {
$meta['notes'] = $notes[$item->id];
}
// Convert amounts to native currency if needed
if ($this->convertToNative && $item->currency_id !== $this->nativeCurrency->id) {
$converter = new ExchangeRateConverter();
$amounts = [
'amount_min' => Steam::bcround($converter->convert($item->transactionCurrency, $this->nativeCurrency, today(), $item->amount_min), $this->nativeCurrency->decimal_places),
'amount_max' => Steam::bcround($converter->convert($item->transactionCurrency, $this->nativeCurrency, today(), $item->amount_max), $this->nativeCurrency->decimal_places),
];
$amounts['average'] = Steam::bcround(bcdiv(bcadd($amounts['amount_min'], $amounts['amount_max']), '2'), $this->nativeCurrency->decimal_places);
}
$item->amounts = $amounts;
$item->meta = $meta;
return $item;
});
return $collection;
}
public function enrichSingle(array|Model $model): array|Model
{
Log::debug(__METHOD__);
$collection = new Collection([$model]);
$collection = $this->enrich($collection);
return $collection->first();
}
private function collectNotes(): void
{
$notes = Note::query()->whereIn('noteable_id', $this->subscriptionIds)
->whereNotNull('notes.text')
->where('notes.text', '!=', '')
->where('noteable_type', Bill::class)->get(['notes.noteable_id', 'notes.text'])->toArray()
;
foreach ($notes as $note) {
$this->notes[(int) $note['noteable_id']] = (string) $note['text'];
}
Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
}
public function setUser(User $user): void
{
$this->user = $user;
$this->userGroup = $user->userGroup;
}
public function setUserGroup(UserGroup $userGroup): void
{
$this->userGroup = $userGroup;
}
public function setConvertToNative(bool $convertToNative): void
{
$this->convertToNative = $convertToNative;
}
public function setNative(TransactionCurrency $nativeCurrency): void
{
$this->nativeCurrency = $nativeCurrency;
}
private function collectSubscriptionIds(): void
{
/** @var Bill $bill */
foreach ($this->collection as $bill) {
$this->subscriptionIds[] = (int) $bill->id;
}
$this->subscriptionIds = array_unique($this->subscriptionIds);
}
private function collectObjectGroups(): void
{
$set = DB::table('object_groupables')
->whereIn('object_groupable_id', $this->subscriptionIds)
->where('object_groupable_type', Bill::class)
->get(['object_groupable_id', 'object_group_id'])
;
$ids = array_unique($set->pluck('object_group_id')->toArray());
foreach ($set as $entry) {
$this->mappedObjects[(int)$entry->object_groupable_id] = (int)$entry->object_group_id;
}
$groups = ObjectGroup::whereIn('id', $ids)->get(['id', 'title', 'order'])->toArray();
foreach ($groups as $group) {
$group['id'] = (int) $group['id'];
$group['order'] = (int) $group['order'];
$this->objectGroups[(int)$group['id']] = $group;
}
}
}

View File

@@ -27,7 +27,6 @@ namespace FireflyIII\Transformers;
use Carbon\Carbon;
use Carbon\CarbonInterface;
use FireflyIII\Models\Bill;
use FireflyIII\Models\ObjectGroup;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
@@ -44,7 +43,7 @@ class BillTransformer extends AbstractTransformer
{
private readonly BillDateCalculator $calculator;
private readonly bool $convertToNative;
private readonly TransactionCurrency $default;
private readonly TransactionCurrency $native;
private readonly BillRepositoryInterface $repository;
/**
@@ -54,7 +53,7 @@ class BillTransformer extends AbstractTransformer
{
$this->repository = app(BillRepositoryInterface::class);
$this->calculator = app(BillDateCalculator::class);
$this->default = Amount::getNativeCurrency();
$this->native = Amount::getNativeCurrency();
$this->convertToNative = Amount::convertToNative();
}
@@ -66,28 +65,14 @@ class BillTransformer extends AbstractTransformer
*/
public function transform(Bill $bill): array
{
$default = $this->parameters->get('defaultCurrency') ?? $this->default;
$paidData = $this->paidData($bill);
$lastPaidDate = $this->getLastPaidDate($paidData);
$start = $this->parameters->get('start') ?? today()->subYears(10);
$end = $this->parameters->get('end') ?? today()->addYears(10);
$payDates = $this->calculator->getPayDates($start, $end, $bill->date, $bill->repeat_freq, $bill->skip, $lastPaidDate);
$currency = $bill->transactionCurrency;
$notes = $this->repository->getNoteText($bill);
$notes = '' === $notes ? null : $notes;
$objectGroupId = null;
$objectGroupOrder = null;
$objectGroupTitle = null;
$this->repository->setUser($bill->user);
/** @var null|ObjectGroup $objectGroup */
$objectGroup = $bill->objectGroups->first();
if (null !== $objectGroup) {
$objectGroupId = $objectGroup->id;
$objectGroupOrder = $objectGroup->order;
$objectGroupTitle = $objectGroup->title;
}
$paidDataFormatted = [];
$payDatesFormatted = [];
@@ -156,15 +141,16 @@ class BillTransformer extends AbstractTransformer
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'native_currency_id' => null === $default ? null : (string)$default->id,
'native_currency_code' => $default?->code,
'native_currency_symbol' => $default?->symbol,
'native_currency_decimal_places' => $default?->decimal_places,
'native_currency_id' => (string)$this->native->id,
'native_currency_code' => $this->native->code,
'native_currency_symbol' => $this->native->symbol,
'native_currency_decimal_places' => $this->native->decimal_places,
'name' => $bill->name,
'amount_min' => app('steam')->bcround($bill->amount_min, $currency->decimal_places),
'amount_max' => app('steam')->bcround($bill->amount_max, $currency->decimal_places),
'native_amount_min' => $this->convertToNative ? app('steam')->bcround($bill->native_amount_min, $default->decimal_places) : null,
'native_amount_max' => $this->convertToNative ? app('steam')->bcround($bill->native_amount_max, $default->decimal_places) : null,
'amount_min' => $bill->amounts['amount_min'],
'amount_max' => $bill->amounts['amount_max'],
'amount_avg' => $bill->amounts['average'],
'date' => $bill->date->toAtomString(),
'end_date' => $bill->end_date?->toAtomString(),
'extension_date' => $bill->extension_date?->toAtomString(),
@@ -172,16 +158,16 @@ class BillTransformer extends AbstractTransformer
'skip' => $bill->skip,
'active' => $bill->active,
'order' => $bill->order,
'notes' => $notes,
'object_group_id' => null !== $objectGroupId ? (string)$objectGroupId : null,
'object_group_order' => $objectGroupOrder,
'object_group_title' => $objectGroupTitle,
'notes' => $bill->meta['notes'],
'object_group_id' => $bill->meta['object_group_id'],
'object_group_order' => $bill->meta['object_group_order'],
'object_group_title' => $bill->meta['object_group_title'],
// these fields need work:
'next_expected_match' => $nem,
'next_expected_match_diff' => $nemDiff,
'pay_dates' => $payDatesFormatted,
'paid_dates' => $paidDataFormatted,
// 'next_expected_match' => $nem,
// 'next_expected_match_diff' => $nemDiff,
// 'pay_dates' => $payDatesFormatted,
// 'paid_dates' => $paidDataFormatted,
'links' => [
[
'rel' => 'self',
@@ -235,14 +221,14 @@ class BillTransformer extends AbstractTransformer
$result = [];
foreach ($set as $entry) {
$array = [
'transaction_group_id' => (string)$entry->transaction_group_id,
'transaction_journal_id' => (string)$entry->id,
'date' => $entry->date->format('Y-m-d'),
'date_object' => $entry->date,
'currency_id' => $entry->transaction_currency_id,
'currency_code' => $entry->transaction_currency_code,
'currency_decimal_places' => $entry->transaction_currency_decimal_places,
'amount' => Steam::bcround($entry->amount, $entry->transaction_currency_decimal_places),
'transaction_group_id' => (string)$entry->transaction_group_id,
'transaction_journal_id' => (string)$entry->id,
'date' => $entry->date->format('Y-m-d'),
'date_object' => $entry->date,
'currency_id' => $entry->transaction_currency_id,
'currency_code' => $entry->transaction_currency_code,
'currency_decimal_places' => $entry->transaction_currency_decimal_places,
'amount' => Steam::bcround($entry->amount, $entry->transaction_currency_decimal_places),
];
if (null !== $entry->foreign_amount && null !== $entry->foreign_currency_code) {
$array['foreign_currency_id'] = $entry->foreign_currency_id;

36
composer.lock generated
View File

@@ -11065,16 +11065,16 @@
},
{
"name": "phpstan/phpstan",
"version": "2.1.20",
"version": "2.1.21",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "a9ccfef95210f92ba6feea6e8d1eef42b5605499"
"reference": "1ccf445757458c06a04eb3f803603cb118fe5fa6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/a9ccfef95210f92ba6feea6e8d1eef42b5605499",
"reference": "a9ccfef95210f92ba6feea6e8d1eef42b5605499",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/1ccf445757458c06a04eb3f803603cb118fe5fa6",
"reference": "1ccf445757458c06a04eb3f803603cb118fe5fa6",
"shasum": ""
},
"require": {
@@ -11119,7 +11119,7 @@
"type": "github"
}
],
"time": "2025-07-26T20:45:26+00:00"
"time": "2025-07-28T19:35:08+00:00"
},
{
"name": "phpstan/phpstan-deprecation-rules",
@@ -11218,16 +11218,16 @@
},
{
"name": "phpunit/php-code-coverage",
"version": "12.3.1",
"version": "12.3.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "ddec29dfc128eba9c204389960f2063f3b7fa170"
"reference": "086553c5b2e0e1e20293d782d788ab768202b621"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ddec29dfc128eba9c204389960f2063f3b7fa170",
"reference": "ddec29dfc128eba9c204389960f2063f3b7fa170",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/086553c5b2e0e1e20293d782d788ab768202b621",
"reference": "086553c5b2e0e1e20293d782d788ab768202b621",
"shasum": ""
},
"require": {
@@ -11283,7 +11283,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.3.1"
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.3.2"
},
"funding": [
{
@@ -11303,7 +11303,7 @@
"type": "tidelift"
}
],
"time": "2025-06-18T08:58:13+00:00"
"time": "2025-07-29T06:19:24+00:00"
},
{
"name": "phpunit/php-file-iterator",
@@ -11552,16 +11552,16 @@
},
{
"name": "phpunit/phpunit",
"version": "12.2.7",
"version": "12.2.8",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "8b1348b254e5959acaf1539c6bd790515fb49414"
"reference": "49dc7c0669b97c7a36f63411635f48eedc140eb4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8b1348b254e5959acaf1539c6bd790515fb49414",
"reference": "8b1348b254e5959acaf1539c6bd790515fb49414",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/49dc7c0669b97c7a36f63411635f48eedc140eb4",
"reference": "49dc7c0669b97c7a36f63411635f48eedc140eb4",
"shasum": ""
},
"require": {
@@ -11575,7 +11575,7 @@
"phar-io/manifest": "^2.0.4",
"phar-io/version": "^3.2.1",
"php": ">=8.3",
"phpunit/php-code-coverage": "^12.3.1",
"phpunit/php-code-coverage": "^12.3.2",
"phpunit/php-file-iterator": "^6.0.0",
"phpunit/php-invoker": "^6.0.0",
"phpunit/php-text-template": "^5.0.0",
@@ -11629,7 +11629,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/12.2.7"
"source": "https://github.com/sebastianbergmann/phpunit/tree/12.2.8"
},
"funding": [
{
@@ -11653,7 +11653,7 @@
"type": "tidelift"
}
],
"time": "2025-07-11T04:11:13+00:00"
"time": "2025-07-30T05:58:18+00:00"
},
{
"name": "rector/rector",

View File

@@ -78,8 +78,8 @@ return [
'running_balance_column' => env('USE_RUNNING_BALANCE', false),
// see cer.php for exchange rates feature flag.
],
'version' => 'develop/2025-07-28',
'build_time' => 1753673705,
'version' => 'develop/2025-07-31',
'build_time' => 1753936691,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 26,

228
package-lock.json generated
View File

@@ -1699,6 +1699,13 @@
"node": ">=10.0.0"
}
},
"node_modules/@epic-web/invariant": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz",
"integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==",
"dev": true,
"license": "MIT"
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.8",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz",
@@ -2142,9 +2149,9 @@
}
},
"node_modules/@fortawesome/fontawesome-free": {
"version": "6.7.2",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.7.2.tgz",
"integrity": "sha512-JUOtgFW6k9u4Y+xeIaEiLr3+cjoUPiAuLXoyKOJSia6Duzb7pq+A76P9ZdPDoAoxHdHzq6gE9/jKBGXlZT8FbA==",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-7.0.0.tgz",
"integrity": "sha512-X48nISrSOa89zu2VMljC4XaRf8NmgTwQBVHfS2Nu5G00ZwM31oOVrAtGxZF3b6wDYf9lJsf/Eq4cCSFKIkOWPQ==",
"license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)",
"engines": {
"node": ">=6"
@@ -2585,9 +2592,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.0.tgz",
"integrity": "sha512-9f3nSTFI2ivfxc7/tHBHcJ8pRnp8ROrELvsVprlQPVvcZ+j5zztYd+PTJGpyIOAdTvNwNrpCXswKSeoQcyGjMQ==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz",
"integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==",
"cpu": [
"arm"
],
@@ -2599,9 +2606,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.0.tgz",
"integrity": "sha512-tFZSEhqJ8Yrpe50TzOdeoYi72gi/jsnT7y8Qrozf3cNu28WX+s6I3XzEPUAqoaT9SAS8Xz9AzGTFlxxCH/w20w==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz",
"integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==",
"cpu": [
"arm64"
],
@@ -2613,9 +2620,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.0.tgz",
"integrity": "sha512-+DikIIs+p6yU2hF51UaWG8BnHbq90X0QIOt5zqSKSZxY+G3qqdLih214e9InJal21af2PuuxkDectetGfbVPJw==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz",
"integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==",
"cpu": [
"arm64"
],
@@ -2627,9 +2634,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.0.tgz",
"integrity": "sha512-5a+NofhdEB/WimSlFMskbFQn1vqz1FWryYpA99trmZGO6qEmiS0IsX6w4B3d91U878Q2ZQdiaFF1gxX4P147og==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz",
"integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==",
"cpu": [
"x64"
],
@@ -2641,9 +2648,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.0.tgz",
"integrity": "sha512-igr/RlKPS3OCy4jD3XBmAmo3UAcNZkJSubRsw1JeM8bAbwf15k/3eMZXD91bnjheijJiOJcga3kfCLKjV8IXNg==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz",
"integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==",
"cpu": [
"arm64"
],
@@ -2655,9 +2662,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.0.tgz",
"integrity": "sha512-MdigWzPSHlQzB1xZ+MdFDWTAH+kcn7UxjEBoOKuaso7z1DRlnAnrknB1mTtNOQ+GdPI8xgExAGwHeqQjntR0Cg==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz",
"integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==",
"cpu": [
"x64"
],
@@ -2669,9 +2676,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.0.tgz",
"integrity": "sha512-dmZseE0ZwA/4yy1+BwFrDqFTjjNg24GO9xSrb1weVbt6AFkhp5pz1gVS7IMtfIvoWy8yp6q/zN0bKnefRUImvQ==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz",
"integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==",
"cpu": [
"arm"
],
@@ -2683,9 +2690,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.0.tgz",
"integrity": "sha512-fzhfn6p9Cfm3W8UrWKIa4l7Wfjs/KGdgaswMBBE3KY3Ta43jg2XsPrAtfezHpsRk0Nx+TFuS3hZk/To2N5kFPQ==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz",
"integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==",
"cpu": [
"arm"
],
@@ -2697,9 +2704,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.0.tgz",
"integrity": "sha512-vVDD+iPDPmJQ5nAQ5Tifq3ywdv60FartglFI8VOCK+hcU9aoG0qlQTsDJP97O5yiTaTqlneZWoARMcVC5nyUoQ==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz",
"integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==",
"cpu": [
"arm64"
],
@@ -2711,9 +2718,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.0.tgz",
"integrity": "sha512-0d0jx08fzDHCzXqrtCMEEyxKU0SvJrWmUjUDE2/KDQ2UDJql0tfiwYvEx1oHELClKO8CNdE+AGJj+RqXscZpdQ==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz",
"integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==",
"cpu": [
"arm64"
],
@@ -2725,9 +2732,9 @@
]
},
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.0.tgz",
"integrity": "sha512-XBYu9oW9eKJadWn8M7hkTZsD4yG+RrsTrVEgyKwb4L72cpJjRbRboTG9Lg9fec8MxJp/cfTHAocg4mnismQR8A==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz",
"integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==",
"cpu": [
"loong64"
],
@@ -2739,9 +2746,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.0.tgz",
"integrity": "sha512-wJaRvcT17PoOK6Ggcfo3nouFlybHvARBS4jzT0PC/lg17fIJHcDS2fZz3sD+iA4nRlho2zE6OGbU0HvwATdokQ==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz",
"integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==",
"cpu": [
"ppc64"
],
@@ -2753,9 +2760,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.0.tgz",
"integrity": "sha512-GZ5bkMFteAGkcmh8x0Ok4LSa+L62Ez0tMsHPX6JtR0wl4Xc3bQcrFHDiR5DGLEDFtGrXih4Nd/UDaFqs968/wA==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz",
"integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==",
"cpu": [
"riscv64"
],
@@ -2767,9 +2774,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.0.tgz",
"integrity": "sha512-7CjPw6FflFsVOUfWOrVrREiV3IYXG4RzZ1ZQUaT3BtSK8YXN6x286o+sruPZJESIaPebYuFowmg54ZdrkVBYog==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz",
"integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==",
"cpu": [
"riscv64"
],
@@ -2781,9 +2788,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.0.tgz",
"integrity": "sha512-nmvnl0ZiuysltcB/cKjUh40Rx4FbSyueERDsl2FLvLYr6pCgSsvGr3SocUT84svSpmloS7f1DRWqtRha74Gi1w==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz",
"integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==",
"cpu": [
"s390x"
],
@@ -2795,9 +2802,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.0.tgz",
"integrity": "sha512-Cv+moII5C8RM6gZbR3cb21o6rquVDZrN2o81maROg1LFzBz2dZUwIQSxFA8GtGZ/F2KtsqQ2z3eFPBb6akvQNg==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz",
"integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==",
"cpu": [
"x64"
],
@@ -2809,9 +2816,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.0.tgz",
"integrity": "sha512-PHcMG8DZTM9RCIjp8QIfN0VYtX0TtBPnWOTRurFhoCDoi9zptUZL2k7pCs+5rgut7JAiUsYy+huyhVKPcmxoog==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz",
"integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==",
"cpu": [
"x64"
],
@@ -2823,9 +2830,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.0.tgz",
"integrity": "sha512-1SI/Rd47e8aQJeFWMDg16ET+fjvCcD/CzeaRmIEPmb05hx+3cCcwIF4ebUag4yTt/D1peE+Mgp0+Po3M358cAA==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz",
"integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==",
"cpu": [
"arm64"
],
@@ -2837,9 +2844,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.0.tgz",
"integrity": "sha512-JwOCYxmumFDfDhx4kNyz6kTVK3gWzBIvVdMNzQMRDubcoGRDniOOmo6DDNP42qwZx3Bp9/6vWJ+kNzNqXoHmeA==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz",
"integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==",
"cpu": [
"ia32"
],
@@ -2851,9 +2858,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.0.tgz",
"integrity": "sha512-IPMIfrfkG1GaEXi+JSsQEx8x9b4b+hRZXO7KYc2pKio3zO2/VDXDs6B9Ts/nnO+25Fk1tdAVtUn60HKKPPzDig==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz",
"integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==",
"cpu": [
"x64"
],
@@ -4479,9 +4486,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001727",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz",
"integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==",
"version": "1.0.30001731",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001731.tgz",
"integrity": "sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==",
"dev": true,
"funding": [
{
@@ -5018,22 +5025,21 @@
}
},
"node_modules/cross-env": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.0.0.tgz",
"integrity": "sha512-aU8qlEK/nHYtVuN4p7UQgAwVljzMg8hB4YK5ThRqD2l/ziSnryncPNn7bMLt5cFYsKVKBh8HqLqyCoTupEUu7Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"cross-spawn": "^7.0.1"
"@epic-web/invariant": "^1.0.0",
"cross-spawn": "^7.0.6"
},
"bin": {
"cross-env": "src/bin/cross-env.js",
"cross-env-shell": "src/bin/cross-env-shell.js"
"cross-env": "dist/bin/cross-env.js",
"cross-env-shell": "dist/bin/cross-env-shell.js"
},
"engines": {
"node": ">=10.14",
"npm": ">=6",
"yarn": ">=1"
"node": ">=20"
}
},
"node_modules/cross-fetch": {
@@ -5694,9 +5700,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.191",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.191.tgz",
"integrity": "sha512-xcwe9ELcuxYLUFqZZxL19Z6HVKcvNkIwhbHUz7L3us6u12yR+7uY89dSl570f/IqNthx8dAw3tojG7i4Ni4tDA==",
"version": "1.5.192",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.192.tgz",
"integrity": "sha512-rP8Ez0w7UNw/9j5eSXCe10o1g/8B1P5SM90PCCMVkIRQn2R0LEHWz4Eh9RnxkniuDe1W0cTSOB3MLlkTGDcuCg==",
"dev": true,
"license": "ISC"
},
@@ -7832,14 +7838,14 @@
}
},
"node_modules/launch-editor": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.10.0.tgz",
"integrity": "sha512-D7dBRJo/qcGX9xlvt/6wUYzQxjh5G1RvZPgPv8vi4KRU99DVQL/oW7tnVOCCTm2HGeo3C5HvGE5Yrh6UBoZ0vA==",
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.11.0.tgz",
"integrity": "sha512-R/PIF14L6e2eHkhvQPu7jDRCr0msfCYCxbYiLgkkAGi0dVPWuM+RrsPu0a5dpuNe0KWGL3jpAkOlv53xGfPheQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"picocolors": "^1.0.0",
"shell-quote": "^1.8.1"
"picocolors": "^1.1.1",
"shell-quote": "^1.8.3"
}
},
"node_modules/leaflet": {
@@ -10117,9 +10123,9 @@
}
},
"node_modules/rollup": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.0.tgz",
"integrity": "sha512-ONmkT3Ud3IfW15nl7l4qAZko5/2iZ5ALVBDh02ZSZ5IGVLJSYkRcRa3iB58VyEIyoofs9m2xdVrm+lTi97+3pw==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz",
"integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -10133,26 +10139,26 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.46.0",
"@rollup/rollup-android-arm64": "4.46.0",
"@rollup/rollup-darwin-arm64": "4.46.0",
"@rollup/rollup-darwin-x64": "4.46.0",
"@rollup/rollup-freebsd-arm64": "4.46.0",
"@rollup/rollup-freebsd-x64": "4.46.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.46.0",
"@rollup/rollup-linux-arm-musleabihf": "4.46.0",
"@rollup/rollup-linux-arm64-gnu": "4.46.0",
"@rollup/rollup-linux-arm64-musl": "4.46.0",
"@rollup/rollup-linux-loongarch64-gnu": "4.46.0",
"@rollup/rollup-linux-ppc64-gnu": "4.46.0",
"@rollup/rollup-linux-riscv64-gnu": "4.46.0",
"@rollup/rollup-linux-riscv64-musl": "4.46.0",
"@rollup/rollup-linux-s390x-gnu": "4.46.0",
"@rollup/rollup-linux-x64-gnu": "4.46.0",
"@rollup/rollup-linux-x64-musl": "4.46.0",
"@rollup/rollup-win32-arm64-msvc": "4.46.0",
"@rollup/rollup-win32-ia32-msvc": "4.46.0",
"@rollup/rollup-win32-x64-msvc": "4.46.0",
"@rollup/rollup-android-arm-eabi": "4.46.2",
"@rollup/rollup-android-arm64": "4.46.2",
"@rollup/rollup-darwin-arm64": "4.46.2",
"@rollup/rollup-darwin-x64": "4.46.2",
"@rollup/rollup-freebsd-arm64": "4.46.2",
"@rollup/rollup-freebsd-x64": "4.46.2",
"@rollup/rollup-linux-arm-gnueabihf": "4.46.2",
"@rollup/rollup-linux-arm-musleabihf": "4.46.2",
"@rollup/rollup-linux-arm64-gnu": "4.46.2",
"@rollup/rollup-linux-arm64-musl": "4.46.2",
"@rollup/rollup-linux-loongarch64-gnu": "4.46.2",
"@rollup/rollup-linux-ppc64-gnu": "4.46.2",
"@rollup/rollup-linux-riscv64-gnu": "4.46.2",
"@rollup/rollup-linux-riscv64-musl": "4.46.2",
"@rollup/rollup-linux-s390x-gnu": "4.46.2",
"@rollup/rollup-linux-x64-gnu": "4.46.2",
"@rollup/rollup-linux-x64-musl": "4.46.2",
"@rollup/rollup-win32-arm64-msvc": "4.46.2",
"@rollup/rollup-win32-ia32-msvc": "4.46.2",
"@rollup/rollup-win32-x64-msvc": "4.46.2",
"fsevents": "~2.3.2"
}
},
@@ -11838,9 +11844,9 @@
"license": "BSD-2-Clause"
},
"node_modules/webpack": {
"version": "5.100.2",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.100.2.tgz",
"integrity": "sha512-QaNKAvGCDRh3wW1dsDjeMdDXwZm2vqq3zn6Pvq4rHOEOGSaUMgOOjG2Y9ZbIGzpfkJk9ZYTHpDqgDfeBDcnLaw==",
"version": "5.101.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.0.tgz",
"integrity": "sha512-B4t+nJqytPeuZlHuIKTbalhljIFXeNRqrUGAQgTGlfOl2lXXKXw+yZu6bicycP+PUlM44CxBjCFD6aciKFT3LQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -12504,7 +12510,7 @@
"@vue/compiler-sfc": "^3.5.11",
"axios": "^1.11",
"bootstrap-sass": "^3",
"cross-env": "^7.0",
"cross-env": "^10.0",
"font-awesome": "^4.7.0",
"jquery": "^3",
"laravel-mix": "^6.0",
@@ -12519,7 +12525,7 @@
"resources/assets/v2": {
"hasInstallScript": true,
"dependencies": {
"@fortawesome/fontawesome-free": "^6.4.0",
"@fortawesome/fontawesome-free": "^7",
"@popperjs/core": "^2.11.8",
"admin-lte": "^4.0.0-rc4",
"alpinejs": "^3.13.7",

View File

@@ -18,7 +18,7 @@
"@vue/compiler-sfc": "^3.5.11",
"axios": "^1.11",
"bootstrap-sass": "^3",
"cross-env": "^7.0",
"cross-env": "^10.0",
"font-awesome": "^4.7.0",
"jquery": "^3",
"laravel-mix": "^6.0",

View File

@@ -16,7 +16,7 @@
"vite-plugin-manifest-sri": "^0.2.0"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^6.4.0",
"@fortawesome/fontawesome-free": "^7",
"@popperjs/core": "^2.11.8",
"admin-lte": "^4.0.0-rc4",
"alpinejs": "^3.13.7",

View File

@@ -39,7 +39,7 @@ export default () => ({
['@convert-to-native.window'](event){
this.convertToNative = event.detail;
this.accountList = [];
console.log('I heard that!');
console.log('I heard that! (dashboard/boxes)');
this.boxData = null;
this.loadBoxes();
}

View File

@@ -48,9 +48,19 @@ export default () => ({
}
this.getFreshData();
},
eventListeners: {
['@convert-to-native.window'](event){
console.log('I heard that! (dashboard/budgets)');
this.convertToNative = event.detail;
chartData = null;
this.loadChart();
}
},
drawChart(options) {
if (null !== chart) {
chart.data.datasets = options.data.datasets;
chart.data = options.data;
chart.update();
return;
}
@@ -59,7 +69,7 @@ export default () => ({
getFreshData() {
const start = new Date(window.store.get('start'));
const end = new Date(window.store.get('end'));
const cacheKey = getCacheKey('ds_bdg_chart', {start: start, end: end});
const cacheKey = getCacheKey('ds_bdg_chart', {convertToNative: this.convertToNative, start: start, end: end});
//const cacheValid = window.store.get('cacheValid');
const cacheValid = false;
let cachedData = window.store.get(cacheKey);

View File

@@ -33,6 +33,17 @@ let afterPromises = false;
export default () => ({
loading: false,
convertToNative: false,
eventListeners: {
['@convert-to-native.window'](event){
console.log('I heard that! (dashboard/categories)');
this.convertToNative = event.detail;
chartData = null;
this.loadChart();
}
},
generateOptions(data) {
currencies = [];
let options = getDefaultChartSettings('column');
@@ -43,11 +54,6 @@ export default () => ({
if (data.hasOwnProperty(i)) {
let current = data[i];
let code = current.currency_code;
// only use native code when doing auto conversion.
if (this.convertToNative) {
code = current.native_currency_code;
}
if (!series.hasOwnProperty(code)) {
series[code] = {
name: code,
@@ -65,9 +71,6 @@ export default () => ({
let yAxis = 'y';
let current = data[i];
let code = current.currency_code;
if (this.convertToNative) {
code = current.native_currency_code;
}
// loop series, add 0 if not present or add actual amount.
for (const ii in series) {
@@ -77,10 +80,6 @@ export default () => ({
// this series' currency matches this column's currency.
amount = parseFloat(current.amount);
yAxis = 'y' + current.currency_code;
if (this.convertToNative) {
amount = parseFloat(current.native_amount);
yAxis = 'y' + current.native_currency_code;
}
}
if (series[ii].data.hasOwnProperty(current.label)) {
// there is a value for this particular currency. The amount from this column will be added.
@@ -147,7 +146,7 @@ export default () => ({
getFreshData() {
const start = new Date(window.store.get('start'));
const end = new Date(window.store.get('end'));
const cacheKey = getCacheKey('ds_ct_chart', {start: start, end: end});
const cacheKey = getCacheKey('ds_ct_chart', {convertToNative: this.convertToNative, start: start, end: end});
const cacheValid = window.store.get('cacheValid');
let cachedData = window.store.get(cacheKey);

View File

@@ -71,9 +71,11 @@ let index = function () {
return {
convertToNative: false,
saveNativeSettings(event) {
setVariable('convert_to_native', event.currentTarget.checked);
this.$dispatch('convert-to-native', event.currentTarget.checked);
console.log('saveNativeSettings + dispatch.');
let target = event.currentTarget || event.target;
setVariable('convert_to_native',target.checked).then(() => {
console.log('Set convert to native to: ', target.checked);
this.$dispatch('convert-to-native', target.checked);
});
},
init() {
Promise.all([getVariable('convert_to_native', false)]).then((values) => {

View File

@@ -36,7 +36,7 @@ export default () => ({
const start = new Date(window.store.get('start'));
const end = new Date(window.store.get('end'));
// needs user data.
const cacheKey = getCacheKey(PIGGY_CACHE_KEY, {start: start, end: end});
const cacheKey = getCacheKey(PIGGY_CACHE_KEY, {convertToNative: this.convertToNative, start: start, end: end});
const cacheValid = window.store.get('cacheValid');
let cachedData = window.store.get(cacheKey);

View File

@@ -29,17 +29,16 @@ import i18next from "i18next";
Chart.register({SankeyController, Flow});
const SANKEY_CACHE_KEY = 'ds_sankey_data';
let currencies = [];
let afterPromises = false;
let chart = null;
let transactions = [];
let convertToNative = false;
let translations = {
let currencies = [];
let afterPromises = false;
let chart = null;
let transactions = [];
let convertToNative = false;
let translations = {
category: null,
unknown_category: null,
in: null,
out: null,
// TODO
unknown_source: null,
unknown_dest: null,
unknown_account: null,
@@ -80,75 +79,97 @@ const getColor = function (key) {
// little helper
function getObjectName(type, name, direction, code) {
if(convertToNative) {
return getObjectNameWithoutCurrency(type, name, direction);
}
return getObjectNameWithCurrency(type, name, direction, code);
// category 4x
if ('category' === type && null !== name && 'in' === direction) {
return translations.category + ' "' + name + '" (' + translations.in + (convertToNative ? ', ' + code + ')' : ')');
}
if ('category' === type && null === name && 'in' === direction) {
return translations.unknown_category + ' (' + translations.in + (convertToNative ? ', ' + code + ')' : ')');
}
if ('category' === type && null !== name && 'out' === direction) {
return translations.category + ' "' + name + '" (' + translations.out + (convertToNative ? ', ' + code + ')' : ')');
}
if ('category' === type && null === name && 'out' === direction) {
return translations.unknown_category + ' (' + translations.out + (convertToNative ? ', ' + code + ')' : ')');
}
// account 4x
if ('account' === type && null === name && 'in' === direction) {
return translations.unknown_source + (convertToNative ? ' (' + code + ')' : '');
}
if ('account' === type && null !== name && 'in' === direction) {
return translations.revenue_account + '"' + name + '"' + (convertToNative ? ' (' + code + ')' : '');
}
if ('account' === type && null === name && 'out' === direction) {
return translations.unknown_dest + (convertToNative ? ' (' + code + ')' : '');
}
if ('account' === type && null !== name && 'out' === direction) {
return translations.expense_account + ' "' + name + '"' + (convertToNative ? ' (' + code + ')' : '');
}
// budget 2x
if ('budget' === type && null !== name) {
return translations.budget + ' "' + name + '"' + (convertToNative ? ' (' + code + ')' : '');
}
if ('budget' === type && null === name) {
return translations.unknown_budget + (convertToNative ? ' (' + code + ')' : '');
}
console.error('Cannot handle: type:"' + type + '", dir: "' + direction + '"');
}
function getLabelName(type, name, code) {
// category
if ('category' === type && null !== name) {
return translations.category + ' "' + name + '"' + (convertToNative ? ' (' + code + ')' : '');
function getObjectNameWithoutCurrency(type, name, direction) {
if('category' === type) {
let catName = null === name ? translations.unknown_category : translations.category + ' "' + name + '"';
let directionText = 'in' === direction ? translations.in : translations.out;
return catName + ' (' + directionText + ')';
}
if ('category' === type && null === name) {
return translations.unknown_category + (convertToNative ? ' (' + code + ')' : '');
if('account' === type) {
let accountName = null === name ? translations.unknown_account : name;
let directionText = 'in' === direction ? translations.in : translations.out;
let fullAccountName = 'in' === direction ? translations.revenue_account + ' "' + accountName + '"' : translations.expense_account + ' "' + accountName + '"';
return fullAccountName + ' (' + directionText + ')';
}
// account
if ('account' === type && null === name) {
return translations.unknown_account + (convertToNative ? ' (' + code + ')' : '');
if('budget' === type) {
return null === name ? translations.unknown_budget : translations.budget + ' "' + name + '"';
}
if ('account' === type && null !== name) {
return name + (convertToNative ? ' (' + code + ')' : '');
console.error('[a] Cannot handle: type:"' + type + '", dir: "' + direction + '"');
}
function getObjectNameWithCurrency(type, name, direction, code) {
if('category' === type) {
let catName = null === name ? translations.unknown_category : translations.category + ' "' + name + '"';
let directionText = 'in' === direction ? translations.in : translations.out;
return catName + ' (' + directionText + ', ' + code + ')';
}
// budget 2x
if ('budget' === type && null !== name) {
return translations.budget + ' "' + name + '"' + (convertToNative ? ' (' + code + ')' : '');
if('account' === type) {
let accountName = null === name ? translations.unknown_account : name;
let directionText = 'in' === direction ? translations.in : translations.out;
let fullAccountName = 'in' === direction ? translations.revenue_account + ' "' + accountName + '"' : translations.expense_account + ' "' + accountName + '"';
return fullAccountName + ' (' + directionText + ', ' + code + ')';
}
if ('budget' === type && null === name) {
return translations.unknown_budget + (convertToNative ? ' (' + code + ')' : '');
if('budget' === type) {
return (null === name ? translations.unknown_budget : translations.budget + ' "' + name + '"') + ' (' + code + ')';
}
console.error('Cannot handle: type:"' + type + '"');
console.error('[b] Cannot handle: type:"' + type + '", dir: "' + direction + '"');
}
function getLabel(type, name, code) {
if(convertToNative) {
return getLabelWithoutCurrency(type, name);
}
return getLabelWithCurrency(type, name, code);
}
function getLabelWithoutCurrency(type, name) {
if('category' === type) {
return null === name ? translations.unknown_category : translations.category + ' "' + name + '"';
}
if('account' === type) {
return null === name ? translations.unknown_account : name;
}
if('budget' === type) {
return null === name ? translations.unknown_budget : translations.budget + ' "' + name + '"';
}
console.error('[a] Cannot handle: type:"' + type + '"');
}
function getLabelWithCurrency(type, name, code) {
if('category' === type) {
return (null === name ? translations.unknown_category : translations.category + ' "' + name + '"') + ' ('+ code + ')';
}
if('account' === type) {
return (null === name ? translations.unknown_account : name) + ' (' + code + ')';
}
if('budget' === type) {
return (null === name ? translations.unknown_budget : translations.budget + ' "' + name + '"') + ' (' + code + ')';;
}
console.error('[b] Cannot handle: type:"' + type + '"');
}
export default () => ({
loading: false,
convertToNative: false,
processedData: null,
eventListeners: {
['@convert-to-native.window'](event){
console.log('I heard that! (dashboard/sankey)');
this.convertToNative = event.detail;
convertToNative = event.detail;
this.processedData = null;
this.loadChart();
}
},
generateOptions() {
let options = getDefaultChartSettings('sankey');
@@ -156,125 +177,22 @@ export default () => ({
currencies = [];
// variables collected for the sankey chart:
let amounts = {};
let labels = {};
for (let i in transactions) {
if (transactions.hasOwnProperty(i)) {
let group = transactions[i];
for (let ii in group.attributes.transactions) {
if (group.attributes.transactions.hasOwnProperty(ii)) {
// properties of the transaction, used in the generation of the chart:
let transaction = group.attributes.transactions[ii];
let currencyCode = this.convertToNative ? transaction.native_currency_code : transaction.currency_code;
if(this.convertToNative && (!transaction.hasOwnProperty('native_amount') || null === transaction.native_amount)) {
// skip this transaction, it has no native amount.
console.error('No native amount for transaction #' + group.id + ' ('+this.convertToNative+')');
continue;
}
let amount = this.convertToNative ? parseFloat(transaction.native_amount) : parseFloat(transaction.amount);
let flowKey;
/*
Two entries in the sankey diagram for deposits:
1. From the revenue account (source) to a category (in).
2. From the category (in) to the big inbox.
*/
if ('deposit' === transaction.type) {
// nr 1
let category = getObjectName('category', transaction.category_name, 'in', currencyCode);
let revenueAccount = getObjectName('account', transaction.source_name, 'in', currencyCode);
labels[category] = getLabelName('category', transaction.category_name, currencyCode);
labels[revenueAccount] = getLabelName('account', transaction.source_name, currencyCode);
flowKey = revenueAccount + '-' + category + '-' + currencyCode;
if (!amounts.hasOwnProperty(flowKey)) {
amounts[flowKey] = {
from: revenueAccount,
to: category,
amount: 0
};
}
amounts[flowKey].amount += amount;
// nr 2
flowKey = category + '-' + translations.all_money + '-' + currencyCode;
if (!amounts.hasOwnProperty(flowKey)) {
amounts[flowKey] = {
from: category,
to: translations.all_money + (this.convertToNative ? ' (' + currencyCode + ')' : ''),
amount: 0
};
}
amounts[flowKey].amount += amount;
}
/*
Three entries in the sankey diagram for withdrawals:
1. From the big box to a budget.
2. From a budget to a category.
3. From a category to an expense account.
*/
if ('withdrawal' === transaction.type) {
// 1.
let budget = getObjectName('budget', transaction.budget_name, 'out', currencyCode);
labels[budget] = getLabelName('budget', transaction.budget_name, currencyCode);
flowKey = translations.all_money + '-' + budget + '-' + currencyCode;
if (!amounts.hasOwnProperty(flowKey)) {
amounts[flowKey] = {
from: translations.all_money + (this.convertToNative ? ' (' + currencyCode + ')' : ''),
to: budget,
amount: 0
};
}
amounts[flowKey].amount += amount;
// 2.
let category = getObjectName('category', transaction.category_name, 'out', currencyCode);
labels[category] = getLabelName('category', transaction.category_name, currencyCode);
flowKey = budget + '-' + category + '-' + currencyCode;
if (!amounts.hasOwnProperty(flowKey)) {
amounts[flowKey] = {
from: budget,
to: category,
amount: 0
};
}
amounts[flowKey].amount += amount;
// 3.
let expenseAccount = getObjectName('account', transaction.destination_name, 'out', currencyCode);
labels[expenseAccount] = getLabelName('account', transaction.destination_name, currencyCode);
flowKey = category + '-' + expenseAccount + '-' + currencyCode;
if (!amounts.hasOwnProperty(flowKey)) {
amounts[flowKey] = {
from: category,
to: expenseAccount,
amount: 0
};
}
amounts[flowKey].amount += amount;
}
}
}
}
}
this.parseTransactionGroups(transactions);
let dataSet =
// sankey chart has one data set.
{
label: 'Firefly III dashboard sankey chart',
data: [],
colorFrom: (c) => getColor(c.dataset.data[c.dataIndex] ? c.dataset.data[c.dataIndex].from : ''),
colorTo: (c) => getColor(c.dataset.data[c.dataIndex] ? c.dataset.data[c.dataIndex].to : ''),
colorMode: 'gradient', // or 'from' or 'to'
labels: labels,
size: 'min', // or 'min' if flow overlap is preferred
};
for (let i in amounts) {
if (amounts.hasOwnProperty(i)) {
let amount = amounts[i];
// sankey chart has one data set.
{
label: 'Firefly III dashboard sankey chart',
data: [],
colorFrom: (c) => getColor(c.dataset.data[c.dataIndex] ? c.dataset.data[c.dataIndex].from : ''),
colorTo: (c) => getColor(c.dataset.data[c.dataIndex] ? c.dataset.data[c.dataIndex].to : ''),
colorMode: 'gradient', // or 'from' or 'to'
labels: this.processedData.labels,
size: 'min', // or 'min' if flow overlap is preferred
};
for (let i in this.processedData.amounts) {
if (this.processedData.amounts.hasOwnProperty(i)) {
let amount = this.processedData.amounts[i];
dataSet.data.push({from: amount.from, to: amount.to, flow: amount.amount});
}
}
@@ -282,6 +200,133 @@ export default () => ({
return options;
},
parseTransactionGroups(groups) {
this.processedData = {
amounts: {},
labels: {}
};
for (let i in groups) {
if (groups.hasOwnProperty(i)) {
let group = groups[i];
this.parseTransactionGroup(group);
}
}
},
parseTransactionGroup(group) {
for (let ii in group.attributes.transactions) {
if (group.attributes.transactions.hasOwnProperty(ii)) {
// properties of the transaction, used in the generation of the chart:
let transaction = group.attributes.transactions[ii];
this.parseTransaction(transaction);
}
}
},
parseTransaction(transaction) {
let currencyCode = transaction.currency_code;
let amount = parseFloat(transaction.amount);
let flowKey;
if (this.convertToNative) {
currencyCode = transaction.native_currency_code;
amount = parseFloat(transaction.native_amount);
}
if ('deposit' === transaction.type) {
this.parseDeposit(transaction, currencyCode, amount);
return;
}
if ('withdrawal' === transaction.type) {
this.parseWithdrawal(transaction, currencyCode, amount);
}
},
parseWithdrawal(transaction, currencyCode, amount) {
/*
Three entries in the sankey diagram for withdrawals:
1. From the big box to a budget.
2. From a budget to a category.
3. From a category to an expense account.
*/
// first one:
let budget = getObjectName('budget', transaction.budget_name, 'out', currencyCode);
this.processedData.labels[budget] = getLabel('budget', transaction.budget_name, currencyCode);
let flowKey = translations.all_money + '-' + budget + '-' + currencyCode;
if (!this.processedData.amounts.hasOwnProperty(flowKey)) {
this.processedData.amounts[flowKey] = {
from: translations.all_money + (this.convertToNative ? ' (' + currencyCode + ')' : ''),
to: budget,
amount: 0
};
}
this.processedData.amounts[flowKey].amount += amount;
// second one:
let category = getObjectName('category', transaction.category_name, 'out', currencyCode);
this.processedData.labels[category] = getLabel('category', transaction.category_name, currencyCode);
flowKey = budget + '-' + category + '-' + currencyCode;
if (!this.processedData.amounts.hasOwnProperty(flowKey)) {
this.processedData.amounts[flowKey] = {
from: budget,
to: category,
amount: 0
};
}
this.processedData.amounts[flowKey].amount += amount;
// third one:
let expenseAccount = getObjectName('account', transaction.destination_name, 'out', currencyCode);
this.processedData.labels[expenseAccount] = getLabel('account', transaction.destination_name, currencyCode);
flowKey = category + '-' + expenseAccount + '-' + currencyCode;
if (!this.processedData.amounts.hasOwnProperty(flowKey)) {
this.processedData.amounts[flowKey] = {
from: category,
to: expenseAccount,
amount: 0
};
}
this.processedData.amounts[flowKey].amount += amount;
},
parseDeposit(transaction, currencyCode, amount) {
/*
Two entries in the sankey diagram for deposits:
1. From the revenue account (source) to a category (in).
2. From the category (in) to the big inbox.
*/
// this is the first one:
let category = getObjectName('category', transaction.category_name, 'in', currencyCode);
let revenueAccount = getObjectName('account', transaction.source_name, 'in', currencyCode);
let flowKey = revenueAccount + '-' + category + '-' + currencyCode;
this.processedData.labels[category] = getLabel('category', transaction.category_name, currencyCode);
this.processedData.labels[revenueAccount] = getLabel('account', transaction.source_name, currencyCode);
// create if necessary:
if (!this.processedData.amounts.hasOwnProperty(flowKey)) {
this.processedData.amounts[flowKey] = {
from: revenueAccount,
to: category,
amount: 0
};
}
this.processedData.amounts[flowKey].amount += amount;
// this is the second one:
flowKey = category + '-' + translations.all_money + '-' + currencyCode;
if (!this.processedData.amounts.hasOwnProperty(flowKey)) {
this.processedData.amounts[flowKey] = {
from: category,
to: translations.all_money + (this.convertToNative ? ' (' + currencyCode + ')' : ''),
amount: 0
};
}
this.processedData.amounts[flowKey].amount += amount;
},
drawChart(options) {
if (null !== chart) {
chart.data.datasets = options.data.datasets;
@@ -292,12 +337,12 @@ export default () => ({
},
getFreshData() {
const start = new Date(window.store.get('start'));
const end = new Date(window.store.get('end'));
const start = new Date(window.store.get('start'));
const end = new Date(window.store.get('end'));
const cacheKey = getCacheKey(SANKEY_CACHE_KEY, {start: start, end: end});
const cacheValid = window.store.get('cacheValid');
let cachedData = window.store.get(cacheKey);
let cachedData = window.store.get(cacheKey);
if (cacheValid && typeof cachedData !== 'undefined') {
transactions = cachedData;
@@ -316,8 +361,8 @@ export default () => ({
this.downloadTransactions(params);
},
downloadTransactions(params) {
const start = new Date(window.store.get('start'));
const end = new Date(window.store.get('end'));
const start = new Date(window.store.get('start'));
const end = new Date(window.store.get('end'));
const cacheKey = getCacheKey(SANKEY_CACHE_KEY, {convertToNative: this.convertToNative, start: start, end: end});
//console.log('Downloading page ' + params.page + '...');
@@ -356,25 +401,25 @@ export default () => ({
transactions = [];
Promise.all([getVariable('convert_to_native', false)]).then((values) => {
this.convertToNative = values[0];
convertToNative = values[0];
convertToNative = values[0];
// some translations:
translations.all_money = i18next.t('firefly.all_money');
translations.category = i18next.t('firefly.category');
translations.in = i18next.t('firefly.money_flowing_in');
translations.out = i18next.t('firefly.money_flowing_out');
translations.unknown_category = i18next.t('firefly.unknown_category_plain');
translations.unknown_source = i18next.t('firefly.unknown_source_plain');
translations.unknown_dest = i18next.t('firefly.unknown_dest_plain');
translations.unknown_account = i18next.t('firefly.unknown_any_plain');
translations.unknown_budget = i18next.t('firefly.unknown_budget_plain');
translations.expense_account = i18next.t('firefly.expense_account');
translations.revenue_account = i18next.t('firefly.revenue_account');
translations.budget = i18next.t('firefly.budget');
// some translations:
translations.all_money = i18next.t('firefly.all_money');
translations.category = i18next.t('firefly.category');
translations.in = i18next.t('firefly.money_flowing_in');
translations.out = i18next.t('firefly.money_flowing_out');
translations.unknown_category = i18next.t('firefly.unknown_category_plain');
translations.unknown_source = i18next.t('firefly.unknown_source_plain');
translations.unknown_dest = i18next.t('firefly.unknown_dest_plain');
translations.unknown_account = i18next.t('firefly.unknown_any_plain');
translations.unknown_budget = i18next.t('firefly.unknown_budget_plain');
translations.expense_account = i18next.t('firefly.expense_account');
translations.revenue_account = i18next.t('firefly.revenue_account');
translations.budget = i18next.t('firefly.budget');
// console.log('sankey after promises');
afterPromises = true;
this.loadChart();
// console.log('sankey after promises');
afterPromises = true;
this.loadChart();
});
window.store.observe('end', () => {

View File

@@ -163,14 +163,10 @@ function downloadSubscriptions(params) {
currency_code: bill.currency_code,
paid: 0,
unpaid: 0,
native_currency_code: bill.native_currency_code,
native_paid: 0,
//native_unpaid: 0,
};
}
subscriptionData[objectGroupId].payment_info[bill.currency_code].unpaid += totalAmount;
//subscriptionData[objectGroupId].payment_info[bill.currency_code].native_unpaid += totalNativeAmount;
}
if (current.attributes.paid_dates.length > 0) {
@@ -178,8 +174,6 @@ function downloadSubscriptions(params) {
if (current.attributes.paid_dates.hasOwnProperty(ii)) {
// bill is paid!
// since bill is paid, 3 possible currencies:
// native, currency, foreign currency.
// foreign currency amount (converted to native or not) will be ignored.
let currentJournal = current.attributes.paid_dates[ii];
// new array for the currency
if (!subscriptionData[objectGroupId].payment_info.hasOwnProperty(currentJournal.currency_code)) {
@@ -187,15 +181,10 @@ function downloadSubscriptions(params) {
currency_code: bill.currency_code,
paid: 0,
unpaid: 0,
// native_currency_code: bill.native_currency_code,
// native_paid: 0,
//native_unpaid: 0,
};
}
const amount = parseFloat(currentJournal.amount) * -1;
// const nativeAmount = parseFloat(currentJournal.native_amount) * -1;
subscriptionData[objectGroupId].payment_info[currentJournal.currency_code].paid += amount;
// subscriptionData[objectGroupId].payment_info[currentJournal.currency_code].native_paid += nativeAmount;
}
}
}
@@ -218,6 +207,17 @@ export default () => ({
loading: false,
convertToNative: false,
subscriptions: [],
formatMoney(amount, currencyCode) {
return formatMoney(amount, currencyCode);
},
eventListeners: {
['@convert-to-native.window'](event){
console.log('I heard that! (dashboard/subscriptions)');
this.convertToNative = event.detail;
this.startSubscriptions();
}
},
startSubscriptions() {
this.loading = true;
let start = new Date(window.store.get('start'));
@@ -256,6 +256,7 @@ export default () => ({
//console.log(group);
}
}
console.log('Subscriptions: ', this.subscriptions);
// then assign to this.subscriptions.
this.loading = false;

View File

@@ -26,7 +26,28 @@ $danger: #CD5029 !default;
$primary: #1E6581 !default;
$success: #64B624 !default;
// admin LTE
@use "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 '@fortawesome/fontawesome-free/scss/variables' with (
$font-path: "@fortawesome/fontawesome-free/webfonts"
);
@use '@fortawesome/fontawesome-free/scss/fontawesome';
@use '@fortawesome/fontawesome-free/scss/fa' as fa;
@use '@fortawesome/fontawesome-free/scss/solid.scss' as fa-solid;
@use '@fortawesome/fontawesome-free/scss/brands.scss' as fa-brands;
@use '@fortawesome/fontawesome-free/scss/regular.scss' as fa-regular;
// some local CSS
.skip-links {display: none;}
/*
@@ -59,16 +80,7 @@ h3.hover-expand:hover {
appearance: auto;
}
// Bootstrap
// @import "bootstrap/scss/bootstrap";
// admin LTE
@import "adminlte-filtered";
// @import "~bootstrap-sass/assets/stylesheets/bootstrap";
// hover buttons
// edit buttons
.hidden-edit-button {
cursor: pointer;
}
@@ -76,12 +88,12 @@ td:not(:hover) .hidden-edit-button {
visibility: hidden;
}
// Bootstrap
// @import "bootstrap/scss/bootstrap";
// @import "~bootstrap-sass/assets/stylesheets/bootstrap";
// hover buttons
// Font awesome
//@import "~font-awesome/css/font-awesome";
$fa-font-path: "@fortawesome/fontawesome-free/webfonts";
@import "@fortawesome/fontawesome-free/scss/fontawesome.scss";
@import "@fortawesome/fontawesome-free/scss/solid.scss";
@import "@fortawesome/fontawesome-free/scss/brands.scss";
@import "@fortawesome/fontawesome-free/scss/regular.scss";

View File

@@ -35,8 +35,9 @@ export function setVariable(name, value = null) {
// post to user preferences (because why not):
let putter = new Put();
putter.put(name, value).then((response) => {
return putter.put(name, value).then((response) => {
console.log('set "'+name+'" to value: ', value);
return Promise.resolve(response);
}).catch((error) => {
console.error(error);
// preference does not exist (yet).
@@ -44,6 +45,8 @@ export function setVariable(name, value = null) {
let poster = (new Post);
poster.post(name, value).then((response) => {
console.log('POST "'+name+'" to value: ', value);
return Promise.resolve(response);
});
});
}

View File

@@ -234,6 +234,7 @@ return [
'advanced_options' => 'Advanced options',
'advanced_options_explain' => 'Some pages in Firefly III have advanced options hidden behind this button. This page doesn\'t have anything fancy here, but do check out the others!',
'here_be_dragons' => 'Hic sunt dracones',
'bad_date_transaction' => 'Firefly III has detected you have transactions from before the year 1970. Please correct these transactions at your earliest convenience.',
// Webhooks
'webhooks' => 'Webhooks',
@@ -1863,6 +1864,9 @@ return [
'remove_budgeted_amount' => 'Remove budgeted amount in :currency',
// bills:
'left_to_pay_lc' => 'left to pay',
'less_than_expected' => 'less than expected',
'more_than_expected' => 'more than expected',
'skip_help_text' => 'Use the skip field to create bi-monthly (skip = 1) or other custom intervals.',
'subscription' => 'Subscription',
'not_expected_period' => 'Not expected this period',

View File

@@ -1,4 +1,4 @@
<div class="row mb-2" x-data="budgets">
<div class="row mb-2" x-data="budgets" x-bind="eventListeners">
<div class="col">
<div class="card">
<div class="card-header">

View File

@@ -1,4 +1,4 @@
<div class="row mb-2" x-data="categories">
<div class="row mb-2" x-data="categories" x-bind="eventListeners">
<div class="col">
<div class="card">
<div class="card-header">

View File

@@ -3,10 +3,8 @@
<template x-for="group in piggies">
<div class="card mb-2">
<div class="card-header">
<h3 class="card-title"><a href="{{ route('piggy-banks.index') }}"
title="{{ __('firefly.go_to_piggies') }}">{{ __('firefly.piggy_banks') }}
(<span
x-text="group.title"></span>)</a></h3>
<h3 class="card-title"><a href="{{ route('piggy-banks.index') }}" title="{{ __('firefly.go_to_piggies') }}">{{ __('firefly.piggy_banks') }}
(<span x-text="group.title"></span>)</a></h3>
</div>
<ul class="list-group list-group-flush">
<template x-for="piggy in group.piggies">

View File

@@ -6,7 +6,7 @@
>{{ __('firefly.income_and_expense') }}</a>
</h3>
</div>
<div class="card-body" x-data="sankey">
<div class="card-body" x-data="sankey" x-bind="eventListeners">
<canvas id="sankey-chart"></canvas>
</div>
</div>

View File

@@ -1,4 +1,4 @@
<div class="col" x-data="subscriptions">
<div class="col" x-data="subscriptions" x-bind="eventListeners">
<template x-for="group in subscriptions">
<div class="card mb-2">
<div class="card-header">
@@ -9,18 +9,21 @@
</h3>
</div>
<div class="card-body">
<template x-for="pi in group.payment_info">
<div class="row mb-2">
<!--
<template x-for="pie in group.payment_info">
<div :class='group.col_size'>
<canvas :id='"pie_" + group.id + "_" + pie.currency_code'
:width="group.width"
x-init="drawPieChart(group.id, group.title, pie)"></canvas>
<div class="col">
<div class="progress" role="progressbar" aria-label="Example with label"
:aria-valuenow="(pi.paid*-1/pi.unpaid)*100" aria-valuemin="0" aria-valuemax="100">
<div class="progress-bar progress-bar-striped" :style="'width: ' + (pi.paid*-1/pi.unpaid)*100 + '%'">
<span x-text="formatMoney(pi.paid*-1,pi.currency_code)"></span>
</div>
</div>
</template>
-->
Here was chart.
<p>
<small>~ <span x-text="formatMoney(pi.unpaid, pi.currency_code)"></span> {{ __('firefly.left_to_pay_lc') }}</small>
</p>
</div>
</div>
</template>
<div class="row mb-2">
<table class="table table-striped table-hover">
<thead>
@@ -33,9 +36,7 @@
<template x-for="bill in group.bills">
<tr>
<td>
<a :href="'{{ route('subscriptions.show',['']) }}/' + bill.id" :title="bill.name">
<span x-text="bill.name"></span>
</a>
<a :href="'{{ route('subscriptions.show',['']) }}/' + bill.id" :title="bill.name"><span x-text="bill.name"></span></a>
<template x-if="bill.paid">
<small class="text-muted"><br>{{ __('firefly.paid') }}</small>
</template>
@@ -60,8 +61,17 @@
<ul class="list-unstyled">
<template x-for="transaction in bill.transactions">
<li>
<span x-text="transaction.amount"></span>
(<span x-text="transaction.percentage"></span>%)
<span :title="transaction.amount" x-text="transaction.amount"></span>
<template x-if="transaction.percentage < 0">
<span>
(<span :title="transaction.percentage + '% {{ __("firefly.less_than_expected") }}'" x-text="transaction.percentage"></span>%)
</span>
</template>
<template x-if="transaction.percentage > 0">
<span>
(<span :title="transaction.percentage + '% {{ __("firefly.more_than_expected") }}'" x-text="transaction.percentage"></span>%)
</span>
</template>
</li>
</template>
</ul>