mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2024-12-25 08:21:08 -06:00
Users can now reorder budgets #1108
This commit is contained in:
parent
b12773bc99
commit
d0d2189d55
@ -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']);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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'];
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
*/
|
||||
|
93
public/js/ff/budgets/index.js
vendored
93
public/js/ff/budgets/index.js
vendored
@ -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 + '"> </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) {
|
||||
|
@ -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%;"> </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.
|
||||
|
@ -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']);
|
||||
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user