diff --git a/application/config/stv.php b/application/config/stv.php
index 34a30a96e..eb96952b0 100644
--- a/application/config/stv.php
+++ b/application/config/stv.php
@@ -1,6 +1,27 @@
[
+ 'js' => 'path/to/snippet.js', // tabulator config snippet (eg: return { name: 'Name', field: 'fieldname' }; )
+ 'default' => "SQL SELECT statement for value",
+ 'joins' => [
+ // additional joins needed for the SELECT statement above
+ [
+ 'tablename',
+ 'join condition', // single fieldname for USING or full condition for ON
+ 'LEFT|RIGHT', // optional - join type
+ 'after_xxx|before_xxx|end' // optional - position in query
+ ]
+ // ...
+ ]
+ ]
+];
+*/
+
$config['tabs'] =
[
'details' => [
diff --git a/application/controllers/js/Tabulatorcolumns.php b/application/controllers/js/Tabulatorcolumns.php
new file mode 100644
index 000000000..0f0cea707
--- /dev/null
+++ b/application/controllers/js/Tabulatorcolumns.php
@@ -0,0 +1,59 @@
+.
+ */
+
+if (! defined('BASEPATH')) exit('No direct script access allowed');
+
+/**
+ * This controller generates javascript from snippets for tabulator columns
+ */
+class Tabulatorcolumns extends Auth_Controller
+{
+ public function __construct()
+ {
+ parent::__construct([
+ 'stv' => ['admin:r', 'assistenz:r']
+ ]);
+ }
+
+ /**
+ * Render tabulator columns for config
+ *
+ * @param string $config
+ *
+ * @return void
+ */
+ public function _remap($config)
+ {
+ $this->load->config($config);
+ $list_columns = $this->config->item('list_columns') ?: [];
+
+ $snippets = [
+ 'jssnippets/tabulatorcolumns/' . $config . '.js'
+ ];
+
+ foreach ($list_columns as $col) {
+ $snippets[] = $col['js'];
+ }
+
+ $this->output->set_content_type('application/javascript');
+
+ $this->load->view('jssnippets/tabulatorcolumns.js', [
+ 'snippets' => $snippets
+ ]);
+ }
+}
diff --git a/application/libraries/stv/StudentListLib.php b/application/libraries/stv/StudentListLib.php
index bb44c8bb0..6d1e047e8 100644
--- a/application/libraries/stv/StudentListLib.php
+++ b/application/libraries/stv/StudentListLib.php
@@ -40,6 +40,8 @@ class StudentListLib
{
$this->_ci =& get_instance(); // get code igniter instance
+ $this->_ci->load->config('stv');
+
$this->_ci->load->model('crm/Prestudent_model', 'PrestudentModel');
if (isset($params['allowedStgs']))
@@ -111,7 +113,7 @@ class StudentListLib
) prest
WHERE laststatus NOT IN ('Abbrecher', 'Abgewiesener', 'Absolvent')
AND priorisierung <= tbl_prestudent.priorisierung
- ) || ' (' || COALESCE(tbl_prestudent.priorisierung::text, ' '::text) || ')' AS priorisierung_relativ", false); // TODO(chris): overwrite in fetchStudents
+ ) || ' (' || COALESCE(tbl_prestudent.priorisierung::text, ' '::text) || ')' AS priorisierung_relativ", false);
$this->addSelect('mentor');
$this->addSelect('b.aktiv AS bnaktiv');
$this->addSelect('unruly');
@@ -119,7 +121,7 @@ class StudentListLib
// Add default JOINs
$this->addJoin('public.tbl_studiengang stg', 'studiengang_kz', 'LEFT');
$this->addJoin('public.tbl_person p', 'person_id');
- $this->addJoin('public.tbl_student s', 'prestudent_id', 'LEFT'); // TODO(chris): overwrite in fetchStudents
+ $this->addJoin('public.tbl_student s', 'prestudent_id', 'LEFT');
$this->addJoin('public.tbl_prestudentstatus pls', '
pls.status_kurzbz=public.get_rolle_prestudent(tbl_prestudent.prestudent_id, NULL)
AND pls.prestudent_id=tbl_prestudent.prestudent_id
@@ -164,6 +166,15 @@ class StudentListLib
$this->addJoin($subQueryTag, 'tag_data_agg.prestudent_id = tbl_prestudent.prestudent_id', 'LEFT');
}
+
+ // Add SELECTs and JOINs from config
+ $config = $this->_ci->config->item('list_columns') ?: [];
+ foreach ($config as $conf) {
+ $this->addSelect($conf['default']);
+ if (isset($conf['joins']))
+ foreach ($conf['joins'] as $join)
+ call_user_func_array([$this, 'addJoin'], $join);
+ }
}
//------------------------------------------------------------------------------------------------------------------
diff --git a/application/views/jssnippets/tabulatorcolumns.js b/application/views/jssnippets/tabulatorcolumns.js
new file mode 100644
index 000000000..44667e45b
--- /dev/null
+++ b/application/views/jssnippets/tabulatorcolumns.js
@@ -0,0 +1,19 @@
+let result = [];
+let current;
+
+
+
+current = (tabulatorcolumns => {
+load->view($path); ?>
+})(result);
+
+if (current) {
+ if (Array.isArray(current))
+ result = result.concat(current);
+ else
+ result.push(current);
+}
+
+
+
+export default result;
\ No newline at end of file
diff --git a/application/views/jssnippets/tabulatorcolumns/stv.js b/application/views/jssnippets/tabulatorcolumns/stv.js
new file mode 100644
index 000000000..6aa395071
--- /dev/null
+++ b/application/views/jssnippets/tabulatorcolumns/stv.js
@@ -0,0 +1,334 @@
+return [
+ {
+ title: "UID",
+ titlePhrase: 'person/uid',
+ field: "uid",
+ headerFilter: true
+ },
+ {
+ title: "TitelPre",
+ titlePhrase: 'person/titelpre',
+ field: "titelpre",
+ headerFilter: "list",
+ headerFilterParams: {
+ valuesLookup: true,
+ listOnEmpty: true,
+ autocomplete: true,
+ sort: "asc"
+ }
+ },
+ {
+ title: "Nachname",
+ titlePhrase: 'person/nachname',
+ field: "nachname",
+ headerFilter: true
+ },
+ {
+ title: "Vorname",
+ titlePhrase: 'person/vorname',
+ field: "vorname",
+ headerFilter: true
+ },
+ {
+ title: "Wahlname",
+ titlePhrase: 'person/wahlname',
+ field: "wahlname",
+ visible: false,
+ headerFilter: true
+ },
+ {
+ title: "Vornamen",
+ titlePhrase: 'person/vornamen',
+ field: "vornamen",
+ visible: false,
+ headerFilter: true
+ },
+ {
+ title: "TitelPost",
+ titlePhrase: 'person/titelpost',
+ field: "titelpost",
+ headerFilter: "list",
+ headerFilterParams: {
+ valuesLookup: true,
+ listOnEmpty: true,
+ autocomplete: true,
+ sort: "asc"
+ }
+ },
+ {
+ title: "Ersatzkennzeichen",
+ titlePhrase: 'person/ersatzkennzeichen',
+ field: "ersatzkennzeichen",
+ headerFilter: true
+ },
+ {
+ title: "Geburtsdatum",
+ titlePhrase: 'person/geburtsdatum',
+ field: "gebdatum",
+ formatter: 'dateFormatter',
+ headerFilter: true,
+ headerFilterFunc(headerValue, rowValue) {
+ const matches = headerValue.match(/^(([0-9]{2})\.)?([0-9]{2})\.([0-9]{4})?$/);
+ let comparestr = headerValue;
+ if(matches !== null) {
+ const year = (matches[4] !== undefined) ? matches[4] : '';
+ const month = matches[3];
+ const day = (matches[2] !== undefined) ? matches[2] : '';
+ comparestr = year + '-' + month + '-' + day;
+ }
+ return rowValue.match(comparestr);
+ }
+ },
+ {
+ title: "Geschlecht",
+ titlePhrase: 'person/geschlecht',
+ field: "geschlecht",
+ headerFilter: "list",
+ headerFilterParams: {
+ values: {
+ 'm': 'männlich',
+ 'w': 'weiblich',
+ 'x': 'divers',
+ 'u': 'unbekannt'
+ },
+ listOnEmpty: true,
+ autocomplete: true
+ }
+ },
+ {
+ title: "Sem.",
+ titlePhrase: 'lehre/sem',
+ field: "semester",
+ headerFilter: "list",
+ headerFilterParams: {
+ valuesLookup: true,
+ listOnEmpty: true,
+ autocomplete: true,
+ sort: "asc"
+ }
+ },
+ {
+ title: "Verb.",
+ titlePhrase: 'lehre/verb',
+ field: "verband",
+ headerFilter: "list",
+ headerFilterParams: {
+ valuesLookup: true,
+ listOnEmpty: true,
+ autocomplete: true,
+ sort: "asc"
+ }
+ },
+ {
+ title: "Grp.",
+ titlePhrase: 'lehre/grp',
+ field: "gruppe",
+ headerFilter: "list",
+ headerFilterParams: {
+ valuesLookup: true,
+ listOnEmpty: true,
+ autocomplete: true,
+ sort: "asc"
+ }
+ },
+ {
+ title: "Studiengang",
+ titlePhrase: 'lehre/studiengang',
+ field: "studiengang",
+ headerFilter: "list",
+ headerFilterParams: {
+ valuesLookup: true,
+ listOnEmpty: true,
+ autocomplete: true,
+ sort: "asc"
+ }
+ },
+ {
+ title: "Studiengang_kz",
+ titlePhrase: 'lehre/studiengang_kz',
+ field: "studiengang_kz",
+ visible: false,
+ headerFilter: true
+ },
+ {
+ title: "Personenkennzeichen",
+ titlePhrase: 'person/personenkennzeichen',
+ field: "matrikelnr",
+ headerFilter: true
+ },
+ {
+ title: "PersonID",
+ titlePhrase: 'person/person_id',
+ field: "person_id",
+ headerFilter: true
+ },
+ {
+ title: "Status",
+ titlePhrase: 'global/status',
+ field: "status",
+ headerFilter: "list",
+ headerFilterParams: {
+ valuesLookup: true,
+ listOnEmpty: true,
+ autocomplete: true,
+ sort: "asc"
+ }
+ },
+ {
+ title: "Status Datum",
+ titlePhrase: 'profilUpdate/statusDate',
+ field: "status_datum",
+ visible: false,
+ formatter: 'dateFormatter'
+ },
+ {
+ title: "Status Bestaetigung",
+ titlePhrase: 'global/status_bestaetigung',
+ field: "status_bestaetigung",
+ visible: false,
+ formatter: 'dateFormatter',
+ headerFilter: true
+ },
+ {
+ title: "EMail (Privat)",
+ titlePhrase: 'person/email_private',
+ field: "mail_privat",
+ visible: false,
+ headerFilter: true
+ },
+ {
+ title: "EMail (Intern)",
+ titlePhrase: 'person/email_intern',
+ field: "mail_intern",
+ visible: false,
+ headerFilter: true
+ },
+ {
+ title: "Anmerkungen",
+ titlePhrase: 'stv/notes_person',
+ field: "anmerkungen",
+ visible: false,
+ headerFilter: true
+ },
+ {
+ title: "AnmerkungPre",
+ titlePhrase: 'stv/notes_prestudent',
+ field: "anmerkung",
+ visible: false,
+ headerFilter: true
+ },
+ {
+ title: "OrgForm",
+ titlePhrase: 'lehre/orgform',
+ field: "orgform_kurzbz",
+ headerFilter: "list",
+ headerFilterParams: {
+ valuesLookup: true,
+ listOnEmpty: true,
+ autocomplete: true,
+ sort: "asc"
+ }
+ },
+ {
+ title: "Aufmerksamdurch",
+ titlePhrase: 'person/aufmerksamDurch',
+ field: "aufmerksamdurch_kurzbz",
+ visible: false
+ },
+ {
+ title: "Gesamtpunkte",
+ titlePhrase: 'admission/gesamtpunkte',
+ field: "punkte",
+ visible: false
+ },
+ {
+ title: "Aufnahmegruppe",
+ titlePhrase: 'stv/aufnahmegruppe_kurzbz',
+ field: "aufnahmegruppe_kurzbz",
+ visible: false
+ },
+ {
+ title: "Dual",
+ titlePhrase: 'lehre/dual_short',
+ field: "dual",
+ visible: false,
+ formatter: 'tickCross',
+ formatterParams: {
+ tickElement: '',
+ crossElement: ''
+ },
+ headerFilter: "tickCross",
+ headerFilterParams: {
+ "tristate": true,
+ elementAttributes: {
+ "value": "true"
+ }
+ },
+ headerFilterEmptyCheck(value) {
+ return value === null
+ }
+ },
+ {
+ title: "Matrikelnummer",
+ titlePhrase: 'person/matrikelnummer',
+ field: "matr_nr",
+ visible: false,
+ headerFilter: true
+ },
+ {
+ title: "Studienplan",
+ titlePhrase: 'lehre/studienplan',
+ field: "studienplan_bezeichnung",
+ headerFilter: "list",
+ headerFilterParams: {
+ valuesLookup: true,
+ listOnEmpty: true,
+ autocomplete: true,
+ sort: "asc"
+ }
+ },
+ {
+ title: "PreStudentInnenID",
+ titlePhrase: 'ui/prestudent_id',
+ field: "prestudent_id",
+ headerFilter: true
+ },
+ {
+ title: "Priorität",
+ titlePhrase: 'lehre/prioritaet',
+ field: "priorisierung_relativ"
+ },
+ {
+ title: "Mentor",
+ titlePhrase: 'stv/mentor',
+ field: "mentor",
+ visible: false
+ },
+ {
+ title: "Aktiv",
+ titlePhrase: 'person/aktiv',
+ field: "bnaktiv",
+ visible: false,
+ formatter: 'tickCross',
+ formatterParams: {
+ allowEmpty: true,
+ tickElement: '',
+ crossElement: ''
+ },
+ headerFilter: "tickCross",
+ headerFilterParams: {
+ "tristate": true,
+ elementAttributes: {
+ "value": "true"
+ }
+ },
+ headerFilterEmptyCheck(value) {
+ return value === null
+ }
+ },
+ {
+ title: "Unruly",
+ field: "unruly",
+ visible: false
+ }
+];
diff --git a/public/js/components/Stv/Studentenverwaltung/List.js b/public/js/components/Stv/Studentenverwaltung/List.js
index 1aa2c6b70..ea9803223 100644
--- a/public/js/components/Stv/Studentenverwaltung/List.js
+++ b/public/js/components/Stv/Studentenverwaltung/List.js
@@ -12,6 +12,8 @@ import { capitalize } from '../../../helpers/StringHelpers.js';
import draggable from '../../../directives/draggable.js';
+import StvColumns from '../../../../../index.ci.php/js/tabulatorcolumns/stv';
+
export default {
name: "ListPrestudents",
components: {
@@ -51,92 +53,24 @@ export default {
'filterActive'
],
data() {
- function dateFormatter(cell)
- {
- let val = cell.getValue();
- if (!val)
- return ' ';
- let date = new Date(val);
- return date.toLocaleDateString('de-AT', {
- "day": "2-digit",
- "month": "2-digit",
- "year": "numeric"
- });
- }
+ Tabulator.extendModule("format", "formatters", {
+ dateFormatter(cell) {
+ let val = cell.getValue();
+ if (!val)
+ return ' ';
+ let date = new Date(val);
+ return date.toLocaleDateString('de-AT', {
+ "day": "2-digit",
+ "month": "2-digit",
+ "year": "numeric"
+ });
+ }
+ });
return {
tabulatorOptions: {
- columns:[
- {title:"UID", field:"uid", headerFilter: true},
- {title:"TitelPre", field:"titelpre", headerFilter: "list", headerFilterParams: {valuesLookup:true, listOnEmpty:true, autocomplete:true, sort:"asc"}},
- {title:"Nachname", field:"nachname", headerFilter: true},
- {title:"Vorname", field:"vorname", headerFilter: true},
- {title:"Wahlname", field:"wahlname", visible:false, headerFilter: true},
- {title:"Vornamen", field:"vornamen", visible:false, headerFilter: true},
- {title:"TitelPost", field:"titelpost", headerFilter: "list", headerFilterParams: {valuesLookup:true, listOnEmpty:true, autocomplete:true, sort:"asc"}},
- {title:"Ersatzkennzeichen", field:"ersatzkennzeichen", headerFilter: true},
- {
- title: "Geburtsdatum",
- field: "gebdatum",
- formatter: dateFormatter,
- headerFilter: true,
- headerFilterFunc(headerValue, rowValue) {
- const matches = headerValue.match(/^(([0-9]{2})\.)?([0-9]{2})\.([0-9]{4})?$/);
- let comparestr = headerValue;
- if(matches !== null) {
- const year = (matches[4] !== undefined) ? matches[4] : '';
- const month = matches[3];
- const day = (matches[2] !== undefined) ? matches[2] : '';
- comparestr = year + '-' + month + '-' + day;
- }
- return rowValue.match(comparestr);
- }
- },
- {title:"Geschlecht", field:"geschlecht", headerFilter: "list", headerFilterParams: {values:{'m':'männlich','w':'weiblich','x':'divers','u':'unbekannt'}, listOnEmpty:true, autocomplete:true}},
- {title:"Sem.", field:"semester", headerFilter: "list", headerFilterParams: {valuesLookup:true, listOnEmpty:true, autocomplete:true, sort:"asc"}},
- {title:"Verb.", field:"verband", headerFilter: "list", headerFilterParams: {valuesLookup:true, listOnEmpty:true, autocomplete:true, sort:"asc"}},
- {title:"Grp.", field:"gruppe", headerFilter: "list", headerFilterParams: {valuesLookup:true, listOnEmpty:true, autocomplete:true, sort:"asc"}},
- {title:"Studiengang", field:"studiengang", headerFilter: "list", headerFilterParams: {valuesLookup:true, listOnEmpty:true, autocomplete:true, sort:"asc"}},
- {title:"Studiengang_kz", field:"studiengang_kz", visible:false, headerFilter: true},
- {title:"Personenkennzeichen", field:"matrikelnr", headerFilter: true},
- {title:"PersonID", field:"person_id", headerFilter: true},
- {title:"Status", field:"status", headerFilter: "list", headerFilterParams: {valuesLookup:true, listOnEmpty:true, autocomplete:true, sort:"asc"}},
- {title:"Status Datum", field:"status_datum", visible:false, formatter:dateFormatter},
- {title:"Status Bestaetigung", field:"status_bestaetigung", visible:false, formatter:dateFormatter, headerFilter: true},
- {title:"EMail (Privat)", field:"mail_privat", visible:false, headerFilter: true},
- {title:"EMail (Intern)", field:"mail_intern", visible:false, headerFilter: true},
- {title:"Anmerkungen", field:"anmerkungen", visible:false, headerFilter: true},
- {title:"AnmerkungPre", field:"anmerkung", visible:false, headerFilter: true},
- {title:"OrgForm", field:"orgform_kurzbz", headerFilter: "list", headerFilterParams: {valuesLookup:true, listOnEmpty:true, autocomplete:true, sort:"asc"}},
- {title:"Aufmerksamdurch", field:"aufmerksamdurch_kurzbz", visible:false},
- {title:"Gesamtpunkte", field:"punkte", visible:false},
- {title:"Aufnahmegruppe", field:"aufnahmegruppe_kurzbz", visible:false},
- {title:"Dual", field:"dual", visible:false,
- formatter:'tickCross', formatterParams: {
- tickElement: '',
- crossElement: ''
- },
- headerFilter:"tickCross", headerFilterParams: {
- "tristate":true, elementAttributes:{"value":"true"}
- }, headerFilterEmptyCheck:function(value){return value === null}
- },
- {title:"Matrikelnummer", field:"matr_nr", visible:false, headerFilter: true},
- {title:"Studienplan", field:"studienplan_bezeichnung", headerFilter: "list", headerFilterParams: {valuesLookup:true, listOnEmpty:true, autocomplete:true, sort:"asc"}},
- {title:"PreStudentInnenID", field:"prestudent_id", headerFilter: true},
- {title:"Priorität", field:"priorisierung_relativ"},
- {title:"Mentor", field:"mentor", visible:false},
- {title:"Aktiv", field:"bnaktiv", visible:false,
- formatter:'tickCross', formatterParams: {
- allowEmpty:true,
- tickElement: '',
- crossElement: ''
- },
- headerFilter:"tickCross", headerFilterParams: {
- "tristate":true, elementAttributes:{"value":"true"}
- }, headerFilterEmptyCheck:function(value){return value === null}
- },
- {title:"Unruly", field:"unruly", visible:false},
- ],
+ columns: StvColumns,
+ locale: true,
rowFormatter(row) {
if (row.getData().bnaktiv === false) {
row.getElement().classList.add('text-black','text-opacity-50','fst-italic');
@@ -307,84 +241,7 @@ export default {
this.tabulatorOptions.columns.splice(2, 0, coltags);
}
},
- watch: {
- '$p.user_language.value'(n, o) {
- if (n !== o && o !== undefined && this.$refs.table.tableBuilt) {
- this.translateTabulator();
- }
- }
- },
methods: {
- translateTabulator() {
- this.$p
- .loadCategory(['global', 'person', 'lehre', 'ui', 'profilUpdate', 'admission', 'stv'])
- .then(() => {
- const translations = {
- uid: capitalize(this.$p.t('person/uid')),
- titelpre: capitalize(this.$p.t('person/titelpre')),
- nachname: capitalize(this.$p.t('person/nachname')),
- vorname: capitalize(this.$p.t('person/vorname')),
- wahlname: capitalize(this.$p.t('person/wahlname')),
- vornamen: capitalize(this.$p.t('person/vornamen')),
- titelpost: capitalize(this.$p.t('person/titelpost')),
- ersatzkennzeichen: capitalize(this.$p.t('person/ersatzkennzeichen')),
- gebdatum: capitalize(this.$p.t('person/geburtsdatum')),
- geschlecht: capitalize(this.$p.t('person/geschlecht')),
- semester: capitalize(this.$p.t('lehre/sem')),
- verband: capitalize(this.$p.t('lehre/verb')),
- gruppe: capitalize(this.$p.t('lehre/grp')),
- studiengang: capitalize(this.$p.t('lehre/studiengang')),
- studiengang_kz: capitalize(this.$p.t('lehre/studiengang_kz')),
- matrikelnr: capitalize(this.$p.t('person/personenkennzeichen')),
- person_id: capitalize(this.$p.t('person/person_id')),
- status: capitalize(this.$p.t('global/status')),
- status_datum: capitalize(this.$p.t('profilUpdate/statusDate')),
- status_bestaetigung: capitalize(this.$p.t('global/status_bestaetigung')),
- mail_privat: capitalize(this.$p.t('person/email_private')),
- mail_intern: capitalize(this.$p.t('person/email_intern')),
- anmerkungen: capitalize(this.$p.t('stv/notes_person')),
- anmerkung: capitalize(this.$p.t('stv/notes_prestudent')),
- orgform_kurzbz: capitalize(this.$p.t('lehre/orgform')),
- aufmerksamdurch_kurzbz: capitalize(this.$p.t('person/aufmerksamDurch')),
- punkte: capitalize(this.$p.t('admission/gesamtpunkte')),
- aufnahmegruppe_kurzbz: capitalize(this.$p.t('stv/aufnahmegruppe_kurzbz')),
- dual: capitalize(this.$p.t('lehre/dual_short')),
- matr_nr: capitalize(this.$p.t('person/matrikelnummer')),
- studienplan_bezeichnung: capitalize(this.$p.t('lehre/studienplan')),
- prestudent_id: capitalize(this.$p.t('ui/prestudent_id')),
- priorisierung_relativ: capitalize(this.$p.t('lehre/prioritaet')),
- mentor: capitalize(this.$p.t('stv/mentor')),
- bnaktiv: capitalize(this.$p.t('person/aktiv'))
- };
-
- /** NOTE(chris):
- * use this approach because updateDefinition
- * on the Tabulator columns is way slower and
- * freezes up the GUI.
- */
- // Overwrite definition for column show/hide
- this.$refs.table.tabulator.getColumns().forEach(col => {
- const trans = translations[col.getField()];
- if (!trans)
- return;
- col.getDefinition().title = trans;
- });
- // Overwrite node in dom
- this.$refs.table.tabulator.element
- .querySelectorAll('.tabulator-col[tabulator-field]')
- .forEach(el => {
- const field = el.getAttribute('tabulator-field');
- if (!translations[field])
- return;
-
- const title = el.querySelector('.tabulator-col-title');
- if (!title)
- return;
-
- title.innerText = translations[field];
- });
- });
- },
reload() {
this.$refs.table.reloadTable();
},
@@ -622,7 +479,6 @@ export default {
new-btn-show
:new-btn-label="$p.t('stv/action_new')"
@click:new="actionNewPrestudent"
- @table-built="translateTabulator"
:useSelectionSpan="false"
@headerFilterOn="handleHeaderFilter"
>