diff --git a/application/config/stv.php b/application/config/stv.php index e03c00084..84b148362 100644 --- a/application/config/stv.php +++ b/application/config/stv.php @@ -61,7 +61,11 @@ $config['tabs'] = 'notes' => [ //if true, the count of Messages will be shown in the header of the Tab Messages 'showCountNotes' => true - ] + ], + 'combinePeople' => [ + //multitab should only be shown with this length of selection + 'validCountMulti' => 2, + ], ]; // List of fields to show when ZGV_DOKTOR_ANZEIGEN is defined @@ -117,5 +121,6 @@ $config['students_tab_order'] = [ 'status', 'groups', 'finalexam', + 'combinePeople', 'archive', ]; diff --git a/application/controllers/api/frontend/v1/stv/Config.php b/application/controllers/api/frontend/v1/stv/Config.php index 97d626246..3bf48bf5b 100644 --- a/application/controllers/api/frontend/v1/stv/Config.php +++ b/application/controllers/api/frontend/v1/stv/Config.php @@ -33,6 +33,8 @@ class Config extends FHCAPI_Controller { // TODO(chris): permissions parent::__construct([ + 'get' => ['admin:r', 'assistenz:r'], + 'set' => ['admin:r', 'assistenz:r'], 'filter' => ['admin:r', 'assistenz:r'], 'student' => ['admin:r', 'assistenz:r'], 'students' => ['admin:r', 'assistenz:r'] @@ -55,6 +57,95 @@ class Config extends FHCAPI_Controller } /** + * get App config + */ + public function get() + { + $this->load->model('system/Variable_model', 'VariableModel'); + $this->load->config('stv'); + + $config = []; + + #number_displayed_past_studiensemester + $result = $this->VariableModel->getVariables(getAuthUID(), ['number_displayed_past_studiensemester']); + $data = $this->getDataOrTerminateWithError($result); + + $number_displayed_past_studiensemester_default = $this->config->item('number_displayed_past_studiensemester_default'); + + $config['number_displayed_past_studiensemester'] = [ + "type" => "number", + "label" => $this->p->t('stv', 'settings_no_displayed_past_sem'), + "value" => $data['number_displayed_past_studiensemester'] + ?? $number_displayed_past_studiensemester_default + ]; + + #font_size + $result = $this->VariableModel->getVariables(getAuthUID(), ['stv_font_size']); + $data = $this->getDataOrTerminateWithError($result); + $config['font_size'] = [ + "type" => "select", + "label" => $this->p->t('stv', 'settings_fontsize'), + "value" => $data['stv_font_size'] ?? "fs_normal", + "options" => [ + "fs_xx-small" => $this->p->t('stv', 'settings_fontsize_xx-small'), + "fs_x-small" => $this->p->t('stv', 'settings_fontsize_x-small'), + "fs_small" => $this->p->t('stv', 'settings_fontsize_small'), + "fs_normal" => $this->p->t('stv', 'settings_fontsize_normal'), + "fs_big" => $this->p->t('stv', 'settings_fontsize_big'), + "fs_huge" => $this->p->t('stv', 'settings_fontsize_huge') + ] + ]; + + #others + Events::trigger('stv_config_get', function & () use (&$config) { + return $config; + }); + + $this->terminateWithSuccess($config); + } + + /** + * set App config + */ + public function set() + { + $this->load->model('system/Variable_model', 'VariableModel'); + $this->load->library('form_validation'); + + $this->form_validation->set_rules( + 'number_displayed_past_studiensemester', + $this->p->t('stv', 'settings_no_displayed_past_sem'), + 'required|integer' + ); + $this->form_validation->set_rules( + 'font_size', + $this->p->t('stv', 'settings_fontsize'), + 'required|in_list[fs_xx-small,fs_x-small,fs_small,fs_normal,fs_big,fs_huge]' + ); + + Events::trigger('stv_config_validation', $this->form_validation); + + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + + + $this->VariableModel->setVariable( + getAuthUID(), + 'number_displayed_past_studiensemester', + $this->input->post('number_displayed_past_studiensemester') + ); + $this->VariableModel->setVariable( + getAuthUID(), + 'stv_font_size', + $this->input->post('font_size') + ); + + Events::trigger('stv_config_set', $this->input); + + $this->terminateWithSuccess(); + } + + /* * Get the config for the student filters * * @return void @@ -407,6 +498,15 @@ class Config extends FHCAPI_Controller ] ]; + if($this->permissionlib->isBerechtigt('basis/person')) + { + $result['combinePeople'] = [ + 'title' => $this->p->t('stv', 'tab_combine_people'), + 'component' => './Stv/Studentenverwaltung/Details/CombinePeople.js', + 'config' => $config['combinePeople'] + ]; + } + $result['kontaktieren'] = [ 'title' => $this->p->t('stv', 'tab_kontaktieren'), 'component' => absoluteJsImportUrl('public/js/components/Stv/Studentenverwaltung/Details/Kontaktieren.js'), diff --git a/application/controllers/api/frontend/v1/stv/Students.php b/application/controllers/api/frontend/v1/stv/Students.php index 12440f036..9dbea65f2 100644 --- a/application/controllers/api/frontend/v1/stv/Students.php +++ b/application/controllers/api/frontend/v1/stv/Students.php @@ -765,6 +765,86 @@ class Students extends FHCAPI_Controller $this->terminateWithSuccess($data); } + /** + * @param string $studiensemester_kurzbz + * + * @return void + */ + public function search($studiensemester_kurzbz) + { + $this->addMeta('ci_method', __FUNCTION__); + $this->addMeta('ci_params', array( + 'studiensemester_kurzbz' => $studiensemester_kurzbz + )); + + $this->load->library('SearchLib', [ 'config' => 'searchstv' ]); + $this->load->library('form_validation'); + + $this->form_validation->set_rules('searchstr', 'searchstr', 'required'); + $this->form_validation->set_rules('types[]', 'types', 'required'); + + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + + $result = $this->searchlib->search($this->input->post('searchstr'), $this->input->post('types')); + + $data = $this->getDataOrTerminateWithError($result); + + + $this->load->model('crm/Prestudent_model', 'PrestudentModel'); + + $this->prepareQuery($studiensemester_kurzbz); + + $this->PrestudentModel->addSelect("COALESCE(v.semester::text, CASE WHEN public.get_rolle_prestudent(tbl_prestudent.prestudent_id, NULL) IN ('Aufgenommener', 'Bewerber', 'Wartender', 'interessent') THEN public.get_absem_prestudent(tbl_prestudent.prestudent_id, NULL)::text ELSE ''::text END) AS semester", false); + $this->PrestudentModel->addSelect('v.verband'); + $this->PrestudentModel->addSelect('v.gruppe'); + + //add status per semester + $this->PrestudentModel->addSelect( + "( + SELECT status_kurzbz + FROM public.tbl_prestudentstatus pss + WHERE pss.prestudent_id = public.tbl_prestudent.prestudent_id + AND pss.studiensemester_kurzbz = " . $this->PrestudentModel->escape($studiensemester_kurzbz) . " + ORDER BY GREATEST(pss.datum, '0001-01-01') DESC + LIMIT 1 + ) AS statusofsemester" + ); + + $this->addSelectPrioRel(); + + $this->addFilter($studiensemester_kurzbz); + + $prestudent_ids = []; + $student_uids = []; + $this->addMeta('data', $data); + foreach ($data as $row) { + $dataset = json_decode($row->data); + if ($row->type == 'prestudent') { + $prestudent_ids[] = $dataset->prestudent_id; + } elseif ($row->type == 'student') { + $student_uids[] = $dataset->uid; + } + } + + if ($prestudent_ids && $student_uids) { + $this->PrestudentModel->db->where_in('tbl_prestudent.prestudent_id', $prestudent_ids); + $this->PrestudentModel->db->or_where_in('s.student_uid', $student_uids); + } elseif ($prestudent_ids) { + $this->PrestudentModel->db->where_in('tbl_prestudent.prestudent_id', $prestudent_ids); + } elseif ($student_uids) { + $this->PrestudentModel->db->where_in('s.student_uid', $student_uids); + } else { + $this->terminateWithSuccess([]); + } + + $result = $this->PrestudentModel->load(); + + $data = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($data); + } + /** * @param string|null $studiensemester_kurzbz * @param string $type diff --git a/application/controllers/api/frontend/v1/stv/Verband.php b/application/controllers/api/frontend/v1/stv/Verband.php index 9fcd97c91..eb25a548b 100644 --- a/application/controllers/api/frontend/v1/stv/Verband.php +++ b/application/controllers/api/frontend/v1/stv/Verband.php @@ -165,7 +165,17 @@ class Verband extends FHCAPI_Controller $this->StudiengangModel->addDistinct(); $this->StudiengangModel->addSelect("CONCAT(" . $this->StudiengangModel->escape($link) . ", semester) AS link", false); - $this->StudiengangModel->addSelect("CONCAT(UPPER(CONCAT(typ, kurzbz)), '-', semester, (SELECT CASE WHEN bezeichnung IS NULL OR bezeichnung='' THEN ''::TEXT ELSE CONCAT(' (', bezeichnung, ')') END FROM public.tbl_lehrverband WHERE studiengang_kz=v.studiengang_kz AND semester=v.semester ORDER BY verband, gruppe LIMIT 1)) AS name", false); + $this->StudiengangModel->addSelect("CONCAT( + UPPER(CONCAT(typ, kurzbz)), + '-', + semester, + ( + SELECT CASE WHEN bezeichnung IS NULL OR bezeichnung='' THEN ''::TEXT ELSE CONCAT(' (', bezeichnung, ')') END + FROM public.tbl_lehrverband + WHERE studiengang_kz=v.studiengang_kz AND semester=v.semester + ORDER BY verband, gruppe LIMIT 1 + ) + ) AS name", false); $this->StudiengangModel->addSelect('semester'); $this->StudiengangModel->addSelect($this->StudiengangModel->escape($studiengang_kz) . '::integer AS stg_kz', false); @@ -173,6 +183,7 @@ class Verband extends FHCAPI_Controller $this->StudiengangModel->addOrder('semester'); if ($org_form !== null) { + $this->StudiengangModel->addSelect("v.orgform_kurzbz"); $this->StudiengangModel->db->group_start(); $this->StudiengangModel->db->where('v.semester', 0); $this->StudiengangModel->db->or_where('v.orgform_kurzbz', $org_form); @@ -188,6 +199,7 @@ class Verband extends FHCAPI_Controller array_unshift($list, [ 'name' => 'PreStudent', 'link' => $link . 'prestudent', + 'no_sem_reload' => true, 'stg_kz' => (int)$studiengang_kz, 'children' => $this->getStdSem($link . 'prestudent/', $studiengang_kz) ]); @@ -216,7 +228,6 @@ class Verband extends FHCAPI_Controller $list = array_merge($list, $result); } } - } $this->terminateWithSuccess($list); } diff --git a/application/views/Studentenverwaltung.php b/application/views/Studentenverwaltung.php index 01e611657..16b8c0045 100644 --- a/application/views/Studentenverwaltung.php +++ b/application/views/Studentenverwaltung.php @@ -53,6 +53,8 @@ $configArray = [ active-addons="" stv-root="" cis-root="" + avatar-url="" + logout-url="" :permissions="" :config="" > diff --git a/public/css/Studentenverwaltung.css b/public/css/Studentenverwaltung.css index 56e99b937..eb6becc15 100644 --- a/public/css/Studentenverwaltung.css +++ b/public/css/Studentenverwaltung.css @@ -11,6 +11,24 @@ html { font-size: .875em; } +html.fs_xx-small { + font-size: .5em; +} +html.fs_x-small { + font-size: .625em; +} +html.fs_small { + font-size: .75em; +} +html.fs_normal { + font-size: .875em; +} +html.fs_big { + font-size: 1em; +} +html.fs_huge { + font-size: 1.125em; +} #appMenu { width: 300px; @@ -43,6 +61,12 @@ html { flex: 1 1 auto; } +#nav-user-btn img { + object-fit: contain; + height: 2.5rem; + width: 2.5rem; +} + .tabulator-row.disabled.tabulator-row-odd .tabulator-cell { color: var(--gray-400); } @@ -160,4 +184,4 @@ html { .tiny-90 div.tox.tox-tinymce { height: 90% !important; -} \ No newline at end of file +} diff --git a/public/css/components/AppMenu.css b/public/css/components/AppMenu.css index b980c1efc..e142858f8 100644 --- a/public/css/components/AppMenu.css +++ b/public/css/components/AppMenu.css @@ -16,11 +16,15 @@ padding: .5rem 1rem; text-decoration: none; } +.fhc-app-menu li a.disabled { + --bs-link-opacity: .5; +} .fhc-app-menu li a.active, .fhc-app-menu li a:hover { --bs-link-color-rgb: var(--bs-link-hover-color-rgb); background: var(--surface-hover); } +.fhc-app-menu li a.disabled, .fhc-app-menu li a.active { pointer-events: none; } diff --git a/public/js/api/factory/stv/config.js b/public/js/api/factory/stv/config.js new file mode 100644 index 000000000..c54b2f8b2 --- /dev/null +++ b/public/js/api/factory/stv/config.js @@ -0,0 +1,32 @@ +/** + * 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 { + get() { + return { + method: 'get', + url: 'api/frontend/v1/stv/config/get' + }; + }, + set(params) { + return { + method: 'post', + url: 'api/frontend/v1/stv/config/set', + params + }; + } +}; \ No newline at end of file diff --git a/public/js/api/factory/stv/students.js b/public/js/api/factory/stv/students.js index 07d4453d8..cae2f31b2 100644 --- a/public/js/api/factory/stv/students.js +++ b/public/js/api/factory/stv/students.js @@ -46,6 +46,14 @@ export default { url: url }; }, + search(params, studiensemester_kurzbz) { + return { + method: 'post', + url: 'api/frontend/v1/stv/students/search/' + + encodeURIComponent(studiensemester_kurzbz), + params + }; + }, verband(relative_path) { return { method: 'get', diff --git a/public/js/apps/Studentenverwaltung.js b/public/js/apps/Studentenverwaltung.js index 5eda16dd6..e6f77d5f5 100644 --- a/public/js/apps/Studentenverwaltung.js +++ b/public/js/apps/Studentenverwaltung.js @@ -148,6 +148,44 @@ const router = VueRouter.createRouter({ next(); } }, + { + name: 'search', + path: `/${ciPath}/studentenverwaltung/:studiensemester_kurzbz/search/:searchstr`, + component: FhcStudentenverwaltung, + props(route) { + return { + url_studiensemester_kurzbz: route.params.studiensemester_kurzbz, + url_mode: 'search', + url_prestudent_id: route.params.searchstr + }; + }, + beforeEnter(to, from, next) { + const isSemester = /^[WS]S\d{4}$/.test(to.params.studiensemester_kurzbz); + if (!isSemester) { + return next({name: 'index'}); + } + next(); + } + }, + { + name: 'search_w_types', + path: `/${ciPath}/studentenverwaltung/:studiensemester_kurzbz/search/:types/:searchstr`, + component: FhcStudentenverwaltung, + props(route) { + return { + url_studiensemester_kurzbz: route.params.studiensemester_kurzbz, + url_mode: 'search', + url_prestudent_id: route.params.type + '/' + route.params.searchstr + }; + }, + beforeEnter(to, from, next) { + const isSemester = /^[WS]S\d{4}$/.test(to.params.studiensemester_kurzbz); + if (!isSemester) { + return next({name: 'index'}); + } + next(); + } + }, { path: '/:pathMatch(.*)*', redirect: { diff --git a/public/js/components/AppConfig.js b/public/js/components/AppConfig.js new file mode 100644 index 000000000..b6b6aaeac --- /dev/null +++ b/public/js/components/AppConfig.js @@ -0,0 +1,135 @@ +/** + * 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 . + */ + +import BsModal from "./Bootstrap/Modal.js"; +import FhcForm from "./Form/Form.js"; +import FormInput from "./Form/Input.js"; + + +export default { + name: 'AppConfig', + components: { + BsModal, + FhcForm, + FormInput + }, + emits: [ + 'update:modelValue' + ], + props: { + modelValue: { + type: Object, + required: true + }, + endpoints: { + type: Object, + required: true + } + }, + data() { + return { + setup: {}, + tempValues: {} + }; + }, + watch: { + '$p.user_language.value'(n, o) { + if (n !== o && o !== undefined && Object.keys(this.setup).length) { + this.$api + .call(this.endpoints.get()) + .then(res => { + this.setup = {}; + Object.keys(res.data).forEach(key => { + const binding = { ...res.data[key] }; + delete binding.value; + delete binding.options; + const options = res.data[key].options; + this.setup[key] = { + binding, + options + }; + }); + }) + .catch(this.$fhcAlert.handleSystemErrors); + } + } + + }, + methods: { + update() { + this.$refs.form + .call(this.endpoints.set(this.tempValues)) + .then(() => { + this.$emit('update:modelValue', { ...this.tempValues }); + this.$refs.modal.hide(); + this.$fhcAlert.alertSuccess(this.$p.t('ui/settings_saved')); + }) + .catch(this.$fhcAlert.handleSystemErrors); + } + }, + created() { + this.$api + .call(this.endpoints.get()) + .then(res => { + Object.keys(res.data).forEach(key => { + const binding = { ...res.data[key] }; + delete binding.value; + delete binding.options; + const options = res.data[key].options; + this.tempValues[key] = res.data[key].value; + this.setup[key] = { + binding, + options + }; + }); + this.$emit('update:modelValue', { ...this.tempValues }); + }) + .catch(this.$fhcAlert.handleSystemErrors); + }, + template: /* html */` + + + + + + + ` +}; diff --git a/public/js/components/AppMenu.js b/public/js/components/AppMenu.js index 33d35f8df..9e85debcd 100644 --- a/public/js/components/AppMenu.js +++ b/public/js/components/AppMenu.js @@ -64,5 +64,6 @@ export default { {{ menu.description }} + ` }; diff --git a/public/js/components/Calendar/Base/Grid.js b/public/js/components/Calendar/Base/Grid.js index c232dd955..3418a9151 100644 --- a/public/js/components/Calendar/Base/Grid.js +++ b/public/js/components/Calendar/Base/Grid.js @@ -316,7 +316,7 @@ export default { template: /* html */`
this.appconfig) } }, data() { return { + appconfig: {}, + configEndpoints: ApiStvConfig, selected: [], searchbaroptions: { origin: 'stv', calcheightonly: true, + nolivesearch: true, types: { student: Vue.computed(() => this.$p.t('search/type_student')), prestudent: Vue.computed(() => this.$p.t('search/type_prestudent')) @@ -123,6 +134,8 @@ export default { studiengangKz: undefined, studiengangKuerzel: '', studiensemesterKurzbz: this.defaultSemester, + selected_semester: undefined, + selected_orgform: undefined, lists: { nations: [], sprachen: [], @@ -131,6 +144,44 @@ export default { verbandEndpoint: ApiStvVerband } }, + computed: { + appMenuExtraItems() { + const extraItems = []; + + if (this.studiengangKz !== undefined && this.selected_semester !== undefined) { + const studiengang_kz = String(this.studiengangKz); + const semester = String(this.selected_semester); + const orgform = this.selected_orgform || ''; + + extraItems.push({ + link: FHC_JS_DATA_STORAGE_OBJECT.app_root + + 'content/statistik/notenspiegel.php?type=xls' + + '&studiengang_kz=' + studiengang_kz + + '&semester=' + semester + + '&orgform=' + orgform, + description: 'stv/grade_report_xls' + }); + extraItems.push({ + link: FHC_JS_DATA_STORAGE_OBJECT.app_root + + 'content/statistik/notenspiegel_erweitert.php?typ=xls' + + '&studiengang_kz=' + studiengang_kz + + '&semester=' + semester + + '&orgform=' + orgform, + description: 'stv/grade_report_xls_extended' + }); + extraItems.push({ + link: FHC_JS_DATA_STORAGE_OBJECT.app_root + + 'content/statistik/notenspiegel.php?type=html' + + '&studiengang_kz=' + studiengang_kz + + '&semester=' + semester + + '&orgform=' + orgform, + description: 'stv/grade_report_html' + }); + } + + return extraItems; + } + }, watch: { 'url_studiensemester_kurzbz': function (newVal, oldVal) { if (newVal !== oldVal) { @@ -146,6 +197,25 @@ export default { }, 'url_mode': function () { this.handlePersonUrl(); + }, + url_prestudent_id() { + this.handlePersonUrl(); + }, + 'appconfig.font_size'() { + // add to html class + const classList = Object.keys(this.$refs.config.setup.font_size.options); + classList.forEach(cn => document.documentElement.classList.remove(cn)); + document.documentElement.classList.add(this.appconfig.font_size); + // recalc Tabulator heights + if (this.$el) { + const tabulatorEls = this.$el.querySelectorAll('.tabulator'); + for (const el of tabulatorEls) { + const tabulators = Tabulator.findTable(el); + if (tabulators) { + tabulators[0].searchRows().forEach(row => row.normalizeHeight()); + } + } + } } }, methods: { @@ -159,7 +229,7 @@ export default { } }, buildPrestudentSearchResultLink(data) { - return this.$fhcApi.getUri( + return this.$api.getUri( '/studentenverwaltung' + '/' + this.studiensemesterKurzbz + '/prestudent/' @@ -167,7 +237,7 @@ export default { ); }, buildStudentSearchResultLink(data) { - return this.$fhcApi.getUri( + return this.$api.getUri( '/studentenverwaltung' + '/' + this.studiensemesterKurzbz + '/student/' @@ -175,14 +245,14 @@ export default { ); }, buildPersonSearchResultLink(data) { - return this.$fhcApi.getUri( + return this.$api.getUri( '/studentenverwaltung' + '/' + this.studiensemesterKurzbz + '/person/' + data.person_id ); }, - onSelectVerband( {link, studiengang_kz}) { + onSelectVerband({ link, studiengang_kz, semester, orgform_kurzbz }) { let urlpath = String(link); if (!urlpath.match(/\/prestudent/)) { @@ -191,6 +261,8 @@ export default { this.$refs.stvList.updateUrl(ApiStv.students.verband(urlpath)); this.studiengangKz = studiengang_kz; + this.selected_semester = semester; + this.selected_orgform = orgform_kurzbz; const stg = this.lists.stgs.find((element) => { return (element.studiengang_kz === this.studiengangKz); }); @@ -249,6 +321,34 @@ export default { ApiStv.students.person(this.$route.params.person_id, 'CURRENT_SEMESTER'), true ); + } else if (this.$route.params.searchstr) { + const searchsettings = { + searchstr: this.$route.params.searchstr, + types: this.$route.params.types?.split('+') || [] + }; + + // init into student list + this.$refs.stvList.updateUrl( + ApiStv.students.search(searchsettings, this.studiensemesterKurzbz) + ); + + // init into searchbar + this.$refs.searchbar.searchsettings.searchstr = searchsettings.searchstr; + this.$refs.searchbar.searchsettings.types = searchsettings.types; + this.$nextTick(this.blurSearchbar); + } + else + { + this.clearTabulator(); + } + }, + clearTabulator() { + if(['index', 'studiensemester'].includes(this.$route.name)) + { + if(this.$refs?.stvList?.$refs?.table?.tabulator) + { + this.$refs.stvList.$refs.table.tabulator.setData([]); + } } }, checkUrlStudiengang() { @@ -269,6 +369,42 @@ export default { }); } } + else + { + this.studiengangKz = undefined; + this.studiengangKuerzel = ''; + this.clearTabulator(); + } + }, + onSearch(e) { + const searchsettings = { ...this.$refs.searchbar.searchsettings }; + if (searchsettings.searchstr.length >= 2) { + this.blurSearchbar(); + + if (!searchsettings.types.length || searchsettings.types.length == this.$refs.searchbar.types.length) { + this.$router.push({ + name: 'search', + params: { + studiensemester_kurzbz: this.studiensemesterKurzbz, + searchstr: searchsettings.searchstr + } + }); + } else { + this.$router.push({ + name: 'search_w_types', + params: { + studiensemester_kurzbz: this.studiensemesterKurzbz, + searchstr: searchsettings.searchstr, + types: searchsettings.types.join('+') + } + }); + } + } + }, + blurSearchbar() { + this.$refs.searchbar.$refs.input.blur(); + this.$refs.searchbar.abort(); + this.$refs.searchbar.hideresult(); } }, created() { @@ -381,10 +517,58 @@ export default { +
@@ -394,14 +578,38 @@ export default {
@@ -416,5 +624,6 @@ export default {
+
` }; diff --git a/public/js/components/Stv/Studentenverwaltung/Details.js b/public/js/components/Stv/Studentenverwaltung/Details.js index 7bd028f3c..dca08c07f 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details.js +++ b/public/js/components/Stv/Studentenverwaltung/Details.js @@ -41,25 +41,34 @@ export default { return Object.fromEntries(Object.entries(this.configStudents).filter(([ , value ]) => !value.showOnlyWithUid && !value.showOnlyWithUid)); } }, + watch: { + '$p.user_language.value'(n, o) { + if (n !== o && o !== undefined) + this.loadConfig(); + } + }, methods: { + loadConfig() { + this.$api + .call(ApiStvApp.configStudent()) + .then(result => { + this.configStudent = result.data; + }) + .catch(this.$fhcAlert.handleSystemError); + this.$api + .call(ApiStvApp.configStudents()) + .then(result => { + this.configStudents = result.data; + }) + .catch(this.$fhcAlert.handleSystemError); + }, reload() { if (this.$refs.tabs?.$refs?.current?.reload) this.$refs.tabs.$refs.current.reload(); } }, created() { - this.$api - .call(ApiStvApp.configStudent()) - .then(result => { - this.configStudent = result.data; - }) - .catch(this.$fhcAlert.handleSystemError); - this.$api - .call(ApiStvApp.configStudents()) - .then(result => { - this.configStudents = result.data; - }) - .catch(this.$fhcAlert.handleSystemError); + this.loadConfig(); }, template: `
diff --git a/public/js/components/Stv/Studentenverwaltung/Details/CombinePeople.js b/public/js/components/Stv/Studentenverwaltung/Details/CombinePeople.js new file mode 100644 index 000000000..81c1a6860 --- /dev/null +++ b/public/js/components/Stv/Studentenverwaltung/Details/CombinePeople.js @@ -0,0 +1,83 @@ +export default { + name: "TabCombinePeople", + inject: { + cisRoot: { + from: 'cisRoot' + }, + }, + props: { + modelValue: Object, + }, + data(){ + return { + iframeUrl: null, + viewLoaded: false + } + }, + computed: { + personIds() { + return Array.isArray(this.modelValue) + ? this.modelValue.map(e => e.person_id) + : [this.modelValue.person_id]; + }, + detailStringPerson1(){ + let person1 = this.modelValue[0]; + return person1.vorname + " " + person1.nachname + "(" + person1.person_id + ")"; + }, + detailStringPerson2(){ + let person2 = this.modelValue[1]; + return person2.vorname + " " + person2.nachname + "(" + person2.person_id+ ")"; + }, + + }, + methods: { + combinePeople(){ + this.viewLoaded = true; + let person1_id = this.personIds[0]; + let person2_id = this.personIds[1]; + + if(person1_id == person2_id) { + return this.$fhcAlert.alertError(this.$p.t('stv', 'error_combinePeople_samePerson')); + } + + let linkCombinePeople = this.cisRoot + 'vilesci/stammdaten/personen_wartung.php?person_id_1=' + person1_id + '&person_id_2='+ person2_id; + this.openLink(linkCombinePeople); + }, + openLink(url) { + this.iframeUrl = url; + }, + goBack(){ + this.viewLoaded = false; + this.iframeUrl = null; + } + }, + template: /*html*/ ` +
+ +
+

Personen zusammenlegen

+
+
+

{{$p.t('stv', 'question_combine_people', { person1: detailStringPerson1, person2: detailStringPerson2 })}}

+ +
+
+ ungültige Anzahl: {{this.modelValue.length}} +
+
+
+
+ +
+ + + + +
+ ` + }; \ No newline at end of file diff --git a/public/js/components/Stv/Studentenverwaltung/List.js b/public/js/components/Stv/Studentenverwaltung/List.js index 23fea3f67..910a34d85 100644 --- a/public/js/components/Stv/Studentenverwaltung/List.js +++ b/public/js/components/Stv/Studentenverwaltung/List.js @@ -2,6 +2,8 @@ import {CoreFilterCmpt} from "../../filter/Filter.js"; import ListNew from './List/New.js'; import ListFilter from './List/Filter.js'; +import { capitalize } from '../../../helpers/StringHelpers.js'; + import draggable from '../../../directives/draggable.js'; export default { @@ -133,7 +135,12 @@ export default { { return Promise.resolve({ data: []}); } - return this.$api.call({method: 'post', url, params}); + /** + * NOTE(chris): Because of a bug in Tabulator + * we need to get the params from elsewhere. + * @see https://github.com/olifolkerd/tabulator/issues/4318 + */ + return this.$api.call({...config, url, params: this.tabulatorOptions.ajaxParams}); }, ajaxResponse: (url, params, response) => { return response?.data; @@ -228,7 +235,84 @@ export default { return "StudentList_" + today + ".csv"; } }, + 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(); }, @@ -290,20 +374,22 @@ export default { encodeURIComponent(this.currentSemester) ); - const params = {}; + const params = (endpoint?.params !== undefined) ? endpoint.params : {}; + const method = (endpoint?.method !== undefined) ? endpoint.method : 'get'; if (this.filter.length) params.filter = this.filter; + this.tabulatorOptions.ajaxURL = endpoint.url; + this.tabulatorOptions.ajaxParams = { ...params }; + this.tabulatorOptions.ajaxConfig = method; if (!this.$refs.table.tableBuilt) { - if (!this.$refs.table.tabulator) { - this.tabulatorOptions.ajaxURL = endpoint.url; - this.tabulatorOptions.ajaxParams = params; - } else + if (this.$refs.table.tabulator) { this.$refs.table.tabulator.on("tableBuilt", () => { - this.$refs.table.tabulator.setData(endpoint.url, params); + this.$refs.table.tabulator.setData(endpoint.url, params, method); }); + } } else - this.$refs.table.tabulator.setData(endpoint.url, params); + this.$refs.table.tabulator.setData(endpoint.url, params, method); }, dragCleanup(evt) { if (evt.dataTransfer.dropEffect == 'none') @@ -400,6 +486,7 @@ export default { new-btn-show :new-btn-label="$p.t('stv/action_new')" @click:new="actionNewPrestudent" + @table-built="translateTabulator" >