Compare commits

...

75 Commits

Author SHA1 Message Date
Harald Bamberger c84146fcbd Merge branch 'bug-77989/StudVW_Datepicker_check_modeltype' 2026-07-17 10:36:23 +02:00
ma0068 35decf476c make fields deletable with keyboard: Konto Buchungsdatum, Prüfung Datum, Abschlussprüfung Datum und Sponsion, Mobilität von und bis 2026-07-17 09:12:02 +02:00
Harald Bamberger 6cfc4b0973 Merge branch 'master' into bug-77989/StudVW_Datepicker_check_modeltype 2026-07-16 16:01:25 +02:00
Harald Bamberger 6296b87ea5 Merge branch 'bug-77989/StudVW_Datepicker_check_modeltype' 2026-07-16 15:54:18 +02:00
Harald Bamberger 576a44838f datetime format bewerbung_abgeschickt_am 2026-07-16 15:53:32 +02:00
Harald Bamberger c00e7a182c Merge branch 'master' into bug-77989/StudVW_Datepicker_check_modeltype 2026-07-16 15:00:45 +02:00
ma0068 fd1b5daf9c enable deletion of zgv and zgv master date with null and empty string 2026-07-16 14:51:38 +02:00
Harald Bamberger c0b5f2f748 add permission to DetailHeader function for use in pv21 2026-07-16 12:58:00 +02:00
ma0068 b217681ee5 use model-type for interessenten anlegen, listNew 2026-07-14 15:23:06 +02:00
ma0068 7841a73fc0 prepare field bewerbung_abgeschicktamum for showing in frontend (datestamp in database) and add also model-type to datepicker 2026-07-14 15:05:54 +02:00
ma0068 2064008f9e add model-type to fields Betriebsmittel/ausgegebenam, Betriebsmittel/retouram, Funktionen/datumvon und Funktionen/datumbis 2026-07-14 14:31:13 +02:00
ma0068 19b9906c86 Aufnahmetermine: use model-type isodate for field anmeldedatum 2026-07-14 14:23:37 +02:00
ma0068 2c3913a763 Mobility: use model-type isodate for fields von and bis 2026-07-14 14:19:25 +02:00
ma0068 9111edf442 Dokumente: use model-type isodate for field nachgereicht_am 2026-07-14 14:06:59 +02:00
ma0068 b1c47da3c3 use model-type isodate for fields in StatusModal datum and bestaetigtam 2026-07-14 14:01:12 +02:00
ma0068 67eb238876 use model-type isodate for field Details Gebdatum 2026-07-14 13:37:19 +02:00
Harald Bamberger a139cc8d2e initialize with luxon date iso format 2026-07-14 13:24:04 +02:00
Harald Bamberger 35ecbdefa8 studvw prestudent ZGV date fields and konto date fields use iso date as model-type 2026-07-14 13:17:27 +02:00
Harald Bamberger 5f0b1ace51 Merge branch 'epic/LV-Evaluierung_STG-KF-Datenaggregation' 2026-07-14 07:33:24 +02:00
Harald Bamberger b891d8baa5 Merge branch 'master' into epic/LV-Evaluierung_STG-KF-Datenaggregation 2026-07-13 18:01:35 +02:00
Cristina 5dbcf9ce70 Added sancho_header_lvevaluierung_rollout.jpg and sancho_footer_lvevaluierung_rollout.jpg 2026-07-13 17:31:34 +02:00
adisposkofh c26db6cc40 minor fix to tabulator localized columns parsing 2026-07-09 11:44:04 +02:00
Cristina 8548b93ddc Added method getPreviousSameSemesterFrom in Studiensemester_model.php
Method returns the current study semester and the previous semesters of the same type (SS/WS).
2026-07-08 18:57:31 +02:00
Cristina bdc4782e9d Merge branch 'master' into epic/LV-Evaluierung_STG-KF-Datenaggregation 2026-07-08 18:54:02 +02:00
Cristina 0583990870 Merge branch 'master' of https://github.com/FH-Complete/FHC-Core 2026-07-08 18:53:37 +02:00
Andreas Österreicher 5972764731 Bugfix für Fallback wenn Config nicht gesetzt ist 2026-07-08 15:42:36 +02:00
Harald Bamberger 2933a525c1 Merge branch '77804-exclude-moodle-from-other-lv-plan' 2026-07-08 14:48:28 +02:00
Harald Bamberger 5694ba7325 Merge branch 'master' into 77804-exclude-moodle-from-other-lv-plan 2026-07-08 14:45:15 +02:00
Harald Bamberger bbec7de8de Merge branch 'master' of github.com:FH-Complete/FHC-Core 2026-07-08 14:38:42 +02:00
Harald Bamberger 1d14e58d8f use current gender form 2026-07-08 14:33:02 +02:00
Harald Bamberger db2beeb51f Merge branch 'feature/76981-missing-mailing-lists' 2026-07-08 14:24:13 +02:00
Harald Bamberger e4f43cc820 Merge branch 'master' into feature/76981-missing-mailing-lists 2026-07-08 14:15:40 +02:00
adisposkofh 8adc1c8bcc not showing moodle events in other users' lv plans 2026-07-08 11:41:58 +02:00
Harald Bamberger fe195f4080 Cis App: define isMobile local since it is used in handleClick event handler 2026-07-08 10:53:40 +02:00
Harald Bamberger c69bb9ccaa Merge branch 'cis40_2026-02_rc_abgabetool_finetuning_76688' 2026-07-08 09:39:44 +02:00
Harald Bamberger 4889299dd9 remove unused file components/Cis/LvPlan/StgOrg_DEPR.js 2026-07-08 09:20:51 +02:00
Harald Bamberger 8f427344d0 remove currently unused package-lock.json 2026-07-08 09:15:25 +02:00
Harald Bamberger 0d6867799c projektbetreuer tabulator select max 1 row 2026-07-08 09:06:14 +02:00
Harald Bamberger 5602c7091a Merge branch 'feature/76688-general-tabulator-issues_76776-localization' into cis40_2026-02_rc_abgabetool_finetuning_76688 2026-07-08 07:17:24 +02:00
Andreas Österreicher 6a16fb7515 Betreuerart wird Übergeben damit Zweitbetreuer keine Termine erstellen können 2026-07-07 14:33:29 +02:00
Andreas Österreicher 33b05e9df5 Abfrage für PersonID entfernt da sonst Zweitbetreuer/Erstbetreuer nicht geladen werden 2026-07-07 14:10:55 +02:00
Harald Bamberger ac2a1147cc do not show download links in OtherLvPlan and StgOrg LvPlan mode 2026-07-07 11:44:04 +02:00
Harald Bamberger 8d7fdb0254 Merge branch 'master' into cis40_2026-02_rc_abgabetool_finetuning 2026-07-07 11:15:53 +02:00
Harald Bamberger 0fd938e85c Merge branch 'master' into feature/76688-general-tabulator-issues_76776-localization 2026-07-07 11:08:19 +02:00
Johann Hoffmann b57fe4839b update tooltip phrase + keyversion 2026-07-06 15:15:02 +02:00
Cristina 335753be8c Adapted Lvevaluierung Mail texte "Evaluierungszeitfenster" instead of "Evaluationszeitraum" 2026-07-06 12:19:28 +02:00
Harald Bamberger 044e29f557 Merge branch 'feature-77662/Absolventen_Zugriff_CIS4' 2026-07-01 14:47:52 +02:00
Harald Bamberger f5cdb0a2fb add configs to define grace period and roles, add logic to benutzerberechtigungs class 2026-07-01 12:10:06 +02:00
Andreas Österreicher 8cc0462712 Merge branch 'bug-76560/Download_Notenlisten_einschraenken' 2026-06-29 13:33:04 +02:00
Paolo 4162305ac6 Improved the permissions check in the cis/private/lehre/notenliste.xls.php 2026-06-29 13:28:57 +02:00
Paolo 6508301526 Merge branch 'master' into bug-76560/Download_Notenlisten_einschraenken 2026-06-29 09:32:53 +02:00
Paolo df8c58f5df Merge branch 'master' into bug-76560/Download_Notenlisten_einschraenken 2026-06-11 11:24:11 +02:00
Cristina 8862f050e8 Merge branch 'master' of https://github.com/FH-Complete/FHC-Core 2026-06-09 10:37:53 +02:00
Paolo 0410c95447 cis/private/lehre/notenliste.xls.php now checks if the lector belongs to
the teaching unit
2026-06-01 12:13:44 +02:00
adisposkofh 23b1e98a7a minor fix 2026-05-26 16:09:21 +02:00
adisposkofh 1d5b915717 minor fix 2026-05-26 15:47:11 +02:00
adisposkofh eb281f3bc3 hiding redundant tabulator warnings 2026-05-26 14:27:22 +02:00
adisposkofh aeebc2a81a reworked localization of collapsed column headings by modifying internal tabulator logic with custom module 2026-05-26 14:25:22 +02:00
adisposkofh 3483146c1a resolved tabulator warnings 2026-05-23 15:53:38 +02:00
adisposkofh ca712781dc localizing tabulator dropdown/context menus 2026-05-23 14:28:14 +02:00
adisposkofh a704ce215b minor mod 2026-05-20 17:08:31 +02:00
adisposkofh 0cc628a8e3 fixed issue where searchbar was producing an error searching with an empty input 2026-05-20 17:06:05 +02:00
adisposkofh cf506a7b1e reused debounce helper in searchbar search 2026-05-20 17:04:05 +02:00
adisposkofh b9670f3f09 localizing collapsed tabulator column headings; tabulator custom modules; debounce helper 2026-05-20 16:30:16 +02:00
adisposkofh e59bd51333 modified getPhrases endpoint: replaced get w/ post, replaced query params w/ payload 2026-05-19 16:35:22 +02:00
adisposkofh 239eaa90e9 separate endpoint for generic tabulator phrases with cacheable response 2026-05-19 16:27:54 +02:00
adisposkofh 4e5bf94a15 identifying missing column heading phrases in localized tabulator 2026-05-19 14:33:09 +02:00
adisposkofh ec4a04d63b updated missing tabulator phrases in german 2026-05-19 14:18:00 +02:00
adisposkofh 95f09e9114 fixed timing of initial localization of tabulator collapsed column headings 2026-05-18 14:07:06 +02:00
adisposkofh a3dd5a188b removed redundant comment 2026-05-18 11:39:03 +02:00
adisposkofh 0a4906c5ac added course-specific mailing lists 2026-05-13 16:07:43 +02:00
adisposkofh 89814d1db2 localizing column names accordion above table 2026-05-12 19:21:52 +02:00
Cristina 29f9b226ab Merge branch 'master' of https://github.com/FH-Complete/FHC-Core 2026-05-12 17:08:53 +02:00
adisposkofh 40a0f6a7e9 implemented tabulator localization 2026-05-06 10:56:24 +02:00
adisposkofh b6e1bb06f3 implemented tabulator localization 2026-05-06 10:54:18 +02:00
55 changed files with 1201 additions and 429 deletions
@@ -796,6 +796,7 @@ class Abgabe extends FHCAPI_Controller
Events::trigger('projektbeurteilung_formular_link', $own_betreuerart_kurzbz, APP_ROOT, $pa->projektarbeit_id, $pa->student_uid, $returnFunc);
$pa->beurteilungLinkNew = $newLink;
$pa->beurteilungLinkOld = $oldLink;
$pa->betreuerart_kurzbz = $own_betreuerart_kurzbz;
// has previously been retrieved via getStudentProjektabgaben but is fetched in advance to avoid having to reload abgaben
$projektarbeitIsCurrent = false;
@@ -418,6 +418,10 @@ class LvPlan extends FHCAPI_Controller
*/
private function fetchMoodleEvents($start_date, $end_date, $uid = null)
{
if ($uid && $uid !== getAuthUID()) {
return [];
}
$this->load->config('calendar');
$tz = new DateTimeZone($this->config->item('timezone'));
@@ -32,6 +32,8 @@ class Phrasen extends FHCAPI_Controller
'setLanguage' => self::PERM_ANONYMOUS,
'getLanguage' => self::PERM_ANONYMOUS,
'getAllLanguages' => self::PERM_ANONYMOUS,
'getPhrases' => self::PERM_ANONYMOUS,
'getTabulatorPhrases' => self::PERM_ANONYMOUS,
]);
$this->load->helper('hlp_language');
@@ -92,4 +94,59 @@ class Phrasen extends FHCAPI_Controller
$this->terminateWithSuccess($langs);
}
public function getPhrases()
{
$postParams = $this->getPostJSON();
$languages = $postParams->languages;
if (!$languages || !count($languages)) {
$this->load->model('system/Sprache_model', 'sprachenModel');
$activeLanguages = $this->sprachenModel->loadWhere(array('content' => true));
$activeLanguagesData = $this->getDataOrTerminateWithError($activeLanguages);
$languages = array_map(
function ($languageData) {
return $languageData->sprache;
},
$activeLanguagesData
);
}
$this->load->model('system/Phrase_model', 'phraseModel');
$phrasesGroupedByCategory = $postParams->phrasesGroupedByCategory;
$result = [];
foreach ($languages as $language) {
$phrases = $this->phraseModel->getPhrasesByCategoryAndPhrasesAndLanguage($phrasesGroupedByCategory, $language);
$result[$language] = $this->getDataOrTerminateWithError($phrases);
}
$this->terminateWithSuccess($result);
}
public function getTabulatorPhrases()
{
$languages = json_decode($this->input->get('languages'));
if (!$languages || !count($languages)) {
$this->load->model('system/Sprache_model', 'sprachenModel');
$activeLanguages = $this->sprachenModel->loadWhere(array('content' => true));
$activeLanguagesData = $this->getDataOrTerminateWithError($activeLanguages);
$languages = array_map(
function ($languageData) {
return $languageData->sprache;
},
$activeLanguagesData
);
}
$this->load->model('system/Phrase_model', 'phraseModel');
$result = [];
foreach ($languages as $language) {
$tabulatorPhrases = $this->phraseModel->getPhrasesByCategoryAndLanguage(['tabulator'], $language);
$result[$language] = $this->getDataOrTerminateWithError($tabulatorPhrases);
}
header('Pragma: private');
header('Cache-Control: private, max-age=' . (60 * 60 * 24 * 30));
$this->terminateWithSuccess($result);
}
}
@@ -259,7 +259,7 @@ class BetriebsmittelP extends FHCAPI_Controller
$kaution = $this->input->post('kaution');
if($kaution) $kaution = str_replace(',', '.', $kaution);
$ausgegebenam = $this->input->post('ausgegebenam');
$retouram = $this->input->post('retouram');
$retouram = $this->input->post('retouram') == "" ? null : $this->input->post('retouram');
// NOTE(chris): transform_kartennummer
if ($betriebsmitteltyp == 'Zutrittskarte' && $nummer)
@@ -9,9 +9,9 @@ class Detailheader extends FHCAPI_Controller
public function __construct()
{
parent::__construct([
'getHeader' => ['vertrag/mitarbeiter:r'],
'getPersonAbteilung' => ['vertrag/mitarbeiter:r'],
'getLeitungOrg' => ['vertrag/mitarbeiter:r'],
'getHeader' => ['vertrag/mitarbeiter:r', 'basis/mitarbeiter:r'],
'getPersonAbteilung' => ['vertrag/mitarbeiter:r', 'basis/mitarbeiter:r'],
'getLeitungOrg' => ['vertrag/mitarbeiter:r', 'basis/mitarbeiter:r'],
]);
}
@@ -294,6 +294,10 @@ class Funktionen extends FHCAPI_Controller
{
$this->terminateWithValidationErrors($this->form_validation->error_array());
}
if($datum_von == '')
$datum_von = null;
if($datum_bis == '')
$datum_bis = null;
$result = $this->BenutzerfunktionModel->update(
[
@@ -371,6 +371,9 @@ class Abschlusspruefung extends FHCAPI_Controller
$this->terminateWithValidationErrors($this->form_validation->error_array());
}
$datum = $formData['datum'] == '' ? null : $formData['datum'];
$sponsion = $formData['sponsion'] == '' ? null : $formData['sponsion'];
$result = $this->AbschlusspruefungModel->update(
[
'abschlusspruefung_id' => $abschlusspruefung_id
@@ -382,8 +385,8 @@ class Abschlusspruefung extends FHCAPI_Controller
'vorsitz' => $vorsitz,
'pruefungsantritt_kurzbz' => $formData['pruefungsantritt_kurzbz'],
'abschlussbeurteilung_kurzbz' => $formData['abschlussbeurteilung_kurzbz'],
'datum' => $formData['datum'],
'sponsion' => $formData['sponsion'],
'datum' => $datum,
'sponsion' => $sponsion,
'pruefer1' => $pruefer1,
'pruefer2' => $pruefer2,
'pruefer3' => $pruefer3,
@@ -170,6 +170,8 @@ class Dokumente extends FHCAPI_Controller
$this->terminateWithValidationErrors($this->form_validation->error_array());
}
$nachgereicht_am = $this->input->post('nachgereicht_am') == '' ? null : $this->input->post('nachgereicht_am');
$uid = getAuthUID();
$result = $this->AkteModel->update(
@@ -180,7 +182,7 @@ class Dokumente extends FHCAPI_Controller
'dokument_kurzbz' => $this->input->post('dokument_kurzbz'),
'anmerkung_intern' => $this->input->post('anmerkung_intern'),
'titel_intern' => $this->input->post('titel_intern'),
'nachgereicht_am' => $this->input->post('nachgereicht_am'),
'nachgereicht_am' => $nachgereicht_am,
'updateamum' => date('c'),
'updatevon' => $uid,
]
@@ -429,9 +429,12 @@ class Konto extends FHCAPI_Controller
'updateamum' => date('c'),
'updatevon' => getAuthUID()
];
foreach ($allowed as $field)
if ($this->input->post($field) !== null)
$data[$field] = $this->input->post($field);
if($data['buchungsdatum'] == '')
$data['buchungsdatum'] = null;
$result = $this->KontoModel->update($id, $data);
@@ -237,8 +237,8 @@ class Mobility extends FHCAPI_Controller
}
$formData = $this->input->post('formData');
$von = $formData['von'] ?? null;
$bis = $formData['bis'] ?? null;
$von = ($formData['von'] ?? '') ?: null;
$bis = ($formData['bis'] ?? '') ?: null;
$nation_code = $formData['nation_code'] ?? null;
$mobilitaetsprogramm_code = $formData['mobilitaetsprogramm_code'] ?? null;
$herkunftsland_code = $formData['herkunftsland_code'] ?? null;
@@ -193,7 +193,7 @@ class Prestudent extends FHCAPI_Controller
}
elseif (
$val === ''
&& in_array($prop, ['zgvnation', 'zgvmanation', 'zgvdoktornation', 'berufstaetigkeit_code', 'ausbildungcode'])
&& in_array($prop, ['zgvnation', 'zgvmanation', 'zgvdoktornation', 'berufstaetigkeit_code', 'ausbildungcode', 'zgvdatum', 'zgvmadatum'])
)
{
$val = null;
@@ -448,8 +448,10 @@ class Pruefung extends FHCAPI_Controller
}
}
$datum = $this->input->post('datum') == '' ? null : $this->input->post('datum');
if(intval($pruefung_id) > 0)
{
$result = $this->PruefungModel->update(
[
'pruefung_id' => $pruefung_id
@@ -459,7 +461,7 @@ class Pruefung extends FHCAPI_Controller
'mitarbeiter_uid' => $this->input->post('mitarbeiter_uid'),
'note' => $this->input->post('note'),
'pruefungstyp_kurzbz' => $this->input->post('pruefungstyp_kurzbz'),
'datum' => $this->input->post('datum'),
'datum' => $datum,
'anmerkung' => $this->input->post('anmerkung'),
'updatevon' => $authUID,
'updateamum' => date('c'),
@@ -472,7 +474,7 @@ class Pruefung extends FHCAPI_Controller
'lehreinheit_id' => $this->input->post('lehreinheit_id'),
'student_uid' => $this->input->post('student_uid'),
'mitarbeiter_uid' => $this->input->post('mitarbeiter_uid'),
'datum' => $this->input->post('datum'),
'datum' => $datum,
'pruefungstyp_kurzbz' => $this->input->post('pruefungstyp_kurzbz'),
'note' => $this->input->post('note'),
'anmerkung' => $this->input->post('anmerkung'),
@@ -520,7 +522,7 @@ class Pruefung extends FHCAPI_Controller
'studiensemester_kurzbz' => $studiensemester_kurzbz,
'note' => $this->input->post('note'),
'uebernahmedatum' => date('c'),
'benotungsdatum' => $this->input->post('datum'),
'benotungsdatum' => $datum,
'insertamum' => date('c'),
'insertvon' => $authUID,
'punkte' => $this->input->post('punkte') ? str_replace(',', '.', $this->input->post('punkte')) : null
@@ -555,7 +557,7 @@ class Pruefung extends FHCAPI_Controller
], [
'note' => $this->input->post('note'),
'uebernahmedatum' => date('c'),
'benotungsdatum' => $this->input->post('datum'),
'benotungsdatum' => $datum,
'updateamum' => date('c'),
'updatevon' => $authUID,
'punkte' => $this->input->post('punkte') ? str_replace(',', '.', $this->input->post('punkte')) : null
@@ -296,6 +296,10 @@ class Student extends FHCAPI_Controller
$fotoval = ($val == '') ? null : str_replace('data:image/jpeg;base64,', '', $val);
$update_person[$prop] = $fotoval;
}
else if ($prop == 'gebdatum')
{
$update_person[$prop] = ($val == '') ? null : $val;
}
else
{
$update_person[$prop] = $val;
+53
View File
@@ -263,6 +263,59 @@ class ProfilLib{
$element->mailto = "mailto:" . $element->gruppe_kurzbz . "@" . DOMAIN;
return $element;
}, $mailverteiler_res);
$this->ci->load->model("crm/Student_model", "StudentModel");
$this->ci->StudentModel->addJoin("tbl_benutzer", "tbl_benutzer.uid = tbl_student.student_uid");
$this->ci->StudentModel->addJoin("tbl_person", "tbl_benutzer.person_id = tbl_person.person_id");
$this->ci->StudentModel->addJoin("tbl_studiengang", "tbl_student.studiengang_kz = tbl_studiengang.studiengang_kz");
$this->ci->StudentModel->addSelect("matr_nr, semester, verband, gruppe, kurzbzlang");
$studentResult = $this->ci->StudentModel->loadWhere(["student_uid" => $uid]);
if (isError($studentResult)) {
return error(getData($studentResult));
}
$studentResultData = getData($studentResult);
$studentData = null;
if (is_array($studentResultData) && count($studentResultData)) {
$studentData = $studentResultData[0];
}
if ($studentData && $studentData->matr_nr) {
$this->ci->load->library("phrasesLib", ["profil"], "phrases");
$standardCourseVerteiler = trim($studentData->kurzbzlang) . "_STD";
$mailverteiler_res[] = [
"beschreibung" => $this->ci->phrases->t('profil', 'alleStudentenVon') . " " . $standardCourseVerteiler,
"gruppe_kurzbz" => $standardCourseVerteiler,
"mailto" => "mailto:" . strtolower($standardCourseVerteiler) . "@" . DOMAIN,
];
$semesterVerteiler = trim($studentData->kurzbzlang) . trim($studentData->semester);
$mailverteiler_res[] = [
"beschreibung" => $this->ci->phrases->t('profil', 'alleStudentenVon') . " " . $semesterVerteiler,
"gruppe_kurzbz" => $semesterVerteiler,
"mailto" => "mailto:" . strtolower($semesterVerteiler) . "@" . DOMAIN,
];
if ($studentData->verband && strlen(trim($studentData->verband))) {
$verbandVerteiler = $semesterVerteiler . trim($studentData->verband);
$mailverteiler_res[] = [
"beschreibung" => $this->ci->phrases->t('profil', 'alleStudentenVon') . " " . $verbandVerteiler,
"gruppe_kurzbz" => $verbandVerteiler,
"mailto" => "mailto:" . strtolower($verbandVerteiler) . "@" . DOMAIN,
];
if ($studentData->gruppe && strlen(trim($studentData->gruppe))) {
$gruppeVerteiler = $verbandVerteiler . trim($studentData->gruppe);
$mailverteiler_res[] = [
"beschreibung" => $this->ci->phrases->t('profil', 'alleStudentenVon') . " " . $gruppeVerteiler,
"gruppe_kurzbz" => $gruppeVerteiler,
"mailto" => "mailto:" . strtolower($gruppeVerteiler) . "@" . DOMAIN,
];
}
}
}
return $mailverteiler_res;
}
@@ -332,7 +332,6 @@ class Projektarbeit_model extends DB_Model
LEFT JOIN public.tbl_benutzer b ON b.person_id = p.person_id
LEFT JOIN lehre.tbl_betreuerart ba ON ba.betreuerart_kurzbz = pb.betreuerart_kurzbz
WHERE pb.betreuerart_kurzbz = ANY('{Zweitbetreuer,Zweitbegutachter,Senatsmitglied}')
AND pb.person_id = ?
ORDER BY pb.projektarbeit_id
),
primary_betreuer AS (
@@ -356,7 +355,6 @@ class Projektarbeit_model extends DB_Model
LEFT JOIN public.tbl_benutzer b ON b.person_id = p.person_id
LEFT JOIN lehre.tbl_betreuerart ba ON ba.betreuerart_kurzbz = pb.betreuerart_kurzbz
WHERE pb.betreuerart_kurzbz = ANY('{Begutachter, Betreuer, Erstbetreuer,Erstbegutachter,Senatsvorsitz}')
AND pb.person_id = ?
ORDER BY pb.projektarbeit_id
)
@@ -446,7 +444,7 @@ class Projektarbeit_model extends DB_Model
ORDER BY tbl_projektarbeit.projektarbeit_id, betreuerart_kurzbz desc) as xy
ORDER BY nachname;";
return $this->execReadOnlyQuery($qry, array($person_id, $person_id, $person_id, $person_id));
return $this->execReadOnlyQuery($qry, array($person_id, $person_id));
}
/**
@@ -122,6 +122,47 @@ class Studiensemester_model extends DB_Model
return $this->execQuery($query, array($studiensemester_kurzbz, $limit));
}
/**
* Returns the current study semester and the previous semesters of the same type (SS/WS).
*
* Example:
* - SS2026, limit 3: SS2026, SS2025, SS2024
* - WS2025, limit 2: WS2025, WS2024
*
* @param $studiensemester_kurzbz
* @param $limit
*/
public function getPreviousSameSemesterFrom($studiensemester_kurzbz, $limit = 1)
{
$qry = '
SELECT
studiensemester_kurzbz,
start,
ende
FROM
public.tbl_studiensemester
WHERE
SUBSTRING(studiensemester_kurzbz FROM 1 FOR 2) = SUBSTRING(? FROM 1 FOR 2)
AND start <= (
SELECT start
FROM public.tbl_studiensemester
WHERE studiensemester_kurzbz = ?
)
ORDER BY
start DESC
LIMIT ?
';
return $this->execQuery($qry,
[
$studiensemester_kurzbz,
$studiensemester_kurzbz,
$limit
]
);
}
/**
* getNearest
*/
@@ -260,7 +260,6 @@ class Benutzerfunktion_model extends DB_Model
return $this->execQuery($query, $parameters_array);
}
/**
* Get active Kompetenzfeldleitung bei UID.
*
@@ -296,7 +295,6 @@ class Benutzerfunktion_model extends DB_Model
return $this->execQuery($query, $parameters_array);
}
public function insertBenutzerfunktion($Json)
{
unset($Json['benutzerfunktion_id']);
+17 -3
View File
@@ -36,6 +36,7 @@ require_once('../../../include/notenschluessel.class.php');
require_once('../../../include/Excel/excel.php');
require_once('../../../include/phrasen.class.php');
require_once('../../../include/pruefung.class.php');
require_once('../../../include/benutzerberechtigung.class.php');
$uid = get_uid();
@@ -44,7 +45,7 @@ $uid = get_uid();
$sprache = getSprache();
$p = new phrasen($sprache);
if(!check_lektor($uid))
if (!check_lektor($uid))
die('Sie haben keine Berechtigung fuer diese Seite');
if (!$db = new basis_db())
@@ -90,6 +91,21 @@ if(isset($_GET['lehreinheit_id']))
else
$lehreinheit_id = '';
// Permissions
$berechtigung = new benutzerberechtigung();
$berechtigung->getBerechtigungen($uid);
// LV load
$lvobj = new lehrveranstaltung($lvid);
// Check permissions
if (!$berechtigung->isBerechtigt('admin')
&& !$berechtigung->isBerechtigt('assistenz')
&& !$berechtigung->isBerechtigt('lehre', $lvobj->oe_kurzbz, 's')
&& !check_lektor_lehrveranstaltung($uid, $lvid, $stsem)
)
die('Sie haben keine Berechtigung fuer diese Seite');
/*
* Create Excel File
*/
@@ -143,8 +159,6 @@ else
// let's merge
$format_title->setAlign('merge');
$lvobj = new lehrveranstaltung($lvid);
$worksheet->write(0,0,$p->t('anwesenheitsliste/notenliste')." ".($sprache=='English'?$lvobj->bezeichnung_english:$lvobj->bezeichnung),$format_bold);
$stg_obj = new studiengang($stg);
+4
View File
@@ -373,4 +373,8 @@ define('TESTTOOL_EXTERNE_UEBERWACHUNG_ALLOWED', false);
//enable tags in StudVW
define('STV_TAGS_ENABLED', false);
//student accounts grace period
define('STUDENTS_KEEP_PERMISSIONS_AFTER_USER_INACTIVE_PERIOD', '0 days');
define('STUDENTS_KEEP_PERMISSIONS_AFTER_USER_INACTIVE_ROLES', serialize(array('NO_DEFINED_ROLE')));
?>
+36 -1
View File
@@ -340,6 +340,40 @@ class benutzerberechtigung extends basis_db
return true;
}
protected function hasPreStudentStatusInGracePeriod($uid)
{
$period = defined('STUDENTS_KEEP_PERMISSIONS_AFTER_USER_INACTIVE_PERIOD')
? STUDENTS_KEEP_PERMISSIONS_AFTER_USER_INACTIVE_PERIOD
: '0 days';
$mapfunc = function($val) {
return $this->db_add_param($val);
};
$roles = defined('STUDENTS_KEEP_PERMISSIONS_AFTER_USER_INACTIVE_ROLES')
? implode(', ', array_map($mapfunc, unserialize(STUDENTS_KEEP_PERMISSIONS_AFTER_USER_INACTIVE_ROLES)))
: "'NO_DEFINED_ROLE'";
$sql = <<<EOSQL
SELECT
1
FROM
campus.vw_student vs
WHERE
vs.uid = {$this->db_add_param($uid)}
AND
vs.aktiv = false
AND
public.get_rolle_prestudent(vs.prestudent_id, null) IN ({$roles})
AND
(vs.updateaktivam + INTERVAL {$this->db_add_param($period)})::date >= CURRENT_DATE
EOSQL;
$result = $this->db_query($sql);
if($result && $this->db_num_rows($result) > 0)
{
return true;
}
return false;
}
/**
* Laedt die Berechtigungen eines Users
* @param $uid
@@ -355,7 +389,8 @@ class benutzerberechtigung extends basis_db
if($row = $this->db_fetch_object($result))
{
// Wenn die Person nicht aktiv ist dann hat diese auch keine Rechte
if($this->db_parse_bool($row->aktiv) == false)
if($this->db_parse_bool($row->aktiv) == false
&& $this->hasPreStudentStatusInGracePeriod($uid) === false)
return false;
}
else
-6
View File
@@ -1,6 +0,0 @@
{
"name": "FHC-Core",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}
+20 -1
View File
@@ -41,5 +41,24 @@ export default {
method: 'get',
url: '/api/frontend/v1/phrasen/getAllLanguages'
};
}
},
getPhrases(phrasesGroupedByCategory, languages = []) {
return {
method: 'post',
url: '/api/frontend/v1/phrasen/getPhrases',
params: {
phrasesGroupedByCategory,
languages,
},
};
},
getTabulatorPhrases(languages = []) {
return {
method: 'get',
url: '/api/frontend/v1/phrasen/getTabulatorPhrases',
params: {
languages: JSON.stringify(languages),
},
};
},
};
+6 -1
View File
@@ -257,9 +257,14 @@ const app = Vue.createApp({
provide() {
return { // provide injectable & watchable language property
language: Vue.computed(() => this.$p.user_language),
isMobile: Vue.computed(() => this.windowWidth < 767),
isMobile: Vue.computed(() => this.isMobile),
}
},
computed: {
isMobile: function() {
return (this.windowWidth < 767);
}
},
methods: {
isInternalRoute(href) {
const internalBase = window.location.origin
@@ -433,6 +433,7 @@ export default {
:label="$p.t('wawi/ausgegebenam')"
name="ausgegebenam"
v-model="formData.ausgegebenam"
model-type="yyyy-MM-dd"
auto-apply
:enable-time-picker="false"
text-input
@@ -449,6 +450,7 @@ export default {
:label="$p.t('wawi/retouram')"
name="retouram"
v-model="formData.retouram"
model-type="yyyy-MM-dd"
auto-apply
:enable-time-picker="false"
text-input
@@ -392,7 +392,7 @@ export const AbgabeStudentDetail = {
if(this.isViewMode) {
return { value: this.$capitalize(this.$p.t('abgabetool/c4studentAbgabeNotAllowedInViewMode')), class: "custom-tooltip" }
} else {
return { value: this.$capitalize(this.$p.t('abgabetool/c4studentAbgabeNotAllowedRegular')), class: "custom-tooltip" }
return { value: this.$capitalize(this.$p.t('abgabetool/c4studentAbgabeNotAllowedRegularv2')), class: "custom-tooltip" }
}
},
getTooltipBeurteilungerforderlich() {
@@ -161,6 +161,7 @@ export const AbgabetoolMitarbeiter = {
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4zweitbetreuerVorname'))), field: 'zweitbetreuer_vorname', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4zweitbetreuerNachname'))), field: 'zweitbetreuer_nachname', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4zweitbetreuerTitelPost'))), field: 'zweitbetreuer_titelpost', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4betreuerartv2'))), field: 'betreuerart_kurzbz', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4prevAbgabetermin'))),
headerFilter: dateFilter,
@@ -64,6 +64,9 @@ export default {
return this.propsViewData?.mode;
},
downloadLinks() {
// do not show download links in otherLvPlan Mode
return false;
if (
!this.studiensemester_start ||
!this.studiensemester_ende ||
@@ -72,6 +72,9 @@ export default {
return this.propsViewData?.mode;
},
downloadLinks() {
// do not show download links in StgOrg Mode
return false;
if (
!this.studiensemester_start ||
!this.studiensemester_ende ||
@@ -1,187 +0,0 @@
import FhcCalendar from "../../Calendar/LvPlan.js";
import ApiLvPlan from '../.././../api/factory/lvPlan.js';
import ApiAuthinfo from '../../../api/factory/authinfo.js';
export const DEFAULT_MODE_LVPLAN = 'Week';
export default {
name: 'LvPlanStgOrg',
inject: {
cisRoot: {
from: 'cisRoot'
},
},
components: {
FhcCalendar,
},
props: {
viewData: Object,
propsViewData: Object
},
data() {
return {
studiensemester_kurzbz: null,
studiensemester_start: null,
studiensemester_ende: null,
uid: null,
isMitarbeiter: false,
isStudent: false,
listStg: [],
currentStgBezeichnung: null
};
},
computed:{
currentDay() {
if (!this.propsViewData?.focus_date || isNaN(new Date(this.propsViewData?.focus_date)))
return luxon.DateTime.now().setZone(this.viewData.timezone).toISODate();
return this.propsViewData?.focus_date;
},
currentMode() {
if (!this.propsViewData?.mode || !['day', 'week', 'month'].includes(this.propsViewData?.mode.toLowerCase()))
return DEFAULT_MODE_LVPLAN;
return this.propsViewData?.mode;
},
downloadLinks() {
if (!this.studiensemester_start || !this.studiensemester_ende || !this.uid)
return false;
let type = false;
type = this.isStudent ? 'student' : type;
type = this.isMitarbeiter ? 'lektor' : type;
if (false === type)
{
return;
}
const opts = { zone: this.viewData.timezone };
const start = luxon.DateTime
.fromISO(this.studiensemester_start, opts)
.toUnixInteger();
const ende = luxon.DateTime
.fromISO(this.studiensemester_ende, opts)
.toUnixInteger();
const download_link = FHC_JS_DATA_STORAGE_OBJECT.app_root
+ 'cis/private/lvplan/stpl_kalender.php'
+ '?type=' + type
+ '&pers_uid=' + this.uid
+ '&begin=' + start
+ '&ende=' + ende;
return [
{ title: "excel", icon: 'fa-solid fa-file-excel', link: download_link + '&format=excel' },
{ title: "csv", icon: 'fa-solid fa-file-csv', link: download_link + '&format=csv' },
{ title: "ical1", icon: 'fa-regular fa-calendar', link: download_link + '&format=ical&version=1&target=ical' },
{ title: "ical2", icon: 'fa-regular fa-calendar', link: download_link + '&format=ical&version=2&target=ical' }
];
}
},
methods: {
handleChangeDate(day, newMode) {
return this.handleChangeMode(newMode, day);
},
handleChangeMode(newMode, day) {
const mode = newMode[0].toUpperCase() + newMode.slice(1)
const focus_date = day.toISODate();
this.$router.push({
name: "StgOrgLvPlan",
params: {
mode,
focus_date,
stgkz: this.propsViewData.stgkz,
sem: this.propsViewData.sem,
verband: this.propsViewData.verband,
gruppe: this.propsViewData.gruppe,
}
});
},
updateRange(rangeInterval) {
this.$api
.call(ApiLvPlan.studiensemesterDateInterval(
rangeInterval.end.startOf('week').toISODate()
))
.then(res => {
this.studiensemester_kurzbz = res.data.studiensemester_kurzbz;
this.studiensemester_start = res.data.start;
this.studiensemester_ende = res.data.ende;
});
},
getPromiseFunc(start, end) {
return [
this.$api.call(ApiLvPlan.eventsStgOrg(start.toISODate(), end.toISODate(), this.propsViewData.stgkz, this.propsViewData.sem, this.propsViewData.verband, this.propsViewData.gruppe)),
//local for test
/* this.$api.call(ApiLvPlan.eventsStgOrg(start.toISODate(), end.toISODate(), this.stgkz, this.sem, this.verband, this.gruppe))*/
];
},
backToDropdown(){
this.$router.push({
name: "OverviewLvPlan",
});
}
},
created(){
this.$api
.call(ApiAuthinfo.getAuthInfo())
.then(res => {
this.uid = res.data.uid;
this.isMitarbeiter = res.data.isMitarbeiter;
this.isStudent = res.data.isStudent;
});
if(this.propsViewData.stgkz) {
this.$api
.call(ApiLvPlan.getStudiengaenge())
.then(result => {
const currentStg = result.data.find(
item => item.studiengang_kz == this.propsViewData.stgkz
);
this.currentStgBezeichnung = currentStg.kurzbzlang + " - " + currentStg.bezeichnung;
})
.catch(this.$fhcAlert.handleSystemError);
}
},
template: `
<div class="cis-lvplan-stg-org d-flex flex-column h-100">
<h2>{{ $p.t('LvPlan/headerLvPlanLvVerband') }}</h2>
<p v-if="propsViewData.stgkz"><span><strong>{{currentStgBezeichnung}}</strong></span>
<span v-if="propsViewData.sem" style="margin-left:10px;"> Semester: {{propsViewData.sem}} </span>
<span v-if="propsViewData.verband" style="margin-left:10px;"> Verband: {{propsViewData.verband}} </span>
<span v-if="propsViewData.gruppe" style="margin-left:10px;"> Gruppe: {{propsViewData.gruppe}} </span>
<button class="py-1 btn btn-outline-secondary" :title="this.$p.t('LvPlan', 'backToDropdown')" style="margin-left:10px;"> <i class="fa fa-arrow-left fa-xs" @click="backToDropdown"></i></button>
</p>
<p v-else>{{ $p.t('LvPlan/noStgProvided') }}</p>
<fhc-calendar
ref="calendar"
:timezone="viewData.timezone"
:get-promise-func="getPromiseFunc"
:date="currentDay"
:mode="currentMode"
@update:date="handleChangeDate"
@update:mode="handleChangeMode"
@update:range="updateRange"
class="responsive-calendar"
>
<div
v-if="downloadLinks"
class="d-flex gap-1 justify-items-start"
>
<div v-for="{ title, icon, link } in downloadLinks">
<a
:href="link"
:aria-label="title"
class="py-1 btn btn-outline-secondary"
>
<div class="d-flex flex-column">
<i aria-hidden="true" :class="icon"></i>
<span style="font-size:.5rem">{{ title }}</span>
</div>
</a>
</div>
</div>
</fhc-calendar>
</div>
`,
};
@@ -36,7 +36,6 @@ export default {
return {
showModal: false,
editDataFilter: null,
arePhrasesPreloaded: false,
// tabulator options
funktionen_table_options: {
persistenceID: "filterTableMaProfilFunktionen",
@@ -49,6 +48,7 @@ export default {
responsiveLayoutCollapseUseFormatters: false,
responsiveLayoutCollapseFormatter: Vue.$collapseFormatter,
responsiveLayoutCollapseStartOpen: false,
locale: true,
columns: [
{
title:
@@ -63,7 +63,8 @@ export default {
responsive: 0,
},
{
title: Vue.computed(() => this.$p.t('ui/bezeichnung')),
title: "placeholder",
titlePhrase: "ui/bezeichnung",
field: "Bezeichnung",
headerFilter: true,
minWidth: 200,
@@ -71,7 +72,8 @@ export default {
responsive: 0,
},
{
title: Vue.computed(() => this.$p.t('lehre/organisationseinheit')),
title: "placeholder",
titlePhrase: "lehre/organisationseinheit",
field: "Organisationseinheit",
headerFilter: true,
minWidth: 200,
@@ -79,7 +81,8 @@ export default {
responsive: 1,
},
{
title: Vue.computed(() => this.$p.t('global/gueltigVon')),
title: "placeholder",
titlePhrase: "global/gueltigVon",
field: "Gültig_von",
headerFilterFunc: 'dates',
headerFilter: dateFilter,
@@ -91,7 +94,8 @@ export default {
responsive: 4,
},
{
title: Vue.computed(() => this.$p.t('global/gueltigBis')),
title: "placeholder",
titlePhrase: "global/gueltigBis",
field: "Gültig_bis",
headerFilterFunc: 'dates',
headerFilter: dateFilter,
@@ -103,7 +107,8 @@ export default {
responsive: 3,
},
{
title: Vue.computed(() => this.$p.t('profil/wochenstunden')),
title: "placeholder",
titlePhrase: "profil/wochenstunden",
field: "Wochenstunden",
headerFilter: true,
minWidth: 200,
@@ -124,6 +129,7 @@ export default {
responsiveLayoutCollapseFormatter: Vue.$collapseFormatter,
data: [{betriebsmittel: "", Nummer: "", Ausgegeben_am: ""}],
responsiveLayoutCollapseStartOpen: false,
locale: true,
columns: [
{
title:
@@ -138,7 +144,8 @@ export default {
responsive: 0,
},
{
title: Vue.computed(() => this.$p.t('profil/entlehnteBetriebsmittel')),
title: "placeholder",
titlePhrase: "profil/entlehnteBetriebsmittel",
field: "betriebsmittel",
headerFilter: true,
minWidth: 200,
@@ -146,7 +153,8 @@ export default {
responsive: 0,
},
{
title: Vue.computed(() => this.$p.t('profil/inventarnummer')),
title: "placeholder",
titlePhrase: "profil/inventarnummer",
field: "Nummer",
headerFilter: true,
resizable: true,
@@ -155,7 +163,8 @@ export default {
responsive: 2,
},
{
title: Vue.computed(() => this.$p.t('profil/ausgabedatum')),
title: "placeholder",
titlePhrase: "profil/ausgabedatum",
field: "Ausgegeben_am",
headerFilterFunc: 'dates',
headerFilter: dateFilter,
@@ -230,10 +239,6 @@ export default {
}
});
},
setTableColumnTitles() { // reevaluates computed phrasen
if(this.$refs.betriebsmittelTable) this.$refs.betriebsmittelTable.tabulator.setColumns(this.betriebsmittel_table_options.columns);
if(this.$refs.funktionenTable) this.$refs.funktionenTable.tabulator.setColumns(this.funktionen_table_options.columns);
},
datetimeFormatterParams: function() {
const params = {
inputFormat:"yyyy-MM-dd",
@@ -316,10 +321,6 @@ export default {
},
created() {
// preload phrasen
this.$p.loadCategory(["ui","lehre","global","profil"]).then(() => {
this.arePhrasesPreloaded = true;
});
//? sorts the profil Updates: pending -> accepted -> rejected
this.data.profilUpdates?.sort(this.sortProfilUpdates);
},
@@ -330,9 +331,6 @@ export default {
'data.mittel'(newVal) {
if(this.$refs.betriebsmittelTable) this.$refs.betriebsmittelTable.tabulator.setData(newVal);
},
'language.value'(newVal) {
this.setTableColumnTitles()
}
},
template: /*html*/ `
<div class="container-fluid text-break fhc-form" >
@@ -454,7 +452,6 @@ export default {
<!-- FUNKTIONEN TABELLE -->
<div class="col-12 mb-4" >
<core-filter-cmpt
v-if="arePhrasesPreloaded"
@tableBuilt="funktionenTableBuilt"
:title="$p.t('person','funktionen')"
ref="funktionenTable"
@@ -466,7 +463,6 @@ export default {
<!-- BETRIEBSMITTEL TABELLE -->
<div class="col-12 mb-4" >
<core-filter-cmpt
v-if="arePhrasesPreloaded"
@tableBuilt="betriebsmittelTableBuilt"
:title="$p.t('profil','entlehnteBetriebsmittel')"
ref="betriebsmittelTable"
@@ -20,7 +20,6 @@ export default {
data() {
return {
collapseIconFunktionen: true,
arePhrasesPreloaded: false,
funktionen_table_options: {
persistenceID: "filterTableMaViewProfilFunktionen",
persistence: {
@@ -31,6 +30,7 @@ export default {
responsiveLayout: "collapse",
responsiveLayoutCollapseUseFormatters: false,
responsiveLayoutCollapseFormatter: Vue.$collapseFormatter,
locale: true,
columns: [
//? option when wanting to hide the collapsed list
@@ -45,25 +45,24 @@ export default {
visible: true,
},
{
title: Vue.computed(() => this.$p.t("ui/bezeichnung")),
title: "placeholder",
titlePhrase: "ui/bezeichnung",
field: "Bezeichnung",
headerFilter: true,
minWidth: 200,
visible: true,
},
{
title: Vue.computed(() =>
this.$p.t("lehre/organisationseinheit"),
),
title: "placeholder",
titlePhrase: "lehre/organisationseinheit",
field: "Organisationseinheit",
headerFilter: true,
minWidth: 200,
visible: true,
},
{
title: Vue.computed(() =>
this.$p.t("global/gueltigVon"),
),
title: "placeholder",
titlePhrase: "global/gueltigVon",
field: "Gültig_von",
headerFilterFunc: "dates",
headerFilter: dateFilter,
@@ -74,9 +73,8 @@ export default {
formatterParams: this.datetimeFormatterParams(),
},
{
title: Vue.computed(() =>
this.$p.t("global/gueltigBis"),
),
title: "placeholder",
titlePhrase: "global/gueltigBis",
field: "Gültig_bis",
headerFilterFunc: "dates",
headerFilter: dateFilter,
@@ -87,9 +85,8 @@ export default {
formatterParams: this.datetimeFormatterParams(),
},
{
title: Vue.computed(() =>
this.$p.t("profil/wochenstunden"),
),
title: "placeholder",
titlePhrase: "profil/wochenstunden",
field: "Wochenstunden",
headerFilter: true,
minWidth: 200,
@@ -214,11 +211,6 @@ export default {
return quickLinks;
},
},
created() {
this.$p.loadCategory(["ui", "lehre", "global", "profil"]).then(() => {
this.arePhrasesPreloaded = true;
});
},
template: /*html*/ `
@@ -273,7 +265,7 @@ export default {
<div class="row">
<!-- FIRST TABLE -->
<div class="col-12 mb-4" >
<core-filter-cmpt v-if="arePhrasesPreloaded" @tableBuilt="funktionenTableBuilt" :title="$p.t('person','funktionen')" ref="funktionenTable" :tabulator-options="funktionen_table_options" tableOnly :sideMenu="false" />
<core-filter-cmpt @tableBuilt="funktionenTableBuilt" :title="$p.t('person','funktionen')" ref="funktionenTable" :tabulator-options="funktionen_table_options" tableOnly :sideMenu="false" />
</div>
<!-- END OF THE ROW WITH THE TABLES UNDER THE PROFIL INFORMATION -->
</div>
@@ -29,13 +29,12 @@ export default {
QuickLinks,
CalendarSync,
},
inject: ["sortProfilUpdates", "collapseFunction", "language","isEditable"],
inject: ["sortProfilUpdates", "collapseFunction", "isEditable"],
data() {
return {
showModal: false,
collapseIconBetriebsmittel: true,
editDataFilter: null,
arePhrasesPreloaded: false,
// tabulator options
zutrittsgruppen_table_options: {
persistenceID: "filterTableStudentProfilZutrittsgruppen",
@@ -44,9 +43,9 @@ export default {
},
minHeight: 200,
layout: "fitColumns",
columns: [
{
title: Vue.computed(() => this.$p.t('profil/zutrittsGruppen')),
columns: [{
title: "placeholder",
titlePhrase: "profil/zutrittsGruppen",
field: "bezeichnung"
}
],
@@ -62,6 +61,7 @@ export default {
responsiveLayoutCollapseUseFormatters: false,
responsiveLayoutCollapseFormatter: Vue.$collapseFormatter,
responsiveLayoutCollapseStartOpen: false,
locale: true,
columns: [
{
title:
@@ -75,7 +75,8 @@ export default {
responsive: 0,
},
{
title: Vue.computed(()=>this.$p.t('profil/entlehnteBetriebsmittel')),
title: "placeholder",
titlePhrase: "profil/entlehnteBetriebsmittel",
field: "betriebsmittel",
headerFilter: true,
minWidth: 200,
@@ -83,7 +84,8 @@ export default {
responsive: 0,
},
{
title: Vue.computed(() => this.$p.t('profil/inventarnummer')) ,
title: "placeholder",
titlePhrase: "profil/inventarnummer",
field: "Nummer",
headerFilter: true,
resizable: true,
@@ -92,7 +94,8 @@ export default {
responsive: 2,
},
{
title: Vue.computed(() => this.$p.t('profil/ausgabedatum')) ,
title: "placeholder",
titlePhrase: "profil/ausgabedatum",
field: "Ausgegeben_am",
headerFilterFunc: 'dates',
headerFilter: dateFilter,
@@ -259,19 +262,9 @@ export default {
},
},
created() {
// preload phrasen
this.$p.loadCategory('profil').then(() => {
this.arePhrasesPreloaded = true;
});
//? sorts the profil Updates: pending -> accepted -> rejected
this.data.profilUpdates?.sort(this.sortProfilUpdates);
},
watch: {
'language.value'(newVal) {
if(this.$refs.betriebsmittelTable) this.$refs.betriebsmittelTable.tabulator.setColumns(this.betriebsmittel_table_options.columns)
if(this.$refs.zutrittsgruppenTable) this.$refs.zutrittsgruppenTable.tabulator.setColumns(this.zutrittsgruppen_table_options.columns)
}
},
template: /*html*/ `
<div class="container-fluid text-break fhc-form">
<edit-profil v-if="showModal" ref="editModal" @hideBsModal="hideEditProfilModal"
@@ -395,7 +388,6 @@ export default {
<div class="row">
<div class="col-12 mb-4" >
<core-filter-cmpt
v-if="arePhrasesPreloaded"
@tableBuilt="betriebsmittelTableBuilt"
:title="$p.t('profil','entlehnteBetriebsmittel')"
ref="betriebsmittelTable"
@@ -405,7 +397,6 @@ export default {
</div>
<div class="col-12 mb-4" >
<core-filter-cmpt
v-if="arePhrasesPreloaded"
@tableBuilt="zutrittsgruppenTableBuilt"
:title="$p.t('profil','zutrittsGruppen')"
ref="zutrittsgruppenTable"
@@ -551,6 +551,7 @@ export default {
container-class="mb-3 col-3"
type="DatePicker"
v-model="formData.datum_von"
model-type="yyyy-MM-dd"
name="datum_von"
:label="$p.t('ui/from') + ' *'"
auto-apply
@@ -566,6 +567,7 @@ export default {
container-class="mb-3 col-3"
type="DatePicker"
v-model="formData.datum_bis"
model-type="yyyy-MM-dd"
name="datum_bis"
:label="$p.t('global/bis')"
auto-apply
@@ -470,6 +470,7 @@ export default {
:label="$p.t('admission', 'anmeldundRtAm')"
type="DatePicker"
v-model="formData.anmeldedatum"
model-type="yyyy-MM-dd"
auto-apply
:enable-time-picker="false"
text-input
@@ -293,6 +293,7 @@ export default {
:label="$p.t('person', 'geburtsdatum')"
type="DatePicker"
v-model="data.gebdatum"
model-type="yyyy-MM-dd"
name="gebdatum"
:clearable="false"
no-today
@@ -103,6 +103,7 @@ export default {
<form-input
type="DatePicker"
v-model="formData.nachgereicht_am"
model-type="yyyy-MM-dd"
name="nachgereicht_am"
:label="$p.t('dokumente/nachreichungAm')"
auto-apply
@@ -27,7 +27,7 @@ export default {
return {
filter: false,
studiengang_kz: false,
counterdate: new Date()
counterdate: luxon.DateTime.now().setZone(FHC_JS_DATA_STORAGE_OBJECT.timezone).toISODate()
};
},
computed: {
@@ -259,6 +259,7 @@ export default {
<form-input
type="DatePicker"
v-model="counterdate"
model-type="yyyy-MM-dd"
input-group
:enable-time-picker="false"
text-input
@@ -79,6 +79,7 @@ export default {
<form-input
type="DatePicker"
v-model="data.buchungsdatum"
model-type="yyyy-MM-dd"
name="buchungsdatum"
:label="$p.t('konto/buchungsdatum')"
:enable-time-picker="false"
@@ -88,7 +88,7 @@ export default {
this.data = {
buchungstyp_kurzbz: '',
betrag: '-0.00',
buchungsdatum: new Date(),
buchungsdatum: luxon.DateTime.now().setZone(FHC_JS_DATA_STORAGE_OBJECT.timezone).toISODate(),
buchungstext: '',
mahnspanne: 30,
studiensemester_kurzbz: this.currentSemester,
@@ -154,6 +154,7 @@ export default {
<form-input
type="DatePicker"
v-model="data.buchungsdatum"
model-type="yyyy-MM-dd"
name="buchungsdatum"
:label="$p.t('konto/buchungsdatum')"
:enable-time-picker="false"
@@ -467,6 +467,7 @@ export default {
:label="$p.t('ui', 'von')"
type="DatePicker"
v-model="formData.von"
model-type="yyyy-MM-dd"
auto-apply
:enable-time-picker="false"
text-input
@@ -501,6 +502,7 @@ export default {
:label="$p.t('global', 'bis')"
type="DatePicker"
v-model="formData.bis"
model-type="yyyy-MM-dd"
auto-apply
:enable-time-picker="false"
text-input
@@ -325,6 +325,7 @@ export default {
:label="$p.t('infocenter', 'zgvDatum')"
type="DatePicker"
v-model="data.zgvdatum"
model-type="yyyy-MM-dd"
name="zgvdatum"
no-today
auto-apply
@@ -389,6 +390,7 @@ export default {
:label="$p.t('lehre', 'zgvMasterDatum')"
type="DatePicker"
v-model="data.zgvmadatum"
model-type="yyyy-MM-dd"
name="zgvmadatum"
no-today
auto-apply
@@ -454,6 +456,7 @@ export default {
:label="$p.t('lehre', 'zgvDoktorDatum')"
type="DatePicker"
v-model="data.zgvdoktordatum"
model-type="yyyy-MM-dd"
name="zgvdoktordatum"
no-today
auto-apply
@@ -121,7 +121,7 @@ export default {
layoutColumnsOnNewData: false,
height: 'auto',
minHeight: '100',
selectableRows: true,
selectableRows: 1,
index: 'betreuer_id',
persistence:{
columns: true, //persist column layout
@@ -276,6 +276,7 @@ export default{
container-class="mb-3"
type="DatePicker"
v-model="formData.datum"
model-type="yyyy-MM-dd"
name="datum"
:label="$p.t('global/datum')"
auto-apply
@@ -291,6 +292,7 @@ export default{
container-class="mb-3"
type="DatePicker"
v-model="formData.bestaetigtam"
model-type="yyyy-MM-dd"
name="bestaetigtam"
:label="$p.t('lehre/bestaetigt_am')"
auto-apply
@@ -306,13 +308,14 @@ export default{
container-class="mb-3"
type="DatePicker"
v-model="formData.bewerbung_abgeschicktamum"
model-type="yyyy-MM-dd HH:mm:ss"
name="bewerbung_abgeschicktamum"
:label="$p.t('lehre/bewerbung_abgeschickt_am')"
auto-apply
:enable-time-picker="false"
format="dd.MM.yyyy"
:enable-time-picker="true"
format="dd.MM.yyyy HH:mm:ss"
text-input
preview-format="dd.MM.yyyy"
preview-format="dd.MM.yyyy HH:mm:ss"
:teleport="true"
:disabled="bisLocked || !hasPrestudentstatusPermission"
>
@@ -285,6 +285,7 @@ export default {
uid="stv-list-new-gebdatum"
name="gebdatum"
v-model="formDataPerson['gebdatum']"
model-type="yyyy-MM-dd"
:disabled="!!person"
@update:model-value="loadSuggestions"
text-input
+205 -31
View File
@@ -22,8 +22,14 @@ import TableDownload from './Table/Download.js';
import collapseAutoClose from '../../directives/collapseAutoClose.js';
import moduleLayoutFitDataStretchFrozen from '../../tabulator/layouts/fitDataStretchFrozen.js';
import InternalToExternalEventBroadcastModule from "../../tabulator/customModules/InternalToExternalEventBroadcastModule.js"
import MenuExtensionModule from "../../tabulator/customModules/MenuExtensionModule.js"
import ResponsiveLayoutExtensionModule from "../../tabulator/customModules/ResponsiveLayoutExtensionModule.js"
import { debounce } from "../../helpers/DebounceHelper.js";
import ApiFilter from '../../api/factory/filter.js';
import ApiPhrases from '../../api/factory/phrasen.js';
//
const FILTER_COMPONENT_NEW_FILTER = 'Filter Component New Filter';
@@ -84,8 +90,9 @@ export const CoreFilterCmpt = {
useSelectionSpan: {
type: Boolean,
default: true
}
},
},
inject: ["language"],
data: function() {
return {
uuid: 0,
@@ -120,7 +127,8 @@ export const CoreFilterCmpt = {
headerFilter: false,
group: false,
page: false,
}
},
collapsedHeadingLocalizationTimer: null,
};
},
computed: {
@@ -183,20 +191,25 @@ export const CoreFilterCmpt = {
return columns;
},
fieldIdsForVisibilty() {
if (!this.tableBuilt)
return [];
return this.tabulator.getColumns().filter(col => {
let def = col.getDefinition();
return !def.frozen && def.title && def.formatter != "responsiveCollapse";
}).map(col => col.getField());
},
fieldNames() {
if (!this.tableBuilt)
return {};
return this.tabulator.getColumns().reduce((res, col) => {
res[col.getField()] = col.getDefinition().title;
return res;
}, {});
if (!this.tableBuilt) return [];
const localizedColumnTitles = this.tabulator.getLang().columns;
const isTabulatorLocalized = !!this.$props.tabulatorOptions.locale;
return this.tabulator
.getColumns()
.filter((col) => {
let def = col.getDefinition();
let title =
isTabulatorLocalized && localizedColumnTitles[def.field]
? localizedColumnTitles[def.field]
: def.title;
return (
!def.frozen &&
title &&
def.formatter != "responsiveCollapse"
);
})
.map((col) => col.getField());
},
idExtra() {
if (!this.uuid)
@@ -204,14 +217,38 @@ export const CoreFilterCmpt = {
return '-' + this.uuid;
},
columnsForFilter() {
if (!this.filteredColumns || !this.datasetMetadata)
return [];
const filterTitles = this.filteredColumns.reduce((a,c) => {
a[c.field] = c.title;
return a;
}, {});
return this.datasetMetadata.map(el => ({...el, ...{title: filterTitles[el.name]}}));
}
if (!this.filteredColumns || !this.datasetMetadata) return [];
const isTabulatorLocalized = !!this.$props.tabulatorOptions.locale;
let filterTitles;
if (isTabulatorLocalized) {
const localizedColumnTitles = this.tabulator.getLang().columns;
filterTitles = this.filteredColumns.reduce((a, c) => {
a[c.field] = localizedColumnTitles[c.field];
return a;
}, {});
} else {
filterTitles = this.filteredColumns.reduce((a, c) => {
a[c.field] = c.title;
return a;
}, {});
}
return this.datasetMetadata.map((el) => ({
...el,
...{ title: filterTitles[el.name] },
}));
},
},
watch: {
"language.value": {
handler(newSelectedLanguage) {
if (!this.$props.tabulatorOptions.locale) return;
this.tabulator.setLocale(newSelectedLanguage);
},
},
},
methods: {
reloadTable() {
@@ -267,6 +304,10 @@ export const CoreFilterCmpt = {
persistence: this.persistence,
}, ...(this.tabulatorOptions || {})};
if (tabulatorOptions.locale) {
tabulatorOptions = await this.configureTabulatorLocalizations(tabulatorOptions);
}
// set default height if no height property is set
if (tabulatorOptions.height === undefined &&
tabulatorOptions.minHeight === undefined &&
@@ -295,10 +336,10 @@ export const CoreFilterCmpt = {
}
// Start the tabulator with the build options
this.tabulator = new Tabulator(
this.$refs.table,
tabulatorOptions
);
this.tabulator = new Tabulator(this.$refs.table, {
...tabulatorOptions,
debugInvalidOptions: false,
});
// If event handlers have been provided
if (Array.isArray(this.tabulatorEvents) && this.tabulatorEvents.length > 0)
{
@@ -306,7 +347,10 @@ export const CoreFilterCmpt = {
for (let evt of this.tabulatorEvents)
this.tabulator.on(evt.event, evt.handler);
}
this.tabulator.on('tableBuilt', () => {this.tableBuilt = true; this.$emit('tableBuilt');});
this.tabulator.on('tableBuilt', () => {
this.tableBuilt = true;
this.$emit('tableBuilt');
});
this.tabulator.on("rowSelectionChanged", data => {
this.selectedData = data;
});
@@ -352,6 +396,121 @@ export const CoreFilterCmpt = {
this.filterActive = filters.length > 0;
this.$emit("headerFilterOn", this.filterActive);
});
this.tabulator.on("localized", () => {
this.tabulator.modules.responsiveLayout.generateCollapsedContent();
});
},
async configureTabulatorLocalizations(tabulatorOptions) {
tabulatorOptions.locale = this.$p.user_language.value;
tabulatorOptions.langs = {};
const genericTabulatorPhrasesResponse = await this.$api.call(
ApiPhrases.getTabulatorPhrases(),
);
const genericTabulatorPhrasesData = genericTabulatorPhrasesResponse.data;
Object.keys(genericTabulatorPhrasesData).forEach((language) => {
let genericTabulatorPhraseToTranslationMapper = {};
genericTabulatorPhrasesData[language].forEach((phrase) => {
genericTabulatorPhraseToTranslationMapper[phrase.phrase] = phrase.text;
});
tabulatorOptions.langs[language] = {
data: {
loading: genericTabulatorPhraseToTranslationMapper["loading"],
error: genericTabulatorPhraseToTranslationMapper["error"],
},
groups: {
item: genericTabulatorPhraseToTranslationMapper["item"],
items: genericTabulatorPhraseToTranslationMapper["items"],
},
pagination: {
page_size:
genericTabulatorPhraseToTranslationMapper["page_size"],
page_title:
genericTabulatorPhraseToTranslationMapper["page_title"],
first: genericTabulatorPhraseToTranslationMapper["first"],
first_title:
genericTabulatorPhraseToTranslationMapper["first_title"],
last: genericTabulatorPhraseToTranslationMapper["last"],
last_title:
genericTabulatorPhraseToTranslationMapper["last_title"],
prev: genericTabulatorPhraseToTranslationMapper["prev"],
prev_title:
genericTabulatorPhraseToTranslationMapper["prev_title"],
next: genericTabulatorPhraseToTranslationMapper["next"],
next_title:
genericTabulatorPhraseToTranslationMapper["next_title"],
all: genericTabulatorPhraseToTranslationMapper["all"],
counter: {
showing:
genericTabulatorPhraseToTranslationMapper["showing"],
of: genericTabulatorPhraseToTranslationMapper["of"],
rows: genericTabulatorPhraseToTranslationMapper["rows"],
pages: genericTabulatorPhraseToTranslationMapper["pages"],
},
},
};
});
let phrasesGroupedByCategoryRequestParam = {};
tabulatorOptions.columns.forEach((column) => {
if (!column.titlePhrase?.length) return;
let [category, phrase] = column.titlePhrase.split("/");
if (phrasesGroupedByCategoryRequestParam[category]) {
phrasesGroupedByCategoryRequestParam[category].push(phrase);
} else {
phrasesGroupedByCategoryRequestParam[category] = [phrase];
}
});
tabulatorOptions.menuItemPhrases?.forEach((menuItemPhrase) => {
let [category, phrase] = menuItemPhrase.split("/");
if (phrasesGroupedByCategoryRequestParam[category]) {
phrasesGroupedByCategoryRequestParam[category].push(phrase);
} else {
phrasesGroupedByCategoryRequestParam[category] = [phrase];
}
});
if (!Object.keys(phrasesGroupedByCategoryRequestParam).length) {
return tabulatorOptions;
}
const phrasesResponse = await this.$api.call(
ApiPhrases.getPhrases(phrasesGroupedByCategoryRequestParam),
);
const phrasesData = phrasesResponse.data;
Object.keys(phrasesData).forEach((language) => {
let phraseToTranslationMapper = {};
phrasesData[language].forEach((phrase) => {
phraseToTranslationMapper[
phrase.category + "/" + phrase.phrase
] = phrase.text;
});
let columnFieldToTranslationMapper = {};
tabulatorOptions.columns.forEach((column) => {
if (!column.titlePhrase?.length) return;
columnFieldToTranslationMapper[column.field] =
phraseToTranslationMapper[column.titlePhrase] ??
"<< PHRASE " + column.titlePhrase + " >>";
});
tabulatorOptions.langs[language].columns =
columnFieldToTranslationMapper;
let menuItemPhraseToTranslationMapper = {};
tabulatorOptions.menuItemPhrases?.forEach((menuItemPhrase) => {
menuItemPhraseToTranslationMapper[menuItemPhrase] =
phraseToTranslationMapper[menuItemPhrase] ??
"<< PHRASE " + menuItemPhrase + " >>";
});
tabulatorOptions.langs[language].menuItems =
menuItemPhraseToTranslationMapper;
});
return tabulatorOptions;
},
updateTabulator() {
if (this.tabulator) {
@@ -659,7 +818,18 @@ export const CoreFilterCmpt = {
// parent not found
return false;
}
},
getColumnNames() {
if (!this.tableBuilt) {
return {};
} else if (this.tabulator.options.locale) {
return this.tabulator.getLang().columns;
}
return this.tabulator.getColumns().reduce((res, col) => {
res[col.getField()] = col.getDefinition().title;
return res;
}, {});
},
},
beforeCreate() {
if (!this.tableOnly == !this.filterType)
@@ -670,6 +840,10 @@ export const CoreFilterCmpt = {
alert('"nwNewEntry" listener is mandatory when sideMenu is true');
this.uuid = _uuid++;
this.$emit('uuidDefined', this.uuid)
Tabulator.registerModule(InternalToExternalEventBroadcastModule);
Tabulator.registerModule(MenuExtensionModule);
Tabulator.registerModule(ResponsiveLayoutExtensionModule);
},
mounted() {
this.initTabulator().then(() => {
@@ -737,7 +911,7 @@ export const CoreFilterCmpt = {
:data-bs-parent="'#filterCollapsables' + idExtra"
:fields="fieldIdsForVisibilty"
:selected="selectedFields"
:names="fieldNames"
:names="getColumnNames()"
@hide="tabulator.hideColumn($event)"
@show="tabulator.showColumn($event)"
v-collapse-auto-close
@@ -42,7 +42,7 @@ export default {
watch: {
selected(n) {
this.selectedFields = n;
}
},
},
methods: {
toggle(field) {
+103 -105
View File
@@ -8,6 +8,7 @@ import dms from "./result/dms.js";
import cms from "./result/cms.js";
import mergedStudent from "./result/mergedstudent.js";
import mergedPerson from "./result/mergedperson.js";
import { debounce } from "../../helpers/DebounceHelper.js";
export default {
name: "FhcSearchbar",
@@ -57,6 +58,107 @@ export default {
settingsDropdown: null,
lastQuery: "",
isSearchShownInMobileView: false,
callsearchapi: debounce(() => {
this.error = null;
this.searchresult.splice(0, this.searchresult.length);
this.searching = true;
this.showsearchresult();
if (this.searchsettings.types.length === 0) {
this.error = this.$p.t("search/error_missing_type");
this.searching = false;
return;
}
if (this.abortController) this.abortController.abort();
if (!this.searchsettings.searchstr?.length) return;
this.abortController = new AbortController();
this.searchfunction(this.searchsettings, {
timeout: 50000,
signal: this.abortController.signal,
})
.then((response) => {
if (!response.data) {
this.error = this.$p.t("search/error_general");
} else {
let res = response.data.map((el) =>
el.data ? { ...el, ...JSON.parse(el.data) } : el,
);
this.lastQuery = response.meta.searchstring;
if (this.searchoptions.mergeResults) {
let counter = 0;
let mergeTypes = [];
let mergedType = "merged-";
let mergeKey = "";
switch (this.searchoptions.mergeResults) {
case "student":
mergeTypes = ["student", "prestudent"];
mergedType +=
this.searchoptions.mergeResults;
mergeKey = "uid";
break;
case "person":
mergeTypes = [
"person",
"employee",
"student",
"prestudent",
];
mergedType +=
this.searchoptions.mergeResults;
mergeKey = "person_id";
break;
}
if (mergeTypes.length) {
res = Object.values(
res.reduce((a, c) => {
if (!mergeTypes.includes(c.renderer)) {
a["nomerge" + counter++] = c;
} else if (c[mergeKey] === null) {
a["nomerge" + counter++] = c;
} else if (
a[c[mergeKey]] === undefined
) {
a[c[mergeKey]] = {
rank: c.rank,
renderer: mergedType,
type: mergedType,
list: [c],
};
} else {
a[c[mergeKey]].list.push(c);
if (c.rank > a[c[mergeKey]].rank)
a[c[mergeKey]].rank = c.rank;
}
return a;
}, {}),
).sort((a, b) => b.rank - a.rank);
}
}
this.searchresult = res;
this.searchmode = response.meta.mode;
}
this.searching = false;
this.retry = 0;
})
.catch((error) => {
if (error.code == "ERR_CANCELED") {
return (this.retry = 0);
}
if (error.code == "ECONNABORTED" && this.retry) {
this.retry--;
return this.callsearchapi();
}
this.error = this.$p.t("search/error_general", error);
this.searching = false;
this.retry = 0;
});
}, 500),
};
},
computed: {
@@ -345,122 +447,18 @@ export default {
this.abort();
if (this.searchsettings.searchstr.length >= 2) {
this.calcSearchResultExtent();
this.searchtimer = setTimeout(this.callsearchapi, 500);
this.callsearchapi();
} else {
this.showresult = false;
}
},
abort() {
if (this.searchtimer !== null) {
clearTimeout(this.searchtimer);
}
if (this.abortController) {
this.abortController.abort();
this.abortController = null;
}
this.searchresult = [];
},
callsearchapi: function () {
this.error = null;
this.searchresult.splice(0, this.searchresult.length);
this.searching = true;
this.showsearchresult();
if (this.searchsettings.types.length === 0) {
this.error = this.$p.t("search/error_missing_type");
this.searching = false;
return;
}
if (this.abortController) this.abortController.abort();
if (!this.searchsettings.searchstr?.length) return;
this.abortController = new AbortController();
this.searchfunction(this.searchsettings, {
timeout: 50000,
signal: this.abortController.signal,
})
.then((response) => {
if (!response.data) {
this.error = this.$p.t("search/error_general");
} else {
let res = response.data.map((el) =>
el.data ? { ...el, ...JSON.parse(el.data) } : el,
);
this.lastQuery = response.meta.searchstring;
if (this.searchoptions.mergeResults) {
let counter = 0;
let mergeTypes = [];
let mergedType = "merged-";
let mergeKey = "";
switch (this.searchoptions.mergeResults) {
case "student":
mergeTypes = ["student", "prestudent"];
mergedType +=
this.searchoptions.mergeResults;
mergeKey = "uid";
break;
case "person":
mergeTypes = [
"person",
"employee",
"student",
"prestudent",
];
mergedType +=
this.searchoptions.mergeResults;
mergeKey = "person_id";
break;
}
if (mergeTypes.length) {
res = Object.values(
res.reduce((a, c) => {
if (!mergeTypes.includes(c.renderer)) {
a["nomerge" + counter++] = c;
} else if (c[mergeKey] === null) {
a["nomerge" + counter++] = c;
} else if (
a[c[mergeKey]] === undefined
) {
a[c[mergeKey]] = {
rank: c.rank,
renderer: mergedType,
type: mergedType,
list: [c],
};
} else {
a[c[mergeKey]].list.push(c);
if (c.rank > a[c[mergeKey]].rank)
a[c[mergeKey]].rank = c.rank;
}
return a;
}, {}),
).sort((a, b) => b.rank - a.rank);
}
}
this.searchresult = res;
this.searchmode = response.meta.mode;
}
this.searching = false;
this.retry = 0;
})
.catch((error) => {
if (error.code == "ERR_CANCELED") {
return (this.retry = 0);
}
if (error.code == "ECONNABORTED" && this.retry) {
this.retry--;
return this.callsearchapi();
}
this.error = this.$p.t("search/error_general", error);
this.searching = false;
this.retry = 0;
});
},
refreshsearch: function () {
this.search();
this.togglesettings();
+7
View File
@@ -0,0 +1,7 @@
export const debounce = (callback, wait) => {
let timeoutId = null;
return (context) => {
window.clearTimeout(timeoutId);
timeoutId = window.setTimeout(() => callback(), wait);
}
}
+47 -2
View File
@@ -1,4 +1,49 @@
export function capitalize(string) {
if (!string) return '';
if (!string) return "";
return string[0].toUpperCase() + string.slice(1);
}
}
export function convertCase(string, from, to) {
const possibleCases = ["kebab", "snake", "camel"];
if (
from === to ||
!possibleCases.includes(from) ||
!possibleCases.includes(to)
)
return string;
const individualWords =
from === "kebab"
? string.split("-")
: from === "snake"
? string.split("_")
: splitWordsInCamelCase(string);
const formattedIndividualWords = ["snake", "kebab"].includes(to)
? individualWords.map((word) => word.toLowerCase())
: individualWords.map((word, index) => {
if (!index) return word.toLowerCase();
return word.slice(0, 1).toUpperCase() + word.slice(1).toLowerCase();
});
const joinCharacter = to === "snake" ? "_" : to === "kebab" ? "-" : "";
return formattedIndividualWords.join(joinCharacter);
}
function splitWordsInCamelCase(string) {
const lastUpperCaseLetterIndex = string
.split("")
.findLastIndex((character) => {
return character.toUpperCase() === character;
});
if (lastUpperCaseLetterIndex < 1) {
return [string];
}
let splitWords = splitWordsInCamelCase(
string.slice(0, lastUpperCaseLetterIndex),
);
splitWords.push(string.slice(lastUpperCaseLetterIndex));
return splitWords;
}
@@ -0,0 +1,31 @@
// changes within tabulator are often not adequately broadcast to the external component
// this module exposes tabulator's internal events
// if external event name is not specified, the dispatched external event will be the camelCase version of the kebab-case internal event
import Module from "../../../../vendor/olifolkerd/tabulator5/src/js/core/Module.js";
import { convertCase } from "../../helpers/StringHelpers.js";
export default class InternalToExternalEventBroadcastModule extends Module {
static moduleName = "internalToExternalEventBroadcast";
static moduleInitNumber = 1;
constructor(table) {
super(table);
this.eventsToBroadcast = [
// example event
// {
// internal: "layout-refreshed",
// external: null,
// },
];
}
initialize() {
this.eventsToBroadcast.forEach((event) => {
this.subscribe(event.internal, () => {
this.dispatchExternal(event.external ?? convertCase(event.internal, "kebab", "camel"));
});
});
}
}
@@ -0,0 +1,25 @@
import Menu from "../../../../vendor/olifolkerd/tabulator5/src/js/modules/Menu/Menu.js";
export default class MenuExtensionModule extends Menu {
static moduleName = "menu";
static moduleInitNumber = 1;
constructor(table) {
super(table);
}
loadMenu(e, component, menu, parentEl, parentPopup){
const isLocalizationEnabled = component.table.options.locale && component.table.options.locale !== "default";
const menuItemTranslations = component.table.getLang().menuItems;
if (isLocalizationEnabled && menuItemTranslations) {
menu = menu.map((menuItem) => {
if (menuItem.phrase && menuItemTranslations[menuItem.phrase]) {
menuItem.label = menuItemTranslations[menuItem.phrase];
}
return menuItem;
});
}
super.loadMenu(e, component, menu, parentEl, parentPopup);
}
}
@@ -0,0 +1,27 @@
import ResponsiveLayout from "../../../../vendor/olifolkerd/tabulator5/src/js/modules/ResponsiveLayout/ResponsiveLayout.js";
export default class ResponsiveLayoutExtensionModule extends ResponsiveLayout {
static moduleName = "responsiveLayout";
static moduleInitNumber = 1;
constructor(table) {
super(table);
}
generateCollapsedRowData(row) {
let result = super.generateCollapsedRowData(row)
const isLocalizationEnabled = this.table.options.locale && this.table.options.locale !== "default";
const columnHeadingTranslations = this.table?.getLang()?.columns;
if (isLocalizationEnabled && columnHeadingTranslations) {
result = result.map((column) => {
if (columnHeadingTranslations[column.field]) {
column.title = columnHeadingTranslations[column.field];
}
return column;
});
}
return result;
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

+409 -3
View File
@@ -29742,6 +29742,26 @@ array(
)
)
),
array(
'app' => 'core',
'category' => 'profil',
'phrase' => 'alleStudentenVon',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Alle Student*innen aus',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'All students from',
'description' => '',
'insertvon' => 'system'
)
)
),
//Profil Phrasen ende
// LvPlan Phrasen start
array(
@@ -45742,14 +45762,16 @@ array(
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4studentAbgabeNotAllowedRegular',
'phrase' => 'c4studentAbgabeNotAllowedRegularv2',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Verspätete Projektabgabe ist bei Terminen, welche von der Studiengangsassistenz für den gesamten Studiengang fixiert wurden nicht erlaubt!
Um einen Endupload durchführen zu können, müssen Sie ein positiv benotetes Quality Gate 1 & Quality Gate 2 in der relevanten Projektarbeit absolviert haben.",
Um einen Endupload durchführen zu können, müssen Sie ein positiv benotetes Quality Gate 1 & Quality Gate 2 in der relevanten Projektarbeit absolviert haben.
Uploads sind für bereits benotete Projektarbeiten gesperrt. Sollten Sie trotzdem etwas hochladen wollen, wenden Sie sich bitte an Ihre Studiengangsassistenz.",
'description' => '',
'insertvon' => 'system'
),
@@ -45757,7 +45779,9 @@ array(
'sprache' => 'English',
'text' => 'Late project submissions are not permitted for deadlines set by the program assistant for the entire program!
To be able to complete a final upload, you must have successfully completed Quality Gate 1 and Quality Gate 2 for the relevant project work.',
To be able to complete a final upload, you must have successfully completed Quality Gate 1 and Quality Gate 2 for the relevant project work.
Uploads are blocked for already graded project work. If you still wish to upload something, please contact your program assistant.',
'description' => '',
'insertvon' => 'system'
)
@@ -59954,6 +59978,388 @@ I have been informed that I am under no obligation to consent to the transmissio
)
),
// ### End Notenstatistik
// generic tabulator phrases START
array(
'app' => 'core',
'category' => 'tabulator',
'phrase' => 'loading',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Lädt',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Loading',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator',
'phrase' => 'error',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Fehler',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Error',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator',
'phrase' => 'item',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Element',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'item',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator',
'phrase' => 'items',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Elemente',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'items',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator',
'phrase' => 'page_size',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Seitengröße',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Page Size',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator',
'phrase' => 'page_title',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Seite anzeigen',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Show Page',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator',
'phrase' => 'first',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Erste',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'First',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator',
'phrase' => 'first_title',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Erste Seite',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'First Page',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator',
'phrase' => 'last',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Letzte',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Last',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator',
'phrase' => 'last_title',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Letzte Seite',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Last Page',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator',
'phrase' => 'prev',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'zurück',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Previous',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator',
'phrase' => 'prev_title',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'vorherige Seite',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Previous Page',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator',
'phrase' => 'next',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Nächste',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Next',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator',
'phrase' => 'next_title',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Nächste Seite',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Next Page',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator',
'phrase' => 'all',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Alle',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'All',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator',
'phrase' => 'showing',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Angezeigt',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Showing',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator',
'phrase' => 'of',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'von',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'of',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator',
'phrase' => 'rows',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Zeilen',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'rows',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator',
'phrase' => 'pages',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Seiten',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'pages',
'description' => '',
'insertvon' => 'system'
)
)
),
// generic tabulator phrases END
);