From b991bbd9a5f457a2a11fbc606fbff8a754dd3d6d Mon Sep 17 00:00:00 2001 From: chfhtw Date: Wed, 8 Jul 2026 11:00:20 +0200 Subject: [PATCH] Allow extra columns for studvw student list --- application/config/stv.php | 21 ++ .../controllers/js/Tabulatorcolumns.php | 59 ++++ application/libraries/stv/StudentListLib.php | 15 +- .../views/jssnippets/tabulatorcolumns.js | 19 + .../views/jssnippets/tabulatorcolumns/stv.js | 334 ++++++++++++++++++ .../Stv/Studentenverwaltung/List.js | 178 +--------- 6 files changed, 463 insertions(+), 163 deletions(-) create mode 100644 application/controllers/js/Tabulatorcolumns.php create mode 100644 application/views/jssnippets/tabulatorcolumns.js create mode 100644 application/views/jssnippets/tabulatorcolumns/stv.js 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" >