Update autocomplete code for #3150

This commit is contained in:
James Cole 2020-07-23 06:19:34 +02:00
parent 0f840ad082
commit 162e791dfd
No known key found for this signature in database
GPG Key ID: B5669F9493CDE38D
16 changed files with 93 additions and 459 deletions

View File

@ -71,12 +71,11 @@ class PiggyBankController extends Controller
$piggies = $this->piggyRepository->searchPiggyBank($data['query'], $data['limit']);
$defaultCurrency = app('amount')->getDefaultCurrency();
$response = [];
/** @var PiggyBank $piggy */
foreach ($piggies as $piggy) {
$currency = $this->accountRepository->getAccountCurrency($piggy->account) ?? $defaultCurrency;
$piggy->objectGroup = $piggy->objectGroups->first();
$piggy->name_with_amount
= $response[] = [
$response[] = [
'id' => $piggy->id,
'name' => $piggy->name,
'currency_id' => $currency->id,
@ -104,9 +103,7 @@ class PiggyBankController extends Controller
foreach ($piggies as $piggy) {
$currency = $this->accountRepository->getAccountCurrency($piggy->account) ?? $defaultCurrency;
$currentAmount = $this->piggyRepository->getRepetition($piggy)->currentamount ?? '0';
$piggy->objectGroup = $piggy->objectGroups->first();
$piggy->name_with_amount
= $response[] = [
$response[] = [
'id' => $piggy->id,
'name' => $piggy->name,
'name_with_balance' => sprintf(

View File

@ -25,6 +25,11 @@ namespace FireflyIII\Api\V1\Controllers\Autocomplete;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteRequest;
use FireflyIII\Models\Tag;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
/**
* Class TagController
@ -32,4 +37,46 @@ use FireflyIII\Api\V1\Controllers\Controller;
class TagController extends Controller
{
private TagRepositoryInterface $repository;
/**
* CurrencyController constructor.
*/
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
/** @var User $user */
$user = auth()->user();
$this->repository = app(TagRepositoryInterface::class);
$this->repository->setUser($user);
return $next($request);
}
);
}
/**
* @param AutocompleteRequest $request
*
* @return JsonResponse
*/
public function tags(AutocompleteRequest $request): JsonResponse
{
$data = $request->getData();
$result = $this->repository->searchTags($data['query'], $data['limit']);
$array = [];
/** @var Tag $tag */
foreach ($result as $tag) {
$array[] = [
'id' => $tag->id,
'name' => $tag->tag,
'tag' => $tag->tag,
];
}
return response()->json($array);
}
}

View File

@ -121,27 +121,8 @@ class AutoCompleteController extends Controller
return response()->json($array);
}
/**
* @param Request $request
*
* @return JsonResponse
* @codeCoverageIgnore
*/
public function tags(Request $request): JsonResponse
{
$search = (string) $request->get('search');
/** @var TagRepositoryInterface $repository */
$repository = app(TagRepositoryInterface::class);
$result = $repository->searchTags($search);
$array = $result->toArray();
foreach ($array as $index => $item) {
// rename field for consistency.
$array[$index]['name'] = $item['tag'];
}
return response()->json($array);
}
/**
* @param Request $request

View File

@ -414,7 +414,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
$search->where('piggy_banks.name', 'LIKE', sprintf('%%%s%%', $query));
}
$search->orderBy('piggy_banks.order', 'ASC')
->orderBy('piggy_banks.name', 'ASC')->where('piggy_banks.active', 1);
->orderBy('piggy_banks.name', 'ASC');
return $search->take($limit)->get();
}

View File

@ -287,10 +287,11 @@ class TagRepository implements TagRepositoryInterface
* Search the users tags.
*
* @param string $query
* @param int $limit
*
* @return Collection
*/
public function searchTags(string $query): Collection
public function searchTags(string $query, int $limit): Collection
{
/** @var Collection $tags */
$tags = $this->user->tags()->orderBy('tag', 'ASC');
@ -299,7 +300,7 @@ class TagRepository implements TagRepositoryInterface
$tags->where('tag', 'LIKE', $search);
}
return $tags->get();
return $tags->take($limit)->get('tags.*');
}
/**

View File

@ -165,10 +165,11 @@ interface TagRepositoryInterface
* Search the users tags.
*
* @param string $query
* @param int $limit
*
* @return Collection
*/
public function searchTags(string $query): Collection;
public function searchTags(string $query, int $limit): Collection;
/**
* @param User $user

View File

@ -27,7 +27,7 @@ function initTagsAC() {
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
prefetch: {
url: 'json/tags?uid=' + uid,
url: 'api/v1/autocomplete/tags?uid=' + uid,
filter: function (list) {
return $.map(list, function (item) {
return {name: item.name};
@ -35,7 +35,7 @@ function initTagsAC() {
}
},
remote: {
url: 'json/tags?search=%QUERY&uid=' + uid,
url: 'api/v1/autocomplete/tags?query=%QUERY&uid=' + uid,
wildcard: '%QUERY',
filter: function (list) {
return $.map(list, function (item) {

View File

@ -29,7 +29,7 @@ $(document).ready(function () {
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('title'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
prefetch: {
url: 'json/object-groups?uid=' + uid,
url: 'api/v1/autocomplete/object-groups?uid=' + uid,
filter: function (list) {
return $.map(list, function (obj) {
return obj;
@ -37,7 +37,7 @@ $(document).ready(function () {
}
},
remote: {
url: 'json/object-groups?search=%QUERY&uid=' + uid,
url: 'api/v1/autocomplete/object-groups?query=%QUERY&uid=' + uid,
wildcard: '%QUERY',
filter: function (list) {
return $.map(list, function (obj) {

View File

@ -245,7 +245,7 @@ function updateActionInput(selectList) {
case 'add_tag':
case 'remove_tag':
console.log('Select list value is ' + selectList.val() + ', so input needs auto complete.');
createAutoComplete(inputResult, 'json/tags');
createAutoComplete(inputResult, 'api/v1/autocomplete/tags');
break;
case 'set_description':
console.log('Select list value is ' + selectList.val() + ', so input needs auto complete.');
@ -316,7 +316,7 @@ function updateTriggerInput(selectList) {
break;
case 'tag_is':
console.log('Select list value is ' + selectList.val() + ', so input needs auto complete.');
createAutoComplete(inputResult, 'json/tags');
createAutoComplete(inputResult, 'api/v1/autocomplete/tags');
break;
case 'budget_is':
console.log('Select list value is ' + selectList.val() + ', so input needs auto complete.');

View File

@ -1 +0,0 @@
Options -Indexes

View File

@ -1,394 +0,0 @@
/*
* edit.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 <https://www.gnu.org/licenses/>.
*/
/** global: originalSum,originalForeignSum, accounting, what, Modernizr, currencySymbol, foreignCurrencySymbol */
var destNames;
var sourceNames;
var categories;
var journalNames;
$(document).ready(function () {
"use strict";
$('.btn-do-split').click(cloneDivRow);
$('.remove-current-split').click(removeDivRow);
// auto complete destination name (expense accounts):
destNames = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
prefetch: {
url: 'json/expense-accounts?uid=' + uid,
filter: function (list) {
return $.map(list, function (name) {
return {name: name};
});
}
},
remote: {
url: 'json/expense-accounts?search=%QUERY&uid=' + uid,
wildcard: '%QUERY',
filter: function (list) {
return $.map(list, function (name) {
return {name: name};
});
}
}
});
destNames.initialize();
$('input[name$="destination_name]"]').typeahead({hint: true, highlight: true,}, {source: destNames, displayKey: 'name', autoSelect: false});
// auto complete source name (revenue accounts):
sourceNames = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
prefetch: {
url: 'json/revenue-accounts?uid=' + uid,
filter: function (list) {
return $.map(list, function (name) {
return {name: name};
});
}
},
remote: {
url: 'json/revenue-accounts?search=%QUERY&uid=' + uid,
wildcard: '%QUERY',
filter: function (list) {
return $.map(list, function (name) {
return {name: name};
});
}
}
});
sourceNames.initialize();
$('input[name$="source_name]"]').typeahead({hint: true, highlight: true,}, {source: sourceNames, displayKey: 'name', autoSelect: false});
// auto complete category fields:
categories = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
prefetch: {
url: 'api/v1/autocomplete/categories?uid=' + uid,
filter: function (list) {
return $.map(list, function (name) {
return {name: name};
});
}
},
remote: {
url: 'api/v1/autocomplete/categories?query=%QUERY&uid=' + uid,
wildcard: '%QUERY',
filter: function (list) {
return $.map(list, function (name) {
return {name: name};
});
}
}
});
categories.initialize();
$('input[name$="category_name]"]').typeahead({hint: true, highlight: true,}, {source: categories, displayKey: 'name', autoSelect: false});
// get transaction journal name things:
journalNames = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
prefetch: {
url: 'json/transaction-journals/' + what + '?uid=' + uid,
filter: function (list) {
return $.map(list, function (name) {
return {name: name};
});
}
},
remote: {
url: 'json/transaction-journals/' + what + '?search=%QUERY&uid=' + uid,
wildcard: '%QUERY',
filter: function (list) {
return $.map(list, function (name) {
return {name: name};
});
}
}
});
journalNames.initialize();
$('input[name="journal_description"]').typeahead({hint: true, highlight: true,}, {source: journalNames, displayKey: 'name', autoSelect: false});
$('input[name$="transaction_description]"]').typeahead({hint: true, highlight: true,}, {source: journalNames, displayKey: 'name', autoSelect: false});
// get tags:
console.log('initTagsAC()');
var tagTags = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
prefetch: {
url: 'json/tags?uid=' + uid,
filter: function (list) {
return $.map(list, function (tagTag) {
return {name: tagTag};
});
}
},
remote: {
url: 'json/tags?search=%QUERY&uid=' + uid,
wildcard: '%QUERY',
filter: function (list) {
return $.map(list, function (name) {
return {name: name};
});
}
}
});
tagTags.initialize();
$('input[name="tags"]').tagsinput({
typeaheadjs: {
hint: true,
highlight: true,
name: 'tags',
displayKey: 'name',
valueKey: 'name',
source: tagTags.ttAdapter()
}
});
$('input[name$="][amount]"]').on('change', calculateBothSums);
$('input[name$="][foreign_amount]"]').on('change', calculateBothSums);
if (!Modernizr.inputtypes.date) {
$('input[type="date"]').datepicker(
{
dateFormat: 'yy-mm-dd'
}
);
}
});
function calculateBothSums() {
console.log("Now in calculateBothSums()");
calculateSum();
calculateForeignSum();
}
/**
* New and cool
* @param e
* @returns {boolean}
*/
function removeDivRow(e) {
"use strict";
var rows = $('div.split_row');
if (rows.length === 1) {
return false;
}
var row = $(e.target);
var index = row.data('split');
if (typeof index === 'undefined') {
var parent = row.parent();
index = parent.data('split');
console.log('Parent. ' + parent.className);
}
console.log('Split index is "' + index + '"');
$('div.split_row[data-split="' + index + '"]').remove();
resetDivSplits();
return false;
}
/**
* New and cool
* @returns {boolean}
*/
function cloneDivRow() {
"use strict";
var source = $('div.split_row').last().clone();
var count = $('div.split_row').length + 1;
source.removeClass('initial-row');
source.find('.count').text('#' + count);
source.find('input[name$="][amount]"]').val("").on('change', calculateBothSums);
source.find('input[name$="][foreign_amount]"]').val("").on('change', calculateBothSums);
if (destNames) {
source.find('input[name$="destination_name]"]').typeahead({hint: true, highlight: true,}, {source: destNames, displayKey: 'name', autoSelect: false});
}
if (sourceNames) {
source.find('input[name$="source_name]"]').typeahead({hint: true, highlight: true,}, {source: sourceNames, displayKey: 'name', autoSelect: false});
}
if (categories) {
source.find('input[name$="category_name]"]').typeahead({hint: true, highlight: true,}, {source: categories, displayKey: 'name', autoSelect: false});
}
if (journalNames) {
source.find('input[name$="transaction_description]"]').typeahead({hint: true, highlight: true,}, {source: journalNames, displayKey: 'name', autoSelect: false});
}
$('div.split_row_holder').append(source);
// remove original click things, add them again:
$('.remove-current-split').unbind('click').click(removeDivRow);
calculateBothSums();
resetDivSplits();
return false;
}
/**
* New and hip
*/
function resetDivSplits() {
"use strict";
// loop rows, reset numbers:
// update the row split number:
$.each($('div.split_row'), function (i, v) {
var row = $(v);
row.attr('data-split', i);
// add or remove class with bg thing
if (i % 2 === 0) {
row.removeClass('bg-gray-light');
}
if (i % 2 === 1) {
row.addClass('bg-gray-light');
}
});
// loop each remove button, update the index
$.each($('.remove-current-split'), function (i, v) {
var button = $(v);
button.attr('data-split', i);
button.find('span').text(' #' + (i + 1));
});
// loop each possible field.
// ends with ][description]
$.each($('input[name$="][transaction_description]"]'), function (i, v) {
var input = $(v);
input.attr('name', 'transactions[' + i + '][transaction_description]');
});
// ends with ][destination_name]
$.each($('input[name$="][destination_name]"]'), function (i, v) {
var input = $(v);
input.attr('name', 'transactions[' + i + '][destination_name]');
});
// ends with ][source_name]
$.each($('input[name$="][source_name]"]'), function (i, v) {
var input = $(v);
input.attr('name', 'transactions[' + i + '][source_name]');
});
// ends with ][amount]
$.each($('input[name$="][amount]"]'), function (i, v) {
var input = $(v);
input.attr('name', 'transactions[' + i + '][amount]');
});
// ends with ][foreign_amount]
$.each($('input[name$="][foreign_amount]"]'), function (i, v) {
var input = $(v);
input.attr('name', 'transactions[' + i + '][foreign_amount]');
});
// ends with ][currency_id]
$.each($('input[name$="][currency_id]"]'), function (i, v) {
var input = $(v);
input.attr('name', 'transactions[' + i + '][currency_id]');
});
// ends with ][foreign_currency_id]
$.each($('input[name$="][foreign_currency_id]"]'), function (i, v) {
var input = $(v);
input.attr('name', 'transactions[' + i + '][foreign_currency_id]');
});
// ends with ][budget_id]
$.each($('select[name$="][budget_id]"]'), function (i, v) {
var input = $(v);
input.attr('name', 'transactions[' + i + '][budget_id]');
});
// ends with ][category]
$.each($('input[name$="][category_name]"]'), function (i, v) {
var input = $(v);
input.attr('name', 'transactions[' + i + '][category_name]');
});
}
function calculateSum() {
"use strict";
console.log("Now in calculateSum()");
var left = originalSum * -1;
var sum = 0;
var set = $('input[name$="][amount]"]');
for (var i = 0; i < set.length; i++) {
var current = $(set[i]);
sum += (current.val() === "" ? 0 : parseFloat(current.val()));
left += (current.val() === "" ? 0 : parseFloat(current.val()));
}
sum = Math.round(sum * 100) / 100;
left = Math.round(left * 100) / 100;
console.log("Sum is " + sum + ", left is " + left);
$('.amount-warning').remove();
if (sum !== originalSum) {
console.log("Is different from original sum " + originalSum);
var paragraph = $('#journal_amount_holder').find('p.form-control-static');
$('<span>').text(' (' + accounting.formatMoney(sum, currencySymbol) + ')').addClass('text-danger amount-warning').appendTo(paragraph);
// also add what's left to divide (or vice versa)
$('<span>').text(' (' + accounting.formatMoney(left, currencySymbol) + ')').addClass('text-danger amount-warning').appendTo(paragraph);
}
}
function calculateForeignSum() {
// "use strict";
var left = originalForeignSum * -1;
var sum = 0;
var set = $('input[name$="][foreign_amount]"]');
for (var i = 0; i < set.length; i++) {
var current = $(set[i]);
sum += (current.val() === "" ? 0 : parseFloat(current.val()));
left += (current.val() === "" ? 0 : parseFloat(current.val()));
}
sum = Math.round(sum * 100) / 100;
left = Math.round(left * 100) / 100;
$('.amount-warning-foreign').remove();
if (sum !== originalForeignSum) {
var paragraph = $('#journal_foreign_amount_holder').find('p.form-control-static');
$('<span>').text(' (' + accounting.formatMoney(sum, foreignCurrencySymbol) + ')').addClass('text-danger amount-warning-foreign').appendTo(paragraph);
// also add what's left to divide (or vice versa)
$('<span>').text(' (' + accounting.formatMoney(left, foreignCurrencySymbol) + ')').addClass('text-danger amount-warning-foreign').appendTo(paragraph);
}
}

View File

@ -32,13 +32,12 @@
<select class="form-control" ref="currency_select" name="foreign_currency[]" @input="handleInput">
<option
v-for="currency in this.enabledCurrencies"
v-if="currency.enabled"
:value="currency.id"
:label="currency.name"
:label="currency.attributes.name"
:selected="value.currency_id === currency.id"
>
{{ currency.name }}
{{ currency.attributes.name }}
</option>
</select>
</div>
@ -174,27 +173,32 @@
}
},
loadCurrencies: function () {
// console.log('loadCurrencies');
let URI = document.getElementsByTagName('base')[0].href + "json/currencies";
//console.log('loadCurrencies');
let URI = document.getElementsByTagName('base')[0].href + "api/v1/currencies";
axios.get(URI, {}).then((res) => {
this.currencies = [
{
name: this.no_currency,
id: 0,
enabled: true
attributes: {
name: this.no_currency,
enabled: true
},
}
];
this.enabledCurrencies = [
{
name: this.no_currency,
attributes: {
name: this.no_currency,
enabled: true
},
id: 0,
enabled: true
}
];
for (const key in res.data.data) {
if (res.data.data.hasOwnProperty(key) && /^0$|^[1-9]\d*$/.test(key) && key <= 4294967294) {
if (res.data.data[key].enabled) {
if (res.data.data[key].attributes.enabled) {
console.log(res.data.data[key].attributes);
this.currencies.push(res.data.data[key]);
this.enabledCurrencies.push(res.data.data[key]);
}

View File

@ -28,7 +28,7 @@
<div class="col-sm-12">
<select name="piggy_bank[]" ref="piggy" @input="handleInput" class="form-control">
<optgroup v-for="(option, key) in this.piggies" v-bind:label="key">
<option v-for="piggy in option.piggies" :label="piggy.name_with_amount" :value="piggy.id">{{piggy.name_with_amount}}</option>
<option v-for="piggy in option.piggies" :label="piggy.name_with_balance" :value="piggy.id">{{piggy.name_with_balance}}</option>
</optgroup>
</select>
<ul class="list-unstyled" v-for="error in this.error">
@ -58,7 +58,7 @@
return this.error.length > 0;
},
loadPiggies: function () {
let URI = document.getElementsByTagName('base')[0].href + "json/piggy-banks";
let URI = document.getElementsByTagName('base')[0].href + "api/v1/autocomplete/piggy-banks-with-balance";
axios.get(URI, {}).then((res) => {
let tempList = {
0: {
@ -67,7 +67,7 @@
},
piggies: [
{
name_with_amount: this.no_piggy_bank,
name_with_balance: this.no_piggy_bank,
id: 0,
}
],
@ -87,11 +87,11 @@
piggies: [],
};
}
tempList[groupOrder].piggies.push({name_with_amount: currentPiggy.name_with_amount, id: currentPiggy.id});
tempList[groupOrder].piggies.push({name_with_balance: currentPiggy.name_with_balance, id: currentPiggy.id});
}
if (!currentPiggy.objectGroup) {
// add to empty one:
tempList[0].piggies.push({name_with_amount: currentPiggy.name_with_amount, id: currentPiggy.id});
tempList[0].piggies.push({name_with_balance: currentPiggy.name_with_balance, id: currentPiggy.id});
}
//console.log(currentPiggy);
this.piggies.push(res.data[key]);

View File

@ -91,7 +91,7 @@
if (this.tag.length < 2) {
return;
}
const url = document.getElementsByTagName('base')[0].href + `json/tags?search=${this.tag}`;
const url = document.getElementsByTagName('base')[0].href + `api/v1/autocomplete/tags?query=${this.tag}`;
clearTimeout(this.debounce);
this.debounce = setTimeout(() => {
@ -108,4 +108,4 @@
<style scoped>
</style>
</style>

View File

@ -70,6 +70,7 @@ Route::group(
Route::get('object-groups', ['uses' => 'ObjectGroupController@objectGroups', 'as' => 'object-groups']);
Route::get('piggy-banks', ['uses' => 'PiggyBankController@piggyBanks', 'as' => 'piggy-banks']);
Route::get('piggy-banks-with-balance', ['uses' => 'PiggyBankController@piggyBanksWithBalance', 'as' => 'piggy-banks-with-balance']);
Route::get('tags', ['uses' => 'TagController@tags', 'as' => 'tags']);
}
);

View File

@ -604,19 +604,16 @@ Route::group(
static function () {
// for auto complete
Route::get('budgets', ['uses' => 'Json\AutoCompleteController@budgets', 'as' => 'autocomplete.budgets']);
Route::get('object-groups', ['uses' => 'Json\AutoCompleteController@objectGroups', 'as' => 'autocomplete.object-groups']);
Route::get('categories', ['uses' => 'Json\AutoCompleteController@categories', 'as' => 'autocomplete.categories']);
Route::get('currencies', ['uses' => 'Json\AutoCompleteController@currencies', 'as' => 'autocomplete.currencies']);
Route::get('piggy-banks', ['uses' => 'Json\AutoCompleteController@piggyBanks', 'as' => 'autocomplete.piggy-banks']);
Route::get('tags', ['uses' => 'Json\AutoCompleteController@tags', 'as' => 'autocomplete.tags']);
Route::get('transaction-journals/all', ['uses' => 'Json\AutoCompleteController@allJournals', 'as' => 'autocomplete.all-journals']);
Route::get('transaction-journals/with-id', ['uses' => 'Json\AutoCompleteController@allJournalsWithID', 'as' => 'autocomplete.all-journals-with-id']);
Route::get('currency-names', ['uses' => 'Json\AutoCompleteController@currencyNames', 'as' => 'autocomplete.currency-names']);
Route::get('transaction-types', ['uses' => 'Json\AutoCompleteController@transactionTypes', 'as' => 'transaction-types']);
// Route::get('transaction-journals/all', ['uses' => 'Json\AutoCompleteController@allJournals', 'as' => 'autocomplete.all-journals']);
// Route::get('transaction-journals/with-id', ['uses' => 'Json\AutoCompleteController@allJournalsWithID', 'as' => 'autocomplete.all-journals-with-id']);
// Route::get('currency-names', ['uses' => 'Json\AutoCompleteController@currencyNames', 'as' => 'autocomplete.currency-names']);
// Route::get('transaction-types', ['uses' => 'Json\AutoCompleteController@transactionTypes', 'as' => 'transaction-types']);
// budgets:
Route::get('budget/total-budgeted/{currency}/{start_date}/{end_date}', ['uses' => 'Json\BudgetController@getBudgetInformation', 'as' => 'budget.total-budgeted']);
Route::get(
'budget/total-budgeted/{currency}/{start_date}/{end_date}',
['uses' => 'Json\BudgetController@getBudgetInformation', 'as' => 'budget.total-budgeted']
);
// boxes