mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-02-25 18:45:27 -06:00
First version of line edit.
This commit is contained in:
parent
845eaed8d7
commit
f0fa21dead
81
app/Api/V2/Controllers/Model/Account/UpdateController.php
Normal file
81
app/Api/V2/Controllers/Model/Account/UpdateController.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/*
|
||||
* UpdateController.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\Api\V2\Controllers\Model\Account;
|
||||
|
||||
use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Api\V2\Request\Model\Account\UpdateRequest;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Transformers\V2\AccountTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class UpdateController extends Controller
|
||||
{
|
||||
public const string RESOURCE_KEY = 'accounts';
|
||||
|
||||
private AccountRepositoryInterface $repository;
|
||||
|
||||
/**
|
||||
* AccountController constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
$this->repository = app(AccountRepositoryInterface::class);
|
||||
// new way of user group validation
|
||||
$userGroup = $this->validateUserGroup($request);
|
||||
if (null !== $userGroup) {
|
||||
$this->repository->setUserGroup($userGroup);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO this endpoint is not yet reachable.
|
||||
*/
|
||||
public function update(UpdateRequest $request, Account $account): JsonResponse
|
||||
{
|
||||
app('log')->debug(sprintf('Now in %s', __METHOD__));
|
||||
$data = $request->getUpdateData();
|
||||
$data['type'] = config('firefly.shortNamesByFullName.'.$account->accountType->type);
|
||||
$account = $this->repository->update($account, $data);
|
||||
$account->refresh();
|
||||
app('preferences')->mark();
|
||||
|
||||
$transformer = new AccountTransformer();
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
||||
return response()
|
||||
->api($this->jsonApiObject('accounts', $account, $transformer))
|
||||
->header('Content-Type', self::CONTENT_TYPE)
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
}
|
119
app/Api/V2/Request/Model/Account/UpdateRequest.php
Normal file
119
app/Api/V2/Request/Model/Account/UpdateRequest.php
Normal file
@ -0,0 +1,119 @@
|
||||
<?php
|
||||
/*
|
||||
* UpdateRequest.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\Api\V2\Request\Model\Account;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\Location;
|
||||
use FireflyIII\Rules\IsBoolean;
|
||||
use FireflyIII\Rules\UniqueAccountNumber;
|
||||
use FireflyIII\Rules\UniqueIban;
|
||||
use FireflyIII\Support\Request\AppendsLocationData;
|
||||
use FireflyIII\Support\Request\ChecksLogin;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateRequest extends FormRequest
|
||||
{
|
||||
use ChecksLogin;
|
||||
use AppendsLocationData;
|
||||
use ConvertsDataTypes;
|
||||
|
||||
/**
|
||||
* TODO is a duplicate of the v1 update thing.
|
||||
* @return array
|
||||
*/
|
||||
public function getUpdateData(): array
|
||||
{
|
||||
$fields = [
|
||||
'name' => ['name', 'convertString'],
|
||||
'active' => ['active', 'boolean'],
|
||||
'include_net_worth' => ['include_net_worth', 'boolean'],
|
||||
'account_type_name' => ['type', 'convertString'],
|
||||
'virtual_balance' => ['virtual_balance', 'convertString'],
|
||||
'iban' => ['iban', 'convertString'],
|
||||
'BIC' => ['bic', 'convertString'],
|
||||
'account_number' => ['account_number', 'convertString'],
|
||||
'account_role' => ['account_role', 'convertString'],
|
||||
'liability_type' => ['liability_type', 'convertString'],
|
||||
'opening_balance' => ['opening_balance', 'convertString'],
|
||||
'opening_balance_date' => ['opening_balance_date', 'convertDateTime'],
|
||||
'cc_type' => ['credit_card_type', 'convertString'],
|
||||
'cc_monthly_payment_date' => ['monthly_payment_date', 'convertDateTime'],
|
||||
'notes' => ['notes', 'stringWithNewlines'],
|
||||
'interest' => ['interest', 'convertString'],
|
||||
'interest_period' => ['interest_period', 'convertString'],
|
||||
'order' => ['order', 'convertInteger'],
|
||||
'currency_id' => ['currency_id', 'convertInteger'],
|
||||
'currency_code' => ['currency_code', 'convertString'],
|
||||
'liability_direction' => ['liability_direction', 'convertString'],
|
||||
'liability_amount' => ['liability_amount', 'convertString'],
|
||||
'liability_start_date' => ['liability_start_date', 'date'],
|
||||
];
|
||||
$data = $this->getAllData($fields);
|
||||
|
||||
return $this->appendLocationData($data, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO is a duplicate of the v1 UpdateRequest method.
|
||||
*
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
/** @var Account $account */
|
||||
$account = $this->route()->parameter('account');
|
||||
$accountRoles = implode(',', config('firefly.accountRoles'));
|
||||
$types = implode(',', array_keys(config('firefly.subTitlesByIdentifier')));
|
||||
$ccPaymentTypes = implode(',', array_keys(config('firefly.ccTypes')));
|
||||
|
||||
$rules = [
|
||||
'name' => sprintf('min:1|max:1024|uniqueAccountForUser:%d', $account->id),
|
||||
'type' => sprintf('in:%s', $types),
|
||||
'iban' => ['iban', 'nullable', new UniqueIban($account, $this->convertString('type'))],
|
||||
'bic' => 'bic|nullable',
|
||||
'account_number' => ['min:1', 'max:255', 'nullable', new UniqueAccountNumber($account, $this->convertString('type'))],
|
||||
'opening_balance' => 'numeric|required_with:opening_balance_date|nullable',
|
||||
'opening_balance_date' => 'date|required_with:opening_balance|nullable',
|
||||
'virtual_balance' => 'numeric|nullable',
|
||||
'order' => 'numeric|nullable',
|
||||
'currency_id' => 'numeric|exists:transaction_currencies,id',
|
||||
'currency_code' => 'min:3|max:51|exists:transaction_currencies,code',
|
||||
'active' => [new IsBoolean()],
|
||||
'include_net_worth' => [new IsBoolean()],
|
||||
'account_role' => sprintf('in:%s|nullable|required_if:type,asset', $accountRoles),
|
||||
'credit_card_type' => sprintf('in:%s|nullable|required_if:account_role,ccAsset', $ccPaymentTypes),
|
||||
'monthly_payment_date' => 'date|nullable|required_if:account_role,ccAsset|required_if:credit_card_type,monthlyFull',
|
||||
'liability_type' => 'required_if:type,liability|in:loan,debt,mortgage',
|
||||
'liability_direction' => 'required_if:type,liability|in:credit,debit',
|
||||
'interest' => 'required_if:type,liability|min:0|max:100|numeric',
|
||||
'interest_period' => 'required_if:type,liability|in:daily,monthly,yearly',
|
||||
'notes' => 'min:0|max:32768',
|
||||
];
|
||||
|
||||
return Location::requestRules($rules);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -28,6 +28,7 @@ use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountMeta;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Services\Internal\Update\AccountUpdateService;
|
||||
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Support\Collection;
|
||||
@ -291,4 +292,12 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
|
||||
return $dbQuery->take($limit)->get(['accounts.*']);
|
||||
}
|
||||
|
||||
#[\Override] public function update(Account $account, array $data): Account
|
||||
{
|
||||
/** @var AccountUpdateService $service */
|
||||
$service = app(AccountUpdateService::class);
|
||||
|
||||
return $service->update($account, $data);
|
||||
}
|
||||
}
|
||||
|
@ -72,5 +72,7 @@ interface AccountRepositoryInterface
|
||||
|
||||
public function setUser(User $user): void;
|
||||
|
||||
public function update(Account $account,array $data): Account;
|
||||
|
||||
public function setUserGroup(UserGroup $userGroup): void;
|
||||
}
|
||||
|
36
resources/assets/v2/api/v2/model/account/put.js
Normal file
36
resources/assets/v2/api/v2/model/account/put.js
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* list.js
|
||||
* Copyright (c) 2022 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {api} from "../../../../boot/axios";
|
||||
import format from "date-fns/format";
|
||||
|
||||
export default class Put {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param identifier
|
||||
* @param params
|
||||
* @returns {Promise<AxiosResponse<any>>}
|
||||
*/
|
||||
put(identifier, params) {
|
||||
return api.put('/api/v2/accounts/' + identifier, params);
|
||||
}
|
||||
|
||||
}
|
@ -28,6 +28,8 @@ 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 GenericEditor from "../../support/editable/GenericEditor.js";
|
||||
import Put from "../../api/v2/model/account/put.js";
|
||||
|
||||
// set type from URL
|
||||
const urlParts = window.location.href.split('/');
|
||||
@ -51,6 +53,7 @@ let index = function () {
|
||||
enabled: true
|
||||
},
|
||||
},
|
||||
editors: {},
|
||||
sortingColumn: '',
|
||||
sortDirection: '',
|
||||
accounts: [],
|
||||
@ -75,7 +78,36 @@ let index = function () {
|
||||
this.notifications.wait.text = i18next.t('firefly.wait_loading_data')
|
||||
this.loadAccounts();
|
||||
},
|
||||
|
||||
submitInlineEdit(e) {
|
||||
e.preventDefault();
|
||||
const newTarget = e.currentTarget;
|
||||
const index = newTarget.dataset.index;
|
||||
const newValue = document.querySelectorAll('[data-index="'+index+'input"]')[0].value ?? '';
|
||||
if('' === newValue) {
|
||||
return;
|
||||
}
|
||||
// submit the field in an update thing?
|
||||
const fieldName = this.editors[index].options.field;
|
||||
const params = {};
|
||||
params[fieldName] = newValue;
|
||||
console.log(params);
|
||||
console.log('New value is ' + newValue + ' for account #' + this.editors[index].options.id);
|
||||
(new Put()).put(this.editors[index].options.id, params);
|
||||
},
|
||||
cancelInlineEdit(e) {
|
||||
const newTarget = e.currentTarget;
|
||||
const index = newTarget.dataset.index;
|
||||
this.editors[index].cancel();
|
||||
},
|
||||
triggerEdit(e) {
|
||||
const target = e.currentTarget;
|
||||
const index = target.dataset.index;
|
||||
// get parent:
|
||||
this.editors[index] = new GenericEditor();
|
||||
this.editors[index].setElement(target);
|
||||
this.editors[index].init();
|
||||
this.editors[index].replace();
|
||||
},
|
||||
loadAccounts() {
|
||||
this.notifications.wait.show = true;
|
||||
this.notifications.wait.text = i18next.t('firefly.wait_loading_data')
|
||||
@ -100,13 +132,13 @@ let index = function () {
|
||||
currency_code: current.attributes.currency_code,
|
||||
native_current_balance: current.attributes.native_current_balance,
|
||||
native_currency_code: current.attributes.native_currency_code,
|
||||
last_activity: null === current.attributes.last_activity ? '' : format(new Date(current.attributes.last_activity),'P'),
|
||||
last_activity: null === current.attributes.last_activity ? '' : format(new Date(current.attributes.last_activity), 'P'),
|
||||
};
|
||||
this.accounts.push(account);
|
||||
}
|
||||
}
|
||||
this.notifications.wait.show = false;
|
||||
|
||||
// add click trigger thing.
|
||||
});
|
||||
},
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ $color-mode-type: media-query;
|
||||
$link-decoration: none !default;
|
||||
$font-family-sans-serif: "Roboto", sans-serif;
|
||||
|
||||
$danger: #CD5029 !default;
|
||||
$danger: #CD5029 !default;
|
||||
$primary: #1E6581 !default;
|
||||
$success: #64B624 !default;
|
||||
|
||||
@ -43,7 +43,13 @@ $success: #64B624 !default;
|
||||
|
||||
// @import "~bootstrap-sass/assets/stylesheets/bootstrap";
|
||||
|
||||
|
||||
// hover buttons
|
||||
.hidden-edit-button {
|
||||
cursor: pointer;
|
||||
}
|
||||
td:not(:hover) .hidden-edit-button {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
107
resources/assets/v2/support/editable/GenericEditor.js
Normal file
107
resources/assets/v2/support/editable/GenericEditor.js
Normal file
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* GenericEditor.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/.
|
||||
*/
|
||||
|
||||
export default class GenericEditor {
|
||||
setElement(element) {
|
||||
console.log('GenericEditor.setElement()', element);
|
||||
this.element = element;
|
||||
this.parent = element.parentElement;
|
||||
this.options = {};
|
||||
}
|
||||
|
||||
init() {
|
||||
// grab some options from element itself:
|
||||
this.options.type = this.element.dataset.type;
|
||||
this.options.id = this.element.dataset.id;
|
||||
this.options.value = this.element.dataset.value;
|
||||
this.options.index = this.element.dataset.index;
|
||||
this.options.model = this.element.dataset.model;
|
||||
this.options.field = this.element.dataset.field;
|
||||
//this.options.field = this.element.dataset.type;
|
||||
console.log('GenericEditor['+this.options.index+'].init()');
|
||||
}
|
||||
|
||||
replace() {
|
||||
console.log('GenericEditor['+this.options.index+'].replace()');
|
||||
// save old HTML in data field (does that work, is it safe?)
|
||||
this.options.original = this.element.parentElement.innerHTML;
|
||||
if (this.options.type === 'text') {
|
||||
this.replaceText();
|
||||
}
|
||||
}
|
||||
|
||||
replaceText() {
|
||||
console.log('GenericEditor['+this.options.index+'].replaceText()');
|
||||
let html = this.formStart() + this.rowStart();
|
||||
|
||||
// input field:
|
||||
html += this.columnStart('7') + this.label() + this.textField() + this.closeDiv();
|
||||
|
||||
// add submit button
|
||||
html += this.columnStart('5') + this.buttonGroup() + this.closeDiv();
|
||||
|
||||
// close column and form:
|
||||
html += this.closeDiv() + this.closeForm();
|
||||
this.element.parentElement.innerHTML = html;
|
||||
}
|
||||
textField() {
|
||||
return '<input data-index="' + this.options.index + 'input" autocomplete="off" type="text" class="form-control form-control-sm" id="input" name="name" value="' + this.options.value + '" placeholder="' + this.options.value + '" autofocus>';
|
||||
}
|
||||
closeDiv() {
|
||||
return '</div>';
|
||||
}
|
||||
closeForm() {
|
||||
return '</form>';
|
||||
}
|
||||
|
||||
formStart() {
|
||||
return '<form class="form-inline">';
|
||||
}
|
||||
|
||||
rowStart() {
|
||||
return '<div class="row">';
|
||||
}
|
||||
|
||||
columnStart(param) {
|
||||
if ('' === param) {
|
||||
return '<div class="col">';
|
||||
}
|
||||
return '<div class="col-' + param + '">';
|
||||
}
|
||||
label() {
|
||||
return '<label class="sr-only" for="input">Field value</label>';
|
||||
}
|
||||
|
||||
buttonGroup() {
|
||||
return '<div class="btn-group btn-group-sm" role="group" aria-label="Options">'+
|
||||
'<button data-index="'+this.options.index+'" type="button" @click="cancelInlineEdit" class="btn btn-danger"><em class="fa-solid fa-xmark text-white"></em></button>'+
|
||||
'<button data-index="'+this.options.index+'" type="submit" @click="submitInlineEdit" class="btn btn-success"><em class="fa-solid fa-check"></em></button>' +
|
||||
'</div>';
|
||||
}
|
||||
cancel() {
|
||||
console.log('GenericEditor['+this.options.index+'].cancel()');
|
||||
console.log(this.element);
|
||||
console.log(this.parent);
|
||||
this.parent.innerHTML = this.options.original;
|
||||
}
|
||||
submitInlineEdit(e) {
|
||||
console.log('Submit?');
|
||||
}
|
||||
}
|
@ -99,6 +99,7 @@
|
||||
<a :href="'./accounts/show/' + account.id">
|
||||
<span x-text="account.name"></span>
|
||||
</a>
|
||||
<em :data-index="account.id + 'name'" @click="triggerEdit" data-type="text" data-model="Account" :data-id="account.id" data-field="name" :data-value="account.name" class="hidden-edit-button inline-edit-button fa-solid fa-pencil" data-id="1"></em>
|
||||
</td>
|
||||
<td>
|
||||
<span x-text="account.type"></span>
|
||||
@ -147,6 +148,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@endsection
|
||||
@section('scripts')
|
||||
@vite(['resources/assets/v2/pages/accounts/index.js'])
|
||||
|
@ -106,6 +106,7 @@ Route::group(
|
||||
static function (): void {
|
||||
Route::get('', ['uses' => 'IndexController@index', 'as' => 'index']);
|
||||
Route::get('{account}', ['uses' => 'ShowController@show', 'as' => 'show']);
|
||||
Route::put('{account}', ['uses' => 'UpdateController@update', 'as' => 'update']);
|
||||
}
|
||||
);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user