Allow extra columns for studvw student list

This commit is contained in:
chfhtw
2026-07-08 11:00:20 +02:00
parent 5bed848aae
commit b991bbd9a5
6 changed files with 463 additions and 163 deletions
+21
View File
@@ -1,6 +1,27 @@
<?php
$config['number_displayed_past_studiensemester_default'] = 5;
# Additional columns:
/*
$config["list_columns"] = [
'fieldname' => [
'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' => [
@@ -0,0 +1,59 @@
<?php
/**
* Copyright (C) 2026 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 <https://www.gnu.org/licenses/>.
*/
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
]);
}
}
+13 -2
View File
@@ -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);
}
}
//------------------------------------------------------------------------------------------------------------------
@@ -0,0 +1,19 @@
let result = [];
let current;
<?php foreach ($snippets as $path) { ?>
current = (tabulatorcolumns => {
<?php $this->load->view($path); ?>
})(result);
if (current) {
if (Array.isArray(current))
result = result.concat(current);
else
result.push(current);
}
<?php } ?>
export default result;
@@ -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: '<i class="fas fa-check text-success"></i>',
crossElement: '<i class="fas fa-times text-danger"></i>'
},
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: '<i class="fas fa-check text-success"></i>',
crossElement: '<i class="fas fa-times text-danger"></i>'
},
headerFilter: "tickCross",
headerFilterParams: {
"tristate": true,
elementAttributes: {
"value": "true"
}
},
headerFilterEmptyCheck(value) {
return value === null
}
},
{
title: "Unruly",
field: "unruly",
visible: false
}
];
@@ -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 '&nbsp;';
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 '&nbsp;';
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: '<i class="fas fa-check text-success"></i>',
crossElement: '<i class="fas fa-times text-danger"></i>'
},
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: '<i class="fas fa-check text-success"></i>',
crossElement: '<i class="fas fa-times text-danger"></i>'
},
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"
>