diff --git a/controllers/UsersApiController.php b/controllers/UsersApiController.php
index e09dba35..d8341c31 100644
--- a/controllers/UsersApiController.php
+++ b/controllers/UsersApiController.php
@@ -219,6 +219,19 @@ class UsersApiController extends BaseApiController
}
}
+ public function DeleteUserSetting(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
+ {
+ try
+ {
+ $value = $this->getUsersService()->DeleteUserSetting(GROCY_USER_ID, $args['settingKey']);
+ return $this->EmptyApiResponse($response);
+ }
+ catch (\Exception $ex)
+ {
+ return $this->GenericErrorResponse($response, $ex->getMessage());
+ }
+ }
+
public function __construct(\DI\Container $container)
{
parent::__construct($container);
diff --git a/grocy.openapi.json b/grocy.openapi.json
index fa83b6f7..28f0da1d 100644
--- a/grocy.openapi.json
+++ b/grocy.openapi.json
@@ -1299,6 +1299,38 @@
}
}
}
+ },
+ "delete": {
+ "summary": "Deletes the given setting of the currently logged in user",
+ "tags": [
+ "Current user"
+ ],
+ "parameters": [
+ {
+ "in": "path",
+ "name": "settingKey",
+ "required": true,
+ "description": "The key of the user setting",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "The operation was successful"
+ },
+ "400": {
+ "description": "The operation was not successful",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error400"
+ }
+ }
+ }
+ }
+ }
}
},
"/stock": {
diff --git a/localization/strings.pot b/localization/strings.pot
index b6951360..0511cc95 100644
--- a/localization/strings.pot
+++ b/localization/strings.pot
@@ -1822,7 +1822,7 @@ msgstr ""
msgid "Price per stock unit"
msgstr ""
-msgid "Hide/view columns"
+msgid "Table options"
msgstr ""
msgid "This product is currently on a shopping list"
@@ -2008,3 +2008,24 @@ msgstr ""
msgid "Show on stock overview page"
msgstr ""
+
+msgid "None"
+msgstr ""
+
+msgid "Group by"
+msgstr ""
+
+msgid "Ingredient group"
+msgstr ""
+
+msgid "Reset"
+msgstr ""
+
+msgid "Are you sure to reset the table options?"
+msgstr ""
+
+msgid "Table layout configuration"
+msgstr ""
+
+msgid "Hide/view columns"
+msgstr ""
diff --git a/public/js/grocy.js b/public/js/grocy.js
index 7684c0af..4766d8d6 100644
--- a/public/js/grocy.js
+++ b/public/js/grocy.js
@@ -433,6 +433,49 @@ Grocy.FrontendHelpers.ShowGenericError = function(message, exception)
console.error(exception);
}
+Grocy.FrontendHelpers.SaveUserSetting = function(settingsKey, value)
+{
+ Grocy.UserSettings[settingsKey] = value;
+
+ jsonData = {};
+ jsonData.value = value;
+ Grocy.Api.Put('user/settings/' + settingsKey, jsonData,
+ function(result)
+ {
+ // Nothing to do...
+ },
+ function(xhr)
+ {
+ if (!xhr.statusText.isEmpty())
+ {
+ Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
+ }
+ }
+ );
+}
+
+Grocy.FrontendHelpers.DeleteUserSetting = function(settingsKey, reloadPageOnSuccess = false)
+{
+ delete Grocy.UserSettings[settingsKey];
+
+ Grocy.Api.Delete('user/settings/' + settingsKey, {},
+ function(result)
+ {
+ if (reloadPageOnSuccess)
+ {
+ location.reload();
+ }
+ },
+ function(xhr)
+ {
+ if (!xhr.statusText.isEmpty())
+ {
+ Grocy.FrontendHelpers.ShowGenericError('Error while deleting, please retry', xhr.response)
+ }
+ }
+ );
+}
+
$(document).on("keyup paste change", "input, textarea", function()
{
$(this).closest("form").addClass("is-dirty");
@@ -468,23 +511,7 @@ $(document).on("change", ".user-setting-control", function()
var value = element.val();
}
- Grocy.UserSettings[settingKey] = value;
-
- jsonData = {};
- jsonData.value = value;
- Grocy.Api.Put('user/settings/' + settingKey, jsonData,
- function(result)
- {
- // Nothing to do...
- },
- function(xhr)
- {
- if (!xhr.statusText.isEmpty())
- {
- Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
- }
- }
- );
+ Grocy.FrontendHelpers.SaveUserSetting(settingKey, value);
});
// Show file name Bootstrap custom file input
@@ -713,25 +740,15 @@ $.extend(true, $.fn.dataTable.defaults, {
'stateSaveCallback': function(settings, data)
{
var settingKey = 'datatables_state_' + settings.sTableId;
- var stateData = JSON.stringify(data);
-
- Grocy.UserSettings[settingKey] = stateData;
-
- jsonData = {};
- jsonData.value = stateData;
- Grocy.Api.Put('user/settings/' + settingKey, jsonData,
- function(result)
- {
- // Nothing to do...
- },
- function(xhr)
- {
- if (!xhr.statusText.isEmpty())
- {
- Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
- }
- }
- );
+ if ($.isEmptyObject(data))
+ {
+ //state.clear was called and unfortunately the table is not refresh, so we are reloading the page
+ Grocy.FrontendHelpers.DeleteUserSetting(settingKey, true);
+ } else
+ {
+ var stateData = JSON.stringify(data);
+ Grocy.FrontendHelpers.SaveUserSetting(settingKey, stateData);
+ }
},
'stateLoadCallback': function(settings, data)
{
@@ -746,9 +763,45 @@ $.extend(true, $.fn.dataTable.defaults, {
return JSON.parse(Grocy.UserSettings[settingKey]);
}
},
+ 'preDrawCallback': function(settings)
+ {
+ // Currently it is not possible to save the state of rowGroup via saveState events
+ var api = new $.fn.dataTable.Api(settings);
+ if (typeof api.rowGroup === "function")
+ {
+ var settingKey = 'datatables_rowGroup_' + settings.sTableId;
+ if (Grocy.UserSettings[settingKey] !== undefined)
+ {
+ var rowGroup = JSON.parse(Grocy.UserSettings[settingKey]);
+
+ // Check if there way changed. the draw event is called often therefore we have to check if it's really necessary
+ if (rowGroup.enable !== api.rowGroup().enabled()
+ || ("dataSrc" in rowGroup && rowGroup.dataSrc !== api.rowGroup().dataSrc()))
+ {
+
+ api.rowGroup().enable(rowGroup.enable);
+
+ if ("dataSrc" in rowGroup)
+ {
+ api.rowGroup().dataSrc(rowGroup.dataSrc);
+
+ // Apply fixed order for group column
+ var fixedOrder = {
+ pre: [rowGroup.dataSrc, 'asc']
+ };
+
+ api.order.fixed(fixedOrder);
+ }
+ }
+ }
+ }
+ },
'columnDefs': [
{ type: 'chinese-string', targets: '_all' }
- ]
+ ],
+ 'rowGroup': {
+ enable: false
+ }
});
// serializeJSON defaults
@@ -847,6 +900,28 @@ $(".change-table-columns-visibility-button").on("click", function(e)
var dataTable = $(dataTableSelector).DataTable();
var columnCheckBoxesHtml = "";
+ var rowGroupRadioBoxesHtml = "";
+
+ var rowGroupDefined = typeof dataTable.rowGroup === "function";
+
+ if (rowGroupDefined)
+ {
+ var rowGroupChecked = (dataTable.rowGroup().enabled()) ? "" : "checked";
+ rowGroupRadioBoxesHtml = ' \
+
\
+ \
+ \
+
';
+ }
+
dataTable.columns().every(function()
{
var index = this.index();
@@ -864,7 +939,7 @@ $(".change-table-columns-visibility-button").on("click", function(e)
checked = "";
}
- columnCheckBoxesHtml += ''
+ ';
+
+ if (rowGroupDefined)
+ {
+ var rowGroupChecked = "";
+ if (dataTable.rowGroup().enabled() && dataTable.rowGroup().dataSrc() == index)
+ {
+ rowGroupChecked = "checked";
+ }
+
+ rowGroupRadioBoxesHtml += ' \
+ \
+ \
+ \
+
';
+ }
});
+ var message = '' + __t('Table options') + '
' + __t('Hide/view columns') + '
' + columnCheckBoxesHtml + '
';
+ if (rowGroupDefined)
+ {
+ message += '' + __t('Group by') + '
' + rowGroupRadioBoxesHtml + '
';
+ }
+
bootbox.dialog({
- message: '' + __t('Hide/view columns') + '
' + columnCheckBoxesHtml + '
',
+ message: message,
size: 'small',
backdrop: true,
closeButton: false,
+ onEscape: true,
buttons: {
- cancel: {
+ reset: {
+ label: __t('Reset'),
+ className: 'btn-outline-danger float-left responsive-button',
+ callback: function()
+ {
+ bootbox.confirm({
+ swapButtonOrder: true,
+ message: __t("Are you sure to reset the table options?"),
+ buttons: {
+ confirm: {
+ label: 'Yes',
+ className: 'btn-danger'
+ },
+ cancel: {
+ label: 'No',
+ className: 'btn-primary'
+ }
+ },
+ callback: function(result)
+ {
+ if (result)
+ {
+ var dataTable = $(dataTableSelector).DataTable();
+ var tableId = dataTable.settings()[0].sTableId;
+
+ // Delete rowgroup settings
+ Grocy.FrontendHelpers.DeleteUserSetting('datatables_rowGroup_' + tableId);
+
+ // Delete state settings
+ dataTable.state.clear();
+ }
+ bootbox.hideAll();
+ }
+ });
+ }
+ },
+ ok: {
label: __t('OK'),
className: 'btn-primary responsive-button',
callback: function()
@@ -896,6 +1036,7 @@ $(".change-table-columns-visibility-button").on("click", function(e)
}
});
});
+
$(document).on("click", ".change-table-columns-visibility-toggle", function()
{
var dataTableSelector = $(this).attr("data-table-selector");
@@ -904,3 +1045,45 @@ $(document).on("click", ".change-table-columns-visibility-toggle", function()
dataTable.columns(columnIndex).visible(this.checked);
});
+
+
+$(document).on("click", ".change-table-columns-rowgroup-toggle", function()
+{
+ var dataTableSelector = $(this).attr("data-table-selector");
+ var columnIndex = $(this).attr("data-column-index");
+ var dataTable = $(dataTableSelector).DataTable();
+ var rowGroup;
+
+ if (columnIndex == -1)
+ {
+ rowGroup = {
+ enable: false
+ };
+
+ dataTable.rowGroup().enable(false);
+
+ //remove fixed order
+ dataTable.order.fixed({});
+ }
+ else
+ {
+ rowGroup = {
+ enable: true,
+ dataSrc: columnIndex
+ }
+
+ dataTable.rowGroup().enable(true);
+ dataTable.rowGroup().dataSrc(columnIndex);
+
+ //apply fixed order for group column
+ var fixedOrder = {
+ pre: [columnIndex, 'asc']
+ };
+ dataTable.order.fixed(fixedOrder);
+ }
+
+ var settingKey = 'datatables_rowGroup_' + dataTable.settings()[0].sTableId;
+ Grocy.FrontendHelpers.SaveUserSetting(settingKey, JSON.stringify(rowGroup));
+
+ dataTable.draw();
+});
diff --git a/public/viewjs/productform.js b/public/viewjs/productform.js
index dac03001..8e7dfef8 100644
--- a/public/viewjs/productform.js
+++ b/public/viewjs/productform.js
@@ -275,6 +275,7 @@ var quConversionsTable = $('#qu-conversions-table-products').DataTable({
{ 'visible': false, 'targets': 4 }
].concat($.fn.dataTable.defaults.columnDefs),
'rowGroup': {
+ enable: true,
dataSrc: 4
}
});
diff --git a/public/viewjs/recipeform.js b/public/viewjs/recipeform.js
index 0df9a397..4833dac5 100644
--- a/public/viewjs/recipeform.js
+++ b/public/viewjs/recipeform.js
@@ -82,6 +82,7 @@ var recipesPosTables = $('#recipes-pos-table').DataTable({
{ 'visible': false, 'targets': 4 }
].concat($.fn.dataTable.defaults.columnDefs),
'rowGroup': {
+ enable: true,
dataSrc: 4
}
});
diff --git a/public/viewjs/shoppinglist.js b/public/viewjs/shoppinglist.js
index 35fb577b..084b7438 100644
--- a/public/viewjs/shoppinglist.js
+++ b/public/viewjs/shoppinglist.js
@@ -9,6 +9,7 @@ var shoppingListTable = $('#shoppinglist-table').DataTable({
{ 'visible': false, 'targets': 3 }
].concat($.fn.dataTable.defaults.columnDefs),
'rowGroup': {
+ enable: true,
dataSrc: 3,
startRender: function(rows, group)
{
diff --git a/public/viewjs/tasks.js b/public/viewjs/tasks.js
index 29cdb4e7..0f28445b 100644
--- a/public/viewjs/tasks.js
+++ b/public/viewjs/tasks.js
@@ -6,6 +6,7 @@
{ 'visible': false, 'targets': 3 }
].concat($.fn.dataTable.defaults.columnDefs),
'rowGroup': {
+ enable: true,
dataSrc: 3
}
});
diff --git a/routes.php b/routes.php
index e101b730..7d39a5ae 100644
--- a/routes.php
+++ b/routes.php
@@ -175,6 +175,7 @@ $app->group('/api', function (RouteCollectorProxy $group) {
$group->get('/user/settings', '\Grocy\Controllers\UsersApiController:GetUserSettings');
$group->get('/user/settings/{settingKey}', '\Grocy\Controllers\UsersApiController:GetUserSetting');
$group->put('/user/settings/{settingKey}', '\Grocy\Controllers\UsersApiController:SetUserSetting');
+ $group->delete('/user/settings/{settingKey}', '\Grocy\Controllers\UsersApiController:DeleteUserSetting');
// Stock
if (GROCY_FEATURE_FLAG_STOCK)
diff --git a/services/UsersService.php b/services/UsersService.php
index 2dd4c4f6..3c5e822d 100644
--- a/services/UsersService.php
+++ b/services/UsersService.php
@@ -114,6 +114,11 @@ class UsersService extends BaseService
}
}
+ public function DeleteUserSetting($userId, $settingKey)
+ {
+ $this->getDatabase()->user_settings()->where('user_id = :1 AND key = :2', $userId, $settingKey)->delete();
+ }
+
private function UserExists($userId)
{
$userRow = $this->getDatabase()->users()->where('id = :1', $userId)->fetch();
diff --git a/views/batteries.blade.php b/views/batteries.blade.php
index 33f8166d..8f949562 100644
--- a/views/batteries.blade.php
+++ b/views/batteries.blade.php
@@ -72,7 +72,7 @@
|
diff --git a/views/batteriesjournal.blade.php b/views/batteriesjournal.blade.php
index 4531e259..0e96eacb 100644
--- a/views/batteriesjournal.blade.php
+++ b/views/batteriesjournal.blade.php
@@ -68,7 +68,7 @@
|
diff --git a/views/batteriesoverview.blade.php b/views/batteriesoverview.blade.php
index 2a05ae9f..03f339f8 100644
--- a/views/batteriesoverview.blade.php
+++ b/views/batteriesoverview.blade.php
@@ -90,7 +90,7 @@
|
diff --git a/views/chores.blade.php b/views/chores.blade.php
index 553103da..09343c06 100644
--- a/views/chores.blade.php
+++ b/views/chores.blade.php
@@ -73,7 +73,7 @@
|
diff --git a/views/choresjournal.blade.php b/views/choresjournal.blade.php
index 0c8f0a96..717783fc 100644
--- a/views/choresjournal.blade.php
+++ b/views/choresjournal.blade.php
@@ -68,7 +68,7 @@
|
diff --git a/views/choresoverview.blade.php b/views/choresoverview.blade.php
index 3c6a79e0..d20605c0 100644
--- a/views/choresoverview.blade.php
+++ b/views/choresoverview.blade.php
@@ -112,7 +112,7 @@
|
diff --git a/views/equipment.blade.php b/views/equipment.blade.php
index 5ad96c06..ae0067bf 100644
--- a/views/equipment.blade.php
+++ b/views/equipment.blade.php
@@ -63,7 +63,7 @@
|
diff --git a/views/layout/default.blade.php b/views/layout/default.blade.php
index 173b20f8..9c4d1238 100644
--- a/views/layout/default.blade.php
+++ b/views/layout/default.blade.php
@@ -65,6 +65,8 @@
rel="stylesheet">
+
-
-
+
+
+
+
@@ -746,4 +750,4 @@
@endif