mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-09-18 18:44:16 +00:00
Expand account index overview.
This commit is contained in:
@@ -157,6 +157,9 @@ class Controller extends BaseController
|
||||
{
|
||||
$manager = new Manager();
|
||||
$baseUrl = request()->getSchemeAndHttpHost().'/api/v2';
|
||||
|
||||
// TODO add stuff to path?
|
||||
|
||||
$manager->setSerializer(new JsonApiSerializer($baseUrl));
|
||||
|
||||
$objects = $paginator->getCollection();
|
||||
|
@@ -33,7 +33,7 @@ use Illuminate\Pagination\LengthAwarePaginator;
|
||||
|
||||
class IndexController extends Controller
|
||||
{
|
||||
public const string RESOURCE_KEY = 'accounts';
|
||||
public const string RESOURCE_KEY = 'accounts';
|
||||
|
||||
private AccountRepositoryInterface $repository;
|
||||
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY, UserRoleEnum::MANAGE_TRANSACTIONS];
|
||||
@@ -48,7 +48,7 @@ class IndexController extends Controller
|
||||
function ($request, $next) {
|
||||
$this->repository = app(AccountRepositoryInterface::class);
|
||||
// new way of user group validation
|
||||
$userGroup = $this->validateUserGroup($request);
|
||||
$userGroup = $this->validateUserGroup($request);
|
||||
$this->repository->setUserGroup($userGroup);
|
||||
|
||||
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.
|
||||
*/
|
||||
public function index(IndexRequest $request): JsonResponse
|
||||
{
|
||||
$this->repository->resetAccountOrder();
|
||||
$types = $request->getAccountTypes();
|
||||
$instructions = $request->getSortInstructions('accounts');
|
||||
$accounts = $this->repository->getAccountsByType($types, $instructions);
|
||||
$pageSize = $this->parameters->get('limit');
|
||||
$count = $accounts->count();
|
||||
$accounts = $accounts->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
|
||||
$paginator = new LengthAwarePaginator($accounts, $count, $pageSize, $this->parameters->get('page'));
|
||||
$transformer = new AccountTransformer();
|
||||
$types = $request->getAccountTypes();
|
||||
$sorting = $request->getSortInstructions('accounts');
|
||||
$filters = $request->getFilterInstructions('accounts');
|
||||
$accounts = $this->repository->getAccountsByType($types, $sorting, $filters);
|
||||
$pageSize = $this->parameters->get('limit');
|
||||
$count = $accounts->count();
|
||||
$accounts = $accounts->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
|
||||
$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
|
||||
|
||||
return response()
|
||||
->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\Request\ChecksLogin;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use FireflyIII\Support\Request\GetFilterInstructions;
|
||||
use FireflyIII\Support\Request\GetSortInstructions;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
@@ -41,6 +42,8 @@ class IndexRequest extends FormRequest
|
||||
use ChecksLogin;
|
||||
use ConvertsDataTypes;
|
||||
use GetSortInstructions;
|
||||
use GetFilterInstructions;
|
||||
|
||||
|
||||
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.
|
||||
$res = array_intersect([AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT], $types);
|
||||
@@ -249,6 +249,19 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
$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:
|
||||
$hasActiveColumn = array_key_exists('active', $sort);
|
||||
if (count($sort) > 0) {
|
||||
|
@@ -55,7 +55,7 @@ interface AccountRepositoryInterface
|
||||
|
||||
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.
|
||||
|
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',
|
||||
],
|
||||
|
||||
'bindables' => [
|
||||
'bindables' => [
|
||||
// models
|
||||
'account' => Account::class,
|
||||
'attachment' => Attachment::class,
|
||||
@@ -494,7 +494,7 @@ return [
|
||||
'userGroupBill' => UserGroupBill::class,
|
||||
'userGroup' => UserGroup::class,
|
||||
],
|
||||
'rule-actions' => [
|
||||
'rule-actions' => [
|
||||
'set_category' => SetCategory::class,
|
||||
'clear_category' => ClearCategory::class,
|
||||
'set_budget' => SetBudget::class,
|
||||
@@ -528,7 +528,7 @@ return [
|
||||
// 'set_foreign_amount' => SetForeignAmount::class,
|
||||
// 'set_foreign_currency' => SetForeignCurrency::class,
|
||||
],
|
||||
'context-rule-actions' => [
|
||||
'context-rule-actions' => [
|
||||
'set_category',
|
||||
'set_budget',
|
||||
'add_tag',
|
||||
@@ -547,13 +547,13 @@ return [
|
||||
'convert_transfer',
|
||||
],
|
||||
|
||||
'test-triggers' => [
|
||||
'test-triggers' => [
|
||||
'limit' => 10,
|
||||
'range' => 200,
|
||||
],
|
||||
|
||||
// expected source types for each transaction type, in order of preference.
|
||||
'expected_source_types' => [
|
||||
'expected_source_types' => [
|
||||
'source' => [
|
||||
TransactionTypeModel::WITHDRAWAL => [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE],
|
||||
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],
|
||||
],
|
||||
],
|
||||
'allowed_opposing_types' => [
|
||||
'allowed_opposing_types' => [
|
||||
'source' => [
|
||||
AccountType::ASSET => [
|
||||
AccountType::ASSET,
|
||||
@@ -688,7 +688,7 @@ return [
|
||||
],
|
||||
],
|
||||
// depending on the account type, return the allowed transaction types:
|
||||
'allowed_transaction_types' => [
|
||||
'allowed_transaction_types' => [
|
||||
'source' => [
|
||||
AccountType::ASSET => [
|
||||
TransactionTypeModel::WITHDRAWAL,
|
||||
@@ -757,7 +757,7 @@ return [
|
||||
],
|
||||
|
||||
// having the source + dest will tell you the transaction type.
|
||||
'account_to_transaction' => [
|
||||
'account_to_transaction' => [
|
||||
AccountType::ASSET => [
|
||||
AccountType::ASSET => TransactionTypeModel::TRANSFER,
|
||||
AccountType::CASH => TransactionTypeModel::WITHDRAWAL,
|
||||
@@ -822,7 +822,7 @@ return [
|
||||
],
|
||||
|
||||
// allowed source -> destination accounts.
|
||||
'source_dests' => [
|
||||
'source_dests' => [
|
||||
TransactionTypeModel::WITHDRAWAL => [
|
||||
AccountType::ASSET => [AccountType::EXPENSE, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE, 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).
|
||||
'journal_meta_fields' => [
|
||||
'journal_meta_fields' => [
|
||||
// sepa
|
||||
'sepa_cc',
|
||||
'sepa_ct_op',
|
||||
@@ -895,33 +895,47 @@ return [
|
||||
'recurrence_count',
|
||||
'recurrence_date',
|
||||
],
|
||||
'webhooks' => [
|
||||
'webhooks' => [
|
||||
'max_attempts' => env('WEBHOOK_MAX_ATTEMPTS', 3),
|
||||
],
|
||||
'can_have_virtual_amounts' => [AccountType::ASSET],
|
||||
'can_have_opening_balance' => [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE],
|
||||
'dynamic_creation_allowed' => [
|
||||
'can_have_virtual_amounts' => [AccountType::ASSET],
|
||||
'can_have_opening_balance' => [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE],
|
||||
'dynamic_creation_allowed' => [
|
||||
AccountType::EXPENSE,
|
||||
AccountType::REVENUE,
|
||||
AccountType::INITIAL_BALANCE,
|
||||
AccountType::RECONCILIATION,
|
||||
AccountType::LIABILITY_CREDIT,
|
||||
],
|
||||
'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_account_fields' => ['account_number', 'currency_id', 'BIC', 'interest', 'interest_period', 'include_net_worth', 'liability_direction'],
|
||||
'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_account_fields' => ['account_number', 'currency_id', 'BIC', 'interest', 'interest_period', 'include_net_worth', 'liability_direction'],
|
||||
|
||||
// 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
|
||||
'allowed_sort_parameters' => ['order', 'name', 'iban'],
|
||||
'allowed_sort_parameters' => ['order', 'name', 'iban'],
|
||||
|
||||
// preselected account lists possibilities:
|
||||
'preselected_accounts' => ['all', 'assets', 'liabilities'],
|
||||
'preselected_accounts' => ['all', 'assets', 'liabilities'],
|
||||
|
||||
// allowed sort columns for API's
|
||||
'sorting' => [
|
||||
// allowed filters (search) for APIs
|
||||
'filters' => [
|
||||
'allowed' => [
|
||||
'accounts' => [
|
||||
'name' => '*',
|
||||
'active' => 'boolean',
|
||||
'iban' => 'iban',
|
||||
'balance' => 'numeric',
|
||||
'last_activity' => 'date',
|
||||
'balance_difference' => 'numeric',
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
// allowed sort columns for APIs
|
||||
'sorting' => [
|
||||
'allowed' => [
|
||||
'transactions' => ['description', 'amount'],
|
||||
'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 format from "date-fns/format";
|
||||
import {getCacheKey} from "../../../../support/get-cache-key.js";
|
||||
|
||||
export default class Get {
|
||||
|
||||
@@ -39,7 +40,24 @@ export default class Get {
|
||||
* @returns {Promise<AxiosResponse<any>>}
|
||||
*/
|
||||
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 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 Put from "../../api/v2/model/account/put.js";
|
||||
import AccountRenderer from "../../support/renderers/AccountRenderer.js";
|
||||
import {showInternalsButton} from "../../support/page-settings/show-internals-button.js";
|
||||
import {showWizardButton} from "../../support/page-settings/show-wizard-button.js";
|
||||
import {getVariable} from "../../store/get-variable.js";
|
||||
import {setVariable} from "../../store/set-variable.js";
|
||||
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
|
||||
@@ -43,6 +42,7 @@ const type = urlParts[urlParts.length - 1];
|
||||
|
||||
let sortingColumn = '';
|
||||
let sortDirection = '';
|
||||
let page = 1;
|
||||
|
||||
// get sort parameters
|
||||
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 ?? '';
|
||||
sortDirection = params.direction ?? '';
|
||||
page = parseInt(params.page ?? 1);
|
||||
|
||||
|
||||
showInternalsButton();
|
||||
showWizardButton();
|
||||
|
||||
// TODO currency conversion
|
||||
// TODO page cleanup and recycle for transaction lists.
|
||||
|
||||
let index = function () {
|
||||
return {
|
||||
// notifications
|
||||
@@ -70,11 +74,13 @@ let index = function () {
|
||||
},
|
||||
totalPages: 1,
|
||||
page: 1,
|
||||
pageUrl: '',
|
||||
filters: {
|
||||
active: 'both',
|
||||
active: null,
|
||||
name: null,
|
||||
},
|
||||
pageOptions: {
|
||||
isLoading: true,
|
||||
groupedAccounts: true,
|
||||
sortingColumn: sortingColumn,
|
||||
sortDirection: sortDirection,
|
||||
@@ -138,29 +144,46 @@ let index = function () {
|
||||
},
|
||||
editors: {},
|
||||
accounts: [],
|
||||
|
||||
goToPage(page) {
|
||||
this.page = page;
|
||||
this.loadAccounts();
|
||||
},
|
||||
accountRole(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) {
|
||||
this.page =1;
|
||||
this.pageOptions.sortingColumn = column;
|
||||
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
|
||||
// TODO variable name in better place
|
||||
const columnKey = 'acc_index_' + type + '_sc';
|
||||
const directionKey = 'acc_index_' + type + '_sd';
|
||||
|
||||
setVariable(columnKey, this.pageOptions.sortingColumn);
|
||||
setVariable(directionKey, this.pageOptions.sortDirection);
|
||||
setVariable(this.getPreferenceKey('sc'), this.pageOptions.sortingColumn);
|
||||
setVariable(this.getPreferenceKey('sd'), this.pageOptions.sortDirection);
|
||||
|
||||
this.loadAccounts();
|
||||
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) {
|
||||
return formatMoney(amount, currencyCode);
|
||||
@@ -177,56 +200,58 @@ let index = function () {
|
||||
}
|
||||
}
|
||||
console.log('New settings', newSettings);
|
||||
setVariable('acc_index_' + type + '_columns', newSettings);
|
||||
setVariable(this.getPreferenceKey('columns'), newSettings);
|
||||
},
|
||||
|
||||
init() {
|
||||
this.pageOptions.isLoading = true;
|
||||
this.notifications.wait.show = true;
|
||||
this.page = page;
|
||||
this.notifications.wait.text = i18next.t('firefly.wait_loading_data');
|
||||
|
||||
// get column preference
|
||||
// TODO key in better variable
|
||||
const key = 'acc_index_' + type + '_columns';
|
||||
const defaultValue = {"drag_and_drop": false};
|
||||
|
||||
// get sort column
|
||||
const columnKey = 'acc_index_' + type + '_sc';
|
||||
const columnDefault = '';
|
||||
|
||||
// get sort direction
|
||||
const directionKey = 'acc_index_' + type + '_sd';
|
||||
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;
|
||||
// start by collecting all preferences, create + put in the local store.
|
||||
getVariables([
|
||||
{name: this.getPreferenceKey('columns'), default: {"drag_and_drop": false}},
|
||||
{name: this.getPreferenceKey('sc'), default: ''},
|
||||
{name: this.getPreferenceKey('sd'), default: ''},
|
||||
{name: this.getPreferenceKey('filters'), default: this.filters},
|
||||
]).then((res) => {
|
||||
// process columns:
|
||||
for (let k in res[0]) {
|
||||
if (res[0].hasOwnProperty(k) && this.tableColumns.hasOwnProperty(k)) {
|
||||
this.tableColumns[k].enabled = res[0][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();
|
||||
});
|
||||
|
||||
},
|
||||
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) {
|
||||
let renderer = new AccountRenderer();
|
||||
@@ -270,36 +295,47 @@ let index = function () {
|
||||
this.accounts[index].nameEditorVisible = true;
|
||||
},
|
||||
loadAccounts() {
|
||||
|
||||
this.pageOptions.isLoading = true;
|
||||
// sort instructions
|
||||
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:
|
||||
const start = new Date(window.store.get('start'));
|
||||
const end = new Date(window.store.get('end'));
|
||||
const today = new Date();
|
||||
|
||||
let params = {
|
||||
sorting: sorting,
|
||||
filters: filters,
|
||||
today: today,
|
||||
type: type,
|
||||
page: this.page,
|
||||
start: start,
|
||||
end: end
|
||||
};
|
||||
|
||||
if(!this.tableColumns.balance_difference.enabled){
|
||||
if (!this.tableColumns.balance_difference.enabled) {
|
||||
delete params.start;
|
||||
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 = [];
|
||||
|
||||
// one page only.o
|
||||
(new Get()).index(params).then(response => {
|
||||
for (let i = 0; i < response.data.data.length; i++) {
|
||||
if (response.data.data.hasOwnProperty(i)) {
|
||||
let current = response.data.data[i];
|
||||
this.totalPages = response.meta.pagination.total_pages;
|
||||
for (let i = 0; i < response.data.length; i++) {
|
||||
if (response.data.hasOwnProperty(i)) {
|
||||
let current = response.data[i];
|
||||
let account = {
|
||||
id: parseInt(current.id),
|
||||
active: current.attributes.active,
|
||||
@@ -317,11 +353,11 @@ let index = function () {
|
||||
balance_difference: current.attributes.balance_difference,
|
||||
native_balance_difference: current.attributes.native_balance_difference
|
||||
};
|
||||
console.log(current.attributes.balance_difference);
|
||||
this.accounts.push(account);
|
||||
}
|
||||
}
|
||||
this.notifications.wait.show = false;
|
||||
this.pageOptions.isLoading = false;
|
||||
// add click trigger thing.
|
||||
});
|
||||
},
|
||||
|
@@ -63,7 +63,6 @@ let administrations = function () {
|
||||
|
||||
pageProperties: {},
|
||||
submitForm() {
|
||||
console.log('submitForm');
|
||||
(new Put()).put({title: this.title}, {id: this.id}).then(response => {
|
||||
if (this.formStates.returnHereButton) {
|
||||
this.notifications.success.show = true;
|
||||
|
@@ -46,16 +46,18 @@ export default () => ({
|
||||
this.autoConversion = !this.autoConversion;
|
||||
setVariable('autoConversion', this.autoConversion);
|
||||
},
|
||||
localCacheKey(type) {
|
||||
return 'ds_accounts_' + type;
|
||||
},
|
||||
getFreshData() {
|
||||
const start = new Date(window.store.get('start'));
|
||||
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');
|
||||
let cachedData = window.store.get(chartCacheKey);
|
||||
|
||||
if (cacheValid && typeof cachedData !== 'undefined') {
|
||||
console.log(cachedData);
|
||||
this.drawChart(this.generateOptions(cachedData));
|
||||
this.loading = false;
|
||||
return;
|
||||
@@ -65,7 +67,6 @@ export default () => ({
|
||||
this.chartData = response.data;
|
||||
// cache generated options:
|
||||
window.store.set(chartCacheKey, response.data);
|
||||
console.log(response.data);
|
||||
this.drawChart(this.generateOptions(this.chartData));
|
||||
this.loading = false;
|
||||
});
|
||||
@@ -168,7 +169,7 @@ export default () => ({
|
||||
}
|
||||
const start = new Date(window.store.get('start'));
|
||||
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');
|
||||
let cachedData = window.store.get(accountCacheKey);
|
||||
@@ -221,7 +222,6 @@ export default () => ({
|
||||
|
||||
// if transfer and source is this account, multiply again
|
||||
if('transfer' === currentTransaction.type && parseInt(currentTransaction.source_id) === accountId) { //
|
||||
console.log('transfer', parseInt(currentTransaction.source_id), accountId);
|
||||
nativeAmountRaw = nativeAmountRaw * -1;
|
||||
amountRaw = amountRaw * -1;
|
||||
}
|
||||
|
@@ -38,7 +38,8 @@ export default () => ({
|
||||
getFreshData() {
|
||||
const start = new Date(window.store.get('start'));
|
||||
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();
|
||||
|
||||
const cacheValid = window.store.get('cacheValid');
|
||||
@@ -208,6 +209,7 @@ export default () => ({
|
||||
// Getter
|
||||
init() {
|
||||
// console.log('boxes init');
|
||||
// TODO can be replaced by "getVariables"
|
||||
Promise.all([getVariable('viewRange'), getVariable('autoConversion', false)]).then((values) => {
|
||||
// console.log('boxes after promises');
|
||||
afterPromises = true;
|
||||
|
@@ -59,7 +59,7 @@ export default () => ({
|
||||
getFreshData() {
|
||||
const start = new Date(window.store.get('start'));
|
||||
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');
|
||||
let cachedData = window.store.get(cacheKey);
|
||||
|
||||
|
@@ -147,7 +147,7 @@ export default () => ({
|
||||
getFreshData() {
|
||||
const start = new Date(window.store.get('start'));
|
||||
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');
|
||||
let cachedData = window.store.get(cacheKey);
|
||||
|
@@ -25,7 +25,7 @@ import i18next from "i18next";
|
||||
|
||||
let apiData = {};
|
||||
let afterPromises = false;
|
||||
const PIGGY_CACHE_KEY = 'dashboard-piggies-data';
|
||||
const PIGGY_CACHE_KEY = 'ds_pg_data';
|
||||
|
||||
export default () => ({
|
||||
loading: false,
|
||||
@@ -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, end);
|
||||
const cacheKey = getCacheKey(PIGGY_CACHE_KEY, {start: start, end: end});
|
||||
|
||||
const cacheValid = window.store.get('cacheValid');
|
||||
let cachedData = window.store.get(cacheKey);
|
||||
@@ -58,7 +58,7 @@ export default () => ({
|
||||
downloadPiggyBanks(params) {
|
||||
const start = new Date(window.store.get('start'));
|
||||
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();
|
||||
getter.list(params).then((response) => {
|
||||
apiData = [...apiData, ...response.data.data];
|
||||
|
@@ -28,7 +28,7 @@ import i18next from "i18next";
|
||||
|
||||
Chart.register({SankeyController, Flow});
|
||||
|
||||
const SANKEY_CACHE_KEY = 'dashboard-sankey-data';
|
||||
const SANKEY_CACHE_KEY = 'ds_sankey_data';
|
||||
let currencies = [];
|
||||
let afterPromises = false;
|
||||
let chart = null;
|
||||
@@ -288,7 +288,7 @@ export default () => ({
|
||||
getFreshData() {
|
||||
const start = new Date(window.store.get('start'));
|
||||
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');
|
||||
let cachedData = window.store.get(cacheKey);
|
||||
@@ -312,7 +312,7 @@ export default () => ({
|
||||
downloadTransactions(params) {
|
||||
const start = new Date(window.store.get('start'));
|
||||
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 + '...');
|
||||
const getter = new Get();
|
||||
|
@@ -186,7 +186,7 @@ export default () => ({
|
||||
let end = new Date(window.store.get('end'));
|
||||
|
||||
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) {
|
||||
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 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;
|
||||
console.log('getCacheKey: ' + cacheKey);
|
||||
return String(cacheKey);
|
||||
|
||||
|
||||
function getCacheKey(string, params) {
|
||||
const lastActivity = store.get('lastActivity')
|
||||
let newParams = {lastActivity: lastActivity, key: string};
|
||||
|
||||
for (const key in params) {
|
||||
if (params.hasOwnProperty(key)) {
|
||||
if(params[key] === null || params[key] === undefined) {
|
||||
newParams[key] = '';
|
||||
continue;
|
||||
}
|
||||
if(params[key] instanceof Date) {
|
||||
newParams[key] = format(params[key], 'yMMdd');
|
||||
continue;
|
||||
}
|
||||
newParams[key] = params[key];
|
||||
}
|
||||
}
|
||||
return 'dcx_' + md5(JSON.stringify(newParams)).substring(0,12) + lastActivity;
|
||||
}
|
||||
|
||||
// Formatted version of a popular md5 implementation
|
||||
// Original copyright (c) Paul Johnston & Greg Holt.
|
||||
// The function itself is now 42 lines long.
|
||||
|
||||
function md5(inputString) {
|
||||
var hc="0123456789abcdef";
|
||||
function rh(n) {var j,s="";for(j=0;j<=3;j++) s+=hc.charAt((n>>(j*8+4))&0x0F)+hc.charAt((n>>(j*8))&0x0F);return s;}
|
||||
function ad(x,y) {var l=(x&0xFFFF)+(y&0xFFFF);var m=(x>>16)+(y>>16)+(l>>16);return (m<<16)|(l&0xFFFF);}
|
||||
function rl(n,c) {return (n<<c)|(n>>>(32-c));}
|
||||
function cm(q,a,b,x,s,t) {return ad(rl(ad(ad(a,q),ad(x,t)),s),b);}
|
||||
function ff(a,b,c,d,x,s,t) {return cm((b&c)|((~b)&d),a,b,x,s,t);}
|
||||
function gg(a,b,c,d,x,s,t) {return cm((b&d)|(c&(~d)),a,b,x,s,t);}
|
||||
function hh(a,b,c,d,x,s,t) {return cm(b^c^d,a,b,x,s,t);}
|
||||
function ii(a,b,c,d,x,s,t) {return cm(c^(b|(~d)),a,b,x,s,t);}
|
||||
function sb(x) {
|
||||
var i;var nblk=((x.length+8)>>6)+1;var blks=new Array(nblk*16);for(i=0;i<nblk*16;i++) blks[i]=0;
|
||||
for(i=0;i<x.length;i++) blks[i>>2]|=x.charCodeAt(i)<<((i%4)*8);
|
||||
blks[i>>2]|=0x80<<((i%4)*8);blks[nblk*16-2]=x.length*8;return blks;
|
||||
}
|
||||
var i,x=sb(""+inputString),a=1732584193,b=-271733879,c=-1732584194,d=271733878,olda,oldb,oldc,oldd;
|
||||
for(i=0;i<x.length;i+=16) {olda=a;oldb=b;oldc=c;oldd=d;
|
||||
a=ff(a,b,c,d,x[i+ 0], 7, -680876936);d=ff(d,a,b,c,x[i+ 1],12, -389564586);c=ff(c,d,a,b,x[i+ 2],17, 606105819);
|
||||
b=ff(b,c,d,a,x[i+ 3],22,-1044525330);a=ff(a,b,c,d,x[i+ 4], 7, -176418897);d=ff(d,a,b,c,x[i+ 5],12, 1200080426);
|
||||
c=ff(c,d,a,b,x[i+ 6],17,-1473231341);b=ff(b,c,d,a,x[i+ 7],22, -45705983);a=ff(a,b,c,d,x[i+ 8], 7, 1770035416);
|
||||
d=ff(d,a,b,c,x[i+ 9],12,-1958414417);c=ff(c,d,a,b,x[i+10],17, -42063);b=ff(b,c,d,a,x[i+11],22,-1990404162);
|
||||
a=ff(a,b,c,d,x[i+12], 7, 1804603682);d=ff(d,a,b,c,x[i+13],12, -40341101);c=ff(c,d,a,b,x[i+14],17,-1502002290);
|
||||
b=ff(b,c,d,a,x[i+15],22, 1236535329);a=gg(a,b,c,d,x[i+ 1], 5, -165796510);d=gg(d,a,b,c,x[i+ 6], 9,-1069501632);
|
||||
c=gg(c,d,a,b,x[i+11],14, 643717713);b=gg(b,c,d,a,x[i+ 0],20, -373897302);a=gg(a,b,c,d,x[i+ 5], 5, -701558691);
|
||||
d=gg(d,a,b,c,x[i+10], 9, 38016083);c=gg(c,d,a,b,x[i+15],14, -660478335);b=gg(b,c,d,a,x[i+ 4],20, -405537848);
|
||||
a=gg(a,b,c,d,x[i+ 9], 5, 568446438);d=gg(d,a,b,c,x[i+14], 9,-1019803690);c=gg(c,d,a,b,x[i+ 3],14, -187363961);
|
||||
b=gg(b,c,d,a,x[i+ 8],20, 1163531501);a=gg(a,b,c,d,x[i+13], 5,-1444681467);d=gg(d,a,b,c,x[i+ 2], 9, -51403784);
|
||||
c=gg(c,d,a,b,x[i+ 7],14, 1735328473);b=gg(b,c,d,a,x[i+12],20,-1926607734);a=hh(a,b,c,d,x[i+ 5], 4, -378558);
|
||||
d=hh(d,a,b,c,x[i+ 8],11,-2022574463);c=hh(c,d,a,b,x[i+11],16, 1839030562);b=hh(b,c,d,a,x[i+14],23, -35309556);
|
||||
a=hh(a,b,c,d,x[i+ 1], 4,-1530992060);d=hh(d,a,b,c,x[i+ 4],11, 1272893353);c=hh(c,d,a,b,x[i+ 7],16, -155497632);
|
||||
b=hh(b,c,d,a,x[i+10],23,-1094730640);a=hh(a,b,c,d,x[i+13], 4, 681279174);d=hh(d,a,b,c,x[i+ 0],11, -358537222);
|
||||
c=hh(c,d,a,b,x[i+ 3],16, -722521979);b=hh(b,c,d,a,x[i+ 6],23, 76029189);a=hh(a,b,c,d,x[i+ 9], 4, -640364487);
|
||||
d=hh(d,a,b,c,x[i+12],11, -421815835);c=hh(c,d,a,b,x[i+15],16, 530742520);b=hh(b,c,d,a,x[i+ 2],23, -995338651);
|
||||
a=ii(a,b,c,d,x[i+ 0], 6, -198630844);d=ii(d,a,b,c,x[i+ 7],10, 1126891415);c=ii(c,d,a,b,x[i+14],15,-1416354905);
|
||||
b=ii(b,c,d,a,x[i+ 5],21, -57434055);a=ii(a,b,c,d,x[i+12], 6, 1700485571);d=ii(d,a,b,c,x[i+ 3],10,-1894986606);
|
||||
c=ii(c,d,a,b,x[i+10],15, -1051523);b=ii(b,c,d,a,x[i+ 1],21,-2054922799);a=ii(a,b,c,d,x[i+ 8], 6, 1873313359);
|
||||
d=ii(d,a,b,c,x[i+15],10, -30611744);c=ii(c,d,a,b,x[i+ 6],15,-1560198380);b=ii(b,c,d,a,x[i+13],21, 1309151649);
|
||||
a=ii(a,b,c,d,x[i+ 4], 6, -145523070);d=ii(d,a,b,c,x[i+11],10,-1120210379);c=ii(c,d,a,b,x[i+ 2],15, 718787259);
|
||||
b=ii(b,c,d,a,x[i+ 9],21, -343485551);a=ad(a,olda);b=ad(b,oldb);c=ad(c,oldc);d=ad(d,oldd);
|
||||
}
|
||||
return rh(a)+rh(b)+rh(c)+rh(d);
|
||||
}
|
||||
|
||||
|
||||
|
||||
export {getCacheKey};
|
||||
|
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 class="row mb-3">
|
||||
<div class="col">
|
||||
Nav
|
||||
<div x-html="pageNavigation()">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
@@ -97,9 +97,9 @@
|
||||
<th x-show="tableColumns.active.visible && tableColumns.active.enabled">
|
||||
<a href="#" x-on:click.prevent="sort('active')">Active?</a>
|
||||
<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'"
|
||||
class="fa-solid fa-arrow-up-wide-short"></em>
|
||||
class="fa-solid fa-arrow-down-wide-short"></em>
|
||||
</th>
|
||||
<th x-show="tableColumns.name.visible && tableColumns.name.enabled">
|
||||
<a href="#" x-on:click.prevent="sort('name')">Name</a>
|
||||
@@ -121,16 +121,16 @@
|
||||
<th x-show="tableColumns.number.visible && tableColumns.number.enabled">
|
||||
<a href="#" x-on:click.prevent="sort('iban')">Account number</a>
|
||||
<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'"
|
||||
class="fa-solid fa-arrow-up-z-a"></em>
|
||||
class="fa-solid fa-arrow-down-z-a"></em>
|
||||
</th>
|
||||
<th x-show="tableColumns.current_balance.visible && tableColumns.current_balance.enabled">
|
||||
<a href="#" x-on:click.prevent="sort('balance')">Current balance</a>
|
||||
<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'"
|
||||
class="fa-solid fa-arrow-up-wide-short"></em>
|
||||
class="fa-solid fa-arrow-down-1-9"></em>
|
||||
</th>
|
||||
<th x-show="tableColumns.amount_due.visible && tableColumns.amount_due.enabled">
|
||||
<a href="#" x-on:click.prevent="sort('amount_due')">Amount due</a>
|
||||
@@ -156,6 +156,11 @@
|
||||
</th>
|
||||
<th x-show="tableColumns.menu.visible && tableColumns.menu.enabled"> </th>
|
||||
</tr>
|
||||
<tr x-show="pageOptions.isLoading">
|
||||
<td colspan="13" class="text-center">
|
||||
<span class="fa fa-spin fa-spinner"></span>
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template x-for="(account, index) in accounts" :key="index">
|
||||
@@ -237,7 +242,7 @@
|
||||
</td>
|
||||
<td x-show="tableColumns.current_balance.visible && tableColumns.current_balance.enabled">
|
||||
<span
|
||||
x-text="formatMoney(account.current_balance, account.currency_code)"></span>
|
||||
x-text="formatMoney(account.native_current_balance, account.currency_code)"></span>
|
||||
</td>
|
||||
<td x-show="tableColumns.amount_due.visible && tableColumns.amount_due.enabled">
|
||||
TODO
|
||||
@@ -289,7 +294,7 @@
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
Nav
|
||||
<div x-html="pageNavigation()">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -320,11 +325,11 @@
|
||||
<div class="row mb-3">
|
||||
<label for="inputEmail3" class="col-sm-4 col-form-label">Active accounts?</label>
|
||||
<div class="col-sm-8">
|
||||
<select x-model="filters.active" class="form-control">
|
||||
<option value="active" label="Active accounts">Active accounts only</option>
|
||||
<option value="inactive" label="Inactive accounts">Inactive accounts only
|
||||
<select @change="saveActiveFilter" class="form-control">
|
||||
<option value="active" :selected="true === filters.active" label="Active accounts">Active accounts only</option>
|
||||
<option value="inactive" :selected="false === filters.active" label="Inactive accounts">Inactive accounts only
|
||||
</option>
|
||||
<option value="both" label="Both">All accounts</option>
|
||||
<option value="both" :selected="null === filters.active" label="Both">All accounts</option>
|
||||
</select>
|
||||
<div id="emailHelp" class="form-text">TODO Bla bla bla.</div>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user