Users can now reorder budgets #1108

This commit is contained in:
James Cole 2018-10-17 15:18:09 +02:00
parent b12773bc99
commit d0d2189d55
7 changed files with 211 additions and 58 deletions

View File

@ -28,6 +28,7 @@ use Carbon\Carbon;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Support\Http\Controllers\DateCalculation;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
@ -63,7 +64,6 @@ class IndexController extends Controller
);
}
/**
* Show all budgets.
*
@ -134,5 +134,29 @@ class IndexController extends Controller
);
}
/**
* @param Request $request
*
* @return JsonResponse
*/
public function reorder(Request $request, BudgetRepositoryInterface $repository): JsonResponse
{
$budgetIds = $request->get('budgetIds');
$page = (int)$request->get('page');
$pageSize = (int)app('preferences')->get('listPageSize', 50)->data;
$currentOrder = (($page - 1) * $pageSize) + 1;
foreach ($budgetIds as $budgetId) {
$budgetId = (int)$budgetId;
$budget = $repository->findNull($budgetId);
if (null !== $budget) {
$repository->setBudgetOrder($budget, $currentOrder);
}
$currentOrder++;
}
return response()->json(['OK']);
}
}

View File

@ -42,6 +42,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
* @property-read string $email
* @property bool encrypted
* @property Collection budgetlimits
* @property int $order
*/
class Budget extends Model
{
@ -61,7 +62,7 @@ class Budget extends Model
'encrypted' => 'boolean',
];
/** @var array Fields that can be filled */
protected $fillable = ['user_id', 'name', 'active'];
protected $fillable = ['user_id', 'name', 'active','order'];
/** @var array Hidden from view */
protected $hidden = ['encrypted'];

View File

@ -107,6 +107,7 @@ class BudgetRepository implements BudgetRepositoryInterface
} catch (Exception $e) {
Log::debug(sprintf('Could not delete budget limit: %s', $e->getMessage()));
}
Budget::where('order',0)->update(['order' => 100]);
// do the clean up by hand because Sqlite can be tricky with this.
$budgetLimits = BudgetLimit::orderBy('created_at', 'DESC')->get(['id', 'budget_id', 'start_date', 'end_date']);
@ -289,11 +290,14 @@ class BudgetRepository implements BudgetRepositoryInterface
public function getActiveBudgets(): Collection
{
/** @var Collection $set */
$set = $this->user->budgets()->where('active', 1)->get();
$set = $this->user->budgets()->where('active', 1)
->get();
$set = $set->sortBy(
function (Budget $budget) {
return strtolower($budget->name);
$str = str_pad((string)$budget->order, 4, '0', STR_PAD_LEFT) . strtolower($budget->name);
return $str;
}
);
@ -554,7 +558,9 @@ class BudgetRepository implements BudgetRepositoryInterface
$set = $set->sortBy(
function (Budget $budget) {
return strtolower($budget->name);
$str = str_pad((string)$budget->order, 4, '0', STR_PAD_LEFT) . strtolower($budget->name);
return $str;
}
);
@ -583,7 +589,9 @@ class BudgetRepository implements BudgetRepositoryInterface
$set = $set->sortBy(
function (Budget $budget) {
return strtolower($budget->name);
$str = str_pad((string)$budget->order, 4, '0', STR_PAD_LEFT) . strtolower($budget->name);
return $str;
}
);
@ -652,6 +660,18 @@ class BudgetRepository implements BudgetRepositoryInterface
return $availableBudget;
}
/**
* @param Budget $budget
* @param int $order
*/
public function setBudgetOrder(Budget $budget, int $order): void
{
$budget->order = $order;
$budget->save();
}
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* @param User $user
*/
@ -660,7 +680,6 @@ class BudgetRepository implements BudgetRepositoryInterface
$this->user = $user;
}
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* @param Collection $budgets
* @param Collection $accounts
@ -825,6 +844,8 @@ class BudgetRepository implements BudgetRepositoryInterface
}
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* @param BudgetLimit $budgetLimit
* @param array $data
@ -848,7 +869,6 @@ class BudgetRepository implements BudgetRepositoryInterface
return $budgetLimit;
}
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* @param Budget $budget
* @param Carbon $start

View File

@ -156,7 +156,6 @@ interface BudgetRepositoryInterface
*/
public function getBudgetLimits(Budget $budget, Carbon $start = null, Carbon $end = null): Collection;
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* @param Collection $budgets
* @param Collection $accounts
@ -167,6 +166,8 @@ interface BudgetRepositoryInterface
*/
public function getBudgetPeriodReport(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): array;
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* @return Collection
*/
@ -195,7 +196,6 @@ interface BudgetRepositoryInterface
*/
public function getNoBudgetPeriodReport(Collection $accounts, Carbon $start, Carbon $end): array;
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* @param TransactionCurrency $currency
* @param Carbon $start
@ -206,6 +206,14 @@ interface BudgetRepositoryInterface
*/
public function setAvailableBudget(TransactionCurrency $currency, Carbon $start, Carbon $end, string $amount): AvailableBudget;
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* @param Budget $budget
* @param int $order
*/
public function setBudgetOrder(Budget $budget, int $order): void;
/**
* @param User $user
*/

View File

@ -48,8 +48,101 @@ $(function () {
}
});
// sortable!
if (typeof $(".sortable-table tbody").sortable !== "undefined") {
$(".sortable-table tbody").sortable(
{
helper: fixHelper,
items: 'tr:not(.ignore)',
stop: sortStop,
handle: '.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('<td colspan="' + cellCount + '">&nbsp;</td>');
}
}
);
}
});
var fixHelper = 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 sortStop(event, ui) {
"use strict";
//var current = $(ui.item);
var list = $('.sortable-table tbody tr');
var submit = [];
$.each(list, function (i, v) {
var row = $(v);
var id = parseInt(row.data('id'));
if (id > 0) {
submit.push(id);
}
});
var arr = {
budgetIds: submit,
page: page,
_token: token
};
// var thisDate = current.data('date');
// var originalBG = current.css('backgroundColor');
//
//
// if (current.prev().data('date') !== thisDate && current.next().data('date') !== thisDate) {
// // animate something with color:
// current.animate({backgroundColor: "#d9534f"}, 200, function () {
// $(this).animate({backgroundColor: originalBG}, 200);
// return undefined;
// });
//
// return false;
// }
//
// // do update
// var list = $('tr[data-date="' + thisDate + '"]');
// var submit = [];
// $.each(list, function (i, v) {
// var row = $(v);
// var id = row.data('id');
// submit.push(id);
// });
//
// // do extra animation when done?
$.get('budgets/reorder', arr);
//
// current.animate({backgroundColor: "#5cb85c"}, 200, function () {
// $(this).animate({backgroundColor: originalBG}, 200);
// return undefined;
// });
// return undefined;
//alert('drop!');
}
function drawSpentBar() {
"use strict";
if ($('.spentBar').length > 0) {

View File

@ -17,7 +17,8 @@
<small>{{ 'budgeted'|_ }}: <span id="budgetedAmount" class="text-success">{{ budgeted|formatAmountPlain }}</span></small>
</div>
<div class="col-lg-9 col-md-9 col-sm-9 col-xs-9" style="text-align:right;margin-bottom:3px;">
<small id="availableBar">{{ trans('firefly.available_between',{start: start.formatLocalized(monthAndDayFormat), end: end.formatLocalized(monthAndDayFormat) }) }}:
<small id="availableBar">{{ trans('firefly.available_between',{start: start.formatLocalized(monthAndDayFormat), end: end.formatLocalized(monthAndDayFormat) }) }}
:
<span id="available" data-value="{{ available }}">{{ available|formatAmountPlain }}</span>
<a href="#" class="updateIncome btn btn-default btn-xs"><i class="fa fa-pencil"></i></a>
<a href="#" class="infoIncome btn btn-info btn-xs"><i class="fa fa-info-circle"></i></a>
@ -39,7 +40,8 @@
</div>
<div class="row" id="spentBar">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<small>{{ trans('firefly.spent_between', {start: start.formatLocalized(monthAndDayFormat), end: end.formatLocalized(monthAndDayFormat)}) }}: {{ spent|formatAmount }}</small>
<small>{{ trans('firefly.spent_between', {start: start.formatLocalized(monthAndDayFormat), end: end.formatLocalized(monthAndDayFormat)}) }}
: {{ spent|formatAmount }}</small>
</div>
</div>
<div class="row">
@ -135,12 +137,12 @@
</div>
<div class="box-body no-padding">
<div style="padding:8px;">
<a href="{{ route('budgets.create') }}" class="btn btn-success"><i class="fa fa-plus fa-fw"></i> {{ 'createBudget'|_ }}</a>
<a href="{{ route('budgets.create') }}" class="btn btn-success"><i class="fa fa-plus fa-fw"></i> {{ 'createBudget'|_ }}</a>
</div>
<div style="padding-left:8px;">
{{ paginator.render|raw }}
</div>
<table class="table table-bordered table-striped sortable" id="budgetList">
<table class="table table-bordered sortable-table table-striped sortable" id="budgetList">
<thead>
<tr>
<th data-defaultsort="disabled" class="hidden-sm hidden-xs" style="width:10%;">&nbsp;</th>
@ -151,53 +153,53 @@
</tr>
</thead>
<tbody>
<tr>
{% for budget in paginator %}
<tr>
<td class="hidden-sm hidden-xs">
<div class="btn-group btn-group-xs">
<a href="{{ route('budgets.edit',budget.id) }}" class="btn btn-xs btn-default"><i class="fa fa-fw fa-pencil"></i></a>
<a href="{{ route('budgets.delete',budget.id) }}" class="btn btn-xs btn-danger"><i class="fa fa-fw fa-trash-o"></i></a>
</div>
</td>
<td data-value="{{ budget.name }}">
{% for budget in paginator %}
<tr data-id="{{ budget.id }}">
<td class="hidden-sm hidden-xs">
<div class="btn-group btn-group-xs">
<a href="#" class="handle btn btn-default"><i class="fa fa-fw fa-arrows-v"></i></a>
<a href="{{ route('budgets.edit',budget.id) }}" class="btn btn-xs btn-default"><i class="fa fa-fw fa-pencil"></i></a>
<a href="{{ route('budgets.delete',budget.id) }}" class="btn btn-xs btn-danger"><i class="fa fa-fw fa-trash-o"></i></a>
</div>
</td>
<td data-value="{{ budget.name }}">
{% if budgetInformation[budget.id]['currentLimit'] %}
<a href="{{ route('budgets.show.limit', [budget.id, budgetInformation[budget.id]['currentLimit'].id]) }}"
class="budget-link"
data-id="{{ budget.id }}">{{ budget.name }}</a>
{% else %}
<a href="{{ route('budgets.show',budget.id) }}" class="budget-link" data-id="{{ budget.id }}">{{ budget.name }}</a>
{% endif %}
</td>
{% if budgetInformation[budget.id]['currentLimit'] %}
<a href="{{ route('budgets.show.limit', [budget.id, budgetInformation[budget.id]['currentLimit'].id]) }}"
class="budget-link"
data-id="{{ budget.id }}">{{ budget.name }}</a>
{% set repAmount = budgetInformation[budget.id]['budgeted'] %}
{% else %}
<a href="{{ route('budgets.show',budget.id) }}" class="budget-link" data-id="{{ budget.id }}">{{ budget.name }}</a>
{% set repAmount = '0' %}
{% endif %}
</td>
{% if budgetInformation[budget.id]['currentLimit'] %}
{% set repAmount = budgetInformation[budget.id]['budgeted'] %}
{% else %}
{% set repAmount = '0' %}
{% endif %}
<td data-value="{{ repAmount }}">
<div class="input-group">
<div class="input-group-addon">{{ defaultCurrency.symbol|raw }}</div>
<input type="hidden" name="balance_currency_id" value="{{ defaultCurrency.id }}"/>
<input class="form-control budgetAmount" data-original="{{ repAmount }}"
data-id="{{ budget.id }}" value="{{ repAmount }}" autocomplete="off"
min="0" name="amount" type="number">
</div>
<span class="text-danger budget_warning" data-id="{{ budget.id }}" style="display:none;"></span>
</td>
<td class="hidden-sm hidden-xs spent" data-id="{{ budget.id }}" data-spent="{{ budgetInformation[budget.id]['spent'] }}"
data-value="{{ budgetInformation[budget.id]['spent'] }}">
{{ budgetInformation[budget.id]['spent']|formatAmount }}
({{ (budgetInformation[budget.id]['spent'] / activeDaysPassed)|formatAmount }})
</td>
<td class="left" data-id="{{ budget.id }}"
data-value="{{ (repAmount + budgetInformation[budget.id]['spent']) }}">
{{ (repAmount + budgetInformation[budget.id]['spent'])|formatAmount }}
{% if repAmount + budgetInformation[budget.id]['spent'] > 0 %}
({{ ((repAmount + budgetInformation[budget.id]['spent']) / activeDaysLeft)|formatAmount }})
{% endif %}
</td>
</tr>
<td data-value="{{ repAmount }}">
<div class="input-group">
<div class="input-group-addon">{{ defaultCurrency.symbol|raw }}</div>
<input type="hidden" name="balance_currency_id" value="{{ defaultCurrency.id }}"/>
<input class="form-control budgetAmount" data-original="{{ repAmount }}"
data-id="{{ budget.id }}" value="{{ repAmount }}" autocomplete="off"
min="0" name="amount" type="number">
</div>
<span class="text-danger budget_warning" data-id="{{ budget.id }}" style="display:none;"></span>
</td>
<td class="hidden-sm hidden-xs spent" data-id="{{ budget.id }}" data-spent="{{ budgetInformation[budget.id]['spent'] }}"
data-value="{{ budgetInformation[budget.id]['spent'] }}">
{{ budgetInformation[budget.id]['spent']|formatAmount }}
({{ (budgetInformation[budget.id]['spent'] / activeDaysPassed)|formatAmount }})
</td>
<td class="left" data-id="{{ budget.id }}"
data-value="{{ (repAmount + budgetInformation[budget.id]['spent']) }}">
{{ (repAmount + budgetInformation[budget.id]['spent'])|formatAmount }}
{% if repAmount + budgetInformation[budget.id]['spent'] > 0 %}
({{ ((repAmount + budgetInformation[budget.id]['spent']) / activeDaysLeft)|formatAmount }})
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
@ -206,7 +208,7 @@
</div>
</div>
<div class="box-footer">
<a href="{{ route('budgets.create') }}" class="btn btn-success"><i class="fa fa-plus fa-fw"></i> {{ 'createBudget'|_ }}</a>
<a href="{{ route('budgets.create') }}" class="btn btn-success"><i class="fa fa-plus fa-fw"></i> {{ 'createBudget'|_ }}</a>
</div>
</div>
</div>
@ -239,6 +241,7 @@
{% endblock %}
{% block scripts %}
<script src="js/lib/jquery-ui.min.js?v={{ FF_VERSION }}" type="text/javascript"></script>
<script type="text/javascript">
// actually spent bar data:
var spent = {{ spent * -1 }}; // must be positive for the calculation to work.

View File

@ -214,6 +214,8 @@ Route::group(
Route::get('list/no-budget/all', ['uses' => 'Budget\ShowController@noBudgetAll', 'as' => 'no-budget-all']);
Route::get('list/no-budget/{start_date?}/{end_date?}', ['uses' => 'Budget\ShowController@noBudget', 'as' => 'no-budget']);
// reorder budgets
Route::get('reorder', ['uses' => 'Budget\IndexController@reorder', 'as' => 'reorder']);
// index
Route::get('{start_date?}/{end_date?}', ['uses' => 'Budget\IndexController@index', 'as' => 'index']);
@ -223,6 +225,8 @@ Route::group(
Route::get('info/{start_date}/{end_date}', ['uses' => 'Budget\AmountController@infoIncome', 'as' => 'income.info']);
Route::post('income', ['uses' => 'Budget\AmountController@postUpdateIncome', 'as' => 'income.post']);
Route::post('amount/{budget}', ['uses' => 'Budget\AmountController@amount', 'as' => 'amount']);
}
);