mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-09-18 10:39:28 +00:00
Expand account index overview.
This commit is contained in:
@@ -157,6 +157,9 @@ class Controller extends BaseController
|
|||||||
{
|
{
|
||||||
$manager = new Manager();
|
$manager = new Manager();
|
||||||
$baseUrl = request()->getSchemeAndHttpHost().'/api/v2';
|
$baseUrl = request()->getSchemeAndHttpHost().'/api/v2';
|
||||||
|
|
||||||
|
// TODO add stuff to path?
|
||||||
|
|
||||||
$manager->setSerializer(new JsonApiSerializer($baseUrl));
|
$manager->setSerializer(new JsonApiSerializer($baseUrl));
|
||||||
|
|
||||||
$objects = $paginator->getCollection();
|
$objects = $paginator->getCollection();
|
||||||
|
@@ -33,7 +33,7 @@ use Illuminate\Pagination\LengthAwarePaginator;
|
|||||||
|
|
||||||
class IndexController extends Controller
|
class IndexController extends Controller
|
||||||
{
|
{
|
||||||
public const string RESOURCE_KEY = 'accounts';
|
public const string RESOURCE_KEY = 'accounts';
|
||||||
|
|
||||||
private AccountRepositoryInterface $repository;
|
private AccountRepositoryInterface $repository;
|
||||||
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY, UserRoleEnum::MANAGE_TRANSACTIONS];
|
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY, UserRoleEnum::MANAGE_TRANSACTIONS];
|
||||||
@@ -48,7 +48,7 @@ class IndexController extends Controller
|
|||||||
function ($request, $next) {
|
function ($request, $next) {
|
||||||
$this->repository = app(AccountRepositoryInterface::class);
|
$this->repository = app(AccountRepositoryInterface::class);
|
||||||
// new way of user group validation
|
// new way of user group validation
|
||||||
$userGroup = $this->validateUserGroup($request);
|
$userGroup = $this->validateUserGroup($request);
|
||||||
$this->repository->setUserGroup($userGroup);
|
$this->repository->setUserGroup($userGroup);
|
||||||
|
|
||||||
return $next($request);
|
return $next($request);
|
||||||
@@ -57,26 +57,28 @@ class IndexController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* TODO the sort instructions need proper repeatable documentation.
|
||||||
* TODO see autocomplete/account controller for list.
|
* TODO see autocomplete/account controller for list.
|
||||||
*/
|
*/
|
||||||
public function index(IndexRequest $request): JsonResponse
|
public function index(IndexRequest $request): JsonResponse
|
||||||
{
|
{
|
||||||
$this->repository->resetAccountOrder();
|
$this->repository->resetAccountOrder();
|
||||||
$types = $request->getAccountTypes();
|
$types = $request->getAccountTypes();
|
||||||
$instructions = $request->getSortInstructions('accounts');
|
$sorting = $request->getSortInstructions('accounts');
|
||||||
$accounts = $this->repository->getAccountsByType($types, $instructions);
|
$filters = $request->getFilterInstructions('accounts');
|
||||||
$pageSize = $this->parameters->get('limit');
|
$accounts = $this->repository->getAccountsByType($types, $sorting, $filters);
|
||||||
$count = $accounts->count();
|
$pageSize = $this->parameters->get('limit');
|
||||||
$accounts = $accounts->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
|
$count = $accounts->count();
|
||||||
$paginator = new LengthAwarePaginator($accounts, $count, $pageSize, $this->parameters->get('page'));
|
$accounts = $accounts->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
|
||||||
$transformer = new AccountTransformer();
|
$paginator = new LengthAwarePaginator($accounts, $count, $pageSize, $this->parameters->get('page'));
|
||||||
|
$transformer = new AccountTransformer();
|
||||||
|
|
||||||
$this->parameters->set('sort', $instructions);
|
$this->parameters->set('sort', $sorting);
|
||||||
|
$this->parameters->set('filters', $filters);
|
||||||
$transformer->setParameters($this->parameters); // give params to transformer
|
$transformer->setParameters($this->parameters); // give params to transformer
|
||||||
|
|
||||||
return response()
|
return response()
|
||||||
->json($this->jsonApiList('accounts', $paginator, $transformer))
|
->json($this->jsonApiList('accounts', $paginator, $transformer))
|
||||||
->header('Content-Type', self::CONTENT_TYPE)
|
->header('Content-Type', self::CONTENT_TYPE);
|
||||||
;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -27,6 +27,7 @@ use Carbon\Carbon;
|
|||||||
use FireflyIII\Support\Http\Api\AccountFilter;
|
use FireflyIII\Support\Http\Api\AccountFilter;
|
||||||
use FireflyIII\Support\Request\ChecksLogin;
|
use FireflyIII\Support\Request\ChecksLogin;
|
||||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||||
|
use FireflyIII\Support\Request\GetFilterInstructions;
|
||||||
use FireflyIII\Support\Request\GetSortInstructions;
|
use FireflyIII\Support\Request\GetSortInstructions;
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
@@ -41,6 +42,8 @@ class IndexRequest extends FormRequest
|
|||||||
use ChecksLogin;
|
use ChecksLogin;
|
||||||
use ConvertsDataTypes;
|
use ConvertsDataTypes;
|
||||||
use GetSortInstructions;
|
use GetSortInstructions;
|
||||||
|
use GetFilterInstructions;
|
||||||
|
|
||||||
|
|
||||||
public function getAccountTypes(): array
|
public function getAccountTypes(): array
|
||||||
{
|
{
|
||||||
|
@@ -240,7 +240,7 @@ class AccountRepository implements AccountRepositoryInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAccountsByType(array $types, ?array $sort = []): Collection
|
public function getAccountsByType(array $types, ?array $sort = [], ?array $filters = []): Collection
|
||||||
{
|
{
|
||||||
$sortable = ['name', 'active']; // TODO yes this is a duplicate array.
|
$sortable = ['name', 'active']; // TODO yes this is a duplicate array.
|
||||||
$res = array_intersect([AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT], $types);
|
$res = array_intersect([AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT], $types);
|
||||||
@@ -249,6 +249,19 @@ class AccountRepository implements AccountRepositoryInterface
|
|||||||
$query->accountTypeIn($types);
|
$query->accountTypeIn($types);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// process filters
|
||||||
|
// TODO this should be repeatable, it feels like a hack when you do it here.
|
||||||
|
foreach($filters as $column => $value) {
|
||||||
|
// filter on NULL values
|
||||||
|
if(null === $value) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ('active' === $column) {
|
||||||
|
$query->where('accounts.active', $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// add sort parameters. At this point they're filtered to allowed fields to sort by:
|
// add sort parameters. At this point they're filtered to allowed fields to sort by:
|
||||||
$hasActiveColumn = array_key_exists('active', $sort);
|
$hasActiveColumn = array_key_exists('active', $sort);
|
||||||
if (count($sort) > 0) {
|
if (count($sort) > 0) {
|
||||||
|
@@ -55,7 +55,7 @@ interface AccountRepositoryInterface
|
|||||||
|
|
||||||
public function getAccountsById(array $accountIds): Collection;
|
public function getAccountsById(array $accountIds): Collection;
|
||||||
|
|
||||||
public function getAccountsByType(array $types, ?array $sort = []): Collection;
|
public function getAccountsByType(array $types, ?array $sort = [], ?array $filters = []): Collection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used in the infinite accounts list.
|
* Used in the infinite accounts list.
|
||||||
|
69
app/Support/Request/GetFilterInstructions.php
Normal file
69
app/Support/Request/GetFilterInstructions.php
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* GetFilterInstructions.php
|
||||||
|
* Copyright (c) 2024 james@firefly-iii.org.
|
||||||
|
*
|
||||||
|
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Support\Request;
|
||||||
|
|
||||||
|
trait GetFilterInstructions
|
||||||
|
{
|
||||||
|
private const string INVALID_FILTER = '%INVALID_JAMES_%';
|
||||||
|
|
||||||
|
final public function getFilterInstructions(string $key): array
|
||||||
|
{
|
||||||
|
$config = config(sprintf('firefly.filters.allowed.%s', $key));
|
||||||
|
$allowed = array_keys($config);
|
||||||
|
$set = $this->get('filters', []);
|
||||||
|
$result = [];
|
||||||
|
if (0 === count($set)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
foreach ($set as $info) {
|
||||||
|
$column = $info['column'] ?? 'NOPE';
|
||||||
|
$filterValue = (string) ($info['filter'] ?? self::INVALID_FILTER);
|
||||||
|
if (false === in_array($column, $allowed, true)) {
|
||||||
|
// skip invalid column
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$filterType = $config[$column] ?? false;
|
||||||
|
switch ($filterType) {
|
||||||
|
default:
|
||||||
|
die(sprintf('Do not support filter type "%s"', $filterType));
|
||||||
|
case 'boolean':
|
||||||
|
$filterValue = $this->booleanInstruction($filterValue);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$result[$column] = $filterValue;
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function booleanInstruction(string $filterValue): ?bool {
|
||||||
|
if ('true' === $filterValue) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ('false' === $filterValue) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -436,7 +436,7 @@ return [
|
|||||||
'transfers' => 'fa-exchange',
|
'transfers' => 'fa-exchange',
|
||||||
],
|
],
|
||||||
|
|
||||||
'bindables' => [
|
'bindables' => [
|
||||||
// models
|
// models
|
||||||
'account' => Account::class,
|
'account' => Account::class,
|
||||||
'attachment' => Attachment::class,
|
'attachment' => Attachment::class,
|
||||||
@@ -494,7 +494,7 @@ return [
|
|||||||
'userGroupBill' => UserGroupBill::class,
|
'userGroupBill' => UserGroupBill::class,
|
||||||
'userGroup' => UserGroup::class,
|
'userGroup' => UserGroup::class,
|
||||||
],
|
],
|
||||||
'rule-actions' => [
|
'rule-actions' => [
|
||||||
'set_category' => SetCategory::class,
|
'set_category' => SetCategory::class,
|
||||||
'clear_category' => ClearCategory::class,
|
'clear_category' => ClearCategory::class,
|
||||||
'set_budget' => SetBudget::class,
|
'set_budget' => SetBudget::class,
|
||||||
@@ -528,7 +528,7 @@ return [
|
|||||||
// 'set_foreign_amount' => SetForeignAmount::class,
|
// 'set_foreign_amount' => SetForeignAmount::class,
|
||||||
// 'set_foreign_currency' => SetForeignCurrency::class,
|
// 'set_foreign_currency' => SetForeignCurrency::class,
|
||||||
],
|
],
|
||||||
'context-rule-actions' => [
|
'context-rule-actions' => [
|
||||||
'set_category',
|
'set_category',
|
||||||
'set_budget',
|
'set_budget',
|
||||||
'add_tag',
|
'add_tag',
|
||||||
@@ -547,13 +547,13 @@ return [
|
|||||||
'convert_transfer',
|
'convert_transfer',
|
||||||
],
|
],
|
||||||
|
|
||||||
'test-triggers' => [
|
'test-triggers' => [
|
||||||
'limit' => 10,
|
'limit' => 10,
|
||||||
'range' => 200,
|
'range' => 200,
|
||||||
],
|
],
|
||||||
|
|
||||||
// expected source types for each transaction type, in order of preference.
|
// expected source types for each transaction type, in order of preference.
|
||||||
'expected_source_types' => [
|
'expected_source_types' => [
|
||||||
'source' => [
|
'source' => [
|
||||||
TransactionTypeModel::WITHDRAWAL => [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE],
|
TransactionTypeModel::WITHDRAWAL => [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE],
|
||||||
TransactionTypeEnum::DEPOSIT->value => [AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE, AccountType::REVENUE, AccountType::CASH],
|
TransactionTypeEnum::DEPOSIT->value => [AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE, AccountType::REVENUE, AccountType::CASH],
|
||||||
@@ -598,7 +598,7 @@ return [
|
|||||||
TransactionTypeModel::LIABILITY_CREDIT => [AccountType::LIABILITY_CREDIT, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE],
|
TransactionTypeModel::LIABILITY_CREDIT => [AccountType::LIABILITY_CREDIT, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'allowed_opposing_types' => [
|
'allowed_opposing_types' => [
|
||||||
'source' => [
|
'source' => [
|
||||||
AccountType::ASSET => [
|
AccountType::ASSET => [
|
||||||
AccountType::ASSET,
|
AccountType::ASSET,
|
||||||
@@ -688,7 +688,7 @@ return [
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
// depending on the account type, return the allowed transaction types:
|
// depending on the account type, return the allowed transaction types:
|
||||||
'allowed_transaction_types' => [
|
'allowed_transaction_types' => [
|
||||||
'source' => [
|
'source' => [
|
||||||
AccountType::ASSET => [
|
AccountType::ASSET => [
|
||||||
TransactionTypeModel::WITHDRAWAL,
|
TransactionTypeModel::WITHDRAWAL,
|
||||||
@@ -757,7 +757,7 @@ return [
|
|||||||
],
|
],
|
||||||
|
|
||||||
// having the source + dest will tell you the transaction type.
|
// having the source + dest will tell you the transaction type.
|
||||||
'account_to_transaction' => [
|
'account_to_transaction' => [
|
||||||
AccountType::ASSET => [
|
AccountType::ASSET => [
|
||||||
AccountType::ASSET => TransactionTypeModel::TRANSFER,
|
AccountType::ASSET => TransactionTypeModel::TRANSFER,
|
||||||
AccountType::CASH => TransactionTypeModel::WITHDRAWAL,
|
AccountType::CASH => TransactionTypeModel::WITHDRAWAL,
|
||||||
@@ -822,7 +822,7 @@ return [
|
|||||||
],
|
],
|
||||||
|
|
||||||
// allowed source -> destination accounts.
|
// allowed source -> destination accounts.
|
||||||
'source_dests' => [
|
'source_dests' => [
|
||||||
TransactionTypeModel::WITHDRAWAL => [
|
TransactionTypeModel::WITHDRAWAL => [
|
||||||
AccountType::ASSET => [AccountType::EXPENSE, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE, AccountType::CASH],
|
AccountType::ASSET => [AccountType::EXPENSE, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE, AccountType::CASH],
|
||||||
AccountType::LOAN => [AccountType::EXPENSE, AccountType::CASH],
|
AccountType::LOAN => [AccountType::EXPENSE, AccountType::CASH],
|
||||||
@@ -861,7 +861,7 @@ return [
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
// if you add fields to this array, don't forget to update the export routine (ExportDataGenerator).
|
// if you add fields to this array, don't forget to update the export routine (ExportDataGenerator).
|
||||||
'journal_meta_fields' => [
|
'journal_meta_fields' => [
|
||||||
// sepa
|
// sepa
|
||||||
'sepa_cc',
|
'sepa_cc',
|
||||||
'sepa_ct_op',
|
'sepa_ct_op',
|
||||||
@@ -895,33 +895,47 @@ return [
|
|||||||
'recurrence_count',
|
'recurrence_count',
|
||||||
'recurrence_date',
|
'recurrence_date',
|
||||||
],
|
],
|
||||||
'webhooks' => [
|
'webhooks' => [
|
||||||
'max_attempts' => env('WEBHOOK_MAX_ATTEMPTS', 3),
|
'max_attempts' => env('WEBHOOK_MAX_ATTEMPTS', 3),
|
||||||
],
|
],
|
||||||
'can_have_virtual_amounts' => [AccountType::ASSET],
|
'can_have_virtual_amounts' => [AccountType::ASSET],
|
||||||
'can_have_opening_balance' => [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE],
|
'can_have_opening_balance' => [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE],
|
||||||
'dynamic_creation_allowed' => [
|
'dynamic_creation_allowed' => [
|
||||||
AccountType::EXPENSE,
|
AccountType::EXPENSE,
|
||||||
AccountType::REVENUE,
|
AccountType::REVENUE,
|
||||||
AccountType::INITIAL_BALANCE,
|
AccountType::INITIAL_BALANCE,
|
||||||
AccountType::RECONCILIATION,
|
AccountType::RECONCILIATION,
|
||||||
AccountType::LIABILITY_CREDIT,
|
AccountType::LIABILITY_CREDIT,
|
||||||
],
|
],
|
||||||
'valid_asset_fields' => ['account_role', 'account_number', 'currency_id', 'BIC', 'include_net_worth'],
|
'valid_asset_fields' => ['account_role', 'account_number', 'currency_id', 'BIC', 'include_net_worth'],
|
||||||
'valid_cc_fields' => ['account_role', 'cc_monthly_payment_date', 'cc_type', 'account_number', 'currency_id', 'BIC', 'include_net_worth'],
|
'valid_cc_fields' => ['account_role', 'cc_monthly_payment_date', 'cc_type', 'account_number', 'currency_id', 'BIC', 'include_net_worth'],
|
||||||
'valid_account_fields' => ['account_number', 'currency_id', 'BIC', 'interest', 'interest_period', 'include_net_worth', 'liability_direction'],
|
'valid_account_fields' => ['account_number', 'currency_id', 'BIC', 'interest', 'interest_period', 'include_net_worth', 'liability_direction'],
|
||||||
|
|
||||||
// dynamic date ranges are as follows:
|
// dynamic date ranges are as follows:
|
||||||
'dynamic_date_ranges' => ['last7', 'last30', 'last90', 'last365', 'MTD', 'QTD', 'YTD'],
|
'dynamic_date_ranges' => ['last7', 'last30', 'last90', 'last365', 'MTD', 'QTD', 'YTD'],
|
||||||
|
|
||||||
// only used in v1
|
// only used in v1
|
||||||
'allowed_sort_parameters' => ['order', 'name', 'iban'],
|
'allowed_sort_parameters' => ['order', 'name', 'iban'],
|
||||||
|
|
||||||
// preselected account lists possibilities:
|
// preselected account lists possibilities:
|
||||||
'preselected_accounts' => ['all', 'assets', 'liabilities'],
|
'preselected_accounts' => ['all', 'assets', 'liabilities'],
|
||||||
|
|
||||||
// allowed sort columns for API's
|
// allowed filters (search) for APIs
|
||||||
'sorting' => [
|
'filters' => [
|
||||||
|
'allowed' => [
|
||||||
|
'accounts' => [
|
||||||
|
'name' => '*',
|
||||||
|
'active' => 'boolean',
|
||||||
|
'iban' => 'iban',
|
||||||
|
'balance' => 'numeric',
|
||||||
|
'last_activity' => 'date',
|
||||||
|
'balance_difference' => 'numeric',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
// allowed sort columns for APIs
|
||||||
|
'sorting' => [
|
||||||
'allowed' => [
|
'allowed' => [
|
||||||
'transactions' => ['description', 'amount'],
|
'transactions' => ['description', 'amount'],
|
||||||
'accounts' => ['name', 'active', 'iban', 'balance', 'last_activity', 'balance_difference'],
|
'accounts' => ['name', 'active', 'iban', 'balance', 'last_activity', 'balance_difference'],
|
||||||
|
@@ -1,58 +0,0 @@
|
|||||||
/*!
|
|
||||||
* _variables.scss
|
|
||||||
* Copyright (c) 2019 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* TODO REMOVE ME */
|
|
||||||
// Body
|
|
||||||
$body-bg: #f5f8fa;
|
|
||||||
|
|
||||||
// Borders
|
|
||||||
$laravel-border-color: darken($body-bg, 10%);
|
|
||||||
$list-group-border: $laravel-border-color;
|
|
||||||
$navbar-default-border: $laravel-border-color;
|
|
||||||
$panel-default-border: $laravel-border-color;
|
|
||||||
$panel-inner-border: $laravel-border-color;
|
|
||||||
|
|
||||||
// Brands
|
|
||||||
$brand-primary: #3097D1;
|
|
||||||
$brand-info: #8eb4cb;
|
|
||||||
$brand-success: #2ab27b;
|
|
||||||
$brand-warning: #cbb956;
|
|
||||||
$brand-danger: #bf5329;
|
|
||||||
|
|
||||||
// Typography
|
|
||||||
$icon-font-path: "~bootstrap-sass/assets/fonts/bootstrap/";
|
|
||||||
$font-family-sans-serif: "Raleway", sans-serif;
|
|
||||||
$font-size-base: 14px;
|
|
||||||
$line-height-base: 1.6;
|
|
||||||
$text-color: #636b6f;
|
|
||||||
|
|
||||||
// Navbar
|
|
||||||
$navbar-default-bg: #fff;
|
|
||||||
|
|
||||||
// Buttons
|
|
||||||
$btn-default-color: $text-color;
|
|
||||||
|
|
||||||
// Inputs
|
|
||||||
$input-border: lighten($text-color, 40%);
|
|
||||||
$input-border-focus: lighten($brand-primary, 25%);
|
|
||||||
$input-color-placeholder: lighten($text-color, 30%);
|
|
||||||
|
|
||||||
// Panels
|
|
||||||
$panel-default-heading-bg: #fff;
|
|
@@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
import {api} from "../../../../boot/axios";
|
import {api} from "../../../../boot/axios";
|
||||||
import format from "date-fns/format";
|
import format from "date-fns/format";
|
||||||
|
import {getCacheKey} from "../../../../support/get-cache-key.js";
|
||||||
|
|
||||||
export default class Get {
|
export default class Get {
|
||||||
|
|
||||||
@@ -39,7 +40,24 @@ export default class Get {
|
|||||||
* @returns {Promise<AxiosResponse<any>>}
|
* @returns {Promise<AxiosResponse<any>>}
|
||||||
*/
|
*/
|
||||||
index(params) {
|
index(params) {
|
||||||
return api.get('/api/v2/accounts', {params: params});
|
// first, check API in some consistent manner.
|
||||||
|
// then, load if necessary.
|
||||||
|
const cacheKey = getCacheKey('/api/v2/accounts', params);
|
||||||
|
const cacheValid = window.store.get('cacheValid');
|
||||||
|
let cachedData = window.store.get(cacheKey);
|
||||||
|
|
||||||
|
if (cacheValid && typeof cachedData !== 'undefined') {
|
||||||
|
console.log('Cache is valid, return cache.');
|
||||||
|
return Promise.resolve(cachedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if not, store in cache and then return res.
|
||||||
|
|
||||||
|
return api.get('/api/v2/accounts', {params: params}).then(response => {
|
||||||
|
console.log('Cache is invalid, return fresh.');
|
||||||
|
window.store.set(cacheKey, response.data);
|
||||||
|
return Promise.resolve({data: response.data.data, meta: response.data.meta});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -24,16 +24,15 @@ import i18next from "i18next";
|
|||||||
import {format} from "date-fns";
|
import {format} from "date-fns";
|
||||||
import formatMoney from "../../util/format-money.js";
|
import formatMoney from "../../util/format-money.js";
|
||||||
|
|
||||||
import '@ag-grid-community/styles/ag-grid.css';
|
|
||||||
import '@ag-grid-community/styles/ag-theme-alpine.css';
|
|
||||||
import '../../css/grid-ff3-theme.css';
|
|
||||||
import Get from "../../api/v2/model/account/get.js";
|
import Get from "../../api/v2/model/account/get.js";
|
||||||
import Put from "../../api/v2/model/account/put.js";
|
import Put from "../../api/v2/model/account/put.js";
|
||||||
import AccountRenderer from "../../support/renderers/AccountRenderer.js";
|
import AccountRenderer from "../../support/renderers/AccountRenderer.js";
|
||||||
import {showInternalsButton} from "../../support/page-settings/show-internals-button.js";
|
import {showInternalsButton} from "../../support/page-settings/show-internals-button.js";
|
||||||
import {showWizardButton} from "../../support/page-settings/show-wizard-button.js";
|
import {showWizardButton} from "../../support/page-settings/show-wizard-button.js";
|
||||||
import {getVariable} from "../../store/get-variable.js";
|
|
||||||
import {setVariable} from "../../store/set-variable.js";
|
import {setVariable} from "../../store/set-variable.js";
|
||||||
|
import {getVariables} from "../../store/get-variables.js";
|
||||||
|
import pageNavigation from "../../support/page-navigation.js";
|
||||||
|
import {getCacheKey} from "../../support/get-cache-key.js";
|
||||||
|
|
||||||
|
|
||||||
// set type from URL
|
// set type from URL
|
||||||
@@ -43,6 +42,7 @@ const type = urlParts[urlParts.length - 1];
|
|||||||
|
|
||||||
let sortingColumn = '';
|
let sortingColumn = '';
|
||||||
let sortDirection = '';
|
let sortDirection = '';
|
||||||
|
let page = 1;
|
||||||
|
|
||||||
// get sort parameters
|
// get sort parameters
|
||||||
const params = new Proxy(new URLSearchParams(window.location.search), {
|
const params = new Proxy(new URLSearchParams(window.location.search), {
|
||||||
@@ -50,11 +50,15 @@ const params = new Proxy(new URLSearchParams(window.location.search), {
|
|||||||
});
|
});
|
||||||
sortingColumn = params.column ?? '';
|
sortingColumn = params.column ?? '';
|
||||||
sortDirection = params.direction ?? '';
|
sortDirection = params.direction ?? '';
|
||||||
|
page = parseInt(params.page ?? 1);
|
||||||
|
|
||||||
|
|
||||||
showInternalsButton();
|
showInternalsButton();
|
||||||
showWizardButton();
|
showWizardButton();
|
||||||
|
|
||||||
|
// TODO currency conversion
|
||||||
|
// TODO page cleanup and recycle for transaction lists.
|
||||||
|
|
||||||
let index = function () {
|
let index = function () {
|
||||||
return {
|
return {
|
||||||
// notifications
|
// notifications
|
||||||
@@ -70,11 +74,13 @@ let index = function () {
|
|||||||
},
|
},
|
||||||
totalPages: 1,
|
totalPages: 1,
|
||||||
page: 1,
|
page: 1,
|
||||||
|
pageUrl: '',
|
||||||
filters: {
|
filters: {
|
||||||
active: 'both',
|
active: null,
|
||||||
name: null,
|
name: null,
|
||||||
},
|
},
|
||||||
pageOptions: {
|
pageOptions: {
|
||||||
|
isLoading: true,
|
||||||
groupedAccounts: true,
|
groupedAccounts: true,
|
||||||
sortingColumn: sortingColumn,
|
sortingColumn: sortingColumn,
|
||||||
sortDirection: sortDirection,
|
sortDirection: sortDirection,
|
||||||
@@ -138,29 +144,46 @@ let index = function () {
|
|||||||
},
|
},
|
||||||
editors: {},
|
editors: {},
|
||||||
accounts: [],
|
accounts: [],
|
||||||
|
goToPage(page) {
|
||||||
|
this.page = page;
|
||||||
|
this.loadAccounts();
|
||||||
|
},
|
||||||
accountRole(roleName) {
|
accountRole(roleName) {
|
||||||
return i18next.t('firefly.account_role_' + roleName);
|
return i18next.t('firefly.account_role_' + roleName);
|
||||||
},
|
},
|
||||||
|
getPreferenceKey(name) {
|
||||||
|
return 'acc_index_' + type + '_' + name;
|
||||||
|
},
|
||||||
|
pageNavigation() {
|
||||||
|
return pageNavigation(this.totalPages, this.page, this.generatePageUrl(false));
|
||||||
|
},
|
||||||
|
|
||||||
sort(column) {
|
sort(column) {
|
||||||
|
this.page =1;
|
||||||
this.pageOptions.sortingColumn = column;
|
this.pageOptions.sortingColumn = column;
|
||||||
this.pageOptions.sortDirection = this.pageOptions.sortDirection === 'asc' ? 'desc' : 'asc';
|
this.pageOptions.sortDirection = this.pageOptions.sortDirection === 'asc' ? 'desc' : 'asc';
|
||||||
const url = './accounts/' + type + '?column=' + column + '&direction=' + this.pageOptions.sortDirection;
|
|
||||||
|
|
||||||
window.history.pushState({}, "", url);
|
this.updatePageUrl();
|
||||||
|
|
||||||
// get sort column
|
// get sort column
|
||||||
// TODO variable name in better place
|
setVariable(this.getPreferenceKey('sc'), this.pageOptions.sortingColumn);
|
||||||
const columnKey = 'acc_index_' + type + '_sc';
|
setVariable(this.getPreferenceKey('sd'), this.pageOptions.sortDirection);
|
||||||
const directionKey = 'acc_index_' + type + '_sd';
|
|
||||||
|
|
||||||
setVariable(columnKey, this.pageOptions.sortingColumn);
|
|
||||||
setVariable(directionKey, this.pageOptions.sortDirection);
|
|
||||||
|
|
||||||
this.loadAccounts();
|
this.loadAccounts();
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
updatePageUrl() {
|
||||||
|
this.pageUrl = this.generatePageUrl(true);
|
||||||
|
|
||||||
|
window.history.pushState({}, "", this.pageUrl);
|
||||||
|
},
|
||||||
|
generatePageUrl(includePageNr) {
|
||||||
|
let url = './accounts/' + type + '?column=' + this.pageOptions.sortingColumn + '&direction=' + this.pageOptions.sortDirection + '&page=';
|
||||||
|
if(includePageNr) {
|
||||||
|
return url + this.page
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
},
|
||||||
|
|
||||||
formatMoney(amount, currencyCode) {
|
formatMoney(amount, currencyCode) {
|
||||||
return formatMoney(amount, currencyCode);
|
return formatMoney(amount, currencyCode);
|
||||||
@@ -177,56 +200,58 @@ let index = function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log('New settings', newSettings);
|
console.log('New settings', newSettings);
|
||||||
setVariable('acc_index_' + type + '_columns', newSettings);
|
setVariable(this.getPreferenceKey('columns'), newSettings);
|
||||||
},
|
},
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
this.pageOptions.isLoading = true;
|
||||||
this.notifications.wait.show = true;
|
this.notifications.wait.show = true;
|
||||||
|
this.page = page;
|
||||||
this.notifications.wait.text = i18next.t('firefly.wait_loading_data');
|
this.notifications.wait.text = i18next.t('firefly.wait_loading_data');
|
||||||
|
|
||||||
// get column preference
|
// start by collecting all preferences, create + put in the local store.
|
||||||
// TODO key in better variable
|
getVariables([
|
||||||
const key = 'acc_index_' + type + '_columns';
|
{name: this.getPreferenceKey('columns'), default: {"drag_and_drop": false}},
|
||||||
const defaultValue = {"drag_and_drop": false};
|
{name: this.getPreferenceKey('sc'), default: ''},
|
||||||
|
{name: this.getPreferenceKey('sd'), default: ''},
|
||||||
// get sort column
|
{name: this.getPreferenceKey('filters'), default: this.filters},
|
||||||
const columnKey = 'acc_index_' + type + '_sc';
|
]).then((res) => {
|
||||||
const columnDefault = '';
|
// process columns:
|
||||||
|
for (let k in res[0]) {
|
||||||
// get sort direction
|
if (res[0].hasOwnProperty(k) && this.tableColumns.hasOwnProperty(k)) {
|
||||||
const directionKey = 'acc_index_' + type + '_sd';
|
this.tableColumns[k].enabled = res[0][k] ?? true;
|
||||||
const directionDefault = '';
|
|
||||||
|
|
||||||
|
|
||||||
getVariable(key, defaultValue).then((response) => {
|
|
||||||
for (let k in response) {
|
|
||||||
if (response.hasOwnProperty(k) && this.tableColumns.hasOwnProperty(k)) {
|
|
||||||
this.tableColumns[k].enabled = response[k] ?? true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).
|
|
||||||
// get sorting preference, and overrule it if is not "" twice
|
|
||||||
then(() => {
|
|
||||||
return getVariable(columnKey, columnDefault).then((response) => {
|
|
||||||
console.log('Sorting column is "' + response + '"');
|
|
||||||
this.pageOptions.sortingColumn = '' === this.pageOptions.sortingColumn ? response : this.pageOptions.sortingColumn;
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.
|
|
||||||
// get sorting preference, and overrule it if is not "" twice
|
|
||||||
then(() => {
|
|
||||||
return getVariable(directionKey, directionDefault).then((response) => {
|
|
||||||
console.log('Sorting direction is "' + response + '"');
|
|
||||||
this.pageOptions.sortDirection = '' === this.pageOptions.sortDirection ? response : this.pageOptions.sortDirection;
|
|
||||||
})
|
|
||||||
}).
|
|
||||||
|
|
||||||
|
// process sorting column:
|
||||||
|
this.pageOptions.sortingColumn = '' === this.pageOptions.sortingColumn ? res[1] : this.pageOptions.sortingColumn;
|
||||||
|
|
||||||
|
// process sort direction
|
||||||
|
this.pageOptions.sortDirection = '' === this.pageOptions.sortDirection ? res[2] : this.pageOptions.sortDirection;
|
||||||
|
|
||||||
|
// filters
|
||||||
|
for(let k in res[3]) {
|
||||||
|
if (res[3].hasOwnProperty(k) && this.filters.hasOwnProperty(k)) {
|
||||||
|
this.filters[k] = res[3][k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
then(() => {
|
|
||||||
this.loadAccounts();
|
this.loadAccounts();
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
saveActiveFilter(e) {
|
||||||
|
this.page = 1;
|
||||||
|
if('both' === e.currentTarget.value) {
|
||||||
|
this.filters.active = null;
|
||||||
|
}
|
||||||
|
if('active' === e.currentTarget.value) {
|
||||||
|
this.filters.active = true;
|
||||||
|
}
|
||||||
|
if('inactive' === e.currentTarget.value) {
|
||||||
|
this.filters.active = false;
|
||||||
|
}
|
||||||
|
setVariable(this.getPreferenceKey('filters'), this.filters);
|
||||||
|
this.loadAccounts();
|
||||||
},
|
},
|
||||||
renderObjectValue(field, account) {
|
renderObjectValue(field, account) {
|
||||||
let renderer = new AccountRenderer();
|
let renderer = new AccountRenderer();
|
||||||
@@ -270,36 +295,47 @@ let index = function () {
|
|||||||
this.accounts[index].nameEditorVisible = true;
|
this.accounts[index].nameEditorVisible = true;
|
||||||
},
|
},
|
||||||
loadAccounts() {
|
loadAccounts() {
|
||||||
|
this.pageOptions.isLoading = true;
|
||||||
// sort instructions
|
// sort instructions
|
||||||
const sorting = [{column: this.pageOptions.sortingColumn, direction: this.pageOptions.sortDirection}];
|
const sorting = [{column: this.pageOptions.sortingColumn, direction: this.pageOptions.sortDirection}];
|
||||||
|
|
||||||
|
// filter instructions
|
||||||
|
let filters = [];
|
||||||
|
for(let k in this.filters) {
|
||||||
|
if(this.filters.hasOwnProperty(k) && null !== this.filters[k]) {
|
||||||
|
filters.push({column: k, filter: this.filters[k]});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// get start and end from the store:
|
// get start and end from the store:
|
||||||
const start = new Date(window.store.get('start'));
|
const start = new Date(window.store.get('start'));
|
||||||
const end = new Date(window.store.get('end'));
|
const end = new Date(window.store.get('end'));
|
||||||
|
const today = new Date();
|
||||||
|
|
||||||
let params = {
|
let params = {
|
||||||
sorting: sorting,
|
sorting: sorting,
|
||||||
|
filters: filters,
|
||||||
|
today: today,
|
||||||
type: type,
|
type: type,
|
||||||
page: this.page,
|
page: this.page,
|
||||||
start: start,
|
start: start,
|
||||||
end: end
|
end: end
|
||||||
};
|
};
|
||||||
|
|
||||||
if(!this.tableColumns.balance_difference.enabled){
|
if (!this.tableColumns.balance_difference.enabled) {
|
||||||
delete params.start;
|
delete params.start;
|
||||||
delete params.end;
|
delete params.end;
|
||||||
}
|
}
|
||||||
|
// check if cache is present:
|
||||||
|
|
||||||
this.notifications.wait.show = true;
|
|
||||||
this.notifications.wait.text = i18next.t('firefly.wait_loading_data')
|
|
||||||
this.accounts = [];
|
this.accounts = [];
|
||||||
|
|
||||||
// one page only.o
|
// one page only.o
|
||||||
(new Get()).index(params).then(response => {
|
(new Get()).index(params).then(response => {
|
||||||
for (let i = 0; i < response.data.data.length; i++) {
|
this.totalPages = response.meta.pagination.total_pages;
|
||||||
if (response.data.data.hasOwnProperty(i)) {
|
for (let i = 0; i < response.data.length; i++) {
|
||||||
let current = response.data.data[i];
|
if (response.data.hasOwnProperty(i)) {
|
||||||
|
let current = response.data[i];
|
||||||
let account = {
|
let account = {
|
||||||
id: parseInt(current.id),
|
id: parseInt(current.id),
|
||||||
active: current.attributes.active,
|
active: current.attributes.active,
|
||||||
@@ -317,11 +353,11 @@ let index = function () {
|
|||||||
balance_difference: current.attributes.balance_difference,
|
balance_difference: current.attributes.balance_difference,
|
||||||
native_balance_difference: current.attributes.native_balance_difference
|
native_balance_difference: current.attributes.native_balance_difference
|
||||||
};
|
};
|
||||||
console.log(current.attributes.balance_difference);
|
|
||||||
this.accounts.push(account);
|
this.accounts.push(account);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.notifications.wait.show = false;
|
this.notifications.wait.show = false;
|
||||||
|
this.pageOptions.isLoading = false;
|
||||||
// add click trigger thing.
|
// add click trigger thing.
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@@ -63,7 +63,6 @@ let administrations = function () {
|
|||||||
|
|
||||||
pageProperties: {},
|
pageProperties: {},
|
||||||
submitForm() {
|
submitForm() {
|
||||||
console.log('submitForm');
|
|
||||||
(new Put()).put({title: this.title}, {id: this.id}).then(response => {
|
(new Put()).put({title: this.title}, {id: this.id}).then(response => {
|
||||||
if (this.formStates.returnHereButton) {
|
if (this.formStates.returnHereButton) {
|
||||||
this.notifications.success.show = true;
|
this.notifications.success.show = true;
|
||||||
|
@@ -46,16 +46,18 @@ export default () => ({
|
|||||||
this.autoConversion = !this.autoConversion;
|
this.autoConversion = !this.autoConversion;
|
||||||
setVariable('autoConversion', this.autoConversion);
|
setVariable('autoConversion', this.autoConversion);
|
||||||
},
|
},
|
||||||
|
localCacheKey(type) {
|
||||||
|
return 'ds_accounts_' + type;
|
||||||
|
},
|
||||||
getFreshData() {
|
getFreshData() {
|
||||||
const start = new Date(window.store.get('start'));
|
const start = new Date(window.store.get('start'));
|
||||||
const end = new Date(window.store.get('end'));
|
const end = new Date(window.store.get('end'));
|
||||||
const chartCacheKey = getCacheKey('dashboard-accounts-chart', start, end)
|
const chartCacheKey = getCacheKey(this.localCacheKey('chart'), {start: start, end: end})
|
||||||
|
|
||||||
const cacheValid = window.store.get('cacheValid');
|
const cacheValid = window.store.get('cacheValid');
|
||||||
let cachedData = window.store.get(chartCacheKey);
|
let cachedData = window.store.get(chartCacheKey);
|
||||||
|
|
||||||
if (cacheValid && typeof cachedData !== 'undefined') {
|
if (cacheValid && typeof cachedData !== 'undefined') {
|
||||||
console.log(cachedData);
|
|
||||||
this.drawChart(this.generateOptions(cachedData));
|
this.drawChart(this.generateOptions(cachedData));
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
return;
|
return;
|
||||||
@@ -65,7 +67,6 @@ export default () => ({
|
|||||||
this.chartData = response.data;
|
this.chartData = response.data;
|
||||||
// cache generated options:
|
// cache generated options:
|
||||||
window.store.set(chartCacheKey, response.data);
|
window.store.set(chartCacheKey, response.data);
|
||||||
console.log(response.data);
|
|
||||||
this.drawChart(this.generateOptions(this.chartData));
|
this.drawChart(this.generateOptions(this.chartData));
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
});
|
});
|
||||||
@@ -168,7 +169,7 @@ export default () => ({
|
|||||||
}
|
}
|
||||||
const start = new Date(window.store.get('start'));
|
const start = new Date(window.store.get('start'));
|
||||||
const end = new Date(window.store.get('end'));
|
const end = new Date(window.store.get('end'));
|
||||||
const accountCacheKey = getCacheKey('dashboard-accounts-data', start, end);
|
const accountCacheKey = getCacheKey(this.localCacheKey('data'), {start: start, end: end});
|
||||||
|
|
||||||
const cacheValid = window.store.get('cacheValid');
|
const cacheValid = window.store.get('cacheValid');
|
||||||
let cachedData = window.store.get(accountCacheKey);
|
let cachedData = window.store.get(accountCacheKey);
|
||||||
@@ -221,7 +222,6 @@ export default () => ({
|
|||||||
|
|
||||||
// if transfer and source is this account, multiply again
|
// if transfer and source is this account, multiply again
|
||||||
if('transfer' === currentTransaction.type && parseInt(currentTransaction.source_id) === accountId) { //
|
if('transfer' === currentTransaction.type && parseInt(currentTransaction.source_id) === accountId) { //
|
||||||
console.log('transfer', parseInt(currentTransaction.source_id), accountId);
|
|
||||||
nativeAmountRaw = nativeAmountRaw * -1;
|
nativeAmountRaw = nativeAmountRaw * -1;
|
||||||
amountRaw = amountRaw * -1;
|
amountRaw = amountRaw * -1;
|
||||||
}
|
}
|
||||||
|
@@ -38,7 +38,8 @@ export default () => ({
|
|||||||
getFreshData() {
|
getFreshData() {
|
||||||
const start = new Date(window.store.get('start'));
|
const start = new Date(window.store.get('start'));
|
||||||
const end = new Date(window.store.get('end'));
|
const end = new Date(window.store.get('end'));
|
||||||
const boxesCacheKey = getCacheKey('dashboard-boxes-data', start, end);
|
// TODO cache key is hard coded, problem?
|
||||||
|
const boxesCacheKey = getCacheKey('ds_boxes_data', {start: start, end: end});
|
||||||
cleanupCache();
|
cleanupCache();
|
||||||
|
|
||||||
const cacheValid = window.store.get('cacheValid');
|
const cacheValid = window.store.get('cacheValid');
|
||||||
@@ -208,6 +209,7 @@ export default () => ({
|
|||||||
// Getter
|
// Getter
|
||||||
init() {
|
init() {
|
||||||
// console.log('boxes init');
|
// console.log('boxes init');
|
||||||
|
// TODO can be replaced by "getVariables"
|
||||||
Promise.all([getVariable('viewRange'), getVariable('autoConversion', false)]).then((values) => {
|
Promise.all([getVariable('viewRange'), getVariable('autoConversion', false)]).then((values) => {
|
||||||
// console.log('boxes after promises');
|
// console.log('boxes after promises');
|
||||||
afterPromises = true;
|
afterPromises = true;
|
||||||
|
@@ -59,7 +59,7 @@ export default () => ({
|
|||||||
getFreshData() {
|
getFreshData() {
|
||||||
const start = new Date(window.store.get('start'));
|
const start = new Date(window.store.get('start'));
|
||||||
const end = new Date(window.store.get('end'));
|
const end = new Date(window.store.get('end'));
|
||||||
const cacheKey = getCacheKey('dashboard-budgets-chart', start, end);
|
const cacheKey = getCacheKey('ds_bdg_chart', {start: start, end: end});
|
||||||
const cacheValid = window.store.get('cacheValid');
|
const cacheValid = window.store.get('cacheValid');
|
||||||
let cachedData = window.store.get(cacheKey);
|
let cachedData = window.store.get(cacheKey);
|
||||||
|
|
||||||
|
@@ -147,7 +147,7 @@ export default () => ({
|
|||||||
getFreshData() {
|
getFreshData() {
|
||||||
const start = new Date(window.store.get('start'));
|
const start = new Date(window.store.get('start'));
|
||||||
const end = new Date(window.store.get('end'));
|
const end = new Date(window.store.get('end'));
|
||||||
const cacheKey = getCacheKey('dashboard-categories-chart', start, end);
|
const cacheKey = getCacheKey('ds_ct_chart', {start: start, end: end});
|
||||||
|
|
||||||
const cacheValid = window.store.get('cacheValid');
|
const cacheValid = window.store.get('cacheValid');
|
||||||
let cachedData = window.store.get(cacheKey);
|
let cachedData = window.store.get(cacheKey);
|
||||||
|
@@ -25,7 +25,7 @@ import i18next from "i18next";
|
|||||||
|
|
||||||
let apiData = {};
|
let apiData = {};
|
||||||
let afterPromises = false;
|
let afterPromises = false;
|
||||||
const PIGGY_CACHE_KEY = 'dashboard-piggies-data';
|
const PIGGY_CACHE_KEY = 'ds_pg_data';
|
||||||
|
|
||||||
export default () => ({
|
export default () => ({
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -36,7 +36,7 @@ export default () => ({
|
|||||||
const start = new Date(window.store.get('start'));
|
const start = new Date(window.store.get('start'));
|
||||||
const end = new Date(window.store.get('end'));
|
const end = new Date(window.store.get('end'));
|
||||||
// needs user data.
|
// needs user data.
|
||||||
const cacheKey = getCacheKey(PIGGY_CACHE_KEY, start, end);
|
const cacheKey = getCacheKey(PIGGY_CACHE_KEY, {start: start, end: end});
|
||||||
|
|
||||||
const cacheValid = window.store.get('cacheValid');
|
const cacheValid = window.store.get('cacheValid');
|
||||||
let cachedData = window.store.get(cacheKey);
|
let cachedData = window.store.get(cacheKey);
|
||||||
@@ -58,7 +58,7 @@ export default () => ({
|
|||||||
downloadPiggyBanks(params) {
|
downloadPiggyBanks(params) {
|
||||||
const start = new Date(window.store.get('start'));
|
const start = new Date(window.store.get('start'));
|
||||||
const end = new Date(window.store.get('end'));
|
const end = new Date(window.store.get('end'));
|
||||||
const cacheKey = getCacheKey(PIGGY_CACHE_KEY, start, end);
|
const cacheKey = getCacheKey(PIGGY_CACHE_KEY, {start: start, end: end});
|
||||||
const getter = new Get();
|
const getter = new Get();
|
||||||
getter.list(params).then((response) => {
|
getter.list(params).then((response) => {
|
||||||
apiData = [...apiData, ...response.data.data];
|
apiData = [...apiData, ...response.data.data];
|
||||||
|
@@ -28,7 +28,7 @@ import i18next from "i18next";
|
|||||||
|
|
||||||
Chart.register({SankeyController, Flow});
|
Chart.register({SankeyController, Flow});
|
||||||
|
|
||||||
const SANKEY_CACHE_KEY = 'dashboard-sankey-data';
|
const SANKEY_CACHE_KEY = 'ds_sankey_data';
|
||||||
let currencies = [];
|
let currencies = [];
|
||||||
let afterPromises = false;
|
let afterPromises = false;
|
||||||
let chart = null;
|
let chart = null;
|
||||||
@@ -288,7 +288,7 @@ export default () => ({
|
|||||||
getFreshData() {
|
getFreshData() {
|
||||||
const start = new Date(window.store.get('start'));
|
const start = new Date(window.store.get('start'));
|
||||||
const end = new Date(window.store.get('end'));
|
const end = new Date(window.store.get('end'));
|
||||||
const cacheKey = getCacheKey(SANKEY_CACHE_KEY, start, end);
|
const cacheKey = getCacheKey(SANKEY_CACHE_KEY, {start: start, end: end});
|
||||||
|
|
||||||
const cacheValid = window.store.get('cacheValid');
|
const cacheValid = window.store.get('cacheValid');
|
||||||
let cachedData = window.store.get(cacheKey);
|
let cachedData = window.store.get(cacheKey);
|
||||||
@@ -312,7 +312,7 @@ export default () => ({
|
|||||||
downloadTransactions(params) {
|
downloadTransactions(params) {
|
||||||
const start = new Date(window.store.get('start'));
|
const start = new Date(window.store.get('start'));
|
||||||
const end = new Date(window.store.get('end'));
|
const end = new Date(window.store.get('end'));
|
||||||
const cacheKey = getCacheKey(SANKEY_CACHE_KEY, start, end);
|
const cacheKey = getCacheKey(SANKEY_CACHE_KEY, {start: start, end: end});
|
||||||
|
|
||||||
//console.log('Downloading page ' + params.page + '...');
|
//console.log('Downloading page ' + params.page + '...');
|
||||||
const getter = new Get();
|
const getter = new Get();
|
||||||
|
@@ -186,7 +186,7 @@ export default () => ({
|
|||||||
let end = new Date(window.store.get('end'));
|
let end = new Date(window.store.get('end'));
|
||||||
|
|
||||||
const cacheValid = window.store.get('cacheValid');
|
const cacheValid = window.store.get('cacheValid');
|
||||||
let cachedData = window.store.get(getCacheKey('subscriptions-data-dashboard', start, end));
|
let cachedData = window.store.get(getCacheKey('ds_sub_data', {start: start, end: end}));
|
||||||
|
|
||||||
if (cacheValid && typeof cachedData !== 'undefined' && false) {
|
if (cacheValid && typeof cachedData !== 'undefined' && false) {
|
||||||
console.error('cannot handle yet');
|
console.error('cannot handle yet');
|
||||||
|
47
resources/assets/v2/src/store/get-variables.js
Normal file
47
resources/assets/v2/src/store/get-variables.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* 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 {getVariable} from "./get-variable.js";
|
||||||
|
|
||||||
|
export function getVariables(preferences) {
|
||||||
|
let chain = Promise.resolve();
|
||||||
|
let allVariables = [];
|
||||||
|
for (let i = 0; i < preferences.length; i++) {
|
||||||
|
|
||||||
|
let current = preferences[i];
|
||||||
|
let name = current.name;
|
||||||
|
let defaultValue = current.default;
|
||||||
|
chain = chain.then(() => {
|
||||||
|
return getVariable(name, defaultValue).then((value) => {
|
||||||
|
allVariables.push(value);
|
||||||
|
return Promise.resolve(allVariables);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return chain;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseResponse(name, response) {
|
||||||
|
let value = response.data.data.attributes.data;
|
||||||
|
window.store.set(name, value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
@@ -20,12 +20,77 @@
|
|||||||
|
|
||||||
import {format} from "date-fns";
|
import {format} from "date-fns";
|
||||||
import store from "store";
|
import store from "store";
|
||||||
|
//const { createHash } = require('crypto');
|
||||||
|
|
||||||
function getCacheKey(string, start, end) {
|
|
||||||
const localValue = store.get('lastActivity');
|
|
||||||
const cacheKey = 'dcx' + format(start, 'yMMdd')+ format(end, 'yMMdd') + string + localValue;
|
function getCacheKey(string, params) {
|
||||||
console.log('getCacheKey: ' + cacheKey);
|
const lastActivity = store.get('lastActivity')
|
||||||
return String(cacheKey);
|
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};
|
export {getCacheKey};
|
||||||
|
95
resources/assets/v2/src/support/page-navigation.js
Normal file
95
resources/assets/v2/src/support/page-navigation.js
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* page-navigation.js
|
||||||
|
* Copyright (c) 2024 james@firefly-iii.org.
|
||||||
|
*
|
||||||
|
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function logarithmicPaginationLinks(lastPage, matchPage, linkURL) {
|
||||||
|
function pageLink(p, page) {
|
||||||
|
if(p === page) {
|
||||||
|
// href="'+ linkURL+ p + '"
|
||||||
|
return '<li class="page-item active" aria-current="page"><a class="page-link" href="#" @click.prevent="goToPage('+p+')">'+p+'</a></li>';
|
||||||
|
}
|
||||||
|
// href="'+ linkURL+ p + '"
|
||||||
|
return '<li class="page-item"><a class="page-link" href="#" @click.prevent="goToPage('+p+')">'+p+'</a></li>';
|
||||||
|
|
||||||
|
// return ((p === page) ? "<b>" + p + "</b>" : '<a href="' + linkURL + p + '">' + p + "</a>");
|
||||||
|
}
|
||||||
|
|
||||||
|
let page = (matchPage ? matchPage : 1), LINKS_PER_STEP = 5, lastp1 = 1, lastp2 = page, p1 = 1, p2 = page,
|
||||||
|
c1 = LINKS_PER_STEP + 1, c2 = LINKS_PER_STEP + 1, s1 = "", s2 = "", step = 1, linkHTML = "";
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (c1 >= c2) {
|
||||||
|
s1 += pageLink(p1, matchPage);
|
||||||
|
lastp1 = p1;
|
||||||
|
p1 += step;
|
||||||
|
c1--;
|
||||||
|
} else {
|
||||||
|
s2 = pageLink(p2, matchPage) + s2;
|
||||||
|
lastp2 = p2;
|
||||||
|
p2 -= step;
|
||||||
|
c2--;
|
||||||
|
}
|
||||||
|
if (c2 === 0) {
|
||||||
|
step *= 25;
|
||||||
|
p1 += step - 1; // Round UP to nearest multiple of step
|
||||||
|
p1 -= (p1 % step);
|
||||||
|
p2 -= (p2 % step); // Round DOWN to nearest multiple of step
|
||||||
|
c1 = LINKS_PER_STEP;
|
||||||
|
c2 = LINKS_PER_STEP;
|
||||||
|
}
|
||||||
|
if (p1 > p2) {
|
||||||
|
linkHTML += s1 + s2;
|
||||||
|
if ((lastp2 > page) || (page >= lastPage)) break;
|
||||||
|
lastp1 = page;
|
||||||
|
lastp2 = lastPage;
|
||||||
|
p1 = page + 1;
|
||||||
|
p2 = lastPage;
|
||||||
|
c1 = LINKS_PER_STEP;
|
||||||
|
c2 = LINKS_PER_STEP + 1;
|
||||||
|
s1 = '';
|
||||||
|
s2 = '';
|
||||||
|
step = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return linkHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function pageNavigation(totalPages, currentPage, navigationURL) {
|
||||||
|
|
||||||
|
totalPages = parseInt(totalPages);
|
||||||
|
currentPage = parseInt(currentPage);
|
||||||
|
let html = '<nav aria-label="Page navigation">';
|
||||||
|
html += '<ul class="pagination">';
|
||||||
|
if(currentPage > 1) {
|
||||||
|
html += '<li class="page-item"><a class="page-link" href="#">Previous</a></li>';
|
||||||
|
}
|
||||||
|
if(1 === currentPage) {
|
||||||
|
html += '<li class="page-item disabled"><a class="page-link">Previous</a></li>';
|
||||||
|
}
|
||||||
|
html += logarithmicPaginationLinks(totalPages, currentPage, navigationURL);
|
||||||
|
if(currentPage !== totalPages) {
|
||||||
|
html += '<li class="page-item"><a class="page-link" href="#">Next</a></li>';
|
||||||
|
}
|
||||||
|
if(currentPage === totalPages) {
|
||||||
|
html += '<li class="page-item disabled"><a class="page-link">Next</a></li>';
|
||||||
|
}
|
||||||
|
html += '</ul></nav>';
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
@@ -37,7 +37,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
Nav
|
<div x-html="pageNavigation()">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
@@ -97,9 +97,9 @@
|
|||||||
<th x-show="tableColumns.active.visible && tableColumns.active.enabled">
|
<th x-show="tableColumns.active.visible && tableColumns.active.enabled">
|
||||||
<a href="#" x-on:click.prevent="sort('active')">Active?</a>
|
<a href="#" x-on:click.prevent="sort('active')">Active?</a>
|
||||||
<em x-show="pageOptions.sortingColumn === 'active' && pageOptions.sortDirection === 'asc'"
|
<em x-show="pageOptions.sortingColumn === 'active' && pageOptions.sortDirection === 'asc'"
|
||||||
class="fa-solid fa-arrow-down-wide-short"></em>
|
class="fa-solid fa-arrow-down-short-wide"></em>
|
||||||
<em x-show="pageOptions.sortingColumn === 'active' && pageOptions.sortDirection === 'desc'"
|
<em x-show="pageOptions.sortingColumn === 'active' && pageOptions.sortDirection === 'desc'"
|
||||||
class="fa-solid fa-arrow-up-wide-short"></em>
|
class="fa-solid fa-arrow-down-wide-short"></em>
|
||||||
</th>
|
</th>
|
||||||
<th x-show="tableColumns.name.visible && tableColumns.name.enabled">
|
<th x-show="tableColumns.name.visible && tableColumns.name.enabled">
|
||||||
<a href="#" x-on:click.prevent="sort('name')">Name</a>
|
<a href="#" x-on:click.prevent="sort('name')">Name</a>
|
||||||
@@ -121,16 +121,16 @@
|
|||||||
<th x-show="tableColumns.number.visible && tableColumns.number.enabled">
|
<th x-show="tableColumns.number.visible && tableColumns.number.enabled">
|
||||||
<a href="#" x-on:click.prevent="sort('iban')">Account number</a>
|
<a href="#" x-on:click.prevent="sort('iban')">Account number</a>
|
||||||
<em x-show="pageOptions.sortingColumn === 'iban' && pageOptions.sortDirection === 'asc'"
|
<em x-show="pageOptions.sortingColumn === 'iban' && pageOptions.sortDirection === 'asc'"
|
||||||
class="fa-solid fa-arrow-down-z-a"></em>
|
class="fa-solid fa-arrow-down-a-z"></em>
|
||||||
<em x-show="pageOptions.sortingColumn === 'iban' && pageOptions.sortDirection === 'desc'"
|
<em x-show="pageOptions.sortingColumn === 'iban' && pageOptions.sortDirection === 'desc'"
|
||||||
class="fa-solid fa-arrow-up-z-a"></em>
|
class="fa-solid fa-arrow-down-z-a"></em>
|
||||||
</th>
|
</th>
|
||||||
<th x-show="tableColumns.current_balance.visible && tableColumns.current_balance.enabled">
|
<th x-show="tableColumns.current_balance.visible && tableColumns.current_balance.enabled">
|
||||||
<a href="#" x-on:click.prevent="sort('balance')">Current balance</a>
|
<a href="#" x-on:click.prevent="sort('balance')">Current balance</a>
|
||||||
<em x-show="pageOptions.sortingColumn === 'balance' && pageOptions.sortDirection === 'asc'"
|
<em x-show="pageOptions.sortingColumn === 'balance' && pageOptions.sortDirection === 'asc'"
|
||||||
class="fa-solid fa-arrow-down-wide-short"></em>
|
class="fa-solid fa-arrow-down-9-1"></em>
|
||||||
<em x-show="pageOptions.sortingColumn === 'balance' && pageOptions.sortDirection === 'desc'"
|
<em x-show="pageOptions.sortingColumn === 'balance' && pageOptions.sortDirection === 'desc'"
|
||||||
class="fa-solid fa-arrow-up-wide-short"></em>
|
class="fa-solid fa-arrow-down-1-9"></em>
|
||||||
</th>
|
</th>
|
||||||
<th x-show="tableColumns.amount_due.visible && tableColumns.amount_due.enabled">
|
<th x-show="tableColumns.amount_due.visible && tableColumns.amount_due.enabled">
|
||||||
<a href="#" x-on:click.prevent="sort('amount_due')">Amount due</a>
|
<a href="#" x-on:click.prevent="sort('amount_due')">Amount due</a>
|
||||||
@@ -156,6 +156,11 @@
|
|||||||
</th>
|
</th>
|
||||||
<th x-show="tableColumns.menu.visible && tableColumns.menu.enabled"> </th>
|
<th x-show="tableColumns.menu.visible && tableColumns.menu.enabled"> </th>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr x-show="pageOptions.isLoading">
|
||||||
|
<td colspan="13" class="text-center">
|
||||||
|
<span class="fa fa-spin fa-spinner"></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<template x-for="(account, index) in accounts" :key="index">
|
<template x-for="(account, index) in accounts" :key="index">
|
||||||
@@ -237,7 +242,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td x-show="tableColumns.current_balance.visible && tableColumns.current_balance.enabled">
|
<td x-show="tableColumns.current_balance.visible && tableColumns.current_balance.enabled">
|
||||||
<span
|
<span
|
||||||
x-text="formatMoney(account.current_balance, account.currency_code)"></span>
|
x-text="formatMoney(account.native_current_balance, account.currency_code)"></span>
|
||||||
</td>
|
</td>
|
||||||
<td x-show="tableColumns.amount_due.visible && tableColumns.amount_due.enabled">
|
<td x-show="tableColumns.amount_due.visible && tableColumns.amount_due.enabled">
|
||||||
TODO
|
TODO
|
||||||
@@ -289,7 +294,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
Nav
|
<div x-html="pageNavigation()">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -320,11 +325,11 @@
|
|||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<label for="inputEmail3" class="col-sm-4 col-form-label">Active accounts?</label>
|
<label for="inputEmail3" class="col-sm-4 col-form-label">Active accounts?</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<select x-model="filters.active" class="form-control">
|
<select @change="saveActiveFilter" class="form-control">
|
||||||
<option value="active" label="Active accounts">Active accounts only</option>
|
<option value="active" :selected="true === filters.active" label="Active accounts">Active accounts only</option>
|
||||||
<option value="inactive" label="Inactive accounts">Inactive accounts only
|
<option value="inactive" :selected="false === filters.active" label="Inactive accounts">Inactive accounts only
|
||||||
</option>
|
</option>
|
||||||
<option value="both" label="Both">All accounts</option>
|
<option value="both" :selected="null === filters.active" label="Both">All accounts</option>
|
||||||
</select>
|
</select>
|
||||||
<div id="emailHelp" class="form-text">TODO Bla bla bla.</div>
|
<div id="emailHelp" class="form-text">TODO Bla bla bla.</div>
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user