From bf4b8348d2b98bf52acb78c6d2342ade83a8fe66 Mon Sep 17 00:00:00 2001 From: adisposkofh Date: Wed, 3 Jun 2026 09:18:40 +0200 Subject: [PATCH 01/24] defined cis profile tabulator columns collapse order --- .../Cis/Profil/MitarbeiterProfil.js | 30 ++++++++++++------- .../Cis/Profil/MitarbeiterViewProfil.js | 18 +++++++---- .../js/components/Cis/Profil/StudentProfil.js | 10 +++++-- 3 files changed, 39 insertions(+), 19 deletions(-) diff --git a/public/js/components/Cis/Profil/MitarbeiterProfil.js b/public/js/components/Cis/Profil/MitarbeiterProfil.js index ed63850c7..b9ebc7dee 100644 --- a/public/js/components/Cis/Profil/MitarbeiterProfil.js +++ b/public/js/components/Cis/Profil/MitarbeiterProfil.js @@ -56,7 +56,8 @@ export default { formatter: "responsiveCollapse", maxWidth: 40, headerClick: this.collapseFunction, - visible: true + visible: true, + responsive: 0, }, { title: "placeholder", @@ -64,7 +65,8 @@ export default { field: "Bezeichnung", headerFilter: true, minWidth: 200, - visible: true + visible: true, + responsive: 0, }, { title: "placeholder", @@ -72,7 +74,8 @@ export default { field: "Organisationseinheit", headerFilter: true, minWidth: 200, - visible: true + visible: true, + responsive: 1, }, { title: "placeholder", @@ -84,7 +87,8 @@ export default { minWidth: 200, visible: true, formatter:"datetime", - formatterParams: this.datetimeFormatterParams() + formatterParams: this.datetimeFormatterParams(), + responsive: 4, }, { title: "placeholder", @@ -96,7 +100,8 @@ export default { minWidth: 200, visible: true, formatter:"datetime", - formatterParams: this.datetimeFormatterParams() + formatterParams: this.datetimeFormatterParams(), + responsive: 3, }, { title: "placeholder", @@ -104,7 +109,8 @@ export default { field: "Wochenstunden", headerFilter: true, minWidth: 200, - visible: true + visible: true, + responsive: 2, }, ], }, @@ -131,7 +137,8 @@ export default { formatter: "responsiveCollapse", maxWidth: 40, headerClick: this.collapseFunction, - visible: true + visible: true, + responsive: 0, }, { title: "placeholder", @@ -139,7 +146,8 @@ export default { field: "betriebsmittel", headerFilter: true, minWidth: 200, - visible: true + visible: true, + responsive: 0, }, { title: "placeholder", @@ -148,7 +156,8 @@ export default { headerFilter: true, resizable: true, minWidth: 200, - visible: true + visible: true, + responsive: 2, }, { title: "placeholder", @@ -159,7 +168,8 @@ export default { minWidth: 200, visible: true, formatter:"datetime", - formatterParams: this.datetimeFormatterParams() + formatterParams: this.datetimeFormatterParams(), + responsive: 1, }, ], } diff --git a/public/js/components/Cis/Profil/MitarbeiterViewProfil.js b/public/js/components/Cis/Profil/MitarbeiterViewProfil.js index 8670a2808..c4934c747 100644 --- a/public/js/components/Cis/Profil/MitarbeiterViewProfil.js +++ b/public/js/components/Cis/Profil/MitarbeiterViewProfil.js @@ -43,7 +43,8 @@ export default { formatter: "responsiveCollapse", maxWidth: 40, headerClick: this.collapseFunction, - visible: true + visible: true, + responsive: 0, }, { title: "placeholder", @@ -51,7 +52,8 @@ export default { field: "Bezeichnung", headerFilter: true, minWidth: 200, - visible: true + visible: true, + responsive: 0, }, { title: "placeholder", @@ -59,7 +61,8 @@ export default { field: "Organisationseinheit", headerFilter: true, minWidth: 200, - visible: true + visible: true, + responsive: 1, }, { title: "placeholder", @@ -71,7 +74,8 @@ export default { minWidth: 200, visible: true, formatter:"datetime", - formatterParams: this.datetimeFormatterParams() + formatterParams: this.datetimeFormatterParams(), + responsive: 4, }, { title: "placeholder", @@ -83,7 +87,8 @@ export default { minWidth: 200, visible: true, formatter:"datetime", - formatterParams: this.datetimeFormatterParams() + formatterParams: this.datetimeFormatterParams(), + responsive: 3, }, { title: "placeholder", @@ -91,7 +96,8 @@ export default { field: "Wochenstunden", headerFilter: true, minWidth: 200, - visible: true + visible: true, + responsive: 2, }, ], }, diff --git a/public/js/components/Cis/Profil/StudentProfil.js b/public/js/components/Cis/Profil/StudentProfil.js index f448d01b1..5356b24b3 100644 --- a/public/js/components/Cis/Profil/StudentProfil.js +++ b/public/js/components/Cis/Profil/StudentProfil.js @@ -68,6 +68,7 @@ export default { formatter: "responsiveCollapse", maxWidth: 40, headerClick: this.collapseFunction, + responsive: 0, }, { title: "placeholder", @@ -75,7 +76,8 @@ export default { field: "betriebsmittel", headerFilter: true, minWidth: 200, - visible: true + visible: true, + responsive: 0, }, { title: "placeholder", @@ -84,7 +86,8 @@ export default { headerFilter: true, resizable: true, minWidth: 200, - visible: true + visible: true, + responsive: 2, }, { title: "placeholder", @@ -95,7 +98,8 @@ export default { minWidth: 200, visible: true, formatter:"datetime", - formatterParams: this.datetimeFormatterParams() + formatterParams: this.datetimeFormatterParams(), + responsive: 1, }, ], }, From b3ae1b74ab032468d1e02e9fb39db692fe593bcd Mon Sep 17 00:00:00 2001 From: adisposkofh Date: Wed, 3 Jun 2026 16:53:55 +0200 Subject: [PATCH 02/24] implemented basic tabulator table presets ui --- .../Cis/Profil/MitarbeiterProfil.js | 2 + public/js/components/filter/Filter.js | 22 ++- .../components/filter/Filter/TablePresets.js | 178 ++++++++++++++++++ 3 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 public/js/components/filter/Filter/TablePresets.js diff --git a/public/js/components/Cis/Profil/MitarbeiterProfil.js b/public/js/components/Cis/Profil/MitarbeiterProfil.js index b9ebc7dee..4d719fcc1 100644 --- a/public/js/components/Cis/Profil/MitarbeiterProfil.js +++ b/public/js/components/Cis/Profil/MitarbeiterProfil.js @@ -446,6 +446,7 @@ export default { :tabulator-options="funktionen_table_options" tableOnly :sideMenu="false" + :isUsingPresets="true" />
@@ -457,6 +458,7 @@ export default { :tabulator-options="betriebsmittel_table_options" tableOnly :sideMenu="false" + :isUsingPresets="true" />
diff --git a/public/js/components/filter/Filter.js b/public/js/components/filter/Filter.js index 5f0447aea..3027ef377 100644 --- a/public/js/components/filter/Filter.js +++ b/public/js/components/filter/Filter.js @@ -18,6 +18,7 @@ import {CoreFetchCmpt} from '../../components/Fetch.js'; import FilterConfig from './Filter/Config.js'; import FilterColumns from './Filter/Columns.js'; +import TablePresets from './Filter/TablePresets.js'; import TableDownload from './Table/Download.js'; import collapseAutoClose from '../../directives/collapseAutoClose.js'; @@ -45,7 +46,8 @@ export const CoreFilterCmpt = { CoreFetchCmpt, FilterConfig, FilterColumns, - TableDownload + TablePresets, + TableDownload, }, directives: { collapseAutoClose @@ -91,6 +93,10 @@ export const CoreFilterCmpt = { type: Boolean, default: true }, + isUsingPresets: { + Boolean, + default: false, + }, }, inject: ["language"], data: function() { @@ -841,7 +847,7 @@ export const CoreFilterCmpt = { }); }, - template: ` + template: /*html*/ ` + + + + + . + */ + +/** + * + */ +export default { + name: "TablePresets", + props: ["identifier", "tabulator"], + emits: {}, + data: function () { + return { + // todo: remove once actual data is fetched + testDataForUIDev: [ + { + id: null, + name: "Test Preset 1", + displayedColumns: { + testCol1: true, + testCol2: false, + testCol3: true, + testCol4: false, + testCol5: true, + }, + filters: { + testCol3: "adis", + testCol5: "01.06.2026.-31.12.2026.", + }, + columnsOrder: ["testCol3", "testCol5", "testCol1"], + sort: { + column: "testCol1", + direction: "asc", + }, + isUserCustom: false, + }, + { + id: 1, + name: "Test Preset 2", + displayedColumns: { + testCol1: true, + testCol2: true, + testCol3: true, + testCol4: false, + testCol5: false, + }, + filters: { + testCol1: "posko", + }, + columnsOrder: ["testCol1", "testCol3", "testCol2"], + sort: { + column: "testCol3", + direction: "desc", + }, + }, + ], + presetInfo: null, + }; + }, + computed: { + presetInfoFormattedStrings() { + if (!this.presetInfo) return null; + + const displayedColumns = Object.keys(this.presetInfo.displayedColumns).filter((column) => this.presetInfo.displayedColumns[column]); + const filters = Object.keys(this.presetInfo.filters).map((column) => column + ": \"" + this.presetInfo.filters[column] + "\""); + return { + displayedColumns: "Displayed columns: " + displayedColumns.join(", "), + columnsOrder: "Columns ordering: " + this.presetInfo.columnsOrder.join(", "), + filters: "Column filters: " + filters.join(", "), + sort: "Sort: " + this.presetInfo.sort.column + " (" + (this.presetInfo.sort.direction === "asc" ? "ascending" : "descending") + ")", + }; + }, + }, + watch: { + // todo: watch presets, once they change nullify presetInfo and collapse preset info section + }, + methods: { + async showPresetInfo(preset) { + if (preset !== this.presetInfo) { + this.presetInfo = preset; + } + await this.$nextTick(); + if (!this.$refs.presetInfoCollapsible.getAttribute("class").split(" ").includes("show")) { + this.togglePresetInfoCollapsible(); + } + }, + hidePresetInfo() { + this.presetInfo = null; + this.togglePresetInfoCollapsible(); + }, + togglePresetInfoCollapsible() { + new bootstrap.Collapse("#presetInfoCollapsible" + this.$props.identifier, {toggle: true}); + }, + deletePreset(preset) { + // todo + }, + showNewPresetForm() { + // todo + if (!this.$refs.newPresetFormCollapsible.getAttribute("class").split(" ").includes("show")) { + this.toggleNewPresetFormCollapsible(); + } + + }, + toggleNewPresetFormCollapsible() { + new bootstrap.Collapse("#newPresetFormCollapsible" + this.$props.identifier, {toggle: true}); + }, + applyPreset() { + // todo + }, + }, + template: /*html*/ ` +
+
+
Table Presets
+
+
+
+
{{ preset.name }}
+
+
+ + +
+
+
+
+
+ + {{ "Save current configuration" }} +
+
+
+
+
+
+
+
+ {{ presetInfo?.name }} + +
+ {{ presetInfoFormattedStrings?.displayedColumns }} + {{ presetInfoFormattedStrings?.columnsOrder }} + {{ presetInfoFormattedStrings?.filters }} + {{ presetInfoFormattedStrings?.sort }} +
+
+
+
+ +
+
+
+
+ {{ "Save current configuration" }} + +
+ adis +
+
+
+
+
+
+ `, +}; From 0bb81b23e55d46b6623de880f53cacb935cedc97 Mon Sep 17 00:00:00 2001 From: adisposkofh Date: Tue, 9 Jun 2026 16:07:55 +0200 Subject: [PATCH 03/24] implemented table presets functionality --- .../api/frontend/v1/TabulatorPresets.php | 124 ++++++ .../tabulator/Tabulator_preset_model.php | 76 ++++ public/js/api/factory/tabulatorPresets.js | 48 +++ .../Cis/Profil/MitarbeiterProfil.js | 1 - public/js/components/filter/Filter.js | 17 +- .../components/filter/Filter/TablePresets.js | 373 ++++++++++++++---- system/dbupdate_3.4.php | 2 + .../77080_tabulator_presets_table.php | 55 +++ 8 files changed, 619 insertions(+), 77 deletions(-) create mode 100644 application/controllers/api/frontend/v1/TabulatorPresets.php create mode 100644 application/models/tabulator/Tabulator_preset_model.php create mode 100644 public/js/api/factory/tabulatorPresets.js create mode 100644 system/dbupdate_3.4/77080_tabulator_presets_table.php diff --git a/application/controllers/api/frontend/v1/TabulatorPresets.php b/application/controllers/api/frontend/v1/TabulatorPresets.php new file mode 100644 index 000000000..05de9bde7 --- /dev/null +++ b/application/controllers/api/frontend/v1/TabulatorPresets.php @@ -0,0 +1,124 @@ +. + */ + +if (! defined('BASEPATH')) exit('No direct script access allowed'); + +/** + * This controller operates between (interface) the JS (GUI) and the PhrasesLib (back-end) + * Provides data to the ajax get calls about the Phrasen plugin + * This controller works with JSON calls on the HTTP GET and the output is always JSON + */ +class TabulatorPresets extends FHCAPI_Controller +{ + public function __construct() + { + parent::__construct([ + 'getTabulatorPresets' => self::PERM_LOGGED, + 'createTabulatorPreset' => self::PERM_LOGGED, + 'deleteTabulatorPreset' => self::PERM_LOGGED, + ]); + + $this->load->model('tabulator/Tabulator_preset_model', 'TabulatorPresetModel'); + } + + //------------------------------------------------------------------------------------------------------------------ + // Public methods + + public function getTabulatorPresets() + { + $tableName = $this->input->get("tableName"); + if (!$tableName) { + $this->terminateWithError(error("Invalid parameters!")); + } + + $uid = getAuthUID(); + $presets = $this->TabulatorPresetModel->getTabulatorPresetsByUserAndTable($uid, $tableName); + $presetsData = $this->getDataOrTerminateWithError($presets) ?? []; + + $this->terminateWithSuccess($presetsData); + } + + public function createTabulatorPreset() + { + $tableName = $this->input->post("tableName"); + $presetName = $this->input->post("presetName"); + $preset = $this->input->post("preset"); + + if (!$tableName || !$presetName || !$preset) { + $this->terminateWithError("Invalid parameters!", "general", 400); + } + + $uid = getAuthUID(); + + $existingPresetsResult = $this->TabulatorPresetModel->getTabulatorPresetsByUserAndTable($uid, $tableName); + $existingPresets = $this->getDataOrTerminateWithError($existingPresetsResult) ?? []; + + if (count($existingPresets) > 19) { + $this->terminateWithError("Maximum number of presets reached!", "general", 409); + } + + $existingPresetNames = array_map(function($presetInfo) { + return $presetInfo->preset_name; + }, $existingPresets); + + if (in_array($presetName, $existingPresetNames)) { + $this->terminateWithError("Preset name duplicate not allowed!", "general", 409); + } + + $presetCreationResult = $this->TabulatorPresetModel->createTabulatorPreset($uid, $tableName, $presetName, $preset); + + if (isError($presetCreationResult)) { + $this->terminateWithError($presetCreationResult->retval); + } + + $newPresetData = $this->TabulatorPresetModel->getTabulatorPreset($presetCreationResult->retval); + $newPresetArray = $this->getDataOrTerminateWithError($newPresetData) ?? [null]; + $newPreset = $newPresetArray[0]; + + $this->terminateWithSuccess($newPreset); + } + + public function deleteTabulatorPreset() + { + $presetId = $this->input->post("presetId"); + if (!$presetId) { + $this->terminateWithError("Invalid parameters!", "general", 400); + } + + $presetData = $this->TabulatorPresetModel->getTabulatorPreset($presetId); + $presetArray = $this->getDataOrTerminateWithError($presetData) ?? [null]; + $preset = $presetArray[0]; + if (!$preset) { + $this->terminateWithError("Preset not found!", "general", 404); + } + + $uid = getAuthUID(); + $this->addMeta("preset", $preset); + if ($preset->benutzer_uid !== $uid) { + $this->terminateWithError("You are not allowed to delete this preset!", "general", 403); + } + + $presetDeletionResult = $this->TabulatorPresetModel->deleteTabulatorPreset($presetId); + if (isError($presetDeletionResult)) { + $this->terminateWithError($presetDeletionResult->retval); + } + + $this->terminateWithSuccess(); + } + +} \ No newline at end of file diff --git a/application/models/tabulator/Tabulator_preset_model.php b/application/models/tabulator/Tabulator_preset_model.php new file mode 100644 index 000000000..7a3de5491 --- /dev/null +++ b/application/models/tabulator/Tabulator_preset_model.php @@ -0,0 +1,76 @@ +dbTable = 'public.tbl_tabulator_presets'; + $this->pk = ['preset_id']; + $this->hasSequence = true; + + + $this->load->model('person/Benutzer_model', 'BenutzerModel'); + } + + public function getTabulatorPreset($presetId) + { + $query = " + SELECT * + FROM public.tbl_tabulator_presets + WHERE preset_id = ? + LIMIT 1 + "; + return $this->execQuery($query, array($presetId)); + } + + public function getTabulatorPresetsByUserAndTable($uid, $tableName) + { + $query = ' + SELECT * + FROM public.tbl_tabulator_presets + WHERE benutzer_uid = ? + AND table_name = ? + ORDER BY preset_id ASC + '; + return $this->execQuery($query, array($uid, $tableName)); + } + + public function createTabulatorPreset($uid, $tableName, $presetName, $preset) + { + $this->db->trans_start(false); + + $result = $this->insert([ + "benutzer_uid" => $uid, + "table_name" => $tableName, + "preset_name" => $presetName, + "preset_json" => json_encode($preset), + ]); + + $this->db->trans_complete(); + + if ($this->db->trans_status() === false) + { + $this->db->trans_rollback(); + return error('Something went wrong with tabulator preset creation.'); + } + + return success($result->retval); + } + + public function deleteTabulatorPreset($presetId) + { + $presetDeletionReseult = $this->delete(["preset_id" => $presetId]); + + if (isError($presetDeletionReseult)) + { + return error('Something went wrong during tabulator preset deletion.'); + } + + return success($presetDeletionReseult->retval); + } + +} diff --git a/public/js/api/factory/tabulatorPresets.js b/public/js/api/factory/tabulatorPresets.js new file mode 100644 index 000000000..1ee32550e --- /dev/null +++ b/public/js/api/factory/tabulatorPresets.js @@ -0,0 +1,48 @@ +/** + * Copyright (C) 2025 fhcomplete.org + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +export default { + getTabulatorPresets(tableName) { + return { + method: "get", + url: + "/api/frontend/v1/TabulatorPresets/getTabulatorPresets?tableName=" + + tableName, + }; + }, + createTabulatorPreset(tableName, presetName, preset) { + return { + method: "post", + url: "/api/frontend/v1/TabulatorPresets/createTabulatorPreset", + params: { + tableName, + presetName, + preset, + }, + }; + }, + deleteTabulatorPreset(tabulatorPresetId) { + return { + method: "post", + url: + "/api/frontend/v1/TabulatorPresets/deleteTabulatorPreset", + params: { + presetId: tabulatorPresetId, + }, + }; + }, +}; diff --git a/public/js/components/Cis/Profil/MitarbeiterProfil.js b/public/js/components/Cis/Profil/MitarbeiterProfil.js index 4d719fcc1..7095380c3 100644 --- a/public/js/components/Cis/Profil/MitarbeiterProfil.js +++ b/public/js/components/Cis/Profil/MitarbeiterProfil.js @@ -446,7 +446,6 @@ export default { :tabulator-options="funktionen_table_options" tableOnly :sideMenu="false" - :isUsingPresets="true" />
diff --git a/public/js/components/filter/Filter.js b/public/js/components/filter/Filter.js index 3027ef377..9b21243dd 100644 --- a/public/js/components/filter/Filter.js +++ b/public/js/components/filter/Filter.js @@ -97,6 +97,14 @@ export const CoreFilterCmpt = { Boolean, default: false, }, + presetsId: { + String, + default: "", + }, + generalPresets: { + type: Array, + default: [] + }, }, inject: ["language"], data: function() { @@ -823,6 +831,9 @@ export const CoreFilterCmpt = { return res; }, {}); }, + afterTablePresetApplied(preset) { + this.selectedFields = preset.displayedColumns; + }, }, beforeCreate() { if (!this.tableOnly == !this.filterType) @@ -894,7 +905,7 @@ export const CoreFilterCmpt = { - + @@ -902,10 +913,12 @@ export const CoreFilterCmpt = {
diff --git a/public/js/components/filter/Filter/TablePresets.js b/public/js/components/filter/Filter/TablePresets.js index d5fcb2aa4..d47e223f0 100644 --- a/public/js/components/filter/Filter/TablePresets.js +++ b/public/js/components/filter/Filter/TablePresets.js @@ -15,111 +15,332 @@ * along with this program. If not, see . */ +import ApiTabulatorPresets from "../../../api/factory/tabulatorPresets.js"; + /** * */ export default { name: "TablePresets", - props: ["identifier", "tabulator"], - emits: {}, + props: { + presetsId: { type: String }, + tabulator: { type: Object }, + generalPresets: { type: Array, default: [] }, + }, + emits: ["applyTablePreset"], data: function () { return { - // todo: remove once actual data is fetched - testDataForUIDev: [ - { - id: null, - name: "Test Preset 1", - displayedColumns: { - testCol1: true, - testCol2: false, - testCol3: true, - testCol4: false, - testCol5: true, - }, - filters: { - testCol3: "adis", - testCol5: "01.06.2026.-31.12.2026.", - }, - columnsOrder: ["testCol3", "testCol5", "testCol1"], - sort: { - column: "testCol1", - direction: "asc", - }, - isUserCustom: false, - }, - { - id: 1, - name: "Test Preset 2", - displayedColumns: { - testCol1: true, - testCol2: true, - testCol3: true, - testCol4: false, - testCol5: false, - }, - filters: { - testCol1: "posko", - }, - columnsOrder: ["testCol1", "testCol3", "testCol2"], - sort: { - column: "testCol3", - direction: "desc", - }, - }, - ], + customUserPresets: [], presetInfo: null, + newPreset: null, + newPresetName: "", }; }, computed: { + allPresets() { + return this.$props.generalPresets.concat(this.customUserPresets); + }, presetInfoFormattedStrings() { if (!this.presetInfo) return null; - const displayedColumns = Object.keys(this.presetInfo.displayedColumns).filter((column) => this.presetInfo.displayedColumns[column]); - const filters = Object.keys(this.presetInfo.filters).map((column) => column + ": \"" + this.presetInfo.filters[column] + "\""); + const columnTitles = this.getColumnTitles(); + + const displayedColumns = this.presetInfo.displayedColumns.map( + (columnField) => columnTitles[columnField], + ); + + const headerFilters = Object.keys( + this.presetInfo.headerFilters, + ).map( + (column) => + columnTitles[column] + + '["' + + this.presetInfo.headerFilters[column] + + '"]', + ); return { - displayedColumns: "Displayed columns: " + displayedColumns.join(", "), - columnsOrder: "Columns ordering: " + this.presetInfo.columnsOrder.join(", "), - filters: "Column filters: " + filters.join(", "), - sort: "Sort: " + this.presetInfo.sort.column + " (" + (this.presetInfo.sort.direction === "asc" ? "ascending" : "descending") + ")", + displayedColumns: + "Displayed columns (in order): " + + displayedColumns.join(", "), + headerFilters: + "Column header filters: " + headerFilters.join(", "), + sort: + "Sort: " + + columnTitles[this.presetInfo.sort.column] + + " (" + + (this.presetInfo.sort.direction === "asc" + ? "ascending" + : "descending") + + ")", + }; + }, + newPresetFormattedStrings() { + if (!this.newPreset) return null; + + const columnTitles = this.getColumnTitles(); + + const displayedColumns = this.newPreset.displayedColumns.map( + (columnField) => columnTitles[columnField], + ); + + const headerFilters = Object.keys(this.newPreset.headerFilters).map( + (column) => + columnTitles[column] + + '["' + + this.newPreset.headerFilters[column] + + '"]', + ); + return { + displayedColumns: + "Displayed columns (in order): " + + displayedColumns.join(", "), + headerFilters: + "Column header filters: " + headerFilters.join(", "), + sort: + "Sort: " + + (this.newPreset.sort + ? columnTitles[this.newPreset.sort.column] + + " (" + + (this.newPreset.sort.direction === "asc" + ? "ascending" + : "descending") + + ")" + : ""), }; }, }, watch: { - // todo: watch presets, once they change nullify presetInfo and collapse preset info section + allPresets: { + handler() { + this.hidePresetInfo(); + this.hideNewPresetForm(); + }, + deep: true, + }, }, methods: { async showPresetInfo(preset) { if (preset !== this.presetInfo) { this.presetInfo = preset; } + + this.hideNewPresetForm(); + await this.$nextTick(); - if (!this.$refs.presetInfoCollapsible.getAttribute("class").split(" ").includes("show")) { + + if ( + !this.$refs.presetInfoCollapsible + .getAttribute("class") + .split(" ") + .includes("show") + ) { this.togglePresetInfoCollapsible(); } }, - hidePresetInfo() { + async hidePresetInfo() { + if ( + this.$refs.presetInfoCollapsible + .getAttribute("class") + .split(" ") + .includes("show") + ) { + this.togglePresetInfoCollapsible(); + } + await this.$nextTick(); this.presetInfo = null; - this.togglePresetInfoCollapsible(); }, togglePresetInfoCollapsible() { - new bootstrap.Collapse("#presetInfoCollapsible" + this.$props.identifier, {toggle: true}); + new bootstrap.Collapse( + "#presetInfoCollapsible_" + this.$props.presetsId, + { toggle: true }, + ); }, - deletePreset(preset) { - // todo - }, - showNewPresetForm() { - // todo - if (!this.$refs.newPresetFormCollapsible.getAttribute("class").split(" ").includes("show")) { + async showNewPresetForm() { + this.generateNewPreset(); + + this.hidePresetInfo(); + + await this.$nextTick(); + + if ( + !this.$refs.newPresetFormCollapsible + .getAttribute("class") + .split(" ") + .includes("show") + ) { this.toggleNewPresetFormCollapsible(); } + }, + generateNewPreset() { + const columns = this.tabulator + .getColumnDefinitions() + .filter((column) => column.field !== "collapse"); + const displayedColumns = columns + .filter((column) => column.visible) + .map((column) => column.field); + + const unparsedHeaderFilters = + this.tabulator.modules.filter.headerFilters; + const headerFilterFieldValuePairs = Object.entries( + unparsedHeaderFilters, + ).map(([columnField, filter]) => [columnField, filter.value]); + const headerFiltersFieldValuePairsExcludingHiddenColumns = + headerFilterFieldValuePairs.filter( + ([columnField, filterValue]) => { + return displayedColumns.includes(columnField); + }, + ); + const headerFilters = Object.fromEntries( + headerFiltersFieldValuePairsExcludingHiddenColumns, + ); + + const activeSort = this.tabulator.getSorters()[0]; + const sort = activeSort + ? { + column: activeSort.field, + direction: activeSort.dir, + } + : null; + + this.newPreset = { + displayedColumns, + headerFilters, + sort, + }; + }, + async hideNewPresetForm() { + if ( + this.$refs.newPresetFormCollapsible + .getAttribute("class") + .split(" ") + .includes("show") + ) { + this.toggleNewPresetFormCollapsible(); + } }, toggleNewPresetFormCollapsible() { - new bootstrap.Collapse("#newPresetFormCollapsible" + this.$props.identifier, {toggle: true}); + this.newPresetName = ""; + new bootstrap.Collapse( + "#newPresetFormCollapsible_" + this.$props.presetsId, + { toggle: true }, + ); }, - applyPreset() { + async createPreset() { + if (!this.newPresetName?.length) return; + + const presetCreationResponse = await this.$api.call( + ApiTabulatorPresets.createTabulatorPreset( + this.$props.presetsId, + this.newPresetName, + this.newPreset, + ), + ); + + if (presetCreationResponse.meta.status === "success") { + this.fetchCustomUserTabulatorPresets(); + this.hideNewPresetForm(); + } + }, + async deletePreset(preset) { + if (!preset.id) return; + + if ( + window.confirm( + "Are you sure you want to delete preset (((presetName)))?".replace( + "(((presetName)))", + preset.name, + ), + ) + ) { + const presetDeletionResponse = await this.$api.call( + ApiTabulatorPresets.deleteTabulatorPreset(preset.id), + ); + + if (presetDeletionResponse.meta.status === "success") { + this.fetchCustomUserTabulatorPresets(); + } + } + }, + async syncPresetWithConfig(preset) { // todo + return preset; }, + async applyPreset(preset) { + preset = await this.syncPresetWithConfig(preset); + + console.log(this.$props.tabulator); + let columns = this.$props.tabulator + .getColumnDefinitions() + .filter((column) => column.field !== "collapse"); + columns.forEach((column) => + this.$props.tabulator.hideColumn(column.field), + ); + preset.displayedColumns.forEach( + (columnField, index, displayedColumns) => { + this.$props.tabulator.showColumn(columnField); + if (index === 0) return; + this.$props.tabulator.moveColumn( + columnField, + displayedColumns[index - 1], + true, + ); + }, + ); + + this.$props.tabulator.clearHeaderFilter(); + Object.entries(preset.headerFilters).forEach( + ([columnField, filterValue]) => { + this.$props.tabulator.setHeaderFilterValue( + columnField, + filterValue, + ); + }, + ); + + this.$props.tabulator.clearSort(); + if (preset.sort?.column) { + this.$props.tabulator.setSort( + preset.sort.column, + preset.sort.direction ?? "asc", + ); + } + + this.$emit("tablePresetApplied", { preset }); + }, + async fetchCustomUserTabulatorPresets() { + let tabulatorPresetsResponse = await this.$api.call( + ApiTabulatorPresets.getTabulatorPresets(this.$props.presetsId), + ); + + if (tabulatorPresetsResponse.meta.status === "success") { + this.customUserPresets = tabulatorPresetsResponse.data.map( + (presetInfo) => { + return { + id: presetInfo.preset_id, + name: presetInfo.preset_name, + ...JSON.parse(presetInfo.preset_json), + }; + }, + ); + } + }, + getColumnTitles() { + const columnLocalizations = + this.$props.tabulator.getLocale() !== "default" + ? this.$props.tabulator.getLang().columns + : {}; + const columnFieldTitlePairs = this.$props.tabulator + .getColumnDefinitions() + .map((column) => [ + column.field, + columnLocalizations[column.field] ?? column.title, + ]); + return Object.fromEntries(columnFieldTitlePairs); + }, + }, + async created() { + await this.fetchCustomUserTabulatorPresets(); }, template: /*html*/ `
@@ -127,10 +348,10 @@ export default {
Table Presets
-
-
{{ preset.name }}
+
+
{{ preset.name }}
-
+
@@ -143,7 +364,7 @@ export default {
-
+

@@ -152,22 +373,26 @@ export default {
{{ presetInfoFormattedStrings?.displayedColumns }} - {{ presetInfoFormattedStrings?.columnsOrder }} - {{ presetInfoFormattedStrings?.filters }} + {{ presetInfoFormattedStrings?.headerFilters }} {{ presetInfoFormattedStrings?.sort }}
-
- +

{{ "Save current configuration" }} - + +
+ {{ newPresetFormattedStrings?.displayedColumns }} + {{ newPresetFormattedStrings?.headerFilters }} + {{ newPresetFormattedStrings?.sort }} +
+ +
{{ "Save" }}
- adis
diff --git a/system/dbupdate_3.4.php b/system/dbupdate_3.4.php index 8b6af9f2d..95714b303 100644 --- a/system/dbupdate_3.4.php +++ b/system/dbupdate_3.4.php @@ -94,6 +94,7 @@ require_once('dbupdate_3.4/71399_dashboard_update_widget_paths.php'); require_once('dbupdate_3.4/71645_studvw_messagetab_ladezeit.php'); require_once('dbupdate_3.4/71566_studienordnungsdokument_neuer_organisationseinheitstyp_programm.php'); require_once('dbupdate_3.4/70376_lohnguide.php'); +require_once('dbupdate_3.4/77080_tabulator_presets_table.php'); // *** Pruefung und hinzufuegen der neuen Attribute und Tabellen echo '

Pruefe Tabellen und Attribute!

'; @@ -391,6 +392,7 @@ $tabellen=array( "public.tbl_studiengangstyp" => array("typ","bezeichnung","beschreibung","bezeichnung_mehrsprachig"), "public.tbl_studienjahr" => array("studienjahr_kurzbz","bezeichnung"), "public.tbl_studiensemester" => array("studiensemester_kurzbz","bezeichnung","start","ende","studienjahr_kurzbz","ext_id","beschreibung","onlinebewerbung"), + "public.tbl_tabulator_presets" => array("preset_id", "benutzer_uid", "table_name", "preset_json"), "public.tbl_tag" => array("tag"), "public.tbl_variable" => array("name","uid","wert"), "public.tbl_variablenname" => array("name","defaultwert"), diff --git a/system/dbupdate_3.4/77080_tabulator_presets_table.php b/system/dbupdate_3.4/77080_tabulator_presets_table.php new file mode 100644 index 000000000..bef240b17 --- /dev/null +++ b/system/dbupdate_3.4/77080_tabulator_presets_table.php @@ -0,0 +1,55 @@ +db_query("SELECT 1 FROM public.tbl_tabulator_presets LIMIT 1")) { + $qry = "CREATE TABLE public.tbl_tabulator_presets ( + preset_id int NOT NULL, + benutzer_uid varchar(33) NOT NULL, + table_name varchar(255) NOT NULL, + preset_name varchar(255) NOT NULL, + preset_json varchar(10000) NOT NULL, + CONSTRAINT tbl_tabulator_presets_preset_id_pk PRIMARY KEY(preset_id), + CONSTRAINT tbl_tabulator_presets_benutzer_uid_fk FOREIGN KEY (benutzer_uid) REFERENCES public.tbl_benutzer(uid) + );"; + + if (!$db->db_query($qry)) + echo 'public.tbl_tabulator_presets: ' . $db->db_last_error() . '
'; + else + echo '
public.tbl_tabulator_presets: table created'; + + + $db->db_query('CREATE SEQUENCE IF NOT EXISTS public.seq_tbl_tabulator_presets_preset_id + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1;'); + + $db->db_query("ALTER TABLE public.tbl_tabulator_presets ALTER COLUMN preset_id SET DEFAULT nextval('public.seq_tbl_tabulator_presets_preset_id');"); + + + $qry = 'GRANT SELECT, UPDATE, INSERT, DELETE ON TABLE public.tbl_tabulator_presets TO web;'; + if (!$db->db_query($qry)) + echo 'public.tbl_tabulator_presets: ' . $db->db_last_error() . '
'; + else + echo '
Granted privileges to web on public.tbl_tabulator_presets'; + + $qry = 'GRANT SELECT, UPDATE, INSERT, DELETE ON TABLE public.tbl_tabulator_presets TO vilesci;'; + if (!$db->db_query($qry)) + echo 'public.tbl_tabulator_presets: ' . $db->db_last_error() . '
'; + else + echo '
Granted privileges to vilesci on public.tbl_tabulator_presets'; + + $qry = 'GRANT USAGE ON public.seq_tbl_tabulator_presets_preset_id TO web;'; + if (!$db->db_query($qry)) + echo 'public.tbl_tabulator_presets: ' . $db->db_last_error() . '
'; + else + echo '
Granted privileges to web on public.tbl_tabulator_presets'; + + $qry = 'GRANT USAGE ON public.seq_tbl_tabulator_presets_preset_id TO vilesci;'; + if (!$db->db_query($qry)) + echo 'public.tbl_tabulator_presets: ' . $db->db_last_error() . '
'; + else + echo '
Granted privileges to vilesci on public.tbl_tabulator_presets'; +} From 1d0ca15bcb53905d54a775b27085e4baa41339d1 Mon Sep 17 00:00:00 2001 From: adisposkofh Date: Tue, 9 Jun 2026 17:02:02 +0200 Subject: [PATCH 04/24] passing generalPresets prop from filter to table presets --- public/js/components/filter/Filter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/public/js/components/filter/Filter.js b/public/js/components/filter/Filter.js index 9b21243dd..1eeea5e20 100644 --- a/public/js/components/filter/Filter.js +++ b/public/js/components/filter/Filter.js @@ -920,6 +920,7 @@ export const CoreFilterCmpt = { :data-bs-parent="'#filterCollapsables' + idExtra" :presetsId="$props.presetsId" :tabulator="tabulator" + :generalPresets="$props.generalPresets" class="card-body collapse" > From d2c97d0238a3b1cbcbe3311fb4c2856d44f25aaf Mon Sep 17 00:00:00 2001 From: adisposkofh Date: Wed, 10 Jun 2026 14:43:20 +0200 Subject: [PATCH 05/24] check to sync table preset with modified config --- .../api/frontend/v1/TabulatorPresets.php | 35 +++- .../tabulator/Tabulator_preset_model.php | 23 ++- public/js/api/factory/tabulatorPresets.js | 10 ++ .../components/filter/Filter/TablePresets.js | 155 ++++++++++++++---- 4 files changed, 180 insertions(+), 43 deletions(-) diff --git a/application/controllers/api/frontend/v1/TabulatorPresets.php b/application/controllers/api/frontend/v1/TabulatorPresets.php index 05de9bde7..e8c27aed6 100644 --- a/application/controllers/api/frontend/v1/TabulatorPresets.php +++ b/application/controllers/api/frontend/v1/TabulatorPresets.php @@ -30,6 +30,7 @@ class TabulatorPresets extends FHCAPI_Controller parent::__construct([ 'getTabulatorPresets' => self::PERM_LOGGED, 'createTabulatorPreset' => self::PERM_LOGGED, + 'updateTabulatorPreset' => self::PERM_LOGGED, 'deleteTabulatorPreset' => self::PERM_LOGGED, ]); @@ -93,6 +94,39 @@ class TabulatorPresets extends FHCAPI_Controller $this->terminateWithSuccess($newPreset); } + public function updateTabulatorPreset() + { + $presetId = $this->input->post("presetId"); + $preset = $this->input->post("preset"); + + if (!$presetId || !$preset) { + $this->terminateWithError("Invalid parameters!", "general", 400); + } + + $existingPresetData = $this->TabulatorPresetModel->getTabulatorPreset($presetId); + $existingPresetArray = $this->getDataOrTerminateWithError($existingPresetData) ?? [null]; + $existingPreset = $existingPresetArray[0]; + if (!$existingPreset) { + $this->terminateWithError("Preset not found!", "general", 404); + } + + $uid = getAuthUID(); + if ($existingPreset->benutzer_uid !== $uid) { + $this->terminateWithError("You are not allowed to delete this preset!", "general", 403); + } + + $presetUpdateResult = $this->TabulatorPresetModel->updateTabulatorPreset($presetId, $preset); + if (isError($presetUpdateResult)) { + $this->terminateWithError($presetUpdateResult->retval); + } + + $updatedPresetData = $this->TabulatorPresetModel->getTabulatorPreset($presetId); + $updatedPresetArray = $this->getDataOrTerminateWithError($updatedPresetData) ?? [null]; + $updatedPreset = $updatedPresetArray[0]; + + $this->terminateWithSuccess($updatedPreset); + } + public function deleteTabulatorPreset() { $presetId = $this->input->post("presetId"); @@ -108,7 +142,6 @@ class TabulatorPresets extends FHCAPI_Controller } $uid = getAuthUID(); - $this->addMeta("preset", $preset); if ($preset->benutzer_uid !== $uid) { $this->terminateWithError("You are not allowed to delete this preset!", "general", 403); } diff --git a/application/models/tabulator/Tabulator_preset_model.php b/application/models/tabulator/Tabulator_preset_model.php index 7a3de5491..209420069 100644 --- a/application/models/tabulator/Tabulator_preset_model.php +++ b/application/models/tabulator/Tabulator_preset_model.php @@ -41,24 +41,31 @@ class Tabulator_preset_model extends DB_Model public function createTabulatorPreset($uid, $tableName, $presetName, $preset) { - $this->db->trans_start(false); - - $result = $this->insert([ + $presetCreationResult = $this->insert([ "benutzer_uid" => $uid, "table_name" => $tableName, "preset_name" => $presetName, "preset_json" => json_encode($preset), ]); - - $this->db->trans_complete(); - if ($this->db->trans_status() === false) + if (isError($presetCreationResult)) { - $this->db->trans_rollback(); return error('Something went wrong with tabulator preset creation.'); } - return success($result->retval); + return success($presetCreationResult->retval); + } + + public function updateTabulatorPreset($presetId, $preset) + { + $presetUpdateResult = $this->update(["preset_id" => $presetId], ["preset_json" => json_encode($preset)]); + + if (isError($presetUpdateResult)) + { + return error('Something went wrong during tabulator preset update.'); + } + + return success($presetUpdateResult->retval); } public function deleteTabulatorPreset($presetId) diff --git a/public/js/api/factory/tabulatorPresets.js b/public/js/api/factory/tabulatorPresets.js index 1ee32550e..1653ccb40 100644 --- a/public/js/api/factory/tabulatorPresets.js +++ b/public/js/api/factory/tabulatorPresets.js @@ -35,6 +35,16 @@ export default { }, }; }, + updateTabulatorPreset(presetId, preset) { + return { + method: "post", + url: "/api/frontend/v1/TabulatorPresets/updateTabulatorPreset", + params: { + presetId, + preset, + }, + }; + }, deleteTabulatorPreset(tabulatorPresetId) { return { method: "post", diff --git a/public/js/components/filter/Filter/TablePresets.js b/public/js/components/filter/Filter/TablePresets.js index d47e223f0..132f7954f 100644 --- a/public/js/components/filter/Filter/TablePresets.js +++ b/public/js/components/filter/Filter/TablePresets.js @@ -45,19 +45,26 @@ export default { const columnTitles = this.getColumnTitles(); - const displayedColumns = this.presetInfo.displayedColumns.map( - (columnField) => columnTitles[columnField], + let displayedColumns = + this.presetInfo.displayedColumns?.map( + (columnField) => columnTitles[columnField] ?? "", + ) ?? []; + displayedColumns = displayedColumns.filter( + (string) => string.length, ); - const headerFilters = Object.keys( - this.presetInfo.headerFilters, - ).map( - (column) => - columnTitles[column] + - '["' + - this.presetInfo.headerFilters[column] + - '"]', - ); + let headerFilters = this.presetInfo.headerFilters + ? Object.keys(this.presetInfo.headerFilters).map((column) => + columnTitles[column] + ? columnTitles[column] + + '["' + + this.presetInfo.headerFilters[column] + + '"]' + : "", + ) + : []; + headerFilters = headerFilters.filter((string) => string.length); + return { displayedColumns: "Displayed columns (in order): " + @@ -65,13 +72,14 @@ export default { headerFilters: "Column header filters: " + headerFilters.join(", "), sort: - "Sort: " + - columnTitles[this.presetInfo.sort.column] + - " (" + - (this.presetInfo.sort.direction === "asc" - ? "ascending" - : "descending") + - ")", + "Sort: " + this.presetInfo.sort + ? columnTitles[this.presetInfo.sort.column] + + " (" + + (this.presetInfo.sort.direction === "asc" + ? "ascending" + : "descending") + + ")" + : "", }; }, newPresetFormattedStrings() { @@ -172,9 +180,9 @@ export default { } }, generateNewPreset() { - const columns = this.tabulator - .getColumnDefinitions() - .filter((column) => column.field !== "collapse"); + const columns = this.tabulator.columnManager.columns.filter( + (column) => column.field !== "collapse", + ); const displayedColumns = columns .filter((column) => column.visible) @@ -262,21 +270,98 @@ export default { } } }, - async syncPresetWithConfig(preset) { - // todo + syncPresetWithConfig(preset) { + const validColumns = this.$props.tabulator.getColumnDefinitions(); + let isUpdateNecessary = false; + + if (preset.displayedColumns) { + const initialNumberOfDisplayedColumns = + preset.displayedColumns.length; + preset.displayedColumns = preset.displayedColumns.filter( + (columnField) => { + return validColumns.some( + (column) => column.field === columnField, + ); + }, + ); + const updatedNumberOfDisplayedColumns = + preset.displayedColumns.length; + + isUpdateNecessary = + updatedNumberOfDisplayedColumns < + initialNumberOfDisplayedColumns; + } + + if (preset.headerFilters) { + const initialNumberOfHeaderFilters = Object.keys( + preset.headerFilters, + ).length; + Object.keys(preset.headerFilters).forEach((columnField) => { + if ( + !validColumns.some( + (column) => + column.field === columnField && + column.headerFilter, + ) || + !preset.displayedColumns.includes(columnField) + ) { + delete preset.headerFilters[columnField]; + } + }); + const updatedNumberOfHeaderFilters = Object.keys( + preset.headerFilters, + ).length; + + isUpdateNecessary = + isUpdateNecessary || + updatedNumberOfHeaderFilters < initialNumberOfHeaderFilters; + } + + if (preset.sort) { + if ( + !validColumns.some( + (column) => + column.field === preset.sort.column && + column.headerSort, + ) + ) { + preset.sort = null; + isUpdateNecessary = true; + } + } + + if (isUpdateNecessary) { + this.updateTabulatorPreset(preset); + } + return preset; }, + async updateTabulatorPreset(preset) { + if (!preset.id) return; + + const tabulatorPresetUpdateResponse = await this.$api.call( + ApiTabulatorPresets.updateTabulatorPreset(preset.id, { + displayedColumns: preset.displayedColumns, + headerFilters: preset.headerFilters, + sort: preset.sort, + }), + ); + + if (tabulatorPresetUpdateResponse.meta.status === "success") { + this.fetchCustomUserTabulatorPresets(); + } + }, async applyPreset(preset) { - preset = await this.syncPresetWithConfig(preset); + preset = this.syncPresetWithConfig(preset); - console.log(this.$props.tabulator); let columns = this.$props.tabulator .getColumnDefinitions() .filter((column) => column.field !== "collapse"); columns.forEach((column) => this.$props.tabulator.hideColumn(column.field), ); - preset.displayedColumns.forEach( + + preset.displayedColumns?.forEach( (columnField, index, displayedColumns) => { this.$props.tabulator.showColumn(columnField); if (index === 0) return; @@ -289,14 +374,16 @@ export default { ); this.$props.tabulator.clearHeaderFilter(); - Object.entries(preset.headerFilters).forEach( - ([columnField, filterValue]) => { - this.$props.tabulator.setHeaderFilterValue( - columnField, - filterValue, - ); - }, - ); + if (preset.headerFilters) { + Object.entries(preset.headerFilters).forEach( + ([columnField, filterValue]) => { + this.$props.tabulator.setHeaderFilterValue( + columnField, + filterValue, + ); + }, + ); + } this.$props.tabulator.clearSort(); if (preset.sort?.column) { From 7abd35d11b3b3d3f48e66af49a48f19f529d1a44 Mon Sep 17 00:00:00 2001 From: adisposkofh Date: Thu, 11 Jun 2026 11:20:34 +0200 Subject: [PATCH 06/24] added tabulator preset phrases --- .../components/filter/Filter/TablePresets.js | 57 +++-- system/phrasesupdate.php | 202 ++++++++++++++++++ 2 files changed, 240 insertions(+), 19 deletions(-) diff --git a/public/js/components/filter/Filter/TablePresets.js b/public/js/components/filter/Filter/TablePresets.js index 132f7954f..f8cbb6c2d 100644 --- a/public/js/components/filter/Filter/TablePresets.js +++ b/public/js/components/filter/Filter/TablePresets.js @@ -67,17 +67,24 @@ export default { return { displayedColumns: - "Displayed columns (in order): " + + this.$p.t("tabulator_presets/displayed_columns") + + " (" + + this.$p.t("tabulator_presets/in_order") + + "): " + displayedColumns.join(", "), headerFilters: - "Column header filters: " + headerFilters.join(", "), + this.$p.t("tabulator_presets/column_header_filters") + + ": " + + headerFilters.join(", "), sort: - "Sort: " + this.presetInfo.sort + this.$p.t("tabulator_presets/sort") + + ": " + + this.presetInfo.sort ? columnTitles[this.presetInfo.sort.column] + " (" + (this.presetInfo.sort.direction === "asc" - ? "ascending" - : "descending") + + ? this.$p.t("tabulator_presets/asc") + : this.$p.t("tabulator_presets/desc")) + ")" : "", }; @@ -100,18 +107,24 @@ export default { ); return { displayedColumns: - "Displayed columns (in order): " + + this.$p.t("tabulator_presets/displayed_columns") + + " (" + + this.$p.t("tabulator_presets/in_order") + + "): " + displayedColumns.join(", "), headerFilters: - "Column header filters: " + headerFilters.join(", "), + this.$p.t("tabulator_presets/column_header_filters") + + ": " + + headerFilters.join(", "), sort: - "Sort: " + + this.$p.t("tabulator_presets/sort") + + ": " + (this.newPreset.sort ? columnTitles[this.newPreset.sort.column] + " (" + (this.newPreset.sort.direction === "asc" - ? "ascending" - : "descending") + + ? this.$p.t("tabulator_presets/asc") + : this.$p.t("tabulator_presets/desc")) + ")" : ""), }; @@ -255,10 +268,9 @@ export default { if ( window.confirm( - "Are you sure you want to delete preset (((presetName)))?".replace( - "(((presetName)))", - preset.name, - ), + this.$p + .t("tabulator_presets/preset_deletion_confirmation") + .replace("(((presetName)))", preset.name), ) ) { const presetDeletionResponse = await this.$api.call( @@ -427,12 +439,13 @@ export default { }, }, async created() { + await this.$p.loadCategory(["tabulator_presets", "global"]); await this.fetchCustomUserTabulatorPresets(); }, template: /*html*/ `
-
Table Presets
+
{{ $p.t("tabulator_presets/table_presets") }}
@@ -447,7 +460,7 @@ export default {
- {{ "Save current configuration" }} + {{ $p.t("tabulator_presets/save_current_config") }}
@@ -470,15 +483,21 @@ export default {
- {{ "Save current configuration" }} + {{ $p.t("tabulator_presets/save_current_config") }}
{{ newPresetFormattedStrings?.displayedColumns }} {{ newPresetFormattedStrings?.headerFilters }} {{ newPresetFormattedStrings?.sort }}
- -
{{ "Save" }}
+ +
+ {{ $p.t('global/speichern') }} +
diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index 8a68a43ff..737239056 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -58559,6 +58559,208 @@ I have been informed that I am under no obligation to consent to the transmissio ) ), // generic tabulator phrases END + // tabulator presets section phrases START + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'displayed_columns', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Angezeigte Spalten', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Displayed columns', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'in_order', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'in Reihenfolge', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'in order', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'column_header_filters', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Spalten Header Filter', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Column header filters', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'asc', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'aufsteigend', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'ascending', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'desc', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'absteigend', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'descending', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'sort', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Sortierung', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Sort', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'preset_deletion_confirmation', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Sind Sie sicher, dass Sie die Voreinstellung (((presetName))) löschen möchten?', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Are you sure you want to delete preset (((presetName)))?', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'table_presets', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Tabelle Voreinstellungen', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Table presets', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'save_current_config', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Akutelle Einstellung speichern', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Save current configuration', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'preset_name', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Voreinstellung Name', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Preset name', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + // tabulator presets section phrases END ); From 34c210a2a77171a038402d6a41ec034e86d1dd0a Mon Sep 17 00:00:00 2001 From: adisposkofh Date: Thu, 11 Jun 2026 11:27:43 +0200 Subject: [PATCH 07/24] fixed table preset info string formatting --- .../js/components/filter/Filter/TablePresets.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/public/js/components/filter/Filter/TablePresets.js b/public/js/components/filter/Filter/TablePresets.js index f8cbb6c2d..f0e0e3328 100644 --- a/public/js/components/filter/Filter/TablePresets.js +++ b/public/js/components/filter/Filter/TablePresets.js @@ -65,6 +65,14 @@ export default { : []; headerFilters = headerFilters.filter((string) => string.length); + const sortColumn = this.presetInfo.sort?.column + ? columnTitles[this.presetInfo.sort.column] + : null; + const sortDirection = + this.presetInfo.sort?.direction === "desc" + ? this.$p.t("tabulator_presets/desc") + : this.$p.t("tabulator_presets/asc"); + return { displayedColumns: this.$p.t("tabulator_presets/displayed_columns") + @@ -79,14 +87,7 @@ export default { sort: this.$p.t("tabulator_presets/sort") + ": " + - this.presetInfo.sort - ? columnTitles[this.presetInfo.sort.column] + - " (" + - (this.presetInfo.sort.direction === "asc" - ? this.$p.t("tabulator_presets/asc") - : this.$p.t("tabulator_presets/desc")) + - ")" - : "", + (sortColumn ? sortColumn + " (" + sortDirection + ")" : ""), }; }, newPresetFormattedStrings() { From f0f9becc1f7e2dfd16a5c342215d100dc14c6a4b Mon Sep 17 00:00:00 2001 From: adisposkofh Date: Thu, 11 Jun 2026 11:29:29 +0200 Subject: [PATCH 08/24] fixed new table preset info string formatting --- .../components/filter/Filter/TablePresets.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/public/js/components/filter/Filter/TablePresets.js b/public/js/components/filter/Filter/TablePresets.js index f0e0e3328..512d2b47e 100644 --- a/public/js/components/filter/Filter/TablePresets.js +++ b/public/js/components/filter/Filter/TablePresets.js @@ -106,6 +106,15 @@ export default { this.newPreset.headerFilters[column] + '"]', ); + + const sortColumn = this.newPreset.sort?.column + ? columnTitles[this.newPreset.sort.column] + : null; + const sortDirection = + this.newPreset.sort?.direction === "desc" + ? this.$p.t("tabulator_presets/desc") + : this.$p.t("tabulator_presets/asc"); + return { displayedColumns: this.$p.t("tabulator_presets/displayed_columns") + @@ -120,14 +129,7 @@ export default { sort: this.$p.t("tabulator_presets/sort") + ": " + - (this.newPreset.sort - ? columnTitles[this.newPreset.sort.column] + - " (" + - (this.newPreset.sort.direction === "asc" - ? this.$p.t("tabulator_presets/asc") - : this.$p.t("tabulator_presets/desc")) + - ")" - : ""), + (sortColumn ? sortColumn + " (" + sortDirection + ")" : ""), }; }, }, From c9011fd6ede90e8dbae31ab2e5cce57cffbd8905 Mon Sep 17 00:00:00 2001 From: adisposkofh Date: Thu, 11 Jun 2026 11:30:42 +0200 Subject: [PATCH 09/24] minor naming change in table presets --- public/js/components/filter/Filter/TablePresets.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/js/components/filter/Filter/TablePresets.js b/public/js/components/filter/Filter/TablePresets.js index 512d2b47e..c764b9284 100644 --- a/public/js/components/filter/Filter/TablePresets.js +++ b/public/js/components/filter/Filter/TablePresets.js @@ -346,12 +346,12 @@ export default { } if (isUpdateNecessary) { - this.updateTabulatorPreset(preset); + this.updatePreset(preset); } return preset; }, - async updateTabulatorPreset(preset) { + async updatePreset(preset) { if (!preset.id) return; const tabulatorPresetUpdateResponse = await this.$api.call( From 538223752b5d9827be63c81bc25cf2e29a7cd151 Mon Sep 17 00:00:00 2001 From: adisposkofh Date: Thu, 11 Jun 2026 12:38:57 +0200 Subject: [PATCH 10/24] hiding preset info/new preset form when table presets section is collapsed --- public/js/components/filter/Filter.js | 4 ++++ public/js/components/filter/Filter/TablePresets.js | 11 +++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/public/js/components/filter/Filter.js b/public/js/components/filter/Filter.js index 1eeea5e20..9b5e14f43 100644 --- a/public/js/components/filter/Filter.js +++ b/public/js/components/filter/Filter.js @@ -143,6 +143,7 @@ export const CoreFilterCmpt = { page: false, }, collapsedHeadingLocalizationTimer: null, + areTablePresetsShown: false, }; }, computed: { @@ -916,11 +917,14 @@ export const CoreFilterCmpt = { v-if="$props.isUsingPresets && $props.presetsId?.length" v-collapse-auto-close @tablePresetApplied="afterTablePresetApplied($event.preset)" + @[\`shown.bs.collapse\`]="areTablePresetsShown = true" + @[\`hide.bs.collapse\`]="areTablePresetsShown = false" :id="'tablePresets' + idExtra" :data-bs-parent="'#filterCollapsables' + idExtra" :presetsId="$props.presetsId" :tabulator="tabulator" :generalPresets="$props.generalPresets" + :isShown="areTablePresetsShown" class="card-body collapse" > diff --git a/public/js/components/filter/Filter/TablePresets.js b/public/js/components/filter/Filter/TablePresets.js index c764b9284..f8432d2db 100644 --- a/public/js/components/filter/Filter/TablePresets.js +++ b/public/js/components/filter/Filter/TablePresets.js @@ -26,6 +26,7 @@ export default { presetsId: { type: String }, tabulator: { type: Object }, generalPresets: { type: Array, default: [] }, + isShown: { type: Boolean, default: true } }, emits: ["applyTablePreset"], data: function () { @@ -141,6 +142,12 @@ export default { }, deep: true, }, + isShown() { + if (!this.$props.isShown) { + this.hidePresetInfo(); + this.hideNewPresetForm(); + } + }, }, methods: { async showPresetInfo(preset) { @@ -467,7 +474,7 @@ export default {
-
+

@@ -481,7 +488,7 @@ export default {
-
+

From 3ced1966f4202200f7b10c4b36736cd592118c07 Mon Sep 17 00:00:00 2001 From: adisposkofh Date: Thu, 11 Jun 2026 12:53:34 +0200 Subject: [PATCH 11/24] minor fix --- application/controllers/api/frontend/v1/TabulatorPresets.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/controllers/api/frontend/v1/TabulatorPresets.php b/application/controllers/api/frontend/v1/TabulatorPresets.php index e8c27aed6..b5aec2362 100644 --- a/application/controllers/api/frontend/v1/TabulatorPresets.php +++ b/application/controllers/api/frontend/v1/TabulatorPresets.php @@ -112,7 +112,7 @@ class TabulatorPresets extends FHCAPI_Controller $uid = getAuthUID(); if ($existingPreset->benutzer_uid !== $uid) { - $this->terminateWithError("You are not allowed to delete this preset!", "general", 403); + $this->terminateWithError("You are not allowed to update this preset!", "general", 403); } $presetUpdateResult = $this->TabulatorPresetModel->updateTabulatorPreset($presetId, $preset); From c9fe633706482dbb31c95344284f202e3bc25093 Mon Sep 17 00:00:00 2001 From: adisposkofh Date: Thu, 11 Jun 2026 13:21:56 +0200 Subject: [PATCH 12/24] localized tabulator preset backend error messages --- .../api/frontend/v1/TabulatorPresets.php | 25 ++- .../tabulator/Tabulator_preset_model.php | 6 +- system/phrasesupdate.php | 180 ++++++++++++++++++ 3 files changed, 198 insertions(+), 13 deletions(-) diff --git a/application/controllers/api/frontend/v1/TabulatorPresets.php b/application/controllers/api/frontend/v1/TabulatorPresets.php index b5aec2362..c38cce033 100644 --- a/application/controllers/api/frontend/v1/TabulatorPresets.php +++ b/application/controllers/api/frontend/v1/TabulatorPresets.php @@ -35,6 +35,11 @@ class TabulatorPresets extends FHCAPI_Controller ]); $this->load->model('tabulator/Tabulator_preset_model', 'TabulatorPresetModel'); + + $this->loadPhrases([ + 'global', + 'tabulator_presets' + ]); } //------------------------------------------------------------------------------------------------------------------ @@ -44,7 +49,7 @@ class TabulatorPresets extends FHCAPI_Controller { $tableName = $this->input->get("tableName"); if (!$tableName) { - $this->terminateWithError(error("Invalid parameters!")); + $this->terminateWithError(error($this->p->t("global", "invalid_params_err_msg"))); } $uid = getAuthUID(); @@ -61,7 +66,7 @@ class TabulatorPresets extends FHCAPI_Controller $preset = $this->input->post("preset"); if (!$tableName || !$presetName || !$preset) { - $this->terminateWithError("Invalid parameters!", "general", 400); + $this->terminateWithError($this->p->t("global", "invalid_params_err_msg"), "general", 400); } $uid = getAuthUID(); @@ -70,7 +75,7 @@ class TabulatorPresets extends FHCAPI_Controller $existingPresets = $this->getDataOrTerminateWithError($existingPresetsResult) ?? []; if (count($existingPresets) > 19) { - $this->terminateWithError("Maximum number of presets reached!", "general", 409); + $this->terminateWithError($this->p->t("tabulator_presets", "max_presets_count_err_msg"), "general", 409); } $existingPresetNames = array_map(function($presetInfo) { @@ -78,7 +83,7 @@ class TabulatorPresets extends FHCAPI_Controller }, $existingPresets); if (in_array($presetName, $existingPresetNames)) { - $this->terminateWithError("Preset name duplicate not allowed!", "general", 409); + $this->terminateWithError($this->p->t("tabulator_presets", "preset_name_duplicate_err_msg"), "general", 409); } $presetCreationResult = $this->TabulatorPresetModel->createTabulatorPreset($uid, $tableName, $presetName, $preset); @@ -100,19 +105,19 @@ class TabulatorPresets extends FHCAPI_Controller $preset = $this->input->post("preset"); if (!$presetId || !$preset) { - $this->terminateWithError("Invalid parameters!", "general", 400); + $this->terminateWithError($this->p->t("global", "invalid_params_err_msg"), "general", 400); } $existingPresetData = $this->TabulatorPresetModel->getTabulatorPreset($presetId); $existingPresetArray = $this->getDataOrTerminateWithError($existingPresetData) ?? [null]; $existingPreset = $existingPresetArray[0]; if (!$existingPreset) { - $this->terminateWithError("Preset not found!", "general", 404); + $this->terminateWithError($this->p->t("tabulator_presets", "preset_not_found_err_msg"), "general", 404); } $uid = getAuthUID(); if ($existingPreset->benutzer_uid !== $uid) { - $this->terminateWithError("You are not allowed to update this preset!", "general", 403); + $this->terminateWithError($this->p->t("tabulator_presets", "preset_not_own_update_err_msg"), "general", 403); } $presetUpdateResult = $this->TabulatorPresetModel->updateTabulatorPreset($presetId, $preset); @@ -131,19 +136,19 @@ class TabulatorPresets extends FHCAPI_Controller { $presetId = $this->input->post("presetId"); if (!$presetId) { - $this->terminateWithError("Invalid parameters!", "general", 400); + $this->terminateWithError($this->p->t("global", "invalid_params_err_msg"), "general", 400); } $presetData = $this->TabulatorPresetModel->getTabulatorPreset($presetId); $presetArray = $this->getDataOrTerminateWithError($presetData) ?? [null]; $preset = $presetArray[0]; if (!$preset) { - $this->terminateWithError("Preset not found!", "general", 404); + $this->terminateWithError($this->p->t("tabulator_presets", "preset_not_found_err_msg"), "general", 404); } $uid = getAuthUID(); if ($preset->benutzer_uid !== $uid) { - $this->terminateWithError("You are not allowed to delete this preset!", "general", 403); + $this->terminateWithError($this->p->t("tabulator_presets", "preset_not_own_delete_err_msg"), "general", 403); } $presetDeletionResult = $this->TabulatorPresetModel->deleteTabulatorPreset($presetId); diff --git a/application/models/tabulator/Tabulator_preset_model.php b/application/models/tabulator/Tabulator_preset_model.php index 209420069..a3c45739d 100644 --- a/application/models/tabulator/Tabulator_preset_model.php +++ b/application/models/tabulator/Tabulator_preset_model.php @@ -50,7 +50,7 @@ class Tabulator_preset_model extends DB_Model if (isError($presetCreationResult)) { - return error('Something went wrong with tabulator preset creation.'); + return error('Something went wrong during preset creation!'); } return success($presetCreationResult->retval); @@ -62,7 +62,7 @@ class Tabulator_preset_model extends DB_Model if (isError($presetUpdateResult)) { - return error('Something went wrong during tabulator preset update.'); + return error('Something went wrong during preset update!'); } return success($presetUpdateResult->retval); @@ -74,7 +74,7 @@ class Tabulator_preset_model extends DB_Model if (isError($presetDeletionReseult)) { - return error('Something went wrong during tabulator preset deletion.'); + return error('Something went wrong during preset deletion!'); } return success($presetDeletionReseult->retval); diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index 737239056..17b852eab 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -33112,6 +33112,26 @@ array( ) ) ), + array( + 'app' => 'anwesenheiten', + 'category' => 'global', + 'phrase' => 'invalid_params_err_msg', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Ungültige Parameterübergabe!', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Invalid parameters!', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), array( 'app' => 'anwesenheiten', 'category' => 'global', @@ -58760,6 +58780,166 @@ I have been informed that I am under no obligation to consent to the transmissio ) ) ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'max_presets_count_err_msg', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Maximale Anzahl an Voreinstellungen erreicht!', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Maximum number of presets reached!', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'preset_name_duplicate_err_msg', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Doppelte Voreinstellunge Namen sind nicht zulässig!', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Preset name duplicate not allowed!', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'preset_not_found_err_msg', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Voreinstellung nicht gefunden!', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Preset not found!', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'preset_not_own_update_err_msg', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Sie dürfen diese Voreinstellung nicht ändern!', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'You are not allowed to update this preset!', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'preset_not_own_delete_err_msg', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Sie dürfen diese Voreinstellung nicht löschen!', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'You are not allowed to delete this preset!', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'preset_creation_generic_err_msg', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Bei der Erstellung der Voreinstellung ist ein Fehler aufgetreten!', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Something went wrong during preset creation!', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'preset_update_generic_err_msg', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Bei der Voreinstellungsänderung ist ein Fehler aufgetreten!', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Something went wrong during preset update!', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'preset_deletion_generic_err_msg', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Beim Löschen der Voreinstellung ist ein Fehler aufgetreten!', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Something went wrong during preset deletion!', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), // tabulator presets section phrases END ); From f59e5a20ea6eeafa10dd6193ba119c7f9c68b4e8 Mon Sep 17 00:00:00 2001 From: adisposkofh Date: Thu, 11 Jun 2026 13:36:26 +0200 Subject: [PATCH 13/24] formatting preset info/new preset overviews --- .../components/filter/Filter/TablePresets.js | 76 ++++++++++++------- 1 file changed, 47 insertions(+), 29 deletions(-) diff --git a/public/js/components/filter/Filter/TablePresets.js b/public/js/components/filter/Filter/TablePresets.js index f8432d2db..f7e4a6934 100644 --- a/public/js/components/filter/Filter/TablePresets.js +++ b/public/js/components/filter/Filter/TablePresets.js @@ -26,7 +26,7 @@ export default { presetsId: { type: String }, tabulator: { type: Object }, generalPresets: { type: Array, default: [] }, - isShown: { type: Boolean, default: true } + isShown: { type: Boolean, default: true }, }, emits: ["applyTablePreset"], data: function () { @@ -53,6 +53,13 @@ export default { displayedColumns = displayedColumns.filter( (string) => string.length, ); + const displayedColumnsString = displayedColumns.length + ? this.$p.t("tabulator_presets/displayed_columns") + + " (" + + this.$p.t("tabulator_presets/in_order") + + "): " + + displayedColumns.join(", ") + : ""; let headerFilters = this.presetInfo.headerFilters ? Object.keys(this.presetInfo.headerFilters).map((column) => @@ -65,6 +72,11 @@ export default { ) : []; headerFilters = headerFilters.filter((string) => string.length); + const headerFiltersString = headerFilters.length + ? this.$p.t("tabulator_presets/column_header_filters") + + ": " + + headerFilters.join(", ") + : ""; const sortColumn = this.presetInfo.sort?.column ? columnTitles[this.presetInfo.sort.column] @@ -73,22 +85,19 @@ export default { this.presetInfo.sort?.direction === "desc" ? this.$p.t("tabulator_presets/desc") : this.$p.t("tabulator_presets/asc"); + const sortString = sortColumn + ? this.$p.t("tabulator_presets/sort") + + ": " + + sortColumn + + " (" + + sortDirection + + ")" + : ""; return { - displayedColumns: - this.$p.t("tabulator_presets/displayed_columns") + - " (" + - this.$p.t("tabulator_presets/in_order") + - "): " + - displayedColumns.join(", "), - headerFilters: - this.$p.t("tabulator_presets/column_header_filters") + - ": " + - headerFilters.join(", "), - sort: - this.$p.t("tabulator_presets/sort") + - ": " + - (sortColumn ? sortColumn + " (" + sortDirection + ")" : ""), + displayedColumns: displayedColumnsString, + headerFilters: headerFiltersString, + sort: sortString, }; }, newPresetFormattedStrings() { @@ -99,6 +108,13 @@ export default { const displayedColumns = this.newPreset.displayedColumns.map( (columnField) => columnTitles[columnField], ); + const displayedColumnsString = displayedColumns.length + ? this.$p.t("tabulator_presets/displayed_columns") + + " (" + + this.$p.t("tabulator_presets/in_order") + + "): " + + displayedColumns.join(", ") + : ""; const headerFilters = Object.keys(this.newPreset.headerFilters).map( (column) => @@ -107,6 +123,11 @@ export default { this.newPreset.headerFilters[column] + '"]', ); + const headerFiltersString = headerFilters.length + ? this.$p.t("tabulator_presets/column_header_filters") + + ": " + + headerFilters.join(", ") + : ""; const sortColumn = this.newPreset.sort?.column ? columnTitles[this.newPreset.sort.column] @@ -115,22 +136,19 @@ export default { this.newPreset.sort?.direction === "desc" ? this.$p.t("tabulator_presets/desc") : this.$p.t("tabulator_presets/asc"); + const sortString = sortColumn + ? this.$p.t("tabulator_presets/sort") + + ": " + + sortColumn + + " (" + + sortDirection + + ")" + : ""; return { - displayedColumns: - this.$p.t("tabulator_presets/displayed_columns") + - " (" + - this.$p.t("tabulator_presets/in_order") + - "): " + - displayedColumns.join(", "), - headerFilters: - this.$p.t("tabulator_presets/column_header_filters") + - ": " + - headerFilters.join(", "), - sort: - this.$p.t("tabulator_presets/sort") + - ": " + - (sortColumn ? sortColumn + " (" + sortDirection + ")" : ""), + displayedColumns: displayedColumnsString, + headerFilters: headerFiltersString, + sort: sortString, }; }, }, From 4474a24ee2d98d3142dba512c6803210cb8742a0 Mon Sep 17 00:00:00 2001 From: adisposkofh Date: Thu, 11 Jun 2026 14:09:45 +0200 Subject: [PATCH 14/24] streamlined collapsing table presets sub-collapsibles when tablePresets is collapsed --- public/js/components/filter/Filter.js | 18 ++++++++++++---- .../components/filter/Filter/TablePresets.js | 21 +++++++------------ 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/public/js/components/filter/Filter.js b/public/js/components/filter/Filter.js index 9b5e14f43..b52a09f65 100644 --- a/public/js/components/filter/Filter.js +++ b/public/js/components/filter/Filter.js @@ -143,7 +143,6 @@ export const CoreFilterCmpt = { page: false, }, collapsedHeadingLocalizationTimer: null, - areTablePresetsShown: false, }; }, computed: { @@ -835,6 +834,17 @@ export const CoreFilterCmpt = { afterTablePresetApplied(preset) { this.selectedFields = preset.displayedColumns; }, + updateTablePresetsSubCollapsibles() { + if ( + !this.$refs.tablePresets.$el + .getAttribute("class") + .split(" ") + .includes("show") + ) { + this.$refs.tablePresets?.hidePresetInfo(); + this.$refs.tablePresets?.hideNewPresetForm(); + } + }, }, beforeCreate() { if (!this.tableOnly == !this.filterType) @@ -917,14 +927,14 @@ export const CoreFilterCmpt = { v-if="$props.isUsingPresets && $props.presetsId?.length" v-collapse-auto-close @tablePresetApplied="afterTablePresetApplied($event.preset)" - @[\`shown.bs.collapse\`]="areTablePresetsShown = true" - @[\`hide.bs.collapse\`]="areTablePresetsShown = false" + @[\`shown.bs.collapse\`]="updateTablePresetsSubCollapsibles()" + @[\`hidden.bs.collapse\`]="updateTablePresetsSubCollapsibles()" :id="'tablePresets' + idExtra" :data-bs-parent="'#filterCollapsables' + idExtra" :presetsId="$props.presetsId" :tabulator="tabulator" :generalPresets="$props.generalPresets" - :isShown="areTablePresetsShown" + ref="tablePresets" class="card-body collapse" > diff --git a/public/js/components/filter/Filter/TablePresets.js b/public/js/components/filter/Filter/TablePresets.js index f7e4a6934..0805be874 100644 --- a/public/js/components/filter/Filter/TablePresets.js +++ b/public/js/components/filter/Filter/TablePresets.js @@ -26,7 +26,6 @@ export default { presetsId: { type: String }, tabulator: { type: Object }, generalPresets: { type: Array, default: [] }, - isShown: { type: Boolean, default: true }, }, emits: ["applyTablePreset"], data: function () { @@ -160,12 +159,6 @@ export default { }, deep: true, }, - isShown() { - if (!this.$props.isShown) { - this.hidePresetInfo(); - this.hideNewPresetForm(); - } - }, }, methods: { async showPresetInfo(preset) { @@ -258,7 +251,7 @@ export default { sort, }; }, - async hideNewPresetForm() { + hideNewPresetForm() { if ( this.$refs.newPresetFormCollapsible .getAttribute("class") @@ -500,9 +493,9 @@ export default { {{ presetInfo?.name }}
- {{ presetInfoFormattedStrings?.displayedColumns }} - {{ presetInfoFormattedStrings?.headerFilters }} - {{ presetInfoFormattedStrings?.sort }} + {{ presetInfoFormattedStrings.displayedColumns }} + {{ presetInfoFormattedStrings.headerFilters }} + {{ presetInfoFormattedStrings.sort }}
@@ -514,9 +507,9 @@ export default { {{ $p.t("tabulator_presets/save_current_config") }}
- {{ newPresetFormattedStrings?.displayedColumns }} - {{ newPresetFormattedStrings?.headerFilters }} - {{ newPresetFormattedStrings?.sort }} + {{ newPresetFormattedStrings.displayedColumns }} + {{ newPresetFormattedStrings.headerFilters }} + {{ newPresetFormattedStrings.sort }}
Date: Tue, 16 Jun 2026 16:59:36 +0200 Subject: [PATCH 15/24] phrase typo fix --- system/phrasesupdate.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index 17b852eab..98d2833f6 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -58748,7 +58748,7 @@ I have been informed that I am under no obligation to consent to the transmissio 'phrases' => array( array( 'sprache' => 'German', - 'text' => 'Akutelle Einstellung speichern', + 'text' => 'Aktuelle Einstellung speichern', 'description' => '', 'insertvon' => 'system' ), From 2692806d4f272e7ea5cf73fe319df06fed5e58b2 Mon Sep 17 00:00:00 2001 From: adisposkofh Date: Tue, 16 Jun 2026 17:19:27 +0200 Subject: [PATCH 16/24] not syncing dev-provided general table presets --- public/js/components/filter/Filter/TablePresets.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/public/js/components/filter/Filter/TablePresets.js b/public/js/components/filter/Filter/TablePresets.js index 0805be874..dd7c73557 100644 --- a/public/js/components/filter/Filter/TablePresets.js +++ b/public/js/components/filter/Filter/TablePresets.js @@ -385,7 +385,9 @@ export default { } }, async applyPreset(preset) { - preset = this.syncPresetWithConfig(preset); + if (preset.id) { + preset = this.syncPresetWithConfig(preset); + } let columns = this.$props.tabulator .getColumnDefinitions() From c0ed296ba744f9fb7d809b56f0b1a17f73df9122 Mon Sep 17 00:00:00 2001 From: adisposkofh Date: Tue, 16 Jun 2026 17:21:15 +0200 Subject: [PATCH 17/24] Revert "phrase typo fix" This reverts commit a59f6085fe6323182613ba34a5dc7fc984d3f7c9. --- system/phrasesupdate.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index 98d2833f6..17b852eab 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -58748,7 +58748,7 @@ I have been informed that I am under no obligation to consent to the transmissio 'phrases' => array( array( 'sprache' => 'German', - 'text' => 'Aktuelle Einstellung speichern', + 'text' => 'Akutelle Einstellung speichern', 'description' => '', 'insertvon' => 'system' ), From ff58f7a102802998b4b6ed6c1e20f6707f8cba3d Mon Sep 17 00:00:00 2001 From: adisposkofh Date: Tue, 16 Jun 2026 17:23:01 +0200 Subject: [PATCH 18/24] minor fix --- public/js/components/filter/Filter/TablePresets.js | 4 +--- system/phrasesupdate.php | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/public/js/components/filter/Filter/TablePresets.js b/public/js/components/filter/Filter/TablePresets.js index dd7c73557..0805be874 100644 --- a/public/js/components/filter/Filter/TablePresets.js +++ b/public/js/components/filter/Filter/TablePresets.js @@ -385,9 +385,7 @@ export default { } }, async applyPreset(preset) { - if (preset.id) { - preset = this.syncPresetWithConfig(preset); - } + preset = this.syncPresetWithConfig(preset); let columns = this.$props.tabulator .getColumnDefinitions() diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index 17b852eab..98d2833f6 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -58748,7 +58748,7 @@ I have been informed that I am under no obligation to consent to the transmissio 'phrases' => array( array( 'sprache' => 'German', - 'text' => 'Akutelle Einstellung speichern', + 'text' => 'Aktuelle Einstellung speichern', 'description' => '', 'insertvon' => 'system' ), From 2fa8fd17030590452b43556f5fe6b0315acca2b9 Mon Sep 17 00:00:00 2001 From: adisposkofh Date: Mon, 27 Jul 2026 16:08:17 +0200 Subject: [PATCH 19/24] resolved warning; checking tabulator init before calling getLang in custom module --- public/js/components/Cis/Profil/Profil.js | 1 - .../customModules/ResponsiveLayoutExtensionModule.js | 10 +++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/public/js/components/Cis/Profil/Profil.js b/public/js/components/Cis/Profil/Profil.js index ab7eb0f3d..cba34951c 100644 --- a/public/js/components/Cis/Profil/Profil.js +++ b/public/js/components/Cis/Profil/Profil.js @@ -176,7 +176,6 @@ export const Profil = { this.data = data.profil_data.data; this.calendarSyncUrls = data.calendar_sync_urls ?? []; this.authPermissions = data.permissions; - console.log(data.profil_data); }, zustellAdressenCount() { if (!this.data || !this.data.adressen) { diff --git a/public/js/tabulator/customModules/ResponsiveLayoutExtensionModule.js b/public/js/tabulator/customModules/ResponsiveLayoutExtensionModule.js index c0e66cff0..038bef527 100644 --- a/public/js/tabulator/customModules/ResponsiveLayoutExtensionModule.js +++ b/public/js/tabulator/customModules/ResponsiveLayoutExtensionModule.js @@ -9,9 +9,13 @@ export default class ResponsiveLayoutExtensionModule extends ResponsiveLayout { } generateCollapsedRowData(row) { - let result = super.generateCollapsedRowData(row) - const isLocalizationEnabled = this.table.options.locale && this.table.options.locale !== "default"; - const columnHeadingTranslations = this.table?.getLang()?.columns; + let result = super.generateCollapsedRowData(row); + const isLocalizationEnabled = + this.table.options.locale && + this.table.options.locale !== "default"; + const columnHeadingTranslations = this.table?.initialized + ? this.table.getLang()?.columns + : null; if (isLocalizationEnabled && columnHeadingTranslations) { result = result.map((column) => { From 47b9e6c086ec95698dd6ea4978b11ecf3704e93a Mon Sep 17 00:00:00 2001 From: adisposkofh Date: Mon, 27 Jul 2026 16:11:47 +0200 Subject: [PATCH 20/24] removed redundant declared emit in tabulator presets --- public/js/components/filter/Filter/TablePresets.js | 1 - 1 file changed, 1 deletion(-) diff --git a/public/js/components/filter/Filter/TablePresets.js b/public/js/components/filter/Filter/TablePresets.js index 0805be874..77b3a6348 100644 --- a/public/js/components/filter/Filter/TablePresets.js +++ b/public/js/components/filter/Filter/TablePresets.js @@ -27,7 +27,6 @@ export default { tabulator: { type: Object }, generalPresets: { type: Array, default: [] }, }, - emits: ["applyTablePreset"], data: function () { return { customUserPresets: [], From e029b09addb9a7484de4bbedf11e19d02a66420e Mon Sep 17 00:00:00 2001 From: adisposkofh Date: Mon, 27 Jul 2026 16:53:07 +0200 Subject: [PATCH 21/24] persisting tabulator preset in local storage --- .../components/filter/Filter/TablePresets.js | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/public/js/components/filter/Filter/TablePresets.js b/public/js/components/filter/Filter/TablePresets.js index 77b3a6348..001eea432 100644 --- a/public/js/components/filter/Filter/TablePresets.js +++ b/public/js/components/filter/Filter/TablePresets.js @@ -27,6 +27,7 @@ export default { tabulator: { type: Object }, generalPresets: { type: Array, default: [] }, }, + emits: ["tablePresetApplied"], data: function () { return { customUserPresets: [], @@ -385,6 +386,8 @@ export default { }, async applyPreset(preset) { preset = this.syncPresetWithConfig(preset); + window.localStorage["tabulatorPreset-" + this.$props.presetsId] = + preset.id ? "custom:" + preset.id : "general:" + preset.name; let columns = this.$props.tabulator .getColumnDefinitions() @@ -457,10 +460,34 @@ export default { ]); return Object.fromEntries(columnFieldTitlePairs); }, + applyStoredPreset() { + let storedPreset = + window.localStorage["tabulatorPreset-" + this.$props.presetsId]; + if (!storedPreset) return; + + let preset = null; + + if (storedPreset.search("custom:") === 0) { + const customPresetId = parseInt(storedPreset.slice(7)); + preset = this.customUserPresets.find( + (customPreset) => customPreset.id === customPresetId, + ); + } else if (storedPreset.search("general:") === 0) { + const generalPresetName = storedPreset.slice(8); + preset = this.$props.generalPresets.find( + (generalPreset) => generalPreset.name === generalPresetName, + ); + } + + if (!preset) return; + + this.applyPreset(preset); + }, }, async created() { await this.$p.loadCategory(["tabulator_presets", "global"]); await this.fetchCustomUserTabulatorPresets(); + this.applyStoredPreset(); }, template: /*html*/ `
From b277725cc34a5fa3966473623f5b634ab0620dac Mon Sep 17 00:00:00 2001 From: adisposkofh Date: Tue, 28 Jul 2026 12:48:28 +0200 Subject: [PATCH 22/24] displaying name of applied tabulator preset (w/ 'modified' marker as necessary) --- .../js/components/LVVerwaltung/Setup/Table.js | 24 +- public/js/components/filter/Filter.js | 583 ++++++++++++------ .../components/filter/Filter/TablePresets.js | 13 +- system/phrasesupdate.php | 20 + 4 files changed, 436 insertions(+), 204 deletions(-) diff --git a/public/js/components/LVVerwaltung/Setup/Table.js b/public/js/components/LVVerwaltung/Setup/Table.js index 67c63c3bb..c26d1cd65 100644 --- a/public/js/components/LVVerwaltung/Setup/Table.js +++ b/public/js/components/LVVerwaltung/Setup/Table.js @@ -111,7 +111,24 @@ export default { raumtypalternativ: this.lehreinheitRaumtypAlternativeDefault, lehrfach_id: '' - } + }, + tabulatorGeneralPresets: [ + { + id: null, + name: "Straight Ls", + displayedColumns: [ + "lehrveranstaltung_id", + "lehreinheit_id", + ], + headerFilters: { + lehrveranstaltung_id: "L" + }, + sort: { + column: "lehreinheit_id", + direction: "desc", + }, + } + ], } }, computed: { @@ -528,7 +545,10 @@ export default { new-btn-label="LV-Teil hinzufügen" new-btn-show :new-btn-disabled="!lv_info" - @click:new="showLehreinheitModal"> + @click:new="showLehreinheitModal" + :isUsingPresets="true" + :presetsId="'lvVerwaltung_table'" + :generalPresets="tabulatorGeneralPresets">