mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-10-15 16:57:09 +00:00
Improve account list and view.
This commit is contained in:
@@ -108,12 +108,7 @@ abstract class Controller extends BaseController
|
|||||||
{
|
{
|
||||||
$bag = new ParameterBag();
|
$bag = new ParameterBag();
|
||||||
$page = (int)request()->get('page');
|
$page = (int)request()->get('page');
|
||||||
if ($page < 1) {
|
$page = min(max(1, $page), 2 ** 16);
|
||||||
$page = 1;
|
|
||||||
}
|
|
||||||
if ($page > 2 ** 16) {
|
|
||||||
$page = 2 ** 16;
|
|
||||||
}
|
|
||||||
$bag->set('page', $page);
|
$bag->set('page', $page);
|
||||||
|
|
||||||
// some date fields:
|
// some date fields:
|
||||||
@@ -131,20 +126,16 @@ abstract class Controller extends BaseController
|
|||||||
$obj = null;
|
$obj = null;
|
||||||
if (null !== $date) {
|
if (null !== $date) {
|
||||||
try {
|
try {
|
||||||
$obj = Carbon::parse((string)$date);
|
$obj = Carbon::parse((string)$date, config('app.timezone'));
|
||||||
} catch (InvalidFormatException $e) {
|
} catch (InvalidFormatException $e) {
|
||||||
// don't care
|
// don't care
|
||||||
Log::warning(
|
Log::warning(sprintf('Ignored invalid date "%s" in API controller parameter check: %s', substr((string)$date, 0, 20), $e->getMessage()));
|
||||||
sprintf(
|
|
||||||
'Ignored invalid date "%s" in API controller parameter check: %s',
|
|
||||||
substr((string)$date, 0, 20),
|
|
||||||
$e->getMessage()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if($obj instanceof Carbon){
|
||||||
$bag->set($field, $obj);
|
$bag->set($field, $obj);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// integer fields:
|
// integer fields:
|
||||||
$integers = ['limit'];
|
$integers = ['limit'];
|
||||||
|
@@ -25,6 +25,7 @@ declare(strict_types=1);
|
|||||||
namespace FireflyIII\Api\V1\Controllers\Models\Account;
|
namespace FireflyIII\Api\V1\Controllers\Models\Account;
|
||||||
|
|
||||||
use FireflyIII\Api\V1\Controllers\Controller;
|
use FireflyIII\Api\V1\Controllers\Controller;
|
||||||
|
use FireflyIII\Api\V1\Requests\Models\Account\ShowRequest;
|
||||||
use FireflyIII\Exceptions\FireflyException;
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
use FireflyIII\Models\Account;
|
use FireflyIII\Models\Account;
|
||||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||||
@@ -33,7 +34,6 @@ use FireflyIII\Support\JsonApi\Enrichments\AccountEnrichment;
|
|||||||
use FireflyIII\Transformers\AccountTransformer;
|
use FireflyIII\Transformers\AccountTransformer;
|
||||||
use FireflyIII\User;
|
use FireflyIII\User;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Pagination\LengthAwarePaginator;
|
use Illuminate\Pagination\LengthAwarePaginator;
|
||||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
||||||
use League\Fractal\Resource\Collection as FractalCollection;
|
use League\Fractal\Resource\Collection as FractalCollection;
|
||||||
@@ -71,23 +71,22 @@ class ShowController extends Controller
|
|||||||
*
|
*
|
||||||
* @throws FireflyException
|
* @throws FireflyException
|
||||||
*/
|
*/
|
||||||
public function index(Request $request): JsonResponse
|
public function index(ShowRequest $request): JsonResponse
|
||||||
{
|
{
|
||||||
$manager = $this->getManager();
|
$manager = $this->getManager();
|
||||||
$type = $request->get('type') ?? 'all';
|
$params = $request->getParameters();
|
||||||
$this->parameters->set('type', $type);
|
$this->parameters->set('type', $params['type']);
|
||||||
|
|
||||||
// types to get, page size:
|
// types to get, page size:
|
||||||
$types = $this->mapAccountTypes($this->parameters->get('type'));
|
$types = $this->mapAccountTypes($params['type']);
|
||||||
$pageSize = $this->parameters->get('limit');
|
|
||||||
|
|
||||||
// get list of accounts. Count it and split it.
|
// get list of accounts. Count it and split it.
|
||||||
$this->repository->resetAccountOrder();
|
$this->repository->resetAccountOrder();
|
||||||
$collection = $this->repository->getAccountsByType($types, $this->parameters->get('sort') ?? []);
|
$collection = $this->repository->getAccountsByType($types, $params['sort']);
|
||||||
$count = $collection->count();
|
$count = $collection->count();
|
||||||
|
|
||||||
// continue sort:
|
// continue sort:
|
||||||
$accounts = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
|
$accounts = $collection->slice(($this->parameters->get('page') - 1) * $params['limit'], $params['limit']);
|
||||||
|
|
||||||
// enrich
|
// enrich
|
||||||
/** @var User $admin */
|
/** @var User $admin */
|
||||||
@@ -98,7 +97,7 @@ class ShowController extends Controller
|
|||||||
$accounts = $enrichment->enrich($accounts);
|
$accounts = $enrichment->enrich($accounts);
|
||||||
|
|
||||||
// make paginator:
|
// make paginator:
|
||||||
$paginator = new LengthAwarePaginator($accounts, $count, $pageSize, $this->parameters->get('page'));
|
$paginator = new LengthAwarePaginator($accounts, $count, $params['limit'], $this->parameters->get('page'));
|
||||||
$paginator->setPath(route('api.v1.accounts.index') . $this->buildParams());
|
$paginator->setPath(route('api.v1.accounts.index') . $this->buildParams());
|
||||||
|
|
||||||
/** @var AccountTransformer $transformer */
|
/** @var AccountTransformer $transformer */
|
||||||
|
96
app/Api/V1/Requests/Models/Account/ShowRequest.php
Normal file
96
app/Api/V1/Requests/Models/Account/ShowRequest.php
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* ShowRequest.php
|
||||||
|
* Copyright (c) 2025 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace FireflyIII\Api\V1\Requests\Models\Account;
|
||||||
|
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use FireflyIII\Models\Preference;
|
||||||
|
use FireflyIII\Support\Facades\Preferences;
|
||||||
|
use FireflyIII\Support\Http\Api\AccountFilter;
|
||||||
|
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||||
|
use FireflyIII\User;
|
||||||
|
use Illuminate\Contracts\Validation\Validator;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class ShowRequest extends FormRequest
|
||||||
|
{
|
||||||
|
use ConvertsDataTypes;
|
||||||
|
use AccountFilter;
|
||||||
|
|
||||||
|
public function getParameters(): array
|
||||||
|
{
|
||||||
|
$limit = $this->convertInteger('limit');
|
||||||
|
if (0 === $limit) {
|
||||||
|
// get default for user:
|
||||||
|
/** @var User $user */
|
||||||
|
$user = auth()->user();
|
||||||
|
|
||||||
|
/** @var Preference $pageSize */
|
||||||
|
$limit = (int)Preferences::getForUser($user, 'listPageSize', 50)->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
$page = $this->convertInteger('page');
|
||||||
|
$page = min(max(1, $page), 2 ** 16);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'type' => $this->convertString('type', 'all'),
|
||||||
|
'limit' => $limit,
|
||||||
|
'sort' => $this->convertString('sort', 'order'),
|
||||||
|
'page' => $page,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
$keys = join(',', array_keys($this->types));
|
||||||
|
return [
|
||||||
|
'date' => 'date',
|
||||||
|
'start' => 'date|present_with:end|before_or_equal:end|before:2038-01-17|after:1970-01-02',
|
||||||
|
'end' => 'date|present_with:start|after_or_equal:start|before:2038-01-17|after:1970-01-02',
|
||||||
|
'sort' => 'in:active,iban,name,order,-active,-iban,-name,-order', // TODO improve me.
|
||||||
|
'type' => sprintf('in:%s', $keys),
|
||||||
|
'limit' => 'number|min:1|max:131337',
|
||||||
|
'page' => 'number|min:1|max:131337',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withValidator(Validator $validator): void
|
||||||
|
{
|
||||||
|
$validator->after(
|
||||||
|
function (Validator $validator): void {
|
||||||
|
if ($validator->failed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$data = $validator->getData();
|
||||||
|
if (array_key_exists('date', $data) && array_key_exists('start', $data) && array_key_exists('end', $data)) {
|
||||||
|
// assume valid dates, before we got here.
|
||||||
|
$start = Carbon::parse($data['start'], config('app.timezone'))->startOfDay();
|
||||||
|
$end = Carbon::parse($data['end'], config('app.timezone'))->endOfDay();
|
||||||
|
$date = Carbon::parse($data['date'], config('app.timezone'));
|
||||||
|
if (!$date->between($start, $end)) {
|
||||||
|
$validator->errors()->add('date', (string)trans('validation.between_date'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -78,7 +78,7 @@ class StoreRequest extends FormRequest
|
|||||||
'object_group_id' => 'numeric|belongsToUser:object_groups,id',
|
'object_group_id' => 'numeric|belongsToUser:object_groups,id',
|
||||||
'object_group_title' => ['min:1', 'max:255'],
|
'object_group_title' => ['min:1', 'max:255'],
|
||||||
'target_amount' => ['required', new IsValidZeroOrMoreAmount()],
|
'target_amount' => ['required', new IsValidZeroOrMoreAmount()],
|
||||||
'start_date' => 'date|nullable',
|
'start_date' => 'required|date|after:1970-01-01|before:2038-01-17',
|
||||||
'transaction_currency_id' => 'exists:transaction_currencies,id|required_without:transaction_currency_code',
|
'transaction_currency_id' => 'exists:transaction_currencies,id|required_without:transaction_currency_code',
|
||||||
'transaction_currency_code' => 'exists:transaction_currencies,code|required_without:transaction_currency_id',
|
'transaction_currency_code' => 'exists:transaction_currencies,code|required_without:transaction_currency_id',
|
||||||
'target_date' => 'date|nullable|after:start_date',
|
'target_date' => 'date|nullable|after:start_date',
|
||||||
|
@@ -32,6 +32,7 @@ import {showWizardButton} from "../../support/page-settings/show-wizard-button.j
|
|||||||
import {setVariable} from "../../store/set-variable.js";
|
import {setVariable} from "../../store/set-variable.js";
|
||||||
import {getVariables} from "../../store/get-variables.js";
|
import {getVariables} from "../../store/get-variables.js";
|
||||||
import pageNavigation from "../../support/page-navigation.js";
|
import pageNavigation from "../../support/page-navigation.js";
|
||||||
|
import {getVariable} from "../../store/get-variable.js";
|
||||||
|
|
||||||
|
|
||||||
// set type from URL
|
// set type from URL
|
||||||
@@ -56,12 +57,12 @@ if(sortingColumn[0] === '-') {
|
|||||||
|
|
||||||
page = parseInt(params.page ?? 1);
|
page = parseInt(params.page ?? 1);
|
||||||
|
|
||||||
|
|
||||||
showInternalsButton();
|
showInternalsButton();
|
||||||
showWizardButton();
|
showWizardButton();
|
||||||
|
|
||||||
// TODO currency conversion
|
// TODO currency conversion
|
||||||
// TODO page cleanup and recycle for transaction lists.
|
// TODO page cleanup and recycle for transaction lists.
|
||||||
|
// TODO process startPeriod and endPeriod.
|
||||||
|
|
||||||
let index = function () {
|
let index = function () {
|
||||||
return {
|
return {
|
||||||
@@ -73,9 +74,9 @@ let index = function () {
|
|||||||
show: false, text: '', url: '',
|
show: false, text: '', url: '',
|
||||||
}, wait: {
|
}, wait: {
|
||||||
show: false, text: '',
|
show: false, text: '',
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
convertToPrimary: false,
|
||||||
totalPages: 1,
|
totalPages: 1,
|
||||||
page: 1,
|
page: 1,
|
||||||
pageUrl: '',
|
pageUrl: '',
|
||||||
@@ -259,6 +260,7 @@ let index = function () {
|
|||||||
{name: this.getPreferenceKey('sd'), default: ''},
|
{name: this.getPreferenceKey('sd'), default: ''},
|
||||||
{name: this.getPreferenceKey('filters'), default: this.filters},
|
{name: this.getPreferenceKey('filters'), default: this.filters},
|
||||||
{name: this.getPreferenceKey('grouped'), default: true},
|
{name: this.getPreferenceKey('grouped'), default: true},
|
||||||
|
{name: 'convert_to_primary', default: false},
|
||||||
]).then((res) => {
|
]).then((res) => {
|
||||||
// process columns:
|
// process columns:
|
||||||
for (let k in res[0]) {
|
for (let k in res[0]) {
|
||||||
@@ -283,6 +285,9 @@ let index = function () {
|
|||||||
// group accounts
|
// group accounts
|
||||||
this.pageOptions.groupedAccounts = res[4];
|
this.pageOptions.groupedAccounts = res[4];
|
||||||
|
|
||||||
|
// convert to primary?
|
||||||
|
this.convertToPrimary = res[5];
|
||||||
|
|
||||||
this.loadAccounts();
|
this.loadAccounts();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -348,7 +353,6 @@ let index = function () {
|
|||||||
if('asc' === this.pageOptions.sortDirection && '' !== sorting) {
|
if('asc' === this.pageOptions.sortDirection && '' !== sorting) {
|
||||||
sorting = '-' + sorting;
|
sorting = '-' + sorting;
|
||||||
}
|
}
|
||||||
//const sorting = [{column: this.pageOptions.sortingColumn, direction: this.pageOptions.sortDirection}];
|
|
||||||
|
|
||||||
// filter instructions
|
// filter instructions
|
||||||
let filters = {};
|
let filters = {};
|
||||||
@@ -371,20 +375,20 @@ let index = function () {
|
|||||||
sort: sorting,
|
sort: sorting,
|
||||||
filter: filters,
|
filter: filters,
|
||||||
active: active,
|
active: active,
|
||||||
currentMoment: today,
|
date: format(today,'yyyy-MM-dd'),
|
||||||
type: type,
|
type: type,
|
||||||
page: this.page,
|
page: this.page,
|
||||||
startPeriod: start,
|
// startPeriod: start,
|
||||||
endPeriod: end
|
// endPeriod: end
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!this.tableColumns.balance_difference.enabled) {
|
if (!this.tableColumns.balance_difference.enabled) {
|
||||||
delete params.startPeriod;
|
// delete params.startPeriod;
|
||||||
delete params.endPeriod;
|
// delete params.endPeriod;
|
||||||
}
|
}
|
||||||
this.accounts = [];
|
this.accounts = [];
|
||||||
let groupedAccounts = {};
|
let groupedAccounts = {};
|
||||||
// one page only.o
|
// one page only
|
||||||
(new Get()).index(params).then(response => {
|
(new Get()).index(params).then(response => {
|
||||||
this.totalPages = response.meta.pagination.total_pages;
|
this.totalPages = response.meta.pagination.total_pages;
|
||||||
for (let i = 0; i < response.data.length; i++) {
|
for (let i = 0; i < response.data.length; i++) {
|
||||||
@@ -406,9 +410,9 @@ let index = function () {
|
|||||||
liability_direction: current.attributes.liability_direction,
|
liability_direction: current.attributes.liability_direction,
|
||||||
interest: current.attributes.interest,
|
interest: current.attributes.interest,
|
||||||
interest_period: current.attributes.interest_period,
|
interest_period: current.attributes.interest_period,
|
||||||
balance: current.attributes.balance,
|
// balance: current.attributes.balance,
|
||||||
pc_balance: current.attributes.pc_balance,
|
// pc_balance: current.attributes.pc_balance,
|
||||||
balances: current.attributes.balances,
|
// balances: current.attributes.balances,
|
||||||
};
|
};
|
||||||
// get group info:
|
// get group info:
|
||||||
let groupId = current.attributes.object_group_id;
|
let groupId = current.attributes.object_group_id;
|
||||||
@@ -426,10 +430,9 @@ let index = function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
groupedAccounts[groupId].accounts.push(account);
|
groupedAccounts[groupId].accounts.push(account);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//this.accounts.push(account);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// order grouped accounts by order.
|
// order grouped accounts by order.
|
||||||
let sortable = [];
|
let sortable = [];
|
||||||
for (let set in groupedAccounts) {
|
for (let set in groupedAccounts) {
|
||||||
|
@@ -119,6 +119,7 @@ return [
|
|||||||
'between.file' => 'The :attribute must be between :min and :max kilobytes.',
|
'between.file' => 'The :attribute must be between :min and :max kilobytes.',
|
||||||
'between.string' => 'The :attribute must be between :min and :max characters.',
|
'between.string' => 'The :attribute must be between :min and :max characters.',
|
||||||
'between.array' => 'The :attribute must have between :min and :max items.',
|
'between.array' => 'The :attribute must have between :min and :max items.',
|
||||||
|
'between_date' => 'The date must be between the given start and end date.',
|
||||||
'boolean' => 'The :attribute field must be true or false.',
|
'boolean' => 'The :attribute field must be true or false.',
|
||||||
'confirmed' => 'The :attribute confirmation does not match.',
|
'confirmed' => 'The :attribute confirmation does not match.',
|
||||||
'date' => 'The :attribute is not a valid date.',
|
'date' => 'The :attribute is not a valid date.',
|
||||||
|
@@ -441,7 +441,7 @@
|
|||||||
<td>
|
<td>
|
||||||
{% for tag in journal.tags %}
|
{% for tag in journal.tags %}
|
||||||
<h4 style="display: inline;"><a class="label label-success"
|
<h4 style="display: inline;"><a class="label label-success"
|
||||||
href="{{ route('tags.show', tag.id) }}">
|
href="{{ route('tags.show', [tag.id]) }}">
|
||||||
<span class="fa fa-fw fa-tag"></span>{{ tag.tag }}</a>
|
<span class="fa fa-fw fa-tag"></span>{{ tag.tag }}</a>
|
||||||
</h4>
|
</h4>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@@ -10,7 +10,7 @@
|
|||||||
<h3 class="card-title">{{ __('firefly.net_worth') }}</h3>
|
<h3 class="card-title">{{ __('firefly.net_worth') }}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
TODO
|
Not yet implemented.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -20,17 +20,17 @@
|
|||||||
<h3 class="card-title">{{ __('firefly.in_out_period') }}</h3>
|
<h3 class="card-title">{{ __('firefly.in_out_period') }}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
TODO
|
Not yet implemented.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xl-4 col-lg-6 col-md-12 col-sm-12 col-xs-12">
|
<div class="col-xl-4 col-lg-6 col-md-12 col-sm-12 col-xs-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title">TODO</h3>
|
<h3 class="card-title">Not yet implemented.</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
TODO
|
Not yet implemented.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -262,17 +262,14 @@
|
|||||||
</template>
|
</template>
|
||||||
</td>
|
</td>
|
||||||
<td x-show="tableColumns.current_balance.visible && tableColumns.current_balance.enabled">
|
<td x-show="tableColumns.current_balance.visible && tableColumns.current_balance.enabled">
|
||||||
<template x-if="null !== account.balance">
|
<span x-show="parseFloat(account.current_balance) < 0.0" class="text-danger"
|
||||||
<template x-for="balance in account.balance">
|
x-text="formatMoney(account.current_balance, account.currency_code)"></span>
|
||||||
<span>
|
<span x-show="parseFloat(account.current_balance) === 0.0" class="text-muted"
|
||||||
<span x-show="parseFloat(balance.balance) < 0.0" class="text-danger"
|
x-text="formatMoney(account.current_balance, account.currency_code)"></span>
|
||||||
x-text="formatMoney(balance.balance, balance.currency_code)"></span>
|
<span x-show="parseFloat(account.current_balance) > 0.0" class="text-success"
|
||||||
<span x-show="parseFloat(balance.balance) === 0.0" class="text-muted"
|
x-text="formatMoney(account.current_balance, account.currency_code)"></span>
|
||||||
x-text="formatMoney(balance.balance, balance.currency_code)"></span>
|
<template x-if="null !== account.pc_current_balance">
|
||||||
<span x-show="parseFloat(balance.balance) > 0.0" class="text-success"
|
<span>PC current balance TODO.</span>
|
||||||
x-text="formatMoney(balance.balance, balance.currency_code)"></span>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</template>
|
</template>
|
||||||
</td>
|
</td>
|
||||||
<td x-show="tableColumns.amount_due.visible && tableColumns.amount_due.enabled">
|
<td x-show="tableColumns.amount_due.visible && tableColumns.amount_due.enabled">
|
||||||
|
Reference in New Issue
Block a user