diff --git a/app/Http/Controllers/Account/ReconcileController.php b/app/Http/Controllers/Account/ReconcileController.php index c16ce9facd..85e47b0be9 100644 --- a/app/Http/Controllers/Account/ReconcileController.php +++ b/app/Http/Controllers/Account/ReconcileController.php @@ -130,7 +130,6 @@ class ReconcileController extends Controller $startDate = clone $start; $startDate->subDay(); $startBalance = round(app('steam')->balance($account, $startDate), $currency->decimal_places); - $endBalance = round(app('steam')->balance($account, $end), $currency->decimal_places); $subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $account->accountType->type)); $subTitle = (string) trans('firefly.reconcile_account', ['account' => $account->name]); diff --git a/app/Http/Controllers/ObjectGroup/DeleteController.php b/app/Http/Controllers/ObjectGroup/DeleteController.php new file mode 100644 index 0000000000..293d953ba6 --- /dev/null +++ b/app/Http/Controllers/ObjectGroup/DeleteController.php @@ -0,0 +1,70 @@ +middleware( + function ($request, $next) { + app('view')->share('mainTitleIcon', 'fa-envelope-o'); + app('view')->share('title', (string) trans('firefly.object_groups_page_title')); + + $this->repository = app(ObjectGroupRepositoryInterface::class); + + return $next($request); + } + ); + } + + /** + * Delete a piggy bank. + * + * @param ObjectGroup $objectGroup + */ + public function delete(ObjectGroup $objectGroup) + { + $subTitle = (string) trans('firefly.delete_object_group', ['title' => $objectGroup->title]); + $piggyBanks = $objectGroup->piggyBanks()->count(); + + // put previous url in session + $this->rememberPreviousUri('object-groups.delete.uri'); + + return view('object-groups.delete', compact('objectGroup', 'subTitle', 'piggyBanks')); + } + + /** + * Destroy the piggy bank. + * + * @param ObjectGroup $objectGroup + */ + public function destroy(ObjectGroup $objectGroup): RedirectResponse + { + session()->flash('success', (string) trans('firefly.deleted_object_group', ['title' => $objectGroup->title])); + app('preferences')->mark(); + $this->repository->destroy($objectGroup); + + return redirect($this->getPreviousUri('object-groups.delete.uri')); + } + +} diff --git a/app/Http/Controllers/ObjectGroup/EditController.php b/app/Http/Controllers/ObjectGroup/EditController.php new file mode 100644 index 0000000000..d760007802 --- /dev/null +++ b/app/Http/Controllers/ObjectGroup/EditController.php @@ -0,0 +1,87 @@ +middleware( + function ($request, $next) { + app('view')->share('mainTitleIcon', 'fa-envelope-o'); + app('view')->share('title', (string) trans('firefly.object_groups_page_title')); + + $this->repository = app(ObjectGroupRepositoryInterface::class); + + return $next($request); + } + ); + } + + /** + * Edit an object group. + * + * @param ObjectGroup $objectGroup + */ + public function edit(ObjectGroup $objectGroup) + { + $subTitle = (string) trans('firefly.edit_object_group', ['title' => $objectGroup->title]); + $subTitleIcon = 'fa-pencil'; + $targetDate = null; + $startDate = null; + + if (true !== session('object-groups.edit.fromUpdate')) { + $this->rememberPreviousUri('object-groups.edit.uri'); + } + session()->forget('object-groups.edit.fromUpdate'); + + return view('object-groups.edit', compact('subTitle', 'subTitleIcon', 'objectGroup')); + } + + + /** + * Update a piggy bank. + * + * @param ObjectGroupFormRequest $request + * @param ObjectGroup $objectGroup + */ + public function update(ObjectGroupFormRequest $request, ObjectGroup $objectGroup) + { + $data = $request->getObjectGroupData(); + $piggyBank = $this->repository->update($objectGroup, $data); + + session()->flash('success', (string) trans('firefly.updated_object_group', ['title' => $objectGroup->title])); + app('preferences')->mark(); + + $redirect = redirect($this->getPreviousUri('object-groups.edit.uri')); + + if (1 === (int) $request->get('return_to_edit')) { + // @codeCoverageIgnoreStart + session()->put('object-groups.edit.fromUpdate', true); + + $redirect = redirect(route('object-groups.edit', [$piggyBank->id])); + // @codeCoverageIgnoreEnd + } + + return $redirect; + } +} diff --git a/app/Http/Controllers/ObjectGroup/IndexController.php b/app/Http/Controllers/ObjectGroup/IndexController.php new file mode 100644 index 0000000000..6185dade42 --- /dev/null +++ b/app/Http/Controllers/ObjectGroup/IndexController.php @@ -0,0 +1,65 @@ +middleware( + function ($request, $next) { + app('view')->share('mainTitleIcon', 'fa-envelope-o'); + app('view')->share('title', (string) trans('firefly.object_groups_page_title')); + $this->repository = app(ObjectGroupRepositoryInterface::class); + + return $next($request); + } + ); + } + + /** + * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function index() + { + $this->repository->sort(); + $subTitle = (string) trans('firefly.object_groups_index'); + $objectGroups = $this->repository->get(); + + return view('object-groups.index', compact('subTitle', 'objectGroups')); + } + + /** + * @param ObjectGroup $objectGroup + */ + public function setOrder(Request $request, ObjectGroup $objectGroup) + { + Log::debug(sprintf('Found object group #%d "%s"', $objectGroup->id, $objectGroup->title)); + $newOrder = (int) $request->get('order'); + $this->repository->setOrder($objectGroup, $newOrder); + + return response()->json([]); + } + +} diff --git a/app/Http/Requests/ObjectGroupFormRequest.php b/app/Http/Requests/ObjectGroupFormRequest.php new file mode 100644 index 0000000000..959bc1afaf --- /dev/null +++ b/app/Http/Requests/ObjectGroupFormRequest.php @@ -0,0 +1,75 @@ +. + */ +declare(strict_types=1); + +namespace FireflyIII\Http\Requests; + +use FireflyIII\Models\ObjectGroup; + +/** + * Class ObjectGroupFormRequest. + */ +class ObjectGroupFormRequest extends Request +{ + /** + * Verify the request. + * + * @return bool + */ + public function authorize(): bool + { + // Only allow logged in users + return auth()->check(); + } + + /** + * Returns the data required by the controller. + * + * @return array + */ + public function getObjectGroupData(): array + { + return [ + 'title' => $this->string('title'), + ]; + } + + /** + * Rules for this request. + * + * @return array + */ + public function rules(): array + { + /** @var ObjectGroup $piggy */ + $objectGroup = $this->route()->parameter('objectGroup'); + + $titleRule = 'required|between:1,255|uniqueObjectGroup'; + + if (null !== $objectGroup) { + $titleRule = sprintf('required|between:1,255|uniqueObjectGroup:%d', $objectGroup->id); + } + + return [ + 'title' => $titleRule, + ]; + } +} diff --git a/app/Models/ObjectGroup.php b/app/Models/ObjectGroup.php index 7e6e03cb26..55e80d82d4 100644 --- a/app/Models/ObjectGroup.php +++ b/app/Models/ObjectGroup.php @@ -5,6 +5,7 @@ namespace FireflyIII\Models; use Illuminate\Database\Eloquent\Model; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class ObjectGroup @@ -31,6 +32,7 @@ use Illuminate\Database\Eloquent\Model; class ObjectGroup extends Model { protected $fillable = ['title', 'order']; + /** * @return \Illuminate\Database\Eloquent\Relations\MorphToMany */ @@ -38,4 +40,25 @@ class ObjectGroup extends Model { return $this->morphedByMany(PiggyBank::class, 'object_groupable'); } + + /** + * Route binder. Converts the key in the URL to the specified object (or throw 404). + * + * @param string $value + * + * @throws NotFoundHttpException + * @return ObjectGroup + */ + public static function routeBinder(string $value): ObjectGroup + { + if (auth()->check()) { + $objectGroupId = (int) $value; + $objectGroup = self::where('object_groups.id', $objectGroupId) + ->where('object_groups.user_id', auth()->user()->id)->first(); + if (null !== $objectGroup) { + return $objectGroup; + } + } + throw new NotFoundHttpException; + } } diff --git a/app/Models/PiggyBank.php b/app/Models/PiggyBank.php index 8417638efc..64770b88ac 100644 --- a/app/Models/PiggyBank.php +++ b/app/Models/PiggyBank.php @@ -80,7 +80,6 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property-read int|null $piggy_bank_repetitions_count * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\ObjectGroup[] $objectGroups * @property-read int|null $object_groups_count - * @property bool $encrypted */ class PiggyBank extends Model { diff --git a/app/Repositories/ObjectGroup/ObjectGroupRepository.php b/app/Repositories/ObjectGroup/ObjectGroupRepository.php index afef824a69..6262a038a3 100644 --- a/app/Repositories/ObjectGroup/ObjectGroupRepository.php +++ b/app/Repositories/ObjectGroup/ObjectGroupRepository.php @@ -6,14 +6,13 @@ namespace FireflyIII\Repositories\ObjectGroup; use DB; use FireflyIII\Models\ObjectGroup; use Illuminate\Support\Collection; +use Log; /** * Class ObjectGroupRepository */ class ObjectGroupRepository implements ObjectGroupRepositoryInterface { - - /** * @inheritDoc */ @@ -73,4 +72,37 @@ class ObjectGroupRepository implements ObjectGroupRepositoryInterface $group->save(); } } + + /** + * @inheritDoc + */ + public function setOrder(ObjectGroup $objectGroup, int $order): ObjectGroup + { + $order = 0 === $order ? 1 : $order; + $objectGroup->order = $order; + $objectGroup->save(); + + Log::debug(sprintf('Objectgroup #%d order is now %d', $objectGroup->id, $order)); + + return $objectGroup; + } + + /** + * @inheritDoc + */ + public function update(ObjectGroup $objectGroup, array $data): ObjectGroup + { + $objectGroup->title = $data['title']; + $objectGroup->save(); + + return $objectGroup; + } + + /** + * @inheritDoc + */ + public function destroy(ObjectGroup $objectGroup): void + { + $objectGroup->delete(); + } } diff --git a/app/Repositories/ObjectGroup/ObjectGroupRepositoryInterface.php b/app/Repositories/ObjectGroup/ObjectGroupRepositoryInterface.php index 6b87378236..b8f42b842b 100644 --- a/app/Repositories/ObjectGroup/ObjectGroupRepositoryInterface.php +++ b/app/Repositories/ObjectGroup/ObjectGroupRepositoryInterface.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace FireflyIII\Repositories\ObjectGroup; +use FireflyIII\Models\ObjectGroup; use Illuminate\Support\Collection; /** @@ -32,4 +33,25 @@ interface ObjectGroupRepositoryInterface */ public function sort(): void; + /** + * @param ObjectGroup $objectGroup + * @param int $index + * + * @return ObjectGroup + */ + public function setOrder(ObjectGroup $objectGroup, int $index): ObjectGroup; + + /** + * @param ObjectGroup $objectGroup + * @param array $data + * + * @return ObjectGroup + */ + public function update(ObjectGroup $objectGroup, array $data): ObjectGroup; + + /** + * @param ObjectGroup $objectGroup + */ + public function destroy(ObjectGroup $objectGroup): void; + } diff --git a/app/Validation/FireflyValidator.php b/app/Validation/FireflyValidator.php index 208a6c87f8..8c10713282 100644 --- a/app/Validation/FireflyValidator.php +++ b/app/Validation/FireflyValidator.php @@ -505,6 +505,30 @@ class FireflyValidator extends Validator * * @return bool */ + public function validateUniqueObjectGroup($attribute, $value, $parameters): bool + { + $exclude = $parameters[0] ?? null; + $query = DB::table('object_groups') + ->whereNull('object_groups.deleted_at') + ->where('object_groups.user_id', auth()->user()->id) + ->where('object_groups.title', $value); + if (null !== $exclude) { + $query->where('object_groups.id', '!=', (int) $exclude); + } + + return 0 === $query->count(); + } + + + /** + * @param $attribute + * @param $value + * @param $parameters + * + * TODO this method does not need a for loop + * + * @return bool + */ public function validateUniquePiggyBankForUser($attribute, $value, $parameters): bool { $exclude = $parameters[0] ?? null; diff --git a/config/firefly.php b/config/firefly.php index e8e7c950ac..70bd0d3dea 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -31,6 +31,7 @@ use FireflyIII\Models\Budget; use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\Category; use FireflyIII\Models\LinkType; +use FireflyIII\Models\ObjectGroup; use FireflyIII\Models\PiggyBank; use FireflyIII\Models\Preference; use FireflyIII\Models\Recurrence; @@ -409,6 +410,7 @@ return [ 'transactionType' => TransactionTypeModel::class, 'journalLink' => TransactionJournalLink::class, 'currency' => TransactionCurrency::class, + 'objectGroup' => ObjectGroup::class, 'piggyBank' => PiggyBank::class, 'preference' => Preference::class, 'tj' => TransactionJournal::class, diff --git a/database/migrations/2020_06_07_063612_changes_for_v530.php b/database/migrations/2020_06_07_063612_changes_for_v530.php index fc7d256109..13de0ff70b 100644 --- a/database/migrations/2020_06_07_063612_changes_for_v530.php +++ b/database/migrations/2020_06_07_063612_changes_for_v530.php @@ -14,7 +14,7 @@ class ChangesForV530 extends Migration * * @return void */ - public function down() + public function down(): void { Schema::dropIfExists('object_groupables'); Schema::dropIfExists('object_groups'); @@ -25,16 +25,18 @@ class ChangesForV530 extends Migration * * @return void */ - public function up() + public function up(): void { if (!Schema::hasTable('object_groups')) { Schema::create( 'object_groups', static function (Blueprint $table) { $table->increments('id'); + $table->integer('user_id', false, true); $table->timestamps(); $table->softDeletes(); $table->string('title', 255); $table->mediumInteger('order', false, true)->default(0); + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); } ); } diff --git a/public/v1/js/ff/object-groups/index.js b/public/v1/js/ff/object-groups/index.js new file mode 100644 index 0000000000..0529d1404e --- /dev/null +++ b/public/v1/js/ff/object-groups/index.js @@ -0,0 +1,83 @@ +/* + * index.js + * 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 . + */ +/** global: token */ +var fixGroupHelper = function (e, tr) { + "use strict"; + var $originals = tr.children(); + var $helper = tr.clone(); + $helper.children().each(function (index) { + // Set helper cell sizes to match the original sizes + $(this).width($originals.eq(index).width()); + }); + return $helper; +}; + +$(function () { + "use strict"; + + $('#sortable').find('tbody').sortable( + { + helper: fixGroupHelper, + stop: stopSorting, + items: 'tr.group-sortable', + handle: '.group-handle', + start: function (event, ui) { + // Build a placeholder cell that spans all the cells in the row + var cellCount = 0; + $('td, th', ui.helper).each(function () { + // For each TD or TH try and get it's colspan attribute, and add that or 1 to the total + var colspan = 1; + var colspanAttr = $(this).attr('colspan'); + if (colspanAttr > 1) { + colspan = colspanAttr; + } + cellCount += colspan; + }); + + // Add the placeholder UI - note that this is the item's content, so TD rather than TR + ui.placeholder.html(' '); + } + } + ); +}); + + + +function stopSorting() { + "use strict"; + + $.each($('#sortable>tbody>tr.group-sortable'), function (i, v) { + var holder = $(v); + var index = i+1; + var originalOrder = parseInt(holder.data('order')); + var id = holder.data('id'); + var name = holder.data('name'); + + if (index === originalOrder) { + // not changed, position is what it should be. + return; + } + console.log('Group "'+name+'" has moved from position ' + originalOrder + ' to ' + index); + + // update position: + holder.data('order', index); + $.post('groups/set-order/' + id, {order: index, _token: token}); + }); +} diff --git a/public/v1/js/ff/piggy-banks/index.js b/public/v1/js/ff/piggy-banks/index.js index fba19c782b..b0ca3466a9 100644 --- a/public/v1/js/ff/piggy-banks/index.js +++ b/public/v1/js/ff/piggy-banks/index.js @@ -101,7 +101,7 @@ function stopSorting() { } if (position < i) { // position is less. - console.log('"' + name + '" ("' + objectGroupTitle + '") has moved up from position ' + originalOrder + ' to ' + (i + 1)); + console.log('"' + name + '" ("' + objectGroupTitle + '") has moved down from position ' + originalOrder + ' to ' + (i + 1)); } if (position > i) { console.log('"' + name + '" ("' + objectGroupTitle + '") has moved up from position ' + originalOrder + ' to ' + (i + 1)); diff --git a/resources/lang/en_US/breadcrumbs.php b/resources/lang/en_US/breadcrumbs.php index c694dc1db1..e119539507 100644 --- a/resources/lang/en_US/breadcrumbs.php +++ b/resources/lang/en_US/breadcrumbs.php @@ -59,4 +59,6 @@ return [ 'delete_journal_link' => 'Delete link between transactions', 'telemetry_index' => 'Telemetry', 'telemetry_view' => 'View telemetry', + 'edit_object_group' => 'Edit group ":title"', + 'delete_object_group' => 'Delete group ":title"', ]; diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 6526b7396b..235f54bd4c 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -891,7 +891,7 @@ return [ 'list_inactive_rule' => 'inactive rule', 'bill_edit_rules' => 'Firefly III will attempt to edit the rule related to this bill as well. If you\'ve edited this rule yourself however, Firefly III won\'t change anything.|Firefly III will attempt to edit the :count rules related to this bill as well. If you\'ve edited these rules yourself however, Firefly III won\'t change anything.', 'bill_expected_date' => 'Expected :date', - + // accounts: 'inactive_account_link' => 'You have :count inactive (archived) account, which you can view on this separate page.|You have :count inactive (archived) accounts, which you can view on this separate page.', 'all_accounts_inactive' => 'These are your inactive accounts.', @@ -1671,5 +1671,18 @@ return [ 'debug_pretty_table' => 'If you copy/paste the box below into a GitHub issue it will generate a table. Please do not surround this text with backticks or quotes.', 'debug_additional_data' => 'You may also share the content of the box below. You can also copy-and-paste this into a new or existing GitHub issue. However, the content of this box may contain private information such as account names, transaction details or email addresses.', + // object groups + 'object_groups_menu_bar' => 'Groups', + 'object_groups_page_title' => 'Groups', + 'object_groups_breadcrumb' => 'Groups', + 'object_groups_index' => 'Overview', + 'object_groups' => 'Groups', + 'object_groups_empty_explain' => 'Some things in Firefly III can be divided into groups. Piggy banks for example, feature a "Group" field in the edit and create screens. When you set this field, you can edit the names and the order of the groups on this page. For more information, check out the help-pages in the top right corner, under the (?)-icon.', + 'object_group_title' => 'Title', + 'edit_object_group' => 'Edit group ":title"', + 'delete_object_group' => 'Edit group ":title"', + 'update_object_group' => 'Update group', + 'updated_object_group' => 'Succesfully updated group ":title"', + 'deleted_object_group' => 'Succesfully deleted group ":title"', ]; diff --git a/resources/lang/en_US/form.php b/resources/lang/en_US/form.php index ee3fdd6002..e7de526805 100644 --- a/resources/lang/en_US/form.php +++ b/resources/lang/en_US/form.php @@ -137,6 +137,7 @@ return [ 'account_areYouSure' => 'Are you sure you want to delete the account named ":name"?', 'bill_areYouSure' => 'Are you sure you want to delete the bill named ":name"?', 'rule_areYouSure' => 'Are you sure you want to delete the rule titled ":title"?', + 'object_group_areYouSure' => 'Are you sure you want to delete the group titled ":title"?', 'ruleGroup_areYouSure' => 'Are you sure you want to delete the rule group titled ":title"?', 'budget_areYouSure' => 'Are you sure you want to delete the budget named ":name"?', 'category_areYouSure' => 'Are you sure you want to delete the category named ":name"?', @@ -156,6 +157,7 @@ return [ 'also_delete_connections' => 'The only transaction linked with this link type will lose this connection.|All :count transactions linked with this link type will lose their connection.', 'also_delete_rules' => 'The only rule connected to this rule group will be deleted as well.|All :count rules connected to this rule group will be deleted as well.', 'also_delete_piggyBanks' => 'The only piggy bank connected to this account will be deleted as well.|All :count piggy bank connected to this account will be deleted as well.', + 'not_delete_piggy_banks' => 'The piggy bank connected to this group will not be deleted.|The :count piggy banks connected to this group will not be deleted.', 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will be spared deletion.', 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will be spared deletion.', 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will be spared deletion.', @@ -163,6 +165,8 @@ return [ 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will be spared deletion.', 'check_for_updates' => 'Check for updates', + 'delete_object_group' => 'Delete group ":title"', + 'email' => 'Email address', 'password' => 'Password', 'password_confirmation' => 'Password (again)', diff --git a/resources/lang/en_US/validation.php b/resources/lang/en_US/validation.php index 1cfa4b0898..64edfe4ba3 100644 --- a/resources/lang/en_US/validation.php +++ b/resources/lang/en_US/validation.php @@ -130,6 +130,7 @@ return [ 'amount_zero' => 'The total amount cannot be zero.', 'current_target_amount' => 'The current amount must be less than the target amount.', 'unique_piggy_bank_for_user' => 'The name of the piggy bank must be unique.', + 'unique_object_group' => 'The group name must be unique', 'secure_password' => 'This is not a secure password. Please try again. For more information, visit https://bit.ly/FF3-password-security', 'valid_recurrence_rep_type' => 'Invalid repetition type for recurring transactions.', diff --git a/resources/views/v1/list/piggy-banks.twig b/resources/views/v1/list/piggy-banks.twig index 7841746069..d7f6c5190f 100644 --- a/resources/views/v1/list/piggy-banks.twig +++ b/resources/views/v1/list/piggy-banks.twig @@ -15,7 +15,7 @@ {% if objectGroup.piggy_banks|length > 0 %} - {% if objectGroupOrder != 0 %}{% endif %} +   {{ objectGroup.object_group_title }} {% for piggy in objectGroup.piggy_banks %} diff --git a/resources/views/v1/object-groups/delete.twig b/resources/views/v1/object-groups/delete.twig new file mode 100644 index 0000000000..aee112d1a9 --- /dev/null +++ b/resources/views/v1/object-groups/delete.twig @@ -0,0 +1,42 @@ +{% extends "./layout/default" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.render(Route.getCurrentRoute.getName, objectGroup) }} +{% endblock %} + +{% block content %} + +
+ +
+
+
+
+

{{ trans('form.delete_object_group', {'title': objectGroup.title}) }}

+
+
+

+ {{ trans('form.permDeleteWarning') }} +

+ +

+ {{ trans('form.object_group_areYouSure', {'title': objectGroup.title}) }} +

+ + {% if piggyBanks > 0 %} +

+ {{ Lang.choice('form.not_delete_piggy_banks', piggyBanks, {count: piggyBanks}) }} +

+ {% endif %} + +
+ +
+
+
+ +
+{% endblock %} diff --git a/resources/views/v1/object-groups/edit.twig b/resources/views/v1/object-groups/edit.twig new file mode 100644 index 0000000000..c0b4f8144f --- /dev/null +++ b/resources/views/v1/object-groups/edit.twig @@ -0,0 +1,58 @@ +{% extends "./layout/default" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.render(Route.getCurrentRoute.getName, objectGroup) }} +{% endblock %} + +{% block content %} + {{ Form.model(objectGroup, {'class' : 'form-horizontal','enctype': 'multipart/form-data','id' : 'update','url' : route('object-groups.update',objectGroup.id)}) }} + + + +
+
+
+
+

{{ 'mandatoryFields'|_ }}

+
+
+ {{ ExpandedForm.text('title') }} +
+
+ +
+
+
+
+ {# panel for options #} +
+
+

{{ 'options'|_ }}

+
+
+ {{ ExpandedForm.optionsList('update','object group') }} +
+ +
+ +
+
+ +{% endblock %} +{% block scripts %} + + + + {# auto complete for object groups #} + + +{% endblock %} + +{% block styles %} + + +{% endblock %} diff --git a/resources/views/v1/object-groups/index.twig b/resources/views/v1/object-groups/index.twig new file mode 100644 index 0000000000..b96860ab9e --- /dev/null +++ b/resources/views/v1/object-groups/index.twig @@ -0,0 +1,74 @@ +{% extends "./layout/default" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.render }} +{% endblock %} +{% block content %} +
+
+
+
+

{{ 'object_groups'|_ }}

+
+ {% if objectGroups|length == 0 %} +
+
+
+

+ {{ 'object_groups_empty_explain'|_ }} +

+
+
+
+ {% endif %} + {% if objectGroups|length > 0 %} +
+ + + + + + + + + + {% for objectGroup in objectGroups %} + + + + + + {% endfor %} + +
  + {{ 'object_group_title'|_ }} + +   +
+ {{ objectGroup.title }}
+ + {% for piggyBank in objectGroup.piggyBanks %} + - {{ 'piggy_bank'|_ }}: {{ piggyBank.name }}
+ {% endfor %} +
+ +
+
+ {% endif %} +
+
+
+{% endblock %} +{% block scripts %} + + +{% endblock %} +{% block styles %} +{% endblock %} diff --git a/resources/views/v1/partials/menu-sidebar.twig b/resources/views/v1/partials/menu-sidebar.twig index 97678bb41f..c133b18ad6 100644 --- a/resources/views/v1/partials/menu-sidebar.twig +++ b/resources/views/v1/partials/menu-sidebar.twig @@ -148,6 +148,12 @@ {{ 'tags'|_ }} +
  • + + + {{ 'object_groups_menu_bar'|_ }} + +
  • diff --git a/routes/breadcrumbs.php b/routes/breadcrumbs.php index e1ed8b8b7c..925acccb50 100644 --- a/routes/breadcrumbs.php +++ b/routes/breadcrumbs.php @@ -30,6 +30,7 @@ use FireflyIII\Models\Budget; use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\Category; use FireflyIII\Models\LinkType; +use FireflyIII\Models\ObjectGroup; use FireflyIII\Models\PiggyBank; use FireflyIII\Models\Recurrence; use FireflyIII\Models\Rule; @@ -1163,13 +1164,30 @@ try { } ); - // SPLIT + // object groups Breadcrumbs::register( - 'transactions.split.edit', - static function (BreadcrumbsGenerator $breadcrumbs, TransactionJournal $journal) { - $breadcrumbs->parent('transactions.show', $journal); - $breadcrumbs->push(trans('breadcrumbs.edit_journal', ['description' => $journal->description]), route('transactions.split.edit', [$journal->id])); + 'object-groups.index', + static function (BreadcrumbsGenerator $breadcrumbs): void { + $breadcrumbs->parent('index'); + $breadcrumbs->push(trans('firefly.object_groups_breadcrumb'), route('object-groups.index')); } ); + + Breadcrumbs::register( + 'object-groups.edit', + static function (BreadcrumbsGenerator $breadcrumbs, ObjectGroup $objectGroup) { + $breadcrumbs->parent('object-groups.index'); + $breadcrumbs->push(trans('breadcrumbs.edit_object_group', ['title' => $objectGroup->title]), route('object-groups.edit', [$objectGroup->id])); + } + ); + + Breadcrumbs::register( + 'object-groups.delete', + static function (BreadcrumbsGenerator $breadcrumbs, ObjectGroup $objectGroup) { + $breadcrumbs->parent('object-groups.index'); + $breadcrumbs->push(trans('breadcrumbs.delete_object_group', ['title' => $objectGroup->title]), route('object-groups.delete', [$objectGroup->id])); + } + ); + } catch (DuplicateBreadcrumbException $e) { } diff --git a/routes/web.php b/routes/web.php index 34edc67500..28c843bb65 100644 --- a/routes/web.php +++ b/routes/web.php @@ -547,6 +547,28 @@ Route::group( } ); + +/** + * Object group controller. + */ +Route::group( + ['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers', 'prefix' => 'groups', 'as' => 'object-groups.'], + static function () { + + // index + Route::get('', ['uses' => 'ObjectGroup\IndexController@index', 'as' => 'index']); + Route::post('set-order/{objectGroup}', ['uses' => 'ObjectGroup\IndexController@setOrder', 'as' => 'set-order']); + + // edit + Route::get('edit/{objectGroup}', ['uses' => 'ObjectGroup\EditController@edit', 'as' => 'edit']); + Route::post('update/{objectGroup}', ['uses' => 'ObjectGroup\EditController@update', 'as' => 'update']); + + // delete + Route::get('delete/{objectGroup}', ['uses' => 'ObjectGroup\DeleteController@delete', 'as' => 'delete']); + Route::post('destroy/{objectGroup}', ['uses' => 'ObjectGroup\DeleteController@destroy', 'as' => 'destroy']); + } +); + /** * Help Controller. */